SMC 自解密代码
简介
自修改代码(SMC, Self Modifying Code)是在程序执行过程中修改其自身指令的技术。通常,这种做法旨在通过减少指令路径长度来提高性能,或者为了避免重复代码,从而简化维护。自修改代码一般是有意进行的,目的是优化或保护程序的运行,而不是由于错误导致的意外修改。
在逆向工程中,自修改代码通常表现为大量的无序代码,未被执行的部分无法直接分析。通过动态修改代码或数据,程序能够在运行时自我解密,防止静态分析工具的破解。这种技术常用于动态代码加密中,旨在通过修改代码来阻止逆向分析,直到程序执行到相应时刻才会恢复正常的可执行代码,从而达到反调试、反逆向的效果。
实现与破解
SMC 的实现方式有很多种,可以通过修改 PE 文件的 Section Header、使用 API Hook 实现代码加密和解密、使用 VMProtect 等第三方加密工具等。
在 CTF 中常见的形式是首先对.text
段的代码进行加密,然后通过 SMC 进行自解密修改。
而一般程序的.text
段是没有写权限的,在进行自修改之前,程序需要修改目标内存的权限。
内存权限修改
在不同的系统中定义有不同的 API 函数来修改内存权限。
- 在 Linux 系统中,可以通过
mprotect
函数修改目标内存的权限。
mprotect
是 Linux 系统的一个 API 函数,可以改写内存权限。这个函数的原型如下:
1 |
|
第一个参数:开始地址(为了页对齐,该地址必须是一个内存页的起始地址,即页大小整数倍,1 页 = 4k = 0x1000)
第二个参数:指定长度(长度也应该是页的整倍数,即 0x1000 的整数倍)
第三个参数:指定属性(r=4(读)、w=2(写)、x=1(执行))
- 在 Windows 系统中,调用
VirtualProtect
函数实现内存权限的修改。
VirtualProtect
是 Windows 操作系统中的一个 API 函数,它允许应用程序改变一个内存页的保护属性。这个函数的原型如下:
1 |
|
第一个参数:起始地址,同样也必须对齐,即页大小整数倍,1页 = 4k = 0x1000
第二个参数:指定长度(长度也应该是页的整倍数,即 0x1000 的整数倍)
第三个参数:
PAGE_READONLY
:页面为只读。PAGE_READWRITE
:页面为可读写。PAGE_WRITECOPY
:页面为写时复制(COW)模式。PAGE_EXECUTE
:页面为可执行。PAGE_EXECUTE_READ
:页面为可执行和可读。PAGE_EXECUTE_READWRITE
:页面为可执行、可读和可写。PAGE_NOACCESS
:页面无访问权限(即不可读、不可写、不可执行)。
第四个参数:指向一个 DWORD
类型的变量,函数会把原先的保护标志存储在此变量中。如果不关心旧的保护属性,可以传入 NULL
。
因此我们也可以通过搜索程序是否使用了这两个函数来结合判断是否进行了 SMC。
实现
环境为 Linux 系统环境 gcc 编译器
下面我们通过实现简单的 SMC 代码来理解其原理。
我们编写一段 C 语言代码,在其中插入汇编代码,功能为输出字符串。
进行编译后,将func
函数部分的字节码进行xor 0xAA
加密,然后作为我们要解密的函数。
1 |
|
接下来我们编写 SMC 代码,将加密后的函数字节码作为数据插入到代码中。
加密算法为xor
,异或值是0xAA
,同样解密算法也是xor
。
首先调用内存权限修改函数给加密函数内存添加可写权限,然后再通过解密算法解密函数的代码数据。
这就实现了一段简单的 SMC 代码。
1 |
|
破解
针对这样的 SMC 我们有两种破解方法:
- 动态解密法:
动态调试目标程序,待 SMC 代码运行解密后直接使用 IDA 分析。
ida 反编译编译后的文件分析代码逻辑。
发现这跟我们写的代码逻辑大差不差。
接下来我们通过动态解密废破解这个 SMC 程序。
我们在最后一行处打一个断点,然后动态调试让程序运行到这里。
到了这里code
函数已经解密完毕,我们跟进查看。
在函数开始处按快捷键c
将数据定义为代码,然后按快捷键p
将代码定义为函数。
之后F5
反编译。
可以看到反编译成功,这就是动态调试解密法。
我们也可以使用 Dump 功能将解密后的 SMC 字节数据 Dump 出来,对密文进行静态替换,再使用 IDA 进行分析。
不过这种方法对比上一种方法或者是下面的静态解密法来说比较麻烦,这里就不细讲了。
- 静态解密法:
找到对代码或数据加密的逻辑后,根据代码逻辑编写 idapython 逆向解密脚本解密代码。
分析代码逻辑,程序对函数起始地址0x404060
到相对偏移0x35
处的代码进行异或解密,异或值为0xAA
- idapython解密脚本
1 |
|
快捷键Shift+F2
打开运行脚本代码窗口,选择 Python。
然后将代码复制进去运行即可。
之后就是定义代码定义函数,然后再进行反编译了。
同样,反编译成功。
总之这两种方法就是哪个方便用哪个。
例题
动态解密法
例题:2024 Newstar SMC
- 分析
拿到附件,程序为 Linux 下的 ELF 文件,ida 反编译分析代码逻辑。
一眼丁真,发现程序调用了mprotect
修改encrypt
函数内存权限,判断可能存在 SMC。
然后程序通过scanf
函数向变量s
中输入字符串,长度要求必须为 28 字节。
之后的代码逻辑很明显就是通过循环异或对encrypt
函数进行解密了。
这里我们对照着encrypt
函数的情况来查看一下。
很明显encrypt
函数的汇编代码都是有问题的,这就是进行过加密的函数。
接下来程序会通过上面的for
循环逻辑对每字节进行异或解密。
然后就是通过encrypt
函数判断输入是否正确了。
这里我们可以通过动态解密法来破解 SMC。
在比较输入处打一个断点,然后动态调试让程序运行起来,这样可以直接让程序执行完 SMC 代码。
运行完之后我们进入encrypt
函数查看代码。
可以看到代码仍然是乱序的。
我们首先在encrypt
函数处按快捷键u
将其取消定义,然后再按快捷键c
将其解释为代码,然后再按快捷键p
将其解释为函数。
然后即可F5
反编译,反编译后我们就可以看到encrypt
函数的代码逻辑了。
可以看到是一段非常复杂的等式,我们直接通过 z3 来进行求解。
- exp
1 |
|
静态解密法
例题:[网鼎杯 2020 青龙组]jocker
- 分析
ida 反编译分析,看到// positive sp value has been detected, the output may be wrong!
。
这是因为栈不平衡,ida 识别报错。
点击菜单的 Options-> General->Stack pointer 来打开 ida 的栈指针设置,分析栈不平衡的位置。
发现异常处,程序在调用了__Z7encryptPc
函数和__Z7finallyPc
函数后栈帧值不断减少。
按快捷键alt+k
,将两处调用函数地方的汇编指令栈帧值修改为 0。
之后再进行反编译,可以看到 ida 没有再报错了。
接下来我们分析程序逻辑。
main
函数调用了VirtualProtect
函数修改encrypt
函数的内存权限为可写。
然后调用scanf
函数向Str
变量输入字符串,如果输入长度不等于 24 则报错退出。
然后将输入的字符串复制到Destination
变量中,将Str
变量作为wrong
函数和omg
函数的参数传入处理。
最后的for
循环就是 SMC 的解密逻辑。
接下来我们跟进查看wrong
函数和omg
函数的代码逻辑。
wrong
函数是一个加密逻辑,对奇数进行偏移加密,对偶数进行异或加密。
接下来分析omg
函数。
可以看到omg
函数将输入和unk_4030c0
数据进行了按位比较,判断unk_4030c0
应该就是密文。
我们根据密文和代码逻辑,尝试写一下解密逻辑。
1 |
|
试了一下,这个 flag 是假的。
接下来继续逆向 SMC 部分。
可以看到程序对encrypt
函数进行了逐字节异或,解密encrypt
函数。
我们这里就使用静态解密法来编写 idapython 代码解密encrypt
函数。
- idapython解密脚本
根据解密encrypt
函数逻辑编写 idapython 解密脚本。
1 |
|
然后运行 idapython脚本,这里前面讲过了。
接下来我们修复解密后的encrypt
函数代码。
从函数开始处开始按快捷键u
将所有指令设置为无定义。
然后在函数开头处按快捷键c
将所有数据内容转换为代码。
最后在函数开始处按快捷键p
定义为函数。
然后就可以F5
反编译了。
继续分析代码逻辑,encrypt
函数的参数是存储输入内容的变量。
encrypt
函数将输入与Buffer
变量存储的内容进行逐字节异或,如果最后的结果不等于密文则报错。
我们跟进查看Buffer
变量的内容,可以看到是wrong
函数中输出的字符串。
我们根据代码逻辑,将密文与Buffer
变量进行逐字节异或运算后即可得出符合的输入。
根据逻辑编写解密代码。
- exp
1 |
|
只解出了前半部分,继续分析程序。
可以看到,在encrypt
函数调用成功之后继续调用了finally
函数。
跟进分析finally
函数代码逻辑。
程序中将%tp&:
复制到了v3
变量中,我们可以猜测这是后半部分的密文。
尝试用先前逻辑进行解密,结果不行。
我们知道 flag 的最后一位一定是}
,我们可以尝试将}
与密文最后一位进行异或。
然后将异或结果与密文进行异或,尝试是否可以解密。
尝试成功,得到flag。
- exp
1 |
|
后言
参考:smc加密研究 - V1ct0r的博客 (gdufs-king.github.io)
参考:SMC自解码总结-安全客 - 安全资讯平台 (anquanke.com)
参考:探究SMC局部代码加密技术以及在CTF中的运用 - SecPulse.COM | 安全脉搏