360加固保免费版分析

分析360加固保免费版,学习逆向技术(此篇未完结)

使用360加固保免费版加固,注意加固时要把签名校验给去除,因为加固之后的app是没有签名的,自己签名之后如果有签名校验,程序可能会闪退

Java层分析

这是我自己写的一个app,可以看到没有加固时可以直接看到MainActivity

image

加固之后MainActivity字样没了,出现了StubAPP类和tianyu.util字样,可以知道这就是该加固的特征

image

StubApp类里面使用a方法传递了一串字符串,跟进a方法,注意a有多个重载方法,需要查看具有一个字符串参数的重载方法

image

发现就是对字符串进行一个解混淆,方式为异或16,这里可以用Cyberchef先解一下看看

image

image

使用Frida hook看看这个方法做了些什么,直接在jadx里选择复制为frida片段

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hook() {


let a = Java.use("com.tianyu.util.a");
a["a"].overload('java.lang.String').implementation = function (str) {
console.log(`a.a is called: str=${str}`);
let result = this["a"](str);
console.log(`a.a result=${result}`);
return result;
};

}

function main() {

Java.perform(function () {
hook();
})
}

可以看到就是加载了一些Android系统内部类或方法名

image

继续观察StubApp类,看见下面会根据设备的架构来加载不同的so文件

image

再与正常未加固的apk对比一下,发现加固之后的apk多了一个assets文件夹,里面存着一些so文件

image

image

不难分析出,该加固是在Native层来释放dex文件

Native层分析

分析so文件,发现导入导出表被抹除得一干二净

image

如果没有导入导出表的话,elf文件应该是使用了自定义的动态链接器来进行链接的,所以,只要再elf文件被装载进内存之后将它dump下来,应该就能恢复符号表了

在Linux系统中,dlopen函数用于动态链接库加载函数,它存在于libdl.so库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 加载库
void* libHandle = dlopen("/data/data/pkg/libnative.so", RTLD_NOW);
if (!libHandle) {
printf("Error: %s\n", dlerror());
return;
}

// 2. 获取函数指针
typedef int (*NativeFunc)(int);
NativeFunc func = (NativeFunc)dlsym(libHandle, "native_add");
if (!func) {
printf("Error: %s\n", dlerror());
dlclose(libHandle);
return;
}

// 3. 调用函数
int result = func(42);

// 4. 卸载库
dlclose(libHandle);

在安卓7.0之后,则需要hook的是android_dlopen_ext函数

frida hook一下看看它加载了哪些函数

android_dlopen_ext() 的格式为android_dlopen_ext("/data/data/pkg/libsecret.so", RTLD_NOW, NULL);,所以我们需要获取第一个参数的值来得到它链接了哪些so文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function hook() {

Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
onEnter: function (args) {
// console.log('Entering ' + functionName);
// Modify or log arguments if needed
console.log("load -> ", args[0].readCString());
},
onLeave: function (retval) {
// console.log('Leaving ' + functionName);
// Modify or log return value if needed
}
});
}

function main() {

Java.perform(function () {
hook();
})
}

setImmediate(main);

正如所料,它链接了这个so文件

image

接下来在它将该so文件装载到内存之后dump下来,就可以得到带有导入导出表的so文件了

(dump脚本来自SWDD)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function dump_so() {
var soName = "libjiagu_64.so";
var libSo = Process.getModuleByName(soName);
var save_path = "/data/data/com.example.nativetest/" + libSo.name + "_Dump";
console.log("[Base]->", libSo.base);
console.log("[Size]->", ptr(libSo.size));
var handle = new File(save_path, "wb");
Memory.protect(ptr(libSo.base), libSo.size, 'rwx');
var Buffer = libSo.base.readByteArray(libSo.size);
handle.write(Buffer);
handle.flush();
handle.close();
console.log("[DumpPath->]", save_path);

}

setImmediate(dump_so);

注意,要在app运行之后再把脚本附加上去,否则如果还没来得及链接就dump的话,frida会直接报错

image

所以这里使用命令

1
frida -U 'NativeTest' -l dump_so.js

image

成功dump,并且得到了文件的基地址和大小

image

成功恢复

image

image

壳文件分析

这里有一个知识点

加固壳的典型行为模式

加固壳的核心任务是 保护原始代码,其常见流程包括:

  1. 解密资源

    • 原始 APK/Dex/So 文件被加密,隐藏在 assetslib/ 或自定义目录中。
    • 运行时,壳代码需要先 读取这些加密文件(通过 open + read)。
  2. 动态加载

    • 解密后的文件(如 Dex、So)会通过 dlopenmmapDexClassLoader 加载到内存。
  3. 内存执行

    • 解密后的代码在内存中执行,避免留下完整的磁盘文件。

关键点
加固壳必须读取加密文件 → 必然调用 open 函数 → Hook open 可以捕获文件访问路径

思路就是 hook open函数来看看它有没有读取什么东西

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
function hook() {

var openPtr = Module.findExportByName(null, "open");
// var openAdrr = new NativePointer(openPtr);
const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

Interceptor.replace(openPtr, new NativeCallback(function (fileNamePtr, flag) {

var fileName = fileNamePtr.readCString();
console.log("[open : ]", fileName);

return open(fileNamePtr, flag);
} , 'int', ['pointer', 'int']))


}

function main() {

Java.perform(function () {
hook();
})
}

// setTimeout(main, 1000);
setImmediate(main);

注意这里一定要new一个NativeCallback

在 Frida 中,NativeCallback 的作用是 创建一个符合原生代码调用约定的 JavaScript 回调函数,用于替换或拦截原生函数(如 open

原生代码(如 C 的 open 函数)有严格的 参数传递规则(如寄存器/栈传参、类型转换),而 JavaScript 是弱类型语言,无法直接匹配

不然的话会出现如下报错

fileNamePtr 可能未被正确识别为 pointer 类型

image

可以看到打开了如下文件

image

发现个很可疑的点,它频繁访问了 /proc/self/maps

/proc/self/maps 的作用

  • 功能:该文件实时显示当前进程的内存映射布局,包括:

    • 加载的模块(.so/.dex)基地址和大小
    • 内存权限(可读/可写/可执行)
    • 文件来源路径
  • 典型访问场景

    • 加固壳:检测内存是否被篡改(反调试)。
    • 动态加载库:定位空闲内存区域加载新代码。
    • 内存扫描:查找敏感数据或函数地址。

这里反复读取maps猜测是为了混淆视听,当加载dex时,maps上的内存映射会发生变化,留下记录,比如 /data/app/xxx/base.dex,这时候壳文件就通过反复读取maps来隐藏打开dex的操作,掩盖真正的 Dex 加载时机

此时,如果我们自定义一个fakeMaps,将壳文件对maps的操作重定向到我们的fakeMaps上,就可以很方便地观察壳文件加载dex的操作了,同时也能避免壳的反检测机制(如果有的话)

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
function hook() {

var openPtr = Module.findExportByName(null, "open");
// var openAdrr = new NativePointer(openPtr);
const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

var fakeMaps = "/data/data/com.example.nativetest/maps"

Interceptor.replace(openPtr, new NativeCallback(function (fileNamePtr, flag) {

var fileName = fileNamePtr.readCString();
console.log("[open : ]", fileName);

var FD = open(fileNamePtr, flag); //调用原始open函数,记录原始open函数的文件描述符

if (fileName.indexOf("maps") >= 0) { //如果文件名包含maps,将其重定向到fakeMaps上

console.log("sucess find maps");

var fakeMapsAddr = Memory.allocUtf8String(fakeMaps);
return open(fakeMapsAddr, flag); // 打开伪造的maps文件,并返回其文件描述符(FD),而非真实的maps

}

if (fileName.indexOf("dex" != -1)) {
console.warn("open dex :", fileName);
}

return FD; //返回原始FD使app正常运行
// return open(fileNamePtr, flag);
}, 'int', ['pointer', 'int']))


}

function main() {

Java.perform(function () {
hook();
})
}

// setTimeout(main, 1000);
setImmediate(main);

可以发现确实是通过open去打开了dex文件,而且通过反复读取了maps来隐藏操作,验证了我们之前的猜想

image

使用来查看dex的内存地址

1
console.warn('dex called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');
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
function hook() {

var openPtr = Module.findExportByName(null, "open");
// var openAdrr = new NativePointer(openPtr);
const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
var fakeMaps = "/data/data/com.example.nativetest/maps";

Interceptor.replace(openPtr, new NativeCallback(function (fileNamePtr, flag) {

var fileName = fileNamePtr.readCString();
console.log("[open : ]", fileName);
var FD = open(fileNamePtr, flag); //调用原始open函数,记录原始open函数的文件描述符

if (fileName.indexOf("maps") >= 0) { //如果文件名包含maps,将其重定向到fakeMaps上

console.log("sucess find maps");
var fakeMapsAddr = Memory.allocUtf8String(fakeMaps);
return open(fakeMapsAddr, flag); // 打开伪造的maps文件,并返回其文件描述符(FD),而非真实的maps

}

if (fileName.indexOf("dex") != -1) {
console.warn("open dex :", fileName);
console.warn('dex called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');

}

return FD; //返回原始FD使app正常运行
// return open(fileNamePtr, flag);
}, 'int', ['pointer', 'int']))


}

function main() {

Java.perform(function () {
hook();
})
}

// setTimeout(main, 1000);+
setImmediate(main);

image

可以发现打开dex的地址基本一模一样,在IDA中查看这个地址

image

嘶,啥也没有,这不对吧

查阅多方资料,发现我漏了一步,在先前dump so文件的时候,so文件可能会有损坏,所以先需要修复一下so文件

使用soFixer

soFixer 0x0x7fff56d99000是之前dump so文件的时候输出的基地址

1
.\SoFixer-Windows-64.exe -s .\libjiagu_64.so_Dump -o .\libjiagu_64.so_Fix -m 0x7fff56d99000 -d

image

image

使用sofix之后IDA将其识别成了ARM架构,但是我原本的文件是x86_64架构,这里要重新选择一下

image

image

这下就没问题了

image

但是这段数据全是未定义的,这个时候就手足无措了

一通乱翻,发现了这样一个函数

image

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
char __fastcall __arm_a_2(char *a1, unsigned __int64 a2, char *a3, int *a4, unsigned int a5)
{
unsigned __int64 v5; // rax
__int64 *v6; // rsi
__int64 v7; // rbx
__int64 *v8; // rdi
__int64 v9; // rax
_QWORD *v10; // rbx
char v12[8]; // [rsp+90h] [rbp-168h] BYREF
unsigned __int64 v13; // [rsp+98h] [rbp-160h]
__int64 v14; // [rsp+A0h] [rbp-158h]
__int64 v15[7]; // [rsp+B0h] [rbp-148h] BYREF
_QWORD v16[2]; // [rsp+E8h] [rbp-110h] BYREF
__int128 v17; // [rsp+F8h] [rbp-100h]
__int128 v18[8]; // [rsp+108h] [rbp-F0h] BYREF
__int128 v19; // [rsp+188h] [rbp-70h]
__int128 v20; // [rsp+198h] [rbp-60h]
__int128 v21[2]; // [rsp+1A8h] [rbp-50h] BYREF
int *v22; // [rsp+1D0h] [rbp-28h]
unsigned __int64 v23; // [rsp+1D8h] [rbp-20h]
unsigned __int64 v24; // [rsp+1E8h] [rbp-10h]

v24 = __readfsqword(0x28u);
v23 = a5;
v22 = a4;
sub_2DA0();
v5 = __readfsqword(0x28u);
if ( v5 != v24 )
{
sub_2D40(qword_1C928, 0x190LL);
v23 = a1;
v22 = __readfsqword(0x28u);
memset(v21, 0, sizeof(v21));
v20 = 0LL;
v19 = 0LL;
memset(v18, 0, sizeof(v18));
v17 = 0LL;
sub_2D20(v18 + 4, "*.so", 0x80LL, 0xBCLL, 5LL);
v16[0] = &qword_234D0;
v16[1] = 0xAC3FDLL;
LODWORD(v18[0]) = 1;
*&v20 = off_233B8;
*(&v19 + 1) = &qword_CFD40;
DWORD2(v21[1]) = 1;
*(&v20 + 1) = 0x400000002LL;
LODWORD(v21[0]) = 5;
*(v21 + 8) = 0LL;
sub_7330(v15);
v15[0] = qword_22CA8 + 16;
v6 = &qword_CF8D0;
if ( sub_7770(v15, &qword_CF8D0, 1062LL) )
{
v6 = v15;
v7 = sub_5C10(v16, v15);
if ( *(&v21[0] + 1) )
(sub_2EE0)();
}
else
{
v7 = 0LL;
}
v8 = v15;
sub_7580(v15);
if ( __readfsqword(0x28u) == v22 )
{
LOBYTE(v5) = v7;
}
else
{
sub_2D40(v15, v6);
v14 = v7;
v13 = __readfsqword(0x28u);
if ( v15 )
{
strcpy(v12, "makekey");
v6 = v12;
v9 = sub_5D40(v15, v12);
if ( v9 )
{
v10 = v9;
v8 = (v9 & 0xFFFFFFFFFFFFF000LL);
v6 = ("pthread_create" + 3);
sub_2E50(v9 & 0xFFFFFFFFFFFFF000LL, 4096LL, 3LL);
*v10 = qword_22CC0;
}
}
v5 = __readfsqword(0x28u);
if ( v5 != v13 )
{
sub_2D40(v8, v6);
LOBYTE(v5) = 1;
}
}
}
return v5;
}

image

查看sub_50E0

image

发现了一些用于加载动态链接相关的字符串,结合之前创建线程,这个时候就猜测,应该已经在加载另外的so了

安卓系统中,完成linker之后,dlopen去加载这个so,我们之前hook的是android_dlopen_ext,这里再去hook一下dlopen看看它加载了哪些so文件

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
function hook() {

Interceptor.attach(Module.findExportByName("libdl.so", "android_dlopen_ext"), {
onEnter: function (args) {
// console.log('Entering ' + functionName);
// Modify or log arguments if needed
console.log("load -> ", args[0].readCString());
},
onLeave: function (retval) {
// console.log('Leaving ' + functionName);
// Modify or log return value if needed
}
});

}

function hook2() {

Interceptor.attach(Module.findExportByName("libdl.so", "dlopen"), {
onEnter: function (args) {
// console.log('Entering ' + functionName);
// Modify or log arguments if needed
console.warn("dlopen load -> ", args[0].readCString());
},
onLeave: function (retval) {
// console.log('Leaving ' + functionName);
// Modify or log return value if needed
}
});
}

function main() {

Java.perform(function () {
hook();
hook2();
})
}

// setTimeout(main, 1000);
setImmediate(main);

image

看到这里,基本上可以说明就是自定义linker加固so文件了

查阅资料发现,自定义linker加固so的流程是,自定义文件格式加密so,然后壳代码实现将加密的so文件加载,链接重定位并修正soinfo(三部曲)

简单来说就是将elf文件的.text等数据段进行加密,然后在link的时候补充soinfo

soinfoAndroid linker 内部维护的数据结构,用于 管理已加载的共享库(.so) 。每个被 dlopen 或程序依赖的 .so 都会有一个对应的 soinfo 结构,存储:

  • 库的基地址(加载地址)
  • 符号表、重定位表、依赖关系
  • 动态段(.dynamic)解析后的信息
  • 命名空间(用于库隔离)
  • soinfo 的生命周期

    1. 加载阶段dlopenlinker 解析 ELF → 创建 soinfo 并填充信息。
    2. 链接阶段linker 根据 .dynamic 段解析依赖、符号、重定位。
    3. 运行时dlsym 通过 soinfo 查找符号地址。
    4. 卸载阶段dlclose 释放 soinfo

由于之前的so在执行的时候link了另外的so,所以将其放入010editor里查找elf头

image

找到了elf头,并且可以看到program header已经被加密了

写一个脚本,将0xd0000之后的内容提取出来

提取出来的so文件ida是打不开的,因为program header已经被加密了

image

此时就需要找到这个so是在哪里被解密的

用oacia大佬的项目来分析一下程序执行流

https://github.com/oacia/stalker_trace_so

image

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
[Android Emulator 5554::com.example.nativetest ]-> start Stalker!
Stalker end!
call1:JNI_OnLoad
call2:sub_C840
call3:ffi_call
call4:sub_C450
call5:sub_7330
call6:sub_7770
call7:sub_7370
call8:_ZN9__arm_c_19__arm_c_0Ev
call9:sub_77B0
call10:sub_71F0
call11:sub_C560
call12:sub_7240
call13:sub_42C0
call14:sub_6310
call15:sub_6A30
call16:sub_6760
call17:sub_4B40
call18:sub_4F70
call19:sub_50E0
call20:sub_3B20
call21:sub_7000
call22:sub_6350
call23:sub_7580
call24:sub_10E1D0
call25:sub_1BC3C0
call26:sub_10B270
call27:sub_1465C0
call28:sub_14AC90
call29:sub_10A5C0
call30:sub_1BDD20
call31:sub_14B3E0
call32:sub_14D100
call33:sub_1BDD50
call34:sub_1A9BD0
call35:sub_1459B0
call36:sub_1BD700
call37:sub_1BD790
call38:sub_10ED90
call39:sub_1120F0
call40:sub_10CC10
call41:sub_106F90
call42:sub_1BB530
call43:sub_1BB620
call44:sub_1BC4E0
call45:sub_1BB980
call46:sub_1BBC00
call47:sub_1BBE10
call48:sub_1BC1D0
call49:sub_1BC4B0
call55:sub_1BDF10
call56:sub_116720
call57:sub_1BDD00
call58:sub_1AD9E0
call59:sub_1ADB60
call60:sub_1C49A0
call61:sub_1CAD30
call62:sub_1C8EC0
call63:sub_16FA70

再看一下fix之后的文件的控制流

image

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
[Android Emulator 5554::com.example.nativetest ]-> start Stalker!
call1:JNI_OnLoad
call2:sub_C840
call3:ffi_call
call4:sub_C450
call5:sub_7330
call6:sub_7770
call7:sub_7370
call8:_ZN9__arm_c_19__arm_c_0Ev
call9:sub_77B0
call10:sub_71F0
call11:sub_C560
call12:sub_7240
call13:sub_42C0
call14:sub_6310
call15:sub_6A30
call16:sub_6760
call17:sub_4B40
call18:sub_4F70
call19:sub_50E0
call20:sub_3B20
call21:sub_7000
call22:sub_6350
call23:sub_7580
call24:sub_10E1D0
call25:sub_1BC3C0
call26:sub_10B270
call27:sub_1465C0
call28:sub_14AC90
call29:sub_10A5C0
call30:sub_1BDD20
call31:sub_14B3E0
call32:sub_14D100
call33:sub_1BDD50
call34:sub_1A9BD0
call35:sub_1459B0
call36:sub_1BD700
call37:sub_1BD790
call38:sub_10ED90
call39:sub_1120F0
call40:sub_10CC10
call41:sub_106F90
call42:sub_1BB530
call43:sub_1BB620
call44:sub_1BC4E0
call45:sub_1BB980
call46:sub_1BBC00
call47:sub_1BBE10
call48:sub_1BC1D0
call49:sub_1BC4B0
call50:sub_14B000
call51:sub_14D310
call52:_Z9__arm_a_2PcmS_Rii
call53:sub_14ACC0
call54:sub_10C5F0
call55:sub_1BDF10
call56:sub_116720
call57:sub_1BDD00
call58:sub_1AD9E0
call59:sub_1ADB60
call60:sub_1C49A0
call61:sub_1CAD30
call62:sub_1C8EC0
call63:sub_16FA70

知道了控制流之后,虽然是自定义linker加固so,但是最后肯定还是需要dlopen去加载so的,在IDA里交叉引用一下dlopen,看看在哪里被调用了

image

image

只有一处调用,全是switch case结构

image

查看Android源码linker的预链接部分,同样也是大量的switch case结构

image

此时就可以在IDA中导入soinfo结构体了(结构体代码来自SWDD)

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
//IMPORTANT
//ELF64 启用该宏
#define __LP64__ 1
//ELF32 启用该宏
//#define __work_around_b_24465209__ 1

/*
//https://android.googlesource.com/platform/bionic/+/master/linker/Android.bp
架构为 32 位 定义__work_around_b_24465209__宏
arch: {
arm: {cflags: ["-D__work_around_b_24465209__"],},
x86: {cflags: ["-D__work_around_b_24465209__"],},
}
*/

//android-platform\bionic\libc\include\link.h
#if defined(__LP64__)
#define ElfW(type) Elf64_ ## type
#else
#define ElfW(type) Elf32_ ## type
#endif

//android-platform\bionic\linker\linker_common_types.h
// Android uses RELA for LP64.
#if defined(__LP64__)
#define USE_RELA 1
#endif

//android-platform\bionic\libc\kernel\uapi\asm-generic\int-ll64.h
//__signed__-->signed
typedef signed char __s8;
typedef unsigned char __u8;
typedef signed short __s16;
typedef unsigned short __u16;
typedef signed int __s32;
typedef unsigned int __u32;
typedef signed long long __s64;
typedef unsigned long long __u64;

//A12-src\msm-google\include\uapi\linux\elf.h
/* 32-bit ELF base types. */
typedef __u32 Elf32_Addr;
typedef __u16 Elf32_Half;
typedef __u32 Elf32_Off;
typedef __s32 Elf32_Sword;
typedef __u32 Elf32_Word;

/* 64-bit ELF base types. */
typedef __u64 Elf64_Addr;
typedef __u16 Elf64_Half;
typedef __s16 Elf64_SHalf;
typedef __u64 Elf64_Off;
typedef __s32 Elf64_Sword;
typedef __u32 Elf64_Word;
typedef __u64 Elf64_Xword;
typedef __s64 Elf64_Sxword;

typedef struct dynamic{
Elf32_Sword d_tag;
union{
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;

typedef struct {
Elf64_Sxword d_tag; /* entry tag value */
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;

typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;

typedef struct elf64_rel {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
} Elf64_Rel;

typedef struct elf32_rela{
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;

typedef struct elf64_rela {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
Elf64_Sxword r_addend; /* Constant addend used to compute value */
} Elf64_Rela;

typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;

typedef struct elf64_sym {
Elf64_Word st_name; /* Symbol name, index in string tbl */
unsigned char st_info; /* Type and binding attributes */
unsigned char st_other; /* No defined meaning, 0 */
Elf64_Half st_shndx; /* Associated section index */
Elf64_Addr st_value; /* Value of the symbol */
Elf64_Xword st_size; /* Associated symbol size */
} Elf64_Sym;

#define EI_NIDENT 16

typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;

typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;

/* These constants define the permissions on sections in the program
header, p_flags. */
#define PF_R 0x4
#define PF_W 0x2
#define PF_X 0x1

typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;

typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;

typedef struct elf32_shdr {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;

typedef struct elf64_shdr {
Elf64_Word sh_name; /* Section name, index in string tbl */
Elf64_Word sh_type; /* Type of section */
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Size of section in bytes */
Elf64_Word sh_link; /* Index of another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;

//android-platform\bionic\linker\linker_soinfo.h
typedef void (*linker_dtor_function_t)();
typedef void (*linker_ctor_function_t)(int, char**, char**);

#if defined(__work_around_b_24465209__)
#define SOINFO_NAME_LEN 128
#endif

struct soinfo {
#if defined(__work_around_b_24465209__)
char old_name_[SOINFO_NAME_LEN];
#endif
const ElfW(Phdr)* phdr;
size_t phnum;
#if defined(__work_around_b_24465209__)
ElfW(Addr) unused0; // DO NOT USE, maintained for compatibility.
#endif
ElfW(Addr) base;
size_t size;

#if defined(__work_around_b_24465209__)
uint32_t unused1; // DO NOT USE, maintained for compatibility.
#endif

ElfW(Dyn)* dynamic;

#if defined(__work_around_b_24465209__)
uint32_t unused2; // DO NOT USE, maintained for compatibility
uint32_t unused3; // DO NOT USE, maintained for compatibility
#endif

soinfo* next;
uint32_t flags_;

const char* strtab_;
ElfW(Sym)* symtab_;

size_t nbucket_;
size_t nchain_;
uint32_t* bucket_;
uint32_t* chain_;

#if !defined(__LP64__)
ElfW(Addr)** unused4; // DO NOT USE, maintained for compatibility
#endif

#if defined(USE_RELA)
ElfW(Rela)* plt_rela_;
size_t plt_rela_count_;

ElfW(Rela)* rela_;
size_t rela_count_;
#else
ElfW(Rel)* plt_rel_;
size_t plt_rel_count_;

ElfW(Rel)* rel_;
size_t rel_count_;
#endif

linker_ctor_function_t* preinit_array_;
size_t preinit_array_count_;

linker_ctor_function_t* init_array_;
size_t init_array_count_;
linker_dtor_function_t* fini_array_;
size_t fini_array_count_;

linker_ctor_function_t init_func_;
linker_dtor_function_t fini_func_;

/*
#if defined (__arm__)
// ARM EABI section used for stack unwinding.
uint32_t* ARM_exidx;
size_t ARM_exidx_count;
#endif
size_t ref_count_;
// 怎么找不 link_map 这个类型的声明...
link_map link_map_head;

bool constructors_called;

// When you read a virtual address from the ELF file, add this
//value to get the corresponding address in the process' address space.
ElfW (Addr) load_bias;

#if !defined (__LP64__)
bool has_text_relocations;
#endif
bool has_DT_SYMBOLIC;
*/
};

image

按Y将该函数类型改成so

image

好看多了,但看着还是有点奇怪,可能这个soinfo被魔改过了

image

交叉引用一下,来到上一个函数,有这样一个函数,我们点进去看看

image

最终来到这个地方

image

有一个很可疑的点是v5往上加了0x38

在ELF文件中,32 位 ELF 文件程序头大小是 0x20(32 字节),64 位 ELF 文件程序头大小是 0x38(56 字节)

这里正好就是0x38,所以这里可能就在加载程序头了。

既然要加载程序头,那么在加载之前肯定需要解密,往上引用一下,最后又来到这里,有点眼熟

image

还记得之前拿到的执行流么,从7000,即加载程序头的函数开始,往上找一找

在50E0的位置发现这里加载了一些东西

image

image

继续往上翻,在7240的位置填入了一个地址进去

image

去那个地址看一眼,AUV,这不rc4么

image

image

上面那个则是初始化算法

image

直接hook出sbox