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