1加壳器代码概述
为了软件的方便使用,这里我采用了mfc框架进行编写,其大致过程如下:
1.将加壳程序读入内存(因为要增加区段用于存放壳代码,并加密代码段)
2.加载壳代码
3.对一系列参数进行初始化(壳代码dll的加载基址,加壳程序的加载机制等等)
4.增加需要添加的区段,并将壳代码dll中的代码段属性拷贝至新增的区段
5.设置新的OEP地址
6.修复壳代码的重定位。
7.将壳代码dll中的代码段数据拷贝至新增的区段
7.将修改后的内容写入文件
1.1 将加壳程序读入内存
// 打开指定PE文件,申请空间,获取文件内容
BOOL CTool::OpenPE(LPCWSTR FileName, ULONG_PTR& FileBase)
{
// 打开文件
HANDLE hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
SetError(L"打开文件失败");
// 获取文件大小
DWORD FileSize = GetFileSize(hFile, NULL);
// 申请空间
FileBase = (ULONG_PTR)malloc(FileSize);
// 将文件中的内容读取到申请的空间中
DWORD RealSize = 0;
if (!ReadFile(hFile, (LPVOID)FileBase, FileSize, &RealSize, NULL))
SetError(L"读取文件失败");
PIMAGE_NT_HEADERS NtHeader = GetNtHeader(FileBase);
// 判断是否为PE文件
if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
SetError(L"PE文件格式错误");
if (PIMAGE_DOS_HEADER((DWORD)FileBase)->e_magic != IMAGE_DOS_SIGNATURE)
SetError(L"PE文件格式错误");
CloseHandle(hFile);
return TRUE;
}
1.2 加载壳代码,并对参数进行初始化(包含从壳代码中导出的结构体)
// 加载生成的壳代码dll
VOID CTool::LoadShellCode(HMODULE& DllHandle, LPCWSTR FileName, DWORD& StartRVA, PSHARE_DATA& sharedata, PPACK_DATA& packdata, PRELOC_DATA& relocdata)
{
// 加载模块
// 获取模块中start运行函数的位置
// 获取start的相对偏移
// 获取用于存放数据的sharedata位置
DllHandle = LoadLibraryEx(FileName, DllHandle, DONT_RESOLVE_DLL_REFERENCES);
if (DllHandle == NULL)
SetError(L"加载壳代码模块失败");
DWORD StartAddr = (DWORD)GetProcAddress(DllHandle, "Start");
if (StartAddr == NULL)
SetError(L"获取函数Start失败");
// 获取Dos头
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllHandle;
// 获取Nt头
PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)(DosHeader->e_lfanew + (DWORD)DllHandle);
// 获取start的相对偏移
PIMAGE_SECTION_HEADER TestSection = FindSection(NtHeader, ".text");
StartRVA = StartAddr - (DWORD)DllHandle - TestSection->VirtualAddress;
// 获取Dll中存放结构体的地址
sharedata = (PSHARE_DATA)GetProcAddress(DllHandle, "share_data");
if (sharedata == NULL)
SetError(L"获取结构体sharedata失败");
// 获取Dll中存放压缩文件数据的地址
packdata = (PPACK_DATA)GetProcAddress(DllHandle, "pack_data");
if (packdata == NULL)
SetError(L"获取结构体packdata失败");
relocdata = (PRELOC_DATA)GetProcAddress(DllHandle, "reloc_data");
if (relocdata == NULL)
SetError(L"获取结构体relocdata失败");
}
1.3 获取加壳程序的文件NT头部地址以及dll的NT头部地址用于后续操作
// 初始化函数,用于获取两个文件的Nt头
VOID CTool::InitNtHeader(PIMAGE_NT_HEADERS& FileNtHeader, PIMAGE_NT_HEADERS& DllNtHeader, DWORD FileBase, HMODULE DllHandle)
{
PIMAGE_DOS_HEADER FileDosHeader = (PIMAGE_DOS_HEADER)FileBase;
FileNtHeader = (PIMAGE_NT_HEADERS)(FileDosHeader->e_lfanew + FileBase);
PIMAGE_DOS_HEADER DllDosHeader = (PIMAGE_DOS_HEADER)DllHandle;
DllNtHeader = (PIMAGE_NT_HEADERS)(DllDosHeader->e_lfanew + (DWORD)DllHandle);
}
1.4 增加加壳程序的区段,用于将壳代码dll中的代码段属性拷贝进去(这里的拷贝仅仅是代码段的属性拷贝,并没有将数据拷贝)
注:这里设计到区块的对齐操作,因为每个区块首地址都是以对齐值的倍数开始的,具体如下:
因此我们需要自行定义一个对齐函数用于区块对齐:
// 用于计算对齐后的大小
DWORD CTool::GetAligMent(DWORD Size, DWORD AligMent)
{
return Size % AligMent == 0 ? Size : (Size / AligMent + 1) * AligMent;
}
// 获取区段数量,并在区段的最后添加自己的区段
BOOL CTool::CopySection(LPCSTR SectionName, ULONG_PTR& FileBase, DWORD& FileSize, PIMAGE_NT_HEADERS DllNtHeader)
{
// 获取Dos头
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)FileBase;
// 获取Nt头
PIMAGE_NT_HEADERS NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);
// 获取区段数量
DWORD SectionNumber = NtHeader->FileHeader.NumberOfSections;
// 获取区段
auto FirstSection = IMAGE_FIRST_SECTION(NtHeader);
// 获取最后一个区段
auto LastSection = &FirstSection[SectionNumber - 1];
// 区段数量加一
NtHeader->FileHeader.NumberOfSections += 1;
// 定义新区段
auto NewSection = LastSection + 1;
// 获取Dll中的.test段并进行拷贝
memcpy(NewSection, FindSection(DllNtHeader, ".text"), sizeof(IMAGE_SECTION_HEADER));
/*
设置新区段属性
*/
// 区段名字
strcpy_s((char*)NewSection->Name, 8, SectionName);
// 区段RVA地址=原本最后一个区段的首地址加上最后一个区段的内存对齐大小
NewSection->VirtualAddress = LastSection->VirtualAddress + GetAligMent(LastSection->Misc.VirtualSize, NtHeader->OptionalHeader.SectionAlignment);
// 区段属性设置为可读可写可执行
NewSection->Characteristics = 0xE00000E0;
// 设置区段的FOA=最后一个区段的FOA+最后一个区段的文件对齐大小
NewSection->PointerToRawData = LastSection->PointerToRawData + GetAligMent(LastSection->Misc.VirtualSize, NtHeader->OptionalHeader.FileAlignment);
// 重新设置文件大小
DWORD jiSize = NtHeader->OptionalHeader.SizeOfImage + NewSection->SizeOfRawData;
NtHeader->OptionalHeader.SizeOfImage = NewSection->VirtualAddress + NewSection->Misc.VirtualSize;
// 重新申请内存,将修改好的PE文件写进去
FileSize = NewSection->PointerToRawData + NewSection->SizeOfRawData;
FileBase = (ULONG_PTR)realloc((PVOID)FileBase, FileSize);
return TRUE;
}
1.5 为加壳程序设置新的OEP(程序入口地址),新的OEP地址为添加的区段的首地址。并保存老的OEP,用于壳代码中做跳转使用。
// 设置新的OEP
VOID CTool::SetOEP(PSHARE_DATA& sharedata, PIMAGE_NT_HEADERS FileNtHeader, DWORD FileBase, DWORD StartRVA)
{
// 保存源程序的OEP
// 将新添加区段的首地址
sharedata->OldOep = FileNtHeader->OptionalHeader.AddressOfEntryPoint;
FileNtHeader->OptionalHeader.AddressOfEntryPoint = FindSection(FileNtHeader, ".Wang")->VirtualAddress + StartRVA;
}
1.6 对IAT,TLS做清除操作,并备份,用于取消系统对其的相关权限。使得后续壳代码可以手动调用TLS以及修复IAT。
// 清零IAT,取消系统对IAT的操作权
VOID CTool::ZeroIAT(PIMAGE_NT_HEADERS& FileNtHeader, PSHARE_DATA& sharedata)
{
sharedata->IATRVA = FileNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress;
FileNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress = 0;
FileNtHeader->OptionalHeader.DataDirectory[12].VirtualAddress = 0;
}
// RVA转FOA
DWORD CTool::RvaToOffset(DWORD lpImage, DWORD dwRva)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)lpImage + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFileHeader = &pNT32->FileHeader;
PIMAGE_SECTION_HEADER pSeciton = IMAGE_FIRST_SECTION(pNT32);
for (int i = 0; i < pFileHeader->NumberOfSections; i++)
{
if (dwRva > pSeciton->VirtualAddress && dwRva < pSeciton->VirtualAddress + pSeciton->SizeOfRawData)
{
DWORD dwChazhi = dwRva - pSeciton->VirtualAddress;
return pSeciton->PointerToRawData + dwChazhi;
}
pSeciton++;
}
}
//备份TLS后清空TLS
VOID CTool::SetTls(PIMAGE_NT_HEADERS& FileNtHeader, ULONG_PTR& FileBase, PSHARE_DATA& sharedata)
{
sharedata->TlsVirtualAddress = FileNtHeader->OptionalHeader.DataDirectory[9].VirtualAddress;
if (sharedata->TlsVirtualAddress)
{
FileNtHeader->OptionalHeader.DataDirectory[9].VirtualAddress = 0;
DWORD TlsFoa = RvaToOffset(FileBase, sharedata->TlsVirtualAddress);
auto TlsTable = (PIMAGE_TLS_DIRECTORY)(TlsFoa + FileBase);
sharedata->TlsCallBackTableVa = TlsTable->AddressOfCallBacks;
}
}
1.7 修复壳代码中的重定位
思路如下:
由于需要修改的重定位指针的VA地址由三部分组成:PE文件的加载基址,重定位数据的开始RVA(VirtualAddress),重定位数组项的低12位(Typeoffset的第十二位)
因此,我们可以先计算出VA地址(即上面三项相加),由于重定位本质就是加载的基址发生变化,但是他的相对偏移并不会发生变化,因此我们只需算出相对偏移,再用相对偏移加上修改后的加载基址即可,同时由于只需要TypeOffset的低十二位,因此我们需要构造结构体,代码如下,:
// 修复重定位表
VOID CTool::FixReloc(PIMAGE_NT_HEADERS& FileNtHeader, PIMAGE_NT_HEADERS DllNtHeader, HMODULE DllHandle, DWORD FileBase)
{
// 遍历可代码dll的重定位表,并进行手动修复
// 修复后的地址=相对偏移+加壳程序的加载基址+区段.Wang的RVA
// 相对偏移=重定位表中保存的地址-Dll的加载基址-dll中.test的RVA
PIMAGE_BASE_RELOCATION DllReloc = (PIMAGE_BASE_RELOCATION)(DllNtHeader->OptionalHeader.DataDirectory[5].VirtualAddress + (DWORD)DllHandle);
while (DllReloc->SizeOfBlock != 0)
{
DWORD Count = (DllReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
// 定义结构体
typedef struct TypeOffset
{
WORD Offset : 12;
WORD Type : 4;
}TypeOffset, * PTypeOffset;
// 获取第一个重定位项
PTypeOffset RelocItem = (PTypeOffset)((DWORD)DllReloc + sizeof(IMAGE_BASE_RELOCATION));
DWORD OldProtect = 0;
for (int i = 0; i < Count; i++)
{
// 如果重定位类型为3:重定位指向的整个地址都需要修正。就进行修复。
if ((RelocItem + i)->Type == 3)
{
DWORD* address = (DWORD*)((DWORD)DllHandle + (RelocItem + i)->Offset + DllReloc->VirtualAddress);
// 先算相对偏移
DWORD RelativeOffset = *address - (DWORD)DllHandle - FindSection(DllNtHeader, ".text")->VirtualAddress;
VirtualProtect(address, 4, PAGE_EXECUTE_READWRITE, &OldProtect);
// 再加起来,算修正后的重定位地址
*address = RelativeOffset + FileNtHeader->OptionalHeader.ImageBase + FindSection(FileNtHeader, ".Wang")->VirtualAddress;
// 恢复内存属性
VirtualProtect(address, 4, OldProtect, &OldProtect);
}
}
// 下一个重定位块
DllReloc = (PIMAGE_BASE_RELOCATION)((DWORD)DllReloc + DllReloc->SizeOfBlock);
}
// 关闭:(& ~标志位) 开启:(|标志位)
// FileNtHeader->OptionalHeader.DllCharacteristics &= ~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
}
1.8 加密加壳程序的代码段
这里我是随机生成了一个密钥并传入壳代码中,利用这个密钥每隔两个字符进行加密。
// 加密代码段
VOID CTool::EnCodeText(PIMAGE_NT_HEADERS& FileNtHeader, DWORD FileBase, PSHARE_DATA& sharedata)
{
// 获取加壳程序的代码区段
// RVA转VA
// 进行字节加密
PIMAGE_SECTION_HEADER FileSection = FindSection(FileNtHeader, ".text");
auto CodeBuffer = (BYTE*)(FileBase + FileSection->PointerToRawData);
sharedata->XorStart = FileSection->VirtualAddress;
sharedata->XorAddr = FileSection->SizeOfRawData;
sharedata->XorKey = (rand() % 0x100);
for (int i = 0; i < sharedata->XorAddr; i += 2)
{
CodeBuffer[i] ^= (sharedata->XorKey);
}
}
1.9 壳代码dll中的代码段的数据拷贝至新增的区段中
// 拷贝区段内容到加壳程序中新增的区段
VOID CTool::CopySectionData(PIMAGE_NT_HEADERS& FileNtHeader, PIMAGE_NT_HEADERS DllNtHeader, DWORD FileBase, HMODULE DllHandle)
{
// 获取加壳程序中新增添的区段
// 获取壳代码中的.test段
// 修改内存属性
// 进行拷贝内容
// 还原内存属性
auto FileSection = FindSection(FileNtHeader, ".Wang");
PVOID FileBuffer = (PVOID)(FileSection->PointerToRawData + FileBase);
auto DllSection = FindSection(DllNtHeader, ".text");
PVOID DllBuffer = (PVOID)(DllSection->VirtualAddress + (DWORD)DllHandle);
DWORD OldProtect = 0;
// VirtualProtect((LPVOID)(FileSection->VirtualAddress + FileBase), 0x1000,PAGE_EXECUTE_READWRITE, &OldProtect);
memcpy(FileBuffer, DllBuffer, FileSection->Misc.VirtualSize);
// VirtualProtect((LPVOID)(FileSection->VirtualAddress + FileBase), 0x1000, OldProtect, &OldProtect);
}
2.0将修改完成的数据写入文件并保存即可
// 将内存写入文件
BOOL CTool::ChangeFile(LPCWSTR FileName, ULONG_PTR& FileBase, DWORD FileSize)
{
HANDLE hFile = CreateFile(FileName, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
SetError(L"写入文件时文件打开失败");
DWORD RealSize = 0;
if (!WriteFile(hFile, (LPVOID)FileBase, FileSize, &RealSize, NULL))
SetError(L"写入文件时写入文件失败");
CloseHandle(hFile);
MessageBox(NULL, NULL, L"加壳成功", NULL);
ExitProcess(0);
return TRUE;
}
2.1 功能汇集
将上述函数依次调用,并最终封装成一个函数,如下(由于我在加壳的时候,往加壳程序中添加了反虚拟的资源,因此这里有一个添加资源的函数):
// 进行加壳
void CJIAKEQIDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
// 添加资源
CTool::AddResource((LPCWSTR)PathString);
// 打开文件并读取文件到内存
CTool::OpenPE((LPCWSTR)PathString, FileBase);
// 加载壳代码模块
CTool::LoadShellCode(DllHandle, L"ShellCode.dll", StartRVA, ShareData, PackData, RelocData);
// 初始化,获取Nt头
CTool::InitNtHeader(FileNtHeader, DllNtHeader, FileBase, DllHandle);
// 增加区段
CTool::CopySection(".Wang", FileBase, FileSize, DllNtHeader);
// 初始化,获取Nt头
CTool::InitNtHeader(FileNtHeader, DllNtHeader, FileBase, DllHandle);
// 设置OEP
CTool::SetOEP(ShareData, FileNtHeader, FileBase, StartRVA);
// 清零IAT
CTool::ZeroIAT(FileNtHeader, ShareData);
// 清零TLS
CTool::SetTls(FileNtHeader, FileBase, ShareData);
// 修复重定位表
CTool::FixReloc(FileNtHeader, DllNtHeader, DllHandle, FileBase);
// 添加区段,将重定位指向新添加区段
CTool::SetReloc(FileNtHeader, DllNtHeader, *RelocData, FileBase, FileSize, DllHandle);
// 加密代码段
CTool::EnCodeText(FileNtHeader, FileBase, ShareData);
// 写入数据
CTool::CopySectionData(FileNtHeader, DllNtHeader, FileBase, DllHandle);
// 写入文件
CTool::ChangeFile((LPCWSTR)PathString, FileBase, FileSize);
}
2效果展示
对exe加完壳后,点击运行会有窗口弹出,且关不掉,只有输入正确密码后才能运行程序
跳转至源程序进行运行