首页
社区
课程
招聘
[原创]VB msvbvm60 GetObject 调用分析
发表于: 2013-4-21 18:26 4943

[原创]VB msvbvm60 GetObject 调用分析

2013-4-21 18:26
4943

头一次发帖,希望能对新手有用。文章中涉及了一点程序运行栈的知识,以及一点点VB的数据类型在内存中的存在形式的介绍。

原来从来没有接触过VB,最近工作原因需要对一个没有源代码的VB60程序进行一点儿汇编级别的小更改,发现VB是一个很有意思的语言。

工作的需求是这样的:
原来的软件里,调用了getObject函数(在msvbvm60.dll里对应rtcGetObject)。通过看别人的调用范例,发现这个函数有两个参数,都是string类型(后来发现其实是variant,这是后话,后面会提到。)。我的工作就是把调用的参数改掉。

问题很简单,我觉得很快就能够做完,可是做起来有些小地方确实耗费了不少时间。

首先,原来软件的作者加了个小壳,简单来说,就是对所有text段的内容都XOR了一个常量,然后跳转到OEP。由于这壳过于简单,去掉小壳不费力,这里不多表述。

去壳之后,根据反汇编的结果,在call msvbvm60.rtcGetObject的地方设置断点,查看寄存器和堆栈的情况。到目前为止,so far so good,心情非常哇哈哈。可是,程序停在断点之后,傻了眼,堆栈和寄存器的值比较杂乱,根本看不出来参数是怎么传的。

既然,是新手区的文章,这里对于程序的调用传统就多说两句。大体上常见的调用传统(calling convention)有以下几种:
1,cdecl (c default call)c的默认调用方式,好处是能比较好的支持可变参数函数,比如printf;缺点是编译结果体积会稍微大一点
    a,参数通过堆栈传递,从右向左(也就是最后一个参数先进栈)压栈
    b,调用者负责清空堆栈中的参数
    c,在object文件中,cdecl函数的名字前缀一个下划线'_'。
2,stdcall (standard call)pascal的默认调用方式
    a,参数通过堆栈传递,从右向左(也就是最后一个参数先进栈)压栈
    b,被调用者负责清空堆栈中的参数
    c,在object文件中,stdcall函数的名字前缀一个下划线'_',后缀一个'@'以及堆栈需要的空间字节数
3,fastcall (fast call)因为使用了寄存器传参数,所以调用速度会快一些
    a,前两个参数放进ECX,EDX里面,两个以上的参数从右向左压栈
    b,被调用者负责清空堆栈中的参数
    c,在object文件中,stdcall函数的名字前缀一个'@',后缀一个'@'以及堆栈需要的空间字节数
4,thiscall (this call)面向对象语言传递this指针的调用方式
    a,参数通过堆栈传递,从右向左(也就是最后一个参数先进栈)压栈,this指针通过ECX传递
    b,被调用者负责清空堆栈中的参数
感兴趣者可以参考这片鸟文,写得很清楚 http://www.codeproject.com/Articles/1388/Calling-Conventions-Demystified

好了,学习了calling convention之后,我们来看一下调用rtcGetObject之际的堆栈和寄存器现场。
为了让运行时现场和程序对应起来,我采用的程序是自己用VB60写的一个范例。这个例子中GetObject的调用如下:
Dim mytest As Object
mytest = GetObject("d:\abc.ljs", "mohaha")

首先使用W32dasm反汇编目标exe,在import function里面找到rtcGetObject函数,查看它被call的地方,设置上断点。

程序如约,跑到了断点处,此时现场如下:

【寄存器】
EAX 0012FA7C
ECX 0012FA5C
EDX 0012FA6C
EBX 00000008
ESP 0012F9DC
EBP 0012FAA4
ESI 00000000
EDI 72A46DF6

【堆栈】:
(从ESP到EBP的内容)
0012F9DC   0012FA5C
0012F9E0   0012FA7C
0012F9E4   0012FA6C
0012F9E8   0012FAE8
0012F9EC   00271C18
0012F9F0   00000001
0012F9F4   FFFFFFFF
0012F9F8   FFFFFFFF
0012F9FC   75F5881F  RETURN to USER32.75F5881F from ntdll.RtlActivateActivationContextUnsafeFast
0012FA00   75F588C9  RETURN to USER32.75F588C9 from ntdll.RtlDeactivateActivationContextUnsafeFast
0012FA04   0012FA14
0012FA08   00000001
0012FA0C   00000000
0012FA10   00000000
0012FA14   0012F9D0
0012FA18   0012FB98
0012FA1C   0012FF70
0012FA20   75FA62E3  USER32.75FA62E3
0012FA24   0E3DB391
0012FA28   FFFFFFFE
0012FA2C   00000008
0012FA30   75F57631  RETURN to USER32.75F57631 from USER32.75F587C3
0012FA34   00401654  UNICODE "mohaha"
0012FA38   7299F74E  MSVBVM60.7299F74E
0012FA3C   00000008
0012FA40   00000080
0012FA44   00401638  UNICODE "d:\abc.ljs"
0012FA48   729A0962  RETURN to MSVBVM60.729A0962 from MSVBVM60.72972B59
0012FA4C   00000000
0012FA50   00001001
0012FA54   0012FAB4
0012FA58   01AEF71C
0012FA5C   00000000
0012FA60   01AEF71C
0012FA64   75F566E3  USER32.IsIconic
0012FA68   01AEF71C
0012FA6C   00000008
0012FA70   75F4CC71  RETURN to USER32.75F4CC71 from USER32.75F57543
0012FA74   00272D3C  UNICODE "mohaha"
0012FA78   0066FDD0
0012FA7C   00000008
0012FA80   0ABB0367
0012FA84   00272D74  UNICODE "d:\abc.ljs"
0012FA88   01AEF71C
0012FA8C   00000000
0012FA90   0012FAC8  Pointer to next SEH record
0012FA94   004010B6  SE handler
0012FA98   0012F9E8
0012FA9C   00401098  12.00401098
0012FAA0   00000000
0012FAA4   0012FADC

下面我们根据刚刚学习的calling convention,但是由于我对VB不熟悉,不知道它在这里到底采用哪种调用传统,我们把和调用参数相关的值都单拿出来看。

CX:0012FA5C
DX:0012FA6C
栈顶 + 0:0012FA5C
栈顶 + 4:0012FA7C
栈顶 + 8:0012FA6C

CX和DX的值和栈里面的值有重复,应该是准备参数时候的中间结果,最后push到栈里面的。所以我估计,参数是全用栈传递的。因为VB是部分面向对象的语言,第一个参数有可能是某些this指针类型的玩意儿,所以栈顶第一个参数可能不是我们需要的。后两个参数可能就应该是字符串参数的实体了。

现在,让我们来看看这些值都对应着什么东西。假设他们是地址指针,我把对应空间的值列在了后面:
栈顶 + 0: 0012FA5C:00000000
栈顶 + 4: 0012FA7C:00000008
栈顶 + 8: 0012FA6C:00000008

我当时的幻想是,字符串的首地址应该出现在这三个值中,就好像C语言一样。但是,回到现实,这三个值......都不是字符串起始地址。

难道是VB下,STRING的内存存储方式不同?于是开始调研VB下的STRING。发现VB采用COM那套BSTR字符串方案,即,字符串前缀整个字符串的字节长度(不算结束符),字符串的每一个字符都是UNICODE双字节表示,字符串结尾以UNICODE的0000表示结束。

OK。回到我们的例子程序,第一个参数"d:\abc.ljs",长度为10,字节应该是20个;第二个参数"mohaha",长度为6,应该是12个字节。

于是,在内存中字符串的开头找这两个数,依然未果。此时,感觉这一切好奇妙......完全摸不着头脑。

又经过了几番思考,几番网上搜索,发现有一个重要的问题被忽略了。VB的官方Document里面定义的GetObject原型是这样的:
GetObject( [ pathname ] [ , class ] ) 
其中 pathname 和 class 都是Variant类型,而不是String。

那好,咱们来再看一下variant类型变量在内存中的组织形式。
|--------------------------------|
| 偏移量 | 域大小  | 域描述      |
|--------|---------|-------------|
| 0      | 2       | 数据类型    |
| 2      | 6       | 保留字段    |
| 8      | 最大  8 | 实际数据内容|
|--------------------------------|

Variant可以存各种类型的数据,他的前两个字节储存着它当前数据的数据类型,随后6字节是保留字,没有任何意义(有的文档上说,这6个字节必须是0,但是我实际观测发现,这6个字节可以是任何值)。

数据类型,有一个大表,可以参考Wiki页http://en.wikipedia.org/wiki/Variant_type。我们可以看到String类型的数据类型码是:0x0008,实际数据内容是一个wchar_t的字符串指针。那好,咱们再回头看看我们的运行现场。

栈内0012FA6C和0012FA7C的内容都是0x08,之后6个字节的随意值,然后跟随了一个字符串的指针,指向我们的参数字符串。

现在,一切都对上了。调试过程中,我对这两个variant进行修改,程序也作出了对应的行为变化。这下,我就可以通过对对应字符常量进行修改、push命令顺序的调整,随意改变调用GetObject时候的参数了。

文毕。关于Variant有几篇网文值得参考:

http://en.wikipedia.org/wiki/Variant_type
http://www.codeguru.com/vb/gen/vb_misc/algorithms/article.php/c7495/How-Visual-Basic-6-Stores-Data.htm
http://www.canaimasoft.com/f90vb/onlinemanuals/usermanual/th_34.htm


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (1)
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
看得一头雾水,楼主能给我调试下一个软件么,看怎么破法?VB写的。。。
2013-5-3 13:29
0
游客
登录 | 注册 方可回帖
返回
//