脱壳与加壳-加壳-3-加壳代码实现
壳代码如何存在
壳代码以什么形式存在?壳代码,就是一段指令,我们这里将其编写成为一个dll文件,把他的代码段,当成是壳代码
但是dll文件也是一个PE文件,也会有各自各样的区段,所以可以采用link指令,把所有区段合并成一个区段,然后把这个区段复制过去复制到前面开辟的段空间里面处理
还需要注意的是,需要把.text代码段改为可写的才行
#pragma comment(linker,"/section:.text,RWE");
//修改属性
#pragma comment(linker,"/merge:.data=.text");
//合并
#pragma comment(linker,"/merge:.rdata=.text");
//合并
壳代码如何跳转回OEP
壳代码需要先让程序的OEP为壳代码的OEP,然后再跳转回真正的OEP,但是每个程序的OEP是不一样的,不可能指定一个地址来处理,那么这个地址就必须被加壳的程序来传递给壳代码才行,那么这里就采用了一个别的办法,就是在dll文件中把这个oep定义为一个变量,保存在导出表里,然后应用程序读取dll的时候就通过dll的导出表把这个数据的值改了,改成真正的OEP就好了,也就是说借用导出表作为一个媒介来处理
还有一个壳代码的OEP,壳代码的OEP被加壳的程序也不知道是多少就无法修改OEP的值,所以需要把壳代码的ShellOEP和RealOEP都用导出表来处理
typedef struct _PACKINFO
{
DWORD ShellOEP;
DWORD RealOEP;
}PACKINFO,*PPACKINFO;
extern "C" _declspec(dllexport) PACKINFO g_PackInfo;
这样定义后,dll就可以把结构体g_PackInfo的值进入到导出表导出出去了
壳代码功能实现
解密
对前面加密了的字段进行
首先我们需要得到加密的字段的地址,那么就需要通过得到进程的OEP然后再偏移处理所以就需要用到GetModuleHandleA() API,参数传一个NULL就会返回一个进程的首地址,但是由于我们这个dll是插进去的,所以就算操作系统不会帮我们修复IAT表,所以不能直接调用API,需要先动态获取dll来获取API处理,可以通过TEB PEB来获得到底一个kernel32,或者是kernelbase.dll两个dll,随便得到一个,然后再通过遍历导出表得到dll中的LoadLibrary加载kernel32.dll
然后再从kernel32.dll中拿到GetProcAddress函数,再通过GetProcAddress拿到GetModuleHandleA()函数的地址就可以调用了
动态获取dll以及函数地址
获取到dll后,申明一个函数指针来定义一个变量执行函数的地址然后调用就好了
//动态获取dll基址,通过TEB和PEB
DWORD GetImportantModule()
{
DWORD dwBase = 0;
__asm
{
mov eax,dword ptr fs:[0x30]
mov eax,dword ptr [eax+0xc]
mov eax,dword ptr [eax+0x1c]
mov eax,[eax]
mov eax,dword ptr [eax+0x8]
mov dwBase,eax
}
return dwBase;
}
//获取函数地址
DWORD MyGetProcAddress(DWORD hModule,LPCSTR funName)
{
//因为要通过导出表来访问,所以需要得到DOS头NT头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + hModule);
//获取导出表
DWORD ExportTableVa = pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ExportTableVa + hModule);
//找到导出名称表,序号表,地址表来访问获取
DWORD* NameTable = (DWORD*)(ExportTable->AddressOfNames + hModule);
DWORD* AddrTable = (DWORD*)(ExportTable->AddressOfFunctions + hModule);
WORD* NumberTable = (WORD*)(ExportTable->AddressOfNameOrdinals + hModule);
for (int i = 0; i < ExportTable->NumberOfNames; i++)
{
//获取函数名字
char* FunNameTemp = (char*)(NameTable[i] + hModule);
if (!strcmp(FunNameTemp, funName))
{
//名称比对成功
return AddrTable[NumberTable[i]];
}
}
return 0;//表示没有找到
}
//动态获取函数地址
void GetFunctions()
{
//1 获取kernel32或者Kernelbase模块的基址
DWORD pKernelBase = GetImportantModule();
//2 得到LoadLibraryEx函数地址,拿到这个函数地址后
//我们就可以想加载那个dll加载那个dll然后拿到对应的函数地址
g_MyLoadLibraryExA = (MyLoadLibraryExA)MyGetProcAddress(pKernelBase, "LoadLibraryExA");
//3 获取kernel32基址
HMODULE Kernel32Base = g_MyLoadLibraryExA("kernel32.dll", 0, 0);
}
壳代码的完整流程
1 保存环境
2 使用GetModule()得到关键模块的首地址
3 GetFunction()得到要调用的函数的地址
4 DecodeSection() 对加密了的区段解密
5 恢复环境
6 回到原来的OEP
//cpp文件
#include"pack.h"
//将代码段数据段合并在一起
#pragma comment(linker,"/merge:.data=.text")
#pragma comment(linker,"/merge:.rdata=.text")
#pragma comment(linker,"/section:.text,RWE")
_declspec(naked) void packStart()
{
__asm
{
//保存寄存器环境
pushad
//壳代码逻辑
GetImportantModule();
GetFunctions();
DeCodeSections();
//恢复寄存器环境再JMP回原始oep
popad
jmp RealOEP
}
}
//定义的全局变量
PACKINFO g_PackInfo = { (DWORD)packStart,0 };
MyLoadLibraryExA g_MyLoadLibraryExA = NULL;
MYGetProcAddress g_MYGetProcAddress = NULL;
MyGetModuleHandleA g_MyGetModuleHandleA = NULL;
MyVirtualProtect g_MyVirtualProtect = NULL;
//动态获取dll基址,通过TEB和PEB
DWORD GetImportantModule()
{
DWORD dwBase = 0;
__asm
{
mov eax,dword ptr fs:[0x30]
mov eax,dword ptr [eax+0xc]
mov eax,dword ptr [eax+0x1c]
mov eax,[eax]
mov eax,dword ptr [eax+0x8]
mov dwBase,eax
}
return dwBase;
}
//获取函数地址
DWORD MyGetProcAddress(DWORD hModule,LPCSTR funName)
{
//因为要通过导出表来访问,所以需要得到DOS头NT头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + hModule);
//获取导出表
DWORD ExportTableVa = pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ExportTableVa + hModule);
//找到导出名称表,序号表,地址表来访问获取
DWORD* NameTable = (DWORD*)(ExportTable->AddressOfNames + hModule);
DWORD* AddrTable = (DWORD*)(ExportTable->AddressOfFunctions + hModule);
WORD* NumberTable = (WORD*)(ExportTable->AddressOfNameOrdinals + hModule);
for (int i = 0; i < ExportTable->NumberOfNames; i++)
{
//获取函数名字
char* FunNameTemp = (char*)(NameTable[i] + hModule);
if (!strcmp(FunNameTemp, funName))
{
//名称比对成功
return AddrTable[NumberTable[i]];
}
}
return 0;//表示没有找到
}
//动态获取函数地址
void GetFunctions()
{
//1 获取kernel32或者Kernelbase模块的基址
DWORD pKernelBase = GetImportantModule();
//2 得到LoadLibraryEx函数地址,拿到这个函数地址后
//我们就可以想加载那个dll加载那个dll然后拿到对应的函数地址
g_MyLoadLibraryExA = (MyLoadLibraryExA)MyGetProcAddress(pKernelBase, "LoadLibraryExA");
//3 获取kernel32基址
HMODULE Kernel32Base = g_MyLoadLibraryExA("kernel32.dll", 0, 0);
//4 获取GetProcAddress函数
g_MYGetProcAddress = (MYGetProcAddress)MyGetProcAddress((DWORD)Kernel32Base, "GetProcAddress");
//5 拿到GetModuleHandleA函数
g_MyGetModuleHandleA = (MyGetModuleHandleA)g_MYGetProcAddress(Kernel32Base, "GetModuleHandleA");
//想要什么函数获取什么函数
//获取VirtualProtect
g_MyVirtualProtect = (MyVirtualProtect)g_MYGetProcAddress(Kernel32Base, "VirtualProtect");
}
//实现解密,解析得到DOS头,NT头
BOOL DeCodeSections()
{
int key = 0x51;
//获取当前进程的模块基址
HMODULE hModule= g_MyGetModuleHandleA(0);
//获取需要解密的模块的首地址
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(hModule + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER FirstSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
char* SectionBuff = (char*)(FirstSectionHeader->VirtualAddress + (DWORD)hModule);
//修改代码段的内存属性
DWORD OldProtect = 0;
g_MyVirtualProtect(SectionBuff, FirstSectionHeader->SizeOfRawData,
PAGE_READWRITE,&OldProtect);
for(int i=0;i<FirstSectionHeader->SizeOfRawData;i++)
{
SectionBuff[i] = SectionBuff[i] ^ 0x51;
}
//再复原内存属性
g_MyVirtualProtect(SectionBuff, FirstSectionHeader->SizeOfRawData,
OldProtect, &OldProtect);
}
//h头文件
#pragma once
#include<Windows.h>
typedef struct _PACKINFO
{
DWORD ShellOEP;
DWORD RealOEP;
}PACKINFO,*PPACKINFO;
extern "C" _declspec(dllexport) PACKINFO g_PackInfo;
typedef HMODULE (WINAPI *MyLoadLibraryExA)(LPCSTR lpLibFileName,
HANDLE hFile,
DWORD dwFlags
);
typedef FARPROC(WINAPI* MYGetProcAddress)(HMODULE hModule,
LPCSTR lpProcName
);
typedef HMODULE (WINAPI *MyGetModuleHandleA)(
LPCSTR lpModuleName
);
typedef BOOL (WINAPI* MyVirtualProtect)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
DWORD GetImportantModule();
DWORD MyGetProcAddress(DWORD hModule, LPCSTR funName);
void GetFunctions();
BOOL DeCodeSections();
需要注意的是,这里的 jmp RealOEP还没值,因为这个OEP需要和要加壳的程序进行交互处理
把壳代码写入到加壳程序里面
这里把之前写好的加壳程序拿出来,然后再在里面把壳代码作为一个dll放到里面就好了,这里就把OEP给修改和添加进去就好了
加壳的思路
对一个程序添加壳代码,首先需要明白壳代码的作用,壳代码就是来保护源程序,也就是说如果不执行壳代码无法正常执行程序,那么这样的话就可以通过加密源程序的区段来实现,因为各种头是PE文件的读取方式不能改变,然后这里采用的是加密.text代码段的代码,加密后壳代码里面肯定就会有解密然后还有一些各种各样的操作,比如不输入密码就不能用什么什么的,然后如何实现壳代码呢?
首先需要给这个壳代码开辟一个空间,所以就给他开辟一个区段来处理,那么还需要添加一个区段头,这里采用的办法是利用对齐值的空白来添加
然后生成一个dll文件,把这个dll文件的代码段和数据段合并为一个区段添加到源程序的区段里面,也就是相当于把壳代码添加到区段里面
然后dll文件内部,因为是我们手动添加的dll文件,所以这里就用不了Windows 的API因为动态链接库操作系统是没有帮我们添加的,所以就需要用到动态获取函数代码,然后还需要修改程序的OEP,让程序的OEP先等于壳代码OEP,然后执行完后再跳转回真正的OEP来实现加密
完整项目--demo
这个项目其实有一个bug就是,因为在加载dll文件后,dll不是通过操作系统添加的,所以它的重定位表就用不上,里面的全局变量就没办法使用,所以后面还需要添加重定义表
//exe
//.cpp文件
#include"main.h"
CPeUtil::CPeUtil()
{
FileBuff = NULL;
pDosHeader = NULL;
pNtHeader = NULL;
pOptionHeader = NULL;
pFileHeader = NULL;
}
CPeUtil::~CPeUtil()
{
if (FileBuff)
{
delete[] FileBuff;
}
}
BOOL CPeUtil::LoadFile(const char* path)
{
//打开文件,获得文件句柄
HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//获得文件大小
FileSize = GetFileSize(hFile,0);
//给FileBuff开辟空间来存储文件内容
FileBuff = new char[FileSize];
//读取文件到缓冲区
DWORD RealRead = 0;
BOOL IfSuccess = ReadFile(hFile, FileBuff, FileSize, &RealRead, NULL);
if (IfSuccess == FALSE)
{
MessageBoxA(0, "打开文件失败", "报错", MB_OK);
return FALSE;
}
CloseHandle(hFile);
return TRUE;
}
BOOL CPeUtil::InitFileInfo()
{
pDosHeader = (PIMAGE_DOS_HEADER)FileBuff;
pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + FileBuff);
pFileHeader = &pNtHeader->FileHeader;
pOptionHeader = &pNtHeader->OptionalHeader;
return 0;
}
DWORD CPeUtil::GetAlignSize(DWORD RealSize, DWORD AlignSize)
{
if (RealSize % AlignSize == 0)
{
return RealSize;
}
else
{
return (RealSize / AlignSize + 1) * AlignSize;
}
}
PIMAGE_SECTION_HEADER CPeUtil::GetLastSection()
{
PIMAGE_SECTION_HEADER FirstSection = IMAGE_FIRST_SECTION(pNtHeader);
FirstSection =FirstSection+ pFileHeader->NumberOfSections - 1;
return FirstSection;
}
BOOL CPeUtil::SaveFile(const char* path)
{
HANDLE hFile = CreateFileA(path,GENERIC_WRITE,0,NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
DWORD RealInput = 0;
BOOL ret =WriteFile(hFile, FileBuff, FileSize,&RealInput , NULL);
CloseHandle(hFile);
return TRUE;
}
BOOL CPeUtil::InsertSection(const char* SectionName, DWORD ShellCodeSize, char* ShellCodeBuff, DWORD dwAttribute)
{
//1 先要判断区段表后面 是否足够存放两个区段头的地址
DWORD k = pFileHeader->NumberOfSections;
k *= sizeof(IMAGE_SECTION_HEADER);//区段数*每个区段头的大小=整个区段头有多大
k = k + 504; //总的区段头的大小+可选PE头的结尾位置的地址
//利用文件对齐来计算剩余的值
DWORD FileAlignment = pOptionHeader->FileAlignment;
DWORD Surplus = FileAlignment - k % FileAlignment;
//Surplus就是剩下的内容了
if (Surplus < 2 * sizeof(IMAGE_SECTION_HEADER))
{
MessageBoxA(0, "区段剩余内存不够", "报错", MB_OK);
return 0;
}
//2 创建新的缓冲区来存放新的PE文件,因为这里假设添加了ShellCode进去
//这里需要注意的是需要注意对齐值的应用,获取对齐后的PE文件大小
DWORD newFileSize = GetAlignSize(FileSize+ ShellCodeSize,pOptionHeader->FileAlignment);
//3 创建新的缓冲区来存放PE文件
char* newFileBuff = new char[newFileSize];
memcpy_s(newFileBuff, newFileSize, FileBuff, FileSize);
FileSize = newFileSize;
delete[] FileBuff;
FileBuff = newFileBuff;
//4更新PE文件信息
InitFileInfo();
//5给新增的区段添加区段头
PIMAGE_SECTION_HEADER LastSection = GetLastSection();
LastSection++;//也就是新添加的区段的首个区段
//给新区段头设置属性
//设置区段内存大小,省事直接安装内存大小对齐
LastSection->Misc.VirtualSize = GetAlignSize(ShellCodeSize, pOptionHeader->SectionAlignment);
//设置名称
strcpy_s((char*)LastSection->Name, 8, SectionName);
//设置区段文件大小
LastSection->SizeOfRawData = GetAlignSize(ShellCodeSize, pOptionHeader->FileAlignment);
//设置偏移值,在内存中的偏移值
PIMAGE_SECTION_HEADER PreLastSectionHeader = GetLastSection();
//新加的区段的内存中的偏移=上一个区段内存中的偏移+内存中的大小对齐后的值
LastSection->VirtualAddress = PreLastSectionHeader->VirtualAddress
+ GetAlignSize(PreLastSectionHeader->Misc.VirtualSize, pOptionHeader->SectionAlignment);
//设置文件中的偏移
LastSection->PointerToRawData = PreLastSectionHeader->PointerToRawData +
+PreLastSectionHeader->SizeOfRawData;
LastSection->Characteristics = dwAttribute;
//修改Section数量
pFileHeader->NumberOfSections++;
//修改整个PE文件在内存中的大小
pOptionHeader->SizeOfImage += GetAlignSize(ShellCodeSize, pOptionHeader->SectionAlignment);
return 0;
}
BOOL CPeUtil::EncodeSections()
{
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
int key = 0x51;
//进行异或来加密
char* pData = FileBuff+(DWORD)pSectionHeader->PointerToRawData;
//pData指向文件偏移中的区段的首地址
//SizeofRawData表示区段在文件中对齐后的大小