知识点回顾

1、PE文件的Magic code(魔数、幻数)是什么?

​ MZ头、PE头

2、PE文件中文件头的信息有哪些?

​ 运行平台、时间戳、PE文件属性、区段数量、扩展头的大小

3、PE文件中扩展头的信息有哪些?

​ EP的RVA、ImageBase(400000)、代码段起始地址、数据段起始地址 数据目录表、数据目录表项数量、文件对齐、内存对齐、映像总大小

4、 PE文件中区段信息有哪些?

​ 区段名称、虚拟地址、虚拟大小、文件偏移、文件大小、区段属性(C0000020、60000020)

5、PE文件中数据目录表有哪些表?

​ 导出表、导入表、异常表、tls表、资源表、IAT、重定位表

6、一个进程,三环下有哪些数据结构?

​ 进程环境块(PEB)、线程环境块(TEB)、tls结构

7、导入表结构体字段有几个,分别是什么?

​ 5个字段

​ 第一个是OrginalFirstThunk,里面是rva,指向的INT,

​ 第二个是时间戳,

​ 第三个是转发机 制用到的ForWarderChain,

​ 第四个是name,rva,dll名称字符串,

​ 第五个是FirstThunk,指向的是 IAT。 INT或IAT在文件中存的是一样的,存的是指向名称字符串的rva或者一个序号

手工加壳

目标:将代码段加密,以防止IDA等静态工具分析。

步骤:

  1. 添加一个区段(文件大小、区段数量)

  2. 将原OEP修改为新区段中的地址

  3. 将代码段异或加密

  4. 在新区段新OEP处,添加异或解密代码

注意: ① 代码段默认没有可写属性,需要修改。 ② 有随机基址属性的程序会重定位代码,一般我们会去掉这个属性 如果没有去掉,就需要在壳代码中进行重定位

具体步骤

添加区段

使用LordPE编辑区段信息

再使用010Editor添加文件数据

修改OEP,增加OEP代码

原OEP:000011D2 新OEP:17000 修改OEP

添加代码

通过技巧获取基地址再跳转

另一种改法

关于代码的演化

① 如果直接去掉随机基址,我们可以在新OEP处,直接写代码跳转到原始OEP

② 我们可以在新OEP处定义出模块基地址,然后再加上原始OEP RVA

③ 可以在新OEP处通过call pop 组合获取当前指令地址,再减去偏移,计算出模块基址

以上代码的机器码复制到有随机基址的同一程序同一位置中,同样也是可以正常运行的

加密代码段

代码段信息

在010Editor中操作代码段

选中代码段

异或代码段

增加解密代码

由于代码段本身有重定位信息,那么如果加密之后,重定位会出现问题,所以应该去掉随机基址 40 81 -> 00 81

脱壳

脱壳的目的:

  1. Cracker(破解者) 脱壳、解密、破解

  2. 杀毒引擎(脱壳引擎、反病毒虚拟机) 解密、查杀病毒、扫描特征

脱壳的步骤:

  1. 找到原始OEP

    一般来说,找到原始OEP或者我们跟踪到原始OEP时,程序都会完成解密操作

  2. Dump内存到文件

    当可执行文件在内存已经完成解密之后,我们将内存中代码数据转储(dump)到文件,就可以进 行进一步分析。

  3. 修复文件(常见于修复IAT,重建导入表)

    从内存中转储的内存数据代码有一些与原本文件中的内容是不一致的。比如说IAT表,内存中IAT 表会被初始为函数地址表,而文件中IAT表与INT表内容一致。所以要想让程序正常运行,一般 都需要修复IAT,因为加壳之后程序一般都会自己去处理导入表、IAT以及重定位等。

导入表和IAT

IAT表在文件中保存的是一个RVA数组,每一项指向指向了函数字符串结构

在内存中,这个RVA数组,被修改为函数地址,每一个函数地址就是之前对应的函数字符串的函 数

所以从内存中dump出的文件,必须进行IAT修复或者修复导入表。

脱自己加的壳

  1. 找到原始OEP

    单步跟踪,很容易找到原始OEP

  2. Dump内存到文件

    在原始OEP处进行DUMP内存,原因就是这个时候内存没有做太多的初始化

3.修复IAT或者修复导入表

使用ImportREC修复

概述

脱壳是一项综合技术,结合PE文件格式、汇编指令的分析,调试加密的程序并将其还原的一 个过程。

壳一般分为两种,加密壳和压缩壳,里面所使用的技术有,压缩算法、对代码加密、对IAT 加密、对资源加密。 我们从简单入手-压缩壳。

关于压缩算法

  1. 有损压缩 一个像素点:RGB 红绿蓝 一个图片:(3,4,5),(4,5,3),(5,4,3) 压缩后:(4,4,4),3
  2. 无损压缩 一个文件:0,0,0,0,0,0,0 压缩后:0,7

脱壳三步法

  1. 寻找原始OEP
  2. dump内存到文件
  3. 修复文件

脱壳三步法-寻找OEP技巧

  1. 堆栈平衡法(ESP定律

    壳代码就像一个函数,进入时会开辟堆栈、保存寄存器环境,退出时会恢复堆栈、恢复 寄存器。所以应该是堆栈平衡的,那我们可以在壳代码操作了堆栈后对堆栈设置访问或 写入断点,然后运行程序,当断点命中的时候,应该就是退出壳代码的时候。在其附近 单步几次,应该就能到达程序的原始OEP。

  2. 特征定位法

    在我们熟悉的程序中,我们可以使用特征来定位程序原始OEP。特征有几种: ① 二进制特征 比如release版的VS2013是: oep入口特征:E8????????E9 第一个CALL内的特征:5657BF4EE640BBBE0000FFFF3BC7 ② API特征 比如release版的VS2013的第一个API调用是: GetSystemTimeAsFileTime 比如vc6.0的第一个API调用是: GetVersion 比如Delphi程序第一个API调用是: GetModuleHandleA ③编译器特征 IAT调用不同的编译器生成的调用机器码是不一样 vs -》 call [IAT地址] -》 FF15 xx xx xx xx delphi -> jmp [IAT地址] -》 FF25 xx xx xx xx

脱壳练习

壳代码的基本流程

① 保存寄存器环境

② 加载一些必要的API

③ 解密代码和数据

④ 修复重定位

⑤ 填充IAT

⑥ 恢复寄存器环境

脱壳-0.aspack.exe

壳代码分析

1、壳OEP

2、加载必要API

3、解密解压缩代码

4、修复重定位

修复重定位的公式:

重定位表中存储两个有用字段:

  1. 需要重定位的分页地址
  2. 需要重定位的分页偏移 重定位分为两步:
  3. 计算出 重定位地址,要重定位的地址=模块基地址+分页地址+分页偏移
  4. 修复要重定位的地址中数据,[要重定位的地址] - 默认模块基地址 + 当前模块基地址

5、填充IAT

原理:

① 从导入表中获取dll名称

② 从导入表中的INT,获取函数名称或者序号

③ 通过GetModuleHandleA或者LoadLibraryA获取模块基地址、通过GetProcAddress获取函数地址

④ 将函数地址填充到对应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
001D726F 03F2 ADD ESI,EDX 								; esi=导入表结构
001D7271 8B46 0C MOV EAX,DWORD PTR DS:[ESI+0xC] ; 获取模块DLL名称RVA
001D7274 85C0 TEST EAX,EAX
001D7276 0F84 0D010000 JE 00_aspac.001D7389
001D727C 03C2 ADD EAX,EDX ; 加上基地址,字符串VA
001D727E 8BD8 MOV EBX,EAX
001D7280 50 PUSH EAX
001D7281 FF95 A90F0000 CALL DWORD PTR SS:[EBP+0xFA9] ; 获取模块基地址
001D7287 85C0 TEST EAX,EAX
001D7289 75 07 JNZ SHORT 00_aspac.001D7292
001D728B 53 PUSH EBX
001D728C FF95 AD0F0000 CALL DWORD PTR SS:[EBP+0xFAD]
001D7292 8985 A9050000 MOV DWORD PTR SS:[EBP+0x5A9],EAX ; 保存模块基地址
001D7298 C785 AD050000>MOV DWORD PTR SS:[EBP+0x5AD],0x0
001D72A2 8B95 88040000 MOV EDX,DWORD PTR SS:[EBP+0x488] ; 获取基地址
001D72A8 8B06 MOV EAX,DWORD PTR DS:[ESI] ; 获取指向OrignalFirstTh
001D72AA 85C0 TEST EAX,EAX
001D72AC 75 03 JNZ SHORT 00_aspac.001D72B1
001D72AE 8B46 10 MOV EAX,DWORD PTR DS:[ESI+0x10]
001D72B1 03C2 ADD EAX,EDX ; 计算得出 OrignalFirstT
001D72B3 0385 AD050000 ADD EAX,DWORD PTR SS:[EBP+0x5AD] ; 0
001D72B9 8B18 MOV EBX,DWORD PTR DS:[EAX] ; 获取INT中的数据,即指向
001D72BB 8B7E 10 MOV EDI,DWORD PTR DS:[ESI+0x10] ; 获取 FirstThunk
001D72BE 03FA ADD EDI,EDX ; 计算得出 IAT 地址
001D72C0 03BD AD050000 ADD EDI,DWORD PTR SS:[EBP+0x5AD] ; 0
001D72C6 85DB TEST EBX,EBX ; 判断结束
001D72C8 0F84 A5000000 JE 00_aspac.001D7373
001D72CE F7C3 00000080 TEST EBX,0x80000000 ; 判断是否是序号
001D72D4 75 04 JNZ SHORT 00_aspac.001D72DA
001D72D6 03DA ADD EBX,EDX ; 指向函数字符串结构=INT
001D72D8 43 INC EBX ; 减去2,跳过字符串结构的
001D72D9 43 INC EBX
001D72DA 53 PUSH EBX ; 保存寄存器环境
001D72DB 81E3 FFFFFF7F AND EBX,0x7FFFFFFF
001D72E1 53 PUSH EBX ; 压入字符串或是序号
001D72E2 FFB5 A9050000 PUSH DWORD PTR SS:[EBP+0x5A9]
001D72E8 FF95 A50F0000 CALL DWORD PTR SS:[EBP+0xFA5] ; 获取函数地址
001D72EE 85C0 TEST EAX,EAX
001D72F0 5B POP EBX ; 恢复寄存器环境

⑤ 修改属性,跳转原始OEP

脱壳分析(aspack.exe 重定位表分析)

根据以上分析,这个壳代码支持重定位,所以最好这个程序脱完后也支持重定位,如果感觉到麻烦可以直接执行到OEP后直接dump即可

重定位表

偏移16000

大小0xC74

支持重定位的脱壳

首先不能让01257238 66:830E FF OR WORD PTR DS:[ESI],0xFFFF 这条语句执行,他会让脱壳之后的程序运行找不到重定位表

找到入口点OEP

在壳入口处的执行完pushad指令后给esp下硬件访问断点

进入该入口点后进行dump

从内存dump到文件

修复IAT

修复重定位

脱壳-00.exe

找到OEP

一步一步往下跟

Dump

修复IAT

修改00.exe入口点

修改00.exe使其支持入口点为0的程序

脱壳-06.exe

下的硬件断点都断不下来,基本断定这个程序存在反调试,

硬件断点的设置有两种方法(更改CONTEXT结构)

1, 通过API(SetThreadContext)

2, 通过异常

这个时候就需要判断是那种方法更改了硬件断点

先在API下一个软件断点发现无法触发,基本断定是异常更改了硬件断点

干掉反调试

只有把所有的异常触发都过一遍,看那个一异常处理了硬件断点

思路:

1.把OD所有的异常忽略取消掉

2.在每一个异常前下一个硬件断点(当硬件断点没有触发的时候就能断定它的前一个异常处理把硬件断点更改了)

3.把更改硬件断点的指令给nop掉

忽略异常

将OD的忽略异常取消勾选

包括插件存在的异常忽略

给异常处理下断

给每一个异常前下一个硬件断点,发现到第四个异常前的硬件断点没有被触发,说明硬件断点的处理在第三个异常理中

验证硬点断点

在上图断点处dump到IDA中验证

将清0指令nop掉

只有把清楚硬件断点的指令nop掉后续才能更好的分析壳代码

找到OEP

可以通过PEID查看到06的链接器是vc 6.0

Ctrl + G 输入 GetVersion(API找到)然后通过栈回溯

OEP:0x409486

找打填充IAT的地址

在OEP出的调用函数中可以看出是被加密的,可以通过对被加密函数的地址下硬件写入断点,从而找出填充IAT的地方

然后在此处断下,可以得到填充IAT的

地址:0x43918C

找到指令:mov [EDI],EAX

找到获取函数地址

OD脚本编写

根据以上信息可写出自动填充IAT的OD脚本

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
//初始化变量
MOV CleanBaPoint, 0043AF59 //清除硬件断点清零指令
MOV OepPoint, 00409486 //入口点
MOV ReadIatPoint, 00438FF0 //读取IAT点
MOV WirteItaPoint, 0043918C


//清除所有断点
BC // 清除所有软件断点
BPHWC // 清除所有硬件断点
BPMC // 清除内存断点


//设置硬件断点
BPHWS CleanBaPoint,"x" //硬件执行,硬件断点默认就是执行,所以也可不写
BPHWS OepPoint
BPHWS ReadIatPoint
BPHWS WirteItaPoint

//循环操作
LOOP1:
RUN
CMP eip,CleanBaPoint
JNZ LOOP2
FILL CleanBaPoint,1A,90
JMP LOOP1

LOOP2:
CMP eip,ReadIatPoint
JNZ LOOP3
MOV IAT,eax
JMP LOOP1

LOOP3:
CMP eip,WirteItaPoint
JNZ LOOP4
MOV [edi],IAT

LOOP4:
CMP eip,OepPoint
JNZ LOOP1
MSG "修复完成"

修复IAT

重建IAT

DUMP已重建IAT的程序

修复IAT

成功

脱壳-07.exe

绕过反调试

直接运行07这个程序发现存在反虚拟机调试

所以这里需要绕过反虚拟机调试,不然后边就没办法调试了

绕过反虚拟机有三种方法

​ 1. 修改Windows 7(专业版).vmx文件(虚拟机系统版本对应的.vmx文件)

一下修改方法只针对这个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
isolation.tools.getPtrLocation.disable = "TRUE"
isolation.tools.setPtrLocation.disable = "TRUE"
isolation.tools.setVersion.disable = "TRUE"
isolation.tools.getVersion.disable = "TRUE"
monitor_control.disable_directexec = "TRUE"
monitor_control.disable_chksimd = "TRUE"
monitor_control.disable_ntreloc = "TRUE"
monitor_control.disable_selfmod = "TRUE"
monitor_control.disable_reloc = "TRUE"
monitor_control.disable_btinout = "TRUE"
monitor_control.disable_btmemspace = "TRUE"
monitor_control.disable_btpriv = "TRUE"
monitor_control.disable_btseg = "TRUE"
usb:0.present = "TRUE"
usb:0.deviceType = "hid"
usb:0.port = "0"
usb:0.parent = "-1"

​ 2.使用RUN跟踪,自动跟踪程序,找出反虚拟机指令

在真机中 IN EAX,DX会触发0xC0000096异常,在虚拟机中不会 直到自动单步到IN EAX,DX,之后会出现虚拟机弹窗

  1. 先跟踪一段程序,待程序代码解密之后,搜索指令

直接搜索:

in eax,dx cmp ebx,’VMXh’

即: in eax,dx

cmp ebx,0x564d5868

我使用第三种方法感觉实在一点

中间od出问题了,所以更换了

更改逻辑,使其能够跳转

找到OEP

由于这个程序是Vc6.0,所以能够通过搜索API,Getversion函数进行栈回溯

但是这个OEP有点奇怪,好像被偷了,不管他把他修改成正常的OEP看能不能运行

修复IAT

使用通用输入表修复工具进行修复

这个工具的逻辑是:

​ 从代码段的开始到结尾进行查找 E8 的指令 更改成FF15开头 (变成相对地址)

DUMP

修复