一路走过之后发现路程很遥远,有时候也会觉得很迷茫。
但看到前辈们不辞辛饶发布教程以及心得,所以我也愿意把这举步维艰的路程走完 。
希望你们在学习时候给自己定下目标 然后思考如何实现 最后则是准备用多长的时间去实现,
在开始脱壳之前了解下调试器,我们通常可以看到之前所找的资料中都有谈到SoftICE调试器和TRW2000(only for Win 9x/Me),不过这两个调试现在已经渐渐退出了自己的舞台,毕竟2010年了很少看到有人用window 98,window2000 。我们看到最多的则是xp win7 vista
不过建议还是在XP下使用OD调试的好 (http://pediy.com/tools/Debuggers.htm) OllyDbg[2008.1.1]
看雪上贴出的这个版本 还是看起来很友好的,不过建议标配三个左右OD版本(其中也应该有一个英文版原因在后面的课程中讲解)
然后我们我们进入到OD的根目录,创建一个新的OD快捷方式 ,把它放到C:\Documents and Settings\Administrator\SendTo ,之后就可以看到我们对目标程序反编译,就比较方便了 具体OD方面的内容你可以(http://bbs.pediy.com/showthread.php?s=&threadid=21284)
这里有vc6.0++ MFC 生成的一个简单的程序,
并且用UPX加壳工具试着Add file 然后点击 compress ,压缩之后我们对比加壳前的大小44.0 K,加壳后的大小 30.0 KB 然后运行,程序照常加壳之后我们用peid载入
(或者用Fi)就可以查壳,像一些大型的软件也有些会用到upx (比如魔兽争霸)。
其实压缩壳的目的很简单,就是对目标程序进行进一步的优化,改变文件大小,提高程序运行速度。
另一方面来讲,如果你需要破解的软件是压缩壳,那么就只能恭喜你了,至少脱壳的部分你可以少走很多路,
总的来说在破解分析过程难度远小于脱壳的难度。(当然到后面的教程中会讲解到修复,以及具体的破解方法。)
这里贴出OD帮助中快捷键内容:
快捷键:
F3,装入程序
F8,单步执行,不进入call
F9 (运行)
F7,单步执行,进入call
CTRL+F9,相当于trw2000的F12
ALT+F9,相关于trw2000的pmodule
F2,设置断点(相当于trw2000的F9)
CTRL+N(当前模块中的名称)
F12(暂定)
CTRL+F12(重新运行)
CTRL+F11(跟踪进入)
CTRL+T(设置条件)
CTRL+F7(自动步入)
CTRL+F8(自动步过)
CTRL+F9(执行到返回)
用OD加载目标程序test.exe后
反汇编窗口显示:
0040C4B0 > 60 pushad
0040C4B1 BE 00604000 mov esi,test.00406000
0040C4B6 8DBE 00B0FFFF lea edi,dword ptr ds:[esi+FFFFB000]
0040C4BC 57 push edi
0040C4BD 83CD FF or ebp,FFFFFFFF
0040C4C0 EB 10 jmp short test.0040C4D2
0040C4C2 90 nop
0040C4C3 90 nop
0040C4C4 90 nop
0040C4C5 90 nop
0040C4C6 90 nop
0040C4C7 90 nop
0040C4C8 8A06 mov al,byte ptr ds:[esi]
0040C4CA 46 inc esi
0040C4CB 8807 mov byte ptr ds:[edi],al
0040C4CD 47 inc edi
0040C4CE 01DB add ebx,ebx
0040C4D0 75 07 jnz short test.0040C4D9
0040C4D2 8B1E mov ebx,dword ptr ds:[esi]
pushad是一个入栈标志位,简单理解就是把数据压入堆栈当中,而相对应的则是popad出栈标志位。
现在所做的内容 就是把压缩过的程序一步一步的解压,可以理解为在程序外部包裹着一层保护膜,去除保护膜后就能到达源程序的入口点,也就是人们常说的OEP ,所以带壳的程序也有一个入口点(通常叫做EP),在本程序中的EP就是虚拟地址就是0040C4B0 (相对虚拟地址(RVA)=虚拟地址(VA)-基址(ImageBase))
然后我们用快捷键F8单步走,如果有指向上面的跳转就用F4(运行到选定位置)
0040C4C7 90 nop
0040C4C8 8A06 mov al,byte ptr ds:[esi]
0040C4CA 46 inc esi
0040C4CB 8807 mov byte ptr ds:[edi],al
0040C4CD 47 inc edi
0040C4CE 01DB add ebx,ebx
0040C4D0 75 07 jnz short test.0040C4D9
0040C4D2 8B1E mov ebx,dword ptr ds:[esi]
0040C4D4 83EE FC sub esi,-4
0040C4D7 11DB adc ebx,ebx
0040C4D9 ^ 72 ED jb short test.0040C4C8
0040C4DB B8 01000000 mov eax,1
jb short test.0040C4C8 跳转指向 0040C4C8
如果用F8单步走,就会到 0040C4C8 8A06 mov al,byte ptr ds:[esi]
所以应该用F4(运行到选定位置)到达0040C4DB B8 01000000 mov eax,1
下面有遇到此种问题,处理是一样的。
0040C5D2 FF96 54C90000 call dword ptr ds:[esi+C954] ; kernel32.LoadLibraryA
0040C5D8 95 xchg eax,ebp
0040C5D9 8A07 mov al,byte ptr ds:[edi]
0040C5DB 47 inc edi
0040C5DC 08C0 or al,al
0040C5DE ^ 74 DC je short test.0040C5BC
0040C5E0 89F9 mov ecx,edi
0040C5E2 79 07 jns short test.0040C5EB
0040C5E4 0FB707 movzx eax,word ptr ds:[edi]
0040C5E7 47 inc edi
到这里之后我们遇到程序的第一个CALL (CALL就是调用子程序,可以理解为C语言中的函数概念,这里的调用的有可能是程序自己的子函数,而另一种可能就是系统API,比如GetModuleHandleA ,VirtualAlloc,LoadLibraryA
等)
当然也有可能是变形的jmp,我们可以看到OD反汇编窗口注释(英文版本的是comment); kernel32.LoadLibraryA 所以这个CALL调用的是一个系统的API
如果用F7跟进去的话 下一句就是:
7C801D77 kernel32.LoadLibraryA 8BFF mov edi,edi ; test.0040B008
7C801D79 55 push ebp
7C801D7A 8BEC mov ebp,esp
7C801D7C 837D 08 00 cmp dword ptr ss:[ebp+8],0
7C801D80 53 push ebx
7C801D81 56 push esi
7C801D82 74 14 je short kernel32.7C801D98
7C801D84 68 C0E0807C push kernel32.7C80E0C0 ; ASCII "twain_32.dll"
7C801D89 FF75 08 push dword ptr ss:[ebp+8]
7C801D8C FF15 9C13807C call dword ptr ds:[<&ntdll._strcmpi>] ; ntdll._stricmp
7C801D92 85C0 test eax,eax
7C801D94 59 pop ecx
我们很清除的看到地址(address)这行马上从0040C5D2 转到 7C801D77 ,所以以后大家在调试过程中在程序正常跟进时突然就跳转到其他区段(显示内存窗口 (Alt+M)可以看到一些区段内容)上去了,这里
就涉及到领空的问题0040C5D2所在地址就是程序领空,而7C801D77是系统 LoadLibraryA API 所以就是系统领空,在后续的课程中则会遇到其他的加密壳中seh(结构化异常处理 ),也通常的说成暗桩。
当然如果你进入了系统领空就只能慢慢的走出来,而如果只是一个API的话 可以用快捷建(ALT+F9)直接返回。
0040C5D8 95 xchg eax,ebp
这句就是返回的结果
0040C5FF ^\EB D8 jmp short test.0040C5D9
0040C601 FF96 68C90000 call dword ptr ds:[esi+C968]
0040C607 8BAE 5CC90000 mov ebp,dword ptr ds:[esi+C95C]
0040C60D 8DBE 00F0FFFF lea edi,dword ptr ds:[esi-1000]
0040C613 BB 00100000 mov ebx,1000
0040C618 50 push eax
0040C619 54 push esp
0040C61A 6A 04 push 4
0040C61C 53 push ebx
0040C61D 57 push edi
0040C61E FFD5 call ebp
0040C620 8D87 F7010000 lea eax,dword ptr ds:[edi+1F7]
走到这里之后我们可以看到jmp(无条件跳转)指向 上面我们用F8跟过后就会一直的做循环,但如果我们用F4(运行到选定位置) 到0040C601 FF96 68C90000 call dword ptr ds:[esi+C968]
程序就直接跑飞(程序运行), 那应该怎么办呢? 没办法我们试试往下一行,也就是 0040C607 8BAE 5CC90000 mov ebp,dword ptr ds:[esi+C95C] F4(运行到选定位置)
呵呵果然可以 ,
然后一直,走到
0040C644 - E9 B789FFFF jmp test.00405000
后在再次一步F8
00405000 E8 00000000 call test.00405005
00405005 5B pop ebx
00405006 81EB 05024000 sub ebx,test.00400205
0040500C 64:8B3D 3000000>mov edi,dword ptr fs:[30]
00405013 8B7F 0C mov edi,dword ptr ds:[edi+C]
00405016 8B7F 1C mov edi,dword ptr ds:[edi+1C]
00405019 8B3F mov edi,dword ptr ds:[edi]
0040501B 8B7F 08 mov edi,dword ptr ds:[edi+8]
0040501E 89BB C2034000 mov dword ptr ds:[ebx+4003C2],edi
00405024 8BF7 mov esi,edi
很遗憾的告诉你这个不是OEP ,看样子这个版本的UPX有的不同不过并不影响 ,那我们只能继续走了,
看到call test.00405005 这是就是一个变形的jmp它会跳转到00405005 ,不过这句你F8,F7都不影响最后结果 但为了有一个好习惯 ,还是建议你用F7(前辈的经验变形CALL尽量用F7)
继续F8走 会经过
004050A3 FFD7 call edi ; kernel32.GetProcAddress
004050B1 FFD0 call eax ; kernel32.GetTempPathA
004050C1 FFD7 call edi ; kernel32.GetProcAddress
004050E0 FFD7 call edi ; kernel32.GetProcAddress
004050F7 FFD0 call eax ; kernel32.CreateFileA
0040510A FFD7 call edi ; kernel32.GetProcAddress
00405153 8932 mov dword ptr ds:[edx],esi
00405155 83C2 04 add edx,4
00405158 ^ E2 F9 loopd short test.00405153 \loopd 循环语句
0040515A BA 44000000 mov edx,44
0040515F 891424 mov dword ptr ss:[esp],edx
00405162 83EC 10 sub esp,10
00405165 8BD4 mov edx,esp
0040516C 6A 00 push 0
0040516E 6A 00 push 0
00405170 6A 00 push 0
00405172 6A 00 push 0
00405174 6A 00 push 0
00405176 6A 00 push 0
00405178 6A 00 push 0
0040517A 83C2 44 add edx,44
0040517D 52 push edx
0040517E FFD0 call eax
00405180 8BE5 mov esp,ebp
00405182 ^ E9 59C5FFFF jmp test.004016E0
到了这之后我们看到一点曙光然后jmp 跟过F8 来到
004016E0 55 push ebp
004016E1 8BEC mov ebp,esp
004016E3 6A FF push -1
004016E5 68 F8244000 push test.004024F8
004016EA 68 66184000 push test.00401866 ; jmp 到
004016EF 64:A1 00000000 mov eax,dword ptr fs:[0]
004016F5 50 push eax
004016F6 64:8925 0000000>mov dword ptr fs:[0],esp
004016FD 83EC 68 sub esp,68
00401700 53 push ebx
00401701 56 push esi
00401702 57 push edi
00401703 8965 E8 mov dword ptr ss:[ebp-18],esp
00401706 33DB xor ebx,ebx
00401708 895D FC mov dword ptr ss:[ebp-4],ebx
0040170B 6A 02 push 2
0040170D FF15 90214000 call dword ptr ds:[402190] ; msvcrt.__set_app_type
告诉你很辛苦的你来到了,OEP(程序入口点)
note: 不能段在nop(空数据)上 要不程序也会跑飞。
好了,今天就这么多了。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)