倚天屠龙(一):妙用IDA Pro--利用IDAPython编写调试插件
一:前言
虽然静态分析有Radare2,Hopper这种新星之秀,动态调试有Ollydbg,Windbg这种老牌霸主,但是IDA Pro仍然是大部分二进制安全工程师最喜爱的工具,除了价格过于昂贵,基本无懈可击。在笔者眼里,它有下面几个特点是别的工具无法比拟的
1:反编译插件,说它是当今世界最好的反编译器也不为过,这个革命性的插件,极大的降低逆向工程的门槛,也极大的提高了逆向工程师的工作效率。
2:IDA的编程接口,单纯的任何工具无法满足安全工程师的所有使用需求,但是完善的SDK包给了这个工具无限可能,特别在自动批量化处理的方面,如虎添翼。
3:以数据库的形式保存,方便对文件进行任何操作并保存
并不是其它的功能就不优秀了,相反,它的其它功能也很强大,比如FLART功能,等等。这系列文章主要以IDA IDC,SDK编程,IDAPython变成的具体案例为主,插叙IDA的各种奇淫巧技。
二:准备工作
先回答一个问题:
1:为什么用IDAPython,而不是用 IDC或者IDA SDK编程?
IDC可以快速解决一些简单的问题,但是对于复杂的问题,就有点力不从心了。IDA SDK包文档过少,而且在调试相关的API,BUG比较多,使用比较难受,相比于起来,IDAPython可以调用IDC和 IDA SDK包的所有函数,而且文档资料丰富。
当然,之前你需要懂Python,逆向工程,能熟练使用IDA Pro,懂得调试的一些常规知识。再加上一个IDA Pro6.8带IdaPython即可。
三:编写
第一步:先来看一下插件的框架
class myIdaPlugin(plugin_t): flags=0 wanted_name="my ida plugin" wanted_hotkey="Alt+c" comment="my ida plugin" help="Something helpful" def init(self): return PLUGIN_KEEP def term(self): pass def run(self,arg): pass def PLUGIN_ENTRY(): return myIdaPlugin()
其中,flags规定了Ida在不同情况下怎么处理插件,一般为0。
wanted_name为插件名称,comments为插件注释,help为帮助字符串,wanted_hotkey为快捷键,没有则赋为空值。
其中最重要的是那三个函数了,init()函数用于加载你的插件,term()函数用于卸载时的清理活动(释放内存,结束处理,保存状态等等)
第二步:看一下调试框架
from idaapi import * class MyDbgHook(DBG_Hooks): """ Own debug hook class that implementd the callback functions """ def dbg_process_start(self, pid, tid, ea, name, base, size): print("MyDbgHook : Process started, pid=%d tid=%d name=%s" % (pid, tid, name)) def dbg_process_exit(self, pid, tid, ea, code): print("MyDbgHook : Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code)) def dbg_library_unload(self, pid, tid, ea, info): print("MyDbgHook : Library unloaded: pid=%d tid=%d ea=0x%x info=%s" % (pid, tid, ea, info)) return 0 def dbg_process_attach(self, pid, tid, ea, name, base, size): print("MyDbgHook : Process attach pid=%d tid=%d ea=0x%x name=%s base=%x size=%x" % (pid, tid, ea, name, base, size)) def dbg_process_detach(self, pid, tid, ea): print("MyDbgHook : Process detached, pid=%d tid=%d ea=0x%x" % (pid, tid, ea)) return 0 def dbg_library_load(self, pid, tid, ea, name, base, size): print "MyDbgHook : Library loaded: pid=%d tid=%d name=%s base=%x" % (pid, tid, name, base) def dbg_bpt(self, tid, ea): print "MyDbgHook : Break point at %s[0x%x] pid=%d" % (GetFunctionName(ea), ea, tid) idaapi.continue_process() return 0 def dbg_suspend_process(self): print "MyDbgHook : Process suspended" def dbg_exception(self, pid, tid, ea, exc_code, exc_can_cont, exc_ea, exc_info): print("MyDbgHook : Exception: pid=%d tid=%d ea=0x%x exc_code=0x%x can_continue=%d exc_ea=0x%x exc_info=%s" % ( pid, tid, ea, exc_code & idaapi.BADADDR, exc_can_cont, exc_ea, exc_info)) return 0 def dbg_trace(self, tid, ea): print("MyDbgHook : Trace tid=%d ea=0x%x" % (tid, ea)) return 0 def dbg_step_into(self): print("MyDbgHook : Step into") self.dbg_step_over() def dbg_run_to(self, pid, tid=0, ea=0): print "MyDbgHook : Runto: tid=%d" % tid idaapi.continue_process() def dbg_step_over(self): print("MyDbgHook : 0x%x %s" % (eip, GetDisasm(eip))) debughook = MyDbgHook() debughook.hook()
这里是调试框架,代码看起来很长,其实,只要在插件框架的init函数进行初始化,即可。然后在调试过程中,会因为各种事件而触发各种函数,从而触发自己需要的操作,实现自动化脱壳或者anti-debug等功能。
第三步:研究实现x64和x32位antii-anti-debug功能
一般anti-anti-debug功能从两方面着手 ,一方面patch内存,一方面是hook函数。
Patch内存:这需要获取FS(x86)/GS(x64)指向的地址,这里提供三种方法,第一种直接使用IDApython提供的API接口
fsBase = regval_t() get_reg_val("fs",fsBase) return internal_get_sreg_base(idaapi.get_current_thread(),int(fsBase.ival) )
但这种在64位上无效,估计是IDA自身Bug,已经提交给 hex-ray公司了。
第二种是利用appcall函数来调用windows api得到,这种过于复杂。
第三种是通过注入dll,来直接用asm汇编进行编程,这里可以使用IDA的APPCALL机制来实现LoadLibrary操作,代码如下:
def LoadLibrary(self,dll_name): return Appcall.proto("kernel32_LoadLibraryA","int __stdcall LoadLibrary(const char * fn);")(dll_name)
之后就可以直接patch_long(addr,byte)了
Hook函数:如上文,最简单的方法是采用dll注入,采用APPCALL来加载并调用函数,如下
def callfunc(self,funcname): if self.bits == "x86": return Appcall.proto("stealthx86_"+funcname,"bool _stdcall "+funcname+"();")(); else: return Appcall.proto("stealthx64_"+funcname,"bool _stdcall "+funcname+"();")();
关于需要注入的dll,由于不在本文 内容中,请自行探究。
关于一些调试过程中自动化处理的一些,留待下一篇继续讲解。