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

前端之家收集整理的这篇文章主要介绍了详解vb字符串与C/C++动态库的交互前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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 Long
Private Declare Function fnVarptr Lib "Hook.dll" (ByVal dwPtr As Long,ByVal dwFlag As Long) As Long
Private Declare Function fnVarptrA Lib "Hook.dll" Alias "fnVarptr" (dwPtr As Any,ByVal dwFlag As Long) As Long
Private 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 Fuck
End Sub

Public Sub Fuck()
    Dim ret As Long,s As String
    s = "abc"   '__vbastrcopy
    Call fnHook(0)
    ret = fnVarptr(StrPtr(s),6)    '0x0014eb14 -> 97,98,99,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,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
    Call fnHook(5)
    ret = fnVarptrA(ByVal VarPtr(s),6) '0x0012fac0 -> 20,0 = 0x0014eb14
    Call fnHook(6)
    ret = fnVarptrA(ByVal s,6) '0x0014eb3c -> 97,0
    'ret = fnVarptrA(s,6) -> fucking no more
    Call fnHook(7)
    ret = fnVarptrS(ByVal s,0
    Call fnHook(8)
    ret = fnVarptrS(s,6)   '0x0012fabc -> 60,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    -1
00001AE2    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        esi
00401AF0   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        ebp
00401B41   mov         ebp,esp
00401B43   sub         esp,0Ch
00401B46   push        4010B6h
00401B4B   mov         eax,fs:[00000000]       ;eax->0012FB00
00401B51   push        eax
00401B52   mov         dword ptr fs:[0],esp
00401B59   sub         esp,20h
00401B5C   push        ebx
00401B5D   push        esi
00401B5E   push        edi
00401B5F   mov         dword ptr [ebp-0Ch],esp
00401B62   mov         dword ptr [ebp-8],4010A0h   ;s = 4010A0
00401B69   xor         esi,esi
00401B6B   mov         dword ptr [ebp-4],esi       ;ret = 0
00401B6E   mov         eax,dword ptr [ebp+8]
00401B71   push        eax
00401B72   mov         ecx,dword ptr [eax]
00401B74   call        dword ptr [ecx+4]        ;MSVBVM60.Zombie_AddRef
00401B77   mov         edx,401948h              ;UNICODE "abc"
00401B7C   lea         ecx,[ebp-1Ch]            ;地址传送
00401B7F   mov         dword ptr [ebp-1Ch],esi  ;*12fac0 = 0
00401B82   mov         dword ptr [ebp-20h],esi  ;*12fabc = 0
00401B85   mov         dword ptr [ebp-24h],esi  ;*12fab8 = 0
00401B88   call        dword ptr ds:[401068h]   ;MSVBVM60.__vbaStrCopy
;执行后EAX=15EB14(Unicode'abc'),EDX=6;ESI=0不变
;Call fnHook(0)
00401B8E   push        esi
00401B8F   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 -> fnHook
004018D0   mov         eax,[004032DC]
004018D5   or          eax,eax      ;if(eax == 0) goto 004018DB -> pointer != null
004018D7   je          004018DB
004018D9   jmp         eax          ;1000100F -> jumped
004018DB   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.__vbaSetSystemError
00401B9A   call        esi                         ;
00401B9C   mov         edx,dword ptr [ebp-1Ch]
00401B9F   mov         edi,dword ptr ds:[401058h]  ;MSvbVM60.VarPtr
00401BA5   push        edx
00401BA6   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) = x
7346DCE9   ret         4

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

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


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

;api -> fnVarptr
00401914   mov         eax,[004032E8]
00401919   or          eax,eax
0040191B   je          0040191F
0040191D   jmp         eax
0040191F   push        4018FCh
00401924   mov         eax,401140h
00401929   call        eax
0040192B   jmp         eax


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

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

;ret = fnVarptr(...)的返回值
00401BB0   mov         dword ptr [ebp-24h],eax
00401BB3   call        esi        ;esi=7345C195,*7345C195 = ntdll.RtlGetLastWin32Error
;Call fnHook(1)
00401BB5   push        1
00401BB7   call        004018D0
00401BBC   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        eax
00401BC8   call        ebx              ;Call VarPtr
00401BCA   push        6
00401BCC   push        eax
00401BCD   call        00401914
00401BD2   mov         dword ptr [ebp-24h],eax
00401BD5   call        esi
00401BD7   push        2
00401BD9   call        004018D0


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

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

从vb的源码可以看出:

ret = fnVarptr(VarPtr(s),6)


执行后fnVarPtr得到的是0x0012fac0,用它作为指针的地址得到四个字节 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        ecx
00401BE4   call        edi                  ;Call VarPtr
00401BE6   lea         edx,[ebp-24h]        ;;EBP-0x24是第8个DWORD临时变量的地址 = 12FAB8 见00401B85指令
00401BE9   push        6
00401BEB   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        3
00401BF8   call        004018D0
00401BFD   call        esi
00401BFF   lea         eax,[ebp-1Ch]
00401C02   push        eax
00401C03   call        ebx
00401C05   lea         ecx,[ebp-24h]
00401C08   push        6
00401C0A   push        ecx                 ;相当于***dwPtr才是"abc"
00401C0B   mov         dword ptr [ebp-24h],eax
00401C0E   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

猜你在找的VB相关文章