首页
社区
课程
招聘
[原创]IDA&Frida学习
发表于: 2023-3-16 12:17 11902

[原创]IDA&Frida学习

2023-3-16 12:17
11902

第一次学习idapython,偶然间发现了一本秘籍《IDA脚本开发之旅》,这是白龙的系列文章,主要是安卓平台,笔者只是根据他的知识点学习,拓展,可以会稍微提及别的平台。本文并不会贴出他的思路分析,只对于源码进行学习,有兴趣可以加他星球一起学习!

另外本文也学习了无名侠的IDAFrida脚本,有兴趣可以加他星球一起学习!

IDA 静态分析 + Frida Hook,可以让分析变得更加丝滑。

这里我们会学习两个脚本 一个是白龙的 一个是 无名侠的。

这是白龙写的一个Python脚本,用于生成Frida的Hook代码。主要包含了以下几个部分:

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的调用流程:

了解了上面两个知识点,在Hook Native时,我们常常通过Spawn抢占两个较早的时机,1是 JNIOnLoad 前 2是 init_proc 以及 init_array 前 ,就可以通过hook上述函数。

接下来就看源码学习吧、我会在代码中给出详细的注释

ShowFridaCode 这是一个十分简单但是很优秀的脚本 包含了ida界面hook frida语法 模板生成 安卓初始化流程 arm汇编的理解

这是无名侠写的一个Python脚本。这段脚本主要是一个基于 IDA Pro 和 Frida 的集成工具,用于辅助逆向工程师进行动态调试。具体来说,这个工具通过 IDA Pro 分析程序的代码,提取出函数的信息(如函数名、地址、参数数量等),然后使用 Frida 注入 JS 脚本,在运行时拦截这些函数,并在函数进入和返回时打印出函数的参数和返回值。这个工具的具体功能包括:

具体来说,这个脚本实现了以下几个类和函数:

我们直接看源码学习吧 同样我也会给出详细的注释

总的来说,这个脚本实现了一个较为完整的 IDA Pro 和 Frida 集成工具,并提供了一些 UI 功能和便捷的函数接口,使得逆向工程师能够更加高效地进行动态调试和分析。

通过上面两个例子,我们已经学习了frida模板的生成 以及如何和ida的ui交互 这样我们就可以自己写个插件 整合上面的代码 并且可以添加自己想要的功能

图片描述

源码: https://github.com/AnxiangLemon/MyIdaFrida

 
 
 
 
 
 
# 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) {
            $result
            console.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 args"
    else:
        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)
    # 根据是否有返回值判断是否打印retval
    retPrint = "// no return"
    if 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 address
    else:
        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 = False
    return 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 initialized
            if not initialized:
                initialized = True
                generateInitCode()
            address = idaapi.get_screen_ea()
            generate_snippet(address)
 
        # 监听单击事件 获取开始和结束地址 进行dump
    def 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 - start
                generate_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 myViewHook
        myViewHook = Hook()
        myViewHook.hook()
 
    # 插件卸载退出的时机
    # 这里适合做资源释放
    def term(self):
        pass
 
 
initialized = False
# register IDA plugin
 
 
def PLUGIN_ENTRY():
    return GenFrida_Plugin_t()
# 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) {
            $result
            console.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 args"
    else:
        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)
    # 根据是否有返回值判断是否打印retval
    retPrint = "// no return"
    if 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 address
    else:
        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 = False
    return 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 initialized
            if not initialized:
                initialized = True
                generateInitCode()
            address = idaapi.get_screen_ea()
            generate_snippet(address)
 
        # 监听单击事件 获取开始和结束地址 进行dump
    def 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 - start
                generate_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 myViewHook
        myViewHook = Hook()
        myViewHook.hook()
 
    # 插件卸载退出的时机
    # 这里适合做资源释放
    def term(self):
        pass
 
 
initialized = False
# register IDA plugin
 
 
def PLUGIN_ENTRY():
    return GenFrida_Plugin_t()
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):
        pass
 
    def 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 ActionManager
    """
    description = None
    hotkey = None
 
    def __init__(self):
        super(Action, self).__init__()
 
    @property
    def name(self):
        return "FridaIDA:" + type(self).__name__
 
    # 当从菜单、弹出菜单、工具栏或以编程方式触发操作时,会调用它
    def activate(self, ctx):
        # type: (idaapi.action_activation_ctx_t) -> None
        raise NotImplementedError
    # 当UI的上下文发生更改时会调用此函数
    def update(self, ctx):
        # type: (idaapi.action_activation_ctx_t) -> None
        raise NotImplementedError
 
############################################################################
import ida_funcs
import idc
import json
import os
 
from 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-ignore
    function print_arg(addr) {
        try {
            var module = Process.findRangeByAddress(addr);
            if (module != null) return "\\n"+hexdump(addr) + "\\n";
            return ptr(addr) + "\\n";
        } catch (e) {
            return addr + "\\n";
        }
    }
    // @ts-ignore
    function hook_native_addr(funcPtr, paramsNum) {
        var module = Process.findModuleByAddress(funcPtr);
        try {
            Interceptor.attach(funcPtr, {
                onEnter: function (args) {
                    this.logs = "";
                    this.params = [];
                    // @ts-ignore
                    this.logs=this.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.logs=this.logs.concat("this.args" + i + " onEnter: " + print_arg(args[i]));
                    }
                }, onLeave: function (retval) {
                    for (let i = 0; i < paramsNum; i++) {
                        this.logs=this.logs.concat("this.args" + i + " onLeave: " + print_arg(this.params[i]));
                    }
                    this.logs=this.logs.concat("retval onLeave: " + print_arg(retval) + "\\n");
                    console.log(this.logs);
                }
            });
        } catch (e) {
            console.log(e);
        }
    }
    // @ts-ignore
    hook_native_addr(Module.findBaseAddress("[filename]").add([offset]), [nargs]);
})();
"""
 
# 配置类: 设置和存储配置信息的方法
class Configuration:
    def __init__(self) -> None:
        self.frida_cmd = """frida -U --attach-name="com.example.app" -l gen.js --no-pause"""
        self.template = default_template
        if os.path.exists("IDAFrida.json"):
            self.load()
 
    def set_frida_cmd(self, s):
        self.frida_cmd = s
        self.store()
 
    def set_template(self, s):
        self.template = s
        self.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 = conf
        self.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 = configuration
        self.imagebase = idaapi.get_imagebase()
 
    @staticmethod
    def get_idb_filename():
        return os.path.basename(idaapi.get_input_file_path())
 
    @staticmethod
    def 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.shtml
        """
        Get the real function name
        """
        # Try to demangle
        function_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 mangled
        if 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_4120000'
        if not function_name:
            function_name = "UNKN_FNC_%s" % hex(ea)
 
        return function_name
 
    #替换模板中某些字符串
    def generate_stub(self, repdata: dict):
        s = self.conf.template
        for 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 False
        try:
            QApplication.clipboard().setText(data)
            print("The generated Frida script has been copied to the clipboard!")
        except Exception as e:
            print(e)
            return False
        return True
 
 
class 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 = "IDAFrida"
 
    def __init__(self):
        super(IDAFridaMenuAction, self).__init__()
 
    def activate(self, ctx) -> None:
        raise NotImplemented
 
    def update(self, ctx) -> None:
        if ctx.form_type == idaapi.BWN_FUNCS or ctx.form_type==idaapi.BWN_PSEUDOCODE or ctx.form_type==idaapi.BWN_DISASM:
            idaapi.attach_action_to_popup(ctx.widget, None, self.name, self.TopDescription + "/")
            return idaapi.AST_ENABLE_FOR_WIDGET
        return idaapi.AST_DISABLE_FOR_WIDGET
 
 
# 继承菜单动作类
class GenerateFridaHookScript(IDAFridaMenuAction):
    description = "Generate Frida Script"
 
    def __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_type==idaapi.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 Template"
 
    def __init__(self):
        super(ViewFridaTemplate, self).__init__()
 
    def activate(self, ctx):
        ui = ConfigurationUI(global_config)
        ui.show()
        ui.exec_()
 
class RunGeneratedScript(IDAFridaMenuAction):
    description = "Run Generated Script"
 
    def __init__(self):
        super(RunGeneratedScript, self).__init__()
 
    def activate(self, ctx):
        print("template")
 
# 设置执行命令
class SetFridaRunCommand(IDAFridaMenuAction):
    description = "Set Frida Command"
 
    def __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())
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)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (3)
雪    币: 8895
活跃值: (5116)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jgs
2
收藏,学习,谢谢楼主分享
2023-3-16 12:23
0
雪    币: 1802
活跃值: (4000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习,感谢分享
2023-3-17 11:00
0
雪    币: 38
活跃值: (1907)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4

学习,感谢分享

最后于 2023-6-2 18:13 被快乐的小跳蛙编辑 ,原因:
2023-5-31 14:31
0
游客
登录 | 注册 方可回帖
返回
//