文档章节

壳的编写(2)-- 编写壳(Stub)部分

o
 osc_wws45aot
发布于 2019/08/20 18:55
字数 2150
阅读 13
收藏 1

精选30+云产品,助力企业轻松上云!>>>

1、添加编译选项
         在Stub项目中的Stub.cpp中,添加一下代码,控制此项目在编译时的连接选项。让我们生成的dll程序.text、.data与rdata区段合并。

#pragma comment(linker, "/merge:.data=.text") // 将.data合并到.text
#pragma comment(linker, "/merge:.rdata=.text") // 将.rdata合并到.text
#pragma comment(linker, "/section:.text,RWE") // 将.text段的属性设置为可读、可写、可执行
重新编译项目,用LoadPe打开Stub.dll,区段表信息如下:

 

2、进一步配置项目
         起始通过之前的配置我们就已经配置好项目了,为了让我们的项目配置更好些,想让最终生成的文件只有MFC工程生成的PackBase.exe和加壳工程生成的Pack_Dll.dll两个文件,而Stub工程生成的Stub.dll以资源的形式存在Pack_Dll项目中。

         为此,我们首先将Stub工程编译一下,它就会在Debug中生成一个名为Stub.dll的文科。

         然后,在项目Pack_dll项目上,右键点击“添加”中的“资源”,在添加资源对话框中,点击“导入”,进入到生成Stub.dll的目录中,在文件过滤对话总选择“所有文件(*.*)”,选中Stub.dll并确定。

 

 

 


在弹出的自定义资源类型对话框中的资源类型中添写“STUB”,为的是后面调用Stub.dll使用。

 

 

 

在资源视图中就会出现资源类型为“STUB”名称为“IDR_STUB1”的资源,如下图:

 

 

通过以上我们就配置好了,不过要注意,当项目路径发生变化的时候,需要重新指定Stub.dll的路径。

 

3、定义结构保存数据
         我们对被加壳程序的壳操作是在Stub.dll中完成的,那么我们就有必要获取到被加壳程序在壳操作需要用到PE信息。因此,我们在Stub.h中定义了如下的结构:

typedef struct _GLOBAL_PARAM
{
BOOL bShowMessage; // 是否显示解密信息
DWORD dwOEP; // 程序入口点
PBYTE lpStartVA; // 起始虚拟地址(被异或加密区)
PBYTE lpEndVA; // 结束虚拟地址(被异或加密区)
}GLOBAL_PARAM, *PGLOBAL_PARAM;

  


为了能够在外面调用该结构并进行赋值,我们需要定义一个全局的导出变量,如下:

extern "C"__declspec(dllexport) GLOBAL_PARAM g_stcParam;

  

4、去除dll的引导代码
         由于dll格式文件同exe格式一样,在执行我们自己代码前会执行很多的引导代码,而这些代码我们不可控,同样为了程序的健壮性,我们需要将引导代码去除掉。

         为此,我们在Stub.cpp文件中自定义一个入口函数即可,代码如下:

#pragma comment(linker, "/entry:\"StubEntryPoint\"") // 指定程序入口函数为StubEntryPoint()

void start()
{
//这里存放对被加壳文件的操作
}

void __declspec(naked) StubEntryPoint()
{
__asm sub esp, 0x50; // 抬高栈顶,提高兼容性
start(); // 执行壳的主体部分
__asm add esp, 0x50; // 平衡堆栈
__asm retn;
}

  

5、动态加载API--获取到GetProcAddress
         由于我们程序到时会丢弃掉自己的IAT和导入表信息,这样就不能够直接调用API,因此,我们需要使用动态加载API方法。我们需要获取到GetProcAddress函数的地址,而该函数是从系统文件Kernel32.dll中导出的,所有的运行的程序都会加载该动态链接库。那么,我们获取到Kernel32.dll加载基址,就可以获取GetProcAddress函数了。要想获取到Kernel32基址,我们可以使用TEB的信息找到Kernel32.dll的加载基址:

         1)通过FS得到TEB的地址

         2)TEB偏移0x30处指向的是PEB指针

         3)PEB偏移0x0C处指向PEB_LDR_DATA结构指针

         4)PEB_LDR_DATA偏移0x1C处是InInitializationOrderModuleLis(模块初始化链表的头指针)

         5)InInitializationOrderModuleLis中按顺序存在着此进程的初始化模块信息,在NT5.x内核中,第一个节点为ntdll.dll的基址,第二个节点为Kernel32.dll的基址; 在NT6.1内核中,第二个节点为KernelBase.dll的基址(包含着Kernel32.dll的大部分实现,其中就有GetProcAddress函数)

 


为此我们需要在Stub.h中定义两个函数,一个用于获取Kernel32.dll基址,一个用于获取GetProcAddress基址,代码如下:

extern DWORD GetKernel32Base(); // 获取Kernel32.dll的模块基址
extern DWORD GetGPAFunAddr(); // 获取GetProcAddress的函数地址
在Stub.cpp中获取Kernel32.dll的代码如下:
DWORD GetKernel32Base()
{
DWORD dwKernel32Addr = 0;
__asm
{
push eax
mov eax,dword ptr fs:[0x30] // eax = PEB的地址
mov eax,[eax+0x0C] // eax = 指向PEB_LDR_DATA结构的指针
mov eax,[eax+0x1C] // eax = 模块初始化链表的头指针InInitializationOrderModuleList
mov eax,[eax] // eax = 列表中的第二个条目
mov eax,[eax+0x08] // eax = 获取到的Kernel32.dll基址(Win7下获取的是KernelBase.dll的基址)
mov dwKernel32Addr,eax
pop eax
}

return dwKernel32Addr;
}

  

注:可参考http://blog.csdn.net/obuyiseng/article/details/50456090

         此时我们就能够获取到Kernel32.dll的基址,那么就可以通过遍历Kernel32.dll的导出表获取到GetProcAddress函数的基址了。

         在Stub.cpp文件中获取GetProcAddress的基址代码如下:

DWORD GetGPAFunAddr()
{
DWORD dwAddrBase = GetKernel32Base();

// 1. 获取DOS头、NT头
PIMAGE_DOS_HEADER pDos_Header;
PIMAGE_NT_HEADERS pNt_Header;
pDos_Header = (PIMAGE_DOS_HEADER)dwAddrBase;
pNt_Header = (PIMAGE_NT_HEADERS)(dwAddrBase + pDos_Header->e_lfanew);

// 2. 获取导出表项
PIMAGE_DATA_DIRECTORY pDataDir;
PIMAGE_EXPORT_DIRECTORY pExport;
pDataDir = pNt_Header->OptionalHeader.DataDirectory; 
pDataDir = &pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT];
pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress);


// 3、获取导出表的必要信息
DWORD dwModOffset = pExport->Name;	// 模块的名称
DWORD dwFunCount = pExport->NumberOfFunctions;	// 导出函数的数量
DWORD dwNameCount = pExport->NumberOfNames;	// 导出名称的数量

PDWORD pEAT = (PDWORD)(dwAddrBase + pExport->AddressOfFunctions);	// 获取地址表的RVA
PDWORD pENT = (PDWORD)(dwAddrBase + pExport->AddressOfNames);	// 获取名称表的RVA
PWORD pEIT = (PWORD)(dwAddrBase + pExport->AddressOfNameOrdinals);	//获取索引表的RVA

// 4、获取GetProAddress函数的地址
for (DWORD i = 0; i < dwFunCount; i++)
{
if (!pEAT[i])
{
continue;
}

// 4.1 获取序号
DWORD dwID = pExport->Base + i;

// 4.2 变量EIT 从中获取到 GetProcAddress的地址
for (DWORD dwIdx = 0; dwIdx < dwNameCount; dwIdx++)
{
// 序号表中的元素的值 对应着函数地址表的位置
if (pEIT[dwIdx] == i)
{
//根据序号获取到名称表中的名字
DWORD dwNameOffset = pENT[dwIdx];
char * pFunName = (char*)(dwAddrBase + dwNameOffset);

//判断是否是GetProcAddress函数
if (!strcmp(pFunName, "GetProcAddress"))
{
// 获取EAT的地址 并将GetProcAddress地址返回
DWORD dwFunAddrOffset = pEAT[i];
return dwAddrBase + dwFunAddrOffset;
}
}
}
}
return -1;
}

  


6、动态加载API--获取到其他用到的API
         在Stub.h中定义相关的函数和函数指针,如下:

extern bool InitializationAPI(); // 初始化各个API

// 基础API定义声明
typedef DWORD (WINAPI *LPGETPROCADDRESS)(HMODULE,LPCSTR); // GetProcAddress
typedef HMODULE (WINAPI *LPLOADLIBRARYEX)(LPCTSTR,HANDLE,DWORD); // LoadLibaryEx
extern LPGETPROCADDRESS g_funGetProcAddress;
extern LPLOADLIBRARYEX g_funLoadLibraryEx;


// 其他API定义声明
typedef VOID (WINAPI *LPEXITPROCESS)(UINT); // ExitProcess
typedef int (WINAPI *LPMESSAGEBOX)(HWND,LPCTSTR,LPCTSTR,UINT); // MessageBox
typedef HMODULE (WINAPI *LPGETMODULEHANDLE)(LPCWSTR); // GetModuleHandle
typedef BOOL (WINAPI *LPVIRTUALPROTECT)(LPVOID,SIZE_T,DWORD,PDWORD); // VirtualProtect
extern LPEXITPROCESS g_funExitProcess;
extern LPMESSAGEBOX g_funMessageBox;
extern LPGETMODULEHANDLE g_funGetModuleHandle;
extern LPVIRTUALPROTECT g_funVirtualProtect;

在Stub.cpp中实现InitializationAPI函数,并初始化要使用的API函数。由于KernelBase.dll中没有导出LoadLibrary函数,所以为了兼容性考虑,我们在加载DLL的时候使用LoadLibraryExW。
LPGETPROCADDRESS g_funGetProcAddress = nullptr;
LPLOADLIBRARYEX g_funLoadLibraryEx = nullptr;

LPEXITPROCESS g_funExitProcess = nullptr;
LPMESSAGEBOX g_funMessageBox = nullptr;
LPGETMODULEHANDLE g_funGetModuleHandle = nullptr;
LPVIRTUALPROTECT g_funVirtualProtect = nullptr;

bool InitializationAPI()
{
HMODULE hModule;

// 1. 初始化基础API 这里使用的是LoadLibraryExW
g_funGetProcAddress = (LPGETPROCADDRESS)GetGPAFunAddr();
g_funLoadLibraryEx = (LPLOADLIBRARYEX)g_funGetProcAddress((HMODULE)GetKernel32Base(), "LoadLibraryExW");

// 2. 初始化其他API
hModule = NULL;
if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false;
g_funExitProcess = (LPEXITPROCESS)g_funGetProcAddress(hModule, "ExitProcess");
hModule = NULL;
if (!(hModule = g_funLoadLibraryEx(L"user32.dll", NULL, NULL))) return false;
g_funMessageBox = (LPMESSAGEBOX)g_funGetProcAddress(hModule, "MessageBoxW");
hModule = NULL;
if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false;
g_funGetModuleHandle = (LPGETMODULEHANDLE)g_funGetProcAddress(hModule, "GetModuleHandleW");
hModule = NULL;
if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false;
g_funVirtualProtect = (LPVIRTUALPROTECT)g_funGetProcAddress(hModule, "VirtualProtect");

return true;
}

  

7、添加解密函数
         在Stub.h中定义一个加密函数

extern void Decrypt(); // 解密函数
在Stub.cpp中实现该函数
void Decrypt()
{
// 在导出的全局变量中读取需解密区域的起始于结束VA
PBYTE lpStart = g_stcParam.lpStartVA;
PBYTE lpEnd = g_stcParam.lpEndVA;

// 循环解密
while (lpStart < lpEnd)
{
*lpStart ^= 0x15;
lpStart++;
}
}

  


8、完善start函数
         我们先初始化GLOBAL_PARAM结构,并在Start函数中调用InitializationAPI()初始化所有API ,调动Decrypt()进行解密,并跳转到被加壳程序的原始OEP。

extern __declspec(dllexport) GLOBAL_PARAM g_stcParam={0};

void start()
{
// 1. 初始化所有API
if (!InitializationAPI()) return;

// 2. 解密宿主程序
Decrypt();

// 3. 跳转到OEP
__asm jmp g_stcParam.dwOEP;
}

为了退出进程时的兼容性,在StubEntryPoint中添加退出进程的兼容代码,总体如下:
void __declspec(naked) StubEntryPoint()
{
__asm sub esp, 0x50; // 抬高栈顶,提高兼容性
start(); // 执行壳的主体部分
__asm add esp, 0x50; // 平衡堆栈

// 主动调用ExitProcess函数退出进程可以解决一些兼容性问题
if (g_funExitProcess)
{
g_funExitProcess(0);
}

__asm retn;
}

  

————————————————
版权声明:本文为CSDN博主「布衣僧」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/oBuYiSeng/article/details/50456858

 

————————————————
版权声明:本文为CSDN博主「布衣僧」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/oBuYiSeng/article/details/50456807

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
壳的编写(4)-- 进行加壳操作

由于我们在编写壳的部分比较简单,那么我们在编写加壳的过程中难免要复杂些。我们要完成加壳的操作必然会要读取被加壳程序的各种信息,并保存到一个结构中,为了便于后面的操作。还有在操作上...

osc_wws45aot
2019/08/20
9
0
【黑客免杀攻防】读书笔记15 - 源码免杀、C++壳的编写

1、源码免杀 1.1 定位产生特征的源码 定位文件特征 1、根据MyCCL的特征码定位工具,定位出有特征的地址 2、根据VS的反汇编窗口,输入有特征的地址得到特征地址与源码的关系 3、插入MessageBo...

osc_1otmny9i
2018/07/09
3
0
壳的编写(1)-- 简介与搭建框架

最近学习了壳,自己手动编写了简单的壳,功能比较简单,现在简单整理成笔记,供大家参考。个人能力有限,如有谬误,请不吝赐教,万分感谢。 参考资料:《黑客免杀攻防》,看雪论坛等 简介 “...

osc_wws45aot
2019/08/20
5
0
20165306 Exp3 免杀原理与实践

Exp3 免杀原理与实践 一、实践内容概述 1.正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasion,加壳工具,使用shellcode编程 2.通过组合应用各种技术实现恶意代码免杀 3.用另...

osc_c0qub4nv
2019/03/31
3
0
使用node-webkit开发exe窗口程序

首发:个人博客,更新&纠错&回复 ====关于原生程序与壳中程序的议论begin==== 在所有用户windows机器上都能直接跑的程序,如果不采用微软系的语言,如VB,C++,C#等,而采用Java,Python,R...

祁达方
2015/11/20
345
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux系统检查用户账户到期时间

如果你在 Linux 上启用了密码策略。密码必须在到期前进行更改,并且登录到系统时会收到通知。如果你很少使用自己的帐户,那么可能由于密码过期而被锁定。在许多情况下,这可能会在无需密码登...

老孟的Linux私房菜
18分钟前
13
0
关于南京哪里有开餐饮费发票?

关于南京哪里有开餐饮费发票?聚焦餐饮行业,谈话〖18 7一電一7 5 3 8一徴一3331〗研究院昨发布数据显示,今年上半年,全国餐饮行业招聘需求增长46.18%,平均月薪6387元.随着餐饮行业的快速...

点击fojewio
51分钟前
7
0
android studio 4.0 打开DDMS

1、先找到AndroidStudio配置的SDK路径; 2、在SDK的/tools/路径下有个monitor.bat 的批处理文件; 3、鼠标连续点击两下monitor.bat这个批处理文件,在屏幕上会打开一个类似CMD的命令行中输入...

chenhongjiang
53分钟前
10
0
如何在Android中使用SharedPreferences来存储,获取和编辑值

问题: Closed . 已关闭 。 This question needs to be more focused. 这个问题需要更加集中。 It is not currently accepting answers. 它当前不接受答案。 Learn more . 了解更多 。 Want...

fyin1314
今天
6
0
【JDK1.8】LinkedList源码分析

LinkedList的特性 LinkedList内部使用双向链表作为存储结构,LinkedList可以理解为链表的扩展对象,封装了常用的和非常用的操作链表的方法。以及在通过索引获取元素时的简单优化,通常Linke...

XuePeng77
今天
36
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部