实例一:一个简单的入口跳转

使用一个简单的exe文件作为示例,使用010Editer打开

Step1:修改NT头中的NumberOfSections字段加一

image-20201107010330026

重新加载文件就能看到新增的section

image-20201107010350340

Step2:SectionHeader的设置:

img

 Name:字段名

 PhysicalAddress

 VirtualSize:与PA一致,为区段字节数(此处200h)

 VirtualAddress:上一seciton的VirtualAddress加上内存对齐的大小(此处1000h)

 SizeOfRawData:文件对其后的大小(此处200h)

 PointerToRawData:上一个区段PointerToRawData + SizeOfRawData

设置完成如下:

img

Step3:设置section属性

img

这里设置为了可执行可读可写

Step4:添加数据

image-20201107010431419

Step5:修改SizeofImage

SizeofImage =( 最后一个section)VirtualAddress + SizeOfRawData

Step6:去除随机基址参数,避免入口点变化

image-20201107010618140

40 80 -> 00 80

现在字段的修改就告一段落了

Step7:LordPE,修改程序入口点为新区段

image-20201107010642390

原本的EntryPoint是0x00001005,记下

image-20201107010650809

EntryPoint改为新区段的Voffset:0x00008000

Step8

X64dbg打开修改后的PE文件调试:

image-20201107010658076

可以看到EntryPoint已经在新section了,全是00

现在的目标是要让程序跳转回正确入口点,那么直接修改

image-20201107010705267

OK,打上补丁,重新载入

image-20201107010711837

image-20201107010722041

跳转正确入口点

image-20201107010748515

运行成功

总结以下,实现程序入口点跳转如下步骤:

  1. 构建/寻找一个位置存放入口跳转代码,这里采用的是新建一个自定义区段
  2. 重新设置程序入口点为添加代码处(这个地方理论可以插入任意多的代码,我们可以在原程序执行前做相当多的工作)
  3. 保证壳代码执行完后顺利跳转到原OEP处,为了保证寄存器值常常使用pushad和popad指令

实例二:简单加密壳

对代码节进行加密处理,添加壳代码使程序完成自解密->正常运行的流程

使用C++编写了一个简单的加密脚本,对代码节异或加密

注意:这里是根据OEP定位的代码节,若需要加密其他区段,应该采用的方法是

1
2
3
if (!strcmp((char*)pSection->Name,".text")){
// code
}
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
#include <Windows.h>
#include <iostream>
using namespace std;
char *pFileSrc = NULL;
int iTextVirtualAddr = 0;
int iTextVirtualLen = 0;


int main(){
char szFileName[] = { "G:\\ReverseTest\\section.exe" };
HANDLE hFile = CreateFileA(szFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL);
// 文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);
// 读入内存
pFileSrc = new char[dwFileSize];
ReadFile(hFile, pFileSrc, dwFileSize, NULL, NULL);
CloseHandle(hFile);

PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)pFileSrc;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
DWORD dwSectionCount = pNtHeader->FileHeader.NumberOfSections;
PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)(&pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader);
// 搜索代码节
for (int i = 0; i < dwSectionCount; i++){
// OEP地址确定代码位置
if (pNtHeader->OptionalHeader.AddressOfEntryPoint
>= pSection->VirtualAddress
&& pNtHeader->OptionalHeader.AddressOfEntryPoint
< (pSection->VirtualAddress + pSection->Misc.VirtualSize)){
cout << (int)(pFileSrc + pSection->PointerToRawData) << endl;
for (int iCount = 0; iCount < pSection->Misc.VirtualSize; iCount++){
// 异或加密操作
*(char*)(pFileSrc + pSection->PointerToRawData + iCount) ^= 0xF2;
}
// 设置section可写
pSection->Characteristics |= 0x80000000;

cout << (int)(pFileSrc + pSection->PointerToRawData + pSection->Misc.VirtualSize-1) << endl;
break;
}
pSection++;
}

// 创建新文件并写入
HANDLE hNewFile = CreateFileA("G:\\ReverseTest\\section-Encrypt.exe",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_ARCHIVE,
NULL);
LPDWORD iNum = NULL;
WriteFile(hNewFile, pFileSrc, dwFileSize, iNum, NULL);
CloseHandle(hNewFile);

system("pause");
return 0;
}


使用编辑器或调试器打开,代码节已经完全加密了

那么我们现在需要考虑如何解密

大概流程就是:

  1. 确定被加密的地址范围
  2. 确定单次解密操作的内存大小
  3. 设计一个循环代码块,对范围内的数据依次解密

这里继续上个实例的方式,将代码添加到新的section:

image-20201107014636551

x64dbg打上补丁,调试运行程序

image-20201107014236534

成功

Hello World!

参考资料:

简单加壳程序编写以及加壳思路的分享 - 『编程语言区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

[原创]C++写壳详解之基础篇-加壳脱壳-看雪论坛-安全社区|安全招聘|bbs.pediy.com

浅谈代码段加密原理(防止静态分析) - __阿阿阿怪 - 博客园 (cnblogs.com)