【IDAPython CTree】反编译代码操作练习
前言
IDA Pro
可对二进制的汇编代码进行反编译,得到C伪代码,帮助安全人员进行分析,反编译的结果是一棵抽象语法树(Abstract Syntax Tree, AST)。同时,IDAPython
提供了相应的接口,让我们能够直接通过API,完成对反编译代码的分析操作。
由于网上相关的资料稀少,在观察比较了官方和一些现有博客后,本文主要通过一个案例,两种不同需求的例子来说明对这些接口的使用。
实验前置条件
这里我们采用一个简单的c语言代码[1],源代码如下:
#include<stdio.h>
#include<string.h>
int func3(int n, char *s)
{
int b;
if ( strcmp(s, "hello") == 0 )
{
b = 100;
}
else if ( strcmp(s, "hello1") == 0 )
{
b = 200;
}
else if ( strcmp(s, "hello2") == 0 )
{
b = 4;
}
else if ( strcmp(s, "hello3") == 0 )
{
b = 300;
}
else if ( strcmp("hello4", s) == 0 )
{
b = 400;
}
else if ( strcmp("hello5", s) == 0 )
{
b = 500;
}
else
{
b = 600;
}
return b + n;
}
int main(){
int res = func3(10, "hello");
printf("%d", res);
return 0;
}
功能非常简单,经gcc
反编译得到二进制文件,并将该二进制文件拖入IDA
,结果如下:
至此,实验前置工作完成。
需求一:修改反编译代码中所有赋值语句
根据需求可以简要获得思路:
- 遍历表达式,找到所有的赋值语句:
idapython
提供了一个ctree_visitor_t
类,通过继承该类,并修改visit_expr
可实现遍历所有表达式 - 获取赋值语句的变量和立即数
- 生成新的表达式替换原有表达式
下面是实现的脚本代码,我在必要的地方添加了注释,帮助理解。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-----------------File Info-----------------------
Name: ctree_test_script.py
Description:
Author: GentleCP
Email: me@gentlecp.com
Create Date: 7/21/2021
-----------------End-----------------------------
"""
import idaapi
import idc
class MyVisitor(idaapi.ctree_visitor_t):
def __init__(self, cfunc):
idaapi.ctree_visitor_t.__init__(self, idaapi.CV_FAST)
self.cfunc = cfunc
def is_strcmp_expr(self, cexpr):
'''
检验是否为strcmp表达式, 注意此处是先取反,其操作数才是strcmp
:param cexpr: cexpr_t
:return:
'''
if cexpr.op != idaapi.cot_lnot:
return False
strcmp_expr = cexpr.x
if strcmp_expr.a.size() != 2:
return False
if idaapi.get_func_name(strcmp_expr.x.obj_ea) != ".strcmp":
return False
return True
def visit_expr(self, expr) -> "int":
if expr.op != idaapi.cot_asg:
# 不是赋值语句跳过
return 0
_x = expr.x # x是第一个操作数
_y = expr.y # y是第二个操作数
print("find expr:{},{}".format(_x.opname, _y.opname))
# 判断是否满足变量和立即数的条件
if _x.op == idaapi.cot_var and _y.op == idaapi.cot_num:
# e.g. v3 = 100,
num = _y.n.value(_y.type) # 获取立即数的数值
# 生成一个新表达式,idapython要想修改表达式,需要新建一个,并调用swap方法
z = idaapi.cexpr_t()
z.swap(_y)
_y.op = idaapi.cot_str # 修改立即数为string
_y.string = 'hello world' # 将所有立即数修改为hello world
return 0
def main():
func = idaapi.get_func(idc.here()) # 获取当前位置的函数
cfunc = idaapi.decompile(func.start_ea) # 反编译函数
my_visitor = MyVisitor(cfunc)
my_visitor.apply_to(cfunc.body, None)
if __name__ == '__main__':
main()
再看对应的反编译结果,发现成功完成了替换
需求二:找到常数参数为hello的strcmp表达式
该需求主要是练习对语句和表达式结合的搜索方式
整理一下思路:
- 找到所有的
if
语句:这次我们需要遍历语句,采取修改ctree_visitor_t
的visit_insn
方法 - 判断其包含的条件表达式是否为
!strcmp()
类型 - 判断其参数中是否有
hello
参数,若有,给出提示
下面是实现的脚本代码,当找打我在必要的地方添加了注释,帮助理解:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-----------------File Info-----------------------
Name: ctree_test_script.py
Description:
Author: GentleCP
Email: me@gentlecp.com
Create Date: 7/21/2021
-----------------End-----------------------------
"""
import idaapi
import idc
import ida_bytes
class MyVisitor(idaapi.ctree_visitor_t):
def __init__(self, cfunc):
idaapi.ctree_visitor_t.__init__(self, idaapi.CV_FAST)
self.cfunc = cfunc
def is_strcmp_expr(self, cexpr):
'''
检验是否为strcmp表达式, 注意此处是先取反,其操作数才是strcmp
:param cexpr: cexpr_t
:return:
'''
if cexpr.op != idaapi.cot_lnot:
return False
strcmp_expr = cexpr.x # !strcmp() -> strcmp()
if strcmp_expr.a.size() != 2:
return False
if idaapi.get_func_name(strcmp_expr.x.obj_ea) != ".strcmp":
return False
return True
def visit_insn(self, ins: idaapi.cinsn_t) -> "int":
# 找到所有if语句,替换其中表达式
if ins.op == idaapi.cit_if:
# print("Find if statement at {}".format(ins.ea))
cif = ins.cif # cif_t
# 判断if中表达式是否满足条件
if self.is_strcmp_expr(cif.expr):
print("Find !strcmp expr at {}".format(cif.expr.ea))
strcmp_expr = cif.expr.x # if -> ! -> strcmp
for carg in strcmp_expr.a:
# 遍历strcmp函数的参数
if carg.op == idaapi.cot_obj:
# obj 对应string,根据地址获取字符串
if ida_bytes.get_strlit_contents(carg.obj_ea, -1, 0).decode == 'hello':
print("Found hello at {}".format(carg.obj_ea))
return 0
def main():
func = idaapi.get_func(idc.here()) # 获取当前位置的函数
cfunc = idaapi.decompile(func.start_ea) # 反编译函数
my_visitor = MyVisitor(cfunc)
my_visitor.apply_to(cfunc.body, None)
if __name__ == '__main__':
main()
执行脚本得到如下结果:
在IDA中检查hello
字符串地址,与查找结果相符
总结
本次主要是通过两个小例子熟悉一下idapython
中反编译相关的操作,因为网上这方面的资料过于稀缺,即便有,也都没有解释清楚,要真正理解其用法,还是得自己多实践,多搜索。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!