IDA从入门到乞讨之IDAPython Script 与 plugin
一、IDAPython Script
在用IDA分析程序时,我们发现有很多重复的工作,这时我们就想到有没有什么东西跟我们自动去处理这些东西好帮我们完成分析,能写个脚本或者插件就好了。当然了IDA也给我们想好了,这就是IDC或者IDAPython接口,IDC是很早之前就和IDA一起发布的,类似C语言,所以可能他的最后面这个字母才是C,然后,由于Python语言的火爆,导致了IDA又把接口暴露给python用,所以形成了IDAPython。在找资料时,具体的接口参数或者宏定义,我们往往发现idapython文档讲得不太全,而是C++ SDK文档接口才是讲得最清楚的。
A、IDC Script
IDC脚本具体可以看官方文档,然后就不做过多的介绍了,因为现在应该很少有人用了,基本都用IDAPython了,不过我们也是可以做个小例子看看。注 意看到IDA主界面的Output window窗口下的按钮Python,一般应该是Python如果你有安装IDApython环境的话(默认是安装的),然后你点击他,就会弹出两个 选择IDC和Ptyhon(idapython),我们选择IDC即可,这个时候我们就可以在edit编辑框上打idc命令(其实也就是API接口或者语法什么的)。我们处理一下<<加密解密4>>的SMC代码例子2,解密还原函数。
我们可以看到程序入口是start ,经过sub_401020函数后,里面存在两个函数的调用sub_401080和sub_401060两个函数,但是当我们鼠标指针移向sub_401060时,却显示的代码很奇怪,不像是一个函数。然后我进入调用他时上面调用的函数,应该是上面这个函数有什么秘密,鼠标双击这个call sub_401080后面的这个sub_401080或者上节讲的用G快捷键输入这个0x401080,然后回车一样的效果,注意g快捷键自动默认你输入的都是16进制。进去后我们空格键让他变为流程图样式,这样让我们看起来更加清楚:
这样我们一看就知道有循环了,而且注意第一条语句是获取刚刚我们说的那个有问题的函数的首地址,经过我们分析发现这个就是对我们刚刚那个函数内容的解密,解密算法也很简单,就是简单的和1进行异或(可以F5看看),那么我们用IDC脚本对这个算法进行还原,把解密后的代码重新patch到那个函数里面去。找到我上面给的那个官方的文档,找到Index of IDC functions这个,然后点击进去,我们找一下我们需要的API。我们可以看到有很多函数,具体要什么函数,我们可以根据这个函数名来猜猜然后点击进去看看具体说明,在这里我们要用到byte和patch_byte这个函数,前者获取一个字节数据,后者写入一个字节数据。那么我们就来写我们的脚本 了
1 2 3 4 5 6 7 8 9 10 | static main(void){
auto i = 0 ;
auto m = 0 ;
for (i = 0x401060 ; i < 0x401075 ; i = i + 1 ){
m = byte(i);
patch_byte(i,m ^ 1 );
}
}
|
这个脚本看起来很简单,就是循环获取指定地址一个字节大小数据,然后异或1在写回去。我们应该怎么用他呢,我们快捷键ALT+F7即可打开脚本调用窗口,选择好我们写好的脚本,按确定即可运行我们的脚本。注意到文件的选择的格式没有idc或者py后缀是吧。运行脚本后我们的函数变为了
这样一看我们就知道这个是调用了一个MessageBox,而且内容是Welcom to www.pediy.com! 是吧 ? 如果你的脚本运行后还不是和我这个一样,那么可能就是之前的反汇编位置不对,但是你patch代码后他还没能自动给你识别出,你这时应该手动去让他识别为一个函数,去到0x401060函数头用U快捷键把他变为未定义状态,然后在按P让他为函数即可。而在Output window下的IDC按钮旁边的edit,我们也是可以直接打命令(函数接口)得到一些我们想到的数据,但是感觉IDC不太好去这样操作,比如刚刚那个还是就不太容易去做。而接下来的IDAPython就比较容易了。
B、IDAPython Script
我们把刚刚那个IDC按钮点一次,选择python,那么这个时候我们往按钮旁边的edit编辑框输入的命令(说命令也感觉不太全面 Python语法,差不多就是那个意识)等回车就是以python的方式去处理的。我们看看IDA官方的IDAPython参考: IDAPython api 参考 。IDA 7.0之前主要的IDAPython只有 三个文件idaapi.py、idautils.py和idc.py。idc.py,它是封装IDA的IDC函数的兼容性模块。idautils.py,这是IDA里的一个高级实用功能模块。idaapi.py,它允许访问更加底层的数据。之后就把很多功能给分割出来了,这3个文件有的功能,其他的文件里面也有,其他文件里面有的可能他们没有,然后可能就会导致新手感觉很乱,但是没关系,用多了就知道了,平时也就用那么几个函数。在IDA 7.0之后,ida不在以python 2.7的版本为其主流,而改用python3版本的,但是其主文件python下依然存在python2和python3,也就是说你也可以通过设置改为python2的idapython。怎么辨别你当前IDA用的是python2或者python3,最简单的方法就是刚刚的命令行(Output window下面的edit框)上 输入print "Hello world",报错不报错就知道了,因为python2和python3最大的区别就是后者要用括号把内容括起来。IDAPython接口的变化和弃用我们可以看到连接 IDAPython版本变化 ,当你用7.0之前的接口函数比如:MaxEA 、MinEA时就会报错,但是我们可以经过引用idc_bc695模块就可以解决这个问题,我们可以打开这个文件看看就知道原因了。其路径在 ida文件目录python -> python3 下能找到,我们在命令行上输入加载这个文件即可使用IDA 7.0等之前的IDA Python接口,这里我们统一说是ida7.0的吧:
结合我们刚刚分析的那个smc函数的处理,我们现在用idapython把他给实现我们只需用一条语句即可:
1 | [PatchByte(ea,Byte(ea) ^ 1 ) for ea in range ( 0x401060 , 0x401075 , 1 )]
|
是不是感觉很帅,当然了ida 7.5这条命令是报错的,因为Byte、PatchByte这两个接口是没有的,他是IDA 7.0下的东西,我们可以选择先加载idc_bc695在用,或者改为新的接口:
1 | [patch_byte(ea,get_wide_byte(ea) ^ 1 ) for ea in range ( 0x401060 , 0x401075 , 1 )]
|
或者我们写得像脚本一样,我们可以把刚刚的那句内容写入文件decrypt_smc.py,然后alt + f7调用也是可以的,或者写的更加像我们之前的理解的样子:
1 2 3 | for ea in range ( 0x401060 , 0x401075 , 1 ):
m = get_wide_byte(ea)
patch_byte(ea,m ^ 1 )
|
写这么啰嗦到底是想表达什么呢 ? 我想表达的就是一种感觉,IDAPython命令行上打的命令的集合就是IDAPython脚本,怎么理解都行,差不多就是那个意思。一般我们打命令就完成了我们很大部分的操作。在给个连接 IDAPython命令(接口)主要是适用于IDA 7.0。
在一个讲就是强化一下我们的的IDAPython中的python,因为原版是没有pip等功能的,很多功能是比较弱的,我们在python官方下载这个python3.9 x64这个版本的,注意要x64版本的,IDA7.0后都是要x64版本的python了,最好要python3的,紧跟时代潮流,找到我们安装的python路径,一般都在C盘根目录一般是pythonXX-x64这个样子,然后安装把他给覆盖就可以了。如果实在找不到可以打开IDA目录下的idapyswitch.exe,就可以看到。当然这个idapyswitch.exe文件也有另一个功能就是当你没有安装过IDAPython时,用他给你选择python,自动处理给你加上idapython环境,注意要输入的是python完整路径,这里我们不需要,我们只是覆盖原来的python。当我们处理好后,我们就有了pip等文件,我们对这个python的各种pip库都会作用于idapython共享,方便以后我们安装其他的idapython plugin。
二、IDAPython plugin
插件,其实也是IDAPython Script的一种。插件一般调用方式有主动和被动,主动就是需要人为的去触发,比如按快捷键,或者菜单栏 Edit -> plugin -> 你的插件,这样去调用。而被动,比如说是一个V850 E1处理器的补充,变为对V850 E2处理器的反汇编,这样的就是给处理器模块加功能的插件,他就不需要去显示,或者要你去点击了。插件写好后,放到IDA 目录下的plugins即可。接下来我们看看IDA插件的一个模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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()
|
我们看一下官方给的IDA插件的一个定义文档,当然是C++的,IDA SDK plugin,我们就可以知道这些参数的含义,我们看一下这个flags的值:
flags |
hex value |
Simple description |
PLUGIN_MOD |
0x0001 |
Plugin changes the database. |
PLUGIN_DRAW |
0x0002 |
IDA should redraw everything after calling the plugin. |
PLUGIN_SEG |
0x0004 |
Plugin may be applied only if the current address belongs to a segment. |
PLUGIN_UNL |
0x0008 |
Unload the plugin immediately after calling 'run'. |
PLUGIN_HIDE |
0x0010 |
Plugin should not appear in the Edit, Plugins menu. |
PLUGIN_DBG |
0x0020 |
A debugger plugin. |
PLUGIN_PROC |
0x0040 |
Load plugin when a processor module is loaded. |
PLUGIN_FIX |
0x0080 |
Load plugin when IDA starts and keep it in the memory until IDA stops. |
PLUGIN_MULTI |
0x0100 |
The plugin can work with multiple idbs in parallel. |
PLUGIN_SCRIPTED |
0x8000 |
Scripted plugin. |
这样我们就可以看出来,这些标志有的是可以组合的是吧,这里我们就可以看到了有的是针对debug的、process module(处理器模块 之前说的V850 E2)的等等。一般我们设置为0就可以了或者忽略他,哈哈哈。。。。。。,看到 init方法的返回值,他应该是有3种:
return value |
hex value |
Simple description |
PLUGIN_SKIP |
0x00 |
跳过,禁用这个插件,不适合的处理器模块,比如插件是针对x86,但你分析的是mips,所以这个返回值就有用了 |
PLUGIN_OK |
0x01 |
允许这个插件运行 |
PLUGIN_KEEP |
0x02 |
允许这个插件运行,驻留内存。具体作用不太清楚 |
但是看官方C++ SDK文档说的这个是plugmod_t类型的返回值,我这里有点看不懂了,可能我们写的是IDAPython有点区别吧,有谁知道的说一下。
接下来我们写一个获取鼠标指向的位置的函数的asm代码的插件,我们先安装一下对剪贴板操作的库,以为我们等下得到的asmble传到剪贴板上去,找到我们IDA对应的python的安装目录的Scripts : C:\python38-x64\Scripts (我这里是这个,你那边就看你自己具体的环境了),然后打开 cmd窗口 跳到这个Scripts目录,输入安装命令 : pip install pyperclip,如果ida已经打开了,关闭重新打开,在IDA的Python命令行Edit编辑框上输入 import pyperclip回车如果没有错误即说明安装成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | from idc_bc695 import *
import pyperclip
import idautils
import idaapi
import idc
class CopyFunctionAsm(idaapi.plugin_t):
comment = "Copy here Function assembly to clip"
help = "todo"
wanted_name = "Copy here func asm"
wanted_hotkey = "Ctrl-Alt-C"
flags = 0
def __init__( self ):
super (CopyFunctionAsm, self ).__init__()
self ._data = None
def term( self ):
print ( "[+] copy func asm plugin term......" )
def init( self ):
self .view = None
print ( "[+] copy func asm plugin init ....." )
return idaapi.PLUGIN_OK
def run( self ,arg):
print ( "[-] Copy Function asm ......." )
print ( "------------------------------------------------------" )
FuncStartAddr = LocByName(GetFunctionName(here()))
FuncEndAddr = FindFuncEnd(FuncStartAddr)
print ( "[*]Function Name offset " )
print ( "[*]%10s 0x%08X" % (GetFunctionName(FuncStartAddr),here() - FuncStartAddr))
print ( "------------------------------------------------------" )
asm = ''
for ea in idautils.Heads(FuncStartAddr,FuncEndAddr):
asm + = GetDisasm(ea) + '\n'
pyperclip.copy(asm)
print ( "[+] run compile..... asmble to clipboard." )
def PLUGIN_ENTRY():
return CopyFunctionAsm()
|
把他(copy_func_asm.py)放到我们的ida主目录plugins目录下,重启IDA即可,重启后,我们可以在Output window看到 init和term方法输出的信息(如果你init返回的是PLUGIN_KEEP那么这个时候你将看不到term方法的调用),知道他们已经被调用。可以在ida的菜单栏edit -> plugins下增加了一个Copy here func asm的选项,这个就是我们的插件了,我们选择点击他后,我们就会发现我们的output窗口输出了我们run方法下的print内容,并且我们的剪贴板获取到了鼠标所在的函数的asm代码。然后你看到run的方法内容,不就是我们的IDAPython脚本的意思吗,是吧,可以把他单独拿出来alt+F7也是效果一样的。最后总结就是把我们上面的给的几个连接 IDAPython的官方文档和 官方SDK做参考就完事了。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)