Day14:IDApython与SMC
IDApythonAPI、SMC自修改代码
IDApython
基础API
- idc.here() / idc.get_screen_ea():返回光标所在处的地址
- idc.get_inf_attr(INF_MIN_EA) / idc.get_inf_attr(INF_MAX_EA):返回最小/最大地址(没有随机化地址情况下)
- idc.generate_disasm_line(ea,0):返回ea所在处的汇编指令
- idc.prev_head(ea)/next_head(ea):返回ea处上一条/下一条指令地址
- idc.print_insn_mnem(ea):打印ea处的助记符(即mov,add什么的)
- idc.print_operand(ea,n):打印ea处第n个操作数(n=0或者1)
段相关API
- idautils.Segments():返回段起始地址列表
- idc.get_segm_name(ea):返回ea所在段的名字
- idc.get_segm_start(ea)/get_segm_end(ea):返回ea所在段的起始地址/结束地址
- idc.get_next_seg(ea):返回ea所在段的下一个段的起始地址
函数相关API
- idautils.Functions(start_addr,end_addr):返回地址间的函数对象(不写参数就是所有函数对象)
- idautils.FuncItems(ea):返回ea所在函数所有指令的地址(相当于地址列表)
- idaapi.get_func(ea):获得ea所在函数的对象(含有如start_ea、end_ea、size等属性)
- idc.get_func_name(ea):返回ea所在函数的名字
- idc.get_func_attr(ea,FUNCATTR_START) / idc.get_func_attr(ea,FUNCATTR_END):返回ea所在函数的起始地址/结束地址
- idc.get_next_func(ea) / idc.get_prev_func(ea):返回ea所在函数的上一个/下一个函数的起始地址
指令相关API
- ida_ua.insn_t(),返回一个空的指令对象(或者说创建一个空的指令对象),包含函数的一些属性
- idaapi.decode_insn(out,ea),解析ea所在处的指令,把解析的结果给out(out必须是一个指令对象)
- out.ea:指令的起始地址
- out.size:指令占用的字节数
- out.get_cannon_mnemonic():指令的助记符
- out.itype:助记符的十进制码
操作数相关API
- idc.get_operand_value(ea,n):返回ea处第n个操作数的值
- idc.get_operand_type(ea,n):返回ea处第n个操作数的类型
数据读写相关API
- idc.get_bytes(ea,size):返回ea处size大小的bytes
- idc.patch_byte(ea,content):修改ea处的1字节为content
- idc.patch_word(ea,content):修改ea处的2字节为content
- idc.patch_dword(ea,content):修改ea处的4字节为content
- idc.patch_qword(ea,content):修改ea处的8字节为content
调试相关API
ida_dbg.load_debugger(“local”,0):启动调试器
ida_dbg.add_bpt(ea):在ea处下断点
ida_dbg.del_bpt(ea):删除ea处断点
ida_dbg.start_process(path,args,sdir):启动调试进程,参数对应路径、命令行参数、工作目录
ida_dbg.step_into():单步步入
ida_dbg.step_over():单步步过
ida_dbg.step_until_ret():运行到返回
idc.get_reg_value(regname):获取regname寄存器的值
idc.set_reg_value(value,regname):设置regname寄存器的值
其他操作相关API
idautils.XrefsFrom(ea):返回ea处引用的对象(有属性frm、to、type)
idautils.XrefsTo(ea):返回所有引用ea处的对象(有属性frm、to、type)(Ctrl+X)
示例:
1
2
3
4
5
6
7import idautils,idaapi
for xref in idautils.XrefsTo(here()):
print(xref.to)#here()
print(xref.frm)#调用here()的函数地址
for xref in idautils.XrefsFrom(here()):
print(xref.frm)#here()
print(xref.to)#here()引用的函数地址idautils.Strings():返回包含所有字符串的对象(有属性ea、length、strtype)(shift+F12)
idc.get_strlit_contents(ea):返回ea处的字符串
示例:
1
2
3
4
5import idc,idautils
for s in idautils.Strings():
string=idc.get_strlit_contents(s.ea)
print(string)
#打印所有字符串
自修改代码(SMC)
自修改代码(Self-Modifying-Code)指某部分代码以加密后的形式存在于程序中,程序执行到这部分代码的时候才会进行动态解密,和加壳有点异曲同工。常用来加密关键逻辑,使其不可直接静态分析
特征:
程序中存在对程序自身某部分的运算,一般还需要VirtualProtect() / mprotect()来改变内存的属性以便将解密后的代码数据写入内存,或者使用VirtualAlloc把解密后的代码数据写入堆中执行(为了避免API暴露,还可能会新增一个有RWX属性的段来存放加密后的代码)。
应对方法有两种:一种是动态调试得到解密后的关键逻辑,另一种是使用脚本解密关键逻辑后再覆盖回去
示例:2021-羊城杯-babysmc
PS:本来想要自己写一个简单程序试试手的,研究了半个下午没研究出来,程序修正到解密函数可以解密出正确代码了,但是调用关键逻辑check的时候莫名其妙跑飞了,在那个安全检查的位置,跳转后像是个畸形函数,不知道是不是改内存属性的时候影响到了,所以只能去找网上的题目了
这是优化后的主函数:

优化的来源就是,enc的位置是一团数字,而loc_7FF6BF081D00处开始是正常代码,加上对下面的decrypt函数的分析,可以确定enc就是被加密后的first_part
这是decrypt函数:

非常明显地调用了VirtualProtect函数,解密逻辑很简单,就是ror后异或0x5a(ror查出来是循环右移,3就是右移的位数了,这点看汇编可能更清晰点)
下面就是今天的成果展示了(ror由DS协助实现),SMC解密脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13import idautils,idc,idaapi
enc=idc.get_bytes(0x00007FF6BF081085,0x00007FF6BF081D00-0x00007FF6BF081085)
loc=0x00007FF6BF081085
def ror(value, shift, bits=8):
shift %= bits # 确保位移量在有效范围内
return (value >> shift) | ((value << (bits - shift)) & ((1 << bits) - 1))
for i in enc:
idc.patch_byte(loc,(ror(i,3)^0x5a)&0xff)
loc+=1
print("done")本来是要写完的,但是first_part的函数解析完好几百行了,是我看不懂的加密🫠去找WP,发现是非常混淆的base64,还加了异或,但是我没看出逻辑,索性点到为止,改天把编码和密码的部分补上再说。
本来也尝试了动调解密,但是调着调着给我下起东西来了,虽然链接看着是微软的,但是还是有点怕,就舍弃这个方法了(下的是kernel.pdb好像,DS说是缺失符号文件🫠🫠)
Day14:IDApython与SMC