目录
1、准备工作
2、加载目标文件进行调试
3、单步跟踪
4、设置断点
5、调试分析
1、准备工作
分析一个Windows程序要比分析一个DOS程序容易得多因为在Windows中只要API函数被使用了,再想对要寻找蛛丝马迹的人隐藏一些信息就比较困难了。因此,在分析一个程序时,以哪个API函数作为切人点就显得比较重要了。如果有一些编程经验,在这方面就更加得心应手了。
为了便于理解,先简单地看一下TraceMe 的序列号验证流程如图25所示。将用户名与序列号输人文本框程序调用GetDigltemTextA函数把字符读出然后进行计算,最后用lstremp函数进行比较。因此,这些调用的函数就是解密跟踪的目标,将这些函数作为断点,跟踪程序的序列号验证过程,就能找出正确的序列号。这种专门为练习解密技术制作的小程序,一般统称为“CrackMe”。
2、加载目标文件进行调试
为了让OllyDbg中断在程序的入口点,在加载程序前必须进行相应的设置。运行OlyDbg,单击“Oplions”一“Debugging options”选项,打开调试选项配置对话框。单击“Event”标签,设置OllyDhg对中断人口点、模块加载/卸载、线程创建/结束等事件的处理方式,一般只需要将断点设置
在“WinMain”处。
设置完成后单击“File”一“Open”选项打开TraceMeexe此时0llyDbg会中断在TraceMe的第1条指令处调试器分析代码并等待用户的下一步操作。如下图所示光标停在004013AOh处004013A0h就是程序的人口点(EntryPoint)。大部分程序在启动时都会停在入口点。通过一些特殊的修改方式,有些程序可以在启动时不停在人口点,以达到反调试的目的。各部分代码的含义如下。
- 虚拟地址:在一般情况下,同一程序的同一条指令在不同系统环境下此值相同。
- 机器码:就是CPU执行的机器代码。
- 汇编指令:与机器码对应的程序代码
寄存器面板上显示了各个寄存器的当前值。寄存器有EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI和EIP等,它们统称为32位存器如下图所示。ESP为指针,指向顶在OllyDbg界面右下角的栈面板上显示了栈的值。另一个重要的寄存器是EIP,它指向当前将要执行的指令,按一下“F7”键将执行一条指令,然后 EIP将指向下一条将要执行的指令。
在调试时,可以双击这些寄存器,修改寄存器里的值。但是对EIP寄存器,不能直接修改,需要在反汇编窗口选择新的指令起始地址,例如004013AAh,在其上单击右键在弹出的快捷菜单中选择“Neworiginhere”(此处为新的EIP选项EIP的值将变成4013AAh,程序将从这条指令开始执行。寄存器下方显示的是标志寄存器,分别是C、P、A、ZS、T、D、0,它们的值只能是两个数字值-0和1,双击数字可以在0和1之间切换。
3、单步跟踪
调试器的一个最基本的功能就是动态跟踪。0llyDbg在“Debug”菜单里控制运行的命令,各菜单项都有相应的快捷键。olyDhg的单步跟踪快捷键如下图所示。
快捷键 | 功能 |
F7 | 单步步进,遇到call 指令跟进 |
F8 | 单步步过,遇到call指令路过,不跟进 |
Ctrl+F9 | 直到出现rer指令时中断 |
Alt+F9 | 若进人系统领空,此命令可瞬间回到应用程序领空 |
F9 | 运行程序 |
F2 | 设置断点 |
“F8”键在调试中的使用很频繁,可以逐句单步执行汇编指令,遇到call 指令不会跟进,而是路过,示例如下。
“F7”键和“F8”键的主要区别在于,若遇到callloop等指令,按“F8”键会路过,按“F7键会跟进,示例如下。
“ call00401DA0”表示调用00401DAOh处的子程序。一旦子程序调用完毕,就返回call指的下一条语句即004013FFh处。按“F7”键跟进00401DAOh处的子程序,观察的情况,会发现call指令的下一条指令的地址004013FFh作为返回地址被压人栈中如图28所示。子程序末尾是一个ret指令执行完00401DD7h处的指令,就能返回call指的下一条语004013FFh处。在进人子程序的过程中,若想回看之前单步跟踪的代码,可以按“-”(减号)键;若想让光标回到当前EIP所指向的语句,可以单击国按钮C或双击EIP寄存器。
当要重复按“F7”键或“F8”键时OllyDbg提供了快捷键Ctl+F7”和“Ctrl+F8”直到用户按“Esc”键、“F12”键或遇到其他断点时停止。
当位于某个call指中,想返回调用这个cl 指的位置时可以按“Ctrl+F9”快捷键执行Executetillreum”(执行到返回)命令OllyDhg会停在遇到的第1个返回命处(retretf或iret).这样可以方便地略过一些没用的代码。
例如上面的代码在00401DAO处如果按“Ctl+F9”快捷键就会返回 004013FFh 处。遇到et 指时是暂停还是路过可以在选项设置,方法是打开调试设置选项对话框,在“Trace”标签页中设置“After Execting tillRETstepover RET”(执行到ret指令后,单步路过ret指令)。
如果已经进人系统DLL提供的API函数,当要返回应用程序领空时可以按快捷键“Alt+F9”执行“Executetillusercode”(执行到用户代码)命令,示例如下。
在004013C6h处按“F7”键就可跟进系统KERNEL32DLL了,示例如下。
“7C8114AB”等都是系统DLL所在的地址空间这时只要按快捷键“Alt+F9”就可以回到应用程序领空,代码如下。
注意:所谓“领空”,实际上是指在某一时刻CPU的CS:EIP 指向的某段代码的所有者。
如果不想单步跟踪,想让程序直接运行,可以按“F9”键或单击工具栏中的右进按钮。如果想重新调试目标程序,可以按“Ctrl+F2”快捷键,OllyDhg将结束被调试进程并重新加载它。如果程序进入死循环,可以按“F12”键暂停程序。
4、设置断点
断点(breakpoint)是调试器的一个重要功能可以让程序中断在指定的地方,从而方便地对其进行分析。如下图所示将光标移到004013A5h处按“F2”键即可设置一个断点再次按“F2”键可以取消断点。也可以双击“Hexdump”列中相应的行设置断点,再次双击可以取消断点。
设置断点后,按“Alt+B”快捷键或单击B按钮,打开断点窗口,查看断点明细如下图所示。这里显示了除硬件断点外的其他断点,其中“Always”表示断点处于激活状态“Disable”表示断点停用。按空格键可切换其状态。也可以通过右键快捷菜单管理这些断点。删除断点的快捷是“Del”键。
00B2A5C8 . E8 1BAA0000 call 路径修复.00B34FE8 ; \路径修复.00B34FE8
00B2A5CD . 59 pop ecx ; kernel32.777D0099
00B2A5CE . B8 4D5A0000 mov eax,0x5A4D
00B2A5D3 . 66:3905 0000B>cmp word ptr ds:[0xB20000],ax
00B2A5DA . 74 04 je short 路径修复.00B2A5E0
00B2A5DC > 33DB xor ebx,ebx
00B2A5DE . EB 33 jmp short 路径修复.00B2A613
00B2A5E0 > A1 3C00B200 mov eax,dword ptr ds:[0xB2003C]
00B2A5E5 . 81B8 0000B200>cmp dword ptr ds:[eax+0xB20000],0x4550
00B2A5EF .^ 75 EB jnz short 路径修复.00B2A5DC
00B2A5F1 . B9 0B010000 mov ecx,0x10B
00B2A5F6 . 66:3988 1800B>cmp word ptr ds:[eax+0xB20018],cx
00B2A5FD .^ 75 DD jnz short 路径修复.00B2A5DC
00B2A5FF . 33DB xor ebx,ebx
00B2A601 . 83B8 7400B200>cmp dword ptr ds:[eax+0xB20074],0xE
00B2A608 . 76 09 jbe short 路径修复.00B2A613
00B2A60A . 3998 E800B200 cmp dword ptr ds:[eax+0xB200E8],ebx
00B2A610 . 0f95c3 setne bl
00B2A613 > 895D E4 mov dword ptr ss:[ebp-0x1C],ebx
00B2A616 . E8 067C0000 call 路径修复.00B32221
00B2A61B . 85C0 test eax,eax
00B2A61D . 75 08 jnz short 路径修复.00B2A627
00B2A61F . 6A 1C push 0x1C
00B2A621 . E8 DC000000 call 路径修复.00B2A702
00B2A626 . 59 pop ecx ; kernel32.777D0099
00B2A627 > E8 F4690000 call 路径修复.00B31020
00B2A62C . 85C0 test eax,eax
00B2A62E . 75 08 jnz short 路径修复.00B2A638
00B2A630 . 6A 10 push 0x10
00B2A632 . E8 CB000000 call 路径修复.00B2A702
00B2A637 . 59 pop ecx ; kernel32.777D0099
00B2A638 > E8 94AA0000 call 路径修复.00B350D1
00B2A63D . 8365 FC 00 and dword ptr ss:[ebp-0x4],0x0
00B2A641 . E8 EE7E0000 call 路径修复.00B32534
00B2A646 . 85C0 test eax,eax
00B2A648 . 79 08 jns short 路径修复.00B2A652
00B2A64A . 6A 1B push 0x1B
00B2A64C . E8 B1000000 call 路径修复.00B2A702
00B2A651 . 59 pop ecx ; kernel32.777D0099
00B2A652 > FF15 3400B400 call dword ptr ds:[<&KERNEL32.GetCommand>; [GetCommandLineA
00B2A658 . A3 C8DCB400 mov dword ptr ds:[0xB4DCC8],eax
00B2A65D . E8 AFAA0000 call 路径修复.00B35111
00B2A662 . A3 4CBDB400 mov dword ptr ds:[0xB4BD4C],eax
00B2A667 . E8 52A40000 call 路径修复.00B34ABE
00B2A66C . 85C0 test eax,eax
00B2A66E . 79 08 jns short 路径修复.00B2A678
00B2A670 . 6A 08 push 0x8
00B2A672 . E8 6D820000 call 路径修复.00B328E4
00B2A677 . 59 pop ecx ; kernel32.777D0099
00B2A678 > E8 70A60000 call 路径修复.00B34CED
00B2A67D . 85C0 test eax,eax
00B2A67F . 79 08 jns short 路径修复.00B2A689
00B2A681 . 6A 09 push 0x9
00B2A683 . E8 5C820000 call 路径修复.00B328E4
00B2A688 . 59 pop ecx ; kernel32.777D0099
00B2A689 > 6A 01 push 0x1 ; /Arg1 = 00000001
00B2A68B . E8 8E820000 call 路径修复.00B3291E ; \路径修复.00B3291E
00B2A690 . 59 pop ecx ; kernel32.777D0099
00B2A691 . 85C0 test eax,eax
00B2A693 . 74 07 je short 路径修复.00B2A69C
00B2A695 . 50 push eax
00B2A696 . E8 49820000 call 路径修复.00B328E4
00B2A69B . 59 pop ecx ; kernel32.777D0099
00B2A69C > E8 FDAA0000 call 路径修复.00B3519E
00B2A6A1 . 56 push esi ; /Arg4 = 00B2A5A8
00B2A6A2 . 50 push eax ; |Arg3 = 010FFC98
00B2A6A3 . 6A 00 push 0x0 ; |Arg2 = 00000000
00B2A6A5 . 68 0000B200 push 路径修复.00B20000 ; |Arg1 = 00B20000
00B2A6AA . E8 B1CBFFFF call 路径修复.00B27260 ; \路径修复.00B27260
00B2A6AF . 8BF0 mov esi,eax
00B2A6B1 . 8975 DC mov dword ptr ss:[ebp-0x24],esi ; 路径修复.<ModuleEntryPoint>
00B2A6B4 . 85DB test ebx,ebx
00B2A6B6 . 75 06 jnz short 路径修复.00B2A6BE
00B2A6B8 . 56 push esi ; 路径修复.<ModuleEntryPoint>
00B2A6B9 . E8 C9840000 call 路径修复.00B32B87
00B2A6BE > E8 4C820000 call 路径修复.00B3290F
00B2A6C3 . EB 2E jmp short 路径修复.00B2A6F3
00B2A6C5 . 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00B2A6C8 . 8B01 mov eax,dword ptr ds:[ecx]
00B2A6CA . 8B00 mov eax,dword ptr ds:[eax]
00B2A6CC . 8945 E0 mov dword ptr ss:[ebp-0x20],eax
00B2A6CF . 51 push ecx ; 路径修复.<ModuleEntryPoint>
00B2A6D0 . 50 push eax
00B2A6D1 . E8 8DA20000 call 路径修复.00B34963
00B2A6D6 . 59 pop ecx ; kernel32.777D0099
00B2A6D7 . 59 pop ecx ; kernel32.777D0099
00B2A6D8 . C3 retn
00B2A6D9 . 8B65 E8 mov esp,dword ptr ss:[ebp-0x18]
00B2A6DC . 8B75 E0 mov esi,dword ptr ss:[ebp-0x20]
00B2A6DF . 8975 DC mov dword ptr ss:[ebp-0x24],esi ; 路径修复.<ModuleEntryPoint>
00B2A6E2 . 837D E4 00 cmp dword ptr ss:[ebp-0x1C],0x0
00B2A6E6 . 75 06 jnz short 路径修复.00B2A6EE
00B2A6E8 . 56 push esi ; 路径修复.<ModuleEntryPoint>
00B2A6E9 . E8 B4820000 call 路径修复.00B329A2
00B2A6EE > E8 0D820000 call 路径修复.00B32900
00B2A6F3 > C745 FC FEFFF>mov dword ptr ss:[ebp-0x4],-0x2
00B2A6FA . 8BC6 mov eax,esi ; 路径修复.<ModuleEntryPoint>
00B2A6FC . E8 847C0000 call 路径修复.00B32385
00B2A701 . C3 retn
00B2A702 /$ 55 push ebp
5、调试分析
按“F8”键单步走出GetDlgllemTextA函数。当然也可以按“Alt+F9”快捷键回到调用函数的地方。OllyDbg非常强大,已经对各函数的调用参数及当前值进行了注释,相关代码如下。
在阅读这些代码时,需要注意以下几点。
- 要清楚各API函数的定义(查看相关API手册)
- API函数大都采用sdcall调用约定即函数人口参数按从右到左的顺序人栈,由被调用者清理栈中的参数,返回值放在eax寄存器中。因此,对相关的API函数,要分析其前面的push 指令,这些指令将参数放人栈,以传送给API调用。在整个跟踪过程中要关注栈数据的变化。
- C代码中的子程序采用的是C调用约定,函数人口参数按从右到左的顺序人栈,由调用者清理栈中的参数。
- 调用约定、参数传递等知识,GetDlgItemText函数采用标准调用约定,参数按从右到左的顺序人栈。本例汇编代码如下:
00B2A728 \. C3 retn
00B2A729 CC int3
00B2A72A CC int3
00B2A72B CC int3
00B2A72C CC int3
00B2A72D CC int3
00B2A72E CC int3
00B2A72F CC int3
00B2A730 $ 57 push edi ; 路径修复.<ModuleEntryPoint>
00B2A731 . 56 push esi ; 路径修复.<ModuleEntryPoint>
00B2A732 . 8B7424 10 mov esi,dword ptr ss:[esp+0x10] ; ntdll.77C67B6E
00B2A736 . 8B4C24 14 mov ecx,dword ptr ss:[esp+0x14]
00B2A73A . 8B7C24 0C mov edi,dword ptr ss:[esp+0xC]
00B2A73E . 8BC1 mov eax,ecx ; 路径修复.<ModuleEntryPoint>
00B2A740 . 8BD1 mov edx,ecx ; 路径修复.<ModuleEntryPoint>
00B2A742 . 03C6 add eax,esi ; 路径修复.<ModuleEntryPoint>