概述

壳作为当前软件保护行业的高端技术,从一出现开始,就一直在安全的对抗前沿领域,经过多年的发展,出现了各种各样的加密壳,但是无论如何基本的思想还是没有变化,即通过对pe文件的变形处理,达到防止软件被分析与破解的目的。

前置知识

壳的运行原理:

加壳过的EXE文件是可执行文件,它可以同正常的EXE文件一样执行。用户执 行的实际上是外壳程序,这个外壳程序负责把用户原来的程序在内存中解压缩,并把控制权交 还给解开后的真正程序,这一切工作都是在内存中运行的,整个过程对用户是透明的。

编译:

将单个的 .c 或 .cpp 编译成中间文件 (.obj),在 VS 下,这个过程由 cl.exe 程序完成。

链接:

将编译出的 .obj 中间文件、系统的启动文件和用到的库文件链接成一个可执行文件 (.exe)。VS 下由 link.exe 程序完成

装载:

将一个可执行文件映射到虚拟地址空间并执行,由操作系统完成。在装载的过程中, 完了让程序正常被执行,会有下列几个步骤:

1.判断是否开启重定位,如果开启了,将 PE 文件加载到指定位置,并且修复目标PE 文件的重 定位。

2.遍历导入表,加载使用到的所有模块到内存,修复模块相关的信息,并根据导入表中的函数 名称,填充所有的 IAT 地址项。

3.查看当前是否存在 TLS 回调函数,如果存在,则传入进程创建事件,依次调用 所有的 TLS 回调函数。

4.以 PE 文件中的 AddressOfEntryPoint 为起始位置,创建线程并运行。

MZ 头:

1
2
(0x00) WORD e_magic: 标识当前是一个有效的 DOS 文件,必须为 0x5A4D
(0x3C) LONG e_lfanew: 保存了一个偏移,用于找到 NT 头部分。

文件头:

对于 NumberOfSections 字段,它保存的是当前 PE 文件内包含的区段数量,区段 的数量必须在 0~96之间,如果超出了这个范围,PE加载器在加载文件时,会提示无效的 PE 文件。

对于 Characteristics 字段,标志位 IMAGE_FILE_RELOCS_STRIPPED 用于标识当前是否包含 重定位信息,如果将此字段设置为 0,则标识不包含重定位信息,必须将其加载到扩展头中 ImageBase 指定的加载基址。可用于关闭程序的重定位

1
2
3
(0x00) WORD Machine: 程序的运行平台,通常为 0x14C0x8664
(0x02) WORD NumberOfSections: 区段的数量,增删区段必须修改
(0x12) WORD Characteristics: 文件属性标识字段,可用于区分 dll/exe 等

扩展头:

1
2
3
4
5
6
7
8
9
10
11
(0x10) DWORD AddressOfEntryPoint: 保存程序的入口点 RVA 。
(0x1C) DWORD ImageBase: 默认的加载基址,通常是 0x400000\0x10000000
(0x20) DWORD SectionAlignment: 内存中的对齐力度,通常是 0x1000
(0x24) DWORD FileAlignment: 文件中的对齐力度,通常是 0x200
(0x38) DWORD SizeOfImage: 映像文件的大小,可以不被对齐。
(0x46) WORD DllCharacteristics: 属性字段,可用于关闭动态基址。
[0]导出表: 最多有一个,保存所有导出的函数、对象、类等信息。
[1]导入表: 可能有多个,保存所有使用到的模块和函数的信息。
[2]资源表: 三层结构,保存用到的所有资源,例如图标、对话框等。
[5]重定位: 由多个重定位块组成,保存了需要重定位的项所在的地址。
[9]TLS表: 延迟加载表,保存了 TLS 变量和函数的信息。

区段表:

PE 文件中的每一个区段都有一个专门的结构体进行描述,这些结构体组合成了一张 区段表,保存在扩展头的后面,可以使用 IMAGE_FIRST_SECTION 找到。在进行加壳时,通 常都会添加一个新的区段保存壳相关的代码,与之相应的也需要添加一个新的区段结构,除此 之外,我们还需要修改文件头中的区段数量以及扩展头中的映像大小等字段。

1
2
3
4
5
6
(0x00) BYTE Name[IMAGE_SIZEOF_SHORT_NAME]: 区段名称,通常具有意义。
(0x08) DWORD VirtualSize: 区段在内存中的大小。
(0x0C) DWORD VirtualAddress: 区段基址在内存中的 RVA。
(0x10) DWORD SizeOfRawData: 区段在文件中占用的大小。
(0x14) DWORD PointerToRawData: 区段基址在文件中的 FOA。
(0x24) DWORD Characteristics: 主要描述了区段的属性,例如是否可执行。

导出表:

导出表记录了当前模块所有被导出内容的信息,最多只会存在一张,其中保存了一些 重要的字段,在这里我们主要需要关注的是其中的三张表,其中序号表和名称表一一对应,序 号表中存储的是地址表的下标,只要理清楚了三张表之间的关系,相应的就可以 实现自己的 GetProcAddress 了:

导入表:

导入表记录当前程序使用了哪些模块的哪些函数,如果使用了多个模块,相应的也会 存在多张导入表,最终会以一个全0 的导入表结构结尾。相对来讲,导入表结构中的 INT并不 是必不可少的,即使 INT内没有保存任何的内容,只要确保 IAT中的值是有效的,程序仍然可 以执行。

当文件没有加载到内存时,IAT和 INT中保存的通常序号或导入名称结构的RVA,一旦程序被 加载到内存中,PE加载器就会为 IAT填充函数的真实地址,填充的过程如下可以使用下面的图 进行描述:

重定位表:

当程序使用了操作全局变量的语句时时,生成的 OPCODE 中会直接包含有绝对地 址,一旦发生重定位,使用这个地址就会产生问题,所以需要通过重定位表保存所有需要重定 位的地址,在程序运行时交由 PE 加载器进行修复。重定位地址的修复依赖下面的公式:

1
重定位后的VA = 重定位前的VA – 默认加载基址 + 实际加载基址

一个 PE 文件中可以有多个重定位块,每一个重定位块由一个重定位结构和一组重定位项组 成,用于描述 一个分页内 所有需要重定位的地址 所在的位置

1
2
3
4
5
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 需要重定位的分页 RVA
DWORD SizeOfBlock; // 重定位块的大小
// WORD TypeOffset[1]; // 所在区段的偏移
} IMAGE_BASE_RELOCATION;

在 PE文件中,一个地址由三个部分组成,分别是 加载基址、区段RVA以及区段内偏移,其中 加载基址 和 区段的RVA 可能都会有所改变,只要任意一个产生了变化,都需要为它进行重定位。

常用代码

获取 kernel32.dll

暴力搜索内存:

暴力搜索法是最早的动态查找Kernel32基址的方法

原理: 几乎所有的 win32 可执行文件(pe 格式文件)运行的时候都会加载 kernel32.dll,可执行文件进入入口点执行后 esp 存放的一般是 Kernel32.DLL 中的某个地 址,所以沿着这个地址向上查找就可以找到 kernel32 的基地址

由于kernel32.dll也是标准的pe结构的文件,PE文件开始是IMAGE_DOS_HEADER结构,这个结构的第一个字段是e_magic,它的值为’MZ’用于证明这是DOS兼容的文件类型,所以找到地址所指向的字符串为MZ就可以确定这个地址就是kernel32的基地址

1
2
3
4
5
6
7
8
9
10
11
12
__asm
{
mov ebx, dword ptr ss : [esp]
and ebx, 0xFFFF0000
cmp_label:
cmp word ptr ds : [ebx], 0x5A4D
je end_label
sub ebx, 0x10000
jmp cmp_label
end_label:
mov eax, ebx
}

使用PEB获取基址的相关结构体

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
// 查看 PEB 结构体,找到 0x0C 偏移位置的 _PEB_LDR_DATA
0:000> dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
// 查看 _PEB_LDR_DATA 结构,其中保存了三个双向链表的结构体
0:000> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY*
+0x014 InMemoryOrderModuleList : _LIST_ENTRY*
+0x01c InInitializationOrderModuleList : _LIST_ENTRY*
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
// 每一个节点都对应了同一个结构体的不同位置
0:000> dt _LDR_DATA_TABLE_ENTRY -b
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x018 DllBase : Ptr32
+0x01c EntryPoint : Ptr32
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32
+0x02c BaseDllName : _UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32

PEB 查找 Kernel32.dll 的基址

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 获取到 PEB 结构体的地址
0:000> !peb
PEB at 00553000
// 查看 PEB 结构体的内容
0:000> dt _PEB 00553000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0x4 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00770000 Void
+0x00c Ldr : 0x7746dca0 _PEB_LDR_DATA
// 查看 Ldr 字段内的数据
0:000> dt _PEB_LDR_DATA 0x7746dca0 -b
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
// 加载顺序 当前模块 -> ntdll.dll -> kernel32.dll
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0xab31f8 - 0xab3998 ]
// mov eax, dword ptr [LDR地址 + 0x0C] 第一个模块结构的地址
+0x000 Flink : 0x00ab31f8
+0x004 Blink : 0x00ab3998
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0xab3200 - 0xab39a0 ]
+0x000 Flink : 0x00ab3200
+0x004 Blink : 0x00ab39a0
// 初始化顺序 ntdll.dll -> kernelbase.dll -> kernel32.dll
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0xab3100 - 0xab35e8 ]
+0x000 Flink : 0x00ab3100 <------------------
+0x004 Blink : 0x00ab35e8
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ''
+0x02c ShutdownThreadId : (null)
// 遍历初始化顺序的链表,找到第一项,因为遍历到的节点位于 _LDR_DATA_TABLE_ENTRY
// 偏移为 0x10 的位置,所以需要将地址 -0x10 获取到结构体的首地址
0:000> dt _LDR_DATA_TABLE_ENTRY -b (0x00ab3100-0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xab35d8 - 0xab31f8 ]
+0x000 Flink : 0x00ab35d8
+0x004 Blink : 0x00ab31f8
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xab35e0 - 0xab3200 ]
+0x000 Flink : 0x00ab35e0
+0x004 Blink : 0x00ab3200
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0xab39a8 - 0x7746dcbc ]
+0x000 Flink : 0x00ab39a8 <------------------
+0x004 Blink : 0x7746dcbc
+0x018 DllBase : 0x77350000
+0x01c EntryPoint : (null)
+0x020 SizeOfImage : 0x19a000
+0x024 FullDllName : _UNICODE_STRING
"C:\Windows\SYSTEM32\ntdll.dll"
+0x000 Length : 0x3a
+0x002 MaximumLength : 0x3c
+0x004 Buffer : 0x00ab2fd0
"C:\Windows\SYSTEM32\ntdll.dll"
+0x02c BaseDllName : _UNICODE_STRING "ntdll.dll"
+0x000 Length : 0x12
+0x002 MaximumLength : 0x14
+0x004 Buffer : 0x77359270 "ntdll.dll"
// 继续遍历初始化顺序的链表,找到第二项
0:000> dt _LDR_DATA_TABLE_ENTRY -b (0x00ab39a8-0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x7746dcac - 0xab35d8 ]
+0x000 Flink : 0x7746dcac
+0x004 Blink : 0x00ab35d8
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x7746dcb4 - 0xab35e0 ]
+0x000 Flink : 0x7746dcb4
+0x004 Blink : 0x00ab35e0
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0xab35e8 - 0xab3100]
+0x000 Flink : 0x00ab35e8 <------------------
+0x004 Blink : 0x00ab3100
+0x018 DllBase : 0x75e80000
+0x01c EntryPoint : 0x75f6cd80
+0x020 SizeOfImage : 0x1fc000
+0x024 FullDllName : _UNICODE_STRING
"C:\Windows\System32\KERNELBASE.dll"
+0x000 Length : 0x44
+0x002 MaximumLength : 0x46
+0x004 Buffer : 0x00ab3aa0
"C:\Windows\System32\KERNELBASE.dll"
+0x02c BaseDllName : _UNICODE_STRING "KERNELBASE.dll"
+0x000 Length : 0x1c
+0x002 MaximumLength : 0x1e
+0x004 Buffer : 0x00ab3ac8 "KERNELBASE.dll"
// 初始化顺序的第三个模块就是 kernel32.dll
0:000> dt _LDR_DATA_TABLE_ENTRY -b (0x00ab35e8-0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xab3998 - 0xab30f0 ]
+0x000 Flink : 0x00ab3998
+0x004 Blink : 0x00ab30f0
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xab39a0 - 0xab30f8 ]
+0x000 Flink : 0x00ab39a0
+0x004 Blink : 0x00ab30f8
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x7746dcbc -
0xab39a8 ]
+0x000 Flink : 0x7746dcbc // 当前所在的位置,可以作为基
址查找其他的内容
+0x004 Blink : 0x00ab39a8
+0x018 DllBase : 0x74dc0000
+0x01c EntryPoint : 0x74dd5f70
+0x020 SizeOfImage : 0xe0000
+0x024 FullDllName : _UNICODE_STRING
"C:\Windows\System32\KERNEL32.DLL"
+0x000 Length : 0x40
+0x002 MaximumLength : 0x42
+0x004 Buffer : 0x00ab36e0
"C:\Windows\System32\KERNEL32.DLL"
+0x02c BaseDllName : _UNICODE_STRING "KERNEL32.DLL"
+0x000 Length : 0x18
+0x002 MaximumLength : 0x1a
+0x004 Buffer : 0x00ab3708 "KERNEL32.DLL"
// 可以通过偏移获取到模块的基址\大小\路径
0:000> dd 0x00ab35e8+0x08 l1
00ab35f0 74dc0000
0:000> dd 0x00ab35e8+0x10 l1
00ab35f8 000e0000
0:000> dd 0x00ab35e8+0x18 l1
00ab3600 00ab36e0
0:000> du 00ab36e0
00ab36e0 "C:\Windows\System32\KERNEL32.DLL"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 按照加载顺序
mov eax, dword ptr fs : [0x30]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax + 0x18]
// 按照初始化顺序
mov eax, dword ptr fs : [0x30]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax + 0x1C]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax + 0x08]

实现自己的 GetProcAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DWORD MyGetProcAddress(DWORD Module, LPCSTR FunName)
{
// 获取 Dos 头和 Nt 头
auto DosHeader = (PIMAGE_DOS_HEADER)Module;
auto NtHeader = (PIMAGE_NT_HEADERS)(Module + DosHeader->e_lfanew);
// 获取导出表结构
DWORD ExportRva = NtHeader-
>OptionalHeader.DataDirectory[0].VirtualAddress;
auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(Module + ExportRva);
// 找到导出名称表、序号表、地址表
auto NameTable = (DWORD*)(ExportTable->AddressOfNames + Module);
auto FuncTable = (DWORD*)(ExportTable->AddressOfFunctions + Module);
auto OrdinalTable = (WORD*)(ExportTable->AddressOfNameOrdinals +
Module);
// 遍历找名字
for (int i = 0; i < ExportTable->NumberOfNames; ++i)
{
// 获取名字
char* Name = (char*)(NameTable[i] + Module);
if (!strcmp(Name, FunName))
return FuncTable[OrdinalTable[i]] + Module;
}
return -1;
}

壳代码

手动加壳的步骤

1
2
3
4
5
6
7
8
9
10
11
1. 修改文件头中的 NumberOfSection,添加区段数量
2. 添加新的区段表结构体,进行如下设置
2.1 设置区段的名称,注意最大长度为 8 字节
2.2 设置区段的大小,分为 Misc.VirtualSize 和 SizeofRawData
2.3 设置区段的 RVA = 上一个区段的RVA + 上一个区段对齐后的内存大小
2.4 设置区段的 FOA = 上一个区段的FOA + 上一个区段对齐后的文件大小
2.5 修改区段的属性为 0xE00000E0,表示读、写、执行
3. 在 PE 文件内填充新的区段,填充的大小应和 SizeofRawData 相同
4. 修改扩展头中 SizeOfImage 的大小,通常是最后一个区段的内存大小 + 最后一个区段的基
址RVA
5. 从壳代码跳转到原始 OEP 位置

实现功能的shellcode

1
2
3
4
5
6
7
8
9
10
11
12
EB 24 75 73 65 72 33 32 2E 64 6C 6C 00 4D 65 73 73 61 67 65 42 6F 78 41
00 4C 6F 61 64 4C 69 62 72 61 72 79 41 00 E8 00 00 00 00 5F 64 A1 30 00
00 00 8B 40 0C 8B 40 1C 8B 00 8B 00 8B 58 08 8D 57 EE 52 53 E8 11 FF FF
FF 83 C4 08 8D 57 D7 52 FF D0 8D 57 E2 52 50 E8 FE FE FF FF 83 C4 08 6A
00 6A 00 6A 00 6A 00 FF D0 E9 00 00 00 00 90 90 90 90 55 8B EC 83 EC 0C
53 8B 5D 08 56 57 33 FF 8B 43 3C 8B 44 18 78 03 C3 8B 48 24 8B 50 20 03
CB 89 4D F8 03 D3 8B 48 1C 8B 40 18 03 CB 89 4D F4 89 55 FC 89 45 08 85
C0 74 44 33 F6 0F 1F 44 00 00 8B 04 B2 8B 4D 0C 03 C3 8A 10 3A 11 75 1A
84 D2 74 12 8A 50 01 3A 51 01 75 0E 83 C0 02 83 C1 02 84 D2 75 E4 33 C0
EB 05 1B C0 83 C8 01 85 C0 74 16 8B 55 FC 47 0F B7 F7 3B 75 08 72 C3 5F
5E 83 C8 FF 5B 8B E5 5D C3 8B 45 F8 8B 4D F4 5F 0F B7 04 70 5E 8B 04 81
03 C3 5B 8B E5 5D C3

代码书写逻辑

添加区段的步骤

  1. 将文件头中的区段数量进行 +1
  2. 在区段头表中增加一项,直接覆盖后面的40字节数据
    1. 要设置区段表的名称,理论不能超过 7 字节
    2. 设置文件大小和内存大小,可以直接设置成一样
    3. 设置 RVA : RVA = 上一个区段的RVA + 上一个区段内存对齐后的内存大小
    4. 设置 FOA:FOA = 上一个区段的FOA + 上一个区段文件对齐后的文件大小
    5. 修改区段的属性,通常需要设置成 0xE00000E0,读写执行
  3. 填充 PE 文件,使其大小 = 最后一个区段的 FOA + 最后一个区段的文件大小
  4. 重新设置 SizeOfImage, 使其 = 最后一个区段的 RVA + 最后一个区段的内存大小

提供一个DLL作为Stub区域使用

  1. 将 DLL 设置为 Release 版本进行编译、小、内联了一些函数
  2. C\C++ ——> 代码生成 -> GS安全检查 -> 禁用 (取消一些库函数的调用)
  3. C\C++ ——> 所有选项 -> 运行库 -> MT (取消一些库函数的调用)
  4. C/C++ ——> 预编译头 -> 预编译头 -> 不使用预编译头
  5. 合并区段,并设置属性为可读可写可执行(0xE00000E0),可以一次拷贝完成
1
2
3
#pragma comment(linker, "/merge:.data=.text") 
#pragma comment(linker, "/merge:.rdata=.text")
#pragma comment(linker, "/section:.text,RWE")

VS设置

注:只是为了方便调试

  1. 设置 .exe(加壳器) 和 .dll(stub) 文件的输出路径为同一路径

    项目属性 -> 常规 -> 输出目录

  2. 设置工作目录为刚才的输出路径

    项目属性 -> 调试 -> 工作目录

写壳步骤

  1. 编写加壳器,加载被加壳程序和壳dll程序
  2. 将 dll 程序中 .text 拷贝到被加壳程序
  3. 将被加壳程序的 eip 指向stub 代码
    1. 需要让 stub 提供一个入口点

STUB区域重定位问题

起初代码是放在了 dll 中,需要重定位的内容,是以 dll 实际加载基址设置的

这里的重定位实际上是为了让 stub 区的代码跑起来,是 必然 要执行的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 为被加壳程序拷贝壳代码  (CopySection -> CopySectionData)
2. 修改程序,添加共享数据,让壳代码能够跳转回原始 OEP (ShareData) 无法执行
3. 需要对壳代码进行重定位,如果壳不支持随机基址,需要关闭随机基址(FixPackCode)
3.1 让壳代码支持重定位 excel
4. 加密代码段 (XorCodeSectiono) 无法执行
5. 需要动态的获取到 APi 的地址 (GetAPis + GetKernelBase + GetProcAddress)

IAT 加密: 为了禁止操作系统修复 IAT,我们需要先将 导入表 从数据目录表中摘除
壳代码在修复代码段后,接管 IAT 修复的操作 -> 第一步
在修复IAT的时候,为每一个函数申请一块空间,在空间中写入 shellcode 动态解密加密后的IAT地址 并完成原始 IAT 的跳转。
IAT[n] -> funcaddr
1. 加密(GetProcAddress(funcname) -> funcaddr) -> 得到加密后的iat
2. 申请一块空间(读写执行) -> 填入代码[xor 加密后 iat 和 0x15151515 + jmp 解密的地址]
3. 将申请的空间,填充到iat的位置