查看原文
其他

API监控+代码乱序的壳

KitTraumen 看雪学院 2019-05-25


1. 前言


在论坛一直混迹也不短时间了,看了很多人的技术文章,总想着自己有一天也能写些东西。遂把这几个月做的一款壳分享一下,若能指点一二,小生感激不尽。因为本人写壳经验不长,其中参考了论坛上不少人的文章,前期感觉发出来有点见不得人哈,后来想想总有人需要的,这里算是做一个整合和优化,并且提出了一些新的保护思路。



2. 特点


① 本系统提供了加壳 和 API 监控。

② 加壳模块的基本功能包括压缩引擎(aplib、JCALG1)、IAT转储、HOOT-IAT、重定位转储。

③ 加壳模块的附加功能包括反调试、反dump、反OD、混淆函数和校验(内存校验和文件校验)。

④ API监控模块可以记录选中的DLL的API调用信息(调用地址、调用模块、API名称和调用次数)和API 调用参数信息。

⑤ 新的思路:利用API的调用频率作为触发概率的参考条件,代码混淆模块对频繁调用的API有更大的概率实现代码混淆,反之亦然。

⑥ 利用已有的花指令模板来实现代码混淆。

⑦ 提供了针对普通用户的三套方案(性能、安全和均衡)。

⑧ 可以对EXE和DLL进行加壳。



3. 效果

对部分程序进行测试,加壳的成功率和兼容性表现良好。结合了API监控的代码混淆属于测试功能,只能对特定程序进行测试,当前的意义仅仅为提供了一个新的保护思路。下面给出系统的界面效果。




 




4. 正文

接下来就该系统各个部分的功能做一个粗略但求精简的讲解。第一部分讨论加壳中各模块细节,第二部分讲述 API 监控 以及 第三部分为代码混淆。


4.1 加壳模块

该小节主要讲述IAT转储、重定位表转储、HOOK-IAT 和 压缩引擎以及压缩过程。


4.1.1 IAT转储

IAT转储是将导入表实现一个结构的简化并存储到指定被保护的区域。此处直接使用了《加密与解密》第三版提供的转储结构。结构如下图。


转储代码的细节提供在项目文件中,有需要的读者可自行阅览。


4.1.2 重定位表转储

重定位表亦使用但是改进了第三版中提供的结构。因为在原版的结构中,只用了1个byte去存储偏移量,但是在实际当中,某些重定位项之间的偏移会大于0xff。由于内存对齐长度为0x1000,而每一重定位项之间的偏移小于0xfff,所以在一份内存页当中2个byte适合用于转储。值得注意的,在测试某些样本中,两个区段的重定位项的偏移可能会大于0xfff,所以每一个内存页都要保存相同的转储结构。


最后,保存的形式如下图。这样就可以清晰地区分每一个需要重定位的内存页了。除此之外,在进行修复每一页时,都要使用VirtualProtect修改内存中该页的属性,使其变为可写,否则会触发中断崩溃。


4.1.3 压缩引擎

压缩后的样本区段结构和upx是一样的。本系统使用了比较常见的压缩库,包括aplib和JCALG1,前者效率均衡,对PE文件的处理较好,后者对含有较多资源的程序表现更佳。

下面给出两个压缩库的使用过程。值得一提,若是遇到TLS表的话,理论上可以压缩,但是只能在TLS表之后压缩,这种压缩策略不仅使操作复杂化,而且效率也不高,故而本系统对含有TLS的程序不进行压缩处理。(注:发现JCALG1不能压缩过大的文件,问题还未解决)

下面给出压缩与解压后的区段结构。pack0是占位区段,用于存放解压后的数据;pack1保存着压缩区段后的数据;pack2保存着外壳代码。


rsrc保存着转储后的小部分关键资源(例如Icons、Dialogs和Group Icons)。


4.1.4 其他相关保护技术.文件校验

计算
对程序文件的数据进行CRC32的计算,计算的值保存到PE标识的前4个byte中。

VOID COperationPE::CalAndSaveCRC(DWORD dwFileSize)
{
DWORD dwCrc32; //计算的值

//1. 生成CRC32表格
if(m_bCRC32Table == FALSE)
MakeCRC32Table();

//2. 计算PE头之后的数据
dwCrc32 = CalcuCRC((UCHAR*)(m_pDosHeader->e_lfanew + m_dwFileDataAddr), dwFileSize - m_pDosHeader->e_lfanew);

//3. 将该CRC32值写进PE头标识前4个字节
*(PDWORD)((DWORD)m_pNtHeader - 4) = dwCrc32;

}


内存校验 

对未压缩的和加密的代码段进行CRC32的计算,如果存在重定位表,则不进行计算,因为重定位修复时会修正全局变量的绝对地址,导致前后计算的数值不一致,从而校验失败。


BOOL CodeMemVerification()
{
DWORD dwCodeBase;
DWORD dwCodeSize;
DWORD dwCRC32;


dwCodeBase = g_stcShellData.dwCodeBase;
dwCodeSize = g_stcShellData.dwCodeSize;

dwCRC32 = CalcuCRC( (UCHAR*) (g_dwImageBase + dwCodeBase), dwCodeSize);

//如果有重定位修复,则不进行验证,因为修复全局变量会改变代码段,所以CRC32的计算会出错
if(g_stcShellData.stcPERelocDir.VirtualAddress == 0)
// if(g_stcShellData.stcIATDir.VirtualAddress == 0)
if (dwCRC32 != g_stcShellData.dwCodeMemCRC32) return FALSE;

return TRUE;

}


③ 反Dump

这是一个非常常见的保护手段,通过修改PEB结构来使某些工具无法获取到加壳程序的进程信息。特别是使用了某些直接获取PEB结构信息的Windows API的工具。借鉴网上通用的某些代码,但是发现PEB中有些信息的删除会导致程序异常,故而将其精简后得到最终代码,效果如下图。



可见原来进程的信息被替换了ntdll.dll,而且进程模块的基本信息(基址和大小)都被抹去。


④ 混淆函数

相当于一个花指令黑盒,把运行地址作为参数传入该函数,最后在花指令结束后便跳转回目标代码处执行。


⑤ 反OD

原理很简单,由于用OD载入程序时,内存中会产生某些特征字符串,故而可以通过搜索这些信息来进行判读自身是否被OD载入调试。

由于在x86程序的虚拟内存中,有2G只分配给用户空间的,所以只需要对该区域进行扫描,经过测试,算法的时间复杂度对壳的性能影响相当大,所以选择优秀的算法非常重要。在本系统中,选择KMP做为字符串匹配算法。


⑥ 反调试

简单的方法有很多,可以通过调用Windows API来查看,也可以自己去PEB结构里面查,本系统采用后者,这种保护手段早已经过时,在这里只是做为一个壳保护功能的完善罢了。


4.2 API监控

这里直接参考SoftSnoop的代码,并且对其的通信模式与hook方法进行修改。

本系统直接使用detour库进行inline hook,这个库的健壮性不是盖的,毕竟是微软自己人做的东西。其次,除了商用软件,发现目前开源的抑或网友制作的工具,稳定性都不太好,故而就萌生一份将其优化与增加适用性的想法。最后,这个模块主要是为代码乱序提供一个API调用频率的信息。


首先给出该模块与SoftSnoop区别的细节:


1) 通信模式改变,由于本系统是用Qt去事先界面,所以监控dll与UI之间的通信不能在使用原来的消息机制,而是改成了命名管道。


2) hook方法更变,原来是对某个模块所有的api进行hook,后来测试发现,有些api不可以hook,原因是Windows中有些涉及到窗口的API是回调函数,如果对这些API进行hook,那也会导致一个不可想象的调用膨胀,以至于崩溃。所以,本系统改变策略,只对提前约定好的API进行hook并解析,这样既可以获取关键的信息,也可以提高程序性能。


4.2.1 流程与原理 

下面给出遍历进程模块的一个流程。


接下来给出命名管道的通信模式。指令的功能包括,遍历某个模块的api名称,其次还可以对某个规定好的api进行hook。



4.2.2 监控日志

接下来就是api监控结束后,除了会生成一份简陋的txt格式的日志文件,如下图。




还会生成一个给代码乱序引擎使用的一个日志。日志包含约定记录的api与其调用次数,而它的存储格式如下。 


用二进制工具查看。




4.2.3 添加规则

添加的规则方式如 SoftSnoop提供的规则文件是一样的。当初因为作这款作品是有一些目的,所以在这一块就没有比较完善,其实我当初的设想是把规则做成一个可视化,更方便使用。这里给出一个kernel32模块约定监控的api的例子。




4.3 代码乱序引擎

这里首先要感谢玩命提供的代码乱序分析框架,这个模块的本意是想实现一个代码虚拟化,但是由于能力不足,时间不够,百忙之中没有办法实现,故而先通过开发一个代码乱序来测试结合api监控的效果。


4.3.1 总体设计

反汇编引擎 - udis86

链式存储指令信息 - 记录每一个需要修改的指令的信息

这里直接使用了玩命提供的结构体,开始觉得信息很多,写到后面觉得这些信息都很重要。


typedef struct _Code_Flow_Node
{
struct _Code_Flow_Node *pNext; //下一个节点
BOOL bGoDown; //是否向下跳
DWORD dwBits; //跳转范围
DWORD dwType; //指令类型
BOOL bFar; //是否是远跳
DWORD dwMemoryAddress; //当前内存地址
LPBYTE pFileAddress; //当前文件地址
DWORD dwGotoMemoryAddress; //跳转后的内存地址
LPBYTE pGotoFileAddress; //跳转后的文件地址
DWORD dwInsLen; //指令长度
pImport_Node pImpNode; //在IAT中的节点信息
DWORD dwFunIndex; //节点函数表的索引
DWORD dwFinalMemoryAddress; //花指令的内存地址
DWORD dwFinalFileAddress; //花指令的文件地址
BOOL bConfused; //是否乱序
union
{
BYTE bOffset;
WORD wOffset;
DWORD dwOffset;
};//偏移
}Code_Flow_Node, *pCode_Flow_Node;


花指令模板

这里仅仅提供了2套花指令模板作为测试,当初想寻找一个自动花指令的生成代码,但是测试之后发现效果都不好,遂放弃。乱序后的指令会跳转到一个充满花指令的区域执行。


导入表-函数检测

众所周知,静态链接直接将功能代码放入代码段,则调用与跳转一般为短跳转,就会与普通的跳转调用指令产生混淆,无法分辨出这是否在调用一个函数。故而这里设计的理念就是找出调用动态链接库的函数的跳转,然后将其记录下来。


涉及的原理:

  • Windows PE导入表的结构

  • 编译器如何实现对动态链接库函数的调用

  • 反汇编引擎及IA-32架构的OPCODE


由下图可知,若某主调函数想调用动态链接库的函数,则一般在实际流程中先Call进入到指定的远跳转区域。再由远跳转FF25(JMP)到DLL的代码中,或直接FF15(CALL)。而FF15(JMP)指令会读取IAT中的数据作为绝对地址去跳转。

可知,反汇编引擎的工作就是找到FF15(JMP)。所以,整个分析由几步构成。

第一、解析到Call,记录下来;

第二、检测Call到的下一个地址是否是远跳转指令FF15;

第三、若是的话,提取出该偏移量,匹配IAT,获得被调用的函数名与相应的动态库名。


⑤ 调用样本数据与随机概率 - Api调用次数

由于我们并不希望每个跳转都被乱序化,这样不仅保护效果没有针对性,还消耗了大量空间去存放花指令。所以根据这个想法,调用次数占总次数的比例越大,则被乱序化的可能性就越高。例如下表。


 API

 Count

 CreateFile

 x1

 VirtualAlloc

 x2

 .......

 xn


计算出每个API的调用概率:




随机概率处理的情况:

  • 无函数识别及调用样本数据 -->       1/4

  • 该函数未产生调用信息 -->    1/3

  • 该函数产生调用信息 -->  1/3 + Pxi


区段信息及结构


花指令平均长度估计区段大小


由于存放花指令的区段是需要事先计算的,否则就导致空间不够使用的情况。





值得注意,有些程序的调用次数相当巨大,会产生大量的结点信息,故而,这里对100个以上的结点数,直接视为100个。这100平均长度的空间用完则不再处理后序的指令了。


4.3.2 测试效果

这里用汇编写了一个switch - case ,循环次数随机地,在每个case中调用一个Windows API的程序。下图是switch - case 的结构。




下图是代码乱序前的部分情况:




下图是代码乱序后的情况:





5. 参考资料

[1] SoftSnoop 1.3.2 + Source(增加了中文版和说明文档) -  https://bbs.pediy.com/thread-55974.htm

[2] 软件保护壳技术专题 - 反汇编引擎的构建 -  https://bbs.pediy.com/thread-74414.htm

[3] The Svin 的OpCode教程(22楼提供doc和pdf版本下载) -  https://bbs.pediy.com/thread-66501.htm

[4] 软件保护壳专题 - 代码乱序引擎的构建 -  https://bbs.pediy.com/thread-96640.htm

[5] 学写压缩壳心得系列之四 实践流程,云开见日 -  https://bbs.pediy.com/thread-146895.htm

[6] 用C++实现的壳(扩展版) -  https://bbs.pediy.com/thread-206873.htm

[7] Writing Your Own Packer - by BigBoote -  https://bbs.pediy.com/thread-4865.htm

[8] 打造自己的反汇编引擎——Intel指令编码学习报告(一) -  https://bbs.pediy.com/thread-75094.htm

[9] 函数hook注意事项 -  https://bbs.pediy.com/thread-159355.htm

[10] 加密与解密(第三版)

[11] 软件保护与分析技术 原理与实践



完整程序

链接:https://pan.baidu.com/s/1-jZBzPMKaIcZkcHhrXvGLQ  
密码:17bc




- End -




看雪ID:KitTraumen           

https://bbs.pediy.com/user-782825.htm



本文由看雪论坛 KitTraumen 原创

转载请注明来自看雪社区



热门图书推荐

 立即购买!




热门文章阅读

1、Tcache利用总结

2、SandHook 第四弹 | Android Q 支持 & Inline 的特别处理

3、看雪课程 | 如何高级地对抗恶意程序的糖衣炮弹?





公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



↙点击下方“阅读原文”,查看更多干货!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存