这东西比较适合练耐性还有就是复习一下各种ANTI DEBUG,还有就是测试一下你OD的抗ANTI能力,假如过不了某写ANTI,就得去找插件或者自己动手了。这里涉及的ANTI有时间校验、ISDEBUG标记、检测Drx、检测INT3、ZwQueryInformationProcess、OUTPUTDEBUGSTRING文件自校验和通过CREATEFILE ANTI DUMP,最后是一个压缩壳。
原程序可在下面连接下载:
http://www.pediy.com/tools/PACK/Protectors/MSLRH/MSLRHv0.31a.rar
这里是已脱壳的程序
http://www.nxer.cn/709394/attachment/1163338396_0.rar
由于有大量时间检查,于是便找来ULTRAEDIT把RDTSC直接替换成XOR EAX,EAX了。运行程序发现出错,有自校验,只好细心跟了,跟了大半天,终于到了自校验的位置,由于不是简单的对比加跳转,必须还原改动过的值。只好去论坛找文章看看有没有更好的方法对付RDTSC。查到前辈写的脱这个的文章,知道了DEJUNK插件,学习了一下,写了下面脚本:
ActivePatList1 = _mslrh01
[CODE_mslrh01]
S = 0f31
R = 33c0
后面有2种解码,分别是XOR 11和XOR 15,所以对应3种情况,替换了825处。
看了前辈的文章之后,对一些之前不明白的ANTI也较为清楚了,下面按流程一步一步列出来。
00456AA0 810424 6F130000 add dword ptr ss:[esp],136F
00456AA7 64:FF35 0000000>push dword ptr fs:[0]
00456AAE 64:8925 0000000>mov dword ptr fs:[0],esp
00456AB5 EB 05 jmp short [MSLRH].00456ABC
装了SEH之后便是
0045745C 33C0 xor eax,eax
0045745E 0FB600 movzx eax,byte ptr ds:[eax]
内存访问错误,进入SEH后继续。大量相同规律的花指令,程序没有乱序,方向总是向下,有意义的代码没怎么花,时间校验的cmp eax,0fff也没花。这是第一次跟这个壳的时候总结的经验,所以可以向下寻找没有花的地方,然后在最接近的cmp eax,0fff直接跳过去,这是第一次跟这个程序时总结出来的经验。
004587B6 8B4424 0C mov eax,dword ptr ss:[esp+C]
004587BA 33C9 xor ecx,ecx
004587BC 3348 04 xor ecx,dword ptr ds:[eax+4]
004587BF 3348 08 xor ecx,dword ptr ds:[eax+8]
004587C2 3348 0C xor ecx,dword ptr ds:[eax+C]
004587C5 3348 10 xor ecx,dword ptr ds:[eax+10]
004587C8 8B6424 08 mov esp,dword ptr ss:[esp+8]
004587CC 64:8F05 0000000>pop dword ptr fs:[0]
这里检查dr0到dr3是否为0,没有下硬断,则ecx为0
00459191 51 push ecx
00459192 33C9 xor ecx,ecx
00459194 E8 00000000 call [MSLRH].00459199
00459199 5F pop edi
0045919A 81C7 C4090000 add edi,9C4
004591A0 5A pop edx
004591A1 83C2 15 add edx,15
004591A4 0FB60439 movzx eax,byte ptr ds:[ecx+edi]
004591A8 33C2 xor eax,edx
004591AA 880439 mov byte ptr ds:[ecx+edi],al
004591AD 41 inc ecx
004591AE 81F9 93000000 cmp ecx,93
004591B4 ^ 72 EE jb short [MSLRH].004591A4
004591B6 EB 05 jmp short [MSLRH].004591BD
这段是对00459b5d-00459bf0进行解码(简单的XOR)。假如曾经下过硬断,那么这里的KEY就不对了。正确的是15。解码前要恢复为清除时间校验而覆盖的数据,解码之后再此清除时间校验,后同。
00459B5D 8B5C24 20 mov ebx,dword ptr ss:[esp+20]
00459B61 66:BB 0000 mov bx,0
00459B65 0FB703 movzx eax,word ptr ds:[ebx]
00459B68 2D 4D5A0000 sub eax,5A4D
00459B6D 74 08 je short 复件_[MS.00459B77
00459B6F 81EB 00000100 sub ebx,10000
00459B75 ^ EB EE jmp short 复件_[MS.00459B65
通过在堆栈中返回系统的地址找KERNEL32的基址。Billy Belceb病毒编写教程里有提到过这种方法。现在EBX是KERNEL32的基址
00459B77 8BFB mov edi,ebx
00459B79 037B 3C add edi,dword ptr ds:[ebx+3C]
00459B7C 83C7 78 add edi,78
00459B7F 8B3F mov edi,dword ptr ds:[edi]
00459B81 03FB add edi,ebx
00459B83 57 push edi
EDI输出表地址
00459B84 83C7 20 add edi,20
00459B87 8B3F mov edi,dword ptr ds:[edi]
00459B89 03FB add edi,ebx
00459B8B 33C0 xor eax,eax
00459B8B 33C0 xor eax,eax
00459B8D 40 inc eax
00459B8E 8B0F mov ecx,dword ptr ds:[edi]
00459B90 03CB add ecx,ebx
00459B92 83C7 04 add edi,4
00459B95 8139 47657450 cmp dword ptr ds:[ecx],50746547
00459B9B ^ 75 F0 jnz short 复件_[MS.00459B8D
00459B9D 8179 04 726F634>cmp dword ptr ds:[ecx+4],41636F72
00459BA4 ^ 75 E7 jnz short 复件_[MS.00459B8D
历遍输出表的AddressOfNames,对比头8个字符识别出GetProcAddress
00459BA6 6BC0 02 imul eax,eax,2
00459BA9 5F pop edi
00459BAA 57 push edi
00459BAB 83C7 24 add edi,24
00459BAE 8B3F mov edi,dword ptr ds:[edi]
00459BB0 03FB add edi,ebx
00459BB2 03F8 add edi,eax
00459BB4 66:8B07 mov ax,word ptr ds:[edi]
00459BB7 6BC0 04 imul eax,eax,4
00459BBA 5F pop edi
00459BBB 83C7 1C add edi,1C
00459BBE 8B3F mov edi,dword ptr ds:[edi]
00459BC0 03FB add edi,ebx
00459BC2 03F8 add edi,eax
00459BC4 8B7F FC mov edi,dword ptr ds:[edi-4]
00459BC7 03FB add edi,ebx
00459BC9 803F CC cmp byte ptr ds:[edi],0CC
00459BCC /75 09 jnz short 复件_[MS.00459BD7
取得GetProcAddress在内存中的位置还不忘检查其入口有没有断点。。。
00459BD7 E8 00000000 call 复件_[MS.00459BDC
00459BDC 58 pop eax
00459BDD 2D EC3A0000 sub eax,3AEC
00459BE2 B0 00 mov al,0
00459BE4 05 00200100 add eax,12000
00459BE9 8BF0 mov esi,eax
00459BEB 891E mov dword ptr ds:[esi],ebx
00459BED 897E 10 mov dword ptr ds:[esi+10],edi
将KERNEL32的基址存入EP,GetProcAddress则放在EP+10
00459BF7 5F pop edi
00459BF8 81C7 C4090000 add edi,9C4
00459BFE 0FB60439 movzx eax,byte ptr ds:[ecx+edi]
00459C02 83F0 15 xor eax,15
00459C05 880439 mov byte ptr ds:[ecx+edi],al
00459C08 41 inc ecx
00459C09 81F9 3F0C0000 cmp ecx,0C3F
00459C0F ^ 72 ED jb short [MSLRH].00459BFE
00459C11 EB 05 jmp short [MSLRH].00459C18
对0045a5bb-0045b1fa解码,同样是XOR 15。
0045A5D0 E8 00000000 call 复件_[MS.0045A5D5
0045A5D5 832C24 18 sub dword ptr ss:[esp],18
0045A5D9 FF36 push dword ptr ds:[esi]
0045A5DB FF56 10 call dword ptr ds:[esi+10] ; KERNEL32.GetProcAddress
0012FF98 77E60000 KERNEL32.77E60000
0012FF9C 0045A5BD ASCII "OutputDebugStringA"
指令间穿插字符。。
0045A5DE 8946 14 mov dword ptr ds:[esi+14],eax ; KERNEL32.OutputDebugStringA
存进EP+14的位置。似乎EP要被重写为输入表了
下一个API地址的载入方法对于我来说比较新鲜。
0045A5EB E8 10000000 call 复件_[MS.0045A600
0045A600 FF36 push dword ptr ds:[esi]
0045A602 FF56 10 call dword ptr ds:[esi+10]
0045A605 8946 18 mov dword ptr ds:[esi+18],eax ; KERNEL32.GetCommandLineA
0045A5EB处CALL的返回地址竟然是
0045A5F0 47 65 74 43 6F 6D 6D 61 6E 64 4C 69 6E 65 41 00 GetCommandLineA.
把OD的堆栈参考骗过去了,只提示是返回地址,没有字符参考。不过跟进KERNEL32的领域就能显示字符参考了。而且后面将地址保存的时候也有注释,这个技巧意义不大。后面全是类似的技巧和函数的载入,就忽略了。
0045B1C9 8CC9 mov cx,cs
0045B1CB 32C9 xor cl,cl
0045B1CD 83F9 00 cmp ecx,0
0045B1D0 75 28 jnz short 复件_[MS.0045B1FA
上面的代码似乎是检查操作系统的,就不会有后面PEB的检查了。据说如果是WIN9X就会跳走
0045B1D2 64:FF35 3000000>push dword ptr fs:[30]
0045B1D9 58 pop eax
0045B1DA 0FB648 02 movzx ecx,byte ptr ds:[eax+2]
0045B1DE 884E 0C mov byte ptr ds:[esi+C],cl
将ISDEBUG标记写入0046800C。当然我有插件。
0045B1E1 8B40 0C mov eax,dword ptr ds:[eax+C]
0045B1E4 8B40 0C mov eax,dword ptr ds:[eax+C]
0045B1E7 8D58 20 lea ebx,dword ptr ds:[eax+20]
0045B1EA 8D48 18 lea ecx,dword ptr ds:[eax+18]
0045B1ED 8103 C8000000 add dword ptr ds:[ebx],0C8
0045B1F3 B8 00000000 mov eax,0
0045B1F8 0101 add dword ptr ds:[ecx],eax
0045B1FA 33C9 xor ecx,ecx
0045B1FC E8 00000000 call 复件_[MS.0045B201
改写PEB中程序在内存中的基址和大小信息,大小+0c8,基址+0。有意义?
0045B1FC E8 00000000 call 复件_[MS.0045B201
0045B201 5F pop edi ; 复件_[MS.0045B201
0045B202 81C7 C1090000 add edi,9C1
0045B208 0FB60439 movzx eax,byte ptr ds:[ecx+edi]
0045B20C 83F0 11 xor eax,11
0045B20F 880439 mov byte ptr ds:[ecx+edi],al
0045B212 41 inc ecx
0045B213 81F9 521D0000 cmp ecx,1D52
0045B219 ^ 72 ED jb short 复件_[MS.0045B208
0045B21B EB 05 jmp short 复件_[MS.0045B222
0045bbc2-0045d914解码,这次是XOR 11
0045C56C E8 00000000 call [MSLRH].0045C571
0045C571 810424 CA090000 add dword ptr ss:[esp],9CA
0045C578 64:FF35 0000000>push dword ptr fs:[0]
0045C57F 64:8925 0000000>mov dword ptr fs:[0],esp
0045C586 33DB xor ebx,ebx
0045C588 8B1B mov ebx,dword ptr ds:[ebx]
安装完SEH就立刻产生内存访问错误进入了。
0045D8DF 8B4424 0C mov eax,dword ptr ss:[esp+C]
0045D8E3 33C9 xor ecx,ecx
0045D8E5 3348 04 xor ecx,dword ptr ds:[eax+4]
0045D8E8 3348 08 xor ecx,dword ptr ds:[eax+8]
0045D8EB 3348 0C xor ecx,dword ptr ds:[eax+C]
0045D8EE 3348 10 xor ecx,dword ptr ds:[eax+10]
继续检查DRX
0045DA76 884E 0D mov byte ptr ds:[esi+D],cl
这里将检查结果存在0046800D了。
0045E42A FF56 14 call dword ptr ds:[esi+14] ; KERNEL32.OutputDebugStringA
0045EDD4 FF56 18 call dword ptr ds:[esi+18] ; KERNEL32.GetCommandLineA
0045EDD7 40 inc eax
0045EDD8 33C9 xor ecx,ecx
0045EDDA 41 inc ecx
0045EDDB 803C01 00 cmp byte ptr ds:[ecx+eax],0
0045EDDF 74 0C je short 复件_[MS.0045EDED
0045EDE1 803C01 22 cmp byte ptr ds:[ecx+eax],22
0045EDE5 ^ 75 F3 jnz short 复件_[MS.0045EDDA
0045EDE7 C60401 00 mov byte ptr ds:[ecx+eax],0
0045EDEB ^ EB ED jmp short 复件_[MS.0045EDDA
0045EDED 6A 00 push 0
0045EDEF 6A 00 push 0
0045EDF1 6A 03 push 3
0045EDF3 6A 00 push 0
0045EDF5 6A 00 push 0
0045EDF7 68 00000080 push 80000000
0045EDFC 50 push eax
0045EDFD FF56 1C call dword ptr ds:[esi+1C]
一段计算字符长度未经优化的代码。然后是CREATEFILE打开自己,这样就DUMP不了了。修改EIP直接跳过
0045F7A7 837E 40 00 cmp dword ptr ds:[esi+40],0
0045F7AB 74 24 je short [MSLRH].0045F7D1
0045F7AD FF56 24 call dword ptr ds:[esi+24]
0045F7B0 50 push eax
0045F7B1 6A 00 push 0
0045F7B3 68 00040000 push 400
0045F7B8 FF56 28 call dword ptr ds:[esi+28]
0045F7BB 8BDC mov ebx,esp
0045F7BD 83EB 04 sub ebx,4
0045F7C0 6A 00 push 0
0045F7C2 6A 00 push 0
0045F7C4 6A 04 push 4
0045F7C6 53 push ebx
0045F7C7 6A 07 push 7
0045F7C9 50 push eax
0045F7CA FF56 40 call dword ptr ds:[esi+40]
0045F7CD 58 pop eax
0045F7CE 8846 0E mov byte ptr ds:[esi+E],al
比较ntdll.是ZwQueryInformationProcess否获取成功,获取成功就用该API检查是否被DEBUG,将结果写入0046800E
00460178 8CC9 mov cx,cs
0046017A 32C9 xor cl,cl
0046017C 83F9 00 cmp ecx,0
0046017F 0F84 A1130000 je [MSLRH].00461526
00460185 8B46 38 mov eax,dword ptr ds:[esi+38]
00460188 8078 01 4C cmp byte ptr ds:[eax+1],4C
0046018C 0F85 94130000 jnz [MSLRH].00461526
检查系统,前半部分检查是不是WIN9X,如果是则通过
SetUnhandledExceptionFilter
在KERNEL32中的偏移来确定具体是哪一个系统?我这里是2K,跳走了
00461ED2 58 pop eax ; [MSLRH].00461ED2
00461ED3 2D E2BD0000 sub eax,0BDE2
00461ED8 B0 00 mov al,0
00461EDA 05 00200100 add eax,12000
00461EDF 8BF0 mov esi,eax
00461EE1 807E 0C 00 cmp byte ptr ds:[esi+C],0
00461EE5 74 51 je short [MSLRH].00461F38
00461F38 807E 0D 00 cmp byte ptr ds:[esi+D],0
00461F3C ^ 0F85 AF41FFFF jnz [MSLRH].004560F1
00461F42 74 04 je short [MSLRH].00461F48
00461F44 75 02 jnz short [MSLRH].00461F48
00461F4B 807E 0E 00 cmp byte ptr ds:[esi+E],0
00461F4F ^ 0F85 9C41FFFF jnz [MSLRH].004560F1
00461F55 E8 0A000000 call [MSLRH].00461F64
00461F6C 807E 0F 00 cmp byte ptr ds:[esi+F],0
00461F70 ^ 0F85 7B41FFFF jnz [MSLRH].004560F1
00461F76 50 push eax
00461F77 E8 02000000 call [MSLRH].00461F7E
0046800F不知道是什么的校验值,估计是针对某操作系统的。后面开始自校验了,清除所有断点,还原花指令。
00461F92 59 pop ecx
00461F9D 83E9 05 sub ecx,5
00461FAA 33DB xor ebx,ebx
00461FB6 B8 9CBE0000 mov eax,0BE9C
00461FC5 8BF9 mov edi,ecx ; [MSLRH].00461F8D
00461FD1 2BF8 sub edi,eax
00461FDD 0FB607 movzx eax,byte ptr ds:[edi]
00461FEA 03D8 add ebx,eax
00461FF6 47 inc edi ; [MSLRH].004560F1
00462001 3BF9 cmp edi,ecx ; [MSLRH].00461F8D
0046200D ^\72 CE jb short [MSLRH].00461FDD
计算004560f1到00461f8d的校验和(从花指令入口处到进入自校验之前的代码)
0046200F BF 00704400 mov edi,[MSLRH].00447000
00462014 B9 00BC0000 mov ecx,0BC00
00462023 0FB607 movzx eax,byte ptr ds:[edi]
00462030 02DF add bl,bh
00462032 32DF xor bl,bh
00462034 32C3 xor al,bl
00462040 8807 mov byte ptr ds:[edi],al
0046204C 47 inc edi
00462057 49 dec ecx
00462062 ^\75 B5 jnz short [MSLRH].00462019
对00447000开始大小为BC00的区域解码,KEY就是刚才的校验和。
00462064 E8 00000000 call [MSLRH].00462069
00462069 59 pop ecx
0046206A 2959 16 sub dword ptr ds:[ecx+16],ebx
0046206D 61 popad
0046206E 60 pushad
0046206F BE 00704400 mov esi,[MSLRH].00447000
00462074 8DBE 00A0FBFF lea edi,dword ptr ds:[esi+FFFBA000]
0046207A 57 push edi
0046207B 83CD FF or ebp,FFFFFFFF
0046207E 68 0FDE9F00 push 9FDE0F
00462083 C3 retn
这里的结果仍然跟校验和关联
0046206A 2959 16 sub dword ptr ds:[ecx+16],ebx
之后,最后两条指令便变成
0046207E 68 D0284500 push [MSLRH].004528D0
00462083 C3 retn
跳到下一部分,据前辈的文章说,就是UPX核心了。
004528d8-004529a1是将00447000解压到00401000。
004529e0-00452a1a是填写IAT(在00450fe7获得API名字,将地址填进00415028,就是说没加密)。
用OLLYDUMP下来就完成脱壳了