首页
社区
课程
招聘
[原创]脱壳之带反调试的PESpin壳
2021-12-31 21:33 33043

[原创]脱壳之带反调试的PESpin壳

2021-12-31 21:33
33043

清除硬件断点的方法有哪些?
修改调试寄存器,CONTEXT结构体

  1. API
    Kernel32.SetThreadContext
    ntdll.ZwSetContextThread
    2.异常
    异常回调中可以修改CONTEXT

1.对API下断点,测试是否会断下
应该是存在判断敏感API的机器码,检测第一个字节是否是0xCC,故此处导致程序崩溃了,破解方法就是在第二行反汇编下断点。
图片描述
尝试在下一行重新下断点,结果程序直接运行了?并没有断下。
图片描述
再次尝试在API对应的底层函数调用的位置下断点
图片描述
图片描述
结果发现程序还是运行起来了,并没有断下,所以应该不是API,而是异常

 

2.搭建异常触发环境
首先我们需要对OD进行一些配置,在菜单中找到调试选项,将一些异常取消忽略,按照以下配置。
图片描述
除了OD调试选项的配置之外,一些反反调试插件也需要做些配置,这里我们使用的是StrongOD插件,在插件中找到StrongOD,取消插件对一些异常的跳过。按照以下配置对插件进行设置。
图片描述
程序运行起来,发现一共有7个异常,需要排查出对硬件断点清0的地方,这里采用在异常点设置硬件断点的方法,尝试看能不能断下来,如果能断下来说明当前异常点前面的异常处理函数中没有对硬件断点清0。

 

第一个异常:
图片描述
第二个异常:
图片描述
第三个异常:
图片描述
第四个异常:
图片描述
第五个异常:
图片描述
第六个异常:
图片描述
第七个异常:
图片描述
按照上面的思路,首先在第二个异常点设置硬件执行断点,然后动态调试程序,如果硬件执行断点有效(在状态栏中可以发现硬件断点命中的提示信息)说明第一个异常点的异常回调函数中没有清除硬件断点的代码。

 

硬件执行断点的特点是到了这个地址就会停下来,而异常是必须触发,即必须执行了这段代码,执行了这条指令才能够触发,如果先触发了硬件断点,就意味着这个异常前面的异常处理函数是没有清除硬件断点的,

 

按照这样的方式,依次测试每一个异常的回调函数。
第二个异常:
这条指令还没有执行之前就已经触发了硬件断点,说明第一个函数内没有清除硬件断点
图片描述
清除第二个异常处的硬件断点,依次向下一个异常继续下硬件执行断点

 

第三个异常
图片描述
这条指令还没有执行之前就已经触发了硬件断点,说明第二个函数内没有清除硬件断点
图片描述
第四个异常
这条指令直接触发了异常,说明第三个函数内有清除硬件断点的代码
图片描述
综上所述,发现第三个异常,即第一个STI特权指令异常回调是清除硬件断点的代码。
打开SEH链观察
图片描述
在0043AF42的位置下断点
图片描述
继续shift+F9进入第三个异常处理函数
图片描述
上面代码由于是和异常、清除硬件断点有关的,所以其中肯定有异常相关的结构体和寄存器相关的Context结构,直接分析代码不太容易看出来,这个时候我们就可以将其直接在这个地方进行DUMP,这里DUMP不是为了脱壳,只是单纯的DUMP,然后方便在IDA中查看其中的代码,使用IDA特有的解析结构体的功能,解析可能存在的结构体。
图片描述
进入IDA,我直接一手F5......
图片描述
在结构体中Inser添加一个Context结构
图片描述
在伪代码中转换结构体
图片描述
图片描述
图片描述
继续回到OD,分析这段代码应该就是清除硬件断点的代码
图片描述
初步思路:在0043AF59的位置设置一个断点,到了这个断点之后将清除硬件断点的这段代码清除一下,将其NOP掉,后面的硬件断点就能正常的触发了

 

将之前更改的设置还原,把异常重新跳过

 

在OEP和清除硬件断点的位置下硬件断点,然后重新运行程序
图片描述
当程序运行至清除硬件断点的位置时将其NOP掉
图片描述
再运行就发现硬件断点正常的触发了
图片描述
继续向下分析,发现IAT加密了
图片描述
所以就需要解密IAT,照常设置硬件写入断点
图片描述
重新运行程序,NOP清除硬件断点的位置,然后程序断在此处,一翻完全看不清代码啊
图片描述
点击EIP回到原来的位置,按Ctrl+↑(微调反汇编引擎,向上一个字节),找到正确的代码,这里看起来不是填充IAT的位置
图片描述
继续运行,仍然看不出来,继续按Ctrl+↑,找到正确的代码,这里看起来应该就是填充IAT的位置
图片描述

 

填充IAT的循环

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
① 循环读取API字符串,判断第一个字节是否指定字符
00438F44      8>MOV EDI,DWORD PTR DS:[EBX]
00438F46      0>ADD EDI,DWORD PTR SS:[EBP+0x40421E]     ; kernel32.75E20000
00438F4C      8>CMP BYTE PTR DS:[EDI],0x48
00438F4F      7>JNZ SHORT 06.00438FA1
 
00438FA1      8>ADD EBX,0x4
00438FA4      4>INC ECX
00438FA5      8>CMP ECX,0x54F
00438FAB    ^ 7>JNZ SHORT 06.00438F44
 
② 循环读取API字符串中的字符,求HASH
0043A70E      8>MOV AL,BYTE PTR DS:[EDI]
0043A710      0>OR AL,AL
0043A712      7>JE SHORT 06.0043A746  ;退出循环
0043A725      4>INC EDI                                 ; kernel32.75EDBBDF
0043A726      3>XOR DL,AL
0043A728      B>MOV AL,0x8
0043A72A      E>JMP SHORT 06.0043A72D
0043A736      D>SHR EDX,1
0043A738      7>JNB SHORT 06.0043A740
0043A73A      8>XOR EDX,0xEDB88320
0043A740      F>DEC AL
0043A742    ^ 7>JNZ SHORT 06.0043A72A
 
③ 判断hash值是否与当前一致,不一致继续循环,一致进行下一步
00438F7A      3>CMP EAX,0xA124E28D
00438F7F      7>JNZ SHORT 06.00438FA1
00438F81      8>MOV EAX,DWORD PTR SS:[EBP+0x40421A]     ; 已经寻找到正确的hash
 
 
④ 根据hash值,寻找API偏移,求出API地址
00438F81      8>MOV EAX,DWORD PTR SS:[EBP+0x40421A]     ; kernel32.75ED7848
00438F87      D>SHL ECX,1
00438F89      0>ADD EAX,ECX
00438F8B      0>MOVZX EAX,WORD PTR DS:[EAX]
00438F8E      C>SHL EAX,0x2
00438F91      0>ADD EAX,DWORD PTR SS:[EBP+0x404226]     ; kernel32.75ED4DD0
00438F97      8>MOV EAX,DWORD PTR DS:[EAX]
00438F99      0>ADD EAX,DWORD PTR SS:[EBP+0x40421E]     ; 计算完之后,EAX是API地址
 
特殊情况,需要特殊处理一下
 
⑤ 读取API中的代码,生成加密API代码
代码特别长,只复制了一部分
00439333      AC             LODS BYTE PTR DS:[ESI]
00439334      8AF8           MOV BH,AL
00439336      8A27           MOV AH,BYTE PTR DS:[EDI]
00439338      47             INC EDI
00439339      C0EC 04        SHR AH,0x4
0043933C      2AC4           SUB AL,AH
0043933E    ^ 73 F6          JNB SHORT 06.00439336
 
⑥ 填充IAT

再接着在写入IAT附件寻找获取API的位置
可以使用RUN跟踪的方法,设置EIP == 0043918C 即可(只需要翻一万两千七百多行就可以找到了)
获取API地址(通过RUN跟踪找到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
00438F91                        0385 26>ADD EAX,DWORD PTR SS:[EBP+0x404226]                        ; kernel32.75ED4DD0
00438F97                        8B00    MOV EAX,DWORD PTR DS:[EAX]
00438F99                        0385 1E>ADD EAX,DWORD PTR SS:[EBP+0x40421E]                        ; kernel32.75E20000
00438F9F                        EB 10   JMP SHORT 06.00438FB1   ; eax=API地址
 
004399A1                        8D8428 >LEA EAX,DWORD PTR DS:[EAX+EBP+0x404D09]
004399A8                        894424 >MOV DWORD PTR SS:[ESP+0x8],EAX                             ; 06.004399B7
004399AC                        5A      POP EDX                                                    ; 06.004292E5
004399AD                        58      POP EAX                                                    ; 06.004292E5
004399AE                        8D6424 >LEA ESP,DWORD PTR SS:[ESP+0x4]  ; eax=API地址
 
004399D7                        8D8428 >LEA EAX,DWORD PTR DS:[EAX+EBP+0x404D4B]
004399DE                        894424 >MOV DWORD PTR SS:[ESP+0x8],EAX                             ; 06.004399F9
004399E2                        5A      POP EDX                                                    ; 06.004292E5
004399E3                        58      POP EAX                                                    ; 06.004292E5
004399E4                        EB 07   JMP SHORT 06.004399ED  ; eax=API地址

继续单步跟踪分析
图片描述
继续跟踪发现将EAX进行比较,说明在判断hash,不相等的时候会跳转,所以直接在00438F81的位置下断点,让程序运行至此
图片描述
继续跟踪,发现到这个位置后EAX中就变成API地址了
图片描述
后来发现在这个位置填充IAT的话会有问题,这里就要提到导入表中的特殊API
有些API 在Kernel32导出,实际代码却在ntdll
比如:Kernel32.HeapAlloc,实际代码是在ntdll.RtlAllocateHeap
这种情况下从kernel32中获取HeapAlloc函数的地址,地址里面保存的是函数字符串,NTDLL.RtlAllocateHeap

 

遇到这种情况,需要特殊处理
①先判断地址处是不是NTDLL打头,是的话就需要二次获取API地址
②解析字符串,获取模块名以及API地址
NTDLL
RtlAllocateHeap
③加载模块获取模块地址,获取API地址

1
2
3
4
5
00438FE2      57             PUSH EDI                                ; 06.00438000
00438FE3      FF95 70584000  CALL DWORD PTR SS:[EBP+0x405870]        ; kernel32.LoadLibraryA
00438FE9      50             PUSH EAX                                ; 06.00428C00
00438FEA      FF95 75584000  CALL DWORD PTR SS:[EBP+0x405875]        ; kernel32.GetProcAddress
00438FF0      EB 01          JMP SHORT 06.00438FF3

拿到这四个地址后就可以编写脚本了。
编写脚本的思路:
①设置必要的断点
②构建运行循环
③第一个断下的是清除硬件断点的异常回调函数,将其NOP掉
④再次循环会断到获取API的点,保存API地址
⑤再次循环会断到写入IAT的点,将保存的API填充IAT
⑥一直循环,最终到达原始OEP

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
27
28
29
30
31
32
33
34
35
36
37
38
39
// 1.定义变量
MOV dwOEP,00409486
MOV dwClearBPH, 0043AF59
MOV dwGetAPI,004399E4 
MOV dwWriteIAT,0043918C
 
// 2. 清除环境
BC    // 清除所有软件断点
BPHWC // 清除所有硬件断点
BPMC  // 清除内存断点
 
// 3. 设置断点
BPHWS dwOEP, "x" //当执行到此地址时产生中断.
BPHWS dwClearBPH, "x" //当执行到此地址时产生中断.
BPHWS dwGetAPI, "x" //当执行到此地址时产生中断.
BPHWS dwWriteIAT, "x" //当执行到此地址时产生中断.
 
// 4. 循环
LOOP0:
  RUN // F9     
  CMP dwClearBPH,eip 
  JNZ CASE0       
  FILL dwClearBPH,1A,90 
  BPHWC dwClearBPH 
  JMP LOOP0
CASE0:
  CMP dwGetAPI,eip 
  JNZ CASE1 
  MOV dwTMP,eax
  JMP LOOP0
CASE1:
  CMP dwWriteIAT,eip   
  JNZ CASE2
  MOV [edi],dwTMP       //MOV DWORD PTR DS:[EDI],EAX
  JMP LOOP0
CASE2:
  CMP dwOEP,eip 
  JNZ LOOP0 
  MSG "OEP已到达"

图片描述
观察数据窗口,发现每一个API地址中间都用00隔开了
图片描述
而且观察地址,发现咋跑到壳这个区段来了,正常来说VC6.0的程序应该在第二个区段才对
图片描述
查看以下422000,发现也有IAT
图片描述
查看一下模块间调用,发现有43的也有42的,这里的原因上文已经提到过了
图片描述
因为这里的IAT比较诡异,所以我们在内存中重新规划IAT表
先申请一块内存
图片描述
复制地址
图片描述
图片描述
以此类推,我们把所有的调用都复制粘贴过来就可以了。

 

还是用工具吧,这也太累了...顺便修复一下输入表
图片描述
修复完成了
图片描述
再看一下,都填充好了,确实比我手工快一点点
图片描述
接下来就是日常DUMP,修复,完活儿
图片描述
图片描述
图片描述


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞6
打赏
分享
最新回复 (9)
雪    币: 2041
活跃值: (2800)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
xiaohang 3 2021-12-31 22:08
2
1
提供了一个处理硬件断点对抗的普遍思路,值得新手借鉴,故设精华,希望再接再厉
雪    币: 132
活跃值: (543)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
China龍星 2022-1-1 01:42
3
0
太硬核了,牛
雪    币: 1992
活跃值: (3688)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
method 2022-1-1 22:35
4
0
学习了
雪    币: 8124
活跃值: (4427)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
sunsjw 1 2022-1-4 13:23
5
0
学习一下。
雪    币: 15
活跃值: (512)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jfztaq 2022-1-4 19:54
6
0
太厉害了,你为什么这么优秀
雪    币: 563
活跃值: (95)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lixupeng 2022-1-5 21:28
7
0
收藏 
雪    币: 3867
活跃值: (3495)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
APT_华生 1 2022-1-7 09:43
8
0
15PB优秀学员?
雪    币:
活跃值: (172)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ricroon 2022-1-8 11:19
9
0
很详细,大佬太厉害了
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 2022-1-8 11:35
10
0
mark
游客
登录 | 注册 方可回帖
返回