这个是继上一篇加密壳帖子
masm32写加密壳 的另一篇新帖。不同的是,这次将采用vc++来编译壳
工程文件已经打包。 解决方案内有2个项目:EasyProtect 和 Shell。
EasyProtect 是用来加壳的。
Shell就是壳的核心,编译后是Shell.dll。
EasyProtect 流程:
打开被加壳文件 -> 打开Shell.dll -> 提取shell.dll代码段(.MyCode) 数据 ->在被加壳文件中创建新的区段->重定位Shell.dll的代码数据->把重定位好的代码数据写到被加壳程序的新区段中->加密被加壳程序代码段->修正OEP
但是还有一些细节没有提及。
packPE 加壳函数。
bool packPE(char * szFileName)
{
CPeFile MyPe;
CPeFile MyShell; // 壳也是一个编译的pe文件
PIMAGE_SECTION_HEADER pNewSec,pShellSec;
PMyDosHeader pDosHead;
char *shellcode;
if(!MyPe.LoadPe(szFileName))
return false;
if(!MyShell.LoadPe(SHELL_FILE))
return false;
pDosHead = (PMyDosHeader)MyPe.GetBuffer();
if (pDosHead->info.flag==0x1447) // 已经加密过了
return false;
/* 获取壳的代码段 */
pShellSec = MyShell.FindSectionByName(".MyCode");
pNewSec = MyPe.AddSection(".fishc",pShellSec->SizeOfRawData);
if(!pNewSec) return false;
// 重定位数据 0.0
FixRelocBase2(MyShell,MyPe.GetImageBase(),pNewSec->VirtualAddress);
// 复制代码数据
shellcode = new char[pShellSec->SizeOfRawData];
bool bRet =
MyShell.ReadDataByRaw(pShellSec->PointerToRawData,shellcode,pShellSec->SizeOfRawData);
if(!bRet)return false;
bRet =
MyPe.WriteDataByRaw(pNewSec->PointerToRawData,shellcode,pShellSec->SizeOfRawData);
if(!bRet) return false;
delete shellcode;
// 加密代码段
char *buf;
char *key = "mooncakeisverylovelygirl";
PIMAGE_SECTION_HEADER cs;
pDosHead->info.CodeSec=MyPe.GetCodeSection();
cs = MyPe.GetSectionById(pDosHead->info.CodeSec);
buf = (char *)MyPe.GetBuffer();
xorPlus(&buf[cs->PointerToRawData],cs->SizeOfRawData,key,strlen(key));
// 修正OEP
DWORD t;
// 获得壳的入口函数在.MyCode中的偏移
t = MyShell.GetEntry() - pShellSec->VirtualAddress;
// 把这个偏移加上新区段的基址就是最终的入口偏移
t+=pNewSec->VirtualAddress;
//保存源程序OEP
DWORD orgOEP;
orgOEP = MyPe.GetEntry()+MyPe.GetImageBase();
pDosHead->info.oep = orgOEP;
pDosHead->info.flag = 0x1447;
MyPe.SetNewEntry(t);
MyPe.FlushBuffer(); //把缓冲区的数据保存到硬盘
return true;
}
CPeFile 是我写的PE类,篇幅有限,就不贴这个类的代码了
谈谈重定位:
重定位的基本原理不想多讲,参看小甲鱼老师的视频。 这里需要注意的是Shell.dll 与 被加壳程序的区块基址是不同的。 所以我把这里的重定位称作“区块重定位”因为不具备普遍性,所以就没有收入到CPeFile类中,但是需要CPeFile类的支持~采用了硬编码,shell.dll的代码段是.MyCode
void FixRelocBase2(CPeFile &cPe,DWORD ImageBase,DWORD SecBase)
{
PIMAGE_BASE_RELOCATION rel;
DWORD relNum;
rel=(PIMAGE_BASE_RELOCATION)(
cPe.rva_to_buffer(cPe.GetDataDirInfo(IMAGE_DIRECTORY_ENTRY_BASERELOC)->VirtualAddress)
);
while (rel->SizeOfBlock)
{
relNum=(rel->SizeOfBlock-8)/2;
for (DWORD i=0;i<relNum;i++)
{
char Type;
Type = rel->TypeOffset[i]>>12;
if (Type==3)
{
DWORD *relAdr;
relAdr = (DWORD *)rel->VirtualAddress;
relAdr = (DWORD *)cPe.rva_to_buffer((DWORD)relAdr + (rel->TypeOffset[i]&0x0fff));
*relAdr -= cPe.GetImageBase(); //算出RVA
*relAdr -=cPe.FindSectionByName(".MyCode")->VirtualAddress;
*relAdr +=SecBase;
*relAdr +=ImageBase;
}
}
rel=(PIMAGE_BASE_RELOCATION)((DWORD)rel + rel->SizeOfBlock);
}
}
在加壳的过程中,我们可能有些重要信息需要保存,比如OEP。 在EasyProtect中这些信息都保存在DOS头中。DOS只有首尾的字段有效,其他的都可以任意发挥、因此,可以把DOS定义为如下数据结构:
#pragma pack(push)
#pragma pack(1)
typedef struct
{
WORD e_magic; // MZ 必须
struct{
WORD selfdata[23];// 共有29 * 2 字节可以自己定义-.-
DWORD flag; //标志位
DWORD oep;
DWORD CodeSec; //代码段序号
}info;
LONG e_lfanew; // 必须字段 PE头的偏移地址
}MyDosHeader,*PMyDosHeader;
#pragma pack(pop)
来看看Shell的流程吧。
Shell寄居在被加壳程序中,拥有优先执行权。
Shell流程如下:
保存寄存器状态 ->动态获取API ->加载配置信息->修改代码段内存属性->解密代码段->跳回OEP
#pragma code_seg(".MyCode") // 设置代码段为.MyCode
#pragma comment(linker, "/MERGE:.data=.MyCode") // 设置数据段为 .MyCode
#pragma comment(linker, "/MERGE:.rdata=.MyCode")
#pragma comment(linker,"/ENTRY:ShellEntry") // 设置入口函数为ShellEntry
#include <windows.h>
#pragma pack(push)
#pragma pack(1)
typedef struct
{
WORD e_magic; // MZ 必须
struct{
WORD selfdata[23];// 共有29 * 2 字节可以自己定义-.-
DWORD flag; //标志位
DWORD oep;
DWORD CodeSec; //代码段序号
}info;
LONG e_lfanew; // 必须字段 PE头的偏移地址
}MyDosHeader,*PMyDosHeader;
#pragma pack(pop)
#define RvaToVa(base_,rva_) ((ULONG)base_+(ULONG)rva_)
//任意指针与整数相减
#define p_sub(s1,s2) ((ULONG)s1-(ULONG)s2)
//任意指针与整数相加
#define p_add(s1,s2) ((ULONG)s1+(ULONG)s2)
// jmp 指令操作码
#define jmp_opcode 0xe9
/// 一些函数定义
typedef DWORD(__stdcall *_GetModuleHandleA)(char *ModuleName);
typedef DWORD(__stdcall *_LoadLibraryA)(char *ModuleName);
typedef bool(__stdcall *_IsDebuggerPresent)();
typedef DWORD(__stdcall *_VirtualProtect)( LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect);
char * shellinfo = "Hello world~.~";
int inline __stdcall _strlen(char *str)
{
int count = 0;
while (*str!='\0')
{
str++;
count++;
}
return count;
}
int inline __declspec(naked) __stdcall GetKernelBase()
{
_asm
{
mov eax,fs:[30h] //;PEB的地址
mov eax, [eax + 0ch] //;Ldr的地址
mov esi, [eax + 01ch] //;Flink地址
lodsd
mov eax, [eax + 08h] //;eax就是kernel32.dll的地址
ret
}
}
PVOID _getProcAddress(HMODULE imageBase,char *ExportName)
{
PIMAGE_DOS_HEADER pDosHead;
PIMAGE_NT_HEADERS pNtHead;
PIMAGE_EXPORT_DIRECTORY pExport;
WORD *Ord;
DWORD *FunAddr;
DWORD *name_list;
DWORD baseorder;
if (!imageBase)
{
return NULL;
}
pDosHead = (PIMAGE_DOS_HEADER)imageBase;
pNtHead = (PIMAGE_NT_HEADERS)p_add(imageBase,pDosHead->e_lfanew);
pExport=(PIMAGE_EXPORT_DIRECTORY)RvaToVa(imageBase,pNtHead->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
baseorder = pExport->Base;
Ord=(WORD *)RvaToVa(imageBase,pExport->AddressOfNameOrdinals);
FunAddr=(DWORD *)RvaToVa(imageBase,pExport->AddressOfFunctions);
name_list = (DWORD *)RvaToVa(imageBase,pExport->AddressOfNames);
for (DWORD i=0;i<pExport->NumberOfFunctions;i++)
{
DWORD Index;
DWORD FunRva;
char *FunName;
FunName = (char *)RvaToVa(imageBase,name_list[i]);
if (!strcmp(FunName,ExportName))
{
Index=Ord[i];
FunRva=FunAddr[Index];
FunRva+=(ULONG)imageBase;
return (PVOID *)FunRva;
}
}
return NULL;
}
void xorPlus(char *soure,int dLen,char *Key,int Klen)
{
for (int i=0;i<dLen;)
{
for (int j=0;j<Klen;j++,i++)
{
soure[i]=soure[i] ^ Key[j];
soure[i]=~soure[i];
}
}
}
PIMAGE_SECTION_HEADER GetSectionById(PIMAGE_DOS_HEADER pDos,WORD id)
{
PIMAGE_SECTION_HEADER fisrt;
char *buf;
buf = (char *)pDos;
fisrt = (PIMAGE_SECTION_HEADER)&buf[pDos->e_lfanew+sizeof(IMAGE_NT_HEADERS)];
return &fisrt[id];
}
PMyDosHeader info; // 这个变量很重要·
char *key = "mooncakeisverylovelygirl";
void _main()
{
DWORD kernelBa;
DWORD kernel32;
_GetModuleHandleA getmodulehandlea;
_VirtualProtect virtualprotect;
DWORD ImageBase;
PIMAGE_SECTION_HEADER cs;
DWORD old,o2;
kernelBa = GetKernelBase();
getmodulehandlea=(_GetModuleHandleA)_getProcAddress((HMODULE)kernelBa,"GetModuleHandleA");
kernel32 = getmodulehandlea("kernel32");
virtualprotect=(_VirtualProtect)_getProcAddress((HMODULE)kernel32,"VirtualProtect");
ImageBase = getmodulehandlea(NULL);
info = (PMyDosHeader)ImageBase;
cs = GetSectionById((PIMAGE_DOS_HEADER)ImageBase,info->info.CodeSec);
// 修改区块属性
char * soure = (char *)RvaToVa(ImageBase,cs->VirtualAddress);
virtualprotect(soure,cs->SizeOfRawData,PAGE_EXECUTE_READWRITE,&old);
xorPlus(soure,cs->SizeOfRawData,key,24);
virtualprotect(soure,cs->SizeOfRawData,old,&o2);
}
int __declspec(naked) __stdcall ShellEntry() // 这里是壳的入口点
{
_asm
{
pushad // 保存寄存器信息
call _main
popad
mov eax,info
push [eax].info.oep
nop
nop
ret
}
}
一个比较有意思的现象,下面是一段网上流传的Kernel32.dll基址获取代码:
int inline __declspec(naked) __stdcall GetKernelBase()
{
_asm
{
mov eax,fs:[30h] //;PEB的地址
mov eax, [eax + 0ch] //;Ldr的地址
mov esi, [eax + 01ch] //;Flink地址
lodsd
mov eax, [eax + 08h] //;eax就是kernel32.dll的地址
ret
}
}
我实际测试的时候得到的是KernelBa.dll的基址(我的系统是win7) 但是KernelBa导出有GetModuleHandleA 所以问题就迎刃而解了。通过GetModuleHandleA 获取Kernel32的基址就OK啦~
EasyProtect.zip源码在此,献丑了
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课