通过无符号程序的日志函数来恢复函数名字
2022-07-24 #ida #python2023-03-12 更新:添加了使用 ida_hexrays.decompile
的方法
在逆向二进制程序中,经常碰到将程序 strip 了,但是日志函数中保留着函数名字的情况。所以写文记录在这种情况下,使用 IDAPython 恢复函数名字的过程。
日志函数长什么样
下面是我手写的一个日志函数。主要有几个部分:
- 日志级别
LogLevel
,记录日志的最低级别do_log_level
,用来在不同的情况下过滤日志。 - 日志文件
log_file
,将日志输出到对应的文件中。 - 记录日志的函数
logger_log
,还有几个辅助记录日志的宏。在宏中则是将函数的名称也记录了下来,方便后期调试。
代码也非常简单粗暴,唯一需要注意的是 logger_log
用了 C 中的变长函数参数,并且在相关的宏中用了 gcc 扩展 “GCC variadic macro” 来处理宏中的变长参数。
;
static FILE *log_file = NULL;
static int do_log_level = 0;
/// @brief init log file and log level
void
/// @brief do log
void
接下来是一点测试代码
int
int
下面是程序运行后的输出,可以看到 DEBUG 级别低于要记录的 INFO 级别,所以 DEBUG 级别的日志没有输出。
在 IDA 中,上面的代码长什么样
在反汇编结果中,可以看到虽然 DEBUG 级别的日志没有输出,但是相关的函数调用仍然在代码中保留着。并且每个日志函数的调用,在参数中都有函数名字。在这种情况下,就可以使用 IDAPython 进行自动化提取函数名字并且恢复回去了。
int
__int64 __fastcall
IDAPython 登场
下面是脚本。思路则是使用 idautils.CodeRefsTo
获取日志函数的调用,并且从调用处使用 idaapi.get_arg_addrs
获得带函数名字参数的地址,然后从这个地址读取出函数名字,调用 idc.set_name
重命名当前函数。
= 0x4011a0 # log function address, Use 'y' to declare argument's type first
# https://reverseengineering.stackexchange.com/questions/25301/getting-function-arguments-in-ida
= 2 # function name in log call position
# https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
=
=
=
=
"""Check `addr` is valid address"""
return
"""Read C string from `addr`"""
return b
=
= b
=
break
break
+=
+= 1
return
"""Search function that `addr` belongs to"""
= 0x0
break
=
return
=
# Get function argument
=
continue
# Get argument address
=
continue
# Get argument's immendiate value
=
=
= .
continue
# Get function name
=
=
有些地方需要注意:判断函数地址是否在程序运行虚拟地址,读取 C 式字符串,获得当前指令所属的函数地址是自己写的函数,可能有更好的内置函数来辅助判断。还有 get_arg_addrs
函数,获得的是给参数赋值的指令地址,例如对下面的代码,使用 ["%x" % i for i in idaapi.get_arg_addrs(0x4012d6)]
,得到的返回值是 ['4012cc', '4012c7', '4012c2']
,而不是字符串地址,所以得对指令使用 idaapi.decode_insn
进行解码,获得立即数的地址,即字符串的地址。还有就是日志函数有可能类型定义有问题,会导致 get_arg_addrs
返回 None,这种情况下,对着日志函数按 "y" 对参数类型和数量进行编辑,编辑后再次运行脚本即可获取参数。
.text:00000000004012C2 mov edx, offset aMain ; "main"
.text:00000000004012C7 mov esi, offset aDebugSDebugWor ; "[DEBUG] [%s] Debug, world!"
.text:00000000004012CC mov edi, 0 ; a1
.text:00000000004012D1 mov eax, 0
.text:00000000004012D6 call sub_4011A0
代码运行结果如下,可以看到,补全了没有符号的 a_important_function
函数名字,达到了基本效果。
[+] ref len 4
[+] 40127b a_important_function
[+] 4012a4 main
[+] 4012a4 main
[+] 4012a4 main
更精准的方法
上面的代码和恢复方式可能会出现找不准函数的参数的情况,或者会出现传参约定和上述代码不一样的情况,这就会导致恢复的函数名字错误。经过搜索我发现 IDA 允许通过 API 来调用反编译器来进行反编译,并且可以遍历反编译出来的 C 语言代码,这样就可以借助反编译器的结果来恢复函数名字了。
下面是相应的 IDAPython 代码,通过 idaapi.decompile
对 xref 的函数进行反编译,然后再通过编写的 LogVisitor
对反编译出来的 C 语言代码进行遍历,在 LogVisitor
类的 visit_expr
函数中定义遍历 C 语言表达式的相应逻辑。这里我们主要是寻找调用日志函数的表达式,并从中提取参数,获得参数的值,来命名当前函数。
"""Recover log by visiting ctree"""
=
=
=
=
# named, skip
return 0
# only need call expression
return 0
# call expression is iog_func
return 0
# length of call argument < idx
return 0
# get argument
=
return 0
=
return 0
# set name
return 0
= 0x00015CDC # XXX: EDIT THIS VALUE
= 4 # XXX: EDIT THIS VALUE
=
=
=
continue
continue
=