首页
论坛
专栏
课程

[求助]WL 脱壳后修复问题求助

scpczc 2018-10-11 11:19 225
WL 脱壳, IAT修复后,代码中存在大量的 变异 call。 本来应该为 FF 15  ?? ?? ?? ?? ->变异为 E8 ?? ?? ?? ?? 90. 跳转到壳代码调用API。不知道
有什么好办法修复。手工修复太麻烦了。 还不知道某些函数是否有偷字节行为。
没学习过壳中进行代码变异的机制,是代码解压的时候就是变异代码?还是解压完后,再变异这些call?

此外,请教下X64DBG脚本,如何存取数组。 我目前是alloc 内存,然后 mov 进去数值。有没有更简洁的方法?

拜谢!



快讯:看雪智能设备漏洞挖掘公开课招生中!

最新回复 (9)
scpczc 2018-10-11 13:12
2
消灭0回复
MistHill 6天前
3
在Themida/Winlicense中对用户代码段的APIs调用,将FF15 Call和FF25 Jmp都转换为E8 Call/Nop和E9 Jmp/Nop。其实就是IAT处理,这种处理方式主要有以下好处:
  • 提高压缩率,与UPX压缩前的预处理类似;
  • 避免可能的重定位带来的麻烦。它不会保留原来的重定位表,只保存那些代码被VM部分的重定位项;
  • 降低代码的可阅读性,从而增加分析难度;
  • 便于实现API加密。
在壳代码运行时,对用户代码段处理的简单流程为:
  • 解密、解压缩该段数据。解密:TMD/WL以DWORD为单位,VMP以BYTE为单位;解压缩TMD/WL、VMP都是使用的aPLib!这里不细说。
  • 导入函数处理:APIs Thunk,又分API加密与否(取决于保护时的选择)。
  • 对被VM的代码重定位。这是一个Bug,到最版都是这样。原来代码的位置变为一堆垃圾,都去VM里跑了,你还重定位干吗!
以下示例代码片段为用户代码段刚解密、解压缩后:
005A2752   56                PUSH    ESI
005A2753   8D45 F8           LEA     EAX, [EBP-0x8]
005A2756   50                PUSH    EAX
005A2757   90                NOP                             <- a
005A2758   90                NOP
005A2759   90                NOP
005A275A   90                NOP
005A275B   90                NOP
005A275C   90                NOP
005A275D   8B75 FC           MOV     ESI, [EBP-0x4]
005A2760   3375 F8           XOR     ESI, [EBP-0x8]
005A2763   90                NOP                             <- b
005A2764   90                NOP
005A2765   90                NOP
005A2766   90                NOP
005A2767   90                NOP
005A2768   90                NOP
005A2769   33F0              XOR     ESI, EAX
005A276B   90                NOP                             <- c
005A276C   90                NOP
005A276D   90                NOP
005A276E   90                NOP
005A276F   90                NOP
005A2770   90                NOP
005A2771   33F0              XOR     ESI, EAX
005A2773   90                NOP                             <- d
005A2774   90                NOP
005A2775   90                NOP
005A2776   90                NOP
005A2777   90                NOP
005A2778   90                NOP
005A2779   33F0              XOR     ESI, EAX
005A277B   8D45 F0           LEA     EAX, [EBP-0x10]
005A277E   50                PUSH    EAX
005A277F   90                NOP                             <- e
005A2780   90                NOP
005A2781   90                NOP
005A2782   90                NOP
005A2783   90                NOP
005A2784   90                NOP
005A2785   8B45 F4           MOV     EAX, [EBP-0xC]
005A2788   3345 F0           XOR     EAX, [EBP-0x10]
005A278B   33F0              XOR     ESI, EAX
005A278D   3BF7              CMP     ESI, EDI
005A278F   75 07             JNZ     SHORT 005A2798
005A2791   BE 4FE640BB       MOV     ESI, 0xBB40E64F
005A2796   EB 0B             JMP     SHORT 005A27A3
请注意:这里有5个连续的6字节NOP指令,后面表明实际上就是5个FF15 Call(API调用)。显然这会提高压缩率!
壳代码在进行IAT处理时,会解密一张保存的表,包括各API Name的32位Hash(是哪个API)、代码中每个调用这个API的RVA、Call/Jmp标志、API是否加密等等,每处理完一表项就清零之。
因为这个表的解密过程被VM了,难以简单地说清楚,按下不表。

当壳代码运行完成到OEP时,这段代码是这个样子:
005A2752   56                PUSH    ESI
005A2753   8D45 F8           LEA     EAX, [EBP-0x8]
005A2756   50                PUSH    EAX
005A2757   E8 44DEEF02       CALL    034A05A0                        <- a
005A275C   90                NOP
005A275D   8B75 FC           MOV     ESI, [EBP-0x4]
005A2760   3375 F8           XOR     ESI, [EBP-0x8]
005A2763   E8 69DA2303       CALL    037E01D1                        <- b
005A2768   90                NOP
005A2769   33F0              XOR     ESI, EAX
005A276B   90                NOP                                     <- c
005A276C   E8 5F70267C       CALL    7C8097D0                        ; kernel32.GetCurrentThreadId
005A2771   33F0              XOR     ESI, EAX
005A2773   E8 82E00403       CALL    035F07FA                        <- d
005A2778   90                NOP
005A2779   33F0              XOR     ESI, EAX
005A277B   8D45 F0           LEA     EAX, [EBP-0x10]
005A277E   50                PUSH    EAX
005A277F   E8 A7DA1503       CALL    0370022B                        <- e
005A2784   90                NOP
005A2785   8B45 F4           MOV     EAX, [EBP-0xC]
005A2788   3345 F0           XOR     EAX, [EBP-0x10]
005A278B   33F0              XOR     ESI, EAX
005A278D   3BF7              CMP     ESI, EDI
005A278F   75 07             JNZ     SHORT 005A2798
005A2791   BE 4FE640BB       MOV     ESI, 0xBB40E64F
005A2796   EB 0B             JMP     SHORT 005A27A3
5个连续的6字节NOP指令处被修改,全部变成E8????????90或90E8????????。
Nop指令在Call之前,还是在Call之后,是随机的!在前的机率要小一些,约0x50/0xFF的概率。

这里,我们只能看到kernel32.GetCurrentThreadId这个API调用,其他4个被“加密”了!你不知道这段代码在干什么,可读性非常差。

我们来看看对应的IAT。有些API入口被修改(加密),有些则没有:
005D7204  034A0000
005D7208  034A05A0                                      <- a
005D720C  7C9300C4  ntdll.RtlAllocateHeap
005D7210  7C92FF2D  ntdll.RtlFreeHeap
...
005D72E8  035F0000
005D72EC  035F07FA                                      <- d
005D72F0  03600000
...
005D73B4  03700000
005D73B8  0370022B                                      <- e
005D73BC  037005E8
...
005D7448  037D0DB2
005D744C  7C8097D0  kernel32.GetCurrentThreadId         <- c
005D7450  037E0000
005D7454  037E009E
005D7458  037E01D1                                      <- b
005D745C  037E020E
...

API“加密”的简单过程:
  • 当一个API被指定为加密,视API代码复杂程度,分配一段0x1000或0x2000大小的可执行内存。该段可能含几个APIs的入口代码片段;
  • 壳代码部分有个算法引擎,按一些规则复制很小部分API代码到该段。这个代码片段最后都会跳到API自己的代码段继续执行;
  • 对该代码片段进行膨胀变异;
  • 修改IAT中API的入口地址。
当我们用脚本把API加密“解除”并“还原”FF15 Call后,该段代码是这个样子:
005A2752   56                PUSH    ESI
005A2753   8D45 F8           LEA     EAX, [EBP-0x8]
005A2756   50                PUSH    EAX
005A2757   FF15 08725D00     CALL    NEAR [0x5D7208]         ; a) kernel32.GetSystemTimeAsFileTime
005A275D   8B75 FC           MOV     ESI, [EBP-0x4]
005A2760   3375 F8           XOR     ESI, [EBP-0x8]
005A2763   FF15 58745D00     CALL    NEAR [0x5D7458]         ; b) kernel32.GetCurrentProcessId
005A2769   33F0              XOR     ESI, EAX
005A276B   FF15 4C745D00     CALL    NEAR [0x5D744C]         ; c) kernel32.GetCurrentThreadId
005A2771   33F0              XOR     ESI, EAX
005A2773   FF15 EC725D00     CALL    NEAR [0x5D72EC]         ; d) kernel32.GetTickCount
005A2779   33F0              XOR     ESI, EAX
005A277B   8D45 F0           LEA     EAX, [EBP-0x10]
005A277E   50                PUSH    EAX
005A277F   FF15 B8735D00     CALL    NEAR [0x5D73B8]         ; e) kernel32.QueryPerformanceCounter
005A2785   8B45 F4           MOV     EAX, [EBP-0xC]
005A2788   3345 F0           XOR     EAX, [EBP-0x10]
005A278B   33F0              XOR     ESI, EAX
005A278D   3BF7              CMP     ESI, EDI
005A278F   75 07             JNZ     SHORT 005A2798
005A2791   BE 4FE640BB       MOV     ESI, 0xBB40E64F
005A2796   EB 0B             JMP     SHORT 005A27A3

是不是能读懂这段代码了!这时“还原”后的IAT为:
005D7204  7C801E54  kernel32.GetStartupInfoW
005D7208  7C8017E9  kernel32.GetSystemTimeAsFileTime            <- a
005D720C  7C9300C4  ntdll.RtlAllocateHeap
005D7210  7C92FF2D  ntdll.RtlFreeHeap
...
005D72E8  7C80A174  kernel32.WideCharToMultiByte
005D72EC  7C80934A  kernel32.GetTickCount                       <- d
005D72F0  7C8112FF  kernel32.WriteFile
...
005D73B4  7C809B84  kernel32.VirtualFree
005D73B8  7C80A4C7  kernel32.QueryPerformanceCounter            <- e
005D73BC  7C8099B5  kernel32.GetACP
...
005D7448  7C81013C  kernel32.GlobalAddAtomW
005D744C  7C8097D0  kernel32.GetCurrentThreadId                 <- c
005D7450  7C80981A  kernel32.InterlockedDecrement
005D7454  7C80B741  kernel32.GetModuleHandleA
005D7458  7C8099C0  kernel32.GetCurrentProcessId                <- b
005D745C  7C80EE9C  kernel32.FindClose
...

显然,在某些Exe和Dll的情况下,用户代码段的这些FF15 Call和FF25 Jmp都是需要重定位的!
脱壳后除了修复IAT,可能还需要把重定位表补回去。
scpczc 5天前
4
今天又仔细看了一遍,非常感谢。
理解了32位下的解密流程。很有作用。
目前手里的程序是X64的。IAT被加密的没有上边那么多,还是比较容易修复。上边的都已经不容易看出IAT的样子了。
X64 dll 入口好像没有偷字节,直接在入口处硬断点就可以dump了。
E8类型 jmp 已经修复了。目前E9 CALL 类型,我正在利用地址特性搜索过滤,然后跑脚本找真正的API。已经找出来了,正在往FF15修复。
回复真的工整详尽,非常感觉!!
scpczc 5天前
5
MistHill 在Themida/Winlicense中对用户代码段的APIs调用,将FF15 Call和FF25 Jmp都转换为E8 Call/Nop和E9 Jmp/Nop。其实就是IAT处理,这种处理方式主要有以 ...
另外,我dll一般有重定位问题,我一般就是把dll直接禁止重定位加载调试,所以地址就在180000000空间上。省去很多地址转换计算。也保证了调试过程中的地址重复可用性问题。
这次问题主要是系统dll 每次开机好像基地址不一样。导致第一次dump计算处的跳转地址找不到。
xiaohang 3天前
6
其实原壳在解压和解密的过程中就有这些操作的,你只要找到原壳修复IAT的重定向的位置,就可以用脚本来一次性修复了。
scpczc 2天前
7
xiaohang 其实原壳在解压和解密的过程中就有这些操作的,你只要找到原壳修复IAT的重定向的位置,就可以用脚本来一次性修复了。
是的.得分析出跳转地址计算方法。目前做法是在后期来修复。困难重重,大部分E8????????90的修复为FF15.但是有一部分 是 E8????????90是FF25变异的。导致修复完后很多异常,堆栈不平衡异常,也只能逐步排查。
scpczc 2天前
8
xiaohang 其实原壳在解压和解密的过程中就有这些操作的,你只要找到原壳修复IAT的重定向的位置,就可以用脚本来一次性修复了。
好像X64DBG的脚本在内存存取上很不稳定。有些需要单步调试才能正常。
xiaohang 1天前
9
x64dbg的脚本的确还不完善,不过你这个是32位的,为什么不用od脚本?
scpczc 1天前
10
xiaohang x64dbg的脚本的确还不完善,不过你这个是32位的,为什么不用od脚本?
我的是X64的,地址是0000000180000000,64位.OD不得行.   MistHill  的示例程序是X32的.
X64的脚本调试很痛苦. mov [addr], 123456 这种要Tab才能正确执行,而且可能有一定几率是没有操作的效果.一般运行2次就好了.
在脚本不能使用var 定义变量, 因为第二次时会报错. 
使用的最新的,10.11发布
返回