首页
论坛
专栏
课程

[原创]API监控+代码乱序的壳

2019-3-30 00:30 6584

[原创]API监控+代码乱序的壳

2019-3-30 00:30
6584

1.前言

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

2.特点

1.本系统提供了加壳 和 API 监控
2.加壳模块的基本功能包括压缩引擎(aplib、JCALG1)、IAT转储、HOOT-IAT、重定位转储
3.加壳模块的附加功能包括反调试、反dump、反OD、混淆函数和校验(内存校验和文件校验)
4.API监控模块可以记录选中的DLL的API调用信息(调用地址、调用模块、API名称和调用次数)和API 调用参数信息
5.新的思路:利用API的调用频率作为触发概率的参考条件,代码混淆模块对频繁调用的API有更大的概率实现代码混淆,反之亦然
6.利用已有的花指令模板来实现代码混淆
7.提供了针对普通用户的三套方案(性能、安全和均衡)
8.可以对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 其他相关保护技术

1.文件校验
对程序文件的数据进行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;

}

2.内存校验 
对未压缩的和加密的代码段进行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;

}

3.反Dump
这是一个非常常见的保护手段,通过修改PEB结构来使某些工具无法获取到加壳程序的进程信息。特别是使用了某些直接获取PEB结构信息的Windows API的工具。借鉴网上通用的某些代码,但是发现PEB中有些信息的删除会导致程序异常,故而将其精简后得到最终代码,效果如下图。
可见原来进程的信息被替换了ntdll.dll,而且进程模块的基本信息(基址和大小)都被抹去。

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

5.反OD
原理很简单,由于用OD载入程序时,内存中会产生某些特征字符串,故而可以通过搜索这些信息来进行判读自身是否被OD载入调试。
由于在x86程序的虚拟内存中,有2G只分配给用户空间的,所以只需要对该区域进行扫描,经过测试,算法的时间复杂度对壳的性能影响相当大,所以选择优秀的算法非常重要。在本系统中,选择KMP做为字符串匹配算法。

6.反调试
简单的方法有很多,可以通过调用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 总体设计

1.反汇编引擎 - udis86
2.链式存储指令信息 - 记录每一个需要修改的指令的信息
这里直接使用了玩命提供的结构体,开始觉得信息很多,写到后面觉得这些信息都很重要。
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;

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

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

涉及的原理:
  • Windows PE导入表的结构
  • 编译器如何实现对动态链接库函数的调用
  • 反汇编引擎及IA-32架构的OPCODE
由下图可知,若某主调函数想调用动态链接库的函数,则一般在实际流程中先Call进入到指定的远跳转区域。再由远跳转FF25(JMP)到DLL的代码中,或直接FF15(CALL)。而FF15(JMP)指令会读取IAT中的数据作为绝对地址去跳转。

可知,反汇编引擎的工作就是找到FF15(JMP)。所以,整个分析由几步构成。
第一,解析到Call,记录下来;
第二,检测Call到的下一个地址是否是远跳转指令FF15;
第三,若是的话,提取出该偏移量,匹配IAT,获得被调用的函数名与相应的动态库名。

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

 API Count
 CreateFile x1
 VirtualAlloc x2
 ....... xn

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


随机概率处理的情况:
  • 无函数识别及调用样本数据 -->       1/4
  • 该函数未产生调用信息 -->    1/3
  • 该函数产生调用信息 -->  1/3 + Pxi
6.区段信息及结构

7.花指令平均长度估计区段大小
由于存放花指令的区段是需要事先计算的,否则就导致空间不够使用的情况。




值得注意,有些程序的调用次数相当巨大,会产生大量的结点信息,故而,这里对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


[公告]LV6级以上的看雪会员可以免费获得《2019安全开发者峰会》门票一张!!

最后于 2019-3-31 21:10 被KitTraumen编辑 ,原因:
上传的附件:
打赏 + 3.00
打赏次数 3 金额 + 3.00
收起 
赞赏  minczsys   +1.00 2019/05/05 感谢分享~
赞赏  毕达哥拉斯   +1.00 2019/04/17
赞赏  余生挚爱传奇   +1.00 2019/04/16 感谢分享~
最新回复 (39)
VicZ 1 2019-3-30 00:39
2
0
感谢大佬分享
Czhiqiang 2019-3-30 00:52
3
0
感谢分享~
ncsi 2019-3-30 03:49
4
0
感谢分享
chixiaojie 2019-3-30 04:48
5
0
很高级不懂,只能膜拜。
最后于 2019-3-30 04:49 被chixiaojie编辑 ,原因:
xiaohang 3 2019-3-31 11:16
6
0
这篇绝对值得一个精华
不知世事 1 2019-3-31 13:01
7
0
有理有据,思路逻辑清晰,值得收藏
bambooqj 2019-3-31 13:04
8
0
牛逼了....
只想睡个好觉 2019-3-31 13:33
9
0
借LZ源码来学习下 
jet_coder 2019-3-31 14:40
10
0
good job!
kongfubull 2019-3-31 15:25
11
0
mark
开花的水管 2019-3-31 18:33
12
0
厉害!mark
KitTraumen 1 2019-3-31 21:03
13
0
谢谢大家的认可
marcoxuu 2019-3-31 23:44
14
0
保镖NB!过来顶帖学习一下!
莫灰灰 8 2019-4-1 10:51
15
0
牛逼,学习了。
lzgking 2019-4-1 13:55
16
0
大神出手就是精华
demoscene 7 2019-4-1 16:09
17
0
牛逼,学习了。
人在塔在 2019-4-1 18:36
18
0
真的牛逼 谢谢
niuzuoquan 2019-4-1 22:29
19
0
感谢分享,niu
Hades一KXXY 2019-4-2 09:20
20
0
想法不错,不适用啊,现在市面上的壳级别都是VM级别的了,不带VM的壳现在看来意义不大,单纯的练手项目。
Tennn 5 2019-4-2 12:30
21
0
修改llvm把 一键启动跨平台
ycmint 5 2019-4-2 13:56
22
0
牛逼 ,看不懂了啊
严启真 2019-4-2 15:57
23
0
厉害了,大神
Editor 2019-4-3 09:36
24
0
感谢分享~
yber 2019-4-8 18:42
25
0
膜拜了
cjkillyes 2019-4-9 01:01
26
0
感谢分享~膜拜了
Justgoon 1 2019-4-10 09:20
27
0
感谢分享
skywhite 2019-4-10 09:35
28
0
这个厉害了。
蛮荒故事 2019-4-12 18:30
29
0
感谢分享。
来啦老弟 2019-4-16 12:24
30
0
这个壳不错,起码比tmd都6
网络游侠 2019-4-16 12:45
31
0
不看代码,直接调壳,反OD的强度弱爆了,IAT部分隐藏做的还凑合,其他的代码虚拟好像指令变换也很弱,不错的game tool! 
wzcgood 2019-4-17 22:10
32
0
厉害
CharlesXYZ 2019-4-18 02:17
33
0
感谢分享 mark 
CHENEASON 2019-4-29 17:41
34
0
请问udis86怎么使用的?试着用了一下,VS项目属性添加了udis86.lib,还是提示无法打开。如果在连接器常规中添加附加库目录,就会提示库计算机类型“X86”与目标计算机类型“x64”冲突
KitTraumen 1 2019-5-4 21:01
35
0
对的,本来是想结合VM搞一搞,但是发现工程量太大,下个阶段考虑一下
最后于 2019-5-4 21:09 被KitTraumen编辑 ,原因:
KitTraumen 1 2019-5-4 21:20
36
0
CHENEASON 请问udis86怎么使用的?试着用了一下,VS项目属性添加了udis86.lib,还是提示无法打开。如果在连接器常规中添加附加库目录,就会提示库计算机类型“X86”与目标计算机类型“x64”冲突
把程序和dll都统一到同一个版本即可
KitTraumen 1 2019-5-4 21:20
37
0
哈哈,没错,这些功能可以忽略,只是提供一个idea
CHENEASON 2019-5-5 11:33
38
0
KitTraumen 把程序和dll都统一到同一个版本即可
问的那天晚上自己研究了下发现问题所在了~~谢谢大佬提供的思路,帮大忙了
sunsjw 1 2019-5-5 13:00
39
0
为什么你这么优秀?
Oday小斯 2019-6-26 22:27
40
0
谢谢分享
游客
登录 | 注册 方可回帖
返回