这是我脱的第一个不能用esp定律搞定的壳,不好的地方还望大家指出。
壳简介:
壳的主要加解密代码在DLL_Loader.dll这个dll中,它由程序在运行时动态解密在最后一个区段中,初始化后就在这个dll中解密原程序及加密输入表了,我们可以dump出完整的dll
。
关于这个dll及壳的详细介绍:
http://www.52pojie.cn/thread-32962-1-1.html
试练品下载:http://www.unpack.cn/thread-35381-1-1.html
先要设置一下OD,StrongOD设置hidePEB,patchfloat勾上,忽略所有异常,而且OD的窗口名改一下。重启OD,载入程序test,开始我们的不归路。
不管有没有试用,它都会访问注册表或者自建的文件或别的什么来查看次数吧。那就先下断RegCreateKeyA,Ctrl+G,输入RegCreateKeyA,来到代码处。下断点就要注意了。因为
壳会将要用到的函数的代码抽取到自己申请的空间中,然后调用。所以在这里下硬件执行断点是段不下来的。像下面这段代码就会被抽取。
vista上的RegCreateKeyA:
77CAB8AE > 8BFF MOV EDI,EDI
77CAB8B0 55 PUSH EBP
77CAB8B1 8BEC MOV EBP,ESP
77CAB8B3 8B55 10 MOV EDX,DWORD PTR SS:[EBP+10]
77CAB8B6 33C0 XOR EAX,EAX
77CAB8B8 3BD0 CMP EDX,EAX
77CAB8BA 0F84 0AA90300 JE ADVAPI32.77CE61CA
77CAB8C0 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C]
77CAB8C3 3BC8 CMP ECX,EAX
77CAB8C5 0F84 07A90300 JE ADVAPI32.77CE61D2
77CAB8CB 3801 CMP BYTE PTR DS:[ECX],AL
77CAB8CD 0F84 FFA80300 JE ADVAPI32.77CE61D2
77CAB8D3 50 PUSH EAX
77CAB8D4 50 PUSH EAX
77CAB8D5 52 PUSH EDX
77CAB8D6 50 PUSH EAX
77CAB8D7 68 00000002 PUSH 2000000
77CAB8DC 50 PUSH EAX
77CAB8DD 50 PUSH EAX
77CAB8DE 50 PUSH EAX
77CAB8DF 51 PUSH ECX
77CAB8E0 FF75 08 PUSH DWORD PTR SS:[EBP+8]
77CAB8E3 E8 82FEFFFF CALL ADVAPI32._RegCreateKeyExInternalA@40
77CAB8E8 5D POP EBP
77CAB8E9 C2 0C00 RETN 0C
大家看到CALL ADVAPI32._RegCreateKeyExInternalA@40,我们可以跟进在它里面下硬件执行断点。顺便说一下xp上与这不同,要找别的地方了。
F9运行,会断下来几次,这些都不管,直到点了try断下后,Alt+F9来到程序领空,
01C24AC5 FF75 10 PUSH DWORD PTR SS:[EBP+10]
01C24AC8 FF75 0C PUSH DWORD PTR SS:[EBP+C]
01C24ACB FF75 08 PUSH DWORD PTR SS:[EBP+8]
01C24ACE E8 976C0876 CALL ADVAPI32._RegCreateKeyExInternalA@40
01C24AD3 5D POP EBP ; 0012FF44
01C24AD4 C2 2400 RETN 24
看并不在advapi32.dll领空,F7走出去:
0058DBF8 85C0 TEST EAX,EAX
0058DBFA 74 04 JE SHORT test.0058DC00
0058DBFC 33C0 XOR EAX,EAX
0058DBFE EB 02 JMP SHORT test.0058DC02
0058DC00 B0 01 MOV AL,1
0058DC02 8BD8 MOV EBX,EAX
0058DC04 84DB TEST BL,BL
0058DC06 74 25 JE SHORT test.0058DC2D
0058DC08 8D4424 0C LEA EAX,DWORD PTR SS:[ESP+C]
0058DC0C 50 PUSH EAX
0058DC0D 8D4424 14 LEA EAX,DWORD PTR SS:[ESP+14]
0058DC11 50 PUSH EAX
0058DC12 8D4424 10 LEA EAX,DWORD PTR SS:[ESP+10]
0058DC16 50 PUSH EAX
0058DC17 6A 00 PUSH 0
0058DC19 E8 DEFEFFFF CALL test.0058DAFC
0058DC1E 50 PUSH EAX
0058DC1F 8B4424 14 MOV EAX,DWORD PTR SS:[ESP+14]
0058DC23 50 PUSH EAX
0058DC24 E8 0F57F7FF CALL test.00503338
0058DC29 85C0 TEST EAX,EAX
0058DC2B 74 04 JE SHORT test.0058DC31
0058DC2D 33C0 XOR EAX,EAX
0058DC2F EB 02 JMP SHORT test.0058DC33
0058DC31 B0 01 MOV AL,1
0058DC33 8BD8 MOV EBX,EAX
0058DC35 833C24 00 CMP DWORD PTR SS:[ESP],0
0058DC39 74 09 JE SHORT test.0058DC44
0058DC3B 8B0424 MOV EAX,DWORD PTR SS:[ESP]
0058DC3E 50 PUSH EAX
0058DC3F E8 C456F7FF CALL test.00503308
0058DC44 8BC3 MOV EAX,EBX ;无使用次数是ebx为1
0058DC46 81C4 10010000 ADD ESP,110
0058DC4C 5B POP EBX
0058DC4D C3 RETN
这个地方就是对使用次数的验证了,结果放在eax中,只要eax返回0就行了。该eax为0删除断点,一路F8(有的时候到了关键代码的附近,最有效的办法就是F8,看看停在那个Call
处)。一直到这:
0059D368 80B8 5E040000 00 CMP BYTE PTR DS:[EAX+45E],0
0059D36F 74 05 JE SHORT test.0059D376
0059D371 E8 9AA4FFFF CALL test.00597810
0059D376 E8 1180FEFF CALL test.0058538C
0059D37B E8 14E7FFFF CALL test.0059BA94
0059D380 E8 8F13FFFF CALL test.0058E714 ;从这个Call出来
0059D385 E8 EE1AFFFF CALL test.0058EE78
0059D38A E8 F916FFFF CALL test.0058EA88
0059D38F E8 8C7FFEFF CALL test.00585320
0059D394 E8 FB80FEFF CALL test.00585494
0059D399 E8 DE60FEFF CALL test.0058347C
0059D39E E8 C581FEFF CALL test.00585568 ;这里要注意
0059D3A3 50 PUSH EAX
0059D3A4 89C1 MOV ECX,EAX
0059D3A6 B8 E4D35900 MOV EAX,test.0059D3E4
0059D3AB E8 6481FEFF CALL test.00585514
0059D3B0 010424 ADD DWORD PTR SS:[ESP],EAX
0059D3B3 C3 RETN
到这里后,就不能F8了,可能有时间验证吧,直接F4到ADD DWORD PTR SS:[ESP],EAX,,再往下F8就不行了。
在这里下硬件执行断点 记作 “第一个断点”
Ctrl+F2重新载入,再来看看壳对输入表的处理,下断CreateWindowExA,这次就直接在函数入口下硬件访问断点,为了直接到关键代码处,可以在注册框出来后下断。点try按钮,
断下:
0057ECF4 55 PUSH EBP
0057ECF5 8BEC MOV EBP,ESP
0057ECF7 53 PUSH EBX
0057ECF8 56 PUSH ESI
0057ECF9 57 PUSH EDI
0057ECFA 8BF0 MOV ESI,EAX
0057ECFC 33FF XOR EDI,EDI
0057ECFE C602 00 MOV BYTE PTR DS:[EDX],0
0057ED01 33C0 XOR EAX,EAX
0057ED03 8901 MOV DWORD PTR DS:[ECX],EAX
0057ED05 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
0057ED08 33DB XOR EBX,EBX
0057ED0A 8918 MOV DWORD PTR DS:[EAX],EBX
0057ED0C 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
0057ED0F 33DB XOR EBX,EBX
0057ED11 8918 MOV DWORD PTR DS:[EAX],EBX
0057ED13 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0057ED16 33DB XOR EBX,EBX
0057ED18 8918 MOV DWORD PTR DS:[EAX],EBX
0057ED1A 33C0 XOR EAX,EAX
0057ED1C 8A06 MOV AL,BYTE PTR DS:[ESI]
0057ED1E 3D FF000000 CMP EAX,0FF ;断在这
0057ED23 0F87 BC3E0000 JA test.00582BE5
0057ED29 8A80 36ED5700 MOV AL,BYTE PTR DS:[EAX+57ED36]
0057ED2F FF2485 36EE5700 JMP DWORD PTR DS:[EAX*4+57EE36]
0057ED36 0001 ADD BYTE PTR DS:[ECX],AL
0057ED38 0203 ADD AL,BYTE PTR DS:[EBX]
0057ED3A 04 00 ADD AL,0
0057ED3C 0000 ADD BYTE PTR DS:[EAX],AL
0057ED3E 0005 00060000 ADD BYTE PTR DS:[600],AL
0057ED44 0007 ADD BYTE PTR DS:[EDI],AL
0057ED46 0000 ADD BYTE PTR DS:[EAX],AL
这里就是壳抽取函数代码及重定向输入表的地方,看看堆栈:
0012FEC0 |00000000
0012FEC4 |00000000
0012FEC8 |00000008
0012FECC |00000005
0012FED0 |00AFFEF4 UNICODE "3B0"
0012FED4 |01BAFA30
0012FED8 |01BB472B
0012FEDC |01BB46FD
0012FEE0 |00000000
0012FEE4 |0059D317 test.0059D317
0012FEE8 |01BAE718
0012FEDC里就是存放CreateWindowExA的代码的地址,那么这个地址肯定要写入程序输入表的IAT中,在0012FEDC下内存访问断点,断在这里:
005958FC 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4]
005958FF 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10] ;读取重定向地址
00595902 894C82 04 MOV DWORD PTR DS:[EDX+EAX*4+4],ECX ;放入程序输入表,要将这 这里nop掉
00595906 47 INC EDI
00595907 FF4D D0 DEC DWORD PTR SS:[EBP-30]
开始准备跟一下输入表加密的,跟着跟着就迷糊了,就放弃了。于是就走了一条不归路。
从输入表加密中出来,就到这:
0059D556 8B00 MOV EAX,DWORD PTR DS:[EAX]
0059D558 E8 CF7DFFFF CALL test.0059532C ;输入表加密
0059D55D A1 E0EF5A00 MOV EAX,DWORD PTR DS:[5AEFE0]
0059D562 80B8 705E0000 00 CMP BYTE PTR DS:[EAX+5E70],0
0059D569 74 07 JE SHORT test.0059D572
0059D56B E8 007BFFFF CALL test.00595070
0059D570 EB 05 JMP SHORT test.0059D577
0059D572 E8 BD7BFFFF CALL test.00595134
0059D577 E8 B4F3FFFF CALL test.0059C930
0059D57C E8 EF82FEFF CALL test.00585870
0059D581 E8 02E7FEFF CALL test.0058BC88
0059D586 E8 71A1FFFF CALL test.005976FC
0059D58B E8 E0A0FFFF CALL test.00597670
0059D590 E8 6B86FFFF CALL test.00595C00
0059D595 E8 DED7FDFF CALL test.0057AD78
0059D59A E8 C919FFFF CALL test.0058EF68
0059D59F 33C0 XOR EAX,EAX
0059D5A1 5A POP EDX
0059D5A2 59 POP ECX
0059D5A3 59 POP ECX
0059D5A4 64:8910 MOV DWORD PTR FS:[EAX],EDX
0059D5A7 EB 1D JMP SHORT test.0059D5C6
0059D5A9 ^ E9 C630F6FF JMP test.00500674
0059D5AE 6A 00 PUSH 0
0059D5B0 68 94D75900 PUSH test.0059D794 ; ASCII "The Enigma Protector"
0059D5B5 68 ACD75900 PUSH test.0059D7AC ; ASCII "Internal Protection Error, please contact to author!"
0059D5BA 6A 00 PUSH 0
0059D5BC E8 6768F6FF CALL test.00503E28
0059D5C1 E8 1634F6FF CALL test.005009DC
0059D5C6 B2 01 MOV DL,1
0059D5C8 33C0 XOR EAX,EAX
0059D5CA E8 415DFEFF CALL test.00583310
0059D5CF E8 947FFEFF CALL test.00585568 ;到这里大家是不是很熟悉啊,就是上面叫大家注意的地方一样
0059D5D4 50 PUSH EAX ;自效验返回值入栈
0059D5D5 89C1 MOV ECX,EAX ;将eax值放入ecx
0059D5D7 B8 15D65900 MOV EAX,test.0059D615 ;将0059D615放入eax
0059D5DC E8 337FFEFF CALL test.00585514 ;计算 大家有兴趣可以跟一下
0059D5E1 010424 ADD DWORD PTR SS:[ESP],EAX ;返回值如上面Call的返回值相加
0059D5E4 C3 RETN ;相加的结果ret到eip
其实这个地方是一段自效验代码,然后通过结果计算eip,只要代码被修改那么eip就是一个错误的值,不过这个地方很好跳过,大家看到没,eip已经给我们了,就是0059D615 ,
执行retn时,只要将【esp】改为0059D615就可以了。
在RETN 处下硬件执行断点,记作 “第二个断点”
再次一路F8
0059D711 8B92 1C010000 MOV EDX,DWORD PTR DS:[EDX+11C]
0059D717 E8 B825FFFF CALL test.0058FCD4
0059D71C 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
0059D71F 50 PUSH EAX
0059D720 A1 E0EF5A00 MOV EAX,DWORD PTR DS:[5AEFE0]
0059D725 8B80 1C010000 MOV EAX,DWORD PTR DS:[EAX+11C]
0059D72B 50 PUSH EAX
0059D72C 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0059D72F 50 PUSH EAX
0059D730 E8 AF25FFFF CALL test.0058FCE4 ;到这跟进
0059D735 E8 D6A0FFFF CALL test.00597810
0059D73A 33C0 XOR EAX,EAX
0059D73C 5A POP EDX
0059D73D 59 POP ECX
0059D73E 59 POP ECX
0059D73F 64:8910 MOV DWORD PTR FS:[EAX],EDX
0059D742 68 5CD75900 PUSH test.0059D75C
0059D747 8D45 D4 LEA EAX,DWORD PTR SS:[EBP-2C]
0059D74A BA 05000000 MOV EDX,5
0059D74F E8 3C39F6FF CALL test.00501090
0059D754 C3 RETN
进去之后,你可以一路F8,也可以拖动滚动条到程序最后,或者查找jmp eax,都可以到:
005903C8 E8 5FD8FAFF CALL test.0053DC2C
005903CD 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
005903D0 FFE0 JMP EAX ;到这就差不多到壳抽取的程序oep了
005903D2 5F POP EDI
005903D3 5E POP ESI
005903D4 5B POP EBX
005903D5 8BE5 MOV ESP,EBP
005903D7 5D POP EBP
005903D8 C2 0C00 RETN 0C
进入后就是大量的跳转及花指令,还有程序自效验,一断程序被修改就会出错,我们再次查找jmp eax,记住不能选择“整个块”查找,找到后,F7进入,就到了oep处了。
01B77950 55 PUSH EBP
01B77951 8BEC MOV EBP,ESP
01B77953 83C4 F0 ADD ESP,-10
01B77956 B8 30E74700 MOV EAX,47E730
01B7795B E8 E4E888FE CALL test.00406244 ;更正
01B77960 8B05 BC024800 MOV EAX,DWORD PTR DS:[4802BC] ; test.00481BDC
01B77966 8B40 00 MOV EAX,DWORD PTR DS:[EAX]
01B77969 E8 E6B98DFE CALL test.00453354 ;更正
01B7796E 8B0D B0034800 MOV ECX,DWORD PTR DS:[4803B0] ; test.00481CF4
01B77974 8B05 BC024800 MOV EAX,DWORD PTR DS:[4802BC] ; test.00481BDC
01B7797A 8B40 00 MOV EAX,DWORD PTR DS:[EAX]
01B7797D 8B15 D4E44700 MOV EDX,DWORD PTR DS:[47E4D4] ; test.0047E520
01B77983 E8 E4B98DFE CALL test.0045336C ;更正
01B77988 8B05 BC024800 MOV EAX,DWORD PTR DS:[4802BC] ; test.00481BDC
01B7798E 8B40 00 MOV EAX,DWORD PTR DS:[EAX]
01B77991 E8 56BA8DFE CALL test.004533EC ;更正
01B77996 E8 99C788FE CALL test.00404134 ;更正
01B7799B 8D40 00 LEA EAX,DWORD PTR DS:[EAX]
01B7799E - E9 357090FE JMP test.0047E9D8 ;更正
将这段程序的二进制代码保存,重新载入程序运行,在“第一个断点”时,查找 “MOV DWORD PTR DS:[EDX+EAX*4+4],ECX”,将这段nop掉,总共有四处,改后F9继续运行,在“
第二个断点”,必须将【esp】改为0059D615,原因上面已经说过。按上面的,来到第一个jmp eax,到这里就不能跟进了。
然后在程序代码段最后全为零的地方,将上面保存的代码粘贴在那,Call地址要更正啊,改变eip指向这里。
到这里似乎就差不多了,只要将输入表修复一下就OK了,相信大部分人是这么想的,当时我也是这么想的,事实是你们错了,我也错了,鬼才知道下面有多难。
下面是壳对输入表一部分的处理:
00401202 8BC0 MOV EAX,EAX
00401204 $- FF25 20ACBF01 JMP DWORD PTR DS:[1BFAC20] ; kernel32.GetStdHandle
0040120A 8BC0 MOV EAX,EAX
0040120C .- FF25 14ACBF01 JMP DWORD PTR DS:[1BFAC14] ; kernel32.RaiseException
00401212 8BC0 MOV EAX,EAX
00401214 .- FF25 08ACBF01 JMP DWORD PTR DS:[1BFAC08] ; ntdll.RtlUnwind
0040121A 8BC0 MOV EAX,EAX
0040121C $- FF25 FCABBF01 JMP DWORD PTR DS:[1BFABFC] ; kernel32.UnhandledExceptionFilter
00401222 8BC0 MOV EAX,EAX
00401224 $- FF25 F0ABBF01 JMP DWORD PTR DS:[1BFABF0] ; kernel32.WriteFile
0040122A 8BC0 MOV EAX,EAX
0040122C $- FF25 50ACBF01 JMP DWORD PTR DS:[1BFAC50] ; user32.CharNextA
00401232 8BC0 MOV EAX,EAX
00401234 $- FF25 E4ABBF01 JMP DWORD PTR DS:[1BFABE4] ; kernel32.ExitProcess
0040123A 8BC0 MOV EAX,EAX
0040123C $- FF25 44ACBF01 JMP DWORD PTR DS:[1BFAC44] ; user32.MessageBoxA
00401242 8BC0 MOV EAX,EAX
00401244 $- FF25 D8ABBF01 JMP DWORD PTR DS:[1BFABD8] ; kernel32.FindClose
0040124A 8BC0 MOV EAX,EAX
0040124C $- FF25 CCABBF01 JMP DWORD PTR DS:[1BFABCC] ; kernel32.FindFirstFileA
看这里:
00406F1A 8BC0 MOV EAX,EAX
00406F1C /$ E8 3FFFFFFF CALL test.00406E60
00406F21 \. C3 RETN
00406F22 8BC0 MOV EAX,EAX
00406F24 $- FF25 F4B3BF01 JMP DWORD PTR DS:[1BFB3F4] ; user32.CreateWindowExA
00406F2A 8BC0 MOV EAX,EAX
00406F2C /$ 55 PUSH EBP
00406F2D |. 8BEC MOV EBP,ESP
还有这里:
0040DCE0 . 30 78 00 ASCII "0x",0
0040DCE3 00 DB 00
0040DCE4 $- FF25 88BCBF01 JMP DWORD PTR DS:[1BFBC88] ; oleaut32.VariantInit
0040DCEA 8BC0 MOV EAX,EAX
0040DCEC $- FF25 7CBCBF01 JMP DWORD PTR DS:[1BFBC7C] ; oleaut32.VariantClear
0040DCF2 8BC0 MOV EAX,EAX
0040DCF4 $- FF25 70BCBF01 JMP DWORD PTR DS:[1BFBC70] ; oleaut32.VariantCopy
0040DCFA 8BC0 MOV EAX,EAX
0040DCFC $- FF25 64BCBF01 JMP DWORD PTR DS:[1BFBC64] ; oleaut32.VariantChangeType
0040DD02 8BC0 MOV EAX,EAX
0040DD04 /. 55 PUSH EBP
0040DD05 |. 8BEC MOV EBP,ESP
壳将输入表IAT全部打烂,不仅不放在一起,而且相互夹杂,真是变态啊,这样ImportREC根本不能修复。这时我想到了“脚本”,虽然我从来没有写过,但脚本这两个字已是如雷
贯耳。首先用zeroadd为test加一个节,大小2000,然后写个脚本将所有的函数地址写入新加的节中,并将IAT改回,再就是大家熟悉的loader,ImportREC修复了。
至此,壳就脱了,当然还要优化,就留给感兴趣的人去做了。
ps:这个壳脱得很繁琐,输入表也没有优化,而且要找到所以的IAT地址,是很麻烦的。期待高手更好的方法。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)