SMC in CTF
方法
SMC一般有两种破解方法
第一种是找到对代码或数据加密的函数后通过idapython写解密脚本。
第二种是动态调试到SMC解密结束的地方dump出来。
SMC的实现是需要对目标内存进行修改的,.text一般是没有写权限的。那么就需要拥有修改目标内存的权限:
-
在linux系统中,可以通过mprotect函数修改目标内存的权限
-
在Windows系统中,VirtualProtect函数实现内存权限的修改
因此也可以观察是否有这俩个函数来判断是否进行了SMC。
这里主要使用动态调试这一方法进行分析
实操
[网鼎杯 2020 青龙组]jocker
进入main函数
可以直接找到被smc的函数 VirtualProtect
VitualProtect函数,这里对内存权限进行了修改,大概率是SMC
分析代码,通过scanf让我们输入一个长度为24的flag,又经过wrong函数和omg函数加密
打开encrypt函数时出错
因为在前面VirtualProtect函数包含了encrypt函数,所以显然在encrypt处进行了SMC加密
动态调试
在这里下断点
F7进入这个call
点击__Z7encryptPc 先按U将其设为未定义
接着选中下面所有数据内容,按C转换为代码,选强制
点击函数按p构造函数
按F5即可反编译
同理,对__Z7finallyPc操作
接下来直接分析伪代码解密即可
buffer = 'hahahaha_do_you_find_me?'
v3 = '%tp&:'
v2 = [14, 13, 9, 6, 19, 5, 88, 86, 62, 6, 12, 60, 31, 87, 20, 107, 87, 89, 13]
for i in range(len(v2)):
a1=v2[i]^ord(buffer[i])
print(chr(a1),end='')
for j in range(5):
a2 = ord(v3[j]) ^ ord(':') ^ ord('}')
print(chr(a2),end='')
# flag{d07abccf8a410cb37a}
idapython
start = 0x401500
for i in range(187):
a = idc.get_wide_byte(start + i)
ida_bytes.patch_byte(start + i,a ^ 0x55 ^ 0x14)
[H&NCTF2024]I_LOVE_SWDD
进入main函数
进入sub_411186函数时,发现无法识别,只有一堆数据
可以猜测是SMC
再看导入表
这下更确定了
动态调试
在这里下断点
点击loc_417000,按u转未定义,再按c分析函数,再按p构造函数
完美
F5反编译成功
之后就是简单的凯撒密码,直接解密即可
str2 = 'S_VYFO_CGNN_GRKD_KLYED_IYE'
str1 = ''
for i in str2:
if 'A' < i < 'Z':
str1 += chr((ord(i) - 10 - 65) % 26 + 65)
else:
str1 += i
print(str1)
# I_LOVE_SWDD_WHAT_ABOUT_YOU
[羊城杯 2021]BabySmc
进入main函数
这里发现无法分析,可以猜测是SMC,但是无法确定
查看导入表,发现VirtualProtect函数,可以确定了
动态调试
在这里下断点动调
F8步过,进入数据处,这里会提示是否让IDA自己根据RIP生成代码,选否,因为这里IDA已经不能自主分辨主函数的结构了。
否了后,从上方的main头一直选到第一个retn处,按C后选择Force强制转换为代码,之后直接F5反编译就好了。
这里开始
这里结束(return)
然后按F5反编译即可
idapython
脚本一
start = 0x140001085
end = 0x140001D00
import idc
import ida_bytes
def change_byte(uint8):
return (((uint8 & 7) << 5) | (uint8 >> 3)) ^ 0x5a
size = end - start + 1
b = idc.get_bytes(start, size)
b2 = bytes([change_byte(x) for x in b])
ida_bytes.patch_bytes(start, b2)
脚本二
from idc_bc695 import *
def ROR(i,index):
tmp bin(i)[2:].rjust(8,"0")
for _ in range(index):
tmp = tmp[-1] + tmp[:-1]
return int(tmp, 2)
def ROL(i,index): #循环左移
tmp bin(i)[2:].rjust(8,"0")
for _ in range(index):
tmp = tmp[1:] + tmp[0]
return int(tmp, 2)
addr1=0x140001085
addr2=0x140001d00
for i in range(addr2-addr1):
PatchByte(addr1+i,ROR(Byte(addr1+i),3)^90)
print('successful')
网上抄的,暂时不清楚原理,猜测是根据VirtualProtect函数来的
总结
-
重点在于找到被SMC的函数的起始地址和结束地址,这样才能针对性的对被改变的数据进行还原
-
可以使用动调让程序自动解密,然后重新构造被SMC的函数
-
也可以使用ida脚本直接对数据修改,得到原来的函数