首页
社区
课程
招聘
[原创]二进制加壳原理与实现
发表于: 2019-1-22 11:52 31555

[原创]二进制加壳原理与实现

2019-1-22 11:52
31555

软件加壳的目的是为了防止外部程序对软件进行静态反汇编分析或者动态分析达到对软件保护的目的。壳子的种类很多,大体种类分2种二进制壳和指令壳,二进制壳不改变硬编码的含义,指令壳则改变了原有硬编码的含义。 本篇文章介绍二进制壳的加壳的一种思路及实现。

1,加壳过程

加壳流程如下图所示:


(1)将待加壳程序全部进行加密,加密可以采用任何方式;(2)在壳子程序中新增一个节,大小为待加壳程序的大小+解壳程序的大小;(3)将解壳程序拷贝到1位置,加密后的待加壳程序拷贝到2位置;(4)修改壳子程序的OEP到新增节的位置。(5)存盘壳子程序。

这里的壳子程序可以是任意程序,只提供了外壳。壳子程序是任意的,新增节后的位置也是变化的,所以解壳程序一定是shell程序。

2,解壳程序

打开壳子程序的时候,程序会从OEP开始启动,执行的是解壳程序:(1)读取加密后的待加壳程序2;(2)解密得到原来的程序;(3)以挂起的方式创建进程,创建的进程是壳子程序进程,这里要注意的是以挂起的方式创建的进程,没有加载任何模块。(4)获取新创建的进程的Contex环境,(5)卸载外壳程序,卸载掉原来壳子程序imageBase开始到程序内存镜像这部分空间的数据,此时只有空间,而内部的内存处于没有使用状态。(6)在创建的进程内部在待加壳程序的imageBase处申请内存空间,大小是待加壳程序的imageBase。(7)将待加壳程序拉伸,复制到刚刚申请到的imageBase处,此时,在壳子的空间内部,已经偷天换日编程了待加壳的程序了。(8)修改外壳Contex的imagebase=待加壳的imagebase,OEP=待加壳程序的OEP了。(9)恢复外壳程序的主线程。(10)主线程启动后,解壳结束,壳子内部运行的是待加壳程序。好一个偷天换日。

整个解壳程序都需要使用shellCode的方式来写,编译后,抠出硬编码存储成字节数组,所以拷贝的解壳程序就是这个字节数组。

3,加壳代码

int _tmain(int argc, _TCHAR* argv[])
{
	char* szShell = "../Debug/shell.exe";
	char* szObj = "../Debug/xxx.exe";

	HPE shell = loadPE(szShell);
	HPE object = loadPE(szObj);

	int length = getFileLength(object);
	BYTE* src = getFileBuffer(object);
	HPE newObj = addSection(shell, ".src", length + 0x2000, 0x60000020);
	IMAGE_OPTIONAL_HEADER32* pOptionalHeader = getOptionalHeader(newObj);
	
	IMAGE_SECTION_HEADER* pShellSection = getLastSection(newObj);
	pOptionalHeader->AddressOfEntryPoint = pShellSection->VirtualAddress;
	memcpy((void*)(pShellSection->PointerToRawData + (int)getFileBuffer(newObj)), myshellcode, sizeof(myshellcode));
	memcpy((void*)(pShellSection->PointerToRawData + (int)getFileBuffer(newObj) + 0x2000), src, length);
	
	writeFile("../Debug/test.exe", newObj);
	releasePE(newObj);
	return 0;
}


4,解壳代码

void Decrypt()
{
	BOOL bResult = TRUE;
	HANDLE hProcess = NULL;
	TERMINATEPROCESS TerminateProcess = NULL;
	try
	{

		HINSTANCE hKernel32 = NULL;
		__asm
		{
			push eax
				mov eax, fs:[0x30]		// PEB
				mov eax, [eax + 0x0c]		// LDR
				mov eax, [eax + 0x0c]		// 本进程模块, LDA_DATA_TABLE_ENTRY 结构
				mov eax, [eax]			// ntdll 模块
				mov eax, [eax]			// kernel32 模块
				mov eax, [eax + 0x18]		// dllBase
				mov hKernel32, eax
				pop eax
		}

		char szGetProcAddress[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '\0' };
		GETPROCADDRESS GetProcAddress = NULL;
		// 在 kernel32中寻找 GetProcAddress 函数
		IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hKernel32;
		IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)hKernel32 + pDosHeader->e_lfanew);
		IMAGE_EXPORT_DIRECTORY* pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)((DWORD)hKernel32 + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
		short* functionOrdinals = (short*)((DWORD)hKernel32 + pExportDirectory->AddressOfNameOrdinals);
		int* adddressOfNames = (int*)((DWORD)hKernel32 + pExportDirectory->AddressOfNames);
		int* addressOfFunctions = (int*)((DWORD)hKernel32 + pExportDirectory->AddressOfFunctions);
		for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
		{
			// 第 i 个函数名地址
			char* name = (char*)((DWORD)hKernel32 + adddressOfNames[i]);
			char* left = name;
			char* right = szGetProcAddress;

			bool res = true;
			while (*left || *right)
			{
				if (*left != *right)
				{
					res = false;
					break;
				}
				++left;
				++right;
			}
			if (res)
			{
				GetProcAddress = (GETPROCADDRESS)((DWORD)hKernel32 + addressOfFunctions[functionOrdinals[i]]);
				break;
			}
		}

		pDosHeader = NULL;
		pNtHeader = NULL;

		char szLoadLibrary[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
		char szGetModuleHandle[] = { 'G', 'e', 't', 'M', 'o', 'd', 'u', 'l', 'e', 'H', 'a', 'n', 'd', 'l', 'e', 'A', '\0' };
		char szCreateProcess[] = { 'C', 'r', 'e', 'a', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', 'A', '\0' };
		char szGetModuleFileName[] = { 'G', 'e', 't', 'M', 'o', 'd', 'u', 'l', 'e', 'F', 'i', 'l', 'e', 'N', 'a', 'm', 'e', 'A', '\0' };
		char szGetThreadContext[] = { 'G', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'C', 'o', 'n', 't', 'e', 'x', 't', '\0'};
		char szReadProcessMemory[] = { 'R', 'e', 'a', 'd', 'P', 'r', 'o', 'c', 'e', 's', 's', 'M', 'e', 'm', 'o', 'r', 'y', '\0' };
		char szZwUnmapViewOfSection[] = { 'Z', 'w', 'U', 'n', 'm', 'a', 'p', 'V', 'i', 'e', 'w', 'O', 'f', 'S', 'e', 'c', 't', 'i', 'o', 'n', '\0' };
		char szNtDll[] = { 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', '\0' };
		char szMemcpy[] = { 'm', 'e', 'm', 'c', 'p', 'y', '\0' };
		char szMemset[] = { 'm', 'e', 'm', 's', 'e', 't', '\0' };
		char szMSVCRT[] = { 'm', 's', 'v', 'c', 'r', 't', '.', 'd', 'l', 'l', '\0' };
		char szMalloc[] = { 'm', 'a', 'l', 'l', 'o', 'c', '\0' };
		char szFree[] = { 'f', 'r', 'e', 'e', '\0' };
		char szVirtualAllocEx[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c', 'E', 'x', '\0' };
		char szWriteProcessMemory[] = { 'W', 'r', 'i', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', 'M', 'e', 'm', 'o', 'r', 'y', '\0' };
		char szSetThreadContext[] = { 'S', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'C', 'o', 'n', 't', 'e', 'x', 't', '\0' };
		char szResumeThread[] = { 'R', 'e', 's', 'u', 'm', 'e', 'T', 'h', 'r', 'e', 'a', 'd', '\0' };
		char szTerminateProcess[] = { 'T', 'e', 'r', 'm', 'i', 'n', 'a', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', '\0' };
		char szExitProcess[] = { 'E', 'x', 'i', 't', 'P', 'r', 'o', 'c', 'e', 's', 's', '\0' };

		LOADLIBRARY LoadLibraryA = (LOADLIBRARY)GetProcAddress(hKernel32, szLoadLibrary);
		GETMODULEHANDLE GetModuleHandleA = (GETMODULEHANDLE)GetProcAddress(hKernel32, szGetModuleHandle);
		CREATEPROCESS CreateProcessA = (CREATEPROCESS)GetProcAddress(hKernel32, szCreateProcess);
		GETMODULEFILENAME GetModuleFileNameA = (GETMODULEFILENAME)GetProcAddress(hKernel32, szGetModuleFileName);
		GETTHREADCONTEXT GetThreadContext = (GETTHREADCONTEXT)GetProcAddress(hKernel32, szGetThreadContext);
		READPROCESSMEMORY ReadProcessMemory = (READPROCESSMEMORY)GetProcAddress(hKernel32, szReadProcessMemory);
		WRITEPROCESSMEMORY WriteProcessMemory = (WRITEPROCESSMEMORY)GetProcAddress(hKernel32, szWriteProcessMemory);
		VIRTUALALLOCEX VirtualAllocEx = (VIRTUALALLOCEX)GetProcAddress(hKernel32, szVirtualAllocEx);
		SETTHREADCONTEXT SetThreadContext = (SETTHREADCONTEXT)GetProcAddress(hKernel32, szSetThreadContext);
		RESUMETHREAD ResumeThread = (RESUMETHREAD)GetProcAddress(hKernel32, szResumeThread);
		TerminateProcess = (TERMINATEPROCESS)GetProcAddress(hKernel32, szTerminateProcess);
		EXITPROCESS ExitProcess = (EXITPROCESS)GetProcAddress(hKernel32, szExitProcess);

		HMODULE hNtDll = GetModuleHandleA(szNtDll);
		ZWUNMAPVIEWOFSECTION ZwUnmapViewOfSection = (ZWUNMAPVIEWOFSECTION)GetProcAddress(hNtDll, szZwUnmapViewOfSection);
		MEMCPY memcpy = (MEMCPY)GetProcAddress(hNtDll, szMemcpy);
		MEMSET memset = (MEMSET)GetProcAddress(hNtDll, szMemset);

		HMODULE hMSVCRT = LoadLibraryA(szMSVCRT);
		MALLOC malloc = (MALLOC)GetProcAddress(hMSVCRT, szMalloc);
		FREE free = (FREE)GetProcAddress(hMSVCRT, szFree);


		HANDLE hModule = GetModuleHandleA(NULL);
		pDosHeader = (IMAGE_DOS_HEADER*)hModule;
		pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)hModule + pDosHeader->e_lfanew);
		IMAGE_SECTION_HEADER* pSectionHeader = (IMAGE_SECTION_HEADER*)((DWORD)&pNtHeader->OptionalHeader + pNtHeader->FileHeader.SizeOfOptionalHeader);
		pSectionHeader = pSectionHeader + pNtHeader->FileHeader.NumberOfSections - 1;

		// 获取程序数据
		char* src = (char*)((DWORD)hModule + pSectionHeader->VirtualAddress + 0x2000);
		int srcSize = pSectionHeader->SizeOfRawData - 0x2000;

		// 拉伸
		pDosHeader = (IMAGE_DOS_HEADER*)src;
		pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)src + pDosHeader->e_lfanew);
		IMAGE_OPTIONAL_HEADER32* pOptionalHeader = &pNtHeader->OptionalHeader;
		pSectionHeader = (IMAGE_SECTION_HEADER*)((DWORD)pOptionalHeader + pNtHeader->FileHeader.SizeOfOptionalHeader);
		int length = pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1].VirtualAddress + pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1].SizeOfRawData;

		//有时候会出现length比Image还要大
		length = length > pOptionalHeader->SizeOfImage ? length : pOptionalHeader->SizeOfImage;

		char* imageBuffer = (char*)malloc(length);
		memset(imageBuffer, 0, length);
		memcpy(imageBuffer, src, pOptionalHeader->SizeOfHeaders);
		for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i)
		{
			memcpy(imageBuffer + pSectionHeader->VirtualAddress, src + pSectionHeader->PointerToRawData, pSectionHeader->SizeOfRawData);
			++pSectionHeader;
		}


		char szModuleName[MAX_PATH];
		GetModuleFileNameA(NULL, szModuleName, MAX_PATH);

		STARTUPINFO si;
		memset(&si, 0, sizeof(si));
		PROCESS_INFORMATION pi;
		memset(&pi, 0, sizeof(pi));
		si.cb = sizeof(si);
		CreateProcessA(NULL, szModuleName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
		hProcess = pi.hProcess;
		CONTEXT context;
		context.ContextFlags = CONTEXT_FULL;
		GetThreadContext(pi.hThread, &context);
		DWORD dwEntryPoint = context.Eax;
		DWORD baseAddress = context.Ebx + 8;
		DWORD shellImageBase = 0;
		ReadProcessMemory(pi.hProcess, (LPVOID)baseAddress, &shellImageBase, 4, NULL);
		ZwUnmapViewOfSection(pi.hProcess, (PVOID)shellImageBase);
		LPVOID buffer = VirtualAllocEx(pi.hProcess, (LPVOID)pOptionalHeader->ImageBase, pOptionalHeader->SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
		if (!buffer)
		{
			VirtualAllocEx(pi.hProcess, NULL, pOptionalHeader->SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
			pDosHeader = (IMAGE_DOS_HEADER*)imageBuffer;
			pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)imageBuffer + pDosHeader->e_lfanew);
			IMAGE_OPTIONAL_HEADER32* pOptionalHeader = &pNtHeader->OptionalHeader;
			IMAGE_BASE_RELOCATION* pBaseRelocation = (IMAGE_BASE_RELOCATION*)((DWORD)imageBuffer + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

			while (pBaseRelocation->SizeOfBlock || pBaseRelocation->VirtualAddress)
			{
				short* pTable = (short*)((int)pBaseRelocation + 8);
				int len = (pBaseRelocation->SizeOfBlock - 8) / 2;
				for (int i = 0; i < len; ++i)
				{
					DWORD RVA = pBaseRelocation->VirtualAddress + (pTable[i] & 0x0FFF);
					if (((pTable[i] & 0xF000) >> 12) == 3)
					{
						*(DWORD*)(RVA + (DWORD)imageBuffer) += ((DWORD)buffer - pOptionalHeader->ImageBase);
					}
				}
				pBaseRelocation = (IMAGE_BASE_RELOCATION*)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock);
			}
			pOptionalHeader->ImageBase = (DWORD)buffer;
		}
		WriteProcessMemory(pi.hProcess, buffer, imageBuffer, pOptionalHeader->SizeOfImage, NULL);
		WriteProcessMemory(pi.hProcess, (LPVOID)(context.Ebx + 8), &buffer, 4, NULL);
		context.Eax = pOptionalHeader->ImageBase + pOptionalHeader->AddressOfEntryPoint;
		SetThreadContext(pi.hThread, &context);
		ResumeThread(pi.hThread);
		free(imageBuffer);
		ExitProcess(0);
	}
	catch (...)
	{
		if (hProcess) TerminateProcess(hProcess, 0);
		return;
	}
}

5,如何脱壳

这种壳子解密完后,内存中的程序就是原来拉伸后的文件,只需要一步一步F8跟进,就会跟到OEP,然后把内存dump出来就可以了。



[课程]Linux pwn 探索篇!

最后于 2019-1-22 15:01 被毕达哥拉斯编辑 ,原因: 修改图片
收藏
免费 10
支持
分享
最新回复 (13)
雪    币: 44229
活跃值: (19950)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
图片显示不出来,建议重帖一下图。
2019-1-22 14:24
0
雪    币: 2089
活跃值: (3111)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
3
支持发码,话说怎么就发到移动板块了呢,多支持我的加壳脱壳板块哦
2019-1-22 15:25
0
雪    币: 13904
活跃值: (17017)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
4
xiaohang 支持发码,话说怎么就发到移动板块了呢,多支持我的加壳脱壳板块哦[em_1]
试图科比??
2019-1-22 16:40
0
雪    币: 6103
活跃值: (1207)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
5
感谢分享!
2019-1-23 15:57
0
雪    币: 199
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享!
2019-1-24 16:12
0
雪    币: 1385
活跃值: (22)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
這種方法反向破解起來不要太容易了吧?而且如果整個程序全部加密的話,資源等其他非代碼段的處理都會有問題。
最后于 2019-1-25 10:47 被DLDLIS编辑 ,原因:
2019-1-25 10:45
0
雪    币: 1808
活跃值: (573)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
8
这只是最简单的框架,还不涉及各种表的移动,压缩壳等等
2019-1-25 10:56
0
雪    币: 164
活跃值: (99)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
毕达哥拉斯 这只是最简单的框架,还不涉及各种表的移动,压缩壳等等
可以直接引用的 不用一个一个截图的
2019-1-25 11:52
0
雪    币: 1808
活跃值: (573)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
10
冰雪冬樱 可以直接引用的 不用一个一个截图的
2019-1-25 11:59
0
雪    币: 21449
活跃值: (62273)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
11
哈哈,有点6
2019-1-25 15:08
0
雪    币: 183
活跃值: (2427)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
能不能把全部的源码发下
2019-6-9 16:02
0
雪    币: 183
活跃值: (2427)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
你为什么中间加壳的时候多加一个0x2000
2019-6-9 22:14
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
请问可不可以发一下全部的源码?
2021-11-15 17:29
0
游客
登录 | 注册 方可回帖
返回
//