为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

实用电脑技术实例-0592

2012-06-20 11页 pdf 154KB 22阅读

用户头像

is_554469

暂无简介

举报
实用电脑技术实例-0592 破解技术实例0592-【学习逆向工程,分析机器代码】(一) 标 题: 【学习逆向工程,分析机器代码】(一) 作 者: sertty 详细信息: 1、序 由于最近对逆向工程产生了浓厚的兴趣,所以就利用UltraEdit32撰写了一 个麻雀虽小,但五脏俱全的“test.c”程序。然后用OD对它进行逆向工程,逐 步分析机器代码。主要目的是:探索C/C++编译器是如何产生机器代码;及 验证CRT函数及带参数的自定义函数的call对栈产生的影响;push和pop对栈 具体的实现;分析for结构和i...
实用电脑技术实例-0592
破解技术实例0592-【学习逆向工程,分析机器代码】(一) 标 题: 【学习逆向工程,分析机器代码】(一) 作 者: sertty 详细信息: 1、序 由于最近对逆向工程产生了浓厚的兴趣,所以就利用UltraEdit32撰写了一 个麻雀虽小,但五脏俱全的“test.c”程序。然后用OD对它进行逆向工程,逐 步分析机器代码。主要目的是:探索C/C++编译器是如何产生机器代码;及 验证CRT函数及带参数的自定义函数的call对栈产生的影响;push和pop对栈 具体的实现;分析for结构和if结构及while产生的机器代码。为此我分别生成 了一个优化版本及另一个未经优化版本。 2、一个具体而微的C程序 包括以下内容: 1)主函数main:主函数main内有一个变量及一些CRT函数的调用和一个 if结构; 2)函数my_strcmp:它是一个字节串比较的自定义函数。函数有两个参 数:一个源字节串,另一个目标字节串;并且函数体内则有三个变量;另外在 程序结构上,有一个for循环及if结构。 ------------------------------------------------------------------------ 源程序如下: #include #include #include //#include int main() { char buffer[100]; printf("请输入序列号:\n"); scanf( "%s", buffer ); if ( my_strcmp( buffer, "SN12345" ) == 0 ) printf("注册成功!\n"); else printf( "注册失败!\n" ); getche(); return 0; } // 为了测试,代码并没有优化,并且还特意使用了三个局部变量 // int my_strcmp( const char* pszSrc, const char* pszDest ) { char* pSrc = (char*)pszSrc; char* pDest = (char*)pszDest; int iResult = 0; for ( ; *pSrc != 0 && *pDest != 0 ; pSrc++, pDest++ ) { iResult = *pSrc - *pDest; if ( iResult != 0 ) return iResult; } return 0; } ------------------------------------------------------------------------ 3、编译 在XP SP2环境下,开一个cmd.exe,键入VC6,进入我们的text.c目录,键入 b,完成未优化版本编译。键入b_opt,完成优化版本编译。 以下是vc6.bat和b.bat及b_opt.bat的批处理内容: VC6.bat ----------------------------------------------------------------------- @echo off set VC6DIR=I:\Program Files\Microsoft Visual Studio\VC98 set include=I:\DXSDK\Include;%VC6DIR%\Include;%VC6DIR%\atl\include; %VC6DIR%\mfc\include set lib=I:\DXSDK\Lib;%VC6DIR%\lib;%VC6DIR%\mfc\lib set path=c:\;I:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin;%VC6DIR%\Bin set %VC6DIR%= echo on ----------------------------------------------------------------------- b.bat ----------------------------------------------------------------------- cl.exe /c /Gz test.c link.exe /subsystem:console test_opt.obj LIBC.LIB kernel32.lib ----------------------------------------------------------------------- b_opt.bat ----------------------------------------------------------------------- cl.exe /c /Gz /O2 /Fotest_opt.obj test.c link.exe /subsystem:console /OUT:test_opt.exe test_opt.obj LIBC.LIB kernel32.lib ----------------------------------------------------------------------- 4、逆向过程 打开OllyDBG,加载test_opt.exe,然后在00401000地址设置断点。按下 F9后我们来到断点处,接着便是F8一路逐行分析代码: 4.1 〖O2优化版本〗 --------------------------------------------------------------------------- --------------------------------------------- // 主函数: int main() imgae地址 机器代码 汇编代码 注释 --------- ----------- --------------------------------- --------------------------------------------------------- 00401000 /$ 83EC 64 sub esp, 64 ; char buffer[100]; //esp - 100 00401003 |. 68 5C804000 push 0040805C ; push ["请输入序列号 :\n"] //esp - 4 00401008 |. E8 AA000000 call ; call printf 0040100D |. 8D4424 04 lea eax, dword ptr [esp+4] ; lea eax, [buffer] //获取buffer的指针 00401011 |. 50 push eax ; push [buffer] //esp - 4 00401012 |. 68 58804000 push 00408058 ; push ["%s"] //esp - 4 00401017 |. E8 84000000 call ; call scanf 0040101C |. 83C4 0C add esp, 0C ; esp + 12 // 释放刚刚函数的参数调用的3个push,堆栈平衡。 ; // 此时esp的值又指向buffer了 0040101F |. 8D4C24 00 lea ecx, dword ptr [esp] ; lea eax, [buffer] //获取buffer的指针。 00401023 |. 68 48804000 push 00408048 ; push ["SN12345"] // 传入我们的序列号, esp - 4 00401028 |. 51 push ecx ; push [buffer] // esp - 4 00401029 |. E8 42000000 call ; 调用自定义函数比较字节串。注意!自定义的函数在执行完后, ; 会执行 retn 释放参数栈。而CRT的则不会。 ; call 指令内部实现: esp - 4, , ; 然后在那函数内的retn也会释放这个esp占用的4字节。 0040102E |. 85C0 test eax, eax ; 测试结果 00401030 |. 75 18 jnz short 0040104A ; 如果刚刚键入的序列号和系统 的不配备,就跳到“注册失败” 00401032 |. 68 3C804000 push 0040803C ; push ["注册成功!\n"] // esp - 4 00401037 |. E8 7B000000 call ; call printf 0040103C |. 83C4 04 add esp, 4 ; 释放printf参数调用占用的stack,堆 栈平衡 0040103F |. E8 7D590000 call ; call getche 00401044 |. 33C0 xor eax, eax ; 执行return 0; 清空返回值EAX 00401046 |. 83C4 64 add esp, 64 ; 释放buffer[100] 00401049 |. C3 retn ; 结束main函数 0040104A |> 68 30804000 push 00408030 ; push ["注册失败!\n"] // esp - 4 0040104F |. E8 63000000 call ; call printf 00401054 |. 83C4 04 add esp, 4 ; 释放printf参数调用占用的stack,堆 栈平衡 00401057 |. E8 65590000 call ; call getche 0040105C |. 33C0 xor eax, eax ; 执行return 0; 清空返回值EAX 0040105E |. 83C4 64 add esp, 64 ; 释放buffer[100] 00401061 \. C3 retn ; 结束main函数 --------------------------------------------------------------------------- --------------------------------------------- // 自定义函数: int my_strcmp( const char* pszSrc, const char* pszDest ) imgae地址 机器代码 汇编代码 注释 --------- ----------- --------------------------------- --------------------------------------------------------- 00401070 >/$ 8B4C24 04 mov ecx, dword ptr [esp+4] ; 获取参数pszSrc。由于CPU执行了call指令,esp目前指向 ; 本函数地址,esp+4则指向第一个参数pszSrc, ; 压参数时是由右至左,所以+4则是指最后入栈的参数 00401074 |. 56 push esi ; 备份esi寄存器,esp - 4 00401075 |. 8039 00 cmp byte ptr [ecx], 0 ; 判断pszSrc指向的第一个字符是否为NULL 00401078 |. 74 1F je short 00401099 ; 如果为NULL就退出函数 0040107A |. 8B7424 0C mov esi, dword ptr [esp+C] ; 获取第二个参数指针pszDest。因为esp+8是esi的备份,so... 0040107E |. 2BF1 sub esi, ecx ; pszDest -= pszSrc,得到一个 pszDest的偏移, ; 从而让下一条指令的esi+ecx完成索引pszDest串操作 00401080 |> 8A140E /mov dl, byte ptr [esi+ecx] ; for结构。获取pszDest指向的字符到dl中 00401083 |. 84D2 |test dl, dl ; 测试 *pszDest == 0 00401085 |. 74 12 |je short 00401099 ; 如果为0就退出函数。表示已到 pszDest串尾 00401087 |. 0FBE01 |movsx eax, byte ptr [ecx] ; 获取pszSrc指向的当前字符到eax中 0040108A |. 0FBED2 |movsx edx, dl ; 获取pszDest指向的当前字符到 edx中 0040108D |. 2BC2 |sub eax, edx ; iResult = *pSrc - *pDest。O2优化的结果。优化为这三条 0040108F |. 75 0A |jnz short 0040109B ; if ( iResult != 0 ) return iResult; 00401091 |. 8A41 01 |mov al, byte ptr [ecx+1] ; al = *(pszSrc + 1); 下一个pszSrc指向的字符 00401094 |. 41 |inc ecx ; pszSrc++; pszSrc指针+1 00401095 |. 84C0 |test al, al ; 测试是否为0 00401097 |.^ 75 E7 \jnz short 00401080 ; 如果不为0表示还未到串尾 ,继续进行下一轮比较 00401099 |> 33C0 xor eax, eax ; 返回0表示相等,和strcmp一样 0040109B |> 5E pop esi ; 恢复esi 0040109C \. C2 0800 retn 8 ; 执行retn 释放参数栈(pszSrc和pszDest) --------------------------------------------------------------------------- --------------------------------------------- 4.2 〖未经优化版本〗 --------------------------------------------------------------------------- ---------------------------------------------- // 主函数: int main() imgae地址 机器代码 汇编代码 注释 --------- ----------- --------------------------------- --------------------------------------------------------- 00401000 /$ 55 push ebp ; backup ebp 00401001 |. 8BEC mov ebp, esp ; backup esp 00401003 |. 83EC 64 sub esp, 64 ; char buffer[100]; 00401006 |. 68 30804000 push 408030 ; ASCII "请输入序列号:\n" 0040100B E8 CB000000 call 00401010 |. 83C4 04 add esp, 4 00401013 |. 8D45 9C lea eax, dword ptr [ebp-64] 00401016 |. 50 push eax 00401017 |. 68 40804000 push 408040 ; ASCII "%s" 0040101C |. E8 A3000000 call ; call scanf 00401021 |. 83C4 08 add esp, 8 00401024 |. 68 44804000 push 408044 ; /Arg2 = ASCII "SN12345" 00401029 |. 8D4D 9C lea ecx, dword ptr [ebp-64] ; | 0040102C |. 51 push ecx ; |Arg1 = [buffer] 0040102D |. E8 2B000000 call ; \call my_strcmp 00401032 |. 85C0 test eax, eax 00401034 |. 75 0F jnz short 00401045 00401036 |. 68 54804000 push 408054 ; ASCII "注册成功!\n" 0040103B |. E8 9B000000 call 00401040 |. 83C4 04 add esp, 4 00401043 |. EB 0D jmp short 00401052 00401045 |> 68 60804000 push 408060 ; ASCII "注册失败!\n" 0040104A |. E8 8C000000 call 0040104F |. 83C4 04 add esp, 4 00401052 |> E8 8A590000 call 00401057 |. 33C0 xor eax, eax 00401059 |. 8BE5 mov esp, ebp ; resume esp 0040105B |. 5D pop ebp ; resume ebp 0040105C \. C3 retn --------------------------------------------------------------------------- ---------------------------------------------- // 自定义函数: int my_strcmp( const char* pszSrc, const char* pszDest ) imgae地址 机器代码 汇编代码 注释 --------- ----------- --------------------------------- --------------------------------------------------------- 0040105D >/$ 55 push ebp ; backup ebp //call+2parms + current = 4 * 4 = 16D = 10H 0040105E |. 8BEC mov ebp, esp ; backup esp 00401060 |. 83EC 0C sub esp, 0C ; 定义三个变量 = 12D = 0CH 00401063 |. 8B45 08 mov eax, dword ptr [ebp+8] ; char* pSrc = (char*)pszSrc; // ebp=push ebp, ebp-4=call, ebp-8=last push param... 00401066 |. 8945 F4 mov dword ptr [ebp-C], eax 00401069 |. 8B4D 0C mov ecx, dword ptr [ebp+C] ; char* pDest = (char*)pszDest; 0040106C |. 894D F8 mov dword ptr [ebp-8], ecx 0040106F |. C745 FC 00000>mov dword ptr [ebp-4], 0 ; int iResult = 0; 00401076 |. EB 12 jmp short 0040108A 00401078 |> 8B55 F4 /mov edx, dword ptr [ebp-C] ; edx = pSrc 0040107B |. 83C2 01 |add edx, 1 ; pSrc++ 0040107E |. 8955 F4 |mov dword ptr [ebp-C], edx ; 00401081 |. 8B45 F8 |mov eax, dword ptr [ebp-8] ; eax = pDest 00401084 |. 83C0 01 |add eax, 1 ; pDest++ 00401087 |. 8945 F8 |mov dword ptr [ebp-8], eax 0040108A |> 8B4D F4 mov ecx, dword ptr [ebp-C] ; *pSrc != 0 0040108D |. 0FBE11 |movsx edx, byte ptr [ecx] 00401090 |. 85D2 |test edx, edx ; 测试是否到串尾 00401092 |. 74 28 |je short 004010BC ; 如果是就退出函数 00401094 |. 8B45 F8 |mov eax, dword ptr [ebp-8] ; *pDest != 0 00401097 |. 0FBE08 |movsx ecx, byte ptr [eax] 0040109A |. 85C9 |test ecx, ecx ; 测试是否到串尾 0040109C |. 74 1E |je short 004010BC ; 如果是就退出函数 0040109E |. 8B55 F4 |mov edx, dword ptr [ebp-C] ; 将pSrc指向的字符 赋给EDX 004010A1 |. 0FBE02 |movsx eax, byte ptr [edx] ; 将pSrc指向的字符赋 给EAX 004010A4 |. 8B4D F8 |mov ecx, dword ptr [ebp-8] ; 将pDest指针赋给 ECX 004010A7 |. 0FBE11 |movsx edx, byte ptr [ecx] ; 将pDest指向的字符赋 给EDX 004010AA |. 2BC2 |sub eax, edx ; iResult = *pSrc - *pDest; 004010AC |. 8945 FC |mov dword ptr [ebp-4], eax 004010AF |. 837D FC 00 |cmp dword ptr [ebp-4], 0 ; if ( iResult != 0 ) 004010B3 |. 74 05 |je short 004010BA ; 如果==0就继续比较下一字符 004010B5 |. 8B45 FC |mov eax, dword ptr [ebp-4] ; 否则就return iResult; 004010B8 |. EB 04 |jmp short 004010BE ; 否则就return iResult; 004010BA |>^ EB BC \jmp short 00401078 ; 继续比较下一字符 004010BC |> 33C0 xor eax, eax 004010BE |> 8BE5 mov esp, ebp ; resume esp 004010C0 |. 5D pop ebp ; resume ebp 004010C1 \. C2 0800 retn 8 ; 执行retn 释放参数栈(pszSrc和pszDest) --------------------------------------------------------------------------- --------------------------------------------- 由上面的代码可看出: 1)由于在编译时我给cl.exe添加了优化选项O2(大写字母o和阿拉伯数字 2),这个选项将会尽最大程度的优化PE的执行速度。 所以这机器代码看起来和C的源程序不太像(具体参照my_strcmp内C程序 的实现及未经优化版本的反汇编代码); 2)if和while及for:根据它们条件的复杂度,相应的编译成适合地跳转指 令; 3)全局变量:被统一放在PE的.data区。在需要使用的代码处都是以地址 操作的; 4)局部变量和参数:都是放在栈中。一般以esp来操作,由于栈是向下伸 长的,所以每增加一个参数的传递(push操作)或是 增加局部变量,都是以“sub esp,”完成的,而它的释放则是“add esp,”。 另外,在跟踪的过程中,我发现CPU在执行call指令时,是先esp-4存 入栈再jmp 的,当执行函数的retn指令时便回收esp+4出栈 ,继续执行下一条指令。虽然这个过程中我们在代码中看 不见,不过这些具体的操作是由call及retn内部实现的。另外,push、pop指 令都是一样的,成对操作!从而完成堆栈平衡的机制。^_^ 5、 在跟踪代码的过程中,明白了之前看别人反汇编代码郁闷的几个地方。那 就是一般CRT函数在进行call之后,编译器不会主动地在CRT函数内帮你释放 参数占用的栈,而是在call之后主动插上一条“add esp, <参数占用的栈数量,以机器字为单位>”来维持堆栈平衡。在自定义的函 数中,我们则无须担心这个问题。编译器会在return处释放参数占用的栈 (retn )。像这种东西只有真正分析过机器代码才知道的。 另外,在未经优化的版本中,所产生的机器代码几乎和C源程序一模一样。 并且在每个函数的实现细节几乎如下: 开头必有: push ebp mov ebp, esp 结尾必有: mov esp, ebp pop ebp 由此大家都可见,未经优化的版本内的局部变量及参数不是直接用esp而是 ebp! 分析完整个流程后,那个心情呀,可真舒畅! ---------------------------------------------------------------------
/
本文档为【实用电脑技术实例-0592】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
热门搜索

历史搜索

    清空历史搜索