smc总结
SMC(Software-Based Memory Encryption)是一种局部代码加密技术,它可以提高恶意代码的抗分析能力,但也存在一些局限性。作为一名CTF比赛的爱好者,虽然经常接触SMC加密技术,但是对其原理和实现并不深入了解,只是停留在解题的层面。最近,在为新生出题的过程中,产生了将SMC加密技术应用在出题上的想法,于是决定深入探究这门技术的原理、实现和应用。本文旨在分享作者学习SMC加密技术的心得,以及介绍如何利用SMC加密技术设计出有趣和有挑战性的CTF题目
本文的结构如下:第一部分介绍SMC加密技术的基本概念和原理;第二部分介绍SMC加密技术的实现方法和工具;第三部分介绍SMC加密技术在CTF出题中的应用和案例
SMC加密技术的基本概念和原理
SMC(Software-Based Memory Encryption)是一种局部代码加密技术,它可以将一个可执行文件的指定区段进行加密,使得黑客无法直接分析区段内的代码,从而增加恶意代码分析难度和降低恶意攻击成功的可能性。
SMC的基本原理是在编译可执行文件时,将需要加密的代码区段(例如函数、代码块等)单独编译成一个section(段),并将其标记为可读、可写、不可执行(readable, writable, non-executable),然后通过某种方式在程序运行时将这个section解密为可执行代码,并将其标记为可读、可执行、不可写(readable, executable, non-writable)。这样,攻击者就无法在内存中找到加密的代码,从而无法直接执行或修改加密的代码。
具体介绍见原文:https://zhuanlan.zhihu.com/p/624554464
SMC加密技术的实现方法
PS:为了方便,部分示例代码省去了对字节码加密和解密的操作,请注意辨别
1.数组保存字节码->调整内存执行权限->函数指针运行
先提取加密段代码的字节码
写入加密段代码,通过调试工具获取加密代码对应的字节码,例如:
1 | byte addData[] = {0x55,0x89,0xe5,0x8b,0x55,0x8,0x8b,0x45,0xc,0x1,0xd0,0x5d,0xc3}; |
通过VirtualProtect函数调整内存为可执行的,定义函数指针,指向addData,调用函数指针
BOOL VirtualProtect(
[in] LPVOID lpAddress, // 要更改保护属性的页区域的起始地址
[in] SIZE_T dwSize, // 要更改保护属性的区域的大小(以字节为单位)
[in] DWORD flNewProtect,// 新的保护属性值
[out] PDWORD lpflOldProtect // 用于接收原来保护属性值的指针
);
第三个参数flNewProtect包括但不限于:
PAGE_EXECUTE 可执行
PAGE_EXECUTE_READ 可读可执行
PAGE_EXECUTE_READWRITE 可读可写可执行
最简单的一种实现方式,以add函数为例
1 |
|
运行成功,在IDA中查看效果
(未加密的)函数如期的到了data段
通过这种方式即可实现大部分的加密算法,例如tea,base64,rc4等
但是对于一些复杂的逻辑加密,可能需要引入外界的库函数,比如说CTF中简单的输入输出提示,需要用到printf,scanf等库函数
举例如下,当add中嵌套了printf时
1 |
|
正常生成但是运行失败,汇编窗口动调发现在call指令时发生了报错
分析可知,call指令后跟的是printf的相对地址,在转换到data段之后相对地址发生改变,访问到了非printf地址
第一种解决方法:可以定义函数指针,在加密函数内通过函数指针来利用库函数
如下:
1 |
|
IDA中伪代码:
从这个例子可以看出,这种传参方式很容易暴露自己的意图,此地无银三百两的感觉,并且会出现调用多个库函数时代码可读性差的问题
为了优化隐蔽性和可读性,我们可以利用kernel32.dll模块来获取LoadLibraryA和GetProcAddress函数的地址,进而动态加载我们需要的模块和函数。
具体的实现步骤在Get_Func函数中有详细说明。
下面是修改后的程序,可正常运行。
1 | //mytool.h |
1 |
这里就是通过Get_Func来获取GetProcAddress函数地址和LoadLibraryA函数地址
从而在加密函数内可以调用这两个函数来获取其他库函数地址
Get_Func函数具体实现方式大致是(大佬略过~~):
通过FS段寄存器获取TEB的地址,然后通过TEB的ProcessEnvironmentBlock成员(偏移量为0x30)获取PEB的地址,再通过PEB结构体获取其成员Ldr(加载模块列表)(偏移量为0xc),通过Ldr结构体访问InInitializationOrderModuleList成员(导入模块列表)(偏移量0x1c),InInitializationOrderModuleList的第二个成员即为kernel32.dll模块,我们想要的GetProcAddress函数地址和LoadLibraryA函数地址都在kernel32.dll里面了,通过了解PE文件结构访问其导出表即可
好了,转成字节码试一下效果
原函数转成字节码之后代码如下:
1 |
|
运行成功,输出7
在IDA中的伪代码窗口如下:
隐藏性有所提升,但是还可以进一步提升
我们可以把Get_Func函数也通过SMC加密,从而进一步提高代码的隐藏性
1 |
|
实现效果:
2.新建可执行的节段存储加密函数
Linux下把加密代码放到数据段:
1 |
|