首页
社区
课程
招聘
[原创]大众DP BR X8跳转清理 && a5分析
发表于: 2025-1-7 11:02 2131

[原创]大众DP BR X8跳转清理 && a5分析

2025-1-7 11:02
2131

之前的帖子清理了jumpout,本文尝试静态分析和利用frida来分析mtgsig参数。

定位算法的步骤这里就省略了,直接从一个hook结果开始分析:

从上面结果可以看出一些很长的猜测很关键的字段base64字符串,所以直接利用ida的插件搜索算法特征。
图片描述
利用上面特征可以定位到函数sub_4cf88, 利用frida hook验证参数中的字段是否出现在此函数中:

通过验证分析看到a5参数调用了此函数,所以分析a5:
图片描述
利用堆栈轻松找到调用base64的位置0xfe538:
图片描述
从上图可以看出确实调用了base64函数,但是此位置无法使用f5生成伪C代码,从这里向前翻查找最近的函数序言或者可能函数,查看为什么后面无法被f5,找到函数0xfdaa4如下图所示:
图片描述
图片描述
是一个BR X8的跳转,ida无法计算x8的值所以没有办法继续向下分析。

仔细分析跳转位置的汇编指令,分析跳转的逻辑:

简单阅读上面的指令,这个br逻辑可以利用现有的信息计算,看着很像是基于跳转表的跳转。用unicron模拟测试一下:

利用上面模拟可以看出,是从so文件中获取跳转表,并根据不同的条件计算出不同的地址。所以这部分可以恢复,起码可以部分恢复。接下来则是需要分析这种跳转模式在当前函数中有多少,然后识别出来手动计算分支清理出跳转地址。首先需要确定当前0xfdaa4函数的范围,可以利用函数的特征或者是其他手段确认,这里可以尝试利用跳转表x22的特性确认,x22的值一般在同一个函数内是不会变化的,所以直接从函数开始搜索和x22修改读取相关的汇编指令:

结果就不展示了,利用上面函数的搜索脚本可以分析出相邻函数0x100cac, elf
文件一般函数和函数之间不会出现交叉,除非有内联函数或者是其他编译优化策略共用代码块,这里就当一般情况处理。函数范围已经确定,并且通过观察ldr读取跳转表指令可以分析出都是通过x22 + 偏移量的方式寻址,这里只有偏移量计算方式的不同:
1. 立即数
2. 寄存器
3. 寄存器移位操作,这里很单一,只有LSL#3

同时按照上面的寻址方式静态分析,可以看到还有一种其他跳转方式:
图片描述
这种方式比较简单,直接读取基地址,然后加减偏移量,没有条件选择。
接下来则是通过搜索匹配这些跳转位置收集信息计算地址然后patch。
首先搜索BR Xd语句:

通过上面函数收集到跳转地址之后,需要利用特征收集关键信息,将之前的两种跳转分类成条件跳转case1和无条件跳转case2.case1关键信息特征为:

case2关键信息特征为:

通过上面总结的特征完成信息收集:

通过上面函数收集到关键信息计算条件跳转地址和无条件跳转地址,并且给出patch位置。所以case1的patch思路是:

case2的patch思路:

利用前面的信息计算跳转地址

上面的寄存器值从之前的unicorn模拟执行中可以找到,至此差不多就完全将逻辑替换完成了,只剩最后一步将语句patch进去

清理之后的结果
图片描述

清理完成之后这个函数大致就可以静态分析。回到最初调用base64函数的部分:
图片描述

直接hook函数sub_4cf88和函数sub_68968因为两个函数都有统一个参数v154:

hook结果对比:
图片描述

可以看出sub_68968的结果和base64函数的输入一致。进入函数sub_68968看到如下:
图片描述
进入函数sub_10f0f8:
图片描述
看着好像是rc4加密,搜索一份c语言rc4加密函数对比:
图片描述
图片描述
对比起来看真的很像,如果是rc4,那么result应该是sbox,接着看函数sub_68958,hook验证
图片描述

那么sub_68958极有可能是rc4 init函数:
图片描述
网上搜索rc4 init函数:
图片描述
好像在rc4 init 过程中多加了一个下标i。先看看key是啥,然后写代码验证魔改位置, 从网上抄个python版本的rc4 init:

图片描述
将上面的key带入到修改的rc4 init中:得到如下结果:
图片描述
验证通过的确是修改了rc4 init函数中的部分。接着看rc4的输入数据然后统一验证。继续向上查看sub_34844函数是和输入相关的
图片描述
图片描述
从上面可以看出是一个json字符串应该是一些设备信息相关的压缩后得到的,开头两个字节zlib的默认压缩方式。接着需要看rc4的key是什么,因为每次hook输入的key都是变化的。
图片描述
所以函数sub_683ec应该是计算rc4 key的,hook验证:
图片描述
图片描述
两个函数的数据对比没有问题确实如此。至此算法流程还是比较清晰的,下面用python简单验证一下hook日志中的数据。首先是rc4 算法key的生成:
图片描述
从上图看着是和v13异或得到,那么将输入和结果异或即可得到v13:

取多次hook结果验证这个在同一个机器上起码是不变的。

a5 第一版

mtgsig = {
    "a0": "3.0",
    "a1": "4069cb78-e02b-45f6-9f0a-b34ddccf389c",
    "a3": 24,
    "a4": 1736155982,
    "a5": "AHepznjTBvV7rHiYm8hD1+169lwTCD+PNAA6LuQx6hGIgKX2FHR3ADYlwF38q/j8dk6sKm+PO57A3AKiYH4RX1FE7Cyy8UkEwWceQURrxfHX2O7LqWtfwYCsTtE0kUoXW4aGxaAiAagM2KtS81ff/NZUgIYvdaLeiljIXbHGV+6QtbotS6/ClGky4cd1RHy7GZsJ8ubecJjv4QKLxAq4cHgsro4z9Iboi4gYCi+dTStYrEWEPRAhk9VPX3oQHeqlpRrqxGQxCbGUWjjEi/CNjuuF7W8xEnTNq0zOY/YO",
    "a6": 0,
    "a7": "jSAxPWen3i2zs6omj1rD+uRsiQhiPxEE1CA+76YkCHDQqmp25ELpd3PUR0uWoNZ0eAc0RLxbkZrYsmQwKjqe27lZZ8o95TEOOSQLRo8bHYA=",
    "a8": "742b25deffb779f5ed08910789926e676ca5f078d42b28d5c7dd370c",
    "a9": "dd051dd5C+3hWx/JTxImXsb+pQWQL/bklyGy4z5sN8nq7p/2yW7SAN9GsygMJe3E3IzoY912YiZ0iCnsGiPPRB6lrUP2uehxqbJlpkuyNQwe7Eh5WcvUlzYW39m7Rh5PNTZhoAUnM7NBMbYqz+WK2hslAUX8mSaQJyebUz8jOxMc7RAdK7SOXFxNX+Mcs/RN2nED0lzB4ZF/I8CFMZHjE5cBAKpeXsQaYalSlK9D50zxK/0sjdlc/knfNcMFq5hFfYz8UDPP4OCeHI/U5YcgrsbgxDH41Qs6bmBF+5YoHT+pKsxc3uc=",
    "a10": "5,155,1.1.1",
    "x0": 1,
    "a2": "673fb25da2fb9e3cb2eff5b39b152559"
}
mtgsig = {
    "a0": "3.0",
    "a1": "4069cb78-e02b-45f6-9f0a-b34ddccf389c",
    "a3": 24,
    "a4": 1736155982,
    "a5": "AHepznjTBvV7rHiYm8hD1+169lwTCD+PNAA6LuQx6hGIgKX2FHR3ADYlwF38q/j8dk6sKm+PO57A3AKiYH4RX1FE7Cyy8UkEwWceQURrxfHX2O7LqWtfwYCsTtE0kUoXW4aGxaAiAagM2KtS81ff/NZUgIYvdaLeiljIXbHGV+6QtbotS6/ClGky4cd1RHy7GZsJ8ubecJjv4QKLxAq4cHgsro4z9Iboi4gYCi+dTStYrEWEPRAhk9VPX3oQHeqlpRrqxGQxCbGUWjjEi/CNjuuF7W8xEnTNq0zOY/YO",
    "a6": 0,
    "a7": "jSAxPWen3i2zs6omj1rD+uRsiQhiPxEE1CA+76YkCHDQqmp25ELpd3PUR0uWoNZ0eAc0RLxbkZrYsmQwKjqe27lZZ8o95TEOOSQLRo8bHYA=",
    "a8": "742b25deffb779f5ed08910789926e676ca5f078d42b28d5c7dd370c",
    "a9": "dd051dd5C+3hWx/JTxImXsb+pQWQL/bklyGy4z5sN8nq7p/2yW7SAN9GsygMJe3E3IzoY912YiZ0iCnsGiPPRB6lrUP2uehxqbJlpkuyNQwe7Eh5WcvUlzYW39m7Rh5PNTZhoAUnM7NBMbYqz+WK2hslAUX8mSaQJyebUz8jOxMc7RAdK7SOXFxNX+Mcs/RN2nED0lzB4ZF/I8CFMZHjE5cBAKpeXsQaYalSlK9D50zxK/0sjdlc/knfNcMFq5hFfYz8UDPP4OCeHI/U5YcgrsbgxDH41Qs6bmBF+5YoHT+pKsxc3uc=",
    "a10": "5,155,1.1.1",
    "x0": 1,
    "a2": "673fb25da2fb9e3cb2eff5b39b152559"
}
// hook java 入口函数
function hook_main3() {
    var Arrays = Java.use("java.util.Arrays");
    var ShellBridge = Java.use("com.meituan.android.common.mtguard.ShellBridge");
    ShellBridge.main3.implementation = function (i, objArr) {
        console.log("hook ShellBridge.main3");
        var ret = this.main3(i, objArr);
        console.log(objArr.length);
        console.log(objArr[0]);
        // var arr = Arrays.asList(objArr);
        // for (var i; i < arr.size(); i++) {
        //     var item = arr.get(i);
        //     console.log(item);
        // }
        console.log(`ShellBridge.main3args:  ${i}\n${objArr}\nret: ${ret}\n`);
        return ret;
    }
}
 
 
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var path_ptr = args[0];
            if (path_ptr != undefined && path_ptr != null) {
                var path = ptr(path_ptr).readCString();
                console.log("[LOAD] " + path);
                if (path.indexOf("libmtguard.so") != -1) {
                    this.canHook = true;
                }
            }
        },
        onLeave: function (retval) {
            if (this.canHook) {
                // 这里添加hook native的函数
                hook_4cf88(); // base64 1
                hook_68958();  // rc4 init
                hook_68968();  // rc4 enc
                hook_683ec();   // rc4 key related
                hook_34844();  // rc4 input
            }
        }
    })
}
 
// hook base64相关算法
function hook_4cf88() {
    var base = Module.findBaseAddress("libmtguard.so");
    console.log(`libmtguard.so base: ${base}`);
    Interceptor.attach(base.add(0x4cf88), {
        onEnter: function (args) {
            console.log("call 0x4cf88");
            console.log(`sub_4cf88 args: ${args[0]}, ${args[1]}, ${args[2]}`);
            console.log(hexdump(args[0]));
            // 尝试打印native堆栈
            console.log(`sub_4cf88 native stack: ${Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n'}`);
        },
        onLeave: function (retval) {
            console.log(`sub_4cf88 ret: ${retval}\n`)
        }
    })
}
// hook java 入口函数
function hook_main3() {
    var Arrays = Java.use("java.util.Arrays");
    var ShellBridge = Java.use("com.meituan.android.common.mtguard.ShellBridge");
    ShellBridge.main3.implementation = function (i, objArr) {
        console.log("hook ShellBridge.main3");
        var ret = this.main3(i, objArr);
        console.log(objArr.length);
        console.log(objArr[0]);
        // var arr = Arrays.asList(objArr);
        // for (var i; i < arr.size(); i++) {
        //     var item = arr.get(i);
        //     console.log(item);
        // }
        console.log(`ShellBridge.main3args:  ${i}\n${objArr}\nret: ${ret}\n`);
        return ret;
    }
}
 
 
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var path_ptr = args[0];
            if (path_ptr != undefined && path_ptr != null) {
                var path = ptr(path_ptr).readCString();
                console.log("[LOAD] " + path);
                if (path.indexOf("libmtguard.so") != -1) {
                    this.canHook = true;
                }
            }
        },
        onLeave: function (retval) {
            if (this.canHook) {
                // 这里添加hook native的函数
                hook_4cf88(); // base64 1
                hook_68958();  // rc4 init
                hook_68968();  // rc4 enc
                hook_683ec();   // rc4 key related
                hook_34844();  // rc4 input
            }
        }
    })
}
 
// hook base64相关算法
function hook_4cf88() {
    var base = Module.findBaseAddress("libmtguard.so");
    console.log(`libmtguard.so base: ${base}`);
    Interceptor.attach(base.add(0x4cf88), {
        onEnter: function (args) {
            console.log("call 0x4cf88");
            console.log(`sub_4cf88 args: ${args[0]}, ${args[1]}, ${args[2]}`);
            console.log(hexdump(args[0]));
            // 尝试打印native堆栈
            console.log(`sub_4cf88 native stack: ${Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n'}`);
        },
        onLeave: function (retval) {
            console.log(`sub_4cf88 ret: ${retval}\n`)
        }
    })
}
.text:00000000000FDB0C    CMP     X27, #0
赋值,和后面的条件选择分支有关
.text:00000000000FDB10    MOV     W8, #0x30 ; '0'
.text:00000000000FDB14    MOV     W9, #0x310
x22 赋值
.text:00000000000FDB18    ADRP    X22, #0x212000
条件选择分支,和读取跳转表寻址相关
.text:00000000000FDB1C    CSEL    X8, X9, X8, EQ
和读取内存寻址相关,x22看着像是跳转表地址
.text:00000000000FDB20    ADD     X22, X22, #0xB0
读取内存,和跳转相关
.text:00000000000FDB24    LDR     X8, [X22,X8]
赋值,和条件选择语句不同条件相关
.text:00000000000FDB28    MOV     W9, #0x433
.text:00000000000FDB2C    MOV     W10, #0x488F
条件选择语句,和计算跳转地址相关
.text:00000000000FDB30    CSEL    X9, X10, X9, EQ
计算 x8 = x8 - x9 和跳转寄存器相关
.text:00000000000FDB34    SUB     X8, X8, X9
保存x27寄存器的值和跳转指令没有什么关系
.text:00000000000FDB38    STR     X27, [SP,#0x710+var_5E8]
下面是跳转指令
.text:00000000000FDB3C    BR      X8
.text:00000000000FDB0C    CMP     X27, #0
赋值,和后面的条件选择分支有关
.text:00000000000FDB10    MOV     W8, #0x30 ; '0'
.text:00000000000FDB14    MOV     W9, #0x310
x22 赋值
.text:00000000000FDB18    ADRP    X22, #0x212000
条件选择分支,和读取跳转表寻址相关
.text:00000000000FDB1C    CSEL    X8, X9, X8, EQ
和读取内存寻址相关,x22看着像是跳转表地址
.text:00000000000FDB20    ADD     X22, X22, #0xB0
读取内存,和跳转相关
.text:00000000000FDB24    LDR     X8, [X22,X8]
赋值,和条件选择语句不同条件相关
.text:00000000000FDB28    MOV     W9, #0x433
.text:00000000000FDB2C    MOV     W10, #0x488F
条件选择语句,和计算跳转地址相关
.text:00000000000FDB30    CSEL    X9, X10, X9, EQ
计算 x8 = x8 - x9 和跳转寄存器相关
.text:00000000000FDB34    SUB     X8, X8, X9
保存x27寄存器的值和跳转指令没有什么关系
.text:00000000000FDB38    STR     X27, [SP,#0x710+var_5E8]
下面是跳转指令
.text:00000000000FDB3C    BR      X8
# -*- coding: utf-8 -*-
 
from loguru import logger
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM
from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN
from unicorn.arm64_const import UC_ARM64_REG_SP, UC_ARM64_REG_X27, UC_ARM64_REG_X8
from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM, UC_HOOK_CODE, UC_HOOK_MEM_READ_UNMAPPED, UC_HOOK_MEM_READ
 
 
# unicorn虚拟机初始化
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
 
 
# 内存区域初始化
BASE_ADDR = 0x40000000
BASE_SIZE = 4 * 1024 * 1024 # 4MB
mu.mem_map(BASE_ADDR, BASE_SIZE)
 
# 堆栈初始化
STACK_ADDR = 0x10000000
STACK_SIZE = 0x00100000
mu.mem_map(STACK_ADDR, STACK_SIZE)
logger.debug(f"Memory map {hex(STACK_ADDR)} - {hex(STACK_ADDR + STACK_SIZE)}")
mu.reg_write(UC_ARM64_REG_SP, STACK_ADDR + STACK_SIZE)
 
# 模拟执行汇编指令
ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
 
assembly_code = ';'.join([
    'CMP             X27, #0',
    'MOV             W8, #0x30',
    'MOV             W9, #0x310',
    'ADRP            X22, #0x212000',
    'CSEL            X8, X9, X8, EQ',
    'ADD             X22, X22, #0xB0',
    'LDR             X8, [X22,X8]',
    'MOV             W9, #0x433',
    'MOV             W10, #0x488F',
    'CSEL            X9, X10, X9, EQ',
    'SUB             X8, X8, X9',
    # 'STR             X27, [SP,#0x128]',
])
 
# 指令转字节
encoding, count = ks.asm(assembly_code.strip())
logger.debug(f"Assembly code {bytes(encoding).hex()}, bytes count {count}")
 
 
# 将代码写入内存
mu.mem_write(BASE_ADDR, bytes(encoding))
 
# hook 指令
def hook_code(uc: Uc, address: int, size: int, user_data):
    # 尝试读取指令
    inst = uc.mem_read(address, size)
    # capstone 反汇编尝试
    md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
    for i in md.disasm(inst, address):
        logger.debug(f">>> {hex(i.address)}:\t{i.mnemonic}\t{i.op_str}")
 
    # 修改读取内存的值
    # if address == 0x4000001c:
    #     logger.debug(f"change x8 value")
        # uc.reg_write(UC_ARM64_REG_X8, 0xff68b)
 
mu.hook_add(UC_HOOK_CODE, hook_code)
 
# hook 读取内存
def hook_mem_read(uc: Uc, access: int, address: int, size: int, value, user_data):
    uc.mem_write(address, int.to_bytes(1046155, 8, byteorder='little'))
    data = uc.mem_read(address, size)
    logger.debug(f">>> Tracing memory read at {hex(address)}, size: {hex(size)}, data: {data.hex()}")
     
 
mu.hook_add(UC_HOOK_MEM_READ, hook_mem_read)
 
# 启动前修改部分寄存器的值
mu.reg_write(UC_ARM64_REG_X27, 1)
 
# 启动
mu.emu_start(BASE_ADDR, BASE_ADDR + (4 * count))  # 这里只执行一句指令。
x8_value = mu.reg_read(UC_ARM64_REG_X8)
logger.debug(f"x8 value: {hex(x8_value)}")
# -*- coding: utf-8 -*-
 
from loguru import logger
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM
from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN
from unicorn.arm64_const import UC_ARM64_REG_SP, UC_ARM64_REG_X27, UC_ARM64_REG_X8
from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM, UC_HOOK_CODE, UC_HOOK_MEM_READ_UNMAPPED, UC_HOOK_MEM_READ
 
 
# unicorn虚拟机初始化
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
 
 
# 内存区域初始化
BASE_ADDR = 0x40000000
BASE_SIZE = 4 * 1024 * 1024 # 4MB
mu.mem_map(BASE_ADDR, BASE_SIZE)
 
# 堆栈初始化
STACK_ADDR = 0x10000000
STACK_SIZE = 0x00100000
mu.mem_map(STACK_ADDR, STACK_SIZE)
logger.debug(f"Memory map {hex(STACK_ADDR)} - {hex(STACK_ADDR + STACK_SIZE)}")
mu.reg_write(UC_ARM64_REG_SP, STACK_ADDR + STACK_SIZE)
 
# 模拟执行汇编指令
ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
 
assembly_code = ';'.join([
    'CMP             X27, #0',
    'MOV             W8, #0x30',
    'MOV             W9, #0x310',
    'ADRP            X22, #0x212000',
    'CSEL            X8, X9, X8, EQ',
    'ADD             X22, X22, #0xB0',
    'LDR             X8, [X22,X8]',
    'MOV             W9, #0x433',
    'MOV             W10, #0x488F',
    'CSEL            X9, X10, X9, EQ',
    'SUB             X8, X8, X9',
    # 'STR             X27, [SP,#0x128]',
])
 
# 指令转字节
encoding, count = ks.asm(assembly_code.strip())
logger.debug(f"Assembly code {bytes(encoding).hex()}, bytes count {count}")
 
 
# 将代码写入内存
mu.mem_write(BASE_ADDR, bytes(encoding))
 
# hook 指令
def hook_code(uc: Uc, address: int, size: int, user_data):
    # 尝试读取指令
    inst = uc.mem_read(address, size)
    # capstone 反汇编尝试
    md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
    for i in md.disasm(inst, address):
        logger.debug(f">>> {hex(i.address)}:\t{i.mnemonic}\t{i.op_str}")
 
    # 修改读取内存的值
    # if address == 0x4000001c:
    #     logger.debug(f"change x8 value")
        # uc.reg_write(UC_ARM64_REG_X8, 0xff68b)
 
mu.hook_add(UC_HOOK_CODE, hook_code)
 
# hook 读取内存
def hook_mem_read(uc: Uc, access: int, address: int, size: int, value, user_data):
    uc.mem_write(address, int.to_bytes(1046155, 8, byteorder='little'))
    data = uc.mem_read(address, size)
    logger.debug(f">>> Tracing memory read at {hex(address)}, size: {hex(size)}, data: {data.hex()}")
     
 
mu.hook_add(UC_HOOK_MEM_READ, hook_mem_read)
 
# 启动前修改部分寄存器的值
mu.reg_write(UC_ARM64_REG_X27, 1)
 
# 启动
mu.emu_start(BASE_ADDR, BASE_ADDR + (4 * count))  # 这里只执行一句指令。
x8_value = mu.reg_read(UC_ARM64_REG_X8)
logger.debug(f"x8 value: {hex(x8_value)}")
# 1. 搜索跳转表寄存器X22范围
def get_jump_table_addr(min_addr, max_addr):
    cur_addr = min_addr
    while True:
        next_ea = idc.next_head(cur_addr, max_addr)
        if next_ea == idc.BADADDR:
            break
        insn = idc.GetDisasm(next_ea)
        if "X22" in insn or "W22" in insn:
            tmp = insn.split()
            if tmp[0] in ["STP", "ADD", "ADRP", "ADRL", "SUB"]:
                print(f"{hex(next_ea)}: {insn}, function address: {hex(idc.get_func_attr(next_ea, idc.FUNCATTR_START))}")
            if tmp[0] == "LDR":
                print(f"[maybe] {hex(next_ea)}: {insn}")
        cur_addr = next_ea
         
min_addr, max_addr = 0xfdaa4, idc.BADADDR
get_jump_table_addr(min_addr, max_addr)
# 1. 搜索跳转表寄存器X22范围
def get_jump_table_addr(min_addr, max_addr):
    cur_addr = min_addr
    while True:
        next_ea = idc.next_head(cur_addr, max_addr)
        if next_ea == idc.BADADDR:
            break
        insn = idc.GetDisasm(next_ea)
        if "X22" in insn or "W22" in insn:
            tmp = insn.split()
            if tmp[0] in ["STP", "ADD", "ADRP", "ADRL", "SUB"]:
                print(f"{hex(next_ea)}: {insn}, function address: {hex(idc.get_func_attr(next_ea, idc.FUNCATTR_START))}")
            if tmp[0] == "LDR":
                print(f"[maybe] {hex(next_ea)}: {insn}")
        cur_addr = next_ea
         
min_addr, max_addr = 0xfdaa4, idc.BADADDR
get_jump_table_addr(min_addr, max_addr)
# 2. 搜索大致范围内的间接跳转地址
def pattern_search_all(start_ea, end_ea, pattern_str):
    image_base = idaapi.get_imagebase()
    pattern = ida_bytes.compiled_binpat_vec_t()
    err = ida_bytes.parse_binpat_str(pattern, image_base, pattern_str, 16)
    if err:
        print(f"Failed to parse pattern: {err}")
        return
    found_addrs = []
    curr_addr = start_ea
    while True:
        curr_addr, _ = ida_bytes.bin_search(curr_addr, end_ea, pattern, ida_bytes.BIN_SEARCH_FORWARD)
        if curr_addr == idc.BADADDR:
            break
        found_addrs.append(curr_addr)
        curr_addr += 1
    return found_addrs
     
     
# 保存间接跳转地址,方便后面信息收集
br_addrs = []
# 目前范围内暂时只发现这两种间接跳转
pattern_str_1 = "00 01 1F D6"  # br x8
pattern_str_2 = "20 01 1F D6"  # br x9
 
tmp = pattern_search_all(min_addr, max_addr, pattern_str_1)
print(f"length of search result for x8: {len(tmp)}")
br_addrs.extend(tmp)
 
tmp = pattern_search_all(min_addr, max_addr, pattern_str_2)
print(f"length of search result for x9: {len(tmp)}")
br_addrs.extend(tmp)
# 2. 搜索大致范围内的间接跳转地址
def pattern_search_all(start_ea, end_ea, pattern_str):
    image_base = idaapi.get_imagebase()
    pattern = ida_bytes.compiled_binpat_vec_t()
    err = ida_bytes.parse_binpat_str(pattern, image_base, pattern_str, 16)
    if err:
        print(f"Failed to parse pattern: {err}")
        return
    found_addrs = []
    curr_addr = start_ea
    while True:
        curr_addr, _ = ida_bytes.bin_search(curr_addr, end_ea, pattern, ida_bytes.BIN_SEARCH_FORWARD)
        if curr_addr == idc.BADADDR:
            break
        found_addrs.append(curr_addr)
        curr_addr += 1
    return found_addrs
     
     
# 保存间接跳转地址,方便后面信息收集
br_addrs = []
# 目前范围内暂时只发现这两种间接跳转
pattern_str_1 = "00 01 1F D6"  # br x8
pattern_str_2 = "20 01 1F D6"  # br x9
 
tmp = pattern_search_all(min_addr, max_addr, pattern_str_1)
print(f"length of search result for x8: {len(tmp)}")
br_addrs.extend(tmp)
 
tmp = pattern_search_all(min_addr, max_addr, pattern_str_2)
print(f"length of search result for x9: {len(tmp)}")
br_addrs.extend(tmp)
# 3. 从间接跳转地址,收集和跳转相关的数据信息,为后面计算真正跳转地址做准备。
def gather_critical_info(addr):
    # 情况1: 一次比较,两次条件选择的条件跳转分支
    # 情况2:  没有比较直接计算的,直接跳转情况,
    # 这些都是发生在第一次过滤之后。
    cur_addr = addr
    infos = []
    has_csel = False
    has_csel_2 = False
    csel_2_addr = None # 从这里搜索为了防止第一个csel条件赋值出现在第二个csel语句之前
     
    # 倒序分析并添加信息。
    # 1. br x8
    insn = idc.GetDisasm(cur_addr)
    jump_reg = insn.split()[1]
    print(f"process {hex(cur_addr)}, jump register: {jump_reg}")
    infos.append((cur_addr, insn))
    # 2. add / sub x8
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        # print(tmp)
        if tmp[0] in ['SUB', 'ADD'] and jump_reg in tmp[1]:
            cur_addr = prev_ea
            # 记录偏移寄存器
            offset_reg = tmp[3]
            print(f"{hex(cur_addr)}: {insn}, offset register: {offset_reg}")
            infos.append((cur_addr, insn))
            break
        cur_addr = prev_ea
         
    # 3. csel / mov
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        # print(tmp)
        if tmp[0] in ['MOV', 'CSEL'] and offset_reg in tmp[1]: # 是否需要在move上增加更多的判断。
            cur_addr = prev_ea
            if tmp[0] == "CSEL"# 分类处理不同的情况
                has_csel = True
                csel_t, csel_f = tmp[2].replace(',', ''), tmp[3].replace(',', '')
                print(f"{hex(prev_ea)}: {insn}, csel true: {csel_t}, csel false: {csel_f}")
            infos.append((prev_ea, insn))
            break
        cur_addr = prev_ea
         
    # 4. ldr [x22, xxx]
    for _ in range(8):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        # print(tmp)
        # print(f"has_csel: {has_csel}")
        if has_csel:
            if tmp[0] == 'MOV' and (csel_t[1:] in tmp[1] or csel_f[1:] in tmp[1]):
                print(f"[csel] {hex(prev_ea)}: {insn}")
                infos.append((prev_ea, insn))
             
                 
        if tmp[0] == "LDR" and "X22" in tmp[2]:
            print(f"{hex(prev_ea)}: {insn}")
            infos.append((prev_ea, insn))
            if not has_csel:
                print("case 2 finished")
                return infos, has_csel
                return
        #  第二个csel指令
        if tmp[0] == "CSEL":
            csel_2_addr = cur_addr # 先记录地址,后面再搜索相关语句。
        cur_addr = prev_ea
         
    cur_addr = cur_addr if csel_2_addr is None else csel_2_addr
     
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        if tmp[0] == "CSEL":
            cur_addr = prev_ea
            csel_t, csel_f = tmp[2].replace(',', ''), tmp[3].replace(',', '')
            print(f"{hex(prev_ea)}: {insn}, csel true: {csel_t}, csel false: {csel_f}")
            infos.append((prev_ea, insn))
            has_csel_2 = True
             
        if has_csel_2:
            if tmp[0] == 'MOV' and (csel_t[1:] in tmp[1] or csel_f[1:] in tmp[1]):
                print(f"[csel] {hex(prev_ea)}: {insn}")
                infos.append((prev_ea, insn))
        cur_addr = prev_ea  
    # 5. 最开始的csel 真假条件的值
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        print(tmp)
        if tmp[0] == "MOV" and (csel_t[1:] in tmp[1] or csel_f[1:] in tmp[1]):
            print(f"[csel] {hex(prev_ea)}: {insn}")
            infos.append((prev_ea, insn))
        cur_addr = prev_ea
    return infos, has_csel
     
critical_infos = []
error_count = 0
for addr in br_addrs:
    try:
        info, has_csel = gather_critical_info(addr)
        critical_infos.append({"address": addr, "info": info, "has_csel": has_csel})
    except Exception as e:
        error_count += 1
        print(f"[gather] {hex(addr)} critical info error: {e}")
print(f"gather critical info error count: {error_count}")
# 3. 从间接跳转地址,收集和跳转相关的数据信息,为后面计算真正跳转地址做准备。
def gather_critical_info(addr):
    # 情况1: 一次比较,两次条件选择的条件跳转分支
    # 情况2:  没有比较直接计算的,直接跳转情况,
    # 这些都是发生在第一次过滤之后。
    cur_addr = addr
    infos = []
    has_csel = False
    has_csel_2 = False
    csel_2_addr = None # 从这里搜索为了防止第一个csel条件赋值出现在第二个csel语句之前
     
    # 倒序分析并添加信息。
    # 1. br x8
    insn = idc.GetDisasm(cur_addr)
    jump_reg = insn.split()[1]
    print(f"process {hex(cur_addr)}, jump register: {jump_reg}")
    infos.append((cur_addr, insn))
    # 2. add / sub x8
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        # print(tmp)
        if tmp[0] in ['SUB', 'ADD'] and jump_reg in tmp[1]:
            cur_addr = prev_ea
            # 记录偏移寄存器
            offset_reg = tmp[3]
            print(f"{hex(cur_addr)}: {insn}, offset register: {offset_reg}")
            infos.append((cur_addr, insn))
            break
        cur_addr = prev_ea
         
    # 3. csel / mov
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        # print(tmp)
        if tmp[0] in ['MOV', 'CSEL'] and offset_reg in tmp[1]: # 是否需要在move上增加更多的判断。
            cur_addr = prev_ea
            if tmp[0] == "CSEL"# 分类处理不同的情况
                has_csel = True
                csel_t, csel_f = tmp[2].replace(',', ''), tmp[3].replace(',', '')
                print(f"{hex(prev_ea)}: {insn}, csel true: {csel_t}, csel false: {csel_f}")
            infos.append((prev_ea, insn))
            break
        cur_addr = prev_ea
         
    # 4. ldr [x22, xxx]
    for _ in range(8):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        # print(tmp)
        # print(f"has_csel: {has_csel}")
        if has_csel:
            if tmp[0] == 'MOV' and (csel_t[1:] in tmp[1] or csel_f[1:] in tmp[1]):
                print(f"[csel] {hex(prev_ea)}: {insn}")
                infos.append((prev_ea, insn))
             
                 
        if tmp[0] == "LDR" and "X22" in tmp[2]:
            print(f"{hex(prev_ea)}: {insn}")
            infos.append((prev_ea, insn))
            if not has_csel:
                print("case 2 finished")
                return infos, has_csel
                return
        #  第二个csel指令
        if tmp[0] == "CSEL":
            csel_2_addr = cur_addr # 先记录地址,后面再搜索相关语句。
        cur_addr = prev_ea
         
    cur_addr = cur_addr if csel_2_addr is None else csel_2_addr
     
    for _ in range(5):
        prev_ea = idc.prev_head(cur_addr)
        insn = idc.GetDisasm(prev_ea)
        tmp = insn.split()
        if tmp[0] == "CSEL":
            cur_addr = prev_ea
            csel_t, csel_f = tmp[2].replace(',', ''), tmp[3].replace(',', '')
            print(f"{hex(prev_ea)}: {insn}, csel true: {csel_t}, csel false: {csel_f}")
            infos.append((prev_ea, insn))
            has_csel_2 = True
             
        if has_csel_2:
            if tmp[0] == 'MOV' and (csel_t[1:] in tmp[1] or csel_f[1:] in tmp[1]):
                print(f"[csel] {hex(prev_ea)}: {insn}")
                infos.append((prev_ea, insn))
        cur_addr = prev_ea  

[注意]APP应用上架合规检测服务,协助应用顺利上架!

最后于 2025-1-7 11:06 被Tubbs编辑 ,原因:
上传的附件:
收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2025-1-7 11:07
0
雪    币: 1467
活跃值: (3026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
这种优秀贴应该给与精华,过程非常精彩,我相信这才是大家想要的,不像那些吹牛逼只给个答案的假贴,支持楼主
6天前
0
游客
登录 | 注册 方可回帖
返回