什么是SMC

SMC,即Self Modifying Code,动态代码加密技术,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果。

(上面这一部分是从别人的博客里抄的)

简单来说,就是在程序内部对代码进行改变,一般是直接对字节码进行操作。

SMC的一些特征

SMC既可以修改代码,也可以修改数据,一般来说,数据段是可读可写的,但是代码段是只读的,因此如果想对代码段的字节码进行修改,肯定改变代码段的属性,使其变成可读可写的。

1. Windows 系统下

Windows 程序中使用了VirtualProtect()函数来改变虚拟内存区域的属性。

BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
/*
VirtualProtect()函数有4个参数,
lpAddress是要改变属性的内存起始地址,
dwSize是要改变属性的内存区域大小,
flAllocationType是内存新的属性类型,
lpflOldProtect内存原始属性类型保存地址。*/

一般会将第三个参数设置成0x40。

2. Linux 系统下

Linux 程序中使用mprotect()函数来改变虚拟内存区域的属性。

#include <sys/mman.h>
int `mprotect`(void *addr, size_t len, int prot);
/*mprotect()系统调用修改起始位置为addr,长度为length字节的虚拟内存区域中分页上的保护。
addr取值必须为分页大小的整数倍,
length会被向上舍入到系统分页大小的下一个整数倍。
prot参数是一个位掩码。*/

修复正常的代码以静态分析

1.使用OD、x96dbg dump下来

优点:简单,容易操作

缺点:dump后的程序可能不能运行

但是仍然可以拖进IDA中进行静态分析

操作步骤:

  1. 首先运行程序至运行完解密函数。
  2. 之后就dump下来,OD的操作方法是右键选择Ollydump,注意把OEP的位置改为原来的OEP,并且选择重建输入表(不过这一点可能没有必要了,我们目的不是为了让dump后的程序还能运行,只是为了查看解密后的伪代码)。x96dbg也类似,点击菜单栏的“插件”,然后点击“Scylla”,先检查OEP是否正确,如果不正确需要手动更改,然后点击“IAT Autosearch”,IAT自动搜索到之后,再点击”Dump”,注意这时候要保存一个文件,记住这个文件的名字,一般是源文件名_dump.exe 保存完之后,再点击“Get Imports”,再点击”Fix Dump”,这时候要选择更改dump之后的文件,并生成一个新的文件,这时候就已经完成了。
  3. X96操作请看下面这两个链接:
  4. https://www.52pojie.cn/thread-1534675-1-1.html

    https://www.52pojie.cn/forum.php?mod=viewthread&tid=1742334&highlight=%BA%EC%B0%FC

  5. 这样会重新生成一个exe文件,这个文件就可以拖入IDA中分析了。

2.使用IDA的动态调试dump下来

与第一点的操作类似

但是有些复杂的地方,经过解密代码之后,,字节码是正确的,但是IDA很有可能无法分析,我们需要手动调整使得IDA分析产生伪代码

操作步骤:

  1. 解密完成后,先在那一串刚刚解密后的字节码往下找,找到第一个retn指令(原则,找到main函数块,retn指令就是main函数的结束位置)

  2. 然后选中从main函数头一直到retn范围的字节码,右键选择分析”分析选中的区域“,然后点击Force。这里需要注意的一点是,IDA可能询问是否按照EIP进行分析(类似的话),这里要选择否。

  3. 捣鼓完后按下F5,如果仍然无法生成伪代码,那就自己编辑函数范围。首先找到retn指令的下一个地址,记下来。然后在main函数头的右键点击“编辑函数”,然后在函数的结束位置填入刚刚找到的地址就可以了。应当注意的是,这个范围是左闭右开的,这也就是为什么我刚才说要找到retn指令的下一个地址。捣鼓完后就可以按下F5查看伪代码了。

3.使用IDApython将代码进行修改

基本使用操作与我之前的文章中花指令那一节是一样的。

这个写一个简单的脚本模板以供参考:

saddr=0x401000  #开始位置
eaddr=0x404000 #结束位置
for i in range(saddr,eaddr):
k=get_wide_byte(i)
patch_byte(i,k^0x99)

关键的两个函数就是get_wide_byte和patch_byte

SMC派生问题(我没有找到这类问题的官方叫法)

一般来说,SMC只是程序自身代码的修改,而不是重新生成一个二进制文件,但是,毫无疑问,重新生成一个二进制文件,然后将这个文件加载进一个新的进程,这种办法是可行的。

这样的话,我们使用IDA进行解密的时候就必须将其dump下来,生成一个新的文件。

这里介绍一个Windows下的API函数,当然不能单纯的将这个函数看做SMC派生问题的标志、

FindResourceA函数

确定具有指定模块中指定类型和名称的资源的位置。

HRSRC FindResourceA(
[in, optional] HMODULE hModule,
[in] LPCSTR lpName,
[in] LPCSTR lpType
);

简单来说,这个函数是可以用来找到需要解密的资源的位置。

我们可以使用Resource Hacker 这个软件来查找这个资源模块。

IDApython dump 脚本

在这个资源文件解密完成后,我们可以用IDApython 将其dump下来。

这里简单提一下PE文件格式,开头一定是“MZ”这个样子,只要找到了这样的标志,说明解密成功了,已经可以开始dump了!

脚本模板:(从一个大佬博客那里抄的)

def main():
begin = 0x17C6A86B940; #需对应修改
size = 0x16000 # #需对应修改
list1 = []
for i in range(size):
byte_tmp = get_bytes(begin + i,1)
list1.append(ord(byte_tmp))
if (i + 1) % 0x1000 == 0:
print("All count:{}, collect current:{}, has finish {}".format(hex(size), hex(i + 1), float(i + 1) / size))
print('collect over')
file = "C:\\Users\\DELL\\Desktop\\dump.exe" #需对应修改
#print(bytearray(list1))
buf = bytearray(list1)
with open(file, 'wb') as fw:
fw.write(buf)
print('write over')

if __name__=='__main__':
main()