前一阵子,研究了一段时间的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。

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

  1. 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作为参数进行传递,那么函数的调用实现模式可以如下

  1. function InvokeRtti(codeptr: Pointer;Params: array of TValue): Integer;overload;
  2. var
  3.   i: Integer;
  4.   type
  5.     tva=record
  6.       case Integer of
  7.       : (vi: Integer);
  8.       : (vb: Boolean);
  9.       : (vc: Char);
  10.       : (vac: AnsiChar);
  11.       : (vf: Single);
  12.       : (vd: DWORD);
  13.     end;
  14.     tpint64dbl = record
  15.       case Integer of
  16.         : (value: int64);
  17.         : (vdouble: Double);
  18.         :
  19.          (
  20.            High: DWORD;
  21.            low: DWORD
  22.          )
  23.     end;
  24. var
  25.   p64: tpint64dbl;
  26.   v: tva;
  27.   tmp: Integer;
  28.   st: string;
  29. begin
  30.   for i := High(Params) downto  do
  31.   begin
  32.     case Params[i].TypeInfo.Kind of
  33.     tkInteger:
  34.       begin
  35.         tmp := Params[i].AsInteger;
  36.         asm
  37.           push tmp
  38.         end;
  39.       end;
  40.     tkChar:
  41.     begin
  42.        v.vac := params[i].AsType<AnsiChar>;
  43.        asm
  44.           push v
  45.        end;
  46.     end;
  47.     tkWChar:
  48.     begin
  49.       v.vc := Params[i].AsType<Char>;
  50.       asm
  51.           push v
  52.       end;
  53.     end;
  54.     tkFloat:
  55.       begin
  56.         v.vf := Params[i].AsType<Single>;
  57.         asm
  58.           push v
  59.         end;
  60.       end;
  61.     tkInt64:
  62.       begin
  63.         p64.value := Params[i].AsInt64;
  64.         asm
  65.           lea ecx,p64
  66.           push [ecx][]  //先压低字节,再压高字节
  67.           push [ecx][]
  68.         end;
  69.       end;
  70.     tkRecord,tkClass:
  71.       begin
  72.         tmp := Integer(Params[i].GetReferenceToRawData);
  73.         asm
  74.           push tmp
  75.         end;
  76.       end;
  77.     tkString,tkUString:
  78.       begin
  79.         st := Params[i].AsString;
  80.         tmp := Integer(PChar(st));
  81.         asm
  82.           push tmp
  83.         end;
  84.       end;
  85.     end;
  86.   end;
  87.   tmp := Integer(codeptr);
  88.   asm
  89.     call tmp
  90.     mov  result,eax
  91.   end;

end;

使用方法

  1. := LoadLibrary('user32.dll');
  2.   if h <>  then
  3.     fh := GetProcAddress(h,'MessageBoxW'); 
  4.  if fh <> nil then
  5.   begin
  6.     InvokeRtti(fh,[TValue.From(Handle),'asf','',])

end;

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

  1. function Invoke(codeptr: Pointer;Params: array of Variant): Integer;overload;
  2. var
  3.   i: Integer;
  4.   type
  5.     tva=record
  6.       case Integer of
  7.       : (vi: Integer);
  8.       : (vb: Boolean);
  9.       : (vc: Char);
  10.       : (vac: AnsiChar);
  11.       : (vf: Single);
  12.       : (vd: DWORD);
  13.     end;
  14.     tpint64dbl = record
  15.       case Integer of
  16.         : (value: int64);
  17.         : (vdouble: Double);
  18.         :
  19.          (
  20.            High: DWORD;
  21.            low: DWORD
  22.          )
  23.     end;
  24. var
  25.   p64: tpint64dbl;
  26.   v: tva;
  27.   tmp: Integer;
  28.   st: string;
  29.   ast: AnsiString;
  30.   vtype: TVarType;
  31. begin
  32.   for i := High(Params) downto  do
  33.   begin
  34.     vtype := VarType(Params[i]);
  35.     case vtype of
  36.     varUString:
  37.       begin
  38.         st := Params[i];
  39.         tmp := Integer(PChar(st));
  40.         asm
  41.           push tmp
  42.         end;
  43.       end;
  44.     varString:
  45.       begin
  46.         st := Params[i];
  47.         ast := AnsiString(st);
  48.         tmp := Integer(PAnsiChar(ast));
  49.         asm
  50.           push tmp
  51.         end;
  52.       end;
  53.     varInteger,varSmallint,varWord,varByte:
  54.       begin
  55.         v.vi := Params[i];
  56.         asm
  57.           push v
  58.         end;
  59.       end;
  60.     varLongWord:
  61.       begin
  62.         v.vd := Params[i];
  63.         asm
  64.           push v
  65.         end;
  66.       end;
  67.     varSingle:
  68.       begin
  69.         v.vf := Params[i];
  70.         asm
  71.           push v
  72.         end;
  73.       end;
  74.     varDouble:
  75.       begin
  76.         p64.vdouble := Params[i];
  77.         asm
  78.           lea ecx,p64
  79.           push [ecx][]  //先压低字节,再压高字节
  80.           push [ecx][]
  81.         end;
  82.       end;
  83.     varInt64:
  84.       begin
  85.         p64.value := Params[i];
  86.         asm
  87.           lea ecx,p64
  88.           push [ecx][]  //先压低字节,再压高字节
  89.           push [ecx][]
  90.         end;
  91.       end;
  92.     end;
  93.   end;
  94.   tmp := Integer(codeptr);
  95.   asm
  96.     call tmp
  97.     mov  result,eax
  98.   end;

end;

用法类似如下:

  1. procedure Showmsg(msg: string;tmp: Double); stdcall;
  2. begin
  3.   ShowMessage(msg+floattostr(tmp));
  4. end;
  5.  
  6. var
  7.   tmp: Single;
  8. begin
  9.   tmp := 13234.24;
  10.   Invoke(@Showmsg,['asfsf',tmp])

end;

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

奇淫怪巧之在Delphi中调用不申明函数的更多相关文章

  1. 你可能不知道的 docker 命令的奇淫怪巧

    你可能不知道的 docker 命令的奇淫怪巧 Intro 介绍并收录一些可能会用到的一些简单实用却很少有人用的 docker 命令 dangling images build 自己的 docker 镜 ...

  2. 如何在Delphi中调用VC6.0开发的COM

    上次写了如何在VC6.0下对Delphi写的COM进行调用,原本想马上写如何在Delphi中调用VC6.0开发的COM时,由于在写事例程序中碰到了个很怪的问题,在我机子上用VC写的接口程序编译能通过. ...

  3. Delphi中unicode转汉字函数(转)

    源:Delphi中unicode转汉字函数 近期用到这个函数,无奈没有找到 delphi 自带的,网上找了下 有类似的,没有现成的,我需要的是 支持 “\u4f00 ” 这种格式的,即前面带标准的 “ ...

  4. [教程]Delphi 中三种回调函数形式解析

    Delphi 支持三种形式的回调函数 全局函数这种方式几乎是所有的语言都支持的,类的静态函数也可以归为此类,它保存的只是一个函数的代码起始地址指针( Pointer ).在 Delphi 中声明一般为 ...

  5. 在VBA中调用工作表函数

    虽然VBA几乎可以完成所有工作表函数的功能,但是有时候在无法打开思路的时候,适当调用一些工作表函数也未尝不可,在VBA中调用工作表函数需要用到 WorksheetFunction对象,例如: Work ...

  6. python利用or在列表解析中调用多个函数.py

    python利用or在列表解析中调用多个函数.py """ python利用or在列表解析中调用多个函数.py 2016年3月15日 05:08:42 codegay & ...

  7. 使用Ajax在javascript中调用后台C#函数

    使用Ajax在javascript中调用后台C#函数 最近一段时间在紧跟一个网站的项目,数据库中用户表的UserName要求是唯一的,所以当用户选定一个用户名进行注册时要首先检查该用户名是否已被占用, ...

  8. 在Delphi中调用"数据链接属性"对话框设置ConnectionString

    项目需要使用"数据链接属性"对话框来设置ConnectionString,查阅了一些资料,解决办法如下: 1.Delphi 在Delphi中比较简单,步骤如下: 方法1: use ...

  9. Delphi 中调用JS文件中的方法

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

随机推荐

  1. 亲测GO环境搭建,理解go build、go install、go get

    GO下载: GO语言中文网下载:https://studygolang.com/dl Mac下直接通过brew instatll go指令即可完成下载安装 GO环境变量配置: $GOROOT=/usr ...

  2. scrollView 刷新显示在中间的问题

    scrollView问题 打开activity之后 屏幕初始位置不是顶部 而是在中间 也就是scroll滚动条不在上面 而是在中间 楼主你好,我大概是和你遇见了同样的问题,你可以灵活处理一下,不要去管 ...

  3. JetBrains Rider 2018.1 汉化

    之前说过了JetBrains系列的破解(最新版本也可以破解)https://www.cnblogs.com/dunitian/p/8478252.html 不少人对全英文的开发环境还是不太适应,那就来 ...

  4. mongodb 字符串查找匹配中$regex的用法

    官网地址:https://docs.mongodb.com/manual/reference/operator/query/regex/#regex-case-insensitive 举个例子来说:现 ...

  5. Android GUI之Activity、Window、View

    相信大家在接触Android之初就已经知道了Activity中的setContentView方法的作用了,很明显此方法是用于为Activity填充相应的布局的.那么,Activity是如何将填充的布局 ...

  6. GeoHash原理和可视化显示

    最近在做附近定位功能的产品,geohash是一个非常不错的实现方式.查询资料,发现阿里的这篇文章讲解的很好.但文中并没有给出geohash显示的工具.无奈,也没有查到类似的.只好自己简单显示一下,方便 ...

  7. NModbus类库使用

    通过串口进行通信 : 1.将 NMobus 类库导入工程中,添加引用.命名空间.工程属性必须配置 为 .NET 4.0. 2.创建 SerialPort 类的一个实例,配置参数,打开串口,如: pub ...

  8. [Functional Programming] From simple implementation to Currying to Partial Application

    Let's say we want to write a most simple implementation 'avg' function: const avg = list => { let ...

  9. 生成springboot docker镜像 并上传到阿里云镜像厂库

    1 mvn package 2 创建Dockerfile ----------------------------------------------------------------------- ...

  10. 微软BI 之SSIS 系列 - 平面文件格式的区别(Delimited,Fixed width,Ragged Right, Fixed width ...)

    开篇介绍 SSIS 中处理文件,一般在描述输出平面文件格式的时候通常会出现以下几种选项: Delimited - 默认输出列使用逗号分隔,也可以选择其它的诸如 | ,或者 Tab 等. Fixed W ...