描述
- 自己实现一个简单的加壳程序,能够对选取程序的代码段进行加密,并添加外壳部分,在运行时还原
- 本文分为三个部分:外壳程序,加壳程序,和用户交互程序
外壳程序
- 写到Stub.dll中,方便同加壳程序共享数据,获取原程序的PE文件信息
- 将数据段合并到代码段,方便加壳程序读取并添加到原程序中
- 由于只迁移代码段,不包含IAT表,所以外壳程序中无法直接调用api函数,需要自己实现一套api函数地址的获取机制
- 具体来说,通过peb获取kernel32.dll的加载基址,对比其导出表中的字符串,查找到getprocaddress函数的地址,通过该函数获取loadlibrary函数地址,从而实现对其它dll的加载和函数调用
- 实现一个简单的针对代码段逐字节的异或解密
- 代码如下
#include "stdafx.h"
#include "Stub.h"
#pragma comment(linker, "/merge:.data=.text")
#pragma comment(linker, "/merge:.rdata=.text")
#pragma comment(linker, "/section:.text,RWE")
extern "C"__declspec(dllexport) GLOBAL_PARAM g_stcParam = { (DWORD)(Start) };
typedef void(*FUN)();
FUN g_oep;
ULONGLONG GetKernel32Addr()
{
ULONGLONG dwKernel32Addr = 0;
// 获取TEB的地址
_TEB* pTeb = NtCurrentTeb();
// 获取PEB的地址
PULONGLONG pPeb = (PULONGLONG)*(PULONGLONG)((ULONGLONG)pTeb + 0x60);
// 获取PEB_LDR_DATA结构的地址
PULONGLONG pLdr = (PULONGLONG)*(PULONGLONG)((ULONGLONG)pPeb + 0x18);
//模块初始化链表的头指针InInitializationOrderModuleList
PULONGLONG pInLoadOrderModuleList = (PULONGLONG)((ULONGLONG)pLdr + 0x10);
// 获取链表中第一个模块信息,exe模块
PULONGLONG pModuleExe = (PULONGLONG)*pInLoadOrderModuleList;
// 获取链表中第二个模块信息,ntdll模块
PULONGLONG pModuleNtdll = (PULONGLONG)*pModuleExe;
// 获取链表中第三个模块信息,Kernel32模块
PULONGLONG pModuleKernel32 = (PULONGLONG)*pModuleNtdll;
// 获取kernel32基址
dwKernel32Addr = pModuleKernel32[6];
return dwKernel32Addr;
}
ULONGLONG MyGetProcAddress()
{
ULONGLONG dwBase = GetKernel32Addr();
// 1. 获取DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwBase;
// 2. 获取NT头
PIMAGE_NT_HEADERS64 pNt = (PIMAGE_NT_HEADERS64)(dwBase + pDos->e_lfanew);
// 3. 获取数据目录表
PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory;
pExportDir = &(pExportDir[IMAGE_DIRECTORY_ENTRY_EXPORT]);
DWORD dwOffset = pExportDir->VirtualAddress;
// 4. 获取导出表信息结构
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwBase + dwOffset);
DWORD dwFunCount = pExport->NumberOfFunctions;
DWORD dwFunNameCount = pExport->NumberOfNames;
DWORD dwModOffset = pExport->Name;
// Get Export Address Table
PDWORD pEAT = (PDWORD)(dwBase + pExport->AddressOfFunctions);
// Get Export Name Table
PDWORD pENT = (PDWORD)(dwBase + pExport->AddressOfNames);
// Get Export Index Table
PWORD pEIT = (PWORD)(dwBase + pExport->AddressOfNameOrdinals);
for (DWORD dwOrdinal = 0; dwOrdinal < dwFunCount; dwOrdinal++)
{
if (!pEAT[dwOrdinal]) // Export Address offset
continue;
// 1. 获取序号
DWORD dwID = pExport->Base + dwOrdinal;
// 2. 获取导出函数地址
ULONGLONG dwFunAddrOffset = pEAT[dwOrdinal];
for (DWORD dwIndex = 0; dwIndex < dwFunNameCount; dwIndex++)
{
// 在序号表中查找函数的序号
if (pEIT[dwIndex] == dwOrdinal)
{
// 根据序号索引到函数名称表中的名字
ULONGLONG dwNameOffset = pENT[dwIndex];
char* pFunName = (char*)((ULONGLONG)dwBase + dwNameOffset);
if (!strcmp(pFunName, "GetProcAddress"))
{// 根据函数名称返回函数地址
return dwBase + dwFunAddrOffset;
}
}
}
}
return 0;
}
void XorCode()
{
// 获取代码基址
PBYTE pBase = (PBYTE)((ULONGLONG)g_stcParam.dwImageBase + g_stcParam.lpStartVA);
// 异或操作
for (DWORD i = 0; i < g_stcParam.dwCodeSize; i++)
{
pBase[i] ^= g_stcParam.byXor;
}
}
void Start()
{
// 获取kernel32基址
fnGetProcAddress pfnGetProcAddress = (fnGetProcAddress)MyGetProcAddress();
ULONGLONG dwBase = GetKernel32Addr();
// 获取API地址
fnLoadLibraryA pfnLoadLibraryA = (fnLoadLibraryA)pfnGetProcAddress((HMODULE)dwBase, "LoadLibraryA");
fnGetModuleHandleA pfnGetModuleHandleA = (fnGetModuleHandleA)pfnGetProcAddress((HMODULE)dwBase, "GetModuleHandleA");
fnVirtualProtect pfnVirtualProtect = (fnVirtualProtect)pfnGetProcAddress((HMODULE)dwBase, "VirtualProtect");
HMODULE hUser32 = (HMODULE)pfnGetModuleHandleA("user32.dll");
fnMessageBox pfnMessageBoxA = (fnMessageBox)pfnGetProcAddress(hUser32, "MessageBoxA");
HMODULE hKernel32 = (HMODULE)pfnGetModuleHandleA("kernel32.dll");
fnExitProcess pfnExitProcess = (fnExitProcess)pfnGetProcAddress(hKernel32, "ExitProcess");
// 弹出信息框
int nRet = pfnMessageBoxA(NULL, "欢迎使用免费64位加壳程序,是否运行主程序?", "Hello PEDIY", MB_YESNO);
if (nRet == IDYES)
{
// 修改代码段属性
ULONGLONG dwCodeBase = g_stcParam.dwImageBase + (DWORD)g_stcParam.lpStartVA;
DWORD dwOldProtect = 0;
pfnVirtualProtect((LPBYTE)dwCodeBase, g_stcParam.dwCodeSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
XorCode(); // 解密代码
pfnVirtualProtect((LPBYTE)dwCodeBase, g_stcParam.dwCodeSize, dwOldProtect, &dwOldProtect);
g_oep = (FUN)(g_stcParam.dwImageBase + g_stcParam.dwOEP);
g_oep(); // 跳回原始OEP
}
// 退出程序
pfnExitProcess(0);
}
加壳程序
- 分为两部分,外层调用和里层实现,里层实现封装到c++类中
- 加壳代码也编译为Pack.dll,供用户交互程序调用
外层调用
- 读取目标程序,记录PE文件信息,实现针对代码段的逐字节异或加密
- 加载外壳程序Stub.dll,将PE文件信息传递给壳程序
- 修复壳程序中的重定位表,将OEP设置为壳程序的入口
- 将壳程序的区段添加到原PE文件中,修正PE的区块表,生成新程序
#include "stdafx.h"
#include "Pack.h"
// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 Pack.h
CPack::CPack()
{
return;
}
#include <Psapi.h>
#pragma comment(lib,"psapi.lib")
#include "../Stub/Stub.h"
#ifdef DEBUG
#pragma comment(lib,"../x64/Debug/Stub.lib")
#endif // DEBUG
#ifdef NDEBUG
#pragma comment(lib,"../x64/release/Stub.lib")
#endif // NDEBUG
#include "PE.h"
BOOL Pack(CString strPath, BYTE byXor)
{
// 1.获取目标文件PE信息
CPE objPE;
objPE.InitPE(strPath);
BOOL bRet = FALSE;
DWORD dwVirtualAddr = objPE.XorCode(byXor);
//2. 获取Stub文件PE信息,将必要的信息设置到Stub中
HMODULE hMod = LoadLibrary(L"Stub.dll");
PGLOBAL_PARAM pstcParam = (PGLOBAL_PARAM)GetProcAddress(hMod, "g_stcParam");
pstcParam->dwImageBase = objPE.m_dwImageBase;
pstcParam->dwCodeSize = objPE.m_dwCodeSize;
pstcParam->dwOEP = objPE.m_dwOEP;
pstcParam->byXor = byXor;
pstcParam->lpStartVA = (PBYTE)dwVirtualAddr;
// 3. 添加Stub代码段到被加壳程序中
// 3.1 读取Stub代码段
MODULEINFO modinfo = { 0 };
GetModuleInformation(GetCurrentProcess(), hMod, &modinfo, sizeof(MODULEINFO));
PBYTE lpMod = new BYTE[modinfo.SizeOfImage];
memcpy_s(lpMod, modinfo.SizeOfImage, hMod, modinfo.SizeOfImage);
PBYTE pCodeSection = NULL;
DWORD dwCodeBaseRVA = 0;
DWORD dwSize = objPE.GetSectionData(lpMod, 0, pCodeSection, dwCodeBaseRVA);
// 3.2 修复Stub段中的代码
objPE.FixReloc(lpMod, pCodeSection, objPE.m_dwNewSectionRVA);
// 3.3 修改被加壳程序的OEP,指向stub
DWORD dwStubOEPRVA = pstcParam->dwStart - (DWORD)hMod;
DWORD dwNewOEP = dwStubOEPRVA - dwCodeBaseRVA;
//StubOEP = dwStubOEPRVA - 原RVA + 新区段的RVA;
objPE.SetNewOEP(dwNewOEP);
objPE.ClearRandBase();
objPE.ClearBundleImport();//兼容有绑定输入表项的程序
// 3.4 读取重定位的信息,修复代码
if (objPE.AddSection(pCodeSection, dwSize, "15pack"))
{
bRet = TRUE;
}
// 释放资源
delete lpMod;
lpMod = NULL;
return bRet;
}
里层实现
#include "stdafx.h"
#include "PE.h"
CPE::CPE() :m_dwFileSize(0), m_pFileBase(NULL),
m_dwFileAlign(0), m_dwMemAlign(0)
{
}
CPE::~CPE()
{
}
DWORD CPE::RVA2OffSet(DWORD dwRVA, PIMAGE_NT_HEADERS64 pNt)
{
DWORD dwOffset = 0;
// 1. 获取第一个区段结构体
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
// 2. 获取区段数量
DWORD dwSectionCount = pNt->FileHeader.NumberOfSections;
// 3. 遍历区段信息表
for (DWORD i = 0; i < dwSectionCount; i++)
{
// 4. 匹配RVA所在的区段
if (dwRVA >= pSection[i].VirtualAddress &&
dwRVA < (pSection[i].VirtualAddress + pSection[i].Misc.VirtualSize)
)
{ // 计算对应的文件偏移
dwOffset = dwRVA - pSection[i].VirtualAddress +
pSection[i].PointerToRawData;
return dwOffset;
}
}
return dwOffset;
}
BOOL CPE::InitPE(CString strPath)
{
if (m_objFile.m_hFile == INVALID_HANDLE_VALUE && m_pFileBase) {
m_objFile.Close(); delete m_pFileBase; m_pFileBase = NULL;
}
m_objFile.Open(strPath, CFile::modeRead);
m_dwFileSize = (DWORD)m_objFile.GetLength();
m_pFileBase = new BYTE[m_dwFileSize];
if (m_objFile.Read(m_pFileBase, (DWORD)m_dwFileSize))
{
// 1. 获取DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_pFileBase;
// 2. 获取NT头
m_pNT = (PIMAGE_NT_HEADERS64)((ULONGLONG)m_pFileBase + pDos->e_lfanew);
// 3. 获取文件对齐、内存对齐等信息
m_dwFileAlign = m_pNT->OptionalHeader.FileAlignment;
m_dwMemAlign = m_pNT->OptionalHeader.SectionAlignment;
m_dwImageBase = m_pNT->OptionalHeader.ImageBase;
m_dwOEP = m_pNT->OptionalHeader.AddressOfEntryPoint;
m_dwCodeBase = m_pNT->OptionalHeader.BaseOfCode;
m_dwCodeSize = m_pNT->OptionalHeader.SizeOfCode;
// 3. 获取最后一个区段后的地址
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(m_pNT);
m_pLastSection = &pSection[m_pNT->FileHeader.NumberOfSections];
// 4. 获取新加区段的起始RVA, 内存4K对齐
DWORD dwVirtualSize = m_pLastSection[-1].Misc.VirtualSize;
if (dwVirtualSize%m_dwMemAlign)
dwVirtualSize = (dwVirtualSize / m_dwMemAlign + 1) * m_dwMemAlign;
else
dwVirtualSize = (dwVirtualSize / m_dwMemAlign)* m_dwMemAlign;
m_dwNewSectionRVA = m_pLastSection[-1].VirtualAddress + dwVirtualSize;
return TRUE;
}
return FALSE;
}
ULONGLONG CPE::AddSection(LPBYTE pBuffer, DWORD dwSize, PCHAR pszSectionName)
{
// 1 修改文件头中的区段数量
m_pNT->FileHeader.NumberOfSections++;
// 2 增加区段表项
memset(m_pLastSection, 0, sizeof(IMAGE_SECTION_HEADER));
strcpy_s((char*)m_pLastSection->Name, IMAGE_SIZEOF_SHORT_NAME, pszSectionName);
DWORD dwVirtualSize = 0; // 区段虚拟大小
DWORD dwSizeOfRawData = 0; // 区段文件大小
DWORD dwSizeOfImage = m_pNT->OptionalHeader.SizeOfImage;
{
if (dwSizeOfImage%m_dwMemAlign)
dwSizeOfImage = (dwSizeOfImage / m_dwMemAlign + 1) * m_dwMemAlign;
else
dwSizeOfImage = (dwSizeOfImage / m_dwMemAlign) * m_dwMemAlign;
if (dwSize%m_dwMemAlign)
dwVirtualSize = (dwSize / m_dwMemAlign + 1) * m_dwMemAlign;
else
dwVirtualSize = (dwSize / m_dwMemAlign) * m_dwMemAlign;
if (dwSize%m_dwFileAlign)
dwSizeOfRawData = (dwSize / m_dwFileAlign + 1) * m_dwFileAlign;
else
dwSizeOfRawData = (dwSize / m_dwFileAlign) * m_dwFileAlign;
}
m_pLastSection->VirtualAddress = dwSizeOfImage;
m_pLastSection->PointerToRawData = m_dwFileSize;
m_pLastSection->SizeOfRawData = dwSizeOfRawData;
m_pLastSection->Misc.VirtualSize = dwVirtualSize;
m_pLastSection->Characteristics = 0XE0000040;
// 3 增加文件大小,创建文件,添加代码
m_pNT->OptionalHeader.SizeOfImage = dwSizeOfImage + dwVirtualSize;
m_pNT->OptionalHeader.AddressOfEntryPoint = m_dwNewOEP + m_pLastSection->VirtualAddress;
// 3.1 生成输出文件路径
CString strPath = m_objFile.GetFilePath();
TCHAR szOutPath[MAX_PATH] = { 0 };
LPWSTR strSuffix = PathFindExtension(strPath); // 获取文件的后缀名
wcsncpy_s(szOutPath, MAX_PATH, strPath, wcslen(strPath)); // 备份目标文件路径到szOutPath
PathRemoveExtension(szOutPath); // 将szOutPath中保存路径的后缀名去掉
wcscat_s(szOutPath, MAX_PATH, L"_15Pack"); // 在路径最后附加“_15Pack”
wcscat_s(szOutPath, MAX_PATH, strSuffix); // 在路径最后附加刚刚保存的后缀名
// 3.2 创建文件
CFile objFile(szOutPath, CFile::modeCreate | CFile::modeReadWrite);
objFile.Write(m_pFileBase, m_dwFileSize);
objFile.SeekToEnd();
objFile.Write(pBuffer, dwSize);
return m_pLastSection->VirtualAddress;
}
// 在内存中重定位Stub
void CPE::FixReloc(PBYTE lpImage, PBYTE lpCode, DWORD dwCodeRVA)
{
// 1. 获取DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
// 2. 获取NT头
PIMAGE_NT_HEADERS64 pNt = (PIMAGE_NT_HEADERS64)((ULONGLONG)lpImage + pDos->e_lfanew);
// 3. 获取数据目录表
PIMAGE_DATA_DIRECTORY pRelocDir = pNt->OptionalHeader.DataDirectory;
pRelocDir = &(pRelocDir[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
// 4. 获取重定位目录
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)lpImage + pRelocDir->VirtualAddress);
typedef struct {
WORD Offset : 12; // (1) 大小为12Bit的重定位偏移
WORD Type : 4; // (2) 大小为4Bit的重定位信息类型值
}TypeOffset, *PTypeOffset; // 这个结构体是A1Pass总结的
// 循环获取每一个MAGE_BASE_RELOCATION结构的重定位信息
while (pReloc->VirtualAddress)
{
PTypeOffset pTypeOffset = (PTypeOffset)(pReloc + 1);
ULONGLONG dwSize = sizeof(IMAGE_BASE_RELOCATION);
ULONGLONG dwCount = (pReloc->SizeOfBlock - dwSize) / 2;
for (ULONGLONG i = 0; i < dwCount; i++)
{
if (*(PULONGLONG)(&pTypeOffset[i]) == NULL)
break;
ULONGLONG dwRVA = pReloc->VirtualAddress + pTypeOffset[i].Offset;
PULONGLONG pRelocAddr = (PULONGLONG)((ULONGLONG)lpImage + dwRVA);
// 修复重定位信息 公式:需要修复的地址-原映像基址-原区段基址+现区段基址+现映像基址
ULONGLONG dwRelocCode = *pRelocAddr - pNt->OptionalHeader.ImageBase - pNt->OptionalHeader.BaseOfCode +
dwCodeRVA + m_dwImageBase;
*pRelocAddr = dwRelocCode;
}
pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)pReloc + pReloc->SizeOfBlock);
}
}
void CPE::SetNewOEP(DWORD dwOEP)
{
m_dwNewOEP = dwOEP;
}
void CPE::ClearBundleImport()
{
// 1. 获取DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_pFileBase;
// 2. 获取NT头
PIMAGE_NT_HEADERS64 pNt = (PIMAGE_NT_HEADERS64)((ULONGLONG)m_pFileBase + pDos->e_lfanew);
// 3. 获取数据目录表
PIMAGE_DATA_DIRECTORY pDir = pNt->OptionalHeader.DataDirectory;
// 4. 清空绑定输入表项
ZeroMemory(&(pDir[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]), sizeof(IMAGE_DATA_DIRECTORY));
}
void CPE::ClearRandBase()
{
m_pNT->OptionalHeader.DllCharacteristics = 0;
}
DWORD CPE::GetSectionData(PBYTE lpImage, DWORD dwSectionIndex, PBYTE& lpBuffer, DWORD& dwCodeBaseRVA)
{
// 1. 获取DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
// 2. 获取NT头
PIMAGE_NT_HEADERS64 pNt = (PIMAGE_NT_HEADERS64)((ULONGLONG)lpImage + pDos->e_lfanew);
// 3. 获取第一个区段结构体
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
DWORD dwSize = pSection[0].SizeOfRawData;
ULONGLONG dwCodeAddr = (ULONGLONG)lpImage + pSection[0].VirtualAddress;
lpBuffer = (PBYTE)dwCodeAddr;
dwCodeBaseRVA = pSection[0].VirtualAddress;
return dwSize;
}
DWORD CPE::XorCode(BYTE byXOR)
{
DWORD dwVirtualAddr = m_dwCodeBase;
DWORD dwOffset = RVA2OffSet(m_dwCodeBase, m_pNT);
if (!dwOffset)
{// 兼容 debug版本,第一个区段没有大小
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(m_pNT);
dwOffset = RVA2OffSet(pSection[1].VirtualAddress, m_pNT);
dwVirtualAddr = pSection[1].VirtualAddress;
}
PBYTE pBase = (PBYTE)((ULONGLONG)m_pFileBase + dwOffset);
for (DWORD i = 0; i < m_dwCodeSize; i++)
{
pBase[i] ^= byXOR;
}
return dwVirtualAddr;
}
用户交互程序
- 从命令行中读取目标程序路径和用于异或加密的十六进制字节
- 用loadlibrary加载Pack.dll,调用pack函数,实现对目标程序的加密
#include <windows.h>
#include <iostream>
#include <atlstr.h>
typedef bool (*Pack)(CString, BYTE);
int main(int argc, char* argv[]) {
// Get the command line arguments
LPWSTR* szArgList;
int argCount;
szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount);
// Check if there are enough arguments
if (argCount < 3) {
MessageBox(NULL, TEXT("Not enough arguments provided"), TEXT("Error"), MB_OK);
return 0;
}
// Get the file path and number from the command line arguments
CString filePath(szArgList[1]);
BYTE hexNumber = (BYTE)_wtoi(szArgList[2]);
// Print the file path and number
std::cout << "加密文件路径: " << filePath.GetString() << std::endl;
std::cout << "加密密钥: " << std::hex << (int)hexNumber << std::endl;
std::cout << "开始加密" << std::endl;
// Load the Pack.dll library
HINSTANCE hinstLib = LoadLibrary(TEXT("Pack.dll"));
if (hinstLib == NULL) {
MessageBox(NULL, TEXT("Unable to load library"), TEXT("Error"), MB_OK);
return 0;
}
// Get the Pach function from the library
Pack pack = (Pack)GetProcAddress(hinstLib, "Pack");
if (pack == NULL) {
MessageBox(NULL, TEXT("Unable to find pack function"), TEXT("Error"), MB_OK);
return 0;
}
// Call the Pach function with the hex number
bool result = pack(filePath, hexNumber);
if (result) {
std::cout << "加密成功!" << std::endl;
}
else {
std::cout << "加密失败!" << std::endl;
}
FreeLibrary(hinstLib);
// Free the memory allocated for the command line arguments
LocalFree(szArgList);
return 0;
}
效果
- 加密前,运行原程序Msg.exe
- 将壳程序Stub.dll,加壳程序Pack.dll和用户交互程序Shell.exe放到Msg.exe同目录下,命令行中运行Shell.exe,生成加壳后的程序Msg_15Pack.exe
- 运行加壳后生成的程序Msg_15Pack.exe,功能正常