脱一个简单的PECompact壳+文件自效验
最近看到不少人讨论文件自效验的修复,刚巧最近碰到一个比较简单的例子,就顺便和新手们分享一下修复的思路。老鸟们请无视。
目标软件是一个****软件,原帖请参考 http://bbs.pediy.com/showthread.php?t=74900
脱壳:
PEiD查壳,显示 PECompact 2.x -> Jeremy Collake
用OD载入ss225.exe,停在00401000。
00401000 > B8 F8686F00 mov eax, 006F68F8
00401005 50 push eax
00401006 64:FF35 00000000 push dword ptr fs:[0]
0040100D 64:8925 00000000 mov dword ptr fs:[0], esp
00401014 33C0 xor eax, eax
00401016 8908 mov dword ptr [eax], ecx
因为壳比较简单,就用ESP定律脱吧
hw 0012ffc0
按F9, 第一次断在
00401006 64:FF35 00000000 push dword ptr fs:[0]
继续F9,断在006F692B
006F692A 55 push ebp
006F692B 53 push ebx
006F692C 51 push ecx
006F692D 57 push edi
006F692E 56 push esi
006F692F 52 push edx
006F6930 8D98 57120010 lea ebx, dword ptr [eax+10001257]
006F6936 8B53 18 mov edx, dword ptr [ebx+18]
往下翻一翻,就能看到一堆pop然后jmp eax,然后就到OEP了。或者此时直接F9,就会停在OEP的下一行。
006358A0 55 push ebp ; <-- OEP
006358A1 8BEC mov ebp, esp
006358A3 83C4 F0 add esp, -10
006358A6 B8 28506300 mov eax, 00635028
006358AB E8 281BDDFF call 004073D8
dump吧,记下入口点偏移 2358A0。dump的时候,2种修复方式都不选。
打开ImportREC,找到ss225.exe,OEP填2358A0,IAT自动搜索,然后获取输入表。发现有2个不能识别的。第一次我直接把他们剪掉了。后来发现程序出错。就跟了一下,来到
0040FD9C /$ 53 push ebx
0040FD9D |. 68 D4FD4000 push 0040FDD4 ; /pModule = "kernel32.dll"
0040FDA2 |. E8 017AFFFF call <jmp.&kernel32.GetModuleHandleA> ; \GetModuleHandleA
0040FDA7 |. 8BD8 mov ebx, eax
0040FDA9 |. 85DB test ebx, ebx
0040FDAB |. 74 10 je short 0040FDBD
0040FDAD |. 68 E4FD4000 push 0040FDE4 ; ASCII "GetDiskFreeSpaceExA"
0040FDB2 |. 53 push ebx ; kernel32.7C800000
0040FDB3 |. E8 087AFFFF call 004077C0
0040FDB8 |. A3 38616300 mov dword ptr [636138], eax
0040FDBD |> 833D 38616300>cmp dword ptr [636138], 0
0040FDC4 |. 75 0A jnz short 0040FDD0
0040FDC6 |. B8 E0AA4000 mov eax, 0040AAE0
0040FDCB |. A3 38616300 mov dword ptr [636138], eax
0040FDD0 |> 5B pop ebx
0040FDD1 \. C3 retn
004077A8 $- FF25 C0836500 jmp dword ptr [<&kernel32.GetModuleH>; kernel32.GetModuleHandleA
004077AE 8BC0 mov eax, eax
004077B0 $- FF25 C0836500 jmp dword ptr [<&kernel32.GetModuleH>; kernel32.GetModuleHandleA
004077B6 8BC0 mov eax, eax
004077B8 $- FF25 BC836500 jmp dword ptr [<&kernel32.GetPrivate>; kernel32.GetPrivateProfileStringA
004077BE 8BC0 mov eax, eax
004077C0 $ FF25 B8836500 jmp dword ptr [6583B8]
004077C6 8BC0 mov eax, eax
004077C8 $- FF25 B4836500 jmp dword ptr [<&kernel32.GetShortPa>; kernel32.GetShortPathNameA
004077CE 8BC0 mov eax, eax
004077D0 $- FF25 B0836500 jmp dword ptr [<&kernel32.GetStdHand>; kernel32.GetStdHandle
004077D6 8BC0 mov eax, eax
ds:[006583B8]=
00000000
Local calls from 0040FDB3, 00410DA9, 00433FB0, 004345EC, 00435E18, 00435E28, 00435E38, 00435E48, 00435E58, 00435E79, 00435E9A, 00435EBB, 00435EDC, 00435EFD, 00435F1E, 00435F3F, 00436472, 00436484, 00436496, 004364A8, 004364BA, 004364CC, 004364DE, ...
上面0040FDB3这里的call就会调用那个不能识别的引入函数(因为被剪掉了,所以里面得值是0)。看看这个call的参数,第一个dll基址,第二个是函数名,那么这个call十有八九是GetProcAddress,用未脱壳的那个exe跟一下这里果然就是GetProcAddress。按刚才的步骤重新dump一下,然后把那两个(其实是同一个)都手动识别成GetProcAddress。
PEiD查壳显示:Borland Delphi 6.0 - 7.0
修复自效验:
脱壳基本完成了。这里还是不能运行,有自效验。用OD载入刚脱好exe。下断点bp CreateFileA。F9跑吧,注意看堆栈信息,只看第一个参数FileName。
第一次断下来:
0012FC18 0040A50B /CALL to CreateFileA from dump00_.0040A506
0012FC1C 01443010 |
FileName = "History.225"
0012FC20 80000000 |Access = GENERIC_READ
0012FC24 00000003 |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
0012FC28 00000000 |pSecurity = NULL
0012FC2C 00000003 |Mode = OPEN_EXISTING
0012FC30 00000080 |Attributes = NORMAL
0012FC34 00000000 \hTemplateFile = NULL
继续F9, 第二次断下来,F9,第三次断下来,F9,第四次断下来 ......
第N次断下来,发现:
0012EF50 0040A50B /CALL to CreateFileA from dump00_.0040A506
0012EF54 01454FF8 |
FileName = "C:\Crackme\ss225\dump00_.exe"
0012EF58 80000000 |Access = GENERIC_READ
0012EF5C 00000003 |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
0012EF60 00000000 |pSecurity = NULL
0012EF64 00000003 |Mode = OPEN_EXISTING
0012EF68 00000080 |Attributes = NORMAL
0012EF6C 00000000 \hTemplateFile = NULL
Alt+F9返回程序领空,可以看到eax的值0000013C,这个就是hFile。这个时候下断 bp SetFilePointer,继续F9,断下
0012EF80 0040A5BB /CALL to SetFilePointer from dump00_.0040A5B6
0012EF84 0000013C |hFile = 0000013C (window)
0012EF88 00000000 |OffsetLo = 0
0012EF8C 0012EFA0 |pOffsetHi = 0012EFA0
0012EF90 00000001 \
Origin = FILE_CURRENT
继续F9,又断下:
0012EF80 0040A5BB /CALL to SetFilePointer from dump00_.0040A5B6
0012EF84 0000013C |hFile = 0000013C (window)
0012EF88 00000000 |OffsetLo = 0
0012EF8C 0012EFA0 |pOffsetHi = 0012EFA0
0012EF90 00000002 \
Origin = FILE_END
看来程序要找出文件末尾的位置了。继续F9,断下发现:
0012EF80 0040A5BB /CALL to SetFilePointer from dump00_.0040A5B6
0012EF84 0000013C |hFile = 0000013C (window)
0012EF88 00000000 |OffsetLo = 0
0012EF8C 0012EFA0 |pOffsetHi = 0012EFA0
0012EF90 00000000 \
Origin = FILE_BEGIN
这下程序也知道文件开头的位置了,看来要计算大小了。Alt+F9返回程序领空,来到0040A5BB
0040A5AC |. 56 push esi ; /Origin
0040A5AD |. 8D45 FC lea eax, dword ptr [ebp-4] ; |
0040A5B0 |. 50 push eax ; |pOffsetHi
0040A5B1 |. 8B45 F8 mov eax, dword ptr [ebp-8] ; |
0040A5B4 |. 50 push eax ; |OffsetLo
0040A5B5 |. 53 push ebx ; |hFile
0040A5B6 |. E8 65D3FFFF call <jmp.&kernel32.SetFilePointer> ; \SetFilePointer
0040A5BB |. 8945 F8 mov dword ptr [ebp-8], eax
0040A5BE |. 8B45 F8 mov eax, dword ptr [ebp-8]
0040A5C1 |. 8B55 FC mov edx, dword ptr [ebp-4]
0040A5C4 |. 5E pop esi
0040A5C5 |. 5B pop ebx
0040A5C6 |. 59 pop ecx
0040A5C7 |. 59 pop ecx
0040A5C8 |. 5D pop ebp
0040A5C9 \. C2 0800 retn 8
接着往回走,看看ret回哪里,来到004224BF
004224A8 /. 55 push ebp
004224A9 |. 8BEC mov ebp, esp
004224AB |. 83C4 F8 add esp, -8
004224AE |. FF75 0C push dword ptr [ebp+C] ; /Arg2
004224B1 |. FF75 08 push dword ptr [ebp+8] ; |Arg1
004224B4 |. 83E2 7F and edx, 7F ; |
004224B7 |. 8B40 04 mov eax, dword ptr [eax+4] ; |
004224BA |. E8 D580FEFF call 0040A594 ; \dump00_.0040A594
004224BF |. 8945 F8 mov dword ptr [ebp-8], eax
004224C2 |. 8955 FC mov dword ptr [ebp-4], edx
004224C5 |. 8B45 F8 mov eax, dword ptr [ebp-8]
004224C8 |. 8B55 FC mov edx, dword ptr [ebp-4]
004224CB |. 59 pop ecx
004224CC |. 59 pop ecx
004224CD |. 5D pop ebp
004224CE \. C2 0800 retn 8
没什么东西,继续ret,来到0042206C
00422063 . 33D2 xor edx, edx
00422065 . 8BC3 mov eax, ebx
00422067 . 8B08 mov ecx, dword ptr [eax]
00422069 . FF51 18 call dword ptr [ecx+18]
0042206C . 8B0424 mov eax, dword ptr [esp]
0042206F . 8B5424 04 mov edx, dword ptr [esp+4]
00422073 . 83C4 10 add esp, 10
00422076 . 5B pop ebx
00422077 . C3 retn
继续ret,来到00623496,这里就是关键了。
0062341C /. 55 push ebp
0062341D |. 8BEC mov ebp, esp
0062341F |. 83C4 F0 add esp, -10
00623422 |. 33C9 xor ecx, ecx
00623424 |. 894D F0 mov dword ptr [ebp-10], ecx
00623427 |. 8955 F8 mov dword ptr [ebp-8], edx
0062342A |. 8945 FC mov dword ptr [ebp-4], eax
0062342D |. 33C0 xor eax, eax
0062342F |. 55 push ebp
00623430 |. 68 77356200 push 00623577
00623435 |. 64:FF30 push dword ptr fs:[eax]
00623438 |. 64:8920 mov dword ptr fs:[eax], esp
0062343B |. 8B45 FC mov eax, dword ptr [ebp-4]
0062343E |. 33D2 xor edx, edx
00623440 |. 8990 88020000 mov dword ptr [eax+288], edx
00623446 |. 8990 8C020000 mov dword ptr [eax+28C], edx
0062344C |. 8B45 FC mov eax, dword ptr [ebp-4]
0062344F |. E8 BCEB0000 call 00632010
00623454 |. E8 175BF5FF call 00578F70
00623459 |. 8B45 FC mov eax, dword ptr [ebp-4]
0062345C |. C680 DC110200>mov byte ptr [eax+211DC], 0
00623463 |. 6A 40 push 40
00623465 |. 8D55 F0 lea edx, dword ptr [ebp-10]
00623468 |. 33C0 xor eax, eax
0062346A |. E8 F1FADDFF call 00402F60
0062346F |. 8B4D F0 mov ecx, dword ptr [ebp-10] ; |
00623472 |. B2 01 mov dl, 1 ; |
00623474 |. A1 E0CC4100 mov eax, dword ptr [41CCE0] ; |
00623479 |. E8 92F0DFFF call 00422510 ; \dump00_.00422510
0062347E |. 8945 F4 mov dword ptr [ebp-C], eax
00623481 |. 33D2 xor edx, edx
00623483 |. 55 push ebp
00623484 |. 68 DE346200 push 006234DE
00623489 |. 64:FF32 push dword ptr fs:[edx]
0062348C |. 64:8922 mov dword ptr fs:[edx], esp
0062348F |. 8B45 F4 mov eax, dword ptr [ebp-C]
00623492 |. 8B10 mov edx, dword ptr [eax]
00623494 |. FF12 call dword ptr [edx]
00623496 |. 8B15 BC226400 mov edx, dword ptr [6422BC] ; dump00_.0064453C
0062349C |. 3B02 cmp eax, dword ptr [edx] ; 比较文件大小
0062349E |. 0F94C2 sete dl
006234A1 |. 8B4D FC mov ecx, dword ptr [ebp-4]
006234A4 |. 8891 DC110200 mov byte ptr [ecx+211DC], dl
006234AA |. 8B15 BC226400 mov edx, dword ptr [6422BC] ; dump00_.0064453C
006234B0 |. 3B02 cmp eax, dword ptr [edx] ; 再比较一次文件大小
006234B2 |. 74 14 je short 006234C8
006234B4 |. 8B45 F4 mov eax, dword ptr [ebp-C]
006234B7 |. E8 EC09DEFF call 00403EA8
006234BC |. A1 30226400 mov eax, dword ptr [642230]
006234C1 |. 8B00 mov eax, dword ptr [eax]
006234C3 |. E8 0052E6FF call 004886C8
006234C8 |> 33C0 xor eax, eax
006234CA |. 5A pop edx
006234CB |. 59 pop ecx
006234CC |. 59 pop ecx
006234CD |. 64:8910 mov dword ptr fs:[eax], edx
006234D0 |. 68 E5346200 push 006234E5
006234D5 |> 8B45 F4 mov eax, dword ptr [ebp-C]
006234D8 |. E8 CB09DEFF call 00403EA8
006234DD \. C3 retn
dword ptr [6422BC] 指向存放原文件大小的地址指针
[edx] 就是原文件大小
eax 是当前文件大小
最简单的改法,把
0062349E |. 0F94C2 sete dl
006234B2 |. 74 14 je short 006234C8
改成
0062349E 0F
95C2 set
ne dl
006234B2
75 14 j
nz short 006234C8
搞定了。
查找自效验的思路:
说一下思路,这种效验文件大小或内容的程序,可以考虑下断以下函数:
CreateFile 判断开始
GetFileSize 判断文件大小
SetFilePointer 判断文件大小/内容
ReadFile 判断文件内容
CloseHandle 判断是否已经结束
如果用的C的库,也要考虑 open,read等另一套文件操作函数。也有一些程序是在内存中完成内容效验的,这样的话可以根据效验失败后程序的行为,下断MessageBox或ExitProcess等函数。如果断不下来的话,可以考虑用OD的追踪功能(时光回溯,统计模块)找到效验点。
这个例子里只用判断了文件大小,只用CreateFileA和SetFilePointer就够了。
关于算法:
最后说一下算法,没有仔细跟,说一下思路好了。那个一次性的注册码,很简单。基本上就是明文比较,内存注册机很方便。
把机器码+用户名+“SS225_USER_REG_DATA”运算一下生成key
验证比较 Base64(用户输入的注册码)和 Base64(Key) 是否相等。
至于永久注册码,可以用找自效验的方法,下断CreateFileA,当FileName是sRegFile.225就下断ReadFile,返回程序慢慢分析就行了。爆破也可以考虑。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)