4.33 IDA异常识别block致使F5代码残缺 https://scz.617.cn/misc/201811281121.txt Q: F5一个函数时,发现代码不全,莫名其妙在调用atol()之后结束了: -------------------------------------------------------------------------- 0000829C 97 02 00 EB BL atol 000082A0 ; -------------------------- 000082A0 0C 00 0B E5 STR R0, [R11,#var_C] -------------------------------------------------------------------------- IDA显示0x829C、0x82A0分属两个不同的block,用图形方式查看时,前一个block没 有后继结点,莫名结束,后一个block没有前趋结点,凭空出现。而实际上0x82A0处 没有任何交叉引用,与0x829C应属同一block。 A: hume 2018-11-28 10:23 如果碰上F5代码残缺,可以检查子函数是否noreturn。 在IDA中发现atol()被标注成noreturn: -------------------------------------------------------------------------- 00008D00 ; Attributes: noreturn 00008D00 00008D00 atol 00008D00 10 40 2D E9 STMFD SP!, {R4,LR} 00008D04 00 10 A0 E3 MOV R1, #0 00008D08 0A 20 A0 E3 MOV R2, #0xA 00008D0C 79 01 00 EB BL strtol 00008D10 ; ---------------------- 00008D10 10 40 BD E8 LDMFD SP!, {R4,LR} 00008D14 1E FF 2F E1 BX LR 00008D14 ; End of function atol -------------------------------------------------------------------------- 正是这个原因导致IDA认为0x829C之后不会走向0x82A0,解决办法很简单: Edit function (Alt-P) 清空 Does not return 仔细看atol(),0x8D0C、0x8D10也被切割到两个block,说明strtol()也noreturn。 顺着这条线找下去,根源在于__aeabi_read_tp()被标注成noreturn: -------------------------------------------------------------------------- 00008CC0 ; Attributes: noreturn 00008CC0 00008CC0 __aeabi_read_tp 00008CC0 0F 0A E0 E3 MOV R0, #0xFFFF0FFF 00008CC4 1F F0 40 E2 SUB PC, R0, #0x1F 00008CC4 ; End of function __aeabi_read_tp 00008CC4 00008CC8 00 00 A0 E1 NOP 00008CCC 00 00 A0 E1 NOP 00008CD0 00008CD0 ; Attributes: noreturn 00008CD0 00008CD0 __errno_location 00008CD0 00008CD0 var_4= -4 00008CD0 00008CD0 04 E0 2D E5 STR LR, [SP,#var_4]! 00008CD4 10 30 9F E5 LDR R3, =aObject_deps ; "object_deps" -------------------------------------------------------------------------- 函数属性会向主调者传递,导致沿线的strtol()、atol()也noreturn。不过, __aeabi_read_tp()被标注成noreturn,是因为它的函数体与__errno_location()共 用了一些代码,这应该是优化导致的,所以也不能怪IDA。 D: bluerust 猜测idaapi能修复这种情况,改block的前趋和后继,但未实践。 D: hume 2019-03-12 有时两条指令本应归属同一block,但莫名被切割到两个block,并且不涉及noreturn, 这种可能是IDA的BUG。其表现形式是出现断头block,一个没有后继结点,一个没有 前趋结点,F5时会丢失代码。 可用如下办法将两个block串起来,消除断头block: add_cref(first,second,fl_F) 其中first对应第一个block最后一条指令所在地址,second对应第二个block第一条 指令所在地址。这个办法不能修正F5丢失的代码。 fl_F在ida_xref.py中定义: fl_F = _ida_xref.fl_F D: scz 2019-03-12 参看ida_gdl.py,这里定义了BasicBlock、FlowChart。 查看当前函数中所有block: ["%#x-%#x-%#x-%#x" % ( block.id, block.startEA, block.endEA, block.type ) for block in FlowChart(get_func(get_screen_ea()))] for block in FlowChart(get_func(get_screen_ea())) : print "[%#x] [%#x,%#x) %#x" % ( block.id, block.startEA, block.endEA, block.type ) 在其输出中可以搜到second。type可取值: 0 normal 1 indjump 2 ret 3 cndret 4 noret 5 enoret 6 extern 7 error 参gdl.hpp enum fc_block_type_t { fcb_normal, // normal block fcb_indjump, // block ends with indirect jump fcb_ret, // return block fcb_cndret, // conditional return block fcb_noret, // noreturn block fcb_enoret, // external noreturn block (does not belong to the function) fcb_extern, // external normal block fcb_error, // block passes execution past the function end }; [block.startEA,block.endEA)是左闭右开区间,为了取指定block最后一条指令所在 地址,这样做: print "%#x" % idc.PrevHead(block.endEA) 查看指定block: id=0x19;block=FlowChart(get_func(get_screen_ea()))[id];print "[%#x] [%#x,%#x) %#x" % ( block.id, block.startEA, block.endEA, block.type ) 查看指定block的后继结点: id=0x19;successors=FlowChart(get_func(get_screen_ea()))[id].succs() for block in successors : print "[%#x] [%#x,%#x) %#x" % ( block.id, block.startEA, block.endEA, block.type ) 查看指定block的前趋结点: id=0x1a;predecessors=FlowChart(get_func(get_screen_ea()),flags=FC_PREDS)[id].preds() for block in predecessors : print "[%#x] [%#x,%#x) %#x" % ( block.id, block.startEA, block.endEA, block.type ) 如果preds()返回空集,检查FlowChart()的flags是否指定成FC_PREDS。 如果能修正指定block的startEA、endEA,或许能将两个block合并成一个。 D: bluerust 2019-03-12 https://www.hex-rays.com/products/ida/support/sdkdoc/classnetnode.html IDA database的底层实现就是netnode,change callee主要是从$vmm function那个 node里去改数据,我遍历了下,发现里面记的都是间接调用,callee不确定的指令地 址。 $vmm function这个node里各个数据的信息没有文档化,需要逆IDA目录里的两个文件 看它是怎么操作的。 IDA 7.2\SDK 7.2\plugins\callee\callee.cpp 我觉得在元数据层面去操作,应该可以,但是得干蛮多前置逆向工作。 IDA打开ida64.exe,在字符串里搜索"$ ",能找到全部的关键node,然后 x = idaapi.netnode( "$ vmm functions" ) 获得一个node,可以枚举这个node下的所有值,也可以修改,但是值的含义不清楚。 建议参考 https://github.com/williballenthin/python-idb 当时没有把这茬事情干到底。