C/C++ 对代码节的动态加解密

加壳的原理就是加密或者压缩程序中的已有资源,然后当程序执行后外壳将模拟PE加载器对EXE中的区块进行动态装入,下面我们来自己实现一个简单的区块加解密程序,来让大家学习了解一下壳的基本运作原理。

本次使用的工具,依旧是上次编写的PETools: https://www.cnblogs.com/LyShark/p/12960816.html

加密第一个节表:

#include <stdio.h>
#include <Windows.h>
#include <ImageHlp.h>
#pragma comment(lib,"Imagehlp.lib")

void EncrySection(LPSTR szFileName)
{
	HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
	HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

	PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
	PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
	PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);

	printf("节表数量: %d \n", FileHdr->NumberOfSections);
	printf("节虚拟地址: %x \n", pSection->Misc.VirtualSize);
	printf("读入FOA基地址: %x \n", pSection->PointerToRawData);
	printf("读入节表长度: %x \n", pSection->SizeOfRawData);

	DWORD dwRead = 0;
	PBYTE pByte = (PBYTE)malloc(pSection->SizeOfRawData);

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	memset(pByte, 0, pSection->SizeOfRawData);
	ReadFile(hFile, pByte, pSection->SizeOfRawData, &dwRead, NULL);

	for (int x = 0; x < pSection->SizeOfRawData; x++)
		pByte[x] ^= 0x10;

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN);

	UnmapViewOfFile(lpBase);
}

int main(int argc, char * argv[])
{
	EncrySection("c://win32.exe");
	system("pause");
	return 0;
}

当需要打印第二个节只需要递增指针.

void EncrySection(LPSTR szFileName)
{
	HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
	HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

	PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
	PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
	PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);

	printf("节表数量: %d \n", FileHdr->NumberOfSections);
	printf("节虚拟地址: %x \n", pSection->Misc.VirtualSize);
	printf("读入FOA基地址: %x \n", pSection->PointerToRawData);
	printf("读入节表长度: %x \n", pSection->SizeOfRawData);

	DWORD dwRead = 0;
	PBYTE pByte = (PBYTE)malloc(pSection->SizeOfRawData);

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	memset(pByte, 0, pSection->SizeOfRawData);
	
	// 逐字节读入
	for (int x = 0; x < pSection->PointerToRawData; x++)
	{
		ReadFile(hFile, &pByte[x], 1, &dwRead, NULL);
	}

	for (int x = 0; x < pSection->SizeOfRawData; x++)
		pByte[x] ^= 0x10;

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN);

	pSection++;
	printf("节虚拟地址: %x \n", pSection->Misc.VirtualSize);
	printf("读入FOA基地址: %x \n", pSection->PointerToRawData);
	printf("读入节表长度: %x \n", pSection->SizeOfRawData);
	UnmapViewOfFile(lpBase);
}

循环加密所有的节,可能会出现问题

#include <stdio.h>
#include <Windows.h>
#include <ImageHlp.h>
#pragma comment(lib,"Imagehlp.lib")

void EncrySection(LPSTR szFileName,DWORD Key)
{
	HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
	HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

	PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
	PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
	PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);
	printf("[-] 加密节表数量: %d \n", FileHdr->NumberOfSections);

	for (int x = 0; x < FileHdr->NumberOfSections; x++)
	{
		printf("[-] 节虚拟地址: 0x%08X 虚拟大小: 0x%08X\n", pSection->VirtualAddress,pSection->Misc.VirtualSize);
		printf("[-] 读入FOA基地址: 0x%08X 节表长度: 0x%08X \n\n", pSection->PointerToRawData,pSection->SizeOfRawData);

		DWORD dwRead = 0;
		PBYTE pByte = (PBYTE)malloc(pSection->SizeOfRawData);

		SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
		memset(pByte, 0, pSection->SizeOfRawData);
		ReadFile(hFile, pByte, pSection->SizeOfRawData, &dwRead, NULL);

		for (int x = 0; x < pSection->SizeOfRawData; x++)
		{
			pByte[x] ^= Key;
		}

		SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
		WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN);

		free(pByte);
		pSection = pSection + 1;
	}
	UnmapViewOfFile(lpBase);
}

int main(int argc, char * argv[])
{
	EncrySection("c://win32.exe",0x10);
	system("pause");
	return 0;
}

添加壳代码

#include <stdio.h>
#include <Windows.h>
#include <ImageHlp.h>
#pragma comment(lib,"Imagehlp.lib")

void EncrySection(LPSTR szFileName, DWORD Key)
{
	HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
	HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

	PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
	PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
	PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);
	printf("[-] 节虚拟地址: 0x%08X 虚拟大小: 0x%08X\n", pSection->VirtualAddress, pSection->Misc.VirtualSize);
	printf("[-] 读入FOA基地址: 0x%08X 节表长度: 0x%08X \n", pSection->PointerToRawData, pSection->SizeOfRawData);
	printf("[*] 已对 %s 节 --> XOR加密/解密 --> XOR密钥: %d \n\n", pSection->Name, Key);

	DWORD dwRead = 0;
	PBYTE pByte = (PBYTE)malloc(pSection->SizeOfRawData);

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	memset(pByte, 0, pSection->SizeOfRawData);
	ReadFile(hFile, pByte, pSection->SizeOfRawData, &dwRead, NULL);

	for (int x = 0; x < pSection->SizeOfRawData; x++)
	{
		pByte[x] ^= Key;
	}

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN);
	pSection->Characteristics = 0xE0000020;

	free(pByte);
	FlushViewOfFile(lpBase, 0);
	UnmapViewOfFile(lpBase);
}

void DecodeCode(LPSTR szFileName)
{
	// 第一步修正程序OEP位置,修正为最后一个节的地址
	HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
	HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

	PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
	PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);

	DWORD ImageBase = NtHdr->OptionalHeader.ImageBase;
	DWORD BaseRVA = NtHdr->OptionalHeader.AddressOfEntryPoint;
	printf("Base RVA %x \n", BaseRVA);


	PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);


	// 首先得到最后一个节的指针,然后找到里面的虚拟偏移值,填入到程序OEP位置即可。
	DWORD SectionNum = FileHdr->NumberOfSections;
	char Code[] =
	{
		"\x60"
		"\xb8\x00\x00\x00\x00"
		"\x80\x30\x88"
		"\x40"
		"\x3d\xff\x4f\x40\x00"
		"\x75\xf5"
		"\x61"
		"\xb8\x00\x00\x00\x00"
		"\xff\xe0"
	};
	DWORD dwWrite = 0;
	printf("%x \n", ImageBase + pSection->VirtualAddress);
	*(DWORD *)&Code[2] = ImageBase + pSection->VirtualAddress;
	*(DWORD *)&Code[11] = ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize;
	*(DWORD *)&Code[19] = ImageBase + BaseRVA;
	pSection = pSection + (SectionNum - 1);
	printf("得到最后一个节的实际地址: %x \n", pSection->PointerToRawData);

	SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
	WriteFile(hFile, (LPVOID)Code, sizeof(Code), &dwWrite, NULL);
	FlushViewOfFile(lpBase, 0);
	UnmapViewOfFile(lpBase);
}

加壳的首要目标是要创建一个具有可写属性的新节

我们使用PESection对win32.exe加一个.hack节,然后大小为2048

加入后再次使用PETools工具检查,发现已经添加成功了。

下一步就是将.text节进行加密了,这里为了简单我使用的是异或加密,如下是加密前的机器码。

使用我们编写的工具进行加密,传入两个参数,一个是文件,一个则是加密密钥

加密有区段会变成如下样子。

接着使用 addpack 传入一个参数,写入解密代码。

电脑管家可能会拦截,请将其取出来。

我们X64dbg载入看看,程序默认停在了,我们的壳的位置,。

运行后对.text节进行动态解密,然后一个jmp跳转到程序的OEP位置即可,这也就是壳的基本原理。

posted @ 2020-10-02 10:24  lyshark  阅读(207)  评论(0编辑  收藏