-
-
[原创]大众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