网站建设项目进度计划,网站项目策划书实例,北京爱空间装修公司,免费制作logo的软件IDAFrida
前言
偶然间发现了一本秘籍《IDA脚本开发之旅》#xff0c;这是白龙的系列文章#xff0c;主要是安卓平台#xff0c;笔者只是根据他的知识点学习#xff0c;拓展#xff0c;可以会稍微提及别的平台。本文并不会贴出他的思路分析#xff0c;只对于源码进…IDAFrida
前言
偶然间发现了一本秘籍《IDA脚本开发之旅》这是白龙的系列文章主要是安卓平台笔者只是根据他的知识点学习拓展可以会稍微提及别的平台。本文并不会贴出他的思路分析只对于源码进行学习有兴趣可以加他星球一起学习
另外本文也学习了无名侠的IDAFrida脚本有兴趣可以加他星球一起学习
脚本学习
IDA 静态分析 Frida Hook可以让分析变得更加丝滑。
这里我们会学习两个脚本 一个是白龙的 一个是 无名侠的。
ShowFridaCode
这是白龙写的一个Python脚本用于生成Frida的Hook代码。主要包含了以下几个部分
导入模块Frida Hook函数模板Frida Inline Hook函数模板打印参数的函数生成函数Hook代码的函数生成Inline Hook代码的函数根据地址生成Hook代码或Inline Hook代码的函数生成初始化代码的函数生成Dump内存代码的函数IDA View_Hooks类用于处理在IDA视图中双击和单击事件插件类实现插件的初始化、运行和退出
前置知识
frida ida就不说了主要说一下 其他的知识
android_dlopen_ext 是 Android 系统中的一个函数用于在运行时动态加载共享库。与标准的 dlopen() 函数相比android_dlopen_ext 提供了更多的参数选项和扩展功能例如支持命名空间、符号版本等特性。该函数通常用于 Android 应用程序或系统进程中用于加载共享库并获取其中定义的符号。在 Android 应用程序中常常会使用 System.loadLibrary() 函数调用 android_dlopen_ext()从而加载与应用程序打包在一起的共享库文件。
在win上我们一般用 LoadLibrary 函数来加载 DLL动态链接库文件并使用 GetProcAddress 函数来获取函数指针。在 iOS 平台上可以使用 dlopen 函数来加载共享库并使用 dlsym 函数来获取函数指针。
linker的调用流程
在do_dlopen中通过find_library进行加载so在加载完so后通过call_constructors完成init_array的加载find_library最后调用load_libray完成so的转载最后通过load_library的elf_reader.load完成so的装载
了解了上面两个知识点在Hook Native时我们常常通过Spawn抢占两个较早的时机1是 JNIOnLoad 前 2是 init_proc 以及 init_array 前 就可以通过hook上述函数。
接下来就看源码学习吧、我会在代码中给出详细的注释
# Template 类提供了一种简单而安全的字符串替换机制可以避免常见的安全漏洞例如 SQL 注入攻击。在使用模板字符串时可以将需要被替换的变量名以 $ 作为前缀并在后面用花括号 {} 将变量名括起来。然后使用 substitute() 方法将变量值传递给模板该方法会将变量名替换为对应的值。
from string import Template
# 文本行相关的函数和类。
import ida_lines
# IDA Pro 插件开发相关的函数和类。
import idaapi
import idc
# 该类型是插件开发的基本接口
from ida_idaapi import plugin_t# hook函数的模板这段代码通过调用 Module.findBaseAddress($soName) 找到指定so库的基地址然后使用 Interceptor.attach() 函数 hook 在基地址加上指定偏移量的函数。通过这些占位符可以实现动态替换相应的值$soName 表示要 hook 的so库的名字$functionName 表示要 hook 的函数名$offset 表示要 hook 的函数在so库中的偏移量$args 表示在 onEnter() 函数中输出的参数值$result 表示在 onLeave() 函数中输出的返回值。hook_function_template
function hook_$functionName(){var base_addr Module.findBaseAddress($soName);Interceptor.attach(base_addr.add($offset), {onEnter(args) {console.log(call $functionName);$args},onLeave(retval) {$resultconsole.log(leave $functionName);}});
}
# 这段模板和上面那段差不多 主要用于某行指令去call函数的时候 在onEnter回调中 可以查看函数参数、寄存器值等调用上下文的详细信息。
inline_hook_template
function hook_$offset(){var base_addr Module.findBaseAddress($soName);Interceptor.attach(base_addr.add($offset), {onEnter(args) {console.log(call $offset);console.log(JSON.stringify(this.context));},});
}
# 参数打印
logTemplate console.log(arg$index:args[$index]);\n# 在指定共享库加载时执行自定义逻辑的功能可以用于动态监视和修改目标进程中的行为。
dlopenAfter_template
var android_dlopen_ext Module.findExportByName(null, android_dlopen_ext);
if(android_dlopen_ext ! null){Interceptor.attach(android_dlopen_ext,{onEnter: function(args){var soName args[0].readCString();if(soName.indexOf($soName) ! -1){this.hook true;}},onLeave: function(retval){if(this.hook) {this.hook false;dlopentodo();}}});
}function dlopentodo(){//todo
}
# 在指定共享库初始化时执行自定义逻辑的功能 例如 init_proc 以及 init_array
init_template
function hookInit(){var linkername;var alreadyHook false;var call_constructor_addr null;var arch Process.arch;if (arch.endsWith(arm)) {linkername linker;} else {linkername linker64;}var symbols Module.enumerateSymbolsSync(linkername);for (var i 0; i symbols.length; i) {var symbol symbols[i];if (symbol.name.indexOf(call_constructor) ! -1) {call_constructor_addr symbol.address;}}if (call_constructor_addr.compare(NULL) 0) {console.log(get construct address);Interceptor.attach(call_constructor_addr, {onEnter: function (args) {if(alreadyHook false){const targetModule Process.findModuleByName($soName);if (targetModule ! null) {alreadyHook true;inittodo();}}}});}
}function inittodo(){//todo
}
# 以十六进制形式打印指定so文件中指定偏移量处内存数据
dump_template
function dump_$offset() {var base_addr Module.findBaseAddress($soName);var dump_addr base_addr.add($offset);console.log(hexdump(dump_addr, {length: $length}));
}
# 生成参数打印脚本
def generate_printArgs(argNum):if argNum 0:return // no argselse:temp Template(logTemplate)logText for i in range(argNum):logText temp.substitute({index: i})logText return logText# 生成hook函数模板 通过 soName, functionName, address, argNum, hasReturn
def generate_for_func(soName, functionName, address, argNum, hasReturn):# 根据参数个数打印argsPrint generate_printArgs(argNum)# 根据是否有返回值判断是否打印retvalretPrint // no returnif hasReturn:retPrint console.log(retval);# 使用Python提供的Template字符串模板方法temp Template(hook_function_template)offset getOffset(address)result temp.substitute({soName: soName, functionName: functionName, offset: hex(offset), args: argsPrint, result: retPrint})print(result)# 获取地址偏移
def getOffset(address):if idaapi.get_inf_structure().is_64bit():return addresselse:return address idc.get_sreg(address, T)# 生成inline hook函数模板 通过 soName, address
def generate_for_inline(soName, address):offset getOffset(address)temp Template(inline_hook_template)result temp.substitute({soName: soName, offset: hex(offset)})if idaapi.is_call_insn(address):callAddr idaapi.get_name_ea(0, idc.print_operand(address, 0))if callAddr ! idaapi.BADADDR:callAddress idc.get_operand_value(address, 0)argnum, _ get_argnum_and_ret(callAddress)argsPrint generate_printArgs(argnum)print(result.replace(console.log(JSON.stringify(this.context));, argsPrint))else:print(result)else:print(result)# 这个函数的作用是获取指定地址处的函数的参数个数和返回值类型。
def get_argnum_and_ret(address):# 获取反编译的代码cfun idaapi.decompile(address)# 获取参数长度argnum len(cfun.arguments)ret True# 先调用cfun.print_dcl() 函数获取函数的声明语句 在调用 tag_remove 去掉字符串中的标记它可以将类似 hex number、function name 这样的标记从字符串中去掉dcl ida_lines.tag_remove(cfun.print_dcl())# 判断函数声明语句是否有返回值 需要注意当一个函数返回 void * 类型时它实际上是返回一个指针指向某个数据的内存地址。if (dcl.startswith(void ) is True) (dcl.startswith(void *) is False):ret Falsereturn argnum, ret# 通过地址去生成 hook 脚本
def generate_for_func_by_address(addr):soName idaapi.get_root_filename()functionName idaapi.get_func_name(addr)argnum, ret get_argnum_and_ret(addr)generate_for_func(soName, functionName, addr, argnum, ret)# 通过地址去生成 inline hook 脚本
def generate_for_inline_by_address(addr):soName idaapi.get_root_filename()generate_for_inline(soName, addr)# 如果传入的地址等于函数头的地址 那么就hook函数头 否则生成函数中
def generate_snippet(addr):startAddress idc.get_func_attr(addr, idc.FUNCATTR_START)if startAddress addr:generate_for_func_by_address(addr)elif startAddress idc.BADADDR:print(不在函数内)else:generate_for_inline_by_address(addr)# 生成初始化模板 脚本
def generateInitCode():soName idaapi.get_root_filename()print(Template(dlopenAfter_template).substitute({soName: soName}))print(Template(init_template).substitute({soName: soName}))# 生成dump 脚本
def generate_dump_script(start, length):soName idaapi.get_root_filename()print(Template(dump_template).substitute({soName: soName, offset: hex(start), length: hex(length)}))# IDA界面hook
class Hook(idaapi.View_Hooks):# 监听双击 如果是反汇编视图则获取当前光标所在地址 然后生成模板def view_dblclick(self, view, event):widgetType idaapi.get_widget_type(view)if widgetType idaapi.BWN_DISASM:global initializedif not initialized:initialized TruegenerateInitCode()address idaapi.get_screen_ea()generate_snippet(address)# 监听单击事件 获取开始和结束地址 进行dumpdef view_click(self, view, event):widgetType idaapi.get_widget_type(view)if widgetType idaapi.BWN_DISASM:start idc.read_selection_start()end idc.read_selection_end()if (start ! idaapi.BADADDR) and (end ! idaapi.BADADDR):length end - startgenerate_dump_script(start, length)class GenFrida_Plugin_t(plugin_t):# 关于插件的注释# 当鼠标浮于菜单插件上方时IDA左下角所示comment A Toy Plugin for Generating Frida Code# 帮助信息我们选择不填help unknown# 插件的特性是一直在内存里还是运行一下就退出等等flags idaapi.PLUGIN_KEEP# 插件的名字wanted_name ShowFridaCode# 快捷键我们选择置空不弄wanted_hotkey # 插件刚被加载到IDA内存中# 这里适合做插件的初始化工作def init(self):print(ShowFridaCode init)return idaapi.PLUGIN_KEEP# 插件运行中# 这里是主要逻辑def run(self, arg):print(ShowFridaCode run)global myViewHookmyViewHook Hook()myViewHook.hook()# 插件卸载退出的时机# 这里适合做资源释放def term(self):passinitialized False
# register IDA plugindef PLUGIN_ENTRY():return GenFrida_Plugin_t()
ShowFridaCode 这是一个十分简单但是很优秀的脚本 包含了ida界面hook frida语法 模板生成 安卓初始化流程 arm汇编的理解
IDAFrida
这是无名侠写的一个Python脚本。这段脚本主要是一个基于 IDA Pro 和 Frida 的集成工具用于辅助逆向工程师进行动态调试。具体来说这个工具通过 IDA Pro 分析程序的代码提取出函数的信息如函数名、地址、参数数量等然后使用 Frida 注入 JS 脚本在运行时拦截这些函数并在函数进入和返回时打印出函数的参数和返回值。这个工具的具体功能包括
通过 IDA Pro 分析程序代码提取函数信息使用 Frida 注入 JS 脚本拦截函数并打印参数和返回值提供一个 UI 界面让用户可以修改 Frida 命令和 JS 脚本。
具体来说这个脚本实现了以下几个类和函数
ActionManager 类一个动作管理器用于管理 IDA Pro 中的动作。它提供了注册、初始化和销毁动作的功能Action 类一个动作基类用于创建 IDA Pro 中的动作。它包含了动作的名称、描述、快捷键等信息并提供了动作激活和更新的虚函数Configuration 类一个配置类用于保存和读取用户配置信息。它包含了 Frida 命令和 JS 脚本的配置信息并提供了保存和读取配置信息的方法ConfigurationUI 类一个 UI 类用于提供一个 UI 界面让用户可以修改配置信息ScriptGenerator 类一个脚本生成器类用于生成注入到目标程序中的 JS 脚本。
我们直接看源码学习吧 同样我也会给出详细的注释
import ida_name
import idaapi###################
# from: https://github.com/igogo-x86/HexRaysPyTools
# 这是一个名为 ActionManager 的类它用于管理多个动作Action并提供初始化和清理方法
class ActionManager(object):def __init__(self):self.__actions []def register(self, action):self.__actions.append(action)idaapi.register_action(# 创建一个动作描述符action descriptor以便注册动作并将其添加到IDA的菜单或工具栏中。action.name是动作的名称action.description是动作的描述action是动作的处理程序action.hotkey是动作的快捷键。idaapi.action_desc_t(action.name, action.description, action, action.hotkey))def initialize(self):passdef finalize(self):for action in self.__actions:idaapi.unregister_action(action.name)# 可以通过 register函数注册动作
action_manager ActionManager()#这是一个基类。它继承自 idaapi.action_handler_t 类并定义了 activate 和 update 方法作为接口但是这些方法都是抛出了 NotImplementedError 异常需要在子类中进行实现。此外该类还定义了 name 属性用于返回一个标识名称方便在 IDA 中进行注册和调用
class Action(idaapi.action_handler_t):Convenience wrapper with name property allowing to be registered in IDA using ActionManagerdescription Nonehotkey Nonedef __init__(self):super(Action, self).__init__()propertydef name(self):return FridaIDA: type(self).__name__# 当从菜单、弹出菜单、工具栏或以编程方式触发操作时会调用它def activate(self, ctx):# type: (idaapi.action_activation_ctx_t) - Noneraise NotImplementedError# 当UI的上下文发生更改时会调用此函数def update(self, ctx):# type: (idaapi.action_activation_ctx_t) - Noneraise NotImplementedError############################################################################
import ida_funcs
import idc
import json
import osfrom PyQt5 import QtCore
from PyQt5.Qt import QApplication
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QTextEdit# [offset] offset of target function in hex value format.
# [funcname] function name
# [filename] input file name of IDA. e.g. xxx.so / xxx.exe
default_template
(function () {// ts-ignorefunction print_arg(addr) {try {var module Process.findRangeByAddress(addr);if (module ! null) return \\nhexdump(addr) \\n;return ptr(addr) \\n;} catch (e) {return addr \\n;}}// ts-ignorefunction hook_native_addr(funcPtr, paramsNum) {var module Process.findModuleByAddress(funcPtr);try {Interceptor.attach(funcPtr, {onEnter: function (args) {this.logs ;this.params [];// ts-ignorethis.logsthis.logs.concat(So: module.name Method: [funcname] offset: ptr(funcPtr).sub(module.base) \\n);for (let i 0; i paramsNum; i) {this.params.push(args[i]);this.logsthis.logs.concat(this.args i onEnter: print_arg(args[i]));}}, onLeave: function (retval) {for (let i 0; i paramsNum; i) {this.logsthis.logs.concat(this.args i onLeave: print_arg(this.params[i]));}this.logsthis.logs.concat(retval onLeave: print_arg(retval) \\n);console.log(this.logs);}});} catch (e) {console.log(e);}}// ts-ignorehook_native_addr(Module.findBaseAddress([filename]).add([offset]), [nargs]);
})();
# 配置类 设置和存储配置信息的方法
class Configuration:def __init__(self) - None:self.frida_cmd frida -U --attach-namecom.example.app -l gen.js --no-pauseself.template default_templateif os.path.exists(IDAFrida.json):self.load()def set_frida_cmd(self, s):self.frida_cmd sself.store()def set_template(self, s):self.template sself.store()def reset(self):self.__init__()def store(self):try:data {frida_cmd: self.frida_cmd, template: self.template}open(IDAFrida.json, w).write(json.dumps(data))except Exception as e:print(e)def load(self):try:data json.loads(open(IDAFrida.json, r).read())self.frida_cmd data[frida_cmd]self.template data[template]except Exception as e:print(e)global_config Configuration()# 修改配置UI 界面类
class ConfigurationUI(QDialog):def __init__(self, conf: Configuration) - None:super(ConfigurationUI, self).__init__()self.conf confself.edit_template QTextEdit()self.edit_template.setPlainText(self.conf.template)layout QHBoxLayout()layout.addWidget(self.edit_template)self.setLayout(layout)def closeEvent(self, a0) - None:self.conf.set_template(self.edit_template.toPlainText())self.conf.store()return super().closeEvent(a0)# 脚本生成类
class ScriptGenerator:def __init__(self, configuration: Configuration) - None:self.conf configurationself.imagebase idaapi.get_imagebase()staticmethoddef get_idb_filename():return os.path.basename(idaapi.get_input_file_path())staticmethoddef get_idb_path():return os.path.dirname(idaapi.get_input_file_path())# 根据地址获取函数的名字def get_function_name(self,ea): # https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtmlGet the real function name# Try to demanglefunction_name idc.demangle_name(idc.get_func_name(ea), idc.get_inf_attr(idc.INF_SHORT_DN))# if function_name:# function_name function_name.split(()[0]# Function name is not mangledif not function_name:function_name idc.get_func_name(ea)if not function_name:function_name idc.get_name(ea, ida_name.GN_VISIBLE)# If we still have no function name, make one up. Format is - UNKN_FNC_4120000if not function_name:function_name UNKN_FNC_%s % hex(ea)return function_name#替换模板中某些字符串def generate_stub(self, repdata: dict):s self.conf.templatefor key, v in repdata.items():s s.replace([%s] % key, v)return s# 根据地址列表来生成模板def generate_for_funcs(self, func_addr_list) - str:stubs []for func_addr in func_addr_list:dec_func idaapi.decompile(func_addr)repdata {filename: self.get_idb_filename(),funcname: self.get_function_name(func_addr),offset: hex(func_addr - self.imagebase),nargs: hex(dec_func.type.get_nargs())}stubs.append(self.generate_stub(repdata))return \n.join(stubs)# 把生成frida脚本 保存到文件 并且置剪辑版def generate_for_funcs_to_file(self, func_addr_list, filename) - bool:data self.generate_for_funcs(func_addr_list)try:open(filename, w).write(data)print(The generated Frida script has been exported to the file: , filename)except Exception as e:print(e)return Falsetry:QApplication.clipboard().setText(data)print(The generated Frida script has been copied to the clipboard!)except Exception as e:print(e)return Falsereturn Trueclass Frida:def __init__(self, conf: Configuration) - None:self.conf conf# 菜单动作类 该方法首先判断当前窗口是否是函数列表、伪代码或反汇编窗口通过检查 ctx.form_type 属性如果是则将当前菜单项关联到右键弹出菜单并返回 AST_ENABLE_FOR_WIDGET否则返回 AST_DISABLE_FOR_WIDGET。 如果菜单项与右键弹出菜单关联成功用户就可以在相应的窗口右键单击并选择该菜单项来触发菜单操作。在 activate 方法中可以实现具体的菜单操作逻辑。
class IDAFridaMenuAction(Action):TopDescription IDAFridadef __init__(self):super(IDAFridaMenuAction, self).__init__()def activate(self, ctx) - None:raise NotImplementeddef update(self, ctx) - None:if ctx.form_type idaapi.BWN_FUNCS or ctx.form_typeidaapi.BWN_PSEUDOCODE or ctx.form_typeidaapi.BWN_DISASM:idaapi.attach_action_to_popup(ctx.widget, None, self.name, self.TopDescription /)return idaapi.AST_ENABLE_FOR_WIDGETreturn idaapi.AST_DISABLE_FOR_WIDGET# 继承菜单动作类
class GenerateFridaHookScript(IDAFridaMenuAction):description Generate Frida Scriptdef __init__(self):super(GenerateFridaHookScript, self).__init__()#在activate方法中使用ScriptGenerator类生成Frida脚本并将其写入到文件中。如果当前活动窗口类型是函数窗口BWN_FUNCS则选择被选中的函数否则只选择当前屏幕所在函数作为目标。最后调用gen.generate_for_funcs_to_file方法生成脚本并写入到指定的文件中def activate(self, ctx):gen ScriptGenerator(global_config)idb_path os.path.dirname(idaapi.get_input_file_path())out_file os.path.join(idb_path, IDAhook.js)if ctx.form_typeidaapi.BWN_FUNCS:selected [idaapi.getn_func(idx).start_ea for idx in ctx.chooser_selection] #from idaapi.getn_func(idx - 1) to idaapi.getn_func(idx)else:selected[idaapi.get_func(idaapi.get_screen_ea()).start_ea]gen.generate_for_funcs_to_file(selected, out_file)class ViewFridaTemplate(IDAFridaMenuAction):description View Frida Templatedef __init__(self):super(ViewFridaTemplate, self).__init__()def activate(self, ctx):ui ConfigurationUI(global_config)ui.show()ui.exec_()class RunGeneratedScript(IDAFridaMenuAction):description Run Generated Scriptdef __init__(self):super(RunGeneratedScript, self).__init__()def activate(self, ctx):print(template)# 设置执行命令
class SetFridaRunCommand(IDAFridaMenuAction):description Set Frida Commanddef __init__(self):super(SetFridaRunCommand, self).__init__()def activate(self, ctx):print(template)action_manager.register(GenerateFridaHookScript())
# action_manager.register(RunGeneratedScript())
action_manager.register(ViewFridaTemplate())
# action_manager.register(SetFridaRunCommand())总的来说这个脚本实现了一个较为完整的 IDA Pro 和 Frida 集成工具并提供了一些 UI 功能和便捷的函数接口使得逆向工程师能够更加高效地进行动态调试和分析。
MyFridaPlugin
通过上面两个例子我们已经学习了frida模板的生成 以及如何和ida的ui交互 这样我们就可以自己写个插件 整合上面的代码 并且可以添加自己想要的功能 源码: https://github.com/AnxiangLemon/MyIdaFrida