SMC

代码自修改,简而言之,就是程序中的部分代码在运行之前被加密成一段数据,不可反编译,通过程序运行后执行相关的解码功能,对加密的代码数据进行解密,让其恢复正常功能


原理

通过 SMC 代码自修改技术可以实现程序的保护,同时也可以将一些特征代码变形隐藏

  1. 代码在二进制文件中就是字节码,本身也就是一段二进制数据
  2. 提前将一部分代码通过某些方式替换为加密数据
  3. 程序在被反编译的时候,核心代码就是一串数据,无法反编译,而程序在运行的时候又能成功将这段核心代码复原

SMC 示例

以本站的《【攻防世界】BABYRE》这道题为例

  1. 可以看到程序一开始就使用了一个 182 次的 for 循环对数组 judge[] 进行异或操作,但是在后面,又以函数的形式来调用 judge()

SMC自解码1.png

  1. 跟进看到 judge 的内容

SMC自解码2.png

形似数组一样的数据,但为什么可以作为函数调用呢?

  1. 其实这就是 SMC 代码自修改

这里的 judge 本来就是一个函数,只是它的函数内容需要先经过对所有 182 个元素进行异或,异或后才能得到真正的 judge() 函数

for ( i = 0; i <= 181; ++i )
  judge[i] ^= 0xCu;
  1. 但是这种行为 IDA 是不知道的,IDA 以为异或前的 judge 只是一个普通的数组,所以识别成了 char judge[182] 的数组

而随着程序执行异或之后,judge 的内容被还原,IDA 又可以将 judge 识别成函数 *(unsigned int (__fastcall **)(char *))judge


SMC 破解

还是以本站的《【攻防世界】BABYRE》这道题为例

  1. 明白了 SMC 代码自修改的原理之后,其实破解方法比较简单

直接按照程序自己解密的逻辑来对数据进行解密即可

比如这里是对 judge 所有的数据进行异或

  1. 但是 judge 数据量比较大,所以通过 IDA 脚本来实现

shift + F2 打开脚本编辑器,输入代码:

address = 0x600B00  # judge的首地址

for i in range(182):  # 进行182次异或并修改IDA中的数据
	ida_bytes.patch_byte(address + i, idc.get_wide_byte(address + i) ^ 0xC)

print("Done")
  1. 可以看到修改前后数据的变化

脚本执行前:

攻防世界_BABYRE4.png

脚本执行后:

攻防世界_BABYRE5.png

  1. 然后在 judge 的首地址处,使用 快捷键 C 将数据生成代码

攻防世界_BABYRE6.png

接着使用 快捷键 P 将代码生成函数

攻防世界_BABYRE8.png

最后 F5 即可看到破解 SMC 后正常的 judge() 函数了

攻防世界_BABYRE9.png