飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 4046|回复: 2

反调试技术回顾与透视——从UltraProtect到ACProtect

[复制链接]

该用户从未签到

发表于 2007-8-8 10:07:48 | 显示全部楼层 |阅读模式
2003年2月,一款名为UltraProtect的国产加密壳进入解密者的视野。然人惊奇的是这款加密壳的作者risco也同时出现在国内加解密领域的最前沿阵地——看雪论坛上,以加密者的身份同众多解密高手展开对话。当时适逢国内加解密群体的高速成长期,各类解密工具层出不穷,UltraProtect作为这一特殊时期的产物,也打上了对抗这些跟踪调试工具的烙印。2003年4月,UltraProtect正式更名为ACProtect(Anit Crack Protector),正如你猜到的那样,这款加密壳开始有意地模仿当时的加密壳老大ASProtect。事实上,无论在命名和代码功能上,ACProtect都吸取了不少ASProtect的优秀成分,一时也被列为密界公认的猛壳之一。经历了漫长的沉寂,ACProtect终于再次升级,
ACProtect发展到今天,代码的加密和变形技术已经使得完美脱壳非常困难了。本文从反跟踪和IAT处理的角度出发,一觑ACProtect的发展历程。从中我们可以看到ACProtect尽管融合了众多外壳的特点,但是单一的重复和独创性的缺失也使我们消除了对新产品的陌生和恐惧。


UltraProtect

作为ACProtect的原型,UltraProtect在反调试方面一点也不亚于它的后继者。事实上,ACProtect的loader反调试技术一直就没有发生很大的变化,尽管OllyDbg和不断发展的插件技术已经能够轻易回避这些检测手段,但是ACProtect的反跟踪在当时看来,还是非常隐蔽和出色的。本文以UltraProtect v1.05为例,详细分析它的反跟踪代码。
OllyDbg载入UProtect.exe,首先在调试选项中忽略INT 3、INT 1以及内存访问异常,避免调试器反复捕获这些中断,并启动IsDebuggerPresent插件,下硬件断点he GetProcAddress,F9运行断在下面的地方:

7C80AC28 > 8BFF MOV EDI,EDI
7C80AC2A 55 PUSH EBP
7C80AC2B 8BEC MOV EBP,ESP
7C80AC2D 51 PUSH ECX
7C80AC2E 51 PUSH ECX
7C80AC2F 53 PUSH EBX

这里是UltraProtect开始装载外壳所使用的API函数,由于UltraProtect借助SDK实现了壳中壳技术,因此即使是脱壳后,这些函数仍然被调用。所以要完成跨平台修复必须增加额外的初始化操作,其中就包括重新装载这些API函数。观察此时的堆栈:

0012FF84 004B761D /CALL to GetProcAddress from UProtect.004B7617
0012FF88 7C800000 |hModule = 7C800000 (kernel32)
0012FF8C 004B6ECA \ProcNameOrOrdinal = "GlobalAlloc"
0012FF90 00402039 UProtect.00402039

看看返回地址004B761D处的代码:

004B7607 8B85 886E4000 MOV EAX,DWORD PTR SS:[EBP+<AddrGetProcAddress>] &#61664;取得GetProcAddress的地址
004B760D 8038 CC CMP BYTE PTR DS:[EAX],0CC &#61664;这就是我们避免使用普通断点的原因了,UltraProtect会在调用函数前检查入口点代码是否为CC
004B7610 74 10 JE SHORT 004B7622
004B7612 90 NOP
004B7613 90 NOP
004B7614 90 NOP
004B7615 90 NOP
004B7616 58 POP EAX
004B7617 FF95 886E4000 CALL DWORD PTR SS:[EBP+<AddrGetProcAddress>]
004B761D /EB 19 JMP SHORT 004B7638 &#61664;返回到这里
在数据窗口中转存至004B6ECA看看,将会有不小的收获:
004B6ECA 47 6C 6F 62 61 6C 41 6C 6C 6F 63 00 47 6C 6F 62 GlobalAlloc.Glob
004B6EDA 61 6C 46 72 65 65 00 47 65 74 43 75 72 72 65 6E alFree.GetCurren
004B6EEA 74 50 72 6F 63 65 73 73 49 64 00 43 72 65 61 74 tProcessId.Creat
004B6EFA 65 54 6F 6F 6C 68 65 6C 70 33 32 53 6E 61 70 73 eToolhelp32Snaps
004B6F0A 68 6F 74 00 50 72 6F 63 65 73 73 33 32 46 69 72 hot.Process32Fir
004B6F1A 73 74 00 50 72 6F 63 65 73 73 33 32 4E 65 78 74 st.Process32Next
004B6F2A 00 43 6C 6F 73 65 48 61 6E 64 6C 65 00 43 72 65 .CloseHandle.Cre
004B6F3A 61 74 65 46 69 6C 65 41 00 54 65 72 6D 69 6E 61 ateFileA.Termina
004B6F4A 74 65 50 72 6F 63 65 73 73 00 49 73 44 65 62 75 teProcess.IsDebu
004B6F5A 67 67 65 72 50 72 65 73 65 6E 74 00 4F 70 65 6E ggerPresent.Open
004B6F6A 50 72 6F 63 65 73 73 00 52 65 61 64 46 69 6C 65 Process.ReadFile
004B6F7A 00 57 72 69 74 65 46 69 6C 65 00 46 72 65 65 4C .WriteFile.FreeL
004B6F8A 69 62 72 61 72 79 00 47 65 74 54 65 6D 70 50 61 ibrary.GetTempPa
004B6F9A 74 68 41 00 55 6E 68 61 6E 64 6C 65 64 45 78 63 thA.UnhandledExc
004B6FAA 65 70 74 69 6F 6E 46 69 6C 74 65 72 00 47 65 74 eptionFilter.Get
004B6FBA 54 68 72 65 61 64 43 6F 6E 74 65 78 74 00 53 65 ThreadContext.Se
004B6FCA 74 54 68 72 65 61 64 43 6F 6E 74 65 78 74 00 47 tThreadContext.G
004B6FDA 65 74 43 75 72 72 65 6E 74 54 68 72 65 61 64 00 etCurrentThread.
004B6FEA 55 53 45 52 33 32 2E 44 4C 4C 00 45 6E 75 6D 57 USER32.DLL.EnumW
004B6FFA 69 6E 64 6F 77 73 00 47 65 74 57 69 6E 64 6F 77 indows.GetWindow
004B700A 54 65 78 74 41 00 47 65 74 43 6C 61 73 73 4E 61 TextA.GetClassNa
004B701A 6D 65 41 00 50 6F 73 74 4D 65 73 73 61 67 65 41 meA.PostMessageA
004B702A 00 52 45 54 52 49 56 41 50 49 5A 43 46 00 00 00 .RETRIVAPIZCF...
CreateToolhelp32Snapshot、IsDebuggerPresent、TerminateProcess……这些都是比较经典的反跟踪组合了,UltraProtect在一个循环中依次加载了这些函数,已经将自己的意图暴露无疑。
下断点bp CreateToolhelp32Snapshot+2,由于UltraProtect会捕获异常检查硬件的调试器,这次我们还是利用普通CC断点,不过不能断在函数的入口。F9运行,经过无数的异常,我们终于停在CreateToolhelp32Snapshot的入口,Alt+F9返回外壳代码:

004B7FA7 C785 951D4000 2>MOV DWORD PTR SS:[EBP+<lpProcessEntry32>],128
004B7FB1 6A 00 PUSH 0
004B7FB3 6A 02 PUSH 2
004B7FB5 FF95 55204000 CALL DWORD PTR SS:[EBP+<AddrCreateToolhelp32Snapshot>] &#61664;取得系统进程快照的句柄
004B7FBB 8985 911D4000 MOV DWORD PTR SS:[EBP+<hSnapshot>],EAX
004B7FC1 BA 951D4000 MOV EDX,<lpProcessEntry32>
004B7FC6 03D5 ADD EDX,EBP
004B7FC8 52 PUSH EDX
004B7FC9 50 PUSH EAX
004B7FCA FF95 59204000 CALL DWORD PTR SS:[EBP+<AddrProcess32First>] &#61664;列举进程
004B7FD0 0BC0 OR EAX,EAX
004B7FD2 0F84 86000000 JE 004B805E &#61664;在这里强行跳转可以避开下面的进程名检测
004B7FD8 8DB5 B91D4000 LEA ESI,DWORD PTR SS:[EBP+<lpProcessEntry32->szExeFile>] &#61664;取得列举的进程名称
004B7FDE 8BFE MOV EDI,ESI
004B7FE0 8A07 MOV AL,BYTE PTR DS:[EDI]
004B7FE2 0AC0 OR AL,AL
004B7FE4 74 12 JE SHORT 004B7FF8
004B7FE6 90 NOP
004B7FE7 90 NOP
004B7FE8 90 NOP
004B7FE9 90 NOP
004B7FEA 3C 5C CMP AL,5C &#61664;是否为‘\’
004B7FEC 75 07 JNZ SHORT 004B7FF5
004B7FEE 90 NOP
004B7FEF 90 NOP
004B7FF0 90 NOP
004B7FF1 90 NOP
004B7FF2 8BF7 MOV ESI,EDI
004B7FF4 46 INC ESI
004B7FF5 47 INC EDI
004B7FF6 ^ EB E8 JMP SHORT 004B7FE0
004B7FF8 8BC6 MOV EAX,ESI
004B7FFA BF B6274000 MOV EDI,<szHostileProcess>
004B7FFF 03FD ADD EDI,EBP &#61664;取得敌意进程名列表
004B8001 8B17 MOV EDX,DWORD PTR DS:[EDI]
004B8003 66:0BD2 OR DX,DX
004B8006 74 22 JE SHORT 004B802A
004B8008 90 NOP
004B8009 90 NOP
004B800A 90 NOP
004B800B 90 NOP
004B800C 0AD2 OR DL,DL
004B800E 74 07 JE SHORT 004B8017
004B8010 90 NOP
004B8011 90 NOP
004B8012 90 NOP
004B8013 90 NOP
004B8014 47 INC EDI
004B8015 ^ EB EA JMP SHORT 004B8001
004B8017 47 INC EDI
004B8018 8BF0 MOV ESI,EAX
004B801A E8 4DF7FFFF CALL <SubCheckProcessName> &#61664;检查当前进程是否为规定的敌意进程
004B801F 80FE 01 CMP DH,1
004B8022 74 1C JE SHORT 004B8040
004B8024 90 NOP
004B8025 90 NOP
004B8026 90 NOP
004B8027 90 NOP
004B8028 ^ EB D7 JMP SHORT 004B8001

这里的敌意进程名列表也就是UltraProtect所定义的调试跟踪程序了,我们到数据转存窗口看看,UltraProtect可谓是对所有可能实施跟踪的程序赶尽杀绝了,大家熟知的OllyDbg和TRW自然位列其中,还有不少程序甚至闻所未闻!
004B77B6 00 45 58 45 53 50 59 00 57 58 52 39 35 00 52 45 .EXESPY.WXR95.RE
004B77C6 47 4D 4F 4E 00 46 49 4C 45 20 4D 4F 4E 49 54 4F GMON.FILE MONITO
004B77D6 52 00 52 45 47 4D 4F 4E 45 58 00 57 49 4E 44 4F R.REGMONEX.WINDO
004B77E6 57 20 44 45 54 45 43 54 49 56 45 00 44 45 42 55 W DETECTIVE.DEBU
004B77F6 47 56 49 45 57 00 52 45 53 53 50 59 00 41 44 56 GVIEW.RESSPY.ADV
004B7806 41 4E 43 45 44 20 52 45 47 49 53 54 52 59 20 54 ANCED REGISTRY T
004B7816 52 41 43 45 52 00 52 45 47 53 4E 41 50 00 4D 45 RACER.REGSNAP.ME
004B7826 4D 53 50 59 00 4D 45 4D 4F 52 59 20 44 4F 43 54 MSPY.MEMORY DOCT
004B7836 4F 52 00 50 52 4F 43 44 55 4D 50 33 32 00 4D 45 OR.PROCDUMP32.ME
004B7846 4D 4F 52 59 20 45 44 49 54 4F 52 00 46 52 4F 47 MORY EDITOR.FROG
004B7856 53 49 43 45 00 53 4D 55 20 57 49 4E 53 50 45 43 SICE.SMU WINSPEC
004B7866 54 4F 52 00 4D 45 4D 4F 52 59 20 44 55 4D 50 45 TOR.MEMORY DUMPE
004B7876 52 00 4D 45 4D 4F 52 59 4D 4F 4E 49 54 4F 52 00 R.MEMORYMONITOR.
004B7886 4E 55 4D 45 47 41 20 53 4F 46 54 49 43 45 20 4C NUMEGA SOFTICE L
004B7896 4F 41 44 45 52 00 55 52 53 4F 46 54 20 57 33 32 OADER.URSOFT W32
004B78A6 44 41 53 4D 00 47 45 4E 45 52 49 43 5F 57 4B 54 DASM.GENERIC_WKT
004B78B6 54 45 4C 4F 43 4B 44 55 4D 50 45 52 00 2D 3D 43 TELOCKDUMPER.-=C
004B78C6 48 49 4E 41 20 43 52 41 43 4B 49 4E 47 20 47 52 HINA CRACKING GR
004B78D6 4F 55 50 3D 2D 00 4F 6C 6C 79 44 62 67 00 54 52 OUP=-.OllyDbg.TR
004B78E6 57 32 30 30 30 00 00 00 00 00 00 00 00 00 00 00 W2000...........
一旦发现当前进程名符合上述特征,则毫不犹豫地调用TerminateProcess终止它。如果该进程恰好是当前正在调试Uprotect.exe的OllyDbg,那么UltraProtect也就自动退出了,这就是没有任何反反调试措施的OllyDbg在放开跟踪UltraProtect时会自动关闭的原因。(icefire:反反调试是相对于壳的反调试而言的。读起来异常拗口。)
004B802A B8 951D4000 MOV EAX,<lpProcessEntry32>
004B802F 03C5 ADD EAX,EBP
004B8031 50 PUSH EAX
004B8032 FFB5 911D4000 PUSH DWORD PTR SS:[EBP+<hSnapshot>]
004B8038 FF95 5D204000 CALL DWORD PTR SS:[EBP+<AddrProcess32Next>] &#61664;列举下一个进程
004B803E ^ EB 90 JMP SHORT 004B7FD0
004B8040 BE 951D4000 MOV ESI,<lpProcessEntry32> &#61664;如果发现被跟踪就会来到这里
004B8045 03F5 ADD ESI,EBP
004B8047 8B46 08 MOV EAX,DWORD PTR DS:[ESI+8] &#61664;取得当前进程的ID
004B804A 50 PUSH EAX
004B804B 6A 00 PUSH 0
004B804D 6A 01 PUSH 1
004B804F FF95 85204000 CALL DWORD PTR SS:[EBP+<AddrOpenProcess>]
004B8055 6A 00 PUSH 0
004B8057 50 PUSH EAX
004B8058 FF95 69204000 CALL DWORD PTR SS:[EBP+<AddrTerminateProcess>] &#61664;终止当前进程
004B805E FFB5 911D4000 PUSH DWORD PTR SS:[EBP+<hSnapshot>]
004B8064 FF95 61204000 CALL DWORD PTR SS:[EBP+<AddrCloseHandle>] ; kernel32.CloseHandle
004B806A 60 PUSHAD
004B806B E8 00000000 CALL 004B8070
004B8070 5E POP ESI
004B8071 83EE 06 SUB ESI,6
004B8074 B9 C8000000 MOV ECX,0C8
004B8079 29CE SUB ESI,ECX
004B807B BA 9A48F869 MOV EDX,69F8489A
004B8080 C1E9 02 SHR ECX,2
004B8083 83E9 02 SUB ECX,2
004B8086 83F9 00 CMP ECX,0
004B8089 7C 1A JL SHORT 004B80A5
004B808B 8B048E MOV EAX,DWORD PTR DS:[ESI+ECX*4]
004B808E 8B5C8E 04 MOV EBX,DWORD PTR DS:[ESI+ECX*4+4]
004B8092 33C3 XOR EAX,EBX
004B8094 C1C0 09 ROL EAX,9
004B8097 33C2 XOR EAX,EDX
004B8099 81F2 68561648 XOR EDX,48165668
004B809F 89048E MOV DWORD PTR DS:[ESI+ECX*4],EAX &#61664;这些代码把上面的进程检查部分重新加密
004B80A2 49 DEC ECX
004B80A3 ^ EB E1 JMP SHORT 004B8086
004B80A5 61 POPAD
004B80A6 61 POPAD
004B80A7 C3 RETN
上段代码是对已知进程名的显式校验,比较容易避开;而下面这段父进程校验就相对较难识别了。再次中断在CreateToolhelp32Snapshot后返回外壳代码:
004B7AF2 50 PUSH EAX
004B7AF3 8B85 51204000 MOV EAX,DWORD PTR SS:[EBP+<AddrGetCurrentProcessId>] &#61664;取得GetCurrentProcessId地址
004B7AF9 8038 CC CMP BYTE PTR DS:[EAX],0CC &#61664;入口断点检查
004B7AFC 74 10 JE SHORT 004B7B0E
004B7AFE 90 NOP
004B7AFF 90 NOP
004B7B00 90 NOP
004B7B01 90 NOP
004B7B02 58 POP EAX
004B7B03 FF95 51204000 CALL DWORD PTR SS:[EBP+<AddrGetCurrentProcessId>] &#61664;取得当前进程ID
004B7B09 EB 19 JMP SHORT 004B7B24
004B7B0B 90 NOP
004B7B0C 90 NOP
004B7B0D 90 NOP
004B7B0E 60 PUSHAD
004B7B0F 8DBD 39204000 LEA EDI,DWORD PTR SS:[EBP+<lpSensitiveAPIAddr>]
004B7B15 B9 26000000 MOV ECX,26
004B7B1A E8 81F8FFFF CALL <GetRandDword>
004B7B1F AB STOS DWORD PTR ES:[EDI] &#61664;若发现入口断点就随机破坏外壳API的地址
004B7B20 ^ E2 F8 LOOPD SHORT 004B7B1A
004B7B22 61 POPAD
004B7B23 58 POP EAX
004B7B24 8985 891D4000 MOV DWORD PTR SS:[EBP+<dwProcessId>],EAX
上面这段代码取得当前进程的ID,为下一步获得父进程名做准备。在接下来的处理中,我们会看到UltraProtect对调试器断点进行了更加严格的检查,所有的函数都不能在入口下普通断点了。
004B7B2A C785 951D4000 2>MOV DWORD PTR SS:[EBP+<lpProcessEntry32>],128
004B7B34 6A 00 PUSH 0
004B7B36 6A 02 PUSH 2
004B7B38 50 PUSH EAX
004B7B39 8B85 55204000 MOV EAX,DWORD PTR SS:[EBP+<AddrCreateToolhelp32Snapshot>]
004B7B3F 8038 CC CMP BYTE PTR DS:[EAX],0CC &#61664;检查入口断点
004B7B42 74 10 JE SHORT 004B7B54
004B7B44 90 NOP
004B7B45 90 NOP
004B7B46 90 NOP
004B7B47 90 NOP
004B7B48 58 POP EAX
004B7B49 FF95 55204000 CALL DWORD PTR SS:[EBP+<AddrCreateToolhelp32Snapshot>] &#61664;取得进程列表快照句柄
004B7B4F EB 19 JMP SHORT 004B7B6A
004B7B51 90 NOP
004B7B52 90 NOP
004B7B53 90 NOP
004B7B54 60 PUSHAD
004B7B55 8DBD 39204000 LEA EDI,DWORD PTR SS:[EBP+<lpSensitiveAPIAddr>]
004B7B5B B9 26000000 MOV ECX,26
004B7B60 E8 3BF8FFFF CALL <GetRandDword>
004B7B65 AB STOS DWORD PTR ES:[EDI] &#61664;发现入口断点就随机破坏外壳API地址
004B7B66 ^ E2 F8 LOOPD SHORT 004B7B60
004B7B68 61 POPAD
004B7B69 58 POP EAX
004B7B6A 8985 911D4000 MOV DWORD PTR SS:[EBP+<hSnapshot>],EAX
004B7B70 BA 951D4000 MOV EDX,<lpProcessEntry32>
004B7B75 03D5 ADD EDX,EBP
004B7B77 52 PUSH EDX
004B7B78 50 PUSH EAX
004B7B79 50 PUSH EAX
004B7B7A 8B85 59204000 MOV EAX,DWORD PTR SS:[EBP+<AddrProcess32First>]
004B7B80 8038 CC CMP BYTE PTR DS:[EAX],0CC &#61664;检查入口断点
004B7B83 74 10 JE SHORT 004B7B95
004B7B85 90 NOP
004B7B86 90 NOP
004B7B87 90 NOP
004B7B88 90 NOP
004B7B89 58 POP EAX
004B7B8A FF95 59204000 CALL DWORD PTR SS:[EBP+<AddrProcess32First>] &#61664;列举进程
004B7B90 EB 19 JMP SHORT 004B7BAB
004B7B92 90 NOP
004B7B93 90 NOP
004B7B94 90 NOP
004B7B95 60 PUSHAD
004B7B96 8DBD 39204000 LEA EDI,DWORD PTR SS:[EBP+<lpSensitiveAPIAddr>]
004B7B9C B9 26000000 MOV ECX,26
004B7BA1 E8 FAF7FFFF CALL <GetRandDword>
004B7BA6 AB STOS DWORD PTR ES:[EDI] &#61664;发现入口断点就随机破坏外壳API地址
004B7BA7 ^ E2 F8 LOOPD SHORT 004B7BA1
004B7BA9 61 POPAD
004B7BAA 58 POP EAX
004B7BAB 0BC0 OR EAX,EAX
004B7BAD 0F84 CE010000 JE 004B7D81
004B7BB3 8B95 9D1D4000 MOV EDX,DWORD PTR SS:[EBP+<lpProcessEntry32->the32ProcessID>]
004B7BB9 3B95 891D4000 CMP EDX,DWORD PTR SS:[EBP+<dwProcessId>] &#61664;是否为当前进程?
004B7BBF 74 46 JE SHORT 004B7C07 &#61664;不是就继续检查下一项,如果找到了就取得其父进程ID
004B7BC1 90 NOP
004B7BC2 90 NOP
004B7BC3 90 NOP
004B7BC4 90 NOP
004B7BC5 B8 951D4000 MOV EAX,<lpProcessEntry32>
004B7BCA 03C5 ADD EAX,EBP
004B7BCC 50 PUSH EAX
004B7BCD FFB5 911D4000 PUSH DWORD PTR SS:[EBP+<hSnapshot>]
004B7BD3 50 PUSH EAX
004B7BD4 8B85 5D204000 MOV EAX,DWORD PTR SS:[EBP+<AddrProcess32Next>]
004B7BDA 8038 CC CMP BYTE PTR DS:[EAX],0CC &#61664;检查入口断点
004B7BDD 74 10 JE SHORT 004B7BEF
004B7BDF 90 NOP
004B7BE0 90 NOP
004B7BE1 90 NOP
004B7BE2 90 NOP
004B7BE3 58 POP EAX
004B7BE4 FF95 5D204000 CALL DWORD PTR SS:[EBP+<AddrProcess32Next>] &#61664;列举进程
004B7BEA EB 19 JMP SHORT 004B7C05
004B7BEC 90 NOP
004B7BED 90 NOP
004B7BEE 90 NOP
004B7BEF 60 PUSHAD
004B7BF0 8DBD 39204000 LEA EDI,DWORD PTR SS:[EBP+<lpSensitiveAPIAddr>]
004B7BF6 B9 26000000 MOV ECX,26
004B7BFB E8 A0F7FFFF CALL <GetRandDword>
004B7C00 AB STOS DWORD PTR ES:[EDI] &#61664;发现入口断点就随机破坏外壳API地址
004B7C01 ^ E2 F8 LOOPD SHORT 004B7BFB
004B7C03 61 POPAD
004B7C04 58 POP EAX
004B7C05 ^ EB A4 JMP SHORT 004B7BAB
004B7C07 8B85 AD1D4000 MOV EAX,DWORD PTR SS:[EBP+<lpProcessEntry32->the32ParentProcessID>]
004B7C0D 8985 8D1D4000 MOV DWORD PTR SS:[EBP+<dwParentProcessId>],EAX &#61664;取得当前进程的父进程ID
004B7C13 FFB5 911D4000 PUSH DWORD PTR SS:[EBP+<hSnapshot>]
004B7C19 50 PUSH EAX
004B7C1A 8B85 61204000 MOV EAX,DWORD PTR SS:[EBP+<AddrCloseHandle>]
004B7C20 8038 CC CMP BYTE PTR DS:[EAX],0CC &#61664;检查入口断点
004B7C23 74 10 JE SHORT 004B7C35
004B7C25 90 NOP
004B7C26 90 NOP
004B7C27 90 NOP
004B7C28 90 NOP
004B7C29 58 POP EAX
004B7C2A FF95 61204000 CALL DWORD PTR SS:[EBP+<AddrCloseHandle>]
004B7C30 EB 19 JMP SHORT 004B7C4B
004B7C32 90 NOP
004B7C33 90 NOP
004B7C34 90 NOP
004B7C35 60 PUSHAD
004B7C36 8DBD 39204000 LEA EDI,DWORD PTR SS:[EBP+<lpSensitiveAPIAddr>]
004B7C3C B9 26000000 MOV ECX,26
004B7C41 E8 5AF7FFFF CALL <GetRandDword>
004B7C46 AB STOS DWORD PTR ES:[EDI] &#61664;发现入口断点就随机破坏外壳API地址
004B7C47 ^ E2 F8 LOOPD SHORT 004B7C41
004B7C49 61 POPAD
004B7C4A 58 POP EAX
以上代码通过系统进程列表取得了当前进程的父进程ID,接下来就是对父进程的合法性进行检查了。列举进程的代码与上面基本相同,限于篇幅这里不再赘述,下面只给出关键校验代码流程分析:

004B7CD4 8B95 9D1D4000 MOV EDX,DWORD PTR SS:[EBP+<lpProcessEntry32->the32ProcessID>]
004B7CDA 3B95 8D1D4000 CMP EDX,DWORD PTR SS:[EBP+<dwParentProcessId>] &#61664;比较当前进程是否为指定的父进程
004B7CE0 74 46 JE SHORT 004B7D28
………………
………………
004B7D28 8DB5 B91D4000 LEA ESI,DWORD PTR SS:[EBP+<lpProcessEntry32->szExeFile>] &#61664;取得父进程名
004B7D2E E8 AFF8FFFF CALL <Capitalize> &#61664;改进程名为大写形式
004B7D33 803E 00 CMP BYTE PTR DS:[ESI],0
004B7D36 74 07 JE SHORT 004B7D3F
004B7D38 90 NOP
004B7D39 90 NOP
004B7D3A 90 NOP
004B7D3B 90 NOP
004B7D3C 46 INC ESI
004B7D3D ^ EB F4 JMP SHORT 004B7D33
004B7D3F 83EE 0C SUB ESI,0C
004B7D42 AD LODS DWORD PTR DS:[ESI] &#61664;取前4各字节
004B7D43 0306 ADD EAX,DWORD PTR DS:[ESI] &#61664;加上后4个字节
004B7D45 0346 04 ADD EAX,DWORD PTR DS:[ESI+4] &#61664;再加后四个字节
004B7D48 8BD8 MOV EBX,EAX &#61664;保存求和结果
004B7D4A 8DB5 DD2A4000 LEA ESI,DWORD PTR SS:[EBP+<lpPredefniedHash>] &#61664;将计算结果与预定义值进行比较,共有三组
004B7D50 AD LODS DWORD PTR DS:[ESI]
004B7D51 0BC0 OR EAX,EAX
004B7D53 74 0E JE SHORT 004B7D63
004B7D55 90 NOP
004B7D56 90 NOP
004B7D57 90 NOP
004B7D58 90 NOP
004B7D59 3BC3 CMP EAX,EBX &#61664;是否等于合法父进程的计算值?
004B7D5B 74 24 JE SHORT 004B7D81
004B7D5D 90 NOP
004B7D5E 90 NOP
004B7D5F 90 NOP
004B7D60 90 NOP
004B7D61 ^ EB ED JMP SHORT 004B7D50
004B7D63 BE 951D4000 MOV ESI,<lpProcessEntry32>
004B7D68 03F5 ADD ESI,EBP
004B7D6A 8B46 08 MOV EAX,DWORD PTR DS:[ESI+8] &#61664;取得当前列举的进程ID
004B7D6D 50 PUSH EAX
004B7D6E 6A 00 PUSH 0
004B7D70 6A 01 PUSH 1
004B7D72 FF95 85204000 CALL DWORD PTR SS:[EBP+<AddrOpenProcess>]
004B7D78 6A 00 PUSH 0
004B7D7A 50 PUSH EAX
004B7D7B FF95 69204000 CALL DWORD PTR SS:[EBP+<AddrTerminateProcess>] &#61664;终止非法父进程
在上面的代码中UltraProtect将父进程名进行求和计算,计算结果同Explorer.exe、cmd.exe等常规进程的计算值进行比较,如果不相等就认为正在被非法调试,并结束自身进程。避开了上述反跟踪代码后,UltraProtect对OllyDbg就没有什么威胁了。直接对CODE区块下内存访问断点,F9运行直接停在原始入口点,调用OllyDump插件保存为dUProtect.exe:
00487370 55 PUSH EBP &#61664;停在这里
00487371 8BEC MOV EBP,ESP
00487373 83C4 F4 ADD ESP,-0C
00487376 B8 18714800 MOV EAX,00487118
0048737B E8 80F1F7FF CALL 00406500
00487380 A1 F0744900 MOV EAX,DWORD PTR DS:[4974F0]
00487385 8B00 MOV EAX,DWORD PTR DS:[EAX]
00487387 E8 18FDFBFF CALL 004470A4
0048738C 8B0D F8724900 MOV ECX,DWORD PTR DS:[4972F8]
00487392 A1 F0744900 MOV EAX,DWORD PTR DS:[4974F0]
00487397 8B00 MOV EAX,DWORD PTR DS:[EAX]
00487399 8B15 F8C94700 MOV EDX,DWORD PTR DS:[47C9F8]
UltraProtect在代码变形方面没有什么采取什么极端手段,除了两个特殊的SDK函数以外,用ImportRec就可以轻松修复。填入入口点RVA:00087370,自动搜索得到IAT的RVA:0009B13C,Size:0000067C,点击Get Imports得到Dll列表如下:
kernel32.dll FThunk:0009B140 NbFunc:2D (decimal:45) valid:YES
? FThunk:0009B1F8 NbFunc:4 (decimal:4) valid:NO
advapi32.dll FThunk:0009B20C NbFunc:3 (decimal:3) valid:YES
oleaut32.dll FThunk:0009B21C NbFunc:7 (decimal:7) valid:YES
kernel32.dll FThunk:0009B23C NbFunc:5 (decimal:5) valid:YES
advapi32.dll FThunk:0009B254 NbFunc:3 (decimal:3) valid:YES
kernel32.dll FThunk:0009B264 NbFunc:49 (decimal:73) valid:YES
gdi32.dll FThunk:0009B38C NbFunc:47 (decimal:71) valid:YES
? FThunk:0009B4AC NbFunc:9B (decimal:155) valid:NO
ole32.dll FThunk:0009B71C NbFunc:1 (decimal:1) valid:YES
comctl32.dll FThunk:0009B724 NbFunc:18 (decimal:24) valid:YES
shell32.dll FThunk:0009B788 NbFunc:1 (decimal:1) valid:YES
comdlg32.dll FThunk:0009B790 NbFunc:1 (decimal:1) valid:YES
jcalg1.dll FThunk:0009B798 NbFunc:2 (decimal:2) valid:YES
perplex.dll FThunk:0009B7A4 NbFunc:4 (decimal:4) valid:YES
至此大多数函数已经成功解析,点击Show Invalid,将没有认出的函数修复为MessageBoxA,单击Fix Dump完成初步脱壳。由于UltraProtect采用了SDK形式的Embedded Protect进行保护,所以如果你要激活所有功能并实现跨平台运行,还要进行SDK修复。这部分已经超出本文的讨论范围,限于篇幅不再讨论。
ACProtect
ACProtect各版本的外壳反调试代码与UltraProtect相比并没有太大变化,但是基于SDK的Dynamic Encrypt、Embedded Protect和RSA Lock加上编译器无关的Code Replace处理仍然十分强悍,要完整地把内部代码脱出至正常运行是十分困难的,更不用说还原为原始的可执行文件了。本文主要介绍一下ACProtect对输入表重定向的一些特殊处理,为了使ImportRec更有效地工作,笔者编写了一个还原IAT的辅助程序,这样无须对外壳代码做任何修改就可以直接在原始入口点完美识别大多数的API函数。由于考虑到兼容性问题,ACProtect主程序并没有采用API随机重定向,ACProtect本身也做了如下建议:以非函数调用指令中直接引用API地址的程序需要慎用这一特性,所以我们以典型的ACProtect保护样本UltraFXP v1.07免费版(新版已更名为FTPRush)为例,简要说明这种加密形式的修复方法。
OllyDbg载入Ultrafxp.exe,首先忽略所有异常,下断点bp IsDebuggerPresent+6,F9运行中断,按下Alt+F9返回到外壳代码:
00696155 C685 98FF4000 C>MOV BYTE PTR SS:[EBP+40FF98],0C3
0069615C FF95 ABD74000 CALL DWORD PTR SS:[EBP+<AddrIsDebuggerPresent>]
00696162 0BC0 OR EAX,EAX &#61664;返回到这里
00696164 74 1F JE SHORT 00696185
00696166 90 NOP
00696167 90 NOP
00696168 90 NOP
00696169 90 NOP
0069616A 8BB5 7AA24100 MOV ESI,DWORD PTR SS:[EBP+41A27A]
接着打开Memory Map窗口,对CODE段下内存访问断点,F9运行,直接来到伪OEP(Original Entry Point):
00407A30 50 PUSH EAX ; UltraFxp.005DB4E8 &#61664;断下后停在这里
00407A31 6A 00 PUSH 0
00407A33 E8 F8FEFFFF CALL 00407930
00407A38 BA 10F15D00 MOV EDX,005DF110
00407A3D 52 PUSH EDX
00407A3E 8905 DC945F00 MOV DWORD PTR DS:[5F94DC],EAX
00407A44 8942 04 MOV DWORD PTR DS:[EDX+4],EAX
00407A47 C742 08 0000000>MOV DWORD PTR DS:[EDX+8],0
00407A4E C742 0C 0000000>MOV DWORD PTR DS:[EDX+C],0
00407A55 E8 8AFFFFFF CALL 004079E4
00407A5A 5A POP EDX
00407A5B 58 POP EAX
00407A5C E8 2BC1FFFF CALL 00403B8C
00407A61 C3 RETN
值得注意的是,ACProtect偷走了主程序入口的大部分主干代码。在这里dump,程序是不可能运行起来的。事实上ACProtect的Stolen Code对Delphi程序特别照顾,一般都要偷走入口以后好几个call才会罢手。
跟进伪OEP的第一个call,来到:
00407930 - FF25 E0C25F00 JMP DWORD PTR DS:[5FC2E0] ; UltraFxp.006872B4
00407936 8BC0 MOV EAX,EAX
很明显这里是调用API的地方了,F8再次跟进:
006872B4 68 7B62C60D PUSH 0DC6627B
006872B9 813424 52D74671 XOR DWORD PTR SS:[ESP],7146D752
006872C0 C3 RETN &#61664;调用GetModuleHandleA
注意006872B4,这次我们来到了ACProtect的外壳代码中。ACProtect用一个随机数异或加密API地址,调用的时候再异或解密,然后以ret的方式进行调用。这样的API加密相对ASProtect等猛壳来说显然幼稚多了,但是从工具恢复的难度而言,对阻碍ImportRec这样的静态模拟跟踪修复却相当有效。
注意上面的“JMP DWORD PTR DS:[5FC2E0],5FC2E0”就是IAT的一个Entry,在数据转存窗口中上下翻页,确定IAT的大小。
IAT上限:005FC1B8
005FC1B8 10 70 68 00 1D 70 68 00 2A 70 68 00 37 70 68 00 ph. ph.*ph.7ph.
005FC1C8 44 70 68 00 51 70 68 00 5E 70 68 00 6B 70 68 00 Dph.Qph.^ph.kph.
005FC1D8 78 70 68 00 85 70 68 00 92 70 68 00 9F 70 68 00 xph.卲h.抪h.焢h
IAT下限:005FCAE8
005FCAC8 00 00 00 00 F7 A8 B2 76 00 00 00 00 27 6E 3C 77 ....鳕瞯....'n<w
005FCAD8 BF FC 3D 77 F9 E8 3C 77 00 00 00 00 41 3F A2 71 奎=w?<w....A??
005FCAE8 F4 2B A2 71 00 00 00 00 00 00 00 00 00 00 00 00 ??............
注意ACProtect只对有限的几个Dll函数(如kernel32.dll、user32.dll等)进行加密,IAT表中68XXXXXX的地址都是被加密的,我们的工具就是用来还原这些API地址的。启动ExtractACP.exe,点击Refresh刷新进程列表,选中Ultrafxp.exe,填入参数,然后点击Extract按钮修复所有地址。
这时启动ImportRec,选中UltraFXP的进程,填入伪OEP的RVA:7A30,自动搜索IAT,点击Get Imports,就可以识别出大多数函数了:
? FThunk:001FC1B8 NbFunc:36 (decimal:54) valid:NO
oleaut32.dll FThunk:001FC294 NbFunc:F (decimal:15) valid:YES
? FThunk:001FC2D4 NbFunc:15 (decimal:21) valid:NO
? FThunk:001FC32C NbFunc:65 (decimal:101) valid:NO
gdi32.dll FThunk:001FC4C4 NbFunc:62 (decimal:98) valid:YES
? FThunk:001FC650 NbFunc:CF (decimal:207) valid:NO
ole32.dll FThunk:001FC990 NbFunc:C (decimal:12) valid:YES
32.dll FThunk:001FC9C4 NbFunc:3 (decimal:3) valid:YES
comctl32.dll FThunk:001FC9D4 NbFunc:1B (decimal:27) valid:YES
imm32.dll FThunk:001FCA44 NbFunc:6 (decimal:6) valid:YES
winspool.drv FThunk:001FCA60 NbFunc:4 (decimal:4) valid:YES
shell32.dll FThunk:001FCA74 NbFunc:A (decimal:10) valid:YES
shell32.dll FThunk:001FCAA0 NbFunc:5 (decimal:5) valid:YES
comdlg32.dll FThunk:001FCAB8 NbFunc:4 (decimal:4) valid:YES
winmm.dll FThunk:001FCACC NbFunc:1 (decimal:1) valid:YES
shell32.dll FThunk:001FCAD4 NbFunc:3 (decimal:3) valid:YES
ws2_32.dll FThunk:001FCAE4 NbFunc:2 (decimal:2) valid:YES
注意其中有一些没有认出的Dll,其实并不是识别失败,而是由于SDK函数与不同Dll的函数混合在一起了。这里我们还是暂时把没有识别成功的函数修复为MessageBoxA,然后手动Cut Thunks分离不同的Dll,最后可以得到一张完整的树文件tree.txt。至此,ACProtect的输入表得到了完美修复。
在众多的国产加密壳中,ACProtect属于比较有特色的一款,但是其最大的难点不在于外壳的反调试,而在于借助SDK实现的RSA加密和壳中壳技术,它极尽可能的使受保护的程序和外壳更紧密地融合在一起,并与脱壳前的部分进行通信和校验。这样即使软件被脱壳,也无法轻易还原所有功能。根据笔者目前对ACProtect v2.0的认识,虽然作者risco将版本号作了大幅度跃迁,但是程序本身的保护体系还是没有太大变化,只是增加了一些完整性校验。ACProtect还在升级,传奇仍将继续
PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-3-13 23:42
  • 签到天数: 7 天

    [LV.3]偶尔看看II

    发表于 2007-8-8 16:32:07 | 显示全部楼层
    那位牛人写的?分析的好细啊,顶一下/:018

    [ 本帖最后由 baof 于 2007-8-8 16:33 编辑 ]
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2007-8-28 12:03:36 | 显示全部楼层
    关于ACProtect的脱壳文章或教程都是极少的,希望有高手发些此类的文章或教程。
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表