欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]动手写加密壳

[原创]动手写加密壳

这个是继上一篇加密壳帖子   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源码在此,献丑了

上传的附件 EasyProtect.zip

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功