奇淫怪巧之在Delphi中调用不申明函数

简介:

   前一阵子,研究了一段时间的Win32Asm,研究到后来发现Win32的ASM实际上还是和C版的介绍的一样。甚至还封装了一个简版的类似VCL库结构框架的32ASM结构库,不过搞着搞着就没兴趣了,也没继续往下深入,唉!发现年龄越来越大,人也越来越懒。

  休息了好长一阵子,在乱七八糟的东西乱弄一堆之后,总算发现了一个能有点用处的东西,于是就欣欣然跑来记录一下日志博客以为备份。

  我们都知道在Delphi,VC等这类静态检测形的语言,如果要使用一个函数,必须要先申明一下此函数结构,然后调用的时候,编译器才会根据申明的函数结构进行编译产生数据以做调用。比如MessageBox这类函数,都是在Windows中申明过了,调用的时候可以根据声明来编辑代码。对于使用Delphi这类使用静态预先编译检查的来说,这种方法非常适用简单,但是如果假设,俺们需要自己设计一个脚本语言,然后在这个脚本语言中,我们需要可以调用DLL中的函数,于是我们也给设计一个类似于Delphi的这种预先声明的模式,然后脚本中调用,甚至,还可以不用预先申明,而只用根据参数以及函数名称来调用这个dll中的函数,那么此时我们在脚本中写的东西,我们在Delphi中就并没有预先声明的这个函数结构了。按照常规来调用自然不可行 ,如果说对应每个DLL中的函数给声明一个函数出来外部,这个更是不可能,因为DLL是不可控的,我们并不知道用户用脚本需要调用什么DLL,以及调用什么DLL中的函数。那么此时,我们如何来调用这个DLL中的函数,让脚本中的实现可用呢。我们先来分析一下,首先脚本调用肯定会要知道DLL名称,然后会输入函数名称,然后就是函数参数了,变相来说,就是我们有DLL名可以获得DllHandle,有 DLLHandle和函数名称我们可以获得函数地址CodeAddr。

就是说比如我们设计一个脚本,在脚本中可能输入如下:

dll=DllObject( ' user32.dll ');
dll.MessageBoxW(0, ' asf ' , ' afd ' ,64); 

 

那么我们在Delphi中能获得的就是MessageBoxW这个函数的函数地址以及 传递进入的参数,但是我们并不会声明这个函数。我们现在的目的就是要在Delphi中把这个脚本给解析出来并正确调用MessageBoxW。也就如题说的通过未声明函数的地址调用函数。

实际上任何语言中调用某一个函数,模式都是差不多,无非是传参和调用,而传参又有好多模式,比如Delphi的就有Register,Stdcall,Safecall,cdecl等,C也有这些调用模式,Delphi的Register模式对应C中的fastcall。正是这些传参模式的不同而导致了参数的入栈 方式不一样,Register是用优先通过寄存器,然后才入栈,stdcall和cdecl等都是直接入栈,并且入栈顺序都是参数从右向左入栈,唯一的区别是stdcall是不用自己管理栈函数在调用完成之后会自动清理堆栈空间,而cdecl需要调用者我们自己来清理堆栈空间。SafeCall是stdcall模式中加上了对于Com的异常处理等。我们一般Dll中的传参模式都是stdcall和cdecl,所以,这里就只针对这两个来进行处理。

那么现在的任务就是将参数压入堆栈,然后call一下函数地址就OK了。于是重要的任何就是参数压栈 ,call地址这个是最简单的。

现在就需要来分析一下参数压栈,我们都知道在32位系统中,以4字节为单位进行传输的,所以说基本上传递的参数都是4字节模式,那么我们byte,char,wchar,boolean等类型都需要变成4字节来做传输,int64,double等占据8字节,就需要变成2个4字节进行压栈。所以此时我们就可以设计两个数据结构用来处理转换这些类型

tva=record
      case Integer of
      0: (vi: Integer);
      1: (vb: Boolean);
      2: (vc: Char);
      3: (vac: AnsiChar);
      4: (vf: Single);
      5: (vd: DWORD);
    end;

 

    tpint64dbl = record
      case Integer of
        0: (value: int64);
        1: (vdouble: Double);
        2:
         (
           High: DWORD;
           low: DWORD
         )

    end; 

 tva结构就专门用来将那些低于4字节的参数变成4字节然后进行Push,而 tpint64dbl就是将8字节的Int64和double变成2个4字节进行压栈, tpint64dbl在压栈时,先压low低字节,然后压入High高4字节。于是这些基本参数就可以一一压入堆栈。然后就是一些特殊类型的字段,比如说object,string,以及Record等复杂类型的数据的压栈模式,这个俺们只要懂一点Win32汇编的就可以知道,这些参数的压栈实际上都是偏移地址入栈,所以,取得其地址然后压入堆栈就行了。

那么处理模式可以如下:

如果参数是String,那么就直接 

        st := Params[i];

        tmp := Integer(PChar(st));
        asm
          push tmp
        end;

 

如果是Object,那么在 Delphi中直接就是地址

然后传递参数的模式,我们就可以用for 尾部 downto 0按照规则进行压栈

如果是高版本的Delphi我们可以直接用array of TValue作为参数进行传递,那么函数的调用实现模式可以如下

复制代码
function InvokeRtti(codeptr: Pointer;Params:  array  of TValue): Integer;overload;
var
  i: Integer;
   type
    tva= record
       case Integer  of
       0: (vi: Integer);
       1: (vb: Boolean);
       2: (vc: Char);
       3: (vac: AnsiChar);
       4: (vf: Single);
       5: (vd: DWORD);
     end;
    tpint64dbl =  record
       case Integer  of
         0: (value: int64);
         1: (vdouble: Double);
         2:
         (
           High: DWORD;
           low: DWORD
         )
     end;
var
  p64: tpint64dbl;
  v: tva;
  tmp: Integer;
  st: string;
begin
   for i := High(Params)  downto  0  do
   begin
     case Params[i].TypeInfo.Kind  of
    tkInteger:
       begin
        tmp := Params[i].AsInteger;
        asm
          push tmp
         end;
       end;
    tkChar:
     begin
       v.vac := params[i].AsType<AnsiChar>;
       asm
          push v
        end;
     end;
    tkWChar:
     begin
      v.vc := Params[i].AsType<Char>;
      asm
          push v
       end;
     end;
    tkFloat:
       begin
        v.vf := Params[i].AsType<Single>;
        asm
          push v
         end;
       end;
    tkInt64:
       begin
        p64.value := Params[i].AsInt64;
        asm
          lea ecx,p64
          push [ecx][ 4]  //先压低字节,再压高字节
          push [ecx][ 0]
         end;
       end;
    tkRecord,tkClass:
       begin
        tmp := Integer(Params[i].GetReferenceToRawData);
        asm
          push tmp
         end;
       end;
    tkString,tkUString:
       begin
        st := Params[i].AsString;
        tmp := Integer(PChar(st));
        asm
          push tmp
         end;
       end;
     end;
   end;
  tmp := Integer(codeptr);
  asm
    call tmp
    mov  result,eax
   end;
复制代码

end;

 

使用方法

h := LoadLibrary( ' user32.dll ');
   if h <>  0  then
    fh := GetProcAddress(h, ' MessageBoxW '); 
  if fh <>  nil  then
   begin
    InvokeRtti(fh,[TValue.From(Handle), ' asf ', ' 234 ', 64])

  end;

 

如果是低版本的,可以由Variant入手来实现如下

复制代码
function Invoke(codeptr: Pointer;Params:  array  of Variant): Integer;overload;
var
  i: Integer;
   type
    tva= record
       case Integer  of
       0: (vi: Integer);
       1: (vb: Boolean);
       2: (vc: Char);
       3: (vac: AnsiChar);
       4: (vf: Single);
       5: (vd: DWORD);
     end;
    tpint64dbl =  record
       case Integer  of
         0: (value: int64);
         1: (vdouble: Double);
         2:
         (
           High: DWORD;
           low: DWORD
         )
     end;
var
  p64: tpint64dbl;
  v: tva;
  tmp: Integer;
  st: string;
  ast: AnsiString;
  vtype: TVarType;
begin
   for i := High(Params)  downto  0  do
   begin
    vtype := VarType(Params[i]);
     case vtype  of
    varUString:
       begin
        st := Params[i];
        tmp := Integer(PChar(st));
        asm
          push tmp
         end;
       end;
    varString:
       begin
        st := Params[i];
        ast := AnsiString(st);
        tmp := Integer(PAnsiChar(ast));
        asm
          push tmp
         end;
       end;
    varInteger,varSmallint,varWord,varByte:
       begin
        v.vi := Params[i];
        asm
          push v
         end;
       end;
    varLongWord:
       begin
        v.vd := Params[i];
        asm
          push v
         end;
       end;
    varSingle:
       begin
        v.vf := Params[i];
        asm
          push v
         end;
       end;
    varDouble:
       begin
        p64.vdouble := Params[i];
        asm
          lea ecx,p64
          push [ecx][ 4]  //先压低字节,再压高字节
          push [ecx][ 0]
         end;
       end;
    varInt64:
       begin
        p64.value := Params[i];
        asm
          lea ecx,p64
          push [ecx][ 4]  //先压低字节,再压高字节
          push [ecx][ 0]
         end;
       end;
     end;
   end;
  tmp := Integer(codeptr);
  asm
    call tmp
    mov  result,eax
   end;
复制代码

end

 

用法类似如下:

复制代码
procedure Showmsg(msg: string;tmp: Double); stdcall;
begin
  ShowMessage(msg+floattostr(tmp));
end;

var
  tmp: Single;
begin
  tmp :=  13234.24;
  Invoke(@Showmsg,[ ' asfsf ',tmp])
复制代码

end;

 

至此基本上的功能就完成了,不过此种方法有一个不好的问题,那就是对于调用函数的返回结果,无法预测,返回的都是integer类型,而无法确定返回的结果是地址还是确实就是integer还是说是其他类型。另外,此方法并非全面的一些实现,如果要使用的全面请反汇编参考Delphi对应的函数调用的参数传递过程以对应进行修正。 

 


本文转自 不得闲 博客园博客,原文链接: http://www.cnblogs.com/DxSoft/p/3469228.html  ,如需转载请自行联系原作者


相关文章
|
6月前
|
API C++ Windows
windows编程入门_链接错误的配置
windows编程入门_链接错误的配置
53 0
|
6月前
|
编译器 C语言
嵌入式C 语言函数宏封装妙招
嵌入式C 语言函数宏封装妙招
54 0
|
JavaScript 前端开发
|
C++ Python
C++语言基础 例程 派生类的声明与构成
贺老师的教学链接  本课讲解 派生类 #include &lt;iostream&gt; #include&lt;string&gt; using namespace std; class Student//声明基类Student { public: void sets(int n,string nam,char s); void show( ); protected:
1150 0
|
C语言
《C语言及程序设计》程序阅读——参数传递方式:传值与传地址
返回:贺老师课程教学链接 请分析下面程序的输出结果,对照在计算机上的运行结果,解释得到输出的过程。 建议:通过在纸上画出内存变化的过程,洞悉计算机执行的过程。(1) #include&lt;stdio.h&gt; void fun(int x,int y,int *z) { *z=x*x+y*y; } int main() { int a=31; fun(5,2,
1259 0