Blast's Security Lab
11 Jul 2011
接着http://www.sacour.cn/post/1002.html的来,这篇最后说到不同编译器编译出来的代码是大相径庭的(虽然结果一样),那么今天就继续调试剩余的两个,一个是VB的,一个是Delphi的,源代码可以在1001.html中找到。
3.VB程序的调试
由于VB会把程序编译成解释型(http://zh.wikipedia.org/wiki/VB_%E4%BC%AA%E4%BB%A3%E7%A0%81)或者P-Code(http://en.wikipedia.org/wiki/Microsoft_P-Code),所以它的执行方式有点奇怪,它要通过MSVBVM60.DLL才可以解释运行,而一般VB程序的入口都是如下代码:
004011E0 >/$ 68 24134000 push 00401324 ; (initial cpu selection)
004011E5 |. E8 EEFFFFFF call <jmp.&MSVBVM60.#100>
这次示例程序也不例外,那么怎么调试它呢?如果你单步走的话你会发现这个程序一会儿在EXE领空,一会儿在DLL领空(http://www.unpack.cn/archiver/tid-46130.html),很是麻烦,有两种方法可以快速调试它。
本文来自www.sacour.cn 转载注明来源
第一种:由于VB程序是基于Unicode的,所以可以右键点击代码区,选择“Ultra String Reference”-“2 Find UNICODE”,然后找到对应的UNICODE字符,再移动到该段代码的第一句(1002.HTML中有提到,即表示为$的语句)下断点,运行程序。
第二种:右键点击代码区,选择“Analysis”-“Analysis code”,
然后按下Ctrl+M,右键点击“Search”-ascii中输入“vb6chs.dll”,然后Search,记下此字符出现的地址,然后回到代码窗口,按下Ctrl+G,进入此地址,往下翻找到类似
00401454 /2C184000 dd 工程1.0040182C ; ASCII "Form"
的地址,然后其后面一个dd xxx.xxxxxx即为Form_Load函数。
找到以后,
0040145C /FC144000 dd 工程1.004014FC
在这儿按Enter键,调到4014FC,
004014FC . /E9 0F060000 jmp 00401B10
再按Enter键,到达Form_Load函数内,在此按F2,然后F9运行:
00401B10 > \55 push ebp
接着就可以调试了,过程都很简单,1002已经说过了,这儿稍微列一下VB使用的函数:
00401B4F . FF52 04 call dword ptr [edx+4] ; MSVBVM60.Zombie_AddRef
这个是用来处理对象引用(Object Reference)的函数
00401B7B . FF15 48104000 call dword ptr [<&MSVBVM60.__vbaFixst>; MSVBVM60.__vbaFixstrConstruct
这句是因为我使用了Dim x As String * 256这一固定长度字符串而出现的,它就是处理固定长度字串的函数。
继续往下走,可以看到:
00401C84 . E8 5FFCFFFF call 004018E8
这儿可以按F7进入
004018E8 $ A1 DC324000 mov eax, dword ptr [4032DC]
004018ED . 0BC0 or eax, eax
004018EF . 74 02 je short 004018F3
004018F1 . FFE0 jmp eax
004018F3 > 68 D0184000 push 004018D0
004018F8 . B8 60114000 mov eax, <jmp.&MSVBVM60.DllFunctionC>
004018FD . FFD0 call eax ; <jmp.&MSVBVM60.DllFunctionCall>
004018FF .- FFE0 jmp eax ; kernel32.GetModuleFileNameA
可以看到这就是VB调用函数的方法,可以跟进看看,跟进系统函数以后,可以使用菜单栏的Debug-Execute till return返回
而函数
00401C9D . FF15 5C104000 call dword ptr [<&MSVBVM60.__vbaStrTo>; MSVBVM60.__vbaStrToUnicode
则是把ANSI字串转为Unicode用的。
00401CA9 . FF15 18104000 call dword ptr [<&MSVBVM60.__vbaLsetF>; MSVBVM60.__vbaLsetFixstr
则是对应String函数
00401D49 . FF15 7C104000 call dword ptr [<&MSVBVM60.__vbaStrCo>; MSVBVM60.__vbaStrCopy
这个就是简单的“=”操作符
00401D50 . FF15 14104000 call dword ptr [<&MSVBVM60.#519>] ; MSVBVM60.rtcTrimBstr
这个对应Trim函数
00401D10 . FF15 28104000 call dword ptr [<&MSVBVM60.#595>] ; MSVBVM60.rtcMsgBox
这个对应Msgbox函数
其他的调试方法都类似C++程序的方法,不过调试的时候应该多注意一下堆栈,很多信息都是在这里面表现出来的。
4.Delphi程序的调试
这次使用的是Delphi 7编译的程序,由于是命令行程序,而且只额外引用了一个Windows库,所以程序结构非常简单:
00407D14 >/$ 55 push ebp
00407D15 |. 8BEC mov ebp, esp
00407D17 |. 83C4 EC add esp, -14
00407D1A |. 53 push ebx
00407D1B |. 56 push esi
00407D1C |. 33C0 xor eax, eax
00407D1E |. 8945 EC mov dword ptr [ebp-14], eax
00407D21 |. A1 A8834000 mov eax, dword ptr [4083A8]
00407D26 |. C600 01 mov byte ptr [eax], 1
00407D29 |. B8 D47C4000 mov eax, 00407CD4
00407D2E |. E8 3DC7FFFF call 00404470
00407D33 |. 33C0 xor eax, eax
00407D35 |. 55 push ebp
00407D36 |. 68 A07D4000 push 00407DA0
00407D3B |. 64:FF30 push dword ptr fs:[eax]
00407D3E |. 64:8920 mov dword ptr fs:[eax], esp
00407D41 |. BE 00010000 mov esi, 100
00407D46 |. 8D4D EC lea ecx, dword ptr [ebp-14]
00407D49 |. BA 00010000 mov edx, 100
00407D4E |. 33C0 xor eax, eax
00407D50 |. E8 BBBCFFFF call 00403A10
00407D55 |. 8B45 EC mov eax, dword ptr [ebp-14]
00407D58 |. E8 03BCFFFF call 00403960
00407D5D |. 8BD8 mov ebx, eax
00407D5F |. 56 push esi ; /BufSize
00407D60 |. 53 push ebx ; |PathBuffer
00407D61 |. 6A 00 push 0 ; |hModule = NULL
00407D63 |. E8 E4C7FFFF call <jmp.&kernel32.GetModuleFileName>; \GetModuleFileNameA
00407D68 |. 85C0 test eax, eax
00407D6A |. 75 13 jnz short 00407D7F
00407D6C |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00407D6E |. 68 B07D4000 push 00407DB0 ; |Title = ""
00407D73 |. 68 B47D4000 push 00407DB4 ; |Text = "函数?,F7,"用失",B0,"?"
00407D78 |. 6A 00 push 0 ; |hOwner = NULL
00407D7A |. E8 35C8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA
00407D7F |> 6A 06 push 6 ; /Style = 6|MB_APPLMODAL
00407D81 |. 53 push ebx ; |Title
00407D82 |. 53 push ebx ; |Text
00407D83 |. 6A 00 push 0 ; |hOwner = NULL
00407D85 |. E8 2AC8FFFF call <jmp.&user32.MessageBoxA> ; \MessageBoxA
00407D8A |. 33C0 xor eax, eax
00407D8C |. 5A pop edx
00407D8D |. 59 pop ecx
00407D8E |. 59 pop ecx
00407D8F |. 64:8910 mov dword ptr fs:[eax], edx
00407D92 |. 68 A77D4000 push 00407DA7
00407D97 |> 8D45 EC lea eax, dword ptr [ebp-14]
00407D9A |. E8 75B8FFFF call 00403614
00407D9F \. C3 retn
整个程序就是这个了,即使程序不是命令行模式的,Delphi的调试还是非常简单,基本就像这样平坦着到头,不过为啥要特别把它拎出来呢,因为这个正好可以体现不同编译器编译的不同,还记得C++程序怎么处理if语句的吗?
C++:
004012FD |. 837D F0 00 cmp dword ptr [ebp-10], 0
00401301 |. 75 26 jnz short 00401329
Delphi:
00407D68 |. 85C0 test eax, eax
00407D6A |. 75 13 jnz short 00407D7F
先看C++的,之前已经说过了,它先把eax移动到dword ptr [ebp-10]处,然后比较这个值是否为0(详见1001.HTML的叙述),cmp操作改变了标志位,这样下一句jnz就可以根据标志位来判断是否跳转了
再看Delphi的,Delphi的这句test eax,eax是什么意思呢,test指令是按位与运算(http://zh.wikipedia.org/wiki/%E9%80%BB%E8%BE%91%E4%B8%8E),这样如果eax=0的话,test eax,eax的值也为0,如果eax!=0的话,结果也就相应的不等于零了,而如果等于0(即jnz不跳转),则会执行对话框,弹出“函数调用出错!”的对话框,如果不等于0,jnz就会跳走,也就不会弹出“函数调用出错!”的对话框了。
由于对一个立即数的操作要比寄存器操作慢,所以Delphi的这一优化还是很好的,既达到了相同的作用,又提高了效率。
差不多就写完了,暂时不写蛋疼的结束语,以后如果想到了什么再继续加。
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。