加壳器第二部分,加壳器

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加完壳后,点击运行会有窗口弹出,且关不掉,只有输入正确密码后才能运行程序
在这里插入图片描述
在这里插入图片描述
跳转至源程序进行运行
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值