1. 程序是什么语言编译的 从目前国内接触到程序看,比较流行的编译器有:VC系列、易语言、.NET、Delphi,一些曾经用的很多但渐渐少了有:VB、ASM、BC++,还有一些用的比较少的有:AutoIt、PB、QT等,下面提供一些实例,结合实例来看看“入口点代码”、“程序区段”和“加载模块”等特征。
1.1 VC6
1 2 3 4 00401700 >/$ 55 push ebp 00401701 |. 8BEC mov ebp,esp 00401703 |. 6A FF push -0x1 00401705 |. 68 00254000 push 吾爱破解.00402500
VC6特点:入口点代码是固定的代码,入口调用的API也是相同的,其中有的push地址不同程序可能会有所不同;区段有四个也是固定的.text、.rdata、.data和.rsrc。
1.2 VS2008与VS2013
1 2 00B5DDAC > $ E8 EF4E0000 call 吾爱破解.00B62CA0 00B5DDB1 .^ E9 79FEFFFF jmp 吾爱破解.00B5DC2F
1 2 00B03359 > $ E8 A9520000 call 吾爱破解.00B08607 00B0335E .^ E9 7FFEFFFF jmp 吾爱破解.00B031E2
1 2 00B08607 /$ 55 push ebp 00B08608 |. 8BEC mov ebp,esp
VS特点:入口点只有两行代码,一个CALL后直接JMP,第一个CALL进去后调用的API也是相同的;区段相对于VC6多了一个.reloc。
1.3 易语言 易语言编译无壳程序分为独立编译和非独立编译。由于易语言独立编译 是调用VC的链接程序编译的,所以从区段和入口代码特征和VC相同,用exeinfoPE查壳也显示是VC6编译的。而非独立编译 却显示不知名EXE,可能查壳工具里面没有存易语言非独立编译的特征码吧。
易语言特点:从程序里找一些call调用,最终都会走到上面位置(文字不太好表达),这个方法可以区分易语言和VC,非独立编译比较容易识别,入口处和E
模块都可以找到krnln.fnr
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 00401000 >/$ E8 89000000 call 吾爱破解.0040108E 00401005 |. 50 push eax ; /ExitCode = 0x0 00401006 \. E8 B5010000 call <jmp.&KERNEL32.ExitProcess> ; \ExitProcess 0040100B . 47 65 74 4E 65 77 53 6F 63 6B 00 ascii "GetNewSock",0 00401016 . 45 72 72 6F 72 00 ascii "Error",0 0040101C . 6B 72 6E 6C 6E 2E 66 6E 65 00 ascii "krnln.fne",0 00401026 . 4E 6F 74 20 66 6F 75 6E 64 20 74 68 65 20 6B 65 ascii "Not found the ke" 00401036 . 72 6E 65 6C 20 6C 69 62 72 61 72 79 20 6F 72 20 ascii "rnel library or " 00401046 . 74 68 65 20 6B 65 72 6E 65 6C 20 6C 69 62 72 61 ascii "the kernel libra" 00401056 . 72 79 20 69 73 20 69 6E 76 61 6C 69 64 21 00 ascii "ry is invalid!",0 00401065 . 6B 72 6E 6C 6E 2E 66 6E 72 00 ascii "krnln.fnr",0 0040106F . 50 61 74 68 00 ascii "Path",0 00401074 . 53 6F 66 74 77 61 72 65 5C 46 6C 79 53 6B 79 5C ascii "Software\FlySky\" 00401084 . 45 5C 49 6E 73 74 61 6C 6C 00 ascii "E\Install",0 0040108E /$ 55 push ebp 0040108F |. 8BEC mov ebp,esp 00401091 |. 81C4 F0FEFFFF add esp,-0x110 00401097 |. 8D85 FCFEFFFF lea eax,[local.65] 0040109D |. 50 push eax 0040109E |. E8 41010000 call 吾爱破解.004011E4 004010A3 |. 68 65104000 push 吾爱破解.00401065 ; /StringToAdd = "krnln.fnr" 004010A8 |. 8D85 FCFEFFFF lea eax,[local.65] ; | 004010AE |. 50 push eax ; |ConcatString = NULL 004010AF |. E8 24010000 call <jmp.&KERNEL32.lstrcatA> ; \lstrcatA
1.4 Delphi
Delphi特点:非常多的call指令,并且push address
与retn
结合相当于jmp address
。比如:
1 2 3 4 push 004A52F4 …… retn ;相当于jmp 004A52F4
1.5 BC++
BC++6与BC++2010都差不多,入口处一样的机器码,接下来调用获取句柄的API函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 004014EC > $ /EB 10 jmp short 吾爱破解.004014FE 004014EE |66 db 66 ; CHAR 'f' 004014EF |62 db 62 ; CHAR 'b' 004014F0 |3A db 3A ; CHAR ':' 004014F1 |43 db 43 ; CHAR 'C' 004014F2 |2B db 2B ; CHAR '+' 004014F3 |2B db 2B ; CHAR '+' 004014F4 |48 db 48 ; CHAR 'H' 004014F5 |4F db 4F ; CHAR 'O' 004014F6 |4F db 4F ; CHAR 'O' 004014F7 |4B db 4B ; CHAR 'K' 004014F8 |90 nop 004014F9 |E9 db E9 004014FA . |ACB04C00 dd offset 吾爱破解.___CPPdebugHook 004014FE > \A1 9FB04C00 mov eax,dword ptr ds:[0x4CB09F] 00401503 . C1E0 02 shl eax,0x2 00401506 . A3 A3B04C00 mov dword ptr ds:[0x4CB0A3],eax 0040150B . 52 push edx ; 吾爱破解.<ModuleEntryPoint> 0040150C . 6A 00 push 0x0 ; /pModule = NULL 0040150E . E8 578F0C00 call <jmp.&KERNEL32.GetModuleHandleA> ; \GetModuleHandleA
1.6 ASM
1 2 0040108B >/$ 6A 00 push 0x0 ; /pModule = NULL 0040108D |. E8 4A000000 call <jmp.&kernel32.GetModuleHandleA> ; \GetModuleHandleA
ASM特点:用汇编编写的程序都非常小,基本很少遇到,但它可用于改变自身代码,某些病毒就是利用多态和变形的特点隐藏自身。具体可见《OD使用教程》中的多态和变形。
1.7 .NET
.NET编译的程序放在OD调试会出现错误,推荐使用专门的net反编译工具。
1.8 AutoIt
在OD中文搜索引擎可以看到它是用AutoIt v3编译的。
1 2 3 4 中文搜索引擎, 条目 504 地址=0014A9F1 反汇编=push 吾爱破解.001E26A8 文本字符串=AutoIt v3 GUI
1.9 PB
PB可能也是跟易语言的独立编译一样,调用VC的链接程序编译,所以才显示VC6。但它还另加了两个库:libjcc.dll
和pbvm90.dll
。
1 2 3 4 5 Executable modules 基址 大小 入口 名称 文件版本 路径 009E0000 0007C000 00A10640 libjcc 4.2.5.06 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\libjcc.dll 10000000 0000B000 10001B30 吾爱破解 1,0,0,1 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\吾爱破解论坛学习脱壳实例_PB.exe 10B00000 003F3000 10DB9000 pbvm90 9.0.3.8784 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\pbvm90.dll
1.10 QT
查壳可看到QT编译,并且OD的E
模块也看到了有关QT的库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Executable modules 基址 大小 入口 名称 文件版本 路径 00400000 0000D000 004014C0 52pojie 0.0.0.0 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\52pojie.exe 01090000 00633000 01091420 Qt5Widge 5.4.1.0 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\Qt5Widgets.dll 016D0000 002AD000 016D1420 icuin53 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\icuin53.dll 61940000 00505000 61941420 Qt5Gui 5.4.1.0 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\Qt5Gui.dll 64940000 00014000 64941420 libwinpt 1, 0, 0, 0 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\libwinpthread-1.dll 66500000 01499000 66501420 icudt53 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\icudt53.dll 68430000 0003F000 68462D70 glu32 10.0.19041.1288 C:\Windows\SysWOW64\glu32.dll 68880000 004A6000 68881420 Qt5Core 5.4.1.0 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\Qt5Core.dll 68F80000 001B3000 68F81420 icuuc53 F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\icuuc53.dll 694A0000 00103000 69573D60 opengl32 10.0.19041.1081 C:\Windows\SysWOW64\opengl32.dll 6C640000 0009F000 6C678870 apphelp 10.0.19041.1288 C:\Windows\SysWOW64\apphelp.dll 6E940000 00024000 6E941420 libgcc_s F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\libgcc_s_dw2-1.dll 6FC40000 00101000 6FC41420 libstdc+ F:\吾爱破解培训\吾爱破解培训第一课例子\无壳程序\52破解脱壳实例\libstdc++-6.dll
1.11 VB 1 2 3 0040129A - FF25 AC104000 jmp dword ptr ds:[<&msvbvm60.#ThunRTMain_100>] ; msvbvm60.ThunRTMain 004012A0 > 68 582E4000 push dumped_.00402E58 004012A5 E8 F0FFFFFF call <jmp.&msvbvm60.#ThunRTMain_100>
2. 如何判断是否加壳
通过查壳工具中内置各种壳的十六进制特征码进行对比查壳
通过程序入口特征与区段信息来判断
3. 程序加的是什么壳 未加壳、压缩壳、传统加密壳、代码虚拟化保护、.Net程序加密…
3.1 压缩壳 尽量减少可执行文件的大小。
3.1.1 ASPacK
看到m
模块,除了最基本VS编译特点的区段,ASPacK壳还加了.aspack
与.adata
模块,这两个模块都可以在加壳时自定义名字。
1 2 3 4 5 6 7 8 9 10 Memory map 地址 大小 属主 区段 包含 类型 访问 初始访问 已映射为 007B0000 00001000 吾爱破解 PE 文件头 Imag R RWE 007B1000 00031000 吾爱破解 .text 代码 Imag R RWE 007E2000 0000D000 吾爱破解 .rdata 数据 Imag R RWE 007EF000 00007000 吾爱破解 .data Imag R RWE 007F6000 00004000 吾爱破解 .rsrc 资源 Imag R RWE 007FA000 00009000 吾爱破解 .reloc Imag R RWE 00803000 00003000 吾爱破解 .aspack SFX,输入表 Imag R RWE 00806000 00001000 吾爱破解 .adata Imag R RWE
3.1.2 UPX
3.2 加密壳 抵抗各类调试器和逆向,可能会加入大量干扰代码。
3.2.1 Themida
从查壳工具看到它有个区段没名字,并且最后两段是随机名称。而且它与其他加壳的入口处不一样,不是pushad
而是三个push
1 2 3 4 5 00AC4000 > 56 push esi ; 吾爱破解.<ModuleEntryPoint> 00AC4001 50 push eax 00AC4002 53 push ebx 00AC4003 E8 01000000 call 吾爱破解.00AC4009 00AC4008 CC int3
但F8运行一段后也是可以发现有pushad
1 2 3 4 008BC76D 89E8 mov eax,ebp 008BC76F 89E2 mov edx,esp 008BC771 60 pushad 008BC772 E8 00000000 call 吾爱破解.008BC777
3.2.2 VMProtect
可看VMProtect有很多个段,一些段的首字节显示ZERO SIZE
。载入OD代码看起来也很乱
1 2 3 4 5 6 7 8 9 007433CC E8 F6EFFCFF call 吾爱破解.007123C7 007433D1 48 dec eax 007433D2 DE1F ficomp word ptr ds:[edi] 007433D4 ff5e 43 call far fword ptr ds:[esi+0x43] 007433D7 29F0 sub eax,esi 007433D9 3950 3F cmp dword ptr ds:[eax+0x3F],edx ; 吾爱破解.00610000 007433DC 16 push ss 007433DD 17 pop ss 007433DE F8 clc
进入第一个函数可以看到类似于加密的pushfd
标志吧。
1 2 3 4 5 007123C7 9C pushfd 007123C8 9C pushfd 007123C9 C74424 08 1F660>mov dword ptr ss:[esp+0x8],0xA00A661F 007123D1 9C pushfd 007123D2 E9 B58D0000 jmp 吾爱破解.0071B18C
3.2.3 Shielden
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0050B119 > $ /EB 08 jmp short 吾爱破解.0050B123 0050B11B |00 db 00 0050B11C |C8 db C8 0050B11D |10 db 10 0050B11E |00 db 00 0050B11F |00 db 00 0050B120 |00 db 00 0050B121 |00 db 00 0050B122 |00 db 00 0050B123 >^\E9 E5F2FFFF jmp 吾爱破解.0050A40D 0050B128 . 1978 5B sbb dword ptr ds:[eax+0x5B],edi ; 吾爱破解.<ModuleEntryPoint> 0050B12B > 0f93c3 setae bl 0050B12E . FEC1 inc cl 0050B130 . 8BDE mov ebx,esi ; 吾爱破解.<ModuleEntryPoint> 0050B132 . 60 pushad 0050B133 . F6DF neg bh
F8走几下,也能看到明显的Shielden加壳特征。
1 2 3 4 0050A40D > /E8 1C000000 call 吾爱破解.0050A42E ; PUSH ASCII "Safengine Shielden v2.3.6.0" 0050A412 . |53 61 66 65 6>ascii "Safengine Shield" 0050A422 . |65 6E 20 76 3>ascii "en v2.3.6.0",0 0050A42E > |9C pushfd
4. 脱壳详解 以下脱壳都是用吾爱专用虚拟机进行操作,因为Win7/8/10(以后仅称Win10)脱壳太不友好,导致各种各样的问题出现。比如Win10系统下LoadPE只显示系统进程、脱壳后的程序无法正常运行(原因是ASLR基地址随机化脱壳插件获取的地址不对),这些问题在XP系统都可以得到解决。
脱壳后的程序回到Win10运行不了的问题,也是因为ASLR基地址随机化。ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。
在XP虚拟机用LoadPE和Scylla结合脱壳后的程序,移到Win10就打不开的情况(点击后加载了一下主程序却没出来)。OD载入看看
1 2 3 4 5 00F42CA0 8BFF mov edi,edi ; dumped_S.<ModuleEntryPoint> 00F42CA2 55 push ebp 00F42CA3 8BEC mov ebp,esp 00F42CA5 83EC 10 sub esp,0x10 00F42CA8 A1 B0074400 mov eax,dword ptr ds:[0x4407B0]
下面是XP系统同一代码位置的情况
1 2 3 4 5 00422CA0 8BFF mov edi,edi 00422CA2 55 push ebp 00422CA3 8BEC mov ebp,esp 00422CA5 83EC 10 sub esp,0x10 00422CA8 A1 B0074400 mov eax,dword ptr ds:[0x4407B0]
发现第5行都是4407B0
,肯定不对,推测在Win10下这个偏移地址为F607B0
(这个地址还会变,因为每次载入程序地址都会随机化)。为了让程序能在Win10正常运行,只需将文件或系统的ASLR取消即可。最好不要取消系统的ASLR,涉及安全问题。
取消文件的ASLR的两种方法:
打开LoadPE->PE编辑器->特征值后面三个点->将重定位已分离勾选->确定->保存->确定
打开CFF Explorer->载入文件->File Header->Click Here->将relocation info stripped from file
勾选->OK
在XP中取消再移到Win10或直接在Win10取消都行。
4.1 UPX 做作业前推荐观看ximo脱壳基础——手脱UPX壳
用ExeinfoPE查壳可知它是UPX壳,并且可以知道区段的名字可以随意改变,所以用区段名字判断它是什么壳其实不准确。UPX脱壳非常简单,只需利用UPX脱壳工具即可。
1 2 3 4 5 6 7 8 9 10 D:\CTF\tools\upx>upx -d 吾爱破解培训第一课作业一.exe -o 1.exe Ultimate Packer for eXecutables Copyright (C) 1996 - 2020 UPX 3.96w Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 File size Ratio Format Name -------------------- ------ ----------- ----------- upx: 吾爱破解培训第一课作业一.exe: CantUnpackException: file is modified/hacked/protected; take care!!! Unpacked 0 files.
呃…脱壳工具好鸡肋,看了ximo的手脱UPX壳,现在4种方法都来试试吧。
4.1.1 单步跟踪法 一路F8,遇到向上跳转不实现,向下跳转忽略。
遇到向上跳转时,有两种方法饶过:
选中向上跳转指令的下一条指令F4(运行至光标处)
选中向上跳转指令的下一条指令F2->F9->F2(下断点->运行->取消断点)
如果向上跳转的下一条指令是跳转指令或是call指令,那就在下下一条指令进行操作,以此类推。
1 2 3 4 5 6 7 00F87757 61 popad 00F87758 8D4424 80 lea eax,dword ptr ss:[esp-0x80] 00F8775C 6A 00 push 0x0 00F8775E 39C4 cmp esp,eax 00F87760 ^ 75 FA jnz short 吾爱破解.00F8775C 00F87762 83EC 80 sub esp,-0x80 00F87765 - E9 4266FCFF jmp 吾爱破解.00F4DDAC
经过漫长的上述重复操作后,终于来到了第7行的大跳转处,大跳转在加壳程序中往往意味着OEP(注意第1行的popad,其它方法会用到)。
1 2 3 00F4DDAC E8 EF4E0000 call 吾爱破解.00F52CA0 00F4DDB1 ^ E9 79FEFFFF jmp 吾爱破解.00F4DC2F 00F4DDB6 3B0D B007F700 cmp ecx,dword ptr ds:[0xF707B0]
以上就是真正程序的起始处,结合1所学的各种语言编译后代码的特点,猜测该程序是由VS编译的。
4.1.2 ESP定律 入口代码第1行是pushad
,经过这一条指令后,在寄存器的ESP右键->数据窗口中跟随,来到数据窗口,选中第一个hex数据右键->断点->硬件访问->Word(或者在命令窗口输入hr esp
回车),F9运行,取消硬件断点,之后就按照4.1.1的方法走,一直走到OEP。
4.1.3 2次内存镜像法 M
模块查看内存,找到程序领空的.rsrc
表,也就是资源表。虽然吾爱把表名改了,但还是可以在包含里看到“资源”字样,就在.cn
处。这里注意,如果资源表是单独一个,就用2次内存镜像,如果与其他表合在一起,只用1次内存镜像即可。
1 2 3 4 5 6 Memory map 地址 大小 属主 区段 包含 类型 访问 初始访问 已映射为 00160000 00001000 吾爱破解 PE 文件头 Imag R RWE 00161000 00039000 吾爱破解 www. Imag R RWE 0019A000 0001E000 吾爱破解 52pojie SFX,代码 Imag R RWE 001B8000 00005000 吾爱破解 .cn 数据,输入表,资源 Imag R RWE
①如果.rsrc
单独一个节,选中.rsrc
右键->在访问上设置中断->F9。再找一次内存,在PE文件头的下一节地址,也就是161000处下断点,运行,删除断点。
②如果.rsrc
并不是单独一个节,只需在PE文件头的下一节地址,也就是161000处下断点,运行,删除断点。
之后按照4.1.1的方法走。
4.1.4 一步直达法 绝大部分UPX壳和AsPack壳都可以用这种方法。载入OD后可直观看到pushad
,既然有pushad
那肯定有popad
。右键->查找->命令,输入popad
,不需要勾选整个块。之后与4.1.1同。
在虚拟机的OD找到OEP后,三种方法dump下来。
①用OllyDump插件,起始地址是E
模块的第一行的00400000
,大小为5D000
,OEP的地址为0041DDAC
,修正的是偏移地址,所以是0041DDAC-00400000=1DDAC
,代码基址和数据基址可以在M
模块看,代码基址是3A000
,数据基址是58000
。用方式1脱壳,保存为ODdump1.exe
,可以运行,载入exeinfoPE说有不知名的壳在保护,但点一下插件Advanced Scan就可看到是由VS2008编译的。
在PEID可以直接看到是由VS2008编译的。
ODdump1.exe
载入OD后还是显示有压缩代码的存在。
那UPX壳到底有没有脱干净呢?其实只要脱壳后的程序可以正常运行,OEP入口代码为无壳代码特征,IAT解密完资源没有被压缩即可。
②用OllyDump插件,与①完全相同,只是用方式2脱壳,保存为ODdump2.exe
,不可运行。
③打开LoadPE,找到OD进程,右键->修复镜像大小->右键->完整转存,将正在调试的程序完整地转存下来,自动生成一个dumped.exe
文件,不可运行,壳还没脱。注意,先别关掉OD。 打开ImportREC,找到原本的程序(不是dumped.exe
),将OEP改为1DDAC
->自动查找IAT->获取输入表,没有无效函数,转储到文件,选中dumped.exe
打开,会自动生成一个dumped_.exe
。这时,dumped_.exe
可以运行。
4.2 ASPacK
ASPacK壳都可以用以上4种方法。现在用单步跟踪法温故知新。F8运行到第2行主程序就出来了,也就是我们所说的“跑飞了”。所以要F7跟进去,以后同理。
1 2 3 00430001 > 60 pushad 00430002 E8 03000000 call QQ个性网.0043000A 00430007 - E9 EB045D45 jmp 45A004F7
单步跟踪法与F7、F8结合,运行至retn
指令后出来的就是真正程序开始的地方。
在一步直达法查找popad
时会遇到很多个popad
可能都不是我们需要的,在不知道哪个是我们所要的popad
情况下,最好不要用这种方法。
4.2.1 模拟跟踪法 模拟跟踪法包括SFX模拟跟踪都是让OD自动查找程序OEP。
M
模块,在程序领空找到包含SFX的区段的地址。在命令窗口输入tc eip < 包含SFX的区段的地址
回车,OD会自动跟踪OEP,但这个过程可能会有点漫长。跟踪完后OD会跳到C
模块的某条指令上,这条指令可能是OEP,也可能是将要到OEP,需要单步跟踪到达OEP。
4.2.2 SFX模拟跟踪 选项->调试设置->SFX,选块方式跟踪或字节方式跟踪,视情况而定。重载后自动跳到OEP或将要到OEP处。
4.3 nsPack
nsPack壳是北斗的壳,可以用上面6种方法手动脱壳,过程与脱ASPacK壳一样。
nsPack壳作业用①方法用PEID查壳第一行Unknown,第二行显示VS2008,程序正常运行,载入OD也正常。用②方法查壳同①,程序不能正常运行,错误提示“应用程序正常初始化(0xc0000005)失败”,这时用importREC自动查找IAT就可正常运行,载入OD正常。用③方法全部同①。
4.4 FSG 2.0
4.4.1 单步跟踪法 单步跟踪到这三个连续跳转处,第1行代码向上跳,绕过。第2行代码跳到1D4地址处,刚好绕过第3行的大跨度跳转。
1 2 3 4 004001CD ^\78 F3 js short qqspirit.004001C2 004001CF 75 03 jnz short qqspirit.004001D4 004001D1 - FF63 0C jmp dword ptr ds:[ebx+0xC] ; qqspirit.0040A86D 004001D4 50 push eax ; qqspirit.0042CBC6
选中1D1地址,F4运行至此处。F8就跳到了OEP。
4.4.2 ESP定律 在程序起始处执行到的第一个push
指令后,用ESP定律法。
1 2 3 4 5 00400154 > 8725 04A24700 xchg dword ptr ds:[0x47A204],esp 0040015A 61 popad 0040015B 94 xchg eax,esp 0040015C 55 push ebp 0040015D A4 movs byte ptr es:[edi],byte ptr ds:[esi]
之后同单步跟踪。
4.4.3 特殊的ESP定律 FSG 2.0专用的ESP定律。
1 2 3 00400154 > 8725 04A24700 xchg dword ptr ds:[0x47A204],esp 0040015A 61 popad 0040015B 94 xchg eax,esp
执行到popad
后(程序起始处就有popad
而没有pushad
),查看堆栈窗口。
1 2 3 4 0047A208 004001E8 qqspirit.004001E8 0047A20C 004001DC qqspirit.004001DC 0047A210 004001DE qqspirit.004001DE 0047A214 0040A86D qqspirit.0040A86D
顺数第4行就是OEP。选中右键->在反汇编窗口中跟随,发现在反汇编窗口全是空代码,右键->断点->硬件执行,F9运行(或Shift+F9)即可看到代码。如果反汇编窗口中还是看到类似于数据的东西,右键->分析->从模块中删除分析就可看到代码了。
用脱壳的三种方法脱壳后程序查壳都显示完美脱壳了,是由V6编译的。
然而三个都不能正常运行,那可能需要我们手动修复IAT。在真正程序段随便找一个call或mov系统函数的偏移地址,比如425210,它的函数名kernel32.GetVersion
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0040A86D /. 55 push ebp ; oleaut32.770F0000 0040A86E |. 8BEC mov ebp,esp 0040A870 |. 6A FF push -0x1 0040A872 |. 68 78794200 push qqspirit.00427978 0040A877 |. 68 F4E14000 push qqspirit.0040E1F4 ; SE 处理程序安装 0040A87C |. 64:A1 0000000>mov eax,dword ptr fs:[0] 0040A882 |. 50 push eax 0040A883 |. 64:8925 00000>mov dword ptr fs:[0],esp 0040A88A |. 83EC 58 sub esp,0x58 0040A88D |. 53 push ebx ; qqspirit.0047A208 0040A88E |. 56 push esi ; qqspirit.0042C0C8 0040A88F |. 57 push edi ; qqspirit.004252AC 0040A890 |. 8965 E8 mov [local.6],esp 0040A893 |. FF15 10524200 call dword ptr ds:[0x425210] ; kernel32.GetVersion
可以在importREC中填好OEP->自动查找IAT->获取输入表,验证虚拟地址为25210处是否为GetVersion
函数。
在OD的命令窗口输入d 425210
回车,数据窗口会刷新数据,右键->长型->地址,程序中所有函数全都显示出来了。往上滑动记下第一个函数地址425000
(再往上全是0),往下滑动记下最后一个kernel32
系列函数的下一个地址425284
。
回到importREC填入RVA25000
,大小425284-425000=284
(比较偷懒的做法就是将大小写成1000或1500,但会产生很多垃圾指针),获取输入表->显示无效函数,无效函数少的话可以直接选中无效函数右键->剪切指针,转储到文件。这时的dumped_.exe
可以正常运行了。(无效函数多的话右键->跟踪级别1或跟踪级别3跟一下,或者右键->插件跟踪)
由于OllyDump是很久远的插件了,所以对现在的脱壳不太友好,最好是用③方法脱壳。而在③方法中importREC最好换成Scylla用作IAT修复,因为ImpREC对一些新系统的API支持不好。
Scylla用法跟importREC差不多,但是OEP要填40A86D
,VA填425000
,大小与importREC一样,同样操作,修复转储后文件。
4.5 PEcompact
PEcompact壳可以用单步跟踪、ESP定律、2次内存镜像法。
使用单步跟踪时,与ASPacK一样都会跑飞,只需F7进入函数即可。
使用ESP定律时,执行完两个push
指令后再进行ESP。
1 2 3 4 0040A86D > B8 74DE4500 mov eax,qqspirit.0045DE74 0040A872 50 push eax ; qqspirit.0045DE74 0040A873 64:FF35 0000000>push dword ptr fs:[0] 0040A87A 64:8925 0000000>mov dword ptr fs:[0],esp
使用2次内存镜像法时,程序跳到此处:
1 2 3 4 5 6 7 0045DE89 C602 E9 mov byte ptr ds:[edx],0xE9 0045DE8C 83C2 05 add edx,0x5 0045DE8F 2BCA sub ecx,edx ; qqspirit.0040A883 0045DE91 894A FC mov dword ptr ds:[edx-0x4],ecx ; qqspirit.0045DE97 0045DE94 33C0 xor eax,eax 0045DE96 C3 retn 0045DE97 B8 F9CB45F0 mov eax,0xF045CBF9
如果执行retn
再单步跟踪,程序跑飞。所以要在retn
指令的下一条指令下断点运行,再单步跟踪到OEP。
4.5.1 使用VirtualFree 方法一:在命令窗口输入bp VirtualFree
回车,运行。取消断点,Alt+F9返回到用户代码处。右键->查找->命令,输入push 8000
(特征码),取消勾选整个块,F4运行至此处,单步跟踪。
方法二:在命令窗口输入bp VirtualFree
回车,两次F9运行。取消断点,Alt+F9返回到用户代码处。单步跟踪。
4.5.2 使用VirtualAlloc 在命令窗口输入bp VirtualAlloc
回车,运行。取消断点,Alt+F9返回到用户代码处。单步跟踪。
4.5.3 使用GetVersion 在命令窗口输入at GetVersion
回车,反汇编窗口跳到相关代码处,F2->F9->F2。
1 2 3 4 5 6 7 8 9 10 11 12 7C81127A > 64:A1 18000000 mov eax,dword ptr fs:[0x18] 7C811280 8B48 30 mov ecx,dword ptr ds:[eax+0x30] 7C811283 8B81 B0000000 mov eax,dword ptr ds:[ecx+0xB0] 7C811289 0FB791 AC000000 movzx edx,word ptr ds:[ecx+0xAC] 7C811290 83F0 FE xor eax,-0x2 7C811293 C1E0 0E shl eax,0xE 7C811296 0BC2 or eax,edx ; ntdll.KiFastSystemCallRet 7C811298 C1E0 08 shl eax,0x8 7C81129B 0B81 A8000000 or eax,dword ptr ds:[ecx+0xA8] 7C8112A1 C1E0 08 shl eax,0x8 7C8112A4 0B81 A4000000 or eax,dword ptr ds:[ecx+0xA4] 7C8112AA C3 retn
一直单步到retn
处返回父函数。往上滑动发现OEP,父函数就是真正代码处。
4.5.4 最后一次异常法 选项->调试设置->异常,将所有选项取消勾选。插件->StrongOD->skip some exceptions取消勾选。重新载入,按Shift+F9几次,直至出现主程序。
比如在ximo教程中是按了Shift+F9两次后跑飞,所以可知主程序在第一次Shift+F9到第二次Shift+F9的代码之间。重新载入,去到倒数第二次的Shift+F9处,在堆栈窗口查看标有SE处理程序
(一般是第二行)的数值0045DE74
,在反汇编窗口Ctrl+G查找0045DE74
,下断点,Shift+F9运行,取消断点。这就来到了与2次镜像内存法一样的地址处,单步跟踪,也要绕过retn
指令。
4.5.5 PEcompact的第一条指令 1 0040A86D > B8 74DE4500 mov eax,qqspirit.0045DE74
加壳程序的第一条指令就是将偏移地址0045DE74
赋值给eax。在命令窗口输入bp 0045DE74
回车->运行->取消断点。后面步骤与2次镜像内存法一样的地址处,单步跟踪,也要绕过retn
指令。
4.6 EZIP 这个可以用单步跟踪和ESP定律。这次主要讲脱壳后程序崩溃问题。用③方法脱壳没有问题,而用①和②方法都出现了找不到dll的问题。
此时,只要打开LoadPE分别将oddump1.exe
和oddump2.exe
重建PE即可。
4.7 tElock 0.98b1 这个壳可以用最后一次异常法、模拟跟踪法、2次内存镜像法。
这个程序中很多的花指令以及SEH暗桩。
花指令是,由设计者特别构思,希望使反汇编的时候出错,让破解者无法清楚正确地反汇编程序的内容,迷失方向。经典的是,目标位置是另一条指令的中间,这样在反汇编的时候便会出现混乱。花指令有可能利用各种指令:jmp, call, ret的一些堆栈技巧,位置运算,等等。
花指令是程序中的无用代码,程序对它没影响,少了它也能正常运行。加花指令后,杀毒软件对木马静态反汇编时,木马的代码就不会正常显示出来,加大杀毒软件的查杀难度。
SEH,结构化异常处理,是WINDOWS中异常处理的机制,简单地说,就是操作系统维护一个用来处理异常的函数指针的链表,如果发生异常,系统就会顺着这个链表去调用其中的函数,直到某个异常处理函数将异常处理完了,或者所有的函数都不处理异常。
使用SEH的机制来进制反调试,就是SEH暗桩。
程序可以将特定的代码注册成为异常处理函数,然后故意产生一些异常。当程序被调试时,所有的异常事件,都会先发由调试器进行处理,调试器可以决定是否处理,以及如何处理;如果调试器决定不处理,程序自己注册的异常处理入口才会得到执行。对于调试器,并不能保证所有的异常都正确的识别并恰当地处理(因此OD中的很多插件都有anti-anti功能),如果因为调试器对异常情况识别有误,那么程序就会执行完全不同的一条执行路径,这样程序就可以知道自己正在被调试了。
4.7.1 最后一次异常法 选项->调试设置->异常,将所有选项取消勾选。插件->StrongOD->skip some exceptions取消勾选。重新载入,按17次Shift+F9出现主程序。
重载,按16次Shift+F9后,观察堆栈窗口的SE处理程序
数值为0042D7FD。在反汇编窗口下断点Shift+F9运行,取消断点。之后用单步跟踪。
4.7.2 模拟跟踪法 使用模拟跟踪法的前提是没有SEH暗桩。而这个程序有很多SEH暗桩。我们知道,按16次Shift+F9没有出现主程序,而在第17次出现主程序,所以第16次到第17次的这一段是没有SEH暗桩的。所以连续按16次Shift+F9再使用模拟跟踪法。
4.7.3 2次内存镜像法 2次内存镜像法即可到达OEP。
用①方法脱壳出现问题:
可以用importREC修复。打开原加壳程序(因为用OD进程会卡死),填好OEP->自动查找IAT->获取输入表,显示无效函数,发现有非常多的无效函数,选中右键->插件跟踪->tElock 0.98,让它自动修复无效函数,修复完后还有5个无效函数,右键->剪切指针。转储到文件,程序正常运行。
用②③方法直接崩溃不出现弹窗。用importREC进行上述修复后同样可以正常运行。
4.8 exe32pack 可以用ESP定律。
4.8.1 使用IsDebuggerPresent 在命令窗口输入bp IsDebuggerPresent
回车,运行。取消断点,Alt+F9返回到用户代码处。
1 2 0040ED24 8BBD 013C4000 mov edi,dword ptr ss:[ebp+0x403C01] 0040ED2A 03BD 273C4000 add edi,dword ptr ss:[ebp+0x403C27] ; sticker.00400000
F8执行到下一条指令,查看信息窗口:
1 2 ss:[0040A167]=00400000 (sticker.00400000), ASCII "MZ0" edi=0000535F
基址为400000
,偏移地址为535F
,第二条指令就是要它俩相加,结果就是OEP=40535F
Ctrl+G去到40535F,F4运行至光标处,光标处即OEP。
4.9 WinUpack 用单步跟踪法跟踪到这一步,发现这是个大跳转,然而这个跳转却没有实现。
1 0043E635 - 0F84 E3BDFCFF je 跑跑排行.0040A41E
不能直接改为jmp,因为如果这样改的话在Scylla获取输入表时一个函数都没有。
要让这个跳转实现,即eax要等于0。
1 2 0043E633 85C0 test eax,eax ; 跑跑排行.0040C034 0043E635 - 0F84 E3BDFCFF je 跑跑排行.0040A41E
运行到跳转指令处,右键->断点->条件,输入eax==0
。F9运行,取消断点,F8跳转至OEP。之后用③方法dump即可。
4.10 脱壳的基本思路及小结 还有非常多的我们没见过的壳,比如KByS、RLPack、PEpack、JDPack、PEncrypt等。但基本都可以用以下几种办法解决:
单步跟踪
ESP定律
2次内存镜像
最后一次异常法
模拟跟踪法
SFX模拟跟踪
压缩壳修不修正无所谓,但加密壳一定要修正镜像大小。
如果脱壳后的程序无法运行,可能是IAT重定位的问题,无效指针的问题,需要重建PE的问题,包括5的疑难杂症等等。这需要慢慢探索吧。
4.11 ASPack变形壳 使用单步跟踪法、2次内存镜像法可以,但ESP定律不行。
另外,对任何壳,短距离call要跟进去,远距离call可以不跟进去。
4.11.1 利用脚本进行脱壳 载入加壳程序后,插件->ODdgScript->运行脚本->打开对应的壳脚本(后缀可以为txt/osc),自动找到程序OEP。
脱壳脚本要自己收集,如果遇到一些变形壳,脱壳脚本可能就太不管用了,在运行了脱壳脚本的基础上再单步跟踪到OEP。
4.12 ACProtect 4.12.1 ACProtect 1.32 ACProtect 1.32是没有Stolen Code的,脱壳相对简单。
Stolen Code的意思是把被保护程序要运行的代码移走,以进行进一步的保护处理。增加分析难度,脱壳难度、增加反跟踪难度等等。
运行一下程序,发现会弹一个ACProtect壳的NAG窗口,再进入到主程序。
载入OD,选项->调试设置->异常->将非法访问内存取消勾选,插件->SrtongOD->options->将skip some exceptions取消勾选。用最后一次异常法,发现它在第2次Shift+F9跑飞了。所以在第1次Shift+F9(如果第一次就跑飞的话用F9)后,选中堆栈窗口的SE处理程序
右键->数据窗口中跟随,在数据窗口中的第一个字节下一个内存访问断点,Shift+F9运行。在反汇编窗口下断点,Shift+F9,再次下断点,Shift+F9。在B
模块取消软件断点。如何删除内存访问断点呢?直接右键->断点->删除内存断点即可。去到M
模块,选中程序领空的.text
区段右键->在访问上设置中断,Shift+F9,弹出NAG窗口,点确定后直达OEP。
用importREC修复IAT,Scylla会加载出非常多无效指针。
4.12.2 Ultraprotect 1.x(stolen code) 查壳显示Ultraprotect壳,这是ACProtect以前的名字,EP区段都显示.perplex
。
这次运行没有NAG窗口。与上述操作一致,到达“OEP”。
1 2 004010D2 56 push esi 004010D3 FF15 E4634000 call dword ptr ds:[0x4063E4] ; notepad9.0040D1BA
这“OEP”长得非常奇怪,与我们之前遇到的语言入口特征都不一样。考虑到ACProtect会有偷代码的习惯,所以猜测入口特征被移到了某处。
入口特征最常见的是push ebp
,先用这个试一下。
同样,删除完3个断点后,F4运行至目前函数的retn
处。调试->设置条件(Ctrl+T),勾选“命令是一个”,在输入框填写“push ebp”。调试->跟踪步入(Ctrl+F11),跟踪需要亿点时间,跟踪完后会跳到某个指令处,刚好就是被偷的入口特征代码。
1 2 3 4 5 004254C9 55 push ebp 004254CA 8BEC mov ebp,esp 004254CC 83EC 44 sub esp,0x44 004254CF 60 pushad 004254D0 60 pushad
后面几个是出栈指令,所以被偷的代码应该是前3条。选中右键->二进制->二进制复制。同样在M
模块的.text
下断点,Shift+F9运行至“OEP”。由于前3条指令占6个字节,所以要粘贴在“OEP”前6个字节处(粘贴前要把粘贴的地方的指令先nop掉)。覆盖后代码如下:
1 2 3 4 5 6 004010CA 90 nop 004010CB 90 nop 004010CC 55 push ebp 004010CD 8BEC mov ebp,esp 004010CF 83EC 44 sub esp,0x44 004010D2 56 push esi
选中真正的OEP右键->此处为新EIP。接下来就是脱壳了。如果遇到很多无效函数,可以用插件跟踪->ACProtect,也可以用跟踪级别跟踪(注意OD进程跟踪会卡死,所以打开加壳程序跟踪)。
4.12.3 Ultraprotect 1.x进阶版 用4.12.2的方法到了跟踪那一步会报错,“无法处理调试异常”。这时,将选项->调试设置->异常->将INT3中断取消勾选,其余勾选。重载,同样用最后一次异常法,取消3个断点后,在命令窗口输入d 12ffc0
回车,下硬件访问断点,Shift+F9。程序找到Stolen Code。
1 2 3 4 004C9B30 61 popad 004C9B31 55 push ebp 004C9B32 8BEC mov ebp,esp 004C9B34 6A FF push -0x1
复制,找到“OEP”往上滑,却发现代码全乱了。
1 2 004431F9 68 D8B24400 push NetClean.0044B2D8 004431FE 68 B4334400 push NetClean.004433B4
1 2 3 004431F7 /2e:79 68 bhnt jns short 00443262 004431FA |D8B2 440068B4 fdiv dword ptr ds:[edx-0x4B97FFBC] 00443200 |334400 64 xor eax,dword ptr ds:[eax+eax+0x64]
去一下别的模块再回来,发现又好了,就是不能向上滑动。右键->分析->分析代码。这时往上滑正常。将被偷的代码粘贴上去,修改EIP。
脱壳,运行程序异常。这是因为某些ACProtect 1.x壳有自我保护功能,即入口点如果不是原本那个地址可能就运行不了了。加壳程序入口点4AC000
。将已脱壳程序载入OD,定位到4AC000
,将被偷代码粘贴,再跳到OEP。
1 2 3 4 5 004AC000 55 push ebp 004AC001 8BEC mov ebp,esp 004AC003 6A FF push -0x1 004AC005 - E9 EA71B9FF jmp 004431F4 ;jmp 4431F4也可改为push 4431F4, retn
复制到可执行文件。再用LoadPE编辑入口点为AC000
,程序正常执行。
4.12.4 ACProtect 2.0.x 4.12.4.1 VB类 用4.12.1方法直接到达“OEP”4012CE
。
1 2 3 4 5 004012CE - FF25 78104000 jmp dword ptr ds:[0x401078] ; msvbvm60.ThunRTMain 004012D4 29BA A821B144 sub dword ptr ds:[edx+0x44B121A8],edi 004012DA BC 4DB59200 mov esp,0x92B54D 004012DF 0000 add byte ptr ds:[eax],al 004012E1 0000 add byte ptr ds:[eax],al
根据语言的编译特点,发现它是VB程序。以下就是VB程序的典型例子。第一条指令push
,第二条指令call
,双击call指令就可知道call的地址call 00401044
,刚好是OEP的上面一条jmp 系统函数
指令。运行到这条jmp指令时,堆栈窗口的第二条就是push的地址。
1 2 3 4 00401044 $- FF25 18104000 jmp dword ptr ds:[<&MSVBVM60.#ThunRTMain_100>] ; msvbvm60.ThunRTMain 0040104A ? 0000 add byte ptr ds:[eax],al 0040104C > $ 68 3C1F4000 push 吾爱破解.00401F3C ; ASCII "VB5!#vb6chs.dll" OEP 00401051 . E8 EEFFFFFF call <jmp.&MSVBVM60.#ThunRTMain_100>
1 2 0012FFBC 00401056 返回到 吾爱破解.00401056 来自 <jmp.&MSVBVM60.#ThunRTMain_100> 0012FFC0 00401F3C ASCII "VB5!#vb6chs.dll"
模仿典型例子修改代码:
1 2 3 004012CE - FF25 78104000 jmp dword ptr ds:[0x401078] ; msvbvm60.ThunRTMain 004012D4 68 54474000 push QQ个性签.00404754 004012D9 E8 F0FFFFFF call QQ个性签.004012CE ; jmp 到 msvbvm60.ThunRTMain
在真正OEP4012D4
处右键->此处为新EIP,脱壳。
4.12.4.2 Delphi 这次的壳发现取消勾选“非内存访问”或“INT3”或两个都取消,都直接跑飞。换方法。
在命令窗口输入bp GetCurrentProcessId
回车,运行,取消断点。用LoadPE查看程序的PID为8C8
(每次都会变)。
1 2 3 7C8099C0 > 64:A1 18000000 mov eax,dword ptr fs:[0x18] 7C8099C6 8B40 20 mov eax,dword ptr ds:[eax+0x20] ; comctl_1.<ModuleEntryPoint> 7C8099C9 C3 retn
修改为:
1 2 3 4 5 6 7C8099C0 > B8 64050000 mov eax,0x564 7C8099C5 90 nop 7C8099C6 90 nop 7C8099C7 90 nop 7C8099C8 90 nop 7C8099C9 C3 retn
光标处修改为PID,下面指令都nop掉。
在命令窗口输入bp GetModuleHandleA
回车,运行,取消断点。去到M
模块,在CODE设置访问中断(F2),F9运行,出现NAG窗口,点确定后,来到OEP。
4.12.5 另类方法解ACProtect ACProtect 1.4x
取消勾选“非法内存访问”和“int3中断”都跑飞。用4.12.4.2方法找到“OEP”。用4.12.2寻找Stolen Code跟踪时间太过漫长,可以用另一种办法。
1 2 3 0040A54C 50 push eax ; 跑跑排行.00400000 0040A54D E8 1B030000 call 跑跑排行.0040A86D 0040A552 8945 98 mov dword ptr ss:[ebp-0x68],eax ; 跑跑排行.00400000
重载,看到有pushad
,试一下用ESP定律,硬件断点先别删除,再用最后一次异常法。Shift+F9连续13次到达最佳脱壳地点。
1 2 3 00441E4D 64:A1 00000000 mov eax,dword ptr fs:[0] 00441E53 8905 39CA4200 mov dword ptr ds:[0x42CA39],eax 00441E59 FF35 39CA4200 push dword ptr ds:[0x42CA39]
但是脱壳却说这个位置没有函数。在importREC先填写OEP为A54C
,获取输入表修复IAT后,再将OEP改为41E4D
,转储到文件。
程序正常运行,查壳说还有壳,但已经可以进行破解美化DIY了。
4.12.6 补区段 ACProtect壳会对MessageBoxA
和ReigisterHotKey
进行处理,所以我们要在它进行处理的地方nop掉不让它处理。
将所有忽略异常勾选,在M
模块的.rdata
处下断运行(没有.rdata
用.idata
)。单步跟踪看注释窗口出现这两个API函数,把它下面的跳转指令nop掉。
1 2 3 4 5 6 7 8 0043396D 3B85 9CE24100 cmp eax,dword ptr ss:[ebp+0x41E29C] ; user32.MessageBoxA 00433973 74 20 je short NgaMy.00433995;nop 00433975 90 nop 00433976 90 nop 00433977 90 nop 00433978 90 nop 00433979 3B85 9D014100 cmp eax,dword ptr ss:[ebp+0x41019D] ; user32.RegisterHotKey 0043397F 74 09 je short NgaMy.0043398A;nop
再继续往下有magic跳,改JMP。
1 2 004339C0 80BD D2594100 0>cmp byte ptr ss:[ebp+0x4159D2],0x0 004339C7 74 57 je short NgaMy.00433A20;jmp
在M
模块的.text
下访问中断,Shift+F9运行到此处。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 00403D38 68 8C3D4000 push NgaMy.00403D8C 00403D3D 64:A1 00000000 mov eax,dword ptr fs:[0] 00403D43 50 push eax 00403D44 8B4424 10 mov eax,dword ptr ss:[esp+0x10] 00403D48 896C24 10 mov dword ptr ss:[esp+0x10],ebp 00403D4C 8D6C24 10 lea ebp,dword ptr ss:[esp+0x10] 00403D50 2BE0 sub esp,eax 00403D52 53 push ebx 00403D53 56 push esi 00403D54 57 push edi 00403D55 8B45 F8 mov eax,dword ptr ss:[ebp-0x8] ; kernel32.7C817080 00403D58 8965 E8 mov dword ptr ss:[ebp-0x18],esp 00403D5B 50 push eax 00403D5C 8B45 FC mov eax,dword ptr ss:[ebp-0x4] 00403D5F C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1 00403D66 8945 F8 mov dword ptr ss:[ebp-0x8],eax 00403D69 8D45 F0 lea eax,dword ptr ss:[ebp-0x10] 00403D6C 64:A3 00000000 mov dword ptr fs:[0],eax 00403D72 C3 retn
F4去到retn
处,F8返回父函数。再去到M
模块.text
设置内存访问断点运行,F4去到retn
处,F8返回父函数。再去到M
模块.text
设置内存访问断点运行,F4去到retn
处,F8返回父函数。到达“OEP”。
1 2 3 4 0040305C 83F9 02 cmp ecx,0x2 0040305F 74 0C je short NgaMy.0040306D 00403061 81CE 00800000 or esi,0x8000 00403067 8935 B0DE4000 mov dword ptr ds:[0x40DEB0],esi
用Scylla脱壳(因为用PEtools和LoadPE脱都显示错误),在importREC输入偏移地址,自动获取输入表没有函数,自己手动查找IAT,起始地址A000,大小174。
下一步,加壳程序重载,用ESP定律。F9运行5次来到这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 004365F4 8915 F5FD4100 mov dword ptr ds:[0x41FDF5],edx ; ntdll.KiFastSystemCallRet 004365FA FF35 F5FD4100 push dword ptr ds:[0x41FDF5] 00436600 8F05 2DFE4100 pop dword ptr ds:[0x41FE2D] ; kernel32.7C817077 00436606 FF35 2DFE4100 push dword ptr ds:[0x41FE2D] 0043660C C70424 60000000 mov dword ptr ss:[esp],0x60 00436613 56 push esi 00436614 890C24 mov dword ptr ss:[esp],ecx 00436617 68 8DFD4100 push NgaMy.0041FD8D 0043661C 59 pop ecx ; kernel32.7C817077 0043661D 8919 mov dword ptr ds:[ecx],ebx 0043661F 8B0C24 mov ecx,dword ptr ss:[esp] ; kernel32.7C817077 00436622 8F05 ADFE4100 pop dword ptr ds:[0x41FEAD] ; kernel32.7C817077 00436628 FF35 8DFD4100 push dword ptr ds:[0x41FD8D] 0043662E C70424 48A24000 mov dword ptr ss:[esp],NgaMy.0040A248 00436635 8905 B9FD4100 mov dword ptr ds:[0x41FDB9],eax 0043663B FF35 B9FD4100 push dword ptr ds:[0x41FDB9] 00436641 90 nop 00436642 90 nop 00436643 60 pushad 00436644 E8 01000000 call NgaMy.0043664A
将pushad
前的代码二进制复制下来:
1 2 3 89 15 F5 FD 41 00 FF 35 F5 FD 41 00 8F 05 2D FE 41 00 FF 35 2D FE 41 00 C7 04 24 60 00 00 00 56 89 0C 24 68 8D FD 41 00 59 89 19 8B 0C 24 8F 05 AD FE 41 00 FF 35 8D FD 41 00 C7 04 24 48 A2 40 00 89 05 B9 FD 41 00 FF 35 B9 FD 41 00
取消硬件断点,F4去到pushad
的下一条指令,在寄存器窗口找ESP,继续用ESP定律。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 00436F16 68 1DFD4100 push NgaMy.0041FD1D 00436F1B 58 pop eax 00436F1C 8930 mov dword ptr ds:[eax],esi ; NgaMy.00403D38 00436F1E 8F05 79FC4100 pop dword ptr ds:[0x41FC79] 00436F24 8B05 79FC4100 mov eax,dword ptr ds:[0x41FC79] 00436F2A FF35 1DFD4100 push dword ptr ds:[0x41FD1D] 00436F30 56 push esi ; NgaMy.00403D38 00436F31 891C24 mov dword ptr ss:[esp],ebx 00436F34 C70424 383D4000 mov dword ptr ss:[esp],NgaMy.00403D38 00436F3B 8B3424 mov esi,dword ptr ss:[esp] 00436F3E 8F05 A5FE4100 pop dword ptr ds:[0x41FEA5] ; NgaMy.00403D38 00436F44 8905 01FF4100 mov dword ptr ds:[0x41FF01],eax 00436F4A FF35 01FF4100 push dword ptr ds:[0x41FF01] 00436F50 891C24 mov dword ptr ss:[esp],ebx 00436F53 56 push esi ; NgaMy.00403D38 00436F54 C70424 45FE4100 mov dword ptr ss:[esp],NgaMy.0041FE45 00436F5B 8F05 31FE4100 pop dword ptr ds:[0x41FE31] ; NgaMy.0041FE45 00436F61 90 nop 00436F62 90 nop 00436F63 60 pushad 00436F64 E8 01000000 call NgaMy.00436F6A
1 2 3 68 1D FD 41 00 58 89 30 8F 05 79 FC 41 00 8B 05 79 FC 41 00 FF 35 1D FD 41 00 56 89 1C 24 C7 04 24 38 3D 40 00 8B 34 24 8F 05 A5 FE 41 00 89 05 01 FF 41 00 FF 35 01 FF 41 00 89 1C 24 56 C7 04 24 45 FE 41 00 8F 05 31 FE 41 00
重复操作n次,到达OEP。
ESP有:12FFA4、12FF98、12FF94、12FF24、12FF1C、12FF20、12FF1C、12FF1C、12FE8C、12FE8C、12FE90。
在12FE90处下断点后运行到达jmp指令,F8去到一个大跳转,跳到“OEP”。
1 0043BE77 /EB 01 jmp short NgaMy.0043BE7A
1 0043BE7A - FF25 BCBE4300 jmp dword ptr ds:[0x43BEBC] ; NgaMy.0040305C
1 2 0040305C 83F9 02 cmp ecx,0x2 0040305F 74 0C je short NgaMy.0040306D
二进制代码汇总:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 89 15 F5 FD 41 00 FF 35 F5 FD 41 00 8F 05 2D FE 41 00 FF 35 2D FE 41 00 C7 04 24 60 00 00 00 56 89 0C 24 68 8D FD 41 00 59 89 19 8B 0C 24 8F 05 AD FE 41 00 FF 35 8D FD 41 00 C7 04 24 48 A2 40 00 89 05 B9 FD 41 00 FF 35 B9 FD 41 00 68 1D FD 41 00 58 89 30 8F 05 79 FC 41 00 8B 05 79 FC 41 00 FF 35 1D FD 41 00 56 89 1C 24 C7 04 24 38 3D 40 00 8B 34 24 8F 05 A5 FE 41 00 89 05 01 FF 41 00 FF 35 01 FF 41 00 89 1C 24 56 C7 04 24 45 FE 41 00 8F 05 31 FE 41 00 8B 1D 31 FE 41 00 89 33 8F 05 39 FC 41 00 FF 35 39 FC 41 00 5B 8F 05 09 FE 41 00 89 1D 21 FC 41 00 FF 35 21 FC 41 00 C7 05 19 FC 41 00 09 FE 41 00 8B 1D 19 FC 41 00 8B 33 8F 05 FD FB 41 00 8B 1D FD FB 41 00 FF 15 45 FE 41 00 89 0D B1 FD 41 00 FF 35 B1 FD 41 00 8F 05 B5 FC 41 00 FF 35 B5 FC 41 00 56 BE FD FC 41 00 89 3E 5E FF 35 FD FC 41 00 68 94 00 00 00 8F 05 E5 FC 41 00 FF 35 E5 FC 41 00 5F 89 3D 3D FE 41 00 FF 35 3D FE 41 00 8B 0C 24 8F 05 7D FE 41 00 8B 3C 24 8F 05 79 FD 41 00 89 35 25 FC 41 00 FF 35 25 FC 41 00 89 0C 24 8B 3C 24 8F 05 B9 FC 41 00 8F 05 19 FE 41 00 89 05 89 FD 41 00 FF 35 89 FD 41 00 57 BF 19 FE 41 00 8B C7 5F 8B 08 8F 05 95 FC 41 00 8B 05 95 FC 41 00 53 8F 05 5D FE 41 00 FF 35 5D FE 41 00 89 0C 24 89 3D 91 FE 41 00 FF 35 91 FE 41 00 8F 05 81 FC 41 00 89 1D 89 FE 41 00 FF 35 89 FE 41 00 68 81 FC 41 00 5B 8B 0B 8F 05 C9 FC 41 00 8B 1D C9 FC 41 00 57 89 04 24 89 0C 24 8B 04 24 8F 05 D5 FD 41 00 8B 0C 24 8F 05 4D FC 41 00 50 89 14 24 8F 05 BD FE 41 00 FF 35 BD FE 41 00 51 B9 DD FD 41 00 89 39 59 FF 35 DD FD 41 00 C7 05 A9 FE 41 00 60 55 40 00 FF 35 A9 FE 41 00 8B 3C 24 8F 05 95 FD 41 00 89 1D 29 FD 41 00 FF 35 29 FD 41 00 8B DF 8B D3 5B 8F 05 E9 FE 41 00 8B 3D E9 FE 41 00 52 89 1C 24 68 9D FE 41 00 5B 89 13 8B 1C 24 8F 05 49 FE 41 00 8B 14 24 8F 05 69 FD 41 00 FF 15 9D FE 41 00 89 65 E8 89 25 C5 FD 41 00 89 1D 21 FD 41 00 FF 35 21 FD 41 00 68 C5 FD 41 00 5B 8B 33 8B 1C 24 8F 05 A9 FC 41 00 89 3E 57 8F 05 F5 FE 41 00 FF 35 F5 FE 41 00 89 34 24 FF 15 BC A0 40 00 8B 4E 10 50 B8 F9 FB 41 00 89 10 58 FF 35 F9 FB 41 00 56 C7 04 24 AC DE 40 00 8B 14 24 8F 05 AD FD 41 00 89 0A 8F 05 29 FE 41 00 FF 35 29 FE 41 00 5A 8B 46 04 A3 B8 DE 40 00 8B 56 08 52 8F 05 3D FD 41 00 FF 35 3D FD 41 00 8F 05 BC DE 40 00 8B 76 0C 81 E6 FF 7F 00 00 53 BB 35 FE 41 00 89 33 5B FF 35 35 FE 41 00 8F 05 B0 DE 40 00
用topo工具在脱壳修复后的程序申请一个新的区段,数以上字节数或直接输入1000,记下起始的地址:0043E000。OD打开脱完壳后的程序,找到0043E000,粘贴入代码,记住,后面得加跳向假OEP的代码!!保存。
用LoadPE修正入口点为3E000。
4.12.7 AC脱壳出现的问题
用LoadPE无法抓取进程内存 。用Scylla填入OEP,获取输入表,不用管无效函数,转储到文件。再用importREC或Scylla重建IAT。
用importREC填入OEP无法获取输入表 。用Scylla重建IAT,但剪切指针时可能麻烦一点。
4.13 ASProtect 4.13.1 ASProtect 1.2 选项->调试设置->异常,将所有异常取消勾选。用最后一次异常法,Shift+F9运行19次。在retn
处F2->Shift+F9->F2。去到M
模块,在401000处下断运行,直接去到OEP。
1 2 3 4 5 0040A41E 55 push ebp 0040A41F 8BEC mov ebp,esp 0040A421 6A FF push -0x1 0040A423 68 C8CB4000 push 跑跑赛道.0040CBC8 0040A428 68 A4A54000 push 跑跑赛道.0040A5A4
4.13.2 ASProtect 1.23 RC1 取消勾选“非法内存访问”,最后一次异常法。Shift+F9运行16次。用4.13.1的方法找到OEP。
也可以用另一种方法。运行到retn
后,查看堆栈窗口的400000
下的第二行的数值12FFA4
。在命令窗口下硬件断点hr 12FFA4
,运行,F8经过一个大跳转也可去到OEP。
1 2 3 4 0012FF5C 00A0381C 0012FF60 00400000 跑跑排行.00400000 0012FF64 3D375BA5 0012FF68 0012FFA4
脱壳正常运行。
也可以用asprdbgr脱壳辅助工具打开加壳程序,让它自动修复IAT,打开importREC发现只有3个无效指针了,直接剪切掉。同样可以运行。
4.13.3 ASProtect 1.23 RC4 判断ASProtect版本:
ASProtect 1.23 RC4 按shift+f9键26次后来到典型异常,在最近处的retn处设断,跳过异常,f8步跟就会来到fake oep。
ASProtect 1.31 04.27 按shift+f9键36次后来到典型异常,在最近处的retn处设断,跳过异常,f8步跟就会来到foep。
ASProtect 1.31 05.18 按shift+f9键40次后来到典型异常,在最近处的retn处设断,跳过异常,f8步跟就会来到foep。
ASProtect 1.31 06.14 按shift+f9键38次后来到典型异常,在最近处的retn处设断,跳过异常,f8步跟就会来到foep。
用最后一次异常法可能判断不准确,也可以用PEID的VerA插件区分版本。
[1]表示上述第一个版本RC4。
取消勾选“非法访问内存”,Shift+F9运行26次,在retn
处F2->Shift+F9->F2。去到M
模块,在401000处下断运行,直接去到FOEP。
1 2 3 4 004F27CF FF15 9CC25200 call dword ptr ds:[0x52C29C] 004F27D5 33D2 xor edx,edx ; ntdll.KiFastSystemCallRet 004F27D7 8AD4 mov dl,ah 004F27D9 8915 34306900 mov dword ptr ds:[0x693034],edx ; ntdll.KiFastSystemCallRet
找回被偷代码,取消勾选“非法访问内存”,Shift+F9运行26次,在retn
处F2->Shift+F9->F2。查看堆栈窗口的400000
往下数第二行数值为12FFA4
,在命令窗口输入hr 12FFA4
下硬件断点,运行。
1 2 3 4 0012FF5C 00C86804 0012FF60 00400000 SoWorker.00400000 0012FF64 E3DE7228 0012FF68 0012FFA4
一路F8跟到以下,call跟进去。
1 2 00C8544B BD 5154C800 mov ebp,0xC85451 00C85450 FF55 03 call dword ptr ss:[ebp+0x3]
发现类似于OEP开头:
1 2 3 4 5 6 7 00C8547A 55 push ebp 00C8547B 8BEC mov ebp,esp 00C8547D 6A FF push -0x1 00C8547F 68 78E35300 push 0x53E378 00C85484 68 407B4F00 push 0x4F7B40 00C85489 64:A1 00000000 mov eax,dword ptr fs:[0] 00C8548F /EB 01 jmp short 00C85492
将jmp以上(不包含jmp)的代码二进制复制
1 55 8B EC 6A FF 68 78 E3 53 00 68 40 7B 4F 00 64 A1 00 00 00 00
执行jmp到以下代码,再复制
1 2 3 4 00C85492 50 push eax 00C85493 64:8925 0000000>mov dword ptr fs:[0],esp 00C8549A 83EC 58 sub esp,0x58 00C8549D EB 01 jmp short 00C854A0
1 50 64 89 25 00 00 00 00 83 EC 58
以此类推
1 2 3 00C854A8 57 push edi ; SoWorker.006CF040 00C854A9 8965 E8 mov dword ptr ss:[ebp-0x18],esp 00C854AC 26:eb 01 jmp short 00c854b0
汇总一下代码:
1 55 8B EC 6A FF 68 78 E3 53 00 68 40 7B 4F 00 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 EC 58 53 56 57 89 65 E8
一路F7到这里,再一路F8去到retn
,返回父函数。
1 2 3 4 5 6 7 8 9 10 11 00C85378 51 push ecx 00C85379 57 push edi ; SoWorker.006CF040 00C8537A 9C pushfd 00C8537B FC cld 00C8537C BF B953C800 mov edi,0xC853B9 00C85381 B9 5E140000 mov ecx,0x145E 00C85386 F3:AA rep stos byte ptr es:[edi] 00C85388 9D popfd 00C85389 5F pop edi ; SoWorker.004F27CF 00C8538A 59 pop ecx ; SoWorker.004F27CF 00C8538B C3 retn
直接来到FOEP,上面全为0是因为真正的OEP被移到了别处,刚才所做的操作就是把OEP拼接起来。
1 2 3 4 004F27C9 0000 add byte ptr ds:[eax],al 004F27CB 0000 add byte ptr ds:[eax],al 004F27CD 0000 add byte ptr ds:[eax],al 004F27CF FF15 9CC25200 call dword ptr ds:[0x52C29C]
将刚才复制的代码粘贴在它的上方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 004F27A9 55 push ebp 004F27AA 8BEC mov ebp,esp 004F27AC 6A FF push -0x1 004F27AE 68 78E35300 push SoWorker.0053E378 004F27B3 68 407B4F00 push SoWorker.004F7B40 004F27B8 64:A1 00000000 mov eax,dword ptr fs:[0] 004F27BE 50 push eax 004F27BF 64:8925 0000000>mov dword ptr fs:[0],esp 004F27C6 83EC 58 sub esp,0x58 004F27C9 53 push ebx 004F27CA 56 push esi 004F27CB 57 push edi ; SoWorker.006CF040 004F27CC 8965 E8 mov dword ptr ss:[ebp-0x18],esp 004F27CF FF15 9CC25200 call dword ptr ds:[0x52C29C]
设置EIP指向第一条指令。脱壳。
4.13.4 以壳解壳 Stolen Code太多,可以用以壳解壳的方法。以壳解壳就是将Stolen Code及Stolen Code所在区段的壳一并放在主程序里。这样会导致比无壳程序多了很多无用代码,文件大小比无壳程序大很多。
与4.13.3操作几乎一致。由于它有ASLR,所以地址与4.13.3会有所不同。FOEP为4F27CF
,OEP为C871DE
。
打开LoadPE,修正镜像大小,先完整转存一个,再区域转存OEP所在的区段。在PE编辑器打开脱壳程序,区段->选中最后一个区段右键->从磁盘载入区段,将我们刚才保存的区段载入,右键->编辑区段,程序基址为400000,所在区段起始地址为C80000,所以虚拟地址应为C80000-400000=880000。保存->确定。
选项->重建->只选“使PE有效”。重建PE->载入脱壳程序。
用importREC修复IAT,OEP先填写FOEP,修复好指针后再修改OEP为真正的OEP。程序正常运行。
5. 疑难杂症 5.1 附加数据的处理方法
PEID查壳是NSPacK壳,后面写着[overlay],也就是这个程序有附加数据。
overlay真正的意思是取消打开功能,将这些需要读取的数据放到pe文件的后面,让程序自动运行打开的功能。比如mp3文件需要播放器打开,那mp3文件除了音乐数据还需要附加数据,让播放器读到能通过播放器运行。
如果单纯用NSPacK脱壳方法还不足够,会发现脱壳后的程序运行不了。正确方法是将加壳程序后面的附加数据粘贴到脱壳程序的后面。
用PEID查看区段信息,重点关注最后一个区段的R偏移和R大小。
8800h+400h=8C00h
用Win HEX打开加壳程序,Alt+G搜索偏移8C00
,选第一个字节右键->选块开始,选文件最后一个字节右键->选块结束,右键->编辑->复制选块->正常。打开脱壳后的程序,选中最后一个字节,右键->编辑->剪贴板数据->粘贴,保存。程序正常运行。
5.2 自校验的去除方法 用了以上各种方法修复程序都运行不了,可以考虑可能是程序有自校验。
自校验原理:程序会检查自己有没有被修改,如果发现被修改的话,便会离开或进行其它动作。基本的校检方法包括checksum,检查大小,检查跳转代码,等等。
将脱壳后的程序也用OD载入,在两个OD的命令窗口下断点bp CreateFileA
回车,Alt+F9执行到用户代码,取消断点。接下来对比两个程序执行的不同之处,主要看条件跳转指令。
1 2 3 4 0040120C /75 07 jnz short 例子.00401215 0040120E |B8 01000000 mov eax,0x1 00401213 |EB 02 jmp short 例子.00401217 00401215 \33C0 xor eax,eax
加壳程序这个跳转不实现,而脱壳程序的这个跳转实现了。只要将脱壳程序的跳转指令修改即可。
1 jnz short 例子.00401215 => jz short 例子.00401215
保存后程序正常运行。
6. 脱壳后的简单应用 程序的破解、汉化、美化都要先脱壳才能进行。
6.1 软件汉化及DIY 6.1.1 VB类 VB程序最好用GetVBRes工具。
6.1.2 VC++/VS类 VC++/VS程序可以用Resource Hacker、PEexplorer、xnresource工具。
脱完壳后加载进Resource Hacker,还是显示“有非标准资源”错误提示,说明我们手工脱壳没脱干净,可能在重建IAT时大小设置为1000,导致有很多垃圾指针。可以用fixres修复一下脱壳后的程序。
6.1.3 BC++类/Delphi BC++/Delphi类最好用PEexplorer工具。
7. 脱壳练习 7.1 kkrunchy 0.23 单步到push
后用ESP定律,继续F8往下,看到一个大跳转,却没有实现。
1 003FFD5B /0F84 9F190000 je UnPackMe.00401700
选中右键->跟随,是一大片空代码。右键->断点->硬件执行->F9,原本空代码的地方已经出现了OEP,删除硬件断点。
1 2 3 4 5 00401700 55 push ebp ; UnPackMe.003F2A08 00401701 8BEC mov ebp,esp 00401703 6A FF push -0x1 00401705 68 00254000 push UnPackMe.00402500 0040170A 68 86184000 push UnPackMe.00401886
用单步跟踪、内存镜像法都是要在大跳转那里同样操作才可到达OEP。
脱壳。注意它的基址是3F0000
,所以OEP的偏移地址为11700
。
7.2 AT4RE Protector PEID显示yoda’s Protector v1.02
载入OD用2次内存镜像法,在.rsrc
资源段设置内存访问断点(A),设置访问中断(F2)断不下来,因为会去到DLL领空,记得要取消内存断点。再在PE文件头下一行下访问中断,运行,到达以下:
1 2 3 4 5 6 7 00407101 8A1C06 mov bl,byte ptr ds:[esi+eax] 00407104 80EB FF sub bl,0xFF 00407107 881C06 mov byte ptr ds:[esi+eax],bl 0040710A 46 inc esi 0040710B 83FE 32 cmp esi,0x32 0040710E ^ 75 F1 jnz short UnPackMe.00407101 00407110 - FFE0 jmp eax ; UnPackMe.00401700
单步跟到jmp eax
去到OEP。
7.3 ORiEN v2.11 - 2.12 开始用ESP定律,再单步几下就到了OEP。
1 004A1AF1 - FFE0 jmp eax ; UnPackMe.0045159C
1 2 3 4 0045159C 55 push ebp 0045159D 8BEC mov ebp,esp 0045159F 83C4 F0 add esp,-0x10 004515A2 B8 BC134500 mov eax,UnPackMe.004513BC ; UNICODE ";"
修复时直接删除无效指针,剪切指针程序会出错。
7.4 MoleBox V2.6.5 单步跟,跑飞的函数跟进去,就可跟到OEP。(ESP定律也行)
1 2 3 4 0045159C 55 push ebp 0045159D 8BEC mov ebp,esp 0045159F 83C4 F0 add esp,-0x10 004515A2 B8 BC134500 mov eax,UnPackMe.004513BC ; UNICODE ";"
脱壳,重建输入表时,有很多无效函数,用跟踪1全部修复。
PEID显示无壳,但是程序不能运行。重新操作一次回到无效函数那里,看其中一个无效函数的RVA55170
,在OD命令窗口输入d 455170
。
1 00455170 00477189 UnPackMe.00477189
发现它是程序函数,而不是系统函数,重建表中应该全是系统函数才对。猜测它是被壳加密了。
在OD重载一下程序,再次d 455170
,这时这个地址什么都没有。
右键->断点->硬件访问。Shift+F9直至这个地址出现系统函数。此时处于未加密状态。而这个函数与跟踪1修复函数的名字完全不一样,所以跟踪修复不一定正确。
1 00455170 7C801D53 kernel32.LoadLibraryExA
现在单步跟踪观察是哪一步让它变成程序函数。执行完下面这一步时,函数改变。也就是说这一步导致IAT重建时RVA为55170
地址的函数无效。
1 00471682 8901 mov dword ptr ds:[ecx],eax ; UnPackMe.00477189
为了不让它改变,往上找能绕过这个指令的跳转指令,这个跳转指令称为magic jump
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 00471658 /74 45 je short UnPackMe.0047169F 0047165A |8D55 F0 lea edx,dword ptr ss:[ebp-0x10] 0047165D |52 push edx ; UnPackMe.0047D378 0047165E |6A 04 push 0x4 00471660 |6A 04 push 0x4 00471662 |8B45 08 mov eax,dword ptr ss:[ebp+0x8] ; UnPackMe.00455170 00471665 |50 push eax ; UnPackMe.00477189 00471666 |FF15 40D84700 call dword ptr ds:[0x47D840] ; kernel32.VirtualProtect 0047166C |85C0 test eax,eax ; UnPackMe.00477189 0047166E |75 0A jnz short UnPackMe.0047167A 00471670 |B9 0B0000EF mov ecx,0xEF00000B 00471675 |E8 9D2F0000 call UnPackMe.00474617 0047167A |8B4D 08 mov ecx,dword ptr ss:[ebp+0x8] ; UnPackMe.00455170 0047167D |8B55 F8 mov edx,dword ptr ss:[ebp-0x8] ; UnPackMe.0047D378 00471680 |8B02 mov eax,dword ptr ds:[edx] ; UnPackMe.00477189 00471682 |8901 mov dword ptr ds:[ecx],eax ; UnPackMe.00477189 00471684 |8D4D F4 lea ecx,dword ptr ss:[ebp-0xC] 00471687 |51 push ecx ; UnPackMe.00455170 00471688 |8B55 F0 mov edx,dword ptr ss:[ebp-0x10] 0047168B |52 push edx ; UnPackMe.0047D378 0047168C |6A 04 push 0x4 0047168E |8B45 08 mov eax,dword ptr ss:[ebp+0x8] ; UnPackMe.00455170 00471691 |50 push eax ; UnPackMe.00477189 00471692 |FF15 40D84700 call dword ptr ds:[0x47D840] ; kernel32.VirtualProtect 00471698 |C745 FC 0100000>mov dword ptr ss:[ebp-0x4],0x1 0047169F \8B45 FC mov eax,dword ptr ss:[ebp-0x4]
重载,同样操作走一次,将je指令修改为jmp指令。Ctrl+G去到OEP,下断点运行,发现信息窗口的这个地址还是系统函数。
1 00455170 7C801D53 kernel32.LoadLibraryExA
重建IAT时已经所有函数都有效了。脱壳,正常运行。
7.5 PESpin 1.32(Stolen Code) ESP定律,Shift+F9运行。单步跟踪发现破碎OEP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 00408D09 55 push ebp 00408D0A /EB 01 jmp short UnPackMe.00408D0D 00408D0C |3D 8BECEB01 cmp eax,0x1EBEC8B 00408D11 2F das 00408D12 6A FF push -0x1 00408D14 EB 01 jmp short UnPackMe.00408D17 00408D16 0C 68 or al,0x68 00408D18 8890 BF01812C mov byte ptr ds:[eax+0x2C8101BF],dl 00408D1E 24 88 and al,0x88 00408D20 6B7F 01 68 imul edi,dword ptr ds:[edi+0x1],0x68 00408D24 ed in eax,dx 00408D25 8824EE mov byte ptr ds:[esi+ebp*8],ah 00408D28 810424 998F1B12 add dword ptr ss:[esp],0x121B8F99 00408D2F 64:A1 00000000 mov eax,dword ptr fs:[0] 00408D35 EB 01 jmp short UnPackMe.00408D38 00408D37 CE into 00408D38 50 push eax 00408D39 EB 01 jmp short UnPackMe.00408D3C
汇总一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 00408D09 55 push ebp 00408D0D 8BEC mov ebp,esp 00408D12 6A FF push -0x1 00408D17 68 8890BF01 push 0x1BF9088 ;00408D1C 812C24 886B7F01 sub dword ptr ss:[esp],0x17F6B88 这个在其他OEP好像没有,去掉(因为OEP不够内存) 00408D23 68 ED8824EE push 0xEE2488ED ;00408D28 810424 998F1B12 add dword ptr ss:[esp],0x121B8F99 这个在其他OEP好像没有,去掉 00408D2F 64:A1 00000000 mov eax,dword ptr fs:[0] 00408D38 50 push eax 00408D3C 64:8925 00000000 mov dword ptr fs:[0],esp 00408D46 83EC 68 sub esp,0x68 00408D4C 53 push ebx 00408D50 56 push esi 00408D54 57 push edi 00408D58 8965 E8 mov dword ptr ss:[ebp-0x18],esp 00408D5E 33DB xor ebx,ebx 00408D63 895D FC mov dword ptr ss:[ebp-0x4],ebx 00408D69 6A 02 push 0x2 00408D6E FF15 90214000 call dword ptr ds:[0x402190] ; msvcrt.__set_app_type 00408D77 59 pop ecx 00408D7B 830D 2C314000 FF or dword ptr ds:[0x40312C],-0x1 00408D85 830D 30314000 FF or dword ptr ds:[0x403130],-0x1 00408D8F FF15 8C214000 call dword ptr ds:[0x40218C] ; msvcrt.__p__fmode
二进制代码汇总:
1 55 8B EC 6A FF 68 88 90 BF 01 68 ED 88 24 EE 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 EC 68 53 56 57 89 65 E8 33 DB 89 5D FC 6A 02 FF 15 90 21 40 00 59 83 0D 2C 31 40 00 FF 83 0D 30 31 40 00 FF FF 15 8C 21 40 00
终于到达大跳转:
1 00408D98 - E9 AB89FFFF jmp UnPackMe.00401748
FOEP:
1 2 3 00401748 8B0D 20314000 mov ecx,dword ptr ds:[0x403120] 0040174E 8908 mov dword ptr ds:[eax],ecx 00401750 FF15 88214000 call dword ptr ds:[0x402188] ; msvcrt.__p__commode
二进制粘贴到FOEP上方,将401700
作为新EIP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 00401700 55 push ebp 00401701 8BEC mov ebp,esp 00401703 6A FF push -0x1 00401705 68 8890BF01 push 0x1BF9088 0040170A 68 ED8824EE push 0xEE2488ED 0040170F 64:A1 00000000 mov eax,dword ptr fs:[0] 00401715 50 push eax ; msvcrt._fmode 00401716 64:8925 00000000 mov dword ptr fs:[0],esp 0040171D 83EC 68 sub esp,0x68 00401720 53 push ebx 00401721 56 push esi 00401722 57 push edi 00401723 8965 E8 mov dword ptr ss:[ebp-0x18],esp 00401726 33DB xor ebx,ebx 00401728 895D FC mov dword ptr ss:[ebp-0x4],ebx 0040172B 6A 02 push 0x2 0040172D FF15 90214000 call dword ptr ds:[0x402190] ; msvcrt.__set_app_type 00401733 59 pop ecx ; 005BFFD0 00401734 830D 2C314000 FF or dword ptr ds:[0x40312C],-0x1 0040173B 830D 30314000 FF or dword ptr ds:[0x403130],-0x1 00401742 FF15 8C214000 call dword ptr ds:[0x40218C] ; msvcrt.__p__fmode 00401748 . 8B0D 20314000 mov ecx,dword ptr ds:[0x403120]
重建IAT用importREC不行,但用Scylla脱壳成功,程序正常运行。
7.6 eXPressor 1.3.0(VB自校验处理) 单步去到大跳转。
1 00407D00 |.- FFE0 jmp eax ; UnPackMe.004012A0
去到OEP。
1 2 3 0040129A - FF25 AC104000 jmp dword ptr ds:[<&msvbvm60.#ThunRTMain_100>] ; msvbvm60.ThunRTMain 004012A0 > 68 582E4000 push dumped_.00402E58 004012A5 E8 F0FFFFFF call <jmp.&msvbvm60.#ThunRTMain_100>
脱壳后还是运行不了,因为本来就是要考自校验的问题。
将脱壳后的程序载入OD,在命令窗口输入bpx papa
去到调用模块。
方法一:找到目标为__vbaNew2
,__vbaNew2
的功能是调用程序的子窗口或子函数。根据分析(?),它调用了退出函数。
右键->在每个调用到__vbaNew2
上设置断点。一个一个看。往上拉紧挨的cmp与跳转指令要格外关注,F4运行到cmp
指令后,观察跳转指令是否绕过退出函数。绕过则不管它,没绕过将跳转实现。
1 2 3 4 5 6 00403D2A 391D E0524000 cmp dword ptr ds:[0x4052E0],ebx 00403D30 75 10 jnz short dumped_.00403D42;跳转实现,不管 00403D32 68 E0524000 push dumped_.004052E0 ; ASCII "d窝" 00403D37 68 2C384000 push dumped_.0040382C 00403D3C FF15 8C104000 call dword ptr ds:[<&msvbvm60.#__vbaNew2_340>] ; msvbvm60.__vbaNew2 00403D42 8B35 E0524000 mov esi,dword ptr ds:[0x4052E0]
看到有两个cmp
与jnz
组合,先F4、F8走一下看它是怎么运行的。
1 2 3 4 5 6 7 00403F35 66:395D E4 cmp word ptr ss:[ebp-0x1C],bx 00403F39 75 56 jnz short dumped_.00403F91;不实现 00403F3B 391D E0524000 cmp dword ptr ds:[0x4052E0],ebx 00403F41 75 10 jnz short dumped_.00403F53;实现 00403F43 68 E0524000 push dumped_.004052E0 ; ASCII "d窝" 00403F48 68 2C384000 push dumped_.0040382C 00403F4D FF15 8C104000 call dword ptr ds:[<&msvbvm60.#__vbaNew2_340>] ; msvbvm60.__vbaNew2
如果jnz不修改,F9出现异常。如果jnz修改为jmp,F9程序正常终止。所以第一个jnz
应该修改为jmp
。
又来两个cmp
与跳转指令结合
1 2 3 4 5 6 7 00404295 817D E4 C05D000>cmp dword ptr ss:[ebp-0x1C],0x5DC0 0040429C 7E 54 jle short dumped_.004042F2;不实现 0040429E 391D E0524000 cmp dword ptr ds:[0x4052E0],ebx 004042A4 75 10 jnz short dumped_.004042B6;实现 004042A6 68 E0524000 push dumped_.004052E0 ; ASCII "d窝" 004042AB 68 2C384000 push dumped_.0040382C 004042B0 FF15 8C104000 call dword ptr ds:[<&msvbvm60.#__vbaNew2_340>] ; msvbvm60.__vbaNew2
如果jnz不修改,F9出现正常终止。如果jnz修改为jmp,F9程序弹出主程序。所以jle
应该修改为jmp
。
因为已经可以出现主程序了,所以后面的调用可以不管它。保存两处修改,程序可以跑起来。
7.7 delphi自效验的处理 是FSG 2.0的壳,上面有说,不再赘述。在修复时,会发现只有3个指针,这种情况是不可能有的,所以要手动查找IAT指针。
1 0045273F A1 E03F4500 mov eax,dword ptr ds:[0x453FE0]
在窗口命令输入d 453FE0
,发现全是程序函数还有ASCII码,不是我们要找的IAT指针。
1 2 3 4 00453FE0 00455BB0 UnPackMe.00455BB0 00453FE4 00453014 UnPackMe.00453014 00453FE8 00407138 ASCII "dVE" 00453FEC 0041ADD0 ASCII "dVE"
往下滑啊滑啊(Zzz…),终于来到IAT,起始地址45612C
,末尾456738
,大小456738-45612C=60C
1 2 3 00456128 00000000 0045612C 7C93135A ntdll.RtlDeleteCriticalSection 00456130 7C9210E0 ntdll.RtlLeaveCriticalSection
脱壳后的程序没有弹出错误提示,双击“运行不了”的情况,大多数是程序有自校验的问题,它是运行了又退出了。
与7.6一样,在命令窗口输入bp FindFirstFileA
(dll领空)下断避免后续程序跑飞。在命令窗口输入bpx papa
,找到EixtProcess
函数下断(因为7.6找不到这个函数,经过分析得出__vbaNew2
调用退出函数)。
F9,运行到dll领空Alt+F9返回。再F9运行到达某个退出函数断点处。这是switch语句执行default时的代码。
1 2 3 4 5 6 7 8 9 10 004523A6 > \50 push eax ; Default case of switch 00451FB8 004523A7 . 89D8 mov eax,ebx 004523A9 . 29D8 sub eax,ebx 004523AB . 89C3 mov ebx,eax 004523AD . 89D8 mov eax,ebx 004523AF . 01D8 add eax,ebx 004523B1 . 89C3 mov ebx,eax 004523B3 . 58 pop eax 004523B4 . 6A 00 push 0x0 ; /ExitCode = 0x0 004523B6 . E8 F540FBFF call <jmp.&kernel32.#ExitProcess_183> ; \ExitProcess
上下找一下发现只有case 2E4E9
没有退出函数。
1 004523BB > \33C0 xor eax,eax ; Case 2E4E9 of switch 00451FB8
这个地址跳转来自很多,但只有一个是条件跳转0045208C
,往上看在00452085
下断点,重载运行几下运行发现可以停在这里。jg不要实现,因为我们想在je跳转。
1 2 3 00452085 > \3D E9E40200 cmp eax,0x2E4E9 0045208A . 7F 6A jg short dumped_.004520F6 0045208C . 0F84 29030000 je dumped_.004523BB
修改一下
1 2 3 0045208A 90 nop 0045208B 90 nop 0045208C E9 2A030000 jmp dumped_.004523BB
一路F8,在这又遇到了2E4E9
,运行到jnz实现了,按道理它实现就不让它实现,nop掉。
1 2 00452775 |. 3D E9E40200 cmp eax,0x2E4E9 0045277A |. 75 0C jnz short dumped_.00452788
一路F8,又遇到了2E4E9
,je要改为jmp,要不然就退出了。
1 2 3 4 5 00452419 |. 3D E9E40200 cmp eax,0x2E4E9 0045241E |. 74 07 je short dumped_.00452427 00452420 |. 6A 00 push 0x0 ; /ExitCode = 0x0 00452422 |. E8 8940FBFF call <jmp.&kernel32.#ExitProcess_183> ; \ExitProcess 00452427 |> 33C0 xor eax,eax
修改完这3处,程序正常运行。结果OD不能选择所有修改(我的原因),那就修改一处保存一次重载新程序一次。
7.8 GHF Protector V1.0 脱壳的最佳时机:手动脱壳理想的最佳dump时机是指壳已经把程序代码包括资源等数据全部解密、输入表等数据还原但未填充系统函数地址、dll还没重定位,此时dump出来的文件只需修正OEP、ImportTableRVA等信息即可正常运行完成脱壳。
PEID查壳说是ASProtect 1.32的壳,但载入OD的壳特征明显不是ASProtect,并且程序的图标也被隐藏了。题目给了这是GHF Protector V1.0的壳。(可能不同程序入口代码不同)
1 2 3 4 5 6 00511709 > 50 push eax 0051170A 7C 05 jl short UnPackMe.00511711 0051170C 52 push edx ; ntdll.KiFastSystemCallRet 0051170D c1c4 80 rol esp,0x80 00511710 5A pop edx ; kernel32.7C817077 00511711 58 pop eax ; kernel32.7C817077
7.8.1 最佳脱壳时机 在M
模块,.idata
下断,Shift+F9运行,F7、F8走到这,这里是最佳的脱壳时机。算是这个壳的一个特征,三个跳转指令,且有0x80000000
和0x7FFFFFFF
。
1 2 3 4 5 005114A2 /74 2A je short UnPackMe.005114CE 005114A4 |F7C2 00000080 test edx,0x80000000 005114AA |74 08 je short UnPackMe.005114B4 005114AC |81E2 FFFFFF7F and edx,0x7FFFFFFF 005114B2 |EB 04 jmp short UnPackMe.005114B8
用PEtools半脱壳,设置PEtools选项:
或者用LoadPE半脱壳,在选项设置一下:
不要修正镜像大小,否则不能抓取进程。程序正常运行,但壳还没脱干净。
再将半脱壳后的程序载入OD,用ESP定律找到OEP。LoadPE脱壳,还原默认设置。
importREC重建IAT,脱壳成功。
7.8.2 使用LoadLibraryA 在命令窗口输入bp LoadLibraryA
回车,Shift+F9运行8次后,Alt+F9返回,取消断点,单步跟踪。(不知道为什么是8次)
到达这里,edx=00401700,是一个大跳转。
F8到达OEP。
用LoadPE,按7.8.1设置PE选项,程序可以运行,但壳还没脱干净,重建IAT即可。如果按PE默认,PEID显示这不是一个有效的PE文件。
或者用PEtools,按如下设置,重建IAT就可成功运行。
7.8.3 使用GlobalFree 在命令窗口输入bp GlobalFree
回车,Shift+F9,再Alt+F9,取消断点,单步跟踪也能去到OEP。
7.9 Armadillo(穿山甲) 6.04 穿山甲需要处理Magic jump,都有IAT加密。
7.9.1 使用GetModuleHandleA+9/+5 忽略所有异常,在StrongOD中勾选“跳过某些异常”。在命令窗口输入bp GetModuleHandleA+9
下断,Shift+F9运行N次,直到堆栈窗口出现:
1 2 3 4 5 6 001293E4 /0012EB40 001293E8 |00B88683 RETURN to 00B88683 from kernel32.GetModuleHandleA 001293EC |00BB514C ASCII "kernel32.dll" 001293F0 |00BB6D64 ASCII "VirtualFree" 001293F4 |0EF6D207 001293F8 |00454380 UnPackMe.00454380
再Shift+F9一次,Alt+F9返回,单步到Magic Jump,将它nop掉。(我也不知道为什么这里是Magic Jump)
1 00B6AA13 /75 05 jnz short 00B6AA1A;nop
往下拉到这里,F4运行到此处,再撤销刚才对Magic Jump的修改。因为穿山甲会检测某种保护下壳的完整性。
1 00B6AC9A /EB 03 jmp short 00B6AC9F
接着再下第二个断点bp CreateThread
,Shift+F9运行。再Alt+F9返回。
1 2 3 00B7614C 50 push eax 00B7614D FF15 9032BB00 call dword ptr ds:[0xBB3290] ; kernel32.CloseHandle 00B76153 5E pop esi ; UnPackMe.00454380
单步跟踪到这,F7进入即可到达OEP。
1 00B930E5 FFD2 call edx ; UnPackMe.00401700
或者不下第二个断点,在M
模块的.text
下断运行,单步跟踪到上面指令再F7进入OEP。
1 2 3 4 5 00401700 55 push ebp 00401701 8BEC mov ebp,esp 00401703 6A FF push -0x1 00401705 68 00254000 push UnPackMe.00402500 0040170A 68 86184000 push UnPackMe.00401886
常规脱壳即可。
7.9.2 使用VirtualProtect 在命令窗口输入bp VirtualProtect
,处理IAT加密。Shift+F9,注意寄存器ecx。当运行到ecx=00401000时,Alt+F9返回,取消断点。右键->查找->命令(Ctrl+F),输入push 100
,勾选“整个块”来到这里。(不勾选会找错地址,血的教训!)
1 2 3 4 5 6 7 00B42EC0 55 push ebp;修改为retn 00B42EC1 8BEC mov ebp,esp 00B42EC3 83EC 2C sub esp,0x2C 00B42EC6 833D 20F6BB00 0>cmp dword ptr ds:[0xBBF620],0x0 00B42ECD 75 59 jnz short 00B42F28 00B42ECF C745 EC 13004BB>mov dword ptr ss:[ebp-0x14],0xBA4B0013 00B42ED6 68 00010000 push 0x100
将第1行代码修改为retn
,因为下面的代码是执行加密。
之后再下断点bp CreateThread
或者去到M
模块.text
下断运行,单步跟踪同样去到OEP。
7.10 Armadillo 4.40 脱壳方法同7.9.1,LoadLibraryA
下面的跳转就是magic jump?将它修改为jmp
(什么时候修改为nop什么时候修改为nop?看它是否跳转?)
1 2 3 4 5 6 00AB5FE3 FF15 BC62AD00 call dword ptr ds:[0xAD62BC] ; kernel32.LoadLibraryA 00AB5FE9 8B0D AC40AE00 mov ecx,dword ptr ds:[0xAE40AC] 00AB5FEF 89040E mov dword ptr ds:[esi+ecx],eax 00AB5FF2 A1 AC40AE00 mov eax,dword ptr ds:[0xAE40AC] 00AB5FF7 391C06 cmp dword ptr ds:[esi+eax],ebx 00AB5FFA 0F84 2F010000 je 00AB612F
之后同7.9.1
用7.9.2方法好像不太可。
7.11 PEBundle 2.0b5 - 3.0x 用ESP定律找到OEP。脱壳后运行不了,但查壳没壳。考虑是importREC重建IAT时那些被我们剪切掉的指针是有用的,壳将它们加密了让我们以为那些指针没用。
随便找一个无效指针偏移地址为5517C
,在OD重载,命令窗口输入d 45517C
,在它上面一行(有效指针)下硬件访问断点。F8几下跑飞,但被我们下的硬件断点截住了。断点上面的地址已经出现系统函数。
1 2 3 4 5 0045516C 7C8101B1 kernel32.lstrcpynA 00455170 7C801D53 kernel32.LoadLibraryExA 00455174 7C80A4B5 kernel32.GetThreadLocale 00455178 0005589E 0045517C 000558B0
F8走几下,发现这里是一个循环,并且45517C
被加密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 00471E14 8B19 mov ebx,dword ptr ds:[ecx] 00471E16 83C1 04 add ecx,0x4 00471E19 85DB test ebx,ebx ; UnPackMe.004558C4 00471E1B 74 33 je short UnPackMe.00471E50 00471E1D 8BC3 mov eax,ebx ; UnPackMe.004558C4 00471E1F F7C3 00000080 test ebx,0x80000000 00471E25 74 08 je short UnPackMe.00471E2F 00471E27 81E3 FFFF0000 and ebx,0xFFFF 00471E2D EB 04 jmp short UnPackMe.00471E33 00471E2F 43 inc ebx ; UnPackMe.004558C4 00471E30 43 inc ebx ; UnPackMe.004558C4 00471E31 03DA add ebx,edx ; UnPackMe.00400000 00471E33 51 push ecx ; UnPackMe.00455184 00471E34 52 push edx ; UnPackMe.00400000 00471E35 899D C2214000 mov dword ptr ss:[ebp+0x4021C2],ebx ; UnPackMe.004558C4 00471E3B 53 push ebx ; UnPackMe.004558C4 00471E3C FFB5 BA214000 push dword ptr ss:[ebp+0x4021BA] ; kernel32.7C800000 00471E42 E8 32010000 call UnPackMe.00471F79 00471E47 5A pop edx ; UnPackMe.00471DBC 00471E48 59 pop ecx ; UnPackMe.00471DBC 00471E49 85C0 test eax,eax ; UnPackMe.0047214A 00471E4B 74 05 je short UnPackMe.00471E52 00471E4D AB stos dword ptr es:[edi] 00471E4E ^ EB C4 jmp short UnPackMe.00471E14
1 2 00455178 7C801EF2 kernel32.GetStartupInfoA 0045517C 00471F79 UnPackMe.00471F79
再准确一点,程序运行完下面这条指令,45517C
显示被加密。
1 00471E4D AB stos dword ptr es:[edi]
STOS指令:是将AL/AX/EAX的值存储到[EDI]指定的内存单元中。
往上看最近的eax就是call产生的。所以这个call很可能是加密函数。跟进去,着重看寄存器eax。我们已知455178
的函数没有被加密,程序运行到这一步,eax显示函数。
1 2 00471FBF 85C0 test eax,eax ; kernel32.GetStartupInfoA 00471FC1 /74 25 je short UnPackMe.00471FE8;没有实现
到这一步eax显示为0。
1 2 00471FDF 85C0 test eax,eax 00471FE1 /74 02 je short UnPackMe.00471FE5;跳转实现
一路到stos,函数出现在数据窗口。第二次循环就是我们要找的45517C
的函数。进去到471FBF
处,这已经显示了45517C
未加密的系统函数kernel32.GetProcAddress
,可以直接在import REC上修改。但我们需要将所有被加密的函数还原,不可能一个一个这样找,太麻烦了。
1 2 00471FBF 85C0 test eax,eax ; kernel32.GetProcAddress 00471FC1 /74 25 je short UnPackMe.00471FE8;没有实现
继续对比两者异同,到这一步eax显示的是加密函数UnPackMe.00471F79
。
1 2 00471FDF 85C0 test eax,eax ; UnPackMe.00471F79 00471FE1 74 02 je short UnPackMe.00471FE5;没有实现
在471FE1
处,未加密函数跳转实现而加密函数跳转未实现,所以应该将471FE1
处的跳转改为jmp
。继续单步观察数据窗口,验证修改是否正确。
1 2 00455178 7C801EF2 kernel32.GetStartupInfoA 0045517C 7C80AE40 kernel32.GetProcAddress
复制到可执行文件,将新的程序载入OD寻找OEP,脱壳,没有无效指针,程序成功运行。
7.12 PUNiSHER 1.5 这个壳比较特殊,下面用原版OD进行破解。首先忽略所有异常,隐藏好OD。Options -> Debugging options -> Exceptions,全选。
载入OD,程序停在此处。在命令行下断bp LoadLibraryA+5
。
1 2 3 00408061 > /EB 04 jmp short UnPackMe.00408067 00408063 |83A4BC CE60EB04>and dword ptr ss:[esp+edi*4+0x4EB60CE],0> 0040806B BC 0411E800 mov esp,0xE81104
Shift + F9运行程序,查看堆栈变化,第一次:
1 2 3 4 0012FF98 FFFEBDA9 0012FF9C 004083EC RETURN to UnPackMe.004083EC 0012FFA0 0040821F ASCII "USER32.DLL" 0012FFA4 005E5918
Shift + F9第二次,已经看到LoadLibraryA
了:
1 2 3 4 0012E9B4 /0012EA5C 0012E9B8 |73FBE2BF RETURN to usp10.73FBE2BF from kernel32.LoadLibraryA 0012E9BC |73FA1840 ASCII "gdi32.dll" 0012E9C0 |73FBE4B9 usp10.<ModuleEntryPoint>
再Shift + F9一次,此时为我们的最佳返回时机。Alt + F9返回:
1 2 3 4 0012FF98 FFFA31E5 0012FF9C 003C0470 RETURN to 003C0470 0012FFA0 003C00B7 ASCII "USER32.DLL" 0012FFA4 005E5918
取消断点,F8单步,遇到向上跳转时忽略,继续往下。直到去到003C08C5
处。
1 2 3 4 003C08C5 8D85 ADD64100 lea eax,dword ptr ss:[ebp+0x41D6AD] 003C08CB 870424 xchg dword ptr ss:[esp],eax ; UnPackMe.00402494 003C08CE FF95 E1CE4100 call dword ptr ss:[ebp+0x41CEE1] ; kernel32.OutputDebugStringA 003C08D4 80BD E9CE4100 0>cmp byte ptr ss:[ebp+0x41CEE9],0x0
此时,信息窗口显示:
1 2 Address=003C0892, (ASCII "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s") eax=00402494 (UnPackMe.00402494)
把上面的汇编代码前两行NOP掉,避免产生溢出错误。继续往下,来到两个连续的jmp指令。
1 2 003C0B1C ^\EB BF jmp short 003C0ADD ;不跳转 003C0B1E ^ E9 36FEFFFF jmp 003C0959 ;跳转
第一个jmp指令不跳,第二个jmp指令跳转。跳转后继续单步,看到大跳转了,跳过去就是Stolen Code的开始处。
1 2 3 4 5 6 7 8 9 10 11 003C0959 8B85 EACE4100 mov eax,dword ptr ss:[ebp+0x41CEEA] 003C095F 8D9D FEDC4100 lea ebx,dword ptr ss:[ebp+0x41DCFE] 003C0965 50 push eax 003C0966 E8 0E000000 call 003C0979 003C096B 50 push eax 003C096C 53 push ebx ; UnPackMe.00400000 003C096D E8 8EF7FFFF call 003C0100 003C0972 83C4 08 add esp,0x8 003C0975 - FF6424 FC jmp dword ptr ss:[esp-0x4] 003C0979 60 pushad 003C097A EB 04 jmp short 003C0980
跳过去是jmp指令,继续F8找到熟悉的VC程序入口,遇到近call要F7跟进去。
1 2 3 4 009F0000 /EB 04 jmp short 009F0006 009F0002 |8182 8241D9EE E>add dword ptr ds:[edx+0xEED94182],0x8C83> 009F000C 8C82 DB5C24FC mov word ptr ds:[edx+0xFC245CDB],es 009F0012 EB 04 jmp short 009F0018
来到这儿时,终于看到被抽取代码的了,VC程序入口:
1 2 3 4 5 6 7 8 9 10 11 009F022D 90 nop 009F022E 64:A1 00000000 mov eax,dword ptr fs:[0] 009F0234 50 push eax 009F0235 64:8925 0000000>mov dword ptr fs:[0],esp 009F023C 83EC 68 sub esp,0x68 009F023F 53 push ebx 009F0240 56 push esi ; UnPackMe.00404038 009F0241 57 push edi ; UnPackMe.00407B90 009F0242 8965 E8 mov dword ptr ss:[ebp-0x18],esp 009F0245 33DB xor ebx,ebx 009F0247 895D FC mov dword ptr ss:[ebp-0x4],ebx
但是前面还缺少了55开头的OEP,此时堆栈前2行就是第4、第5行push的地址。自行补上:
1 2 3 4 5 push ebp mov ebp esp push -1 push 004023D0 push 00401616
继续找被抽取的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 009F02B7 90 nop 009F02B8 FF15 7C214000 call dword ptr ds:[0x40217C] ; msvcrt.__set_app_type 009F02BE 59 pop ecx 009F02BF 830D 3C314000 F>or dword ptr ds:[0x40313C],0xFFFFFFFF 009F02C6 830D 40314000 F>or dword ptr ds:[0x403140],0xFFFFFFFF 009F02CD FF15 78214000 call dword ptr ds:[0x402178] ; msvcrt.__p__fmode 009F02D3 8B0D 30314000 mov ecx,dword ptr ds:[0x403130] 009F02D9 8908 mov dword ptr ds:[eax],ecx 009F02DB FF15 74214000 call dword ptr ds:[0x402174] ; msvcrt.__p__commode 009F02E1 8B0D 2C314000 mov ecx,dword ptr ds:[0x40312C] 009F02E7 8908 mov dword ptr ds:[eax],ecx 009F02E9 A1 70214000 mov eax,dword ptr ds:[0x402170] 009F02EE 8B00 mov eax,dword ptr ds:[eax] 009F02F0 A3 38314000 mov dword ptr ds:[0x403138],eax
遇到下面这种垃圾语句一定要将它改为NOP,否则程序会出错。
接下来遇到这一语句,注释和信息窗口都显示0040开头,是我们的用户代码段,这里把指令转换,还原我们的真实地址。
1 009F03DA 8B6D 00 mov ebp,dword ptr ss:[ebp] ; UnPackMe.00401615
即在被抽取代码段中添加call 00401615
。
运行到这不要retn,F4运行到jmp处,继续F8单步走。
1 2 009F0402 C3 retn 009F0403 EB 04 jmp short 009F0409
提取最后一句被抽取代码,retn去到伪OEP401504
处。
1 2 3 4 009F0409 90 nop 009F040A 391D 50304000 cmp dword ptr ds:[0x403050],ebx;最后一句被抽取的代码 009F0410 68 04154000 push 0x401504 009F0415 C3 retn
总结一下被抽取的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 push ebp mov ebp esp push -1 push 004023D0 push 00401616 009F022E 64:A1 00000000 mov eax,dword ptr fs:[0] 009F0234 50 push eax 009F0235 64:8925 0000000>mov dword ptr fs:[0],esp 009F023C 83EC 68 sub esp,0x68 009F023F 53 push ebx 009F0240 56 push esi ; UnPackMe.00404038 009F0241 57 push edi ; UnPackMe.00407B90 009F0242 8965 E8 mov dword ptr ss:[ebp-0x18],esp 009F0245 33DB xor ebx,ebx 009F0247 895D FC mov dword ptr ss:[ebp-0x4],ebx call 00401615 009F040A 391D 50304000 cmp dword ptr ds:[0x403050],ebx
去到伪OEP,如果看到全是字节,右键 -> Analysis -> remove analysis from module 。这样就可以看到汇编代码了。
1 2 3 4 5 00401504 /75 0C jnz short UnPackMe.00401512 00401506 |68 12164000 push UnPackMe.00401612 0040150B |FF15 6C214000 call dword ptr ds:[0x40216C] ; msvcrt.__setusermatherr 00401511 |59 pop ecx ; UnPackMe.00407B90 00401512 \E8 E9000000 call UnPackMe.00401600
往上拉发现代码被混淆,先不管。拉到retn下面的一句,也就是4014A7
处,往下NOP到伪代码处。(后来发现4014A7不够,4014A6也需要NOP掉)
1 2 3 4 5 6 004014A1 c2 2513 retn 0x1325 004014A4 ^ E3 A5 jecxz short UnPackMe.0040144B 004014A6 cb retf 004014A7 7B D5 jpo short UnPackMe.0040147E 004014A9 64:03d4 add edx,esp 004014AC 3F aas
发现401504
也被NOP掉了,选中被NOP掉的前3行右键 -> Undo selection ,还原代码。
1 2 3 4 5 00401504 90 nop 00401505 90 nop 00401506 90 nop 00401507 1216 adc dl,byte ptr ds:[esi] 00401509 40 inc eax
接下来我们去段首可以选择二进制粘贴的形式,也可以采取逐一汇编的方式进行修改。
二进制代码如下:
1 2 3 4 55 8B EC 6A FF 68 D0 23 40 00 68 16 16 40 00 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 EC 68 53 56 57 89 65 E8 33 DB 89 5D FC 6A 02 FF 15 7C 21 40 00 59 83 0D 3C 31 40 00 FF 83 0D 40 31 40 00 FF FF 15 78 21 40 00 8B 0D 30 31 40 00 89 08 FF 15 74 21 40 00 8B 0D 2C 31 40 00 89 08 A1 70 21 40 00 8B 00 A3 38 31 40 00 E8 17 01 00 00 39 1D 50 30 40 00
选中所有NOP二进制粘贴,新建EIP,运行loadPE和importREC,修正指针,脱壳成功。
7.13 未知壳 7.13.1 PolyBox 这个是个捆绑壳,意思是伪装成其他类别的壳,加大脱壳的难度。
PEID查壳显示FSG壳,这种壳用了FSG的外衣,把要加壳的程序当成一种资源加密起来。
用FSG专用的ESP定律找到第2层壳的OEP。
1 2 3 4 00402F18 55 push ebp 00402F19 8BEC mov ebp,esp 00402F1B 83C4 F0 add esp,-0x10 00402F1E B8 C02E4000 mov eax,UnPackMe.00402EC0 ; ASCII "\n"
在命令窗口输入bp WriteProcessMemory
。WriteProcessMemory
是读取程序的大小,也可以解释为处理写入的数据。F9运行,程序停在dll领空,查看堆栈窗口,它给程序写入了起始地址为3D0000
的程序。
1 2 3 4 5 6 7 0012FDE4 0040201B /CALL 到 WriteProcessMemory 来自 UnPackMe.00402016 0012FDE8 00000048 |hProcess = 00000048 (window) 0012FDEC 00400000 |Address = 0x400000 0012FDF0 003D0000 |Buffer = 003D0000 0012FDF4 00001000 |BytesToWrite = 1000 (4096.) 0012FDF8 0012FF48 \pBytesWritten = 0012FF48 0012FDFC 0012FE08 指向下一个 SEH 记录的指针
用LoadPE找到进程,右键->区域转存,找到起始地址为3D0000
的区域转存,后缀名改为exe即可运行。
7.13.2 UPX(Stolen Code) 单步和ESP定律都不太行,用两次内存镜像到达一串jmp指令的地方。
1 2 004014E4 - FF25 1C204000 jmp dword ptr ds:[<&MFC42.#CWinApp::InitApplication_3922>] ; mfc42.#CWinApp::InitApplication_3922 004014EA - FF25 20204000 jmp dword ptr ds:[<&MFC42.#CWinApp::AddToRecentFileList_1089>] ; mfc42.#CWinApp::AddToRecentFileList_1089
很明显这是由MFC写的程序,拿一个完整的MFC程序做对比。
1 2 3 4 5 6 7 8 9 10 11 12 0040163D C3 RETN 0040163E - FF25 94214000 JMP DWORD PTR DS:[<&msvcrt.__CxxFrameHandler>] 00401644 CC INT3 00401645 CC INT3 0040164F CC INT3 00401650 - FF25 90214000 JMP DWORD PTR DS:[<&msvcrt._except_handler3>] 00401656 55 PUSH EBP;这里是OEP 00401657 8BEC MOV EBP,ESP 00401659 6A FF PUSH -1 0040165B 68 E0234000 PUSH 004023E0 00401660 68 50164000 PUSH <JMP.&msvcrt._except_handler3> 00401665 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
被偷代码的程序:
1 2 3 4 5 6 7 8 9 10 11 004016F5 C3 retn 004016F6 - FF25 A4214000 jmp dword ptr ds:[<&MSVCRT.__CxxFrameHandler>] ; msvcrt.__CxxFrameHandler 004016FC CC int3 004016FD CC int3 004016FE CC int3 004016FF CC int3 00401700 98 cwde 00401701 e5 27 in eax,0x27 00401703 69AB ED2F71B3 0>imul ebp,dword ptr ds:[ebx-0x4C8ED013],0xC6844200 0040170D 084A 64 or byte ptr ds:[edx+0x64],cl 00401710 A1 00000000 mov eax,dword ptr ds:[0]
OEP前面的代码都是一样的,所以我们要找<&msvcrt._except_handler3>
函数的地址。查看与它同模块的<&MSVCRT.__CxxFrameHandler>
的地址(双击即可查看)为4021A4
,在命令窗口输入d 4021A4
回车。
1 2 3 4 5 00402194 >77C05C94 msvcrt._except_handler3 00402198 >77C1EE4F msvcrt._controlfp 0040219C >77C04DF8 msvcrt._onexit 004021A0 >77C04E51 msvcrt.__dllonexit 004021A4 >77BF27FA msvcrt.__CxxFrameHandler
很容易找到<&msvcrt._except_handler3>
的地址为402194
,然后是入口代码,都一样的,只有两个下面的push指令不一样。因为第二个push也刚好是<&msvcrt._except_handler3>
函数地址,所以我们只需找第一个push的地址。
把堆栈窗口的滚动条往下拉到底(因为最先压栈的在最底部),从下往上看,找到第一个压入的程序函数,就是我们第一个push进去的地址。
1 2 3 4 5 6 7 8 9 10 11 12 0012FFD0 0012FFE0 指向下一个 SEH 记录的指针 0012FFD4 00401886 SE处理程序 0012FFD8 00402500 UnPackMe.00402500;程序函数,所以是这个 0012FFDC 00000000 0012FFE0 0012CFA0 指向下一个 SEH 记录的指针 0012FFE4 7C839AD8 SE处理程序 0012FFE8 7C817080 返回到 kernel32.7C817080;这个是系统函数,不是 0012FFEC 00000000 0012FFF0 00000000 0012FFF4 00000000 0012FFF8 00407000 UnPackMe.<ModuleEntryPoint>;这个是入口点 0012FFFC 00000000
还有最后一句,正常程序的代码为64:A1 00000000
,而加壳程序的代码与上面一句混淆了,所以也要将它修改为64:A1 00000000
。
将代码拼凑起来,由于中间的代码不够用,所以把第1行jmp去掉,因为其它代码都是入口点必须的。
1 2 3 4 5 6 7 jmp 402194;去掉 push ebp mov ebp,esp push -1 push 402500 push 402194 mov eax,dword ptr fs:[0]
1 2 3 4 5 6 00401700 55 push ebp;将此处设置为新EIP 00401701 8BEC mov ebp,esp 00401703 6A FF push -0x1 00401705 68 00254000 push UnPackMe.00402500 0040170A 68 94214000 push <&MSVCRT._except_handler3> 0040170F 64:A1 00000000 mov eax,dword ptr fs:[0]
这时用ODdump说“不能创建程序”,用LoadPE也不能抓取进程,是因为这个程序由反调试功能。用任务管理器将程序进程结束,再在OD用ODdump即可。
7.13.3 yoda’s Protector 1.03.3 PEID说是这个壳,但也不知道是不是。OD载入
1 2 3 0040A6ED > E8 03000000 call UnPackMe.0040A6F5 0040A6F2 EB 01 jmp short UnPackMe.0040A6F5 0040A6F4 E9 BB550000 jmp UnPackMe.0040FCB4
忽略除非法内存访问的所有异常,在StrongOD中取消勾选“跳过某些异常”。重载,F9运行。程序停在空白代码处。
方法一:此时堆栈窗口显示
1 2 0012EBE0 0012EC70 指向下一个 SEH 记录的指针 0012EBE4 0040CA2C SE处理程序
在反汇编窗口Ctrl+G,跟随SE句柄。F2下断,Shift+F9运行,取消断点。
1 2 3 0040CA2C 55 push ebp 0040CA2D 8BEC mov ebp,esp 0040CA2F 57 push edi
F8往下,走到此处EDI的值就是OEP。
1 0040CA55 3E:89B8 B800000>mov dword ptr ds:[eax+0xB8],edi ; UnPackMe.00401700
Ctrl+G跟随401700
到达OEP。脱壳,一定要修正镜像大小。这个程序是有涉及到锁键盘、锁任务栏的,但可以用这种方法巧妙绕过。
方法二:在M
模块.text
处下断,Shift+F9也能运行到
1 2 3 0040CA2C 55 push ebp 0040CA2D 8BEC mov ebp,esp 0040CA2F 57 push edi
往后操作一样。