首页
社区
课程
招聘
[原创]动手写加密壳
发表于: 2015-7-15 18:46 27353

[原创]动手写加密壳

2015-7-15 18:46
27353
这个是继上一篇加密壳帖子   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直播授课

上传的附件:
收藏
免费 4
支持
分享
最新回复 (37)
雪    币: 205
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢分享!
2015-7-15 19:28
0
雪    币: 4580
活跃值: (992)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
好东西收藏一份
2015-7-15 19:47
0
雪    币: 44229
活跃值: (20000)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
帖子我移到脱壳版块上来了。

有时间,再研究一下VM
2015-7-15 21:09
0
雪    币: 135
活跃值: (106)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
5
有一个问题:
Shell.dll 只用里面的代码段的话, 那在编写的时候, 能使用全局变量吗
2015-7-15 21:19
0
雪    币: 8
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
学习学习了学习学习了
2015-7-15 21:43
0
雪    币: 6890
活跃值: (8964)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
7
可以的,代码段和数据段都合并在一起的。
#pragma code_seg(".MyCode")  // 设置代码段为.MyCode
#pragma comment(linker, "/MERGE:.data=.MyCode") // 设置数据段为 .MyCode
#pragma comment(linker, "/MERGE:.rdata=.MyCode")
2015-7-15 21:54
0
雪    币: 6890
活跃值: (8964)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
8
谢谢大大的鼓励~
2015-7-15 21:56
0
雪    币: 322
活跃值: (113)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
谢谢分享!
2015-7-15 23:42
0
雪    币: 56
活跃值: (34)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
多谢分享~
2015-7-16 08:37
0
雪    币: 135
活跃值: (106)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
11
懂了, 不错的。 还有一个问题: 这个壳能给dll加壳吗?
2015-7-16 09:40
0
雪    币: 602
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
加壳 学习了
2015-7-16 12:04
0
雪    币: 6890
活跃值: (8964)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
13
还没实现。
要实现的话要需要重新修正重定位表。
2015-7-16 12:07
0
雪    币: 341
活跃值: (138)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
14
mooncakeisverylovelygirl
2015-7-16 16:46
0
雪    币: 110
活跃值: (527)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
15
能给任意DLL和EXE加壳吗。
2015-7-16 17:06
0
雪    币: 144
活跃值: (335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
哪里是加密壳了 - -
2015-7-16 17:09
0
雪    币: 6890
活跃值: (8964)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
17
好眼力!
2015-7-16 19:53
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Sry
18
支持技术分享,受教了。
2015-7-18 01:33
0
雪    币: 34682
活跃值: (7145)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
19
14楼看你眼熟
2015-7-18 07:00
0
雪    币: 112
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
不错,学习下..
2015-7-18 10:25
0
雪    币: 12973
活跃值: (3972)
能力值: ( LV15,RANK:1575 )
在线值:
发帖
回帖
粉丝
21
下载学习中.3Q.
2015-7-18 22:59
0
雪    币: 5067
活跃值: (3469)
能力值: ( LV13,RANK:283 )
在线值:
发帖
回帖
粉丝
22
写的挺好,支持一下
2015-7-19 14:39
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
比较高深,先收藏再慢慢消化。
2015-7-19 20:46
0
雪    币: 454
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
谢谢分析,文章对我很有用。
2015-7-20 10:00
0
雪    币: 74
活跃值: (713)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
谢谢分享
2015-7-21 09:27
0
游客
登录 | 注册 方可回帖
返回
//