详解vb字符串与C/C++动态库的交互

来源:互联网 发布:网络热词及解释 编辑:程序博客网 时间:2024/06/03 17:38

又是崭新的一年,大侠又来雷人了。

 

QQ版本更新以后,偶然翻看一下以前写的自动聊天机器人,居然不适用了!

于是重写了个动态库,为String传递所困扰,于是决定借假期搞翻这颗钉子。

 

之前也写过关于参数传递的文章,不过对字符串的讲述很少,原因是我没亲自测试过,呵呵

先说C/C++的两个函数:

EXPORT_API DWORD __stdcall fnHook(DWORD dwIndex)EXPORT_API DWORD __stdcall fnVarptr(void *dwArg, DWORD dwFlag)


在VB中调用应该是:

Private Declare Function fnHook Lib "Hook.dll" (ByVal dwIndex As Long) As LongPrivate Declare Function fnVarptr Lib "Hook.dll" (ByVal dwPtr As Long, ByVal dwFlag As Long) As LongPrivate Declare Function fnVarptrA Lib "Hook.dll" Alias "fnVarptr" (dwPtr As Any, ByVal dwFlag As Long) As LongPrivate Declare Function fnVarptrS Lib "Hook.dll" Alias "fnVarptr" (dwPtr As String, ByVal dwFlag As Long) As Long


后面两种其实是一样的,不过使用了别名调用,改变了参数的类型。

每声明一个API,vb就会自己写一个函数,这个函数会调用DllFunctionCall得到API函数地址,再次调用则直接转到地址去执行。

下面是VB调用的代码,和调用后在VC中DEBUG得到传递进来的参数值:

Private Sub Form_Load()    '    Call fnHook(-1)    Call FuckEnd SubPublic Sub Fuck()    Dim ret As Long, s As String    s = "abc"   '__vbastrcopy    Call fnHook(0)    ret = fnVarptr(StrPtr(s), 6)    '0x0014eb14 -> 97,0,98,0,99,0,0,0    Call fnHook(1)    ret = fnVarptr(VarPtr(s), 6)    '0x0012fac0 -> 20,235,20,0 = 0x0014eb14    Call fnHook(2)    ret = fnVarptrA(StrPtr(s), 6)   '0x0012fab8 -> 20,235,20,0 = 0x0014eb14    Call fnHook(3)    ret = fnVarptrA(VarPtr(s), 6)   '0x0012fab8 -> 192,250,18,0 = 0x0012fac0    Call fnHook(4)    ret = fnVarptrA(ByVal StrPtr(s), 6) '0x0014eb14 -> 97,0,98,0,99,0,0,0    Call fnHook(5)    ret = fnVarptrA(ByVal VarPtr(s), 6) '0x0012fac0 -> 20,235,20,0 = 0x0014eb14    Call fnHook(6)    ret = fnVarptrA(ByVal s, 6) '0x0014eb3c -> 97,98,99,0,0,0    'ret = fnVarptrA(s, 6) -> fucking no more    Call fnHook(7)    ret = fnVarptrS(ByVal s, 6) '0x0014eb3c -> 97,98,99,0,0,0    Call fnHook(8)    ret = fnVarptrS(s, 6)   '0x0012fabc -> 60,235,20,0 = 0x0014eb3c    Call fnHook(9)End Sub


对于使用来说,到这里就足够了。你足以开发一些操作硬件之类的DLL来给VB或者其他程序调用。

因为上面很清楚,只有fnVarptr(StrPtr(s), 6)和fnVarptrA(ByVal StrPtr(s), 6)得到了abc的Unicode编码

而fnVarptrA(ByVal s, 6)和fnVarptrS(ByVal s, 6)得到的是ANSI编码,这两个根据实际情况使用

 

然而对于技术研究,还不够。我们要把它切开,把里面的东西挖出来。。。

Call fnHook(-1) 就是我们打响战斗的信号弹:

;Call fnHook(-1)00001AE0    push    -100001AE2    call    000018D0


注意,这里000018D0是vb自己写的函数,不是fnHook的地址,不过最终它会跳转到fnHook执行。

执行到返回就是它的主调函数,Form_Load的代码了。本来我想从调用Fuck开始看String的初始化和赋值,可惜啊。

00401AE7   call        dword ptr ds:[401010h]  ;段间调用(N行指令)00401AED   mov         edx,dword ptr [esi]     ;ESI=0014DEC0 未改变00401AEF   push        esi00401AF0   call        dword ptr [edx+6F8h]    ;EDX=7C99B178


这个00401AE7转来转去,1000多条指令还没返回这里。为了减少文章读者中引发精神失常的人数,我又把它们删掉了。

当然他们都是一些无关紧要的东西,完全可以忽略。而最后00401AF0的Call就是调用Fuck函数的。。。

004014D1   jmp         00401B40


这个00401B40就是函数的地址:

;FS寄存器指向当前活动线程的TEB结构(线程结构);偏移 说明;000 指向SEH链指针;下面是OllyIce的跟踪,内存从0x150000开始(VC的DLL模块是自身0x10000)00401B40   push        ebp00401B41   mov         ebp,esp00401B43   sub         esp,0Ch00401B46   push        4010B6h00401B4B   mov         eax,fs:[00000000]       ;eax->0012FB0000401B51   push        eax00401B52   mov         dword ptr fs:[0],esp00401B59   sub         esp,20h00401B5C   push        ebx00401B5D   push        esi00401B5E   push        edi00401B5F   mov         dword ptr [ebp-0Ch],esp00401B62   mov         dword ptr [ebp-8],4010A0h   ;s = 4010A000401B69   xor         esi,esi00401B6B   mov         dword ptr [ebp-4],esi       ;ret = 000401B6E   mov         eax,dword ptr [ebp+8]00401B71   push        eax00401B72   mov         ecx,dword ptr [eax]00401B74   call        dword ptr [ecx+4]        ;MSVBVM60.Zombie_AddRef00401B77   mov         edx,401948h              ;UNICODE "abc"00401B7C   lea         ecx,[ebp-1Ch]            ;地址传送00401B7F   mov         dword ptr [ebp-1Ch],esi  ;*12fac0 = 000401B82   mov         dword ptr [ebp-20h],esi  ;*12fabc = 000401B85   mov         dword ptr [ebp-24h],esi  ;*12fab8 = 000401B88   call        dword ptr ds:[401068h]   ;MSVBVM60.__vbaStrCopy;执行后EAX=15EB14(Unicode'abc'),EDX=6;ESI=0不变;Call fnHook(0)00401B8E   push        esi00401B8F   call        004018D0


我们看到,vb自己又定义了三个指针。

EBP是堆栈指针,调用后因mov ebp,esp变为栈底,而sub esp,xx使esp仍然是栈顶。

加入定义的局部变量都是32位,如DWORD,或者vb的Long等,那么:

EBP - 4 是第一个局部变量

EBP - 8 是第二个局部变量

EBP 调用前的栈顶

EBP + 4 返回值地址

EBP + 8 第一个参数

另外VC对函数的调用也有一些约定:

; 调用约定   堆栈清除 参数传递
; __cdecl    调用者   从右到左,通过堆栈传递
; __stdcall  函数体   从右到左,通过堆栈传递
; __fastcall 函数体   从右到左,优先使用寄存器(ECX,EDX),然后使用堆栈
; thiscall   函数体   this指针默认通过ECX传递,其他参数从右到左入栈

那么,vb自己定义的这三个指针,等下就告诉你。我们先跟00401B8F这个Call进去看看它都干了什么坏事:

;api -> fnHook004018D0   mov         eax,[004032DC]004018D5   or          eax,eax      ;if(eax == 0) goto 004018DB -> pointer != null004018D7   je          004018DB004018D9   jmp         eax          ;1000100F -> jumped004018DB   push        4018B8h      ;*4018B8 = 004018A0, *004018A0 = "Hook.dll"004018E0   mov         eax,401140h  ;*401140 = jmp dword ptr [401030], *401030=7339a0e5(MSvbVM60.DllFunctionCall)004018E5   call        eax          ;调用DllFunctionCall,执行LoadLibray之类,而后得到DLL函数地址004018E7   jmp         eax          ;跳到函数地址开始执行第一次


元芳,DllFunctionCall的真相居然是这样!这里1000100F就是vb给API函数分配的一个标签。

;@ILT+10(?fnHook@@YGKK@Z):1000100F   jmp         fnHook (10001070); source code optimized


其实标签放的是一条Jump指令,哎,跟女人一样啰嗦。。。

执行到返回,自然是调用fnVarPtr函数,但是函数的调用是从准备参数开始的:

00401B94   mov         esi,dword ptr ds:[401010h]  ;MSvbVM60.__vbaSetSystemError00401B9A   call        esi                         ;00401B9C   mov         edx,dword ptr [ebp-1Ch]00401B9F   mov         edi,dword ptr ds:[401058h]  ;MSvbVM60.VarPtr00401BA5   push        edx00401BA6   call        edi                         ;Call VarPtr


前面两条指令是SetLastError的封装,不管它(它是为什么vb总是能报错,而C/C++直接崩溃的原因)。
这里它把[ebp-1Ch]这个地址(就是前面说的vb自己定义的变量),入栈,调用VarPtr,就是VarPtr([ebp-1Ch])!

7346DCE5   mov         eax,dword ptr [esp+4]       ;前面push x,*(esp + 4) = x7346DCE9   ret         4

 

这才是VarPtr的庐山真面目!
在VC中,寄存器EAX和EDX是作为返回值的,所以执行后eax就是结果。紧接着自然是调用函数了:

;ret = fnVarptr(StrPtr(s), 6)00401BA8   push        600401BAA   push        eax00401BAB   call        00401914    ;api -> fnVarptr 类似fnHook的调用


这个00401BAB的Call与调用fnHook的过程是一样的,vb给每个声明的API都自己写了个函数:

;api -> fnVarptr00401914   mov         eax,[004032E8]00401919   or          eax,eax0040191B   je          0040191F0040191D   jmp         eax0040191F   push        4018FCh00401924   mov         eax,401140h00401929   call        eax0040192B   jmp         eax


在je那里就返回了,看完上面的上面的汇编,你懂的。

然后是取得返回值,接着vb有自己去检测异常了,汗!

;ret = fnVarptr(...)的返回值00401BB0   mov         dword ptr [ebp-24h],eax00401BB3   call        esi        ;esi=7345C195, *7345C195 = ntdll.RtlGetLastWin32Error;Call fnHook(1)00401BB5   push        100401BB7   call        004018D000401BBC   call        esi


跟着是第二次调用:

;ret = fnVarptr(VarPtr(s), 6) -> 因此,fnVarPtr得到的是s的地址的地址00401BBE   mov         ebx,dword ptr ds:[401058h]00401BC4   lea         eax,[ebp-1Ch]    ;对比StrPtr:mov edx,dword ptr [ebp-1Ch](__cdecl方式)00401BC7   push        eax00401BC8   call        ebx              ;Call VarPtr00401BCA   push        600401BCC   push        eax00401BCD   call        0040191400401BD2   mov         dword ptr [ebp-24h],eax00401BD5   call        esi00401BD7   push        200401BD9   call        004018D0


不难看出,StrPtr是把String的地址传递给VarPtr,相当于String是char* s,再VarPtr就是&s了。

当然,第二次调用是不能正常传递字符串(指针)的了,而是传递了字符串(指针)的地址。

从vb的源码可以看出:

ret = fnVarptr(VarPtr(s), 6)


执行后fnVarPtr得到的是0x0012fac0,用它作为指针的地址得到四个字节 20,235,20,0 就是 0x0014eb14 高高低低放置的结果。

而0x0014eb14是字符串的指针,也就是字符数据的内存地址,就是String啦!

注:用OD或OllyIce跟踪,因为加载是hModule不是0x10000而变为0x15eb14,当然这一行文字是我的观点,没有具体考究。

 

同理,对于ret = fnVarptrA(StrPtr(s), 6),fnVarPtr的第一个参数自然也是mov到通用寄存器e[abcd]x
但是,由于APIfnVarPtr的函数声明有所不同(dwPtr As Any -> void*),导致了编译后的指令也有所不同:

00401BE0   mov         ecx,dword ptr [ebp-1Ch]00401BE3   push        ecx00401BE4   call        edi                  ;Call VarPtr00401BE6   lea         edx,[ebp-24h]        ;;EBP-0x24是第8个DWORD临时变量的地址 = 12FAB8 见00401B85指令00401BE9   push        600401BEB   push        edx                  ;;第8个DWORD的地址入栈00401BEC   mov         dword ptr [ebp-24h],eax    ;;EAX=15EB14是__vbaStrCopy赋值字符串存放的地址00401BEF   call        00401914             ;执行Call的时候,得到dwPtr=12FAB8,而**dwPtr才是"abc"00401BF4   call        esi


对于ret = fnVarptrA(VarPtr(s), 6)

00401BF6   push        300401BF8   call        004018D000401BFD   call        esi00401BFF   lea         eax,[ebp-1Ch]00401C02   push        eax00401C03   call        ebx00401C05   lea         ecx,[ebp-24h]00401C08   push        600401C0A   push        ecx                 ;相当于***dwPtr才是"abc"00401C0B   mov         dword ptr [ebp-24h],eax00401C0E   call        00401914


OK,文章贵精不贵长(其实很多东西都不是越长越好,精才是最珍贵,你懂的)
那么至于传递了字符串,如何反过来设置其值,看两行代码

p = (PBYTE)dwArg;//if(p[0] == 0x61/*97*/){if(p[1] == 0x00){p[0] = 'd';p[2] = 'e';p[4] = 'f';}else{p[0] = 'd';p[1] = 'e';p[2] = 'f';}}


本例中,vb的字符串s其实也就是个指针,经过调用会变为0x0014eb14和0x0014eb3c以及其他不合法的指针值
给这两个有效的指针传送数据,都可以改变指针指向的内存,那么从vb的代码大家都知道该怎么做了。

 

阿弥陀佛,不知道有多少人精神失常了,反正我要。。去吃点东西,先这样了

 

梁侠

2013-01-02 01:43:00

原创粉丝点击