0%

目的:过掉bilibili v7.31.0 的frida 检测

apk 源码

什么都不注入,使用frida ,应用直接挂了,那必然是有 frida 的检测了

定位 在哪个 so

先 借助 android_dlopen_ext 确定frida 的检测在哪个so 中

void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* info);

在android 中除了 静态so 或者自定义的 elf 解析器,几乎所有的so 在加载时都会触发 android_dlopen_ext 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook_dlopen() {
//实时监控目标进程自己在运行过程中的 so 加载行为
//确定 frida 检测在哪个 so 中
var dlopen_addr = Module.findExportByName("libdl.so", "android_dlopen_ext");
console.log("dlopen_addr", dlopen_addr);
Interceptor.attach(dlopen_addr, {
onEnter: function (args) {

console.log("args1", args[0].readCString(), "\n");
},
onLeave: function (retval) {

}
})
}

setImmediate(hook_dlopen)

frida -U -l dlopen.js -f tv.danmaku.bili

可见其挂在 libmsaoaidsec.so ,那么frida 的检测点就在其中了

定位目标函数

定位到了目标 so, 进一步定位 目标函数

so 的加载到运行内存中的流程是:

先解析so 的元数据(elf头等)-> 把so 文件从硬盘移到内存中(mmap映射+申请内存)-> 如果该so 有依赖库,那就递归加载全部依赖 -> 执行重定位,至此,so算是准备好

然后 android_dlopen_ext 主动执行 .init 和 .init_array 里的代码,若该so 是通过java层的 system.load() / system.loadlibrary() 加载,ART 会主动调用 JNI_OnLoad

顺序大概是 .init() -> .init_array() - > JNI_OnLoad()

故借助 ‘android_dlopen_ext 主动执行 .init 和 .init_array 里的代码’ 这个点,判断检测点是否在.init中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//如果调用了该函数,就输出一行日志,如果没有hook上就退出了,说明检测点在 .init函数中
function hook_dlopen(){
var dlopen_addr = Module.findExportByName("libdl.so","android_dlopen_ext")
console.log("hook [android_dlopen_ext]:",dlopen_addr)
Interceptor.attach(dlopen_addr,{
onEnter(args){
console.log("arg1",args[0].readCString(),"\n")
var name = ptr(args[0]).readCString()
this.isTarget = false;
if(name.indexOf("libmsaoaidsec") >= 0){
this.isTarget = true;
console.log("frida 触发点在libmsaoaidsec.so")
console.log("\n========================================");
console.log("[*] 准备加载 libmsaoaidsec.so ...");
console.log("[*] 如果日志卡在这里不往下走,说明检测点在 .init / .init_array");
console.log("========================================\n");

}
else{
console.log("not this")
}
},
onLeave(retval){
if (this.isTarget) {
// 3. 只有当库加载完毕(即 .init_array 执行完了),才会走到这里
console.log("[+] libmsaoaidsec.so 加载完成 (通过了 .init 检测)");
// 4. 此时库已经在内存里了,可以安全地 Hook JNI_OnLoad
hook_JNI_OnLoad();
}

}
})
}

function hook_JNI_OnLoad(){
var moudle = Process.getModuleByName("libmsaoaidsec.so")
// 我需要知道哪里 触发了 JNI_OnLoad
console.log("found libmsaoaidsec.so")
var addr = moudle.base.add(0x13A94)
Interceptor.attach(addr,{
onEnter(args){
console.log("call JNI_OnLoad")
}
})
}
setImmediate(hook_dlopen)

frida -U -l function.js -f tv.danmaku.bili

很明显,检测点在 .init 中

借助 pthread_create() 找触发frida的线程

将 libmsaoaidsec.so 拖入 ida中分析,搜索 .init 函数

在ida中 .init_proc 就表示初始化函数

进入 函数发现,调用了_system_property_get,来查看 android 版本

使用frida 缩小一下范围,看能不能hook到 _system_property_get 函数,若能,代表监测点在它之后,若不能说明检测点在它之前

借助 pthread_create() 函数,查看执行过_system_property_get 判断后so 执行的线程

int pthread_create(
pthread_t *thread, // args[0]
const pthread_attr_t *attr, // args[1]
void *(*start_routine)(void *), // args[2] 表示线程真正执行的函数地址
void *arg // args[3]
);

pthread_create(&tid, NULL, sub_123456, arg);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

function hook_dlopen() {
var dlopen_addr = Module.findExportByName("libdl.so", "android_dlopen_ext");
console.log("dlopen_addr", dlopen_addr);
Interceptor.attach(dlopen_addr, {
onEnter: function (args) {

//console.log("args1", args[0].readCString(), "\n");
var name = ptr(args[0]).readCString();
if (name.indexOf("libmsaoaidsec") >= 0) {
console.log("this");

//hook_pthread_creat()
//进入该so 就放钩子
hook_init();
}
}
})


}

function hook_init() {
var system_addr = Module.findExportByName("libc.so", "__system_property_get");
Interceptor.attach(system_addr, {
onEnter: function (args) {
var name = ptr(args[0]).readCString()
if (name.indexOf("ro.build.version.sdk") >= 0) {
console.log("name", name);
//这是.init_proc刚开始执行的地方,是一个比较早的时机点
var addr = Process.getModuleByName("libmsaoaidsec.so").base;
console.log("libmsaoaidsec.so----", addr);
hook_pthread_creat()

//bypass()
//注入代码

}

}


})
}

function hook_pthread_creat() {

var pthread_creat = Module.findExportByName("libc.so", "pthread_create")
Interceptor.attach(pthread_creat, {
onEnter: function (args) {

var func_addr = args[2];
console.log("The thread function address is " + func_addr)
}
})
}

hook_dlopen()

可见创建了两个线程偏移分别是 : 0x1AEE4   0x1A574

转到ida中搜索 pthread_create() 函数,并交叉引用,得知调用 pthread_create() 的地址

一个一个点进去看

得知frida 检测函数所在之处,分别是 0x1A858 0x1B8B4

用inline-frida nop掉检测函数

直接将这两个监测点nop掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

function hook_dlopen() {
var dlopen_addr = Module.findExportByName("libdl.so", "android_dlopen_ext");
console.log("dlopen_addr", dlopen_addr);
Interceptor.attach(dlopen_addr, {
onEnter: function (args) {

//console.log("args1", args[0].readCString(), "\n");
var name = ptr(args[0]).readCString();
if (name.indexOf("libmsaoaidsec") >= 0) {
console.log("this");
//hook_pthread_creat()
hook_init();
}
}
})


}

function hook_init() {
var system_addr = Module.findExportByName("libc.so", "__system_property_get");
Interceptor.attach(system_addr, {
onEnter: function (args) {
var name = ptr(args[0]).readCString()
//console.log(name);
if (name.indexOf("ro.build.version.sdk") >= 0) {
console.log("name", name);
//这是.init_proc刚开始执行的地方,是一个比较早的时机点
//在这bypassfrida检测
bypass()
}

}


})
}


function hook_pthread_creat() {

var pthread_creat = Module.findExportByName("libc.so", "pthread_create")
Interceptor.attach(pthread_creat, {
onEnter: function (args) {

var func_addr = args[2];
console.log("The thread function address is " + func_addr)
}
})

}

function nop(addr) {
// 运行时打补丁,inline patch
Memory.patchCode(ptr(addr), 4,
code => {
// pc寄存器表示 cou 正在/将要执行的指令地址,cpu每执行一条指令,都靠pc找下一条

const cw = new Arm64Writer(code, { pc: ptr(addr) });
// const cw = new ThumbWriter(code, { pc: ptr(addr) });
cw.putNop();
cw.putNop();
cw.putNop();
cw.putNop();
cw.flush();
});
}

function bypass() {

var addr = Process.getModuleByName("libmsaoaidsec.so").base;
console.log("libmsaoaidsec.so----", addr);
nop(addr.add(0x1A858));
nop(addr.add(0x1B8B4));

}

hook_dlopen()

成功绕过 frida 检测!

2023腾讯安全大赛-android客户端初赛

最终目的:编写注册机,实现输入任意token,都可计算出激活外挂的码

单机游戏:单机游戏是指游戏运行和核心玩法不依赖网络服务器,玩家可在本地独立完成游戏内容的游戏类型

GitHub - yagyiqing/-storehouse: 收集项目源码

环境:

1.pixel3,arm64

2.win11

工具:

  1. ida 9.2/7.7

  2. jadx -1.5.3

  3. il2cppDumper

  4. GAMBA-main

  5. 010editor

  6. jeb5.1

一个好的工具可以简化很多工作量

开始逆思路:

拿到 apk文件后,push到手机端,在电脑端解压并打开lib文件夹找切入点

解压后的文件夹中观察到有 libil2cpp.so 文件,这是 unity 游戏使用 IL2CPP 编译方式很明显的标志,游戏的核心算法几乎都在lib2cpp.so文件中

使用IL2CPP(所有 C# 源代码 → IL → C++ → 编译成 lib2cpp.so)的unity游戏一般还伴随着 global-metadata.dat 元数据文件(一般在:assets\bin\Data\Managed\Metadata),元数据文件中包含方法名,类名,字段名等信息,但它是二进制文件不能直接打开

所以先借助 il2cppDumper 工具 提取出想要的信息

原始的 c# 代码已经不在游戏里了,IL2CppDumper 做的就是 从libil2cpp.so中把类型,字段,方法名,方法参数等提取出来,再组合成一个dump.cs文件

使用方法:

1
2
3
4
完整参数格式
Il2CppDumper.exe <executable-file> <global-metadata> <output-directory>>

Il2CppDumper libil2cpp.so global-metadata.dat ./output

发现失败了,打开 global-metadata.dat 文件,发现并没有被加密

那就是 libil2cpp.so文件被加密

dump 解密状态的 libil2cpp.so 文件

so 文件在运行的时候肯定是解密的状态,所以使用 frida 动态 dump 内存中的 so 文件

但是直接注入frida 会提示 hack detect ,然后就退出了

应该是有 frida 检测

去一下 frida 的特征

1
2
3
4
5
6
7
8
9
10
11
12
13
修改frida-server的名称
rm frida-server fs
修改端口:
./fs -l 0.0.0.0:1234
-l: listen 0.0.0.0:监听所有网络接口

端口转发:
adb forward tcp:1234 tcp:1234
在PC上访问 127.0.0.1:1234 会被转发到Android设备上的127.0.0.1:1234

脚本注入:
frida -H 127.0.0.1:1234 -l test1.js -F
-H 指定远程(或本地)frida-server 的地址/端口

再注入就不会提示 hack detect 了

dump脚本如下:

把进程内存中已加载的 .so 模块整段读取出来,然以写到文件中,dump 出来

在 Android 应用里有一个顶层对象 是 Application:它代表整个应用程序的全局状态,系统启动应用时,会先创建一个Application 实例,然后再创建具体的 Activity, Service等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function dump_so(so_name){

    Java.perform(function(){

        // 拿到当前应用程序的Application对象

        var currentApplication = Java.use("android.app.ActivityThread").currentApplication();

        if(currentApplication ==null){

            console.log("Application还没有初始化成功,请稍等...")

            setTimeout(tryAgain,1000);

        }

        else{

            var dir = currentApplication.getApplicationContext().getFilesDir().getPath();}

        // 拿到全局Context,获取文件路径

        function tryAgain(){

            var currentApplication2 = Java.use("android.app.ActivityThread").currentApplication();

            if(currentApplication2 ==null){

            console.log("Application还没有初始化成功,请稍等...")

            setTimeout(tryAgain,1000);

        } else{var dir = currentApplication2.getApplicationContext().getFilesDir().getPath();}

        }

        // 获取指定模块信息

        var libso = Process.getModuleByName(so_name);

        console.log("[name]:", libso.name);

        console.log("[base]:", libso.base);

        console.log("[size]:", ptr(libso.size));

        console.log("[path]:", libso.path);

        var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";

        var  file_handle = new File(file_path, "wb");

        if(file_handle && file_handle != null){

            // 修改指定内存区域的保护属性

            Memory.protect(ptr(libso.base), libso.size, 'rwx');

            // 读取字节数组

            var libso_buffer = ptr(libso.base).readByteArray(libso.size);  

            file_handle.write(libso_buffer);

            file_handle.flush();

            file_handle.close();

            console.log("[dump]:", file_path);

        }

    });

}

dump_so("libil2cpp.so");

直接移动提示没有权限,所以直接复制到 /data/local/tmp

1
2
3
cp /data/user/0/com.com.sec2023.rocketmouse.mouse/files/libil2cpp.so_0x6fdae91000_0x13cc000.so /data/local/tmp
chmod 777 libil2cpp.so_0x6fdae91000_0x13cc000.so
adb pull /data/local/tmp/libil2cpp.so_0x6fdae91000_0x13cc000.so "C:\Users\y2467\Desktop\"

再使用 il2cppDumper 工具

成功!

修复 dump 下来的 so 文件

对于dump下来的so文件,所有段和节的偏移都是在运行内存中的偏移,dump 下来的只是一个平铺的内存镜像,对于静态分析来说不是一个合法的 elf 文件,所以需要修复 Segment 偏移和大小 / Section 头的偏移 / Section的内容和名称 / Section的偏移和大小,以便ELF 文件分析器 (ida)可以找到相关的段和节

修正段(Segment)的偏移:

段的位置和大小由程序头表中的这四个元素决定

故需要将 program_header 中每一个元素的

p_vaddr_VIRTUAL_ADDRESS 复制给 p_offset_FROM_FILE_BEGIN

p_memsz_SEGMENT_RAM_LENGTH 复制给 p_filesz_SEGMENT_FILE_LENGTH

修正 节表头的偏移:

节表头的位置在最后一个段(segment)之后

ELF头中的Elf64_Off e_shoff_SECTION_HEADER_OFFSET_IN_FILE字段表示 Section Header 在文件中的偏移

修改前是:0x11AB778

要修改成在运行内存中的偏移,计算过程如下:

Program_Header 中的最后一个元素的

偏移 + 文件大小 = p_vaddr_VIRTUAL_ADDRESS + p_memsz_SEGMENT_RAM_LENGTH =0x00000000013BC000 + 0xF778 = 0x13CB778

补充Section 头的内容:

因为Android 的Linker 在加载so文件时只需要 Program Header ,不会加载 Section 头的内容,所以dump下来的so中section头是空的

从libil2cpp.so 中复制 节的内容到dump下来的so 中,复制后需要按下F5重新运行一下 模板 ELF.bt

恢复节的名称:

修补内容后,Section的名称还是乱码

ELF 文件中每个Section 都是有名字的,比如 .data .text等,每个名字都是一个字符串,既然是字符串就需要一个字符串池来保存,而这个字符串池也是一个Section(保存了其他Section以及它自己的名字),这个特殊的Section 叫做 .shatrtab

所有section的头部是连续放在一块的,类似于一个数组,Elf64_Half e_shtrndx_STRING_TABLE_INDEX 变量就是 节(.shatrtab) 在这个数组中的下标

定位到struct section_table_entry64_t section_table_element[26] 中的 Elf64_Off s_offset

Elf64_Offs_offset的值决定了 section 的名称将从哪里去索引

右击该数值,点击转到,会发现是乱码

回到dump前的so,同样转到这个位置,发现是明文,直接复制过去,F5更新

段的名称正确显示

修正节的偏移

节的位置和大小由节头表中的两个字段决定

所以如果 s_addr = 0,无需修改s_offset

如果s_addr ≠ 0,将s_addr 的值复制给 s_offset

将修复好的 libil2cpp.so_0x6fdae91000_0x13cc000.so 拖入IDA

分析完如下图:

之后,点击 File->Script file… 运行 il2cppdumper 中的 ida_with_struct_py3.py ,选择 script.json,选择 il2cpp.h

等待分析完成…第一次运行等待的时间会久一点

根据 coin 找切入点

unity 使用的不是原生的 UI,所以setOnClickListener监听不到按钮

随便翻翻 dump.cs 文件,搜索 coin 字符串,可以看到 有CollentCoin方法,转去ida中搜索一下,果然搜到了

切换到 汇编试图

很明显,是获取flag的逻辑判断

使用frida patch一下,将1000换成0

1
2
3
4
5
6
7
8
9
function flag(){
var moudle = Process.getModuleByName("libil2cpp.so");
//IDA定位数值偏移地址:右击数据 -> 同步到IDA View-A
var cmp = moudle.base.add(0x4653CC)
console.log("patch--");
Memory.protect(cmp,4,"rwx");
cmp.writeByteArray([0x1F,0x00,0x00,0x71])
}
flag();

随便吃几个金币,就可以获取flag

再回到 dump.cs 文件随外下翻翻,看到有一个 SamllKeyboard.KeyboardType枚举类

不同的操作数对应不同的变量

转到ida中搜索一下SamllKeyboard

点开函数看看,注意到 SmallKeyboard__oO0oOo0 ,真是此地无银三百两

SmallKeyboard__oO0oOo0 中有随机数生成函数 并且 循环8次

小键盘处token的长度也是8,并且每次打开的数值不同

SmallKeyboard__iI1Ii 中,如果keyType == 2就执行 包含生成随机数的一系列函数

猜测 System_Convert__ToUInt64_8763844 为用户输入函数,使用frida 验证一下猜想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook_input(){
var module = Process.getModuleByName("libil2cpp.so")
var addr = module.base.add(0x85B9C4)

Interceptor.attach(addr,{
onEnter(args){
},
onLeave(retval){
console.log("返回值是:",retval.toInt32())
}
})
}

hook_input()

在小键盘处输入 123123

frida 注入 frida -H 127.0.0.1:1234 -l input.js -F

很明显,验证了猜想

每次按下 ok 键后,token 都会更新,正好对应了此处SmallKeyboard__oO0oOo0(v8, v18) 函数的调用

v16 作为参数传入了 SmallKeyboard__iI1Ii_4610736 函数,所以这个函数就是要找的加密点,转到汇编窗口,点进去SmallKeyboard__iI1Ii_4610736 经过两个B (无条件跳转),转到了

ARM64 不允许用一条指令直接取64位地址,所以一般用ADRP+LDR的组合获取全局变量地址

点进去0ff_13BAFF0

跳到了导入函数 g_sec2023_p_array 处,表示要找的加密点应该在libsec2023.so文件的导出函数g_sec2023_p_array 的 0x48偏移处

直接将 /lib 文件夹下的 libsec2023.so 拖入ida中分析,找到导出函数,点进去

sub_31164 就是跳转处

去除花指令,找到第一个加密点

接下来就是分析 sub_31164 函数

看到有两个加密函数 sub_3B8CC 和 v5

先分析 sub_3B8CC

但是遇到了控制流阻塞 ,ida 静态分析时无法动态的获取寄存器的值,所以无法展示正常的控制流

以sub_3B9D4() 函数为例 转到汇编窗口看看

计算一下跳转的地址

手动修复一个控制流,把不需要的汇编代码都nop掉,ida9.2破解版好像缺失 arm64的汇编器插件,太新了,先用ida7.7打开

每个条件码的含义

修复后如下图:

再u,c,p,重新F5

阻塞就消失了,变为我们修改的逻辑

so 文件中使用该结构的太多了,所以选择使用半手工半脚本的方式修复

手工修复之前,使用frida stalker先追踪一下每个寄存器值的变化,可以减轻一些工作量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
function anti_BR(){
// 因为ida反汇编出的内容遇到 BR X8的阻塞,所以现在使用frida stalker追踪一下x8寄存器
var module = Process.getModuleByName("libsec2023.so")
var func_addr = module.base.add(0x31164)
console.log("[hook]:",func_addr)
Interceptor.attach(func_addr,{
onEnter(args){
console.log("start stalker!")
this.tid = Process.getCurrentThreadId()
Stalker.exclude({
"base":Process.getModuleByName("libc.so").base,
"size":Process.getModuleByName("libc.so").size
}
)
Stalker.follow(this.tid,{
events:{
call: true, // CALL instructions: yes please
// Other events:
ret: false, // RET instructions
exec: false, // all instructions: not recommended as it's
//a lot of data
block: false, // block executed: coarse execution trace
compile: false // block compiled: useful for coverage

},
transform(iterator){
let instruction = iterator.next();
const startAddress = instruction.address;
// 判断该条指令是否在代码库中
const isAppCode = startAddress.compare(module.base) >= 0 &&startAddress.compare(module.base.add(module.size)) <= 0;
do{
if(isAppCode){
if(instruction.mnemonic === "br"){
// 保存指令的操作数字符串,比如 br x8 ,reg_name保存的便是寄存器的名称
var reg_name = instruction.opStr;
var inst_addr = new NativePointer(instruction.address);
iterator.putCallout(function(context){
// 地址显示是:undefined
//console.log(Process.getModuleByAddress(inst_addr).base)
var addr_before = inst_addr.sub(Process.findModuleByAddress(inst_addr).base).toString()
//console.log(addr_before)
//这是br x8 跳转后的地址
//console.log(Process.findModuleByAddress(ptr(context[reg_name])).base)
var addr_after = ptr(context[reg_name]).sub(Process.findModuleByAddress(ptr(context[reg_name])).base).toString()
//console.log(addr_after)
if(addr_after == undefined){
addr_after = "unknown:"+ context[reg_name]
}
console.log(addr_before,"jump to:",addr_after,":",reg_name)
});
}
}
iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
})
console.log("stalker end!");
},
onLeave(retval){
Stalker.unfollow(this.tid);
Stalker.garbageCollect();
}
});
}

anti_BR()

代码注入时,提示 hack ,但是是提顿一会再提示的,猜测这个延迟是sleep,故尝试hook 一下sleep,把 sleep 的时间传入一个指针随便指向什么地方,让sleep一直运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function AntiDebug(){
var addr = Module.findExportByName(null,"sleep");
if(!addr){
console.log("sleep 没有找到")
}else{
Interceptor.attach(addr,{
onEnter(args){
console.log("success")
console.log("原始sleep时间是:",args[0])
console.log("sleep called from:\n"+Thread.backtrace(this.context,Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') )
args[0] = ptr(100000)
},
onLeave(retval){
}
})
}

}
AntiDebug()

再注入 stalker ,就不再退出

stalker追踪的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
stalker end!
0x3ba00 jump to: 0x3ba04 : x10
0x3ba30 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba74 : x12
0x3badc jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bb2c : x13
0x3bb4c jump to: 0x3ba04 : x10
0x3ba30 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba34 : x12
0x3ba70 jump to: 0x3ba74 : x12
0x3badc jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bae0 : x13
0x3bb28 jump to: 0x3bb2c : x13
0x3bb4c jump to: 0x3bb50 : x10
0x3a08c jump to: 0x3a0f0 : x8
0x3b508 jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b550 : x11
0x3b5c0 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b608 : x11
0x3aa70 jump to: 0x3aa74 : x8
0x3aaa0 jump to: 0x3aaa4 : x8
0x3aad4 jump to: 0x3aad8 : x8
0x3ab04 jump to: 0x3ab70 : x8
0xf28c jump to: 0x82c40 : x17
0xf4ac jump to: 0x83100 : x17
0x3ac90 jump to: 0x3acc0 : x11
0xf40c jump to: 0x1db30 : x17
0xf28c jump to: 0x82c40 : x17
0xf4ac jump to: 0x83100 : x17
0x3b950 jump to: 0x3b95c : x8
0x3a08c jump to: 0x3a0f0 : x8
0x3b508 jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b50c : x11
0x3b54c jump to: 0x3b550 : x11
0x3b5c0 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b5c4 : x11
0x3b604 jump to: 0x3b608 : x11
0x3aa70 jump to: 0x3aa74 : x8
0x3aaa0 jump to: 0x3aaa4 : x8
0x3aad4 jump to: 0x3aad8 : x8
0x3ab04 jump to: 0x3ab70 : x8
0xf28c jump to: 0x82c40 : x17
0xf4ac jump to: 0x83100 : x17
0x3ac90 jump to: 0x3acc0 : x11
0xf40c jump to: 0x1db30 : x17
0xf28c jump to: 0x82c40 : x17
0xf4ac jump to: 0x83100 : x17
0x3b990 jump to: 0x3b99c : x8
0x311a0 jump to: 0x13b8d64 : x2
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17
0xf3fc jump to: 0x1c924 : x17

由追踪到的结果可知,br 跳转指令,要么跳到紧接着的下一个指令处,要么跳转到另一个函数处

去除CSEL 指令的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import ida_bytes
import ida_ida
import ida_segment
import idc
import idautils
import idaapi
from keystone import *

def patch_nop(begin, end): # arm64中的NOP指令是b'\x1F\x20\x03\xD5'
while end > begin:
ida_bytes.patch_bytes(begin, b'\x1F\x20\x03\xD5')
begin = begin + 4


#获取text段的起始地址
text_seg=ida_segment.get_segm_by_name(".text")
start=text_seg.start_ea
end=text_seg.end_ea
cur_addr=start
#在结构修复完之后需要nop的指令地址
nop_addr_after_finsh=[]
while cur_addr<end:
#找到CSEL指令
if idc.print_insn_mnem(cur_addr)=="CSEL":
csel_addr=cur_addr
nop_addr_after_finsh_tmp=[]
nop_addr_after_finsh_tmp.append(csel_addr)
BR_reg=""
BR_addr=0
tmp_addr=idc.next_head(csel_addr)
#向下寻找BR指令
for i in range(9):
if idc.print_insn_mnem(tmp_addr)=="BR":
BR_addr=tmp_addr
BR_reg=idc.print_operand(tmp_addr,0)
break
if idc.print_insn_mnem(tmp_addr)=="CSEL":
break
tmp_addr=idc.next_head(tmp_addr)
#找到了BR指令
if BR_addr!=0:
#获取CSEL的op1~3寄存器,以及条件码
CSEL_opt1=idc.print_operand(csel_addr,0)
CSEL_opt2=idc.print_operand(csel_addr,1)
CSEL_opt2_val=-1
CSEL_opt3=idc.print_operand(csel_addr,2)
CSEL_opt3_val=-1
CSEL_cond=idc.print_operand(csel_addr,3)
tmp_addr=idc.prev_addr(csel_addr)
while (CSEL_opt2_val==-1 or CSEL_opt3_val==-1) and tmp_addr >start:
#读取条件分支语句CSEL要赋值给目标寄存器的两个源寄存器中的值
#XZR代表0
if CSEL_opt2=="XZR":
CSEL_opt2_val=0
if CSEL_opt3=="XZR":
CSEL_opt3_val=0
if idc.print_insn_mnem(tmp_addr)=="MOV":
#这里切片是因为x19和W19是一样的寄存器,用[1::]可以只取到后面的19
if idc.print_operand(tmp_addr,0)[1::]==CSEL_opt2[1::] and CSEL_opt2_val==-1:
CSEL_opt2_val=idc.get_operand_value(tmp_addr,1)
nop_addr_after_finsh_tmp.append(tmp_addr)
if idc.print_operand(tmp_addr, 0)[1::] == CSEL_opt3[1::] and CSEL_opt3_val == -1:
CSEL_opt3_val = idc.get_operand_value(tmp_addr, 1)
nop_addr_after_finsh_tmp.append(tmp_addr)
tmp_addr=idc.prev_head(tmp_addr)
assert CSEL_opt3_val!=-1 and CSEL_opt2_val!=-1
#print(f"偏移是{CSEL_opt2_val},{CSEL_opt3_val}")
#现在开始取跳转表所在的位置,加到跳转的值
tmp_addr=BR_addr
jump_array_reg=""#储存跳转表所在的寄存器
jump_array_addr=-1#存储跳转表所在的地址
add_reg=[]#存储加到跳转表的值的寄存器
add_val=-1#加到跳转表的值
#从BR后往前找,到CSEL_结束,存储跳转表等修复数据
while tmp_addr>csel_addr:
if idc.print_insn_mnem(tmp_addr)=="ADD" and idc.print_operand(tmp_addr,0)==BR_reg:
add_reg.append(idc.print_operand(tmp_addr,1)[1::])
add_reg.append(idc.print_operand(tmp_addr,2)[1::])
nop_addr_after_finsh_tmp.append(tmp_addr)

elif idc.print_insn_mnem(tmp_addr)=="MOV" and idc.print_operand(tmp_addr,0)[1::] in add_reg:
add_val=idc.get_operand_value(tmp_addr,1)
nop_addr_after_finsh_tmp.append(tmp_addr)
elif idc.print_insn_mnem(tmp_addr)=="LDR":
#获取存储跳转表地址的寄存器名称,后面是那个寄存器加载的不重要
jump_array_reg=idc.print_operand(tmp_addr,1)[1:-1].split(",")[0]#切片去除[],以,为标记分割,取第一个寄存器
nop_addr_after_finsh_tmp.append(tmp_addr)
elif idc.print_insn_mnem(tmp_addr)=="ADRL" :
jump_array_reg=idc.print_operand(tmp_addr,0)
jump_array_addr=idc.get_operand_value(tmp_addr,1)
nop_addr_after_finsh_tmp.append(tmp_addr)
elif idc.print_insn_mnem(tmp_addr)=="SXTW":
if idc.print_operand(tmp_addr,0)[1::] in add_reg:
reg=idc.print_operand(tmp_addr,1)[1::]
while tmp_addr>start:
if idc.print_insn_mnem(tmp_addr)=="MOV" and idc.print_operand(tmp_addr,1)[1::]==reg:
add_val=idc.get_operand_value(tmp_addr,1)
nop_addr_after_finsh_tmp.append(tmp_addr)
break
tmp_addr=idc.prev_head(tmp_addr)

tmp_addr=idc.prev_head(tmp_addr)

#如果在CSEL_BR中间没有跳转表地址,就向上找
if jump_array_addr==-1:
tmp_addr=idc.prev_head(tmp_addr)
while tmp_addr>start:
if idc.print_insn_mnem(tmp_addr)=="ADRL":
if idc.print_operand(tmp_addr,0)==jump_array_reg:
jump_array_addr = idc.get_operand_value(tmp_addr, 1)
nop_addr_after_finsh_tmp.append(tmp_addr)
break
elif idc.print_insn_mnem(tmp_addr)=="ADRP":
if idc.print_operand(tmp_addr,0)==jump_array_reg:
jump_array_addr = idc.get_operand_value(tmp_addr, 1)
nop_addr_after_finsh_tmp.append(tmp_addr)
#观察到ADPR还有一部分ADD
while tmp_addr<end:
if idc.print_insn_mnem(tmp_addr)=="ADD":
if idc.print_operand(tmp_addr,0)==jump_array_reg:
jump_array_addr+=idc.get_operand_value(tmp_addr,2)
nop_addr_after_finsh_tmp.append(tmp_addr)
break
tmp_addr=idc.next_head(tmp_addr)
break
tmp_addr=idc.prev_head(tmp_addr)

#如果没有找到加在跳转表的值
if add_val==-1:
tmp_addr=tmp_addr
while tmp_addr>start:
if idc.print_insn_mnem(tmp_addr)=="MOV":
if idc.print_operand(tmp_addr,0)[1::] in add_reg:
add_val=idc.get_operand_value(tmp_addr,1)
nop_addr_after_finsh_tmp.append(tmp_addr)
break
elif idc.print_insn_mnem(tmp_addr)=="SXTW":
if idc.print_operand(tmp_addr,0)[1::] in add_reg:
reg=idc.print_operand(tmp_addr,1)[1::]
while tmp_addr>start:
if idc.print_insn_mnem(tmp_addr)=="MOV" and idc.print_operand(tmp_addr,1)[1::]==reg:
add_val=idc.get_operand_value(tmp_addr,1)
nop_addr_after_finsh_tmp.append(tmp_addr)
break
tmp_addr=idc.prev_head(tmp_addr)
break
tmp_addr=idc.prev_head(tmp_addr)
'''
print(f"加到跳转表的值{hex(add_val)}")
print(f"加到跳转表的值{hex(add_val)}")
print(add_reg)
print(f"寄存表的值{hex(ida_bytes.get_qword(jump_array_addr+CSEL_opt2_val)+add_val)}")
print(f"寄存表的值{hex(ida_bytes.get_qword(jump_array_addr+CSEL_opt3_val)+add_val)}")
'''

#计算两个分支的位置
branch_a=(ida_bytes.get_qword(jump_array_addr+CSEL_opt2_val)+add_val)& 0xffffffffffffffff
branch_b=(ida_bytes.get_qword(jump_array_addr+CSEL_opt3_val)+add_val)& 0xffffffffffffffff
logic_rev = {"GE": "LT", "LT": "GE", "EQ": "NE", "NE": "EQ", "CC": "CS", "CS": "CC", "HI": "LS", "LS": "HI"}
ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
code = ""
if branch_b == idc.next_head(BR_addr): # 判断逻辑不取反
code = f"B.{CSEL_cond} #{hex(branch_a)}"
elif branch_a == idc.next_head(BR_addr): # 判断逻辑取反
code = f"B.{logic_rev[CSEL_cond]} #{hex(branch_b)}"
#修复BR
if code!="":
patch_br_byte,cout=ks.asm(code,addr=BR_addr)
ida_bytes.patch_bytes(BR_addr,bytes(patch_br_byte))
print(f"fix CSEL_RB at {hex(BR_addr)}")
nop_addr_after_finsh.extend(nop_addr_after_finsh_tmp)
cur_addr=idc.next_addr(BR_addr)
else:
print(f"error! unable to fix CSEL-BR at {hex(cur_addr)},branch:{hex(branch_a)}, {hex(branch_b)}")

cur_addr=idc.next_head(cur_addr)

for addr in nop_addr_after_finsh:
patch_nop(addr,addr+idc.get_item_size(addr))

控制流都清晰了,捋一下sub_3B9D4()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if(x8 >=2){
sub_3BB50()
}
else{
do
{
*((_BYTE *)&a5 + v5 + 4) = (*(_DWORD *)(a1 + 4 * a3) >> v6) ^ v5;
--v5;
v6 -= 8;
}
while ( v5 >= 0 );
HIBYTE(a5) ^= 0x86u;
BYTE6(a5) -= 94;
BYTE5(a5) ^= 0xD3u;
BYTE4(a5) -= 28;
*(_DWORD *)(a1 + 4 * a3) = 0;
do
{
v8 = *((_BYTE *)&a5 + v6 + 4) - v7;
*((_BYTE *)&a5 + v6-- + 4) = v8;
v5 += v8 << v7;
*(_DWORD *)(a1 + 4 * a3) = v5;
v7 -= 8;
}
while ( v6 >= 0 );

}
然后进入循环

加密过程比较清晰了:去除数据的每一位进行加密

hook一下 sub_3B9D4(),看输入的值和返回的值

hex(999999999999) = 0xE8D4A50FFF

加密后:0x 6d94cacc 3939d5e3 ,小端存储:最低有效位在低地址

编写解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 用户输入:999999999999,返回值是:0x6d94cacc3939d5e3
algorithm_1={
(0,"enc") :lambda x: (x-28) & 0xff, # 防止溢出或负数, 确保结果在 0~255 之间
(1,"enc") :lambda x: (x^0xD3),
(2,"enc") :lambda x: (x-94) & 0xff,
(3,"enc") :lambda x: (x^0x86),

(0,"dec") :lambda x: (x+28) & 0xff,
(1,"dec") :lambda x: (x^0xD3),
(2,"dec") :lambda x: (x+94) & 0xff,
(3,"dec") :lambda x: (x^0x86)
}

#加密
def enc_1(input):
input_bytes = bytearray(input.to_bytes(8,'little'))
enc_array = [0] * len(input_bytes)
for i in range(len(input_bytes)):
index = i % 4
enc_array[i] = (algorithm_1[(index,"enc")](input_bytes[i]^ index) - 8*index ) & 0xff
return int.from_bytes(bytearray(enc_array),'little')

#解密
def dec_1(input):
input_bytes = bytearray(input.to_bytes(8,'little'))
dec_array = [0] * len(input_bytes)
for i in range(len(input_bytes)):
index = i % 4
dec_array[i] = (algorithm_1[(index,"dec")]((input_bytes[i] + 8*index)&0xff) ^ index)
return int.from_bytes(bytearray(dec_array),'little')


def test():
test_value = 999999999999
enc_value = enc_1(test_value)
dec_value = dec_1(enc_value)
print(f"Test Value: {test_value}")
print(f"Encrypted Value: {hex(enc_value)}")
print(f"Decrypted Value: {dec_value}")
assert test_value == dec_value, "Decryption did not return the original value!"

test()

第一个加密点搞定

第二个加密点

回到 sub_3B8CC(),继续往下分析,发现CSET结构,结合stalker追踪到的值修复一下控制流,在修改过控制流函数的任意处,按下u,c,p,再F5就可显示出逻辑

整理一下sub_3B8CC(),逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
unsigned __int64 __fastcall sub_3B8CC(__int64 a1, __int64 a2)
{
__int64 v2; // x0
unsigned int v4[3]; // [xsp+8h] [xbp-48h] BYREF
unsigned int v5; // [xsp+14h] [xbp-3Ch] BYREF
__int64 v6; // [xsp+18h] [xbp-38h]

v6 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v4[0] = HIDWORD(a1);
v4[1] = a1;
sub_3B9D4((__int64)v4, a2); ;第一个加密处
v4[2] = 0;
v5 = bswap32(v4[0]); ;大小端转换
sub_3A054();
if ( (unsigned int)sub_3A924(v2, (__int64)&v5, 4u) ) ;第二个加密点,先加密高位
return sub_3B954();
else
HIDWORD(a11) = bswap32(HIDWORD(a10));
sub_3A054();
v12 = sub_3A924(v11, (__int64)&a11 + 4, 4u); ;第二个加密点,再加密低位
if ( v15 != v16 )
return sub_3B994(v12, v13, v14);
else
return v0 | ((unsigned __int64)v2 << 32); ;高位和低位交换位置
}

hook一下 sub_3A924,在ida中追踪下来,返回值是布尔值,所以该加密函数将加密的结果存放在了参数指针指向的缓冲区中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// sub_3A924  查看其参数和加密缓冲区的内容
// 很多 C/C++ 的加密函数并不返回加密结果,而是把结果写到参数传进来的内存地址里(比如 args[3])。
// 函数的返回值只返回了符号象征,0或1
var count = 0;
var outbuffer_addr = [0,0]
function sub_3A924(){
var module = Process.getModuleByName("libsec2023.so")
var func_addr = module.base.add(0x3A924)
// 当这个函数执行时,就执行我们写的回调函数
Interceptor.attach(func_addr,{
//函数被调用的瞬间执行
onEnter(args){
console.log("before first enc",count+1)
//这个参数可能是加密输出的缓冲区
outbuffer_addr[count] = args[3]
console.log(hexdump(args[1],{
offset: 0,
length: 64,
header: true,
ansi: true
}))
},
//函数执行完毕,返回前执行
onLeave(retval){
console.log("before first enc",count+1)
console.log(hexdump(outbuffer_addr[count],{
offset: 0,
length: 64,
header: true,
ansi: true
}));
count += 1;
}

})
}
sub_3A924()

第一个加密点的返回值是:0x 6d94cacc 3939d5e3

此处的两个加密参数正好对应两次 bswap32

第二次加密后得到的值为:0x10d817aa 368ec0f4

进入 sub_3A924 分析:

发现有很多 v7 + 1408LL v7 + 1664LL …. v7+offset的 ____fastcall 函数调用,v7一般就是 JNIEnv* 类型的指针了,但是ida分析时不知道它是什么类型的,所以默认为普通的指针

理解JNIEnv

手动修改一下

选中变量v7 ->按下快捷Y -> 手动修改为JNIEnv*类型 - > IDA就会将 V7 识别为一个 JNI环境指针

更改后如下:

注意到有jni 函数 GetStaticMethodID(获取JAVA类中实例方法的id,以便native层可以调用该实例方法),hook一下,看调用了什么函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//hook GetStaticMethodID
function Hook_GetStaticMethodID(){
var symbols = Module.enumerateSymbolsSync("libart.so")
// console.log("hook:",symbols)
for(var i =0 ;i<symbols.length;i++)
{
var symbol = symbols[i];
if(symbol.name.indexOf("GetStaticMethodID") != -1)
{
console.log("name:",symbol.name)
console.log("hook",symbol.address)
Interceptor.attach(symbol.address,{
onEnter(args){
console.log("name:",args[2].readCString())
console.log("sig:",args[3].readCString())
},
onLeave(retval){

}
})
}
}

}
Hook_GetStaticMethodID();

调用了 java 方法:encrypt

转去 jadx 中搜索,没有结果,应该是dex动态加载的,故使用frida-dexdump 把内存中dex dump下来

dump下来后,把dex拖入jadx中反编译,搜索 encrypt

加了 BlackObfuscator 混淆,直接拖入 jeb5.1直接去混淆,使控制流平坦化

这样看逻辑就相当清晰了,写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import struct
#大端序和小端序
#看变量的类型,整数和字节数组

print(hex(int.from_bytes(0xccca946d.to_bytes(4, 'little'),'big')))

def enc_2(input):
# input_high = input = 0xccca946d
# input_low = input = 0xe3d53939
# 按照大端进行加密
input = int.from_bytes(input.to_bytes(4, 'little'), 'big') # 字节再次翻转
input = (input >> 7 | input << 25) & 0xffffffff
# 变成一个可操作的字节序列
input_byte = bytearray(input.to_bytes(4, 'big'))
xor_arr = [50, -51, -1, -104, 25, -78, 0x7C, -102]
for i in range(4):
input_byte[i] = (input_byte[i] ^ xor_arr[i]) & 0xff
input_byte[i] = (input_byte[i] + i) & 0xff
return int.from_bytes(input_byte, 'big')


def dec_2(input):
input_byte = bytearray(input.to_bytes(4, 'big'))
xor_arr = [50, -51, -1, -104, 25, -78, 0x7C, -102]
for i in range(4):
input_byte[i] = (input_byte[i] - i) & 0xff
input_byte[i] = (input_byte[i] ^ xor_arr[i]) & 0xff

input = int.from_bytes(input_byte, 'big')
input = (input << 7 | input >> 25) & 0xffffffff
input = int.from_bytes(input.to_bytes(4, 'little'), 'big') # 字节再次翻转

return input


def mytest_2():
# # 0x6d94cacc 3939d5e3,进入encrypt之前大小端转换了一次
# 把字节流解释成一个64位整数,按照小端的规则拼接
# 先加密高32位逆序字节,再加密低32位逆序字节

# struct.pack(fmt, v1, v2, …) 把数字打包成字节流
# struct.unpack(fmt, bytes) 把字节流打包成数字
data = b'\x6d\x94\xca\xcc\x39\x39\xd5\xe3'
input_high,input_low = struct.unpack('<2I',data)
# 验证加密算法
input_high = enc_2(input_high)
assert input_high.to_bytes(4, 'big') == b'\xaa\x17\xd8\x10'
input_low = enc_2(input_low)
assert input_low.to_bytes(4, 'big') == b'\xf4\xc0\x8e\x36'

# 验证解密算法
input_high = dec_2(input_high)
input_low = dec_2(input_low)
input = struct.pack('>2I', input_low, input_high)
assert input == b'\xe3\xd5\x39\x39\xcc\xca\x94\x6d'


mytest_2()

# print(int.from_bytes(b'\xe3\xd5\x39\x39\xcc\xca\x94\x6d','little'))
# print(int.from_bytes(b'\xe3\xd5\x39\x39\xcc\xca\x94\x6d','big'))
# data = b'\x6d\x94\xca\xcc\x39\x39\xd5\xe3'
# input_high,input_low = struct.unpack('<2I',data)
# print(hex(input_high),hex(input_low))

总体看 sub_3B8CC 看传入的参数和返回的数值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function sub_3B8CC(){
var module = Process.getModuleByName("libsec2023.so")
var func_addr = module.base.add(0x3b8cc)
Interceptor.attach(func_addr,{
onEnter(args){
console.log("参数是:",args[0])
},
onLeave(retval){
console.log("返回值是:",retval)

}
})
}
sub_3B8CC()

第二次加密后得到的值为:0x10d817aa 368ec0f4

正好对应将高低位反转

vm指令加密:

返回 sub_31164 函数,可知经过 sub_3B8CC两个加密后,经过br又发生了跳转

试着在 stalker 中搜一下

可以看到如下跳转

那就回到 libil2cpp.so 的 0x13b8d64 偏移处

经过一个 B 的跳转来到 loc_465AB4,可知该段是 .init_proc 函数 chunk

修改一下函数的结尾:

然后就可以继续分析了

libsec.so 中的 br x2 跳转到这里,可以继续分析

在 c# 中,自己写的普通类,需要用实例变量/方法的需要new(初始化)

c#中一个类的构造函数是 public MyCmlass(){},编译到 IL(中间语言)后,构造名称变成 MyClass::.ctor(),再通过 IL2CPP -> c++ -> so,其中的.ctor 会被转成 MyClass_ctor

可以看到类名和字段都被混淆

搜索一下该类名

点开几个方法,发现都有MBA表达式

使用工具 GAMBA 简化一下

简化后为 v6 + v8,重命名函数名为 add

以此类推

因为4294967296=0x100000000 已经溢出32位了,所以 4294967296+v8=v8

简化后:

这个加密算法应该和VM指令有关

返回x2 寄存器跳转处,往下就调用了改类

该类中出现了经典的 while(1) 循环,那其中必定有 opcode 和 eip, 只是名称被混淆了

但是eip 常常作为数组下标出现,因此可以区分出 eip和 opcode

重新命名

this->fields.OoOOO00; // opcode

this->fields.oOOO0Oo0; // eip

再继续分析其他全局变量:

由于 oooO0oOo 的初始值为 -1,所以 oooO0oOo 为堆栈,而不是数组,esp指向栈顶

这里 OOoOO0 入栈了,故 OOoOO0 为输入值

分析下来全局变量如下:

到这里vm 关键变量已经分析的差不多了,点进 add 函数的第一个参数,结构体嵌套结构体

并且已经知道 vm 指令中加减乘除位运算的位置和输入输出是多少,就可以写脚本hook相关vm指令了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
function hook_vm(){
//先找到该so文件
var moudle = Process.getModuleByName("libil2cpp.so");
var my_base = 0x7e78baa000;
var esp,eip,opcode,input,stack,ptr_esp,ptr_eip

//加载vm指令处,开始处就是while(1)循环处
var addr_begin = moudle.base.add(0x7E79014D44-my_base)
Interceptor.attach(addr_begin,{
onEnter(args){
opcode = ptr(args[0].add(0x10).readS64()).add(0x1C)
input = ptr(args[0].add(0x18).readS64()).add(0x1C)
stack = ptr(args[0].add(0x20).readS64()).add(0x1C)
ptr_esp = args[0].add(0x28)
ptr_eip = args[0].add(0x2C)
console.log("========start========")
for(var i =1;i<8;i++){
console.log("input["+i+"]=0x"+input.add(4*i).readS32().toString(16))
}
},
onLeave(retval){
console.log("=========end=========")
}
});

var addr_add = moudle.base.add(0x7E79014E50-my_base)
Interceptor.attach(addr_add,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
"+"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})

var addr_sub = moudle.base.add(0x7E79014ECC-my_base)
Interceptor.attach(addr_sub,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
"-"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})


var addr_mul = moudle.base.add(0x7E79014F44-my_base)
Interceptor.attach(addr_mul,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
"*"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})

var addr_Lshift = moudle.base.add(0x7E79014FC4-my_base)
Interceptor.attach(addr_Lshift,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
"<<"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})

var addr_Rshift = moudle.base.add(0x7E79015040-my_base)
Interceptor.attach(addr_Rshift,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
">>"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})
var addr_and = moudle.base.add(0x7E790150BC-my_base)
Interceptor.attach(addr_and,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
"&"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})
var addr_xor = moudle.base.add(0x7E7901513C-my_base)
Interceptor.attach(addr_xor,{
onEnter(args){
esp = ptr_esp.readS32()
eip = ptr_eip.readS32()
//乘4是因为每个元素占4字节,取出esp和esp+1偏移处的内容
console.log("stack["+esp+"]=0x"+stack.add(4*esp).readS32().toString(16)+
"^"+stack.add(4*(esp+1)).readS32());
},
onLeave(retval){

}

})


}

hook_vm();

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
========start========
input[1]=0x10d817aa //输入值的低32位
input[2]=0x368ec0f4 //输入值的高32位

stack[1]=0x10d817aa>>24
stack[1]=0x10&255 //取出第4个字节 记为byte4 = 0x10
stack[2]=0x18-8
stack[2]=0x10d817aa>>16
stack[2]=0x10d8&255 //取出第3个字节 记为byte3 = 0xd8
stack[3]=0x10-8
stack[3]=0x10d817aa>>8 //取出第2个字节 记为byte2 = 0x17
stack[3]=0x10d817&255
stack[4]=0x8-8
stack[4]=0x10d817aa>>0
stack[4]=0x10d817aa&255 //取出第1个字节 记为byte1 = 0xaa
stack[5]=0x0-8
stack[4]=0xaa-27 // byte1 = byte1 -27(0x1B)=0x8f
stack[3]=0x17^194 // byte2 = byte2 ^ 194(0xC2) = 0xD5
stack[2]=0xd8+168 // byte3 = byte3 + 168(0xA8) = 0x180
stack[1]=0x10^54 // byte4 = byte4 ^ 54(0x36) = 0x26
stack[1]=0x8f^0 // byte1 ^ 0
stack[1]=0x8f<<0 //byte1 << 0
stack[2]=0xff<<0
stack[1]=0x8f&255
stack[1]=0x8f+0
stack[1]=0x4+1
stack[1]=0x0+8
stack[1]=0xd5^8 //byte2 = byte2^8 = 0xDD
stack[1]=0xdd<<8 //byte2 << 8 = 0xDD00
stack[2]=0xff<<8
stack[1]=0xdd00&65280 //byte2 = byte2 & 65280 = 0xdd00
stack[1]=0xdd00+143 //byte2 = byte2 + 143 = 0x8F
stack[1]=0x5+1
stack[1]=0x8+8
stack[1]=0x180^16 //byte3 = byte3 ^ 16 = 0x190
stack[1]=0x190<<16 //byte3 = byte3 << 16 = 0xBE000
stack[2]=0xff<<16
stack[1]=0x1900000&16711680
stack[1]=0x900000+56719
stack[1]=0x6+1
stack[1]=0x10+8
stack[1]=0x26^24 //byte4 = byte4 ^ 24 = 0x3e
stack[1]=0x3e<<24 //byte4 = byte4 << 24 = 0x3E000000
stack[2]=0xff<<24
stack[1]=0x3e000000&-16777216
stack[1]=0x3e000000+9493903 //byte4 = byte4 + 9492903 = 0x3E90DD8F

//高32位加密
stack[1]=0x7+1
stack[1]=0x18+8
stack[1]=0x368ec0f4>>24 //byte4 = 0x36
stack[1]=0x36&255
stack[2]=0x18-8
stack[2]=0x368ec0f4>>16 //byte3 = 0x8e
stack[2]=0x368e&255
stack[3]=0x10-8
stack[3]=0x368ec0f4>>8 //byte2 = 0xc0
stack[3]=0x368ec0&255
stack[4]=0x8-8
stack[4]=0x368ec0f4>>0 //byte1 = 0xf4
stack[4]=0x368ec0f4&255
stack[5]=0x0-8
stack[4]=0xf4-47 //byte1 = byte1 - 47 = 0xc5
stack[3]=0xc0^182 //byte2 = byte2 ^ 182 = 0x76
stack[2]=0x8e+55 //byte3 = byte3 + 55 = 0xc5
stack[1]=0x36^152 //byte4 = byte4 ^ 152 = 0xAE
stack[1]=0xc5+0 //byte1 +0
stack[1]=0xc5<<0 // byte1 <<0
stack[2]=0xff<<0
stack[1]=0xc5&255
stack[1]=0xc5+0
stack[1]=0x4+1
stack[1]=0x0+8
stack[1]=0x76+8 //byte2 = byte2+8 = 0x7E
stack[1]=0x7e<<8 //byte2 = byte2 << 8 = 0x7E00
stack[2]=0xff<<8
stack[1]=0x7e00&65280
stack[1]=0x7e00+197 //byte2 = byte2 +197 = 0x7EC5
stack[1]=0x5+1
stack[1]=0x8+8
stack[1]=0xc5+16 //byte3 +16
stack[1]=0xd5<<16 //byte3 <<16
stack[2]=0xff<<16
stack[1]=0xd50000&16711680
stack[1]=0xd50000+32453
stack[1]=0x6+1
stack[1]=0x10+8
stack[1]=0xae+24 //byte4 +24
stack[1]=0xc6<<24 //byte4 <<24
stack[2]=0xff<<24
stack[1]=0x-3a000000&-16777216
stack[1]=0x-3a000000+13991621
stack[1]=0x7+1
stack[1]=0x18+8
=========end=========

逻辑很清晰,写解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import struct

def enc_vm_low(input):
# 按照大端序解包4个字节
byte4,byte3,byte2,byte1 = struct.unpack(">4B",input.to_bytes(4,'big'))
# 0x10d817aa 从左到右分别是byte4,byte3,byte2,byte1
byte1 = (byte1 - 27) ^ 0
byte2 = (byte2 ^ 194) ^ 8
byte3 = (byte3 + 168) ^ 16
byte4 = (byte4 ^ 54) ^ 24
# print("byte4:"+hex(byte4)+" byte3:"+hex(byte3)+" byte2:"+hex(byte2)+" byte1:"+hex(byte1))
#小端序列打包4个字节
input = struct.pack("<4B",byte1 & 0xff,byte2 & 0xff,byte3 & 0xff,byte4 & 0xff)
return int.from_bytes(input,'little')

#print(hex(enc_vm_low(int.from_bytes(b'\x10\xd8\x17\xaa','big'))))


def dec_vm_low(input):
# 按照大端序解包4个字节
byte4,byte3,byte2,byte1 = struct.unpack(">4B",input.to_bytes(4,'big'))
byte1 = (byte1 ^ 0) + 27
byte2 = (byte2 ^ 8)^ 194
byte3 = (byte3 ^ 16)- 168
byte4 = (byte4 ^ 24)^ 54
input = struct.pack("<4B",byte1 & 0xff,byte2 & 0xff,byte3 & 0xff,byte4 & 0xff)
return int.from_bytes(input,"little")

def enc_vm_high(input):
# 按照大端序解包4个字节
byte4,byte3,byte2,byte1 = struct.unpack(">4B",input.to_bytes(4,'big'))
byte1 = (byte1 -47) + 0
byte2 = (byte2 ^ 182) + 8
byte3 = (byte3 + 55) + 16
byte4 = (byte4 ^ 152) + 24
input = struct.pack("<4B",byte1 & 0xff,byte2 & 0xff,byte3 & 0xff,byte4 & 0xff)
return int.from_bytes(input,"little")

def dec_vm_high(input):
# 按照大端序解包4个字节
byte4,byte3,byte2,byte1 = struct.unpack(">4B",input.to_bytes(4,'big'))
byte1 = (byte1 -0) + 47
byte2 = (byte2 -8) ^ 182
byte3 = (byte3 -16 ) - 55
byte4 = (byte4 -24) ^ 152
input = struct.pack("<4B",byte1 & 0xff,byte2 & 0xff,byte3 & 0xff,byte4 & 0xff)
return int.from_bytes(input,"little")

def test():
# 由第二个加密点传过来的值为:0x368ec0f4 10d817aa
input_high = int.from_bytes(b'\x36\x8e\xc0\xf4','big')
input_low = int.from_bytes(b'\x10\xd8\x17\xaa','big')

input_low = enc_vm_low(input_low)
input_high = enc_vm_high(input_high)
print("input_low:0x10d817aa 加密后:"+hex(input_low))
print("input_high:0x368ec0f4 加密后:"+hex(input_high))

#验证解密算法
input_low = dec_vm_low(input_low)
input_high = dec_vm_high(input_high)
print("0x3e90dd8f 解密后:"+hex(input_low))
print("0xc6d57ec5 解密后:"+hex(input_high))


test()

进行加密的数值正好对应第二次加密后得到的数值

第三处加密就结束了

XTEA

回到 libsec.so br x2跳转来到的函数继续往下分析

很明显的 XTEA加密

逻辑比较清晰,只是密钥未知

使用frida 将 密钥v36 hook下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function hook_x20(){
var module = Process.getModuleByName("libil2cpp.so")
var my_base = 0x7e78baa000
var addr = module.base.add(0x7E7900FC60 - my_base)

Interceptor.attach(addr,{
onEnter(args){
console.log(hexdump(this.context.x20,{
offset: 0,
length: 64,
header: true,
ansi: true
}));
},
onLeave(retval){
}
})

}
hook_x20()

并且把加密后的值 hook下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook_xtea(){
var module = Process.getModuleByName("libil2cpp.so")
var my_base = 0x7e78baa000
var addr = module.base.add(0x7E7900FD14 - my_base)

Interceptor.attach(addr,{
onEnter(args){
console.log("result_low:",this.context.x21);
console.log("result_high:",this.context.x22);
console.log("result:",this.context.x0);
},
onLeave(retval){
}
})

}
hook_xtea()

写 xtea 的解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import ctypes

def enc_xtea(input_low,input_high):
# tea系列的每个值都必须是32位无符号整数
v0,v1 = ctypes.c_uint32(input_low),ctypes.c_uint32(input_high)
# ctypes.c_uint32 不是普通数字,它是一个结构体对象,它内部有个 .value 才是真正的 32-bit 整数。
key = [0x7b777c63,0xc56f6bf2,0x2b670130,0x76abd7fe]
delta = 0x21524111
total1 = ctypes.c_uint32(0xBEEFBEEF)
total2 = ctypes.c_uint32(0x9D9D7DDE)
for i in range(64):
v0.value += (((v1.value << 7) ^ (v1.value >> 8)) + v1.value) ^ (total1.value - key[total1.value & 3])
total1.value -= delta
v1.value += (((v0.value << 8) ^ (v0.value >> 7)) - v0.value) ^ (total2.value + key[(total2.value >> 13) & 3])
total2.value -= delta
return v0.value ,v1.value

def dec_xtea(input_low,input_high):
v0,v1 = ctypes.c_uint32(input_low),ctypes.c_uint32(input_high)
key = [0x7b777c63,0xc56f6bf2,0x2b670130,0x76abd7fe]
delta = 0x21524111
total1 = ctypes.c_uint32(0xBEEFBEEF - 64 * delta)
total2 = ctypes.c_uint32(0x9D9D7DDE - 64 * delta)
for i in range(64):
total2.value += delta
v1.value -= (((v0.value << 8) ^ (v0.value >> 7)) - v0.value) ^ (total2.value + key[(total2.value >> 13) & 3])
total1.value += delta
v0.value -= (((v1.value << 7) ^ (v1.value >> 8)) + v1.value) ^ (total1.value - key[total1.value & 3])
return v0.value ,v1.value


def test():
input_low = 0x3e90dd8f
input_high = 0xc6d57ec5

#验证解密算法
input_low,input_high = enc_xtea(input_low,input_high)
assert(input_low,input_high) == (0xabba3c01,0x7223607f)

#验证加密算法
input_low,input_high = dec_xtea(input_low,input_high)
assert(input_low,input_high) == (0x3e90dd8f,0xc6d57ec5)

test()

至此四处加密都分析完了

注册机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import struct
import ctypes
algorithm_1={
(0,"enc") :lambda x: (x-28) & 0xff, # 防止溢出或负数, 确保结果在 0~255 之间
(1,"enc") :lambda x: (x^0xD3),
(2,"enc") :lambda x: (x-94) & 0xff,
(3,"enc") :lambda x: (x^0x86),

(0,"dec") :lambda x: (x+28) & 0xff,
(1,"dec") :lambda x: (x^0xD3),
(2,"dec") :lambda x: (x+94) & 0xff,
(3,"dec") :lambda x: (x^0x86)
}

# 加密点4,xtea
def dec_xtea(input_low,input_high):
v0,v1 = ctypes.c_uint32(input_low),ctypes.c_uint32(input_high)
key = [0x7b777c63,0xc56f6bf2,0x2b670130,0x76abd7fe]
delta = 0x21524111
total1 = ctypes.c_uint32(0xBEEFBEEF - 64 * delta)
total2 = ctypes.c_uint32(0x9D9D7DDE - 64 * delta)
for i in range(64):
total2.value += delta
v1.value -= (((v0.value << 8) ^ (v0.value >> 7)) - v0.value) ^ (total2.value + key[(total2.value >> 13) & 3])
total1.value += delta
v0.value -= (((v1.value << 7) ^ (v1.value >> 8)) + v1.value) ^ (total1.value - key[total1.value & 3])
return v0.value ,v1.value


# 加密点3,vm
def dec_vm_low(input):
# 按照大端序解包4个字节
byte4,byte3,byte2,byte1 = struct.unpack(">4B",input.to_bytes(4,'big'))
byte1 = (byte1 ^ 0) + 27
byte2 = (byte2 ^ 8)^ 194
byte3 = (byte3 ^ 16)- 168
byte4 = (byte4 ^ 24)^ 54
input = struct.pack("<4B",byte1 & 0xff,byte2 & 0xff,byte3 & 0xff,byte4 & 0xff)
return int.from_bytes(input,"little")

def dec_vm_high(input):
# 按照大端序解包4个字节
byte4,byte3,byte2,byte1 = struct.unpack(">4B",input.to_bytes(4,'big'))
byte1 = (byte1 -0) + 47
byte2 = (byte2 -8) ^ 182
byte3 = (byte3 -16 ) - 55
byte4 = (byte4 -24) ^ 152
input = struct.pack("<4B",byte1 & 0xff,byte2 & 0xff,byte3 & 0xff,byte4 & 0xff)
return int.from_bytes(input,"little")


# 加密点2 blackObfuscator
def dec_2(input):
input_byte = bytearray(input.to_bytes(4, 'big'))
xor_arr = [50, -51, -1, -104, 25, -78, 0x7C, -102]
for i in range(4):
input_byte[i] = (input_byte[i] - i) & 0xff
input_byte[i] = (input_byte[i] ^ xor_arr[i]) & 0xff

input = int.from_bytes(input_byte, 'big')
input = (input << 7 | input >> 25) & 0xffffffff
input = int.from_bytes(input.to_bytes(4, 'little'), 'big') # 字节再次翻转

return input

#加密点1 花指令
def dec_1(input):
input_bytes = bytearray(input.to_bytes(8,'little'))
dec_array = [0] * len(input_bytes)
for i in range(len(input_bytes)):
index = i % 4
dec_array[i] = (algorithm_1[(index,"dec")]((input_bytes[i] + 8*index)&0xff) ^ index)
return int.from_bytes(bytearray(dec_array),'little')


token = int(input("enter the token plz~:"))
input_low,input_high = token,0

# xtea
input_low,input_high = dec_xtea(input_low,input_high)

#vm
input_low,input_high = dec_vm_low(input_low),dec_vm_high(input_high)

# bswap32 ,字节序列完全反转
# 把 input_low 和 input_high 以“小端格式”写进字节数组, 返回一个 8 字节的 bytes 对象,字节数组
input = struct.pack("<2I",input_low,input_high)
# 把 8 字节(bytes)重新“读出”为两个 32bit 整数
input_low,input_high = struct.unpack(">2I",input)

# 高低位相互转换
input_low,input_high = input_high,input_low

# blackObfuscator
input_low = dec_2(input_low)
input_high = dec_2(input_high)

# 花指令
input= struct.pack('>2I',input_low,input_high)
input=int.from_bytes(input,'little')
input = dec_1(input)

print(input)

输入token后,即可生成激活外挂的密钥

跨平台前端 + Node.js 后端的全栈开发

数据库:Sealos 云端 MongoDB

源码:https://github.com/yagyiqing/uni-app-Express-v0.1-

初衷:传承社团文化


前后端之间使用 HTTP协议传输数据,数据格式用 JSON


一共 8 个页面:登录页面,主页,社团背景页面,学习路线指引页面,项目积累页面,DayBreak通讯录页面,社团活动记录页面,社团文化页面

页面样式分别如下:

登录页面:

主页:

社团背景页面

学习路线指引页面

项目积累页面

DayBreak通讯录页面

社团活动页面

社团文化页面

抓包

打开手机端小黄鸟 / 进入嘟嘟牛登录页面 / 输入账号和密码 /点击登录

抓取到一个数据包,分析得到一个 “Encrypt”

0

查壳

1743070291861.jpg

pkid.jar

脱壳

拖入Jadx 分析代码

搜索 “Encrypt” 出现 两条 可疑结果

使用 Frida 定位 确定点击登录时会走哪条方法

Hook第一个可疑函数

paraMap

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function(){
// 使用app的类
var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest");
// 确认以下看是否找到
console.log("jsonRequest:",jsonRequest);

jsonRequest.paraMap.implementation = function(a){
console.log("param1:",a);
this.paraMap();
}
});

成功找到该类 / 进入嘟嘟牛 ,点击登录 / 没有反应

0

App 可能没有走这个函数,也可能是代码写错了或者Frida版本问题

Hook第二个可疑函数

addRequestMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function(){
// 使用app的类
var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest");
// 确认看是否找到
console.log("jsonRequest:",jsonRequest);

jsonRequest.paraMap.implementation = function(a){
console.log("param1:",a);
this.paraMap();
}
jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function(a,b){
console.log("addRequestMap param:",a,b);
this.addRequestMap(a,b);
}
});

进入App 点击登录,会出现参数

970565.jpg

确定该App在登陆时走的是第二个可疑函数

打印可疑函数的参数

addRequestMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java.perform(function(){
// 使用app的类
var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest");
// 确认看是否找到
console.log("jsonRequest:",jsonRequest);

jsonRequest.paraMap.implementation = function(a){
console.log("param1:",a);
this.paraMap();
}
jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function(a,b){
console.log("addRequestMap param:",a,b);
var v = Java.cast(a,Java.use("java.util.HashMap"));
console.log("addRequestMap param:",v.toString());
this.addRequestMap(a,b);
}
});

4.jpg

app协议分析

找到第一个加密点

分析确定的可疑方法 addRequestMap

找到第一个加密点

“sign” 值加密

57.jpg

709.jpg

分析RequestUtil.encodeDesMap 方法

1743079108144.jpg

Hook Utils.md5 方法查看传入的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Java.perform(function(){
// 使用app的类
var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest");
// 确认看是否找到
console.log("jsonRequest:",jsonRequest);

jsonRequest.paraMap.implementation = function(a){
console.log("param1:",a);
this.paraMap();
}
jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function(a,b){
console.log("addRequestMap param:",a,b);
var v = Java.cast(a,Java.use("java.util.HashMap"));
console.log("addRequestMap param:",v.toString());

this.addRequestMap(a,b);
}
var utils = Java.use("com.dodonew.online.util.Utils");
utils.md5.implementation = function(a){
console.log("md5 param:",a);
var retval = this.md5(a);
console.log("md5 retval:",retval);
return retval;
}
});

1743079145050.jpg

测试下看是不是标准的md5

1743079160873.jpg

是标准的md5

找第二个加密点

返回 可疑方法 addRequestMap

1743079181739.jpg

Hook RequestUtil.encodeDesMap函数查看传入的参数

1
2
3
4
5
6
7
8
var requestUtil = Java.use("com.dodonew.online.http.RequestUtil");
requestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a,b,c){
console.log("encodeDesMap params:",a);
console.log("encodeDesMap key:",b);
console.log("encodeDesMap iv:",c);
var retval = this.encodeDesMap(a,b,c);
console.log("encodeDesMap retval:",retval);
return retval;

这里将第一个加密点的 sign 值换成大写后放入 data 参数中

1743079197955.jpg

进入 encodeDesMap 方法分析

分析该方法对参数的操作

1743079212821.jpg

该方法操作了 data, key, iv 参数

涉及 DesSecurity 方法 和 encrypt64 方法

进入 DesSecurity 方法分析

1743079225922.jpg

对密钥 key 进行了 md5 加密

利用所得密钥 对data 进行了 DES 加密

Hook DESKeySpec 方式

取 md5 加密后的前八位

1
2
3
4
5
var base64 = Java.use("android.util.Base64");
var dESkeySpec = Java.use("javax.crypto.spec.DESKeySpec");
dESkeySpec.$init.overload('[B').implementation = function(a){
console.log("DESKeySpec param:",base64.encodeToString(a,0));
this.$init(a);

1743079249013.jpg

算法复现

Hook到的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// md5 param: equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp=1742895425805&userPwd=12345678&username=15639245016&key=sdlkjsdljf0j2fsjk
// md5 retval: 174d17095a6c843775597826b40dacc2
import CryptoJS from "crypto-js";
function getSign(user,pwd,time){
var data = "equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp="+time +"&userPwd="+pwd +"&username=" + user+"&key=sdlkjsdljf0j2fsjk";

return CryptoJS.MD5(data).toString();
}
// encodeDesMap params: {"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"174D17095A6C843775597826B40DACC2","timeStamp":"1742895425805","userPwd":"12345678","username":"15639245016"}
// encodeDesMap key: 65102933
// encodeDesMap iv: 32028092
function getData(user,pwd){
var time = "1742904583925";
var sign = getSign(user,pwd,time).toUpperCase();
console.log(sign);
var _plainText = '{"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"'+ sign +'","timeStamp":"'+time +'","userPwd":"'+pwd +'","username":"'+user +'"}';

var keyMD5 = CryptoJS.MD5("65102933").toString();
var _key= CryptoJS.enc.Hex.parse(keyMD5);
var _iv = CryptoJS.enc.Utf8.parse("32028092");

return CryptoJS.DES.encrypt(_plainText,_key,{
iv:_iv,
mode:CryptoJS.mode.CBC,
padding:CryptoJS.pad.Pkcs7
}).toString();

}
console.log(getData("15639245016","12345678"));

1743079291541.jpg

协议复现

app 与服务器的交互 是依靠数据(抓包抓取到的)

js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// md5 param: equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp=1742895425805&userPwd=12345678&username=15639245016&key=sdlkjsdljf0j2fsjk
// md5 retval: 174d17095a6c843775597826b40dacc2
var CryptoJS = require('crypto-js');
function getSign(user,pwd,time){
var data = "equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp="+time +"&userPwd="+pwd +"&username=" + user+"&key=sdlkjsdljf0j2fsjk";

return CryptoJS.MD5(data).toString();
}
// encodeDesMap params: {"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"174D17095A6C843775597826B40DACC2","timeStamp":"1742895425805","userPwd":"12345678","username":"15639245016"}
// encodeDesMap key: 65102933
// encodeDesMap iv: 32028092
function encrypt(user,pwd){
var time = new Date().getTime();
var sign = getSign(user,pwd,time).toUpperCase();
console.log(sign);
var _plainText = '{"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"'+ sign +'","timeStamp":"'+time +'","userPwd":"'+pwd +'","username":"'+user +'"}';

var keyMD5 = CryptoJS.MD5("65102933").toString();
var _key= CryptoJS.enc.Hex.parse(keyMD5);
var _iv = CryptoJS.enc.Utf8.parse("32028092");

return CryptoJS.DES.encrypt(_plainText,_key,{
iv:_iv,
mode:CryptoJS.mode.CBC,
padding:CryptoJS.pad.Pkcs7
}).toString();

}
function decrypt(cipherText){
var key = CryptoJS.enc.Hex.parse(CryptoJS.MD5("65102933").toString());
var iv = CryptoJS.enc.Utf8.parse("32028092");
return CryptoJS.DES.decrypt(cipherText,key,{
iv: iv,
mode:CryptoJS.mode.CBC,
padding:CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
}
// var a = decrypt("2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=")
// console.log(a)

使用 python 进行请求伪造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import base64
import requests
import json
import execjs
import _locale
_locale._getdefaultlocale = (lambda *args: ['zh_CN', 'utf8'])
f = open("demo.js", "r")
demo_list = f.readlines()
print(list(range(0, len(demo_list))))
jscode = ""
for i in range(0,len(demo_list)):
jscode += demo_list[i]
f.close()
js = execjs.compile(jscode)
plainText = js.call('encrypt', '15639245016', '12345678')
print(plainText)
url = 'http://api.dodovip.com/api/user/login'
data = json.dumps({"Encrypt": plainText})
headers = {
"Content-Type": "application/json;charset=utf-8",
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; Pixel 3 Build/PD1A.180720.030)"
}
r = requests.post(url=url, data=data, headers=headers)
print(r)
print(r.text)
print(type(r.text))
print(r.content)
cipherText = js.call('decrypt', 'r.text')
print(cipherText)

1743079307691.jpg

1743079317970.jpg

伪造协议成功

APP简介:

基于环信SDK与UIkit 的即时通信云,实现单群聊

Android 四大组件:Activity,Service,BroadcastReceiver, CotentProvider 皆有所体现

编程语言:Kotlin