首页
社区
课程
招聘
国外某手游保护的简单分析
2023-5-16 18:34 16872

国外某手游保护的简单分析

2023-5-16 18:34
16872

记录的比较流水账,大佬们将就看看
样本:魔灵召唤,apkpure可以下载
加固:nprotect
工具:ida、010、jadx

apk简单分析

  • jadx打开目标apk,通过application类,可以找到目标so

  • 010打开目标libcompatible.so,发现dynamic节是正常的,不需要额外处理

  • section是不正常的,直接删除后简单修复一下,只需要修复部分对分析有用的节信息

分析init_proc

  • 函数体中被加入了很多没什么意义的导出符号,目的是为了影响ida的静态分析,方法F5会失败
  • 解决办法是在被ida错误识别为函数的位置,按下快捷键u,再快捷键c重新解析为代码,最后选中全部函数,快捷键p解析为函数

  • 首先会调用calloc申请一块大小为0x1070的内存,后文简称calloc_addr_config

  • 开始会从文件0x94c偏移处拷贝了0x70大小的数据到calloc_addr_config
1
2
3
4
5
6
7
1C 6D 00 00 00 00 00 00  38 62 00 00 00 00 00 00
38 62 00 00 00 00 00 00  54 CF 00 00 00 00 00 00
EC 12 00 00 00 00 00 00  94 02 00 00 00 00 00 00
1C 6D 00 00 00 00 00 00  54 CF 00 00 00 00 00 00
B0 D0 18 00 00 00 00 00  40 E2 00 00 00 00 00 00
90 0B 02 00 00 00 00 00  2C 1F 03 00 00 00 00 00
E8 24 16 00 00 00 00 00  C8 19 00 00 00 00 00 00
  • 利用重定位的方法获取到了自身模块基址
  • 从自身模块基址解析ELF格式,获取到自身模块映射到内存后的大小,并使用mprotect修改了内存权限为rwx
  • 同时调用mmap申请了一块大小为0x1000的内存,后文简称mmap_addr_config

  • calloc_addr_config+0x60处获取到0x1624e8+0x14 =0x1624fc, 再从文件0x1624fc偏移处拷贝了0x1000大小的数据到calloc_addr_config+0x70

  • 紧接着对拷贝过去的数据进行了解密,调用的是sub_18CB9C方法来解密
  • 部分处理后数据如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
12 00 00 00 03 00 00 00  84 01 00 00 3C 00 00 00
09 00 00 00 C4 01 00 00  3C 00 00 00 0B 00 00 00
04 02 00 00 3C 00 00 00  08 00 00 00 44 02 00 00
3C 00 00 00 0E 00 00 00  84 02 00 00 3C 00 00 00
07 00 00 00 C4 02 00 00  3C 00 00 00 12 00 00 00
04 03 00 00 3C 00 00 00  02 00 00 00 44 03 00 00
3C 00 00 00 0C 00 00 00  84 03 00 00 3C 00 00 00
0D 00 00 00 C4 03 00 00  3C 00 00 00 04 00 00 00
04 04 00 00 3C 00 00 00  05 00 00 00 44 04 00 00
3C 00 00 00 0A 00 00 00  84 04 00 00 3C 00 00 00
06 00 00 00 C4 04 00 00  3C 00 00 00 10 00 00 00
04 05 00 00 3C 00 00 00  0F 00 00 00 44 05 00 00
3C 00 00 00 11 00 00 00  84 05 00 00 3C 00 00 00
16 00 00 00 C4 05 00 00  3C 00 00 00 00 00 00 00
 
第一个DWORD,是整个数组的大小,大小为0x12
12个字节为一组
{
  +0  03    // 序号
  +4  0x184 // 数据基于 (calloc_addr_config + 0x70) 的偏移
  +8  0x3c
}
  • 循环获取上述数组中的偏移,从(calloc_addr_config + 0x70) + offset处拷贝数据到mmap_addr_config

  • 再获取对应的序号,根据序号将上述的mmap_addr_config的地址保存到对应的全局变量中

  • 在方法sub_172444中解密了一些字符串,并保存到全局变量中

1
2
3
4
5
6
7
8
9
10
byte_207620  -> /proc/
byte_2076A0  -> /mem
byte_207720  -> /pagemap
byte_2077A0  -> /maps
byte_207820  -> task
byte_2078A0  -> self
byte_207920  -> /proc/%d/%s
byte_2079A0  -> task/%d/%s
byte_207A20  -> /proc/%d/task/%d%s
byte_207AA0  -> proc/
  • 调用了_system_property_get方法
1
2
3
-> ro.build.version.sdk获取了手机sdk  : off_1FF300 -> sdk
-> ro.product.cpu.abi获取了手机cpu架构 : 将对应的 "arm" 字符串保存到了 off_1FF6C8
-> ro.build.version.codename         : 将对应的 "REL" 字符串保存到了 off_1FFCF0
  • 通过ELF格式解析,获取到了自身模块的重定位表的信息
  • 获取到重定位表在内存中的地址和大小后,调用memset将内存中的重定位相关的数据全部清零抹除
  • 防止内存dump?,原文件又不是没有,有点多此一举

  • 开始进行0xcf54处方法的解密

  • 方法实际上只加密了一部分,在方法sub_18CB9C执行完后,将解密后的数据dump下来即可
  • 后续很多方法都是先解密再调用,调用完后又给加密上
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
解密流程:
1、先从calloc_addr_config中获取到目标函数的偏移
  v603 = *(_QWORD *)(calloc_addr_94C_qword_206EA0 + 24);// 24 = 0xcf54, 32 = 0x12ec
  v602 = *(_QWORD *)(calloc_addr_94C_qword_206EA0 + 32);
 
2、调用方法sub_18ACB8,生成对应的一个key
  get_keydata_sub_18ACB8(                       // 先根据 self_module_base + 0xCF54处的数据,获取到key_data
      (__int64)v793,
      (unsigned __int8 *)(self_module_base_qword_206EA8 + *(_QWORD *)(calloc_addr_94C_qword_206EA0 + 24)),
      self_module_base_qword_206EA8 + *(_QWORD *)(calloc_addr_94C_qword_206EA0 + 24),
      0x10u,
      16);
 
3、拷贝需要解密的数据到 calloc_addr_config + 偏移的位置
  v658 = (unsigned __int64)off_1FFD98 & 0xFFFFFFFFFFFFFFFELL;
  do
  {
    v659 = *(_OWORD *)(v658 + count_v657);
    v660 = *(_OWORD *)(v658 + count_v657 + 16);
    v661 = (_OWORD *)(v646 + count_v657);
    count_v657 += 32LL;
    *v661 = v659;                         // 0ff_1ffd98 拷贝数据到self_module_base + 0xcf54
    v661[1] = v660;
  }
  while ( v656 != count_v657 );
 
4、调用方法sub_18CB9C,进行解密,此时函数的解密已经完成
  decrypt_data_sub_18CB9C(
                (__int64)v793,
                (unsigned __int8 *)v626,                    // self_module_base + 0xcf54
                self_module_base_qword_206EA8 + v678,       // self_module_base + 0xcf54
                (v625 ^ 0xFFFFFFFF0000000FLL) & v625,       // 0x290
                0);
  • 解密完后,会立即调用0xcf54处方法

0xCF54方法分析

  • 首先解密字符串"JNI_OnLoad",保存到 module_base + 0x8540b
  • 这样Android虚拟机在调用JNI_OnLoad之前还原了字符串,可以让虚拟机正确找到JNI_OnLoad的方法地址
  • 但是使用ida打开so文件时,则无法找到JNI_OnLoad。算是静态分析对抗吧。
  • 字符串还原后,可以知道JNI_OnLoad方法的地址即0x52abc,会在后续的方法中进行解密

  • 解密方法 0x18D0B0,与0xCF54方法解密相似,同样直接dump就行了

  • 调用0xcf54方法结束后,会立即将0xcf54处方法抹除掉,再调用解密完成的0x18D0B0方法

0x18D0B0方法分析

  • 解密方法 0xE240,与0xCF54方法解密相似,同样直接dump就行了

  • 调用0x18D0B0方法结束后,同样会立即将0x18D0B0处方法抹除掉

  • so走到0xc978调用0xE240处的方法

0xE240方法分析

  • 首先是调用sub_D3474方法,用来动态生成svc的调用代码
  • svc方法存放的地址会根据随机数随机确认,之前的方法中有生成过一个全局的随机数数组
  • 随后会将svc方法的调用地址保存到全局变量off_1FFA18[19]off_1FFA18[20]各一份
  • 分析svc的代码可以知道对应的系统调用号是由w7 ^ 0xE350F765得到
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
svc代码:
libcompatible.so:0000007479C6E4E0 FF 03 04 D1 SUB             SP, SP, #0x100
libcompatible.so:0000007479C6E4E4 FD 7B 09 A9 STP             X29, X30, [SP,#0x90]
libcompatible.so:0000007479C6E4E8 7A 00 00 14 B               loc_7479C6E6D0
 
libcompatible.so:0000007479C6E6D0 68 00 00 18 LDR             W8, =0xE350F765
libcompatible.so:0000007479C6E6D4 08 01 07 4A EOR             W8, W8, W7
libcompatible.so:0000007479C6E6D8 85 FF FF 17 B               loc_7479C6E4EC
 
libcompatible.so:0000007479C6E4EC 50 00 00 58 LDR             X16, =0x7479C6E8D0
libcompatible.so:0000007479C6E4F0 00 02 1F D6 BR              X16
libcompatible.so:0000007479C6E4F4 D0 E8 C6 79+qword_7479C6E4F4 DCQ 0x7479C6E8D0
 
libcompatible.so:0000007479C6E8D0 01 00 00 D4 SVC             0
libcompatible.so:0000007479C6E8D4 01 00 00 90 ADRP            X1, #0x7479C6E000
libcompatible.so:0000007479C6E8D8 50 00 00 58 LDR             X16, =0x7479C6E80C
libcompatible.so:0000007479C6E8DC 00 02 3F D6 BLR             X16
libcompatible.so:0000007479C6E8E0 0C E8 C6 79+qword_7479C6E8E0 DCQ 0x7479C6E80C
 
libcompatible.so:0000007479C6E80C E0 07 08 A9 STP             X0, X1, [SP,#0x80]
libcompatible.so:0000007479C6E810 E2 0F 07 A9 STP             X2, X3, [SP,#0x70]
libcompatible.so:0000007479C6E814 E4 17 06 A9 STP             X4, X5, [SP,#0x60]
libcompatible.so:0000007479C6E818 E6 1F 05 A9 STP             X6, X7, [SP,#0x50]
libcompatible.so:0000007479C6E81C E2 03 1E AA MOV             X2, X30
libcompatible.so:0000007479C6E820 28 01 00 58 LDR             X8, =0x7479C5B42C
libcompatible.so:0000007479C6E824 00 01 3F D6 BLR             X8
libcompatible.so:0000007479C6E828 E0 07 48 A9 LDP             X0, X1, [SP,#0x80]
libcompatible.so:0000007479C6E82C E2 0F 47 A9 LDP             X2, X3, [SP,#0x70]
libcompatible.so:0000007479C6E830 E4 17 46 A9 LDP             X4, X5, [SP,#0x60]
libcompatible.so:0000007479C6E834 E6 1F 45 A9 LDP             X6, X7, [SP,#0x50]
libcompatible.so:0000007479C6E838 FD 7B 49 A9 LDP             X29, X30, [SP,#0x90]
libcompatible.so:0000007479C6E83C FF 03 04 91 ADD             SP, SP, #0x100
libcompatible.so:0000007479C6E840 C0 03 5F D6 RET
libcompatible.so:0000007479C6E844 2C B4 C5 79+qword_7479C6E844 DCQ 0x7479C5B42C 
 
libcompatible.so:0000007479C5B42C 88 09 00 F0 ADRP            X8, #qword_7479D8EEA8@PAGE
libcompatible.so:0000007479C5B430 08 55 47 F9 LDR             X8, [X8,#qword_7479D8EEA8@PAGEOFF]
libcompatible.so:0000007479C5B434 1F 01 01 EB CMP             X8, X1
libcompatible.so:0000007479C5B438 28 01 00 54 B.HI            loc_7479C5B45C
libcompatible.so:0000007479C5B43C 89 09 00 F0 ADRP            X9, #qword_7479D8EEB0@PAGE
libcompatible.so:0000007479C5B440 29 59 47 F9 LDR             X9, [X9,#qword_7479D8EEB0@PAGEOFF]
libcompatible.so:0000007479C5B444 3F 01 01 EB CMP             X9, X1
libcompatible.so:0000007479C5B448 A3 00 00 54 B.CC            loc_7479C5B45C
libcompatible.so:0000007479C5B44C 1F 01 02 EB CMP             X8, X2
libcompatible.so:0000007479C5B450 68 00 00 54 B.HI            loc_7479C5B45C
libcompatible.so:0000007479C5B454 3F 01 02 EB CMP             X9, X2
libcompatible.so:0000007479C5B458 C2 00 00 54 B.CS            locret_7479C5B470
libcompatible.so:0000007479C5B45C
libcompatible.so:0000007479C5B45C             loc_7479C5B45C                          ; CODE XREF: libcompatible.so:SoLibraryStart+13A8↑j
libcompatible.so:0000007479C5B45C                                                     ; libcompatible.so:SoLibraryStart+13B8↑j ...
libcompatible.so:0000007479C5B45C 68 09 00 90 ADRP            X8, #off_7479D87388@PAGE
libcompatible.so:0000007479C5B460 08 C5 41 F9 LDR             X8, [X8,#off_7479D87388@PAGEOFF]
libcompatible.so:0000007479C5B464 09 01 40 B9 LDR             W9, [X8]
libcompatible.so:0000007479C5B468 29 01 1D 32 ORR             W9, W9, #8
libcompatible.so:0000007479C5B46C 09 01 00 B9 STR             W9, [X8]
libcompatible.so:0000007479C5B470
libcompatible.so:0000007479C5B470             locret_7479C5B470                       ; CODE XREF: libcompatible.so:SoLibraryStart+13C8↑j
libcompatible.so:0000007479C5B470 C0 03 5F D6 RET
  • svc调用代码准备好以后,开始对手机是否刷入magisk做检测
  • 当检测到magisk存在时,会设置对应的全局标志magisk_flag_off_1FF838
  • 对该标志交叉引用,可以发现有jni方法会获取该标志
1
2
3
4
5
6
7
magiskMountsPattern()
    读取了文件 "/proc/self/mounts",查找是否存在"magisk"字符串
 
magiskTimePattern()
  获取了文件 "/proc/%d/mountinfo" "Modify" 时间戳 mountinfo_FileTimeStamp
  获取了文件 "/proc/%d/ns/mnt" "Modify" 时间戳    mnt_FileTimeStamp
  当 mountinfo_FileTimeStamp > mnt_FileTimeStamp 时,代表存在magisk
  • 0x52abc处的方法进行解密,和之前一样可以直接dump下来,该方法为JNI_OnLoad方法
  • 0x20b90处的方法进行解密,和之前一样可以直接dump下来,但貌似没有调用到,在下面的初始化函数调用结束后会加密回去

  • 开始调用init_array初始化方法

  • init_array的信息在文件格式中也是被抹除了的,转换成了so自己来调用

  • 从下图也可以看出来那么一点相关性

  • 初始化函数调用完后,会解密和拼接字符串,得到/system/lib64/libc.so字符串

  • sub_9FAA8方法中解析了/system/lib64/libc.soELF文件格式,并将各种文件信息填充到对应的结构体中

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
get_maps_info_sub_173638();            // 获取当前进程的maps的各种信息
struct maps_info{ // size:448
  +0  maps_fd;                        // maps fd
  *(_BYTE *)(a1 + 4) = 0;             // flag
  *(_QWORD *)(a1 + 8) = 0LL;          // str_array_addr,每个数组成员保存了一行maps中的内容
  *(_QWORD *)(a1 + 16) = 0LL;
  *(_QWORD *)(a1 + 24) = 0LL;
  *(_DWORD *)(a1 + 32)                // 模块列表的总行数
  *(_DWORD *)(a1 + 36) = result + 1// 模块列表的遍历次数
 
  +40 format_str;                     // %lx-%lx %4s %lx %6s %d %s
  a1[15] = 100LL;
  a1 + 124;                          // _module_info的个数
  a1[16] = calloc(8uLL, 0x64uLL);{
        struct module_info _module_info[];
  }
  a1[17] = struct internal _internal;
};
 
struct internal{
  *(_QWORD *)a2        = module_start;
  *(_QWORD *)(a2 + 8= module_end;
  *(_QWORD *)(a2 + 16) = size;
  *(_BYTE *)(a2 + 24= 0;
  *(_BYTE *)(a2 + 24= mem_flag;
  *(_QWORD *)(a2 + 32) = v14;       // 00000000
  *(_BYTE *)(a2 + 40= *((_BYTE *)&dword_1ABCEC + (unsigned __int8)s[4097]);  //0
  *(_BYTE *)(a2 + 41= *((_BYTE *)&dword_1ABCEC + (unsigned __int8)s[4099]);  //0
  *(_QWORD *)(a2 + 48) = v13;       //
  *(_BYTE *)(a2 + 56)               // file-path
};
 
 
find_dst_module_info_sub_17392C(v4, (_BYTE *)a1, 1, v6);// 找到目标so文件的module—info
struct module_info{
  +0
  +8  = module_start
  +16 = module_end
  +24 = module_size
  +64 = file-path
};
 
 
sub_9F944(v6[0], (unsigned __int8 *)a1, a2 & 1);// 解析so的文件格式,将对应的各种节信息填充结构体中
struct libc_info{
  +0   file-name;
  +280 phd_vaddr;
  +288 phd_num;
  +304 module_start;
  +352 dynamic_addr;
  +360 dynamic_mem_flag;
  +584 module_start;
  +592 real_module_start;
 
  *(_QWORD *)(struct_libc_info + 400); // DT_SUNW_SYMTAB  0x60000011
  *(_QWORD *)(struct_libc_info + 408// DT_SUNW_SYMSZ   0x60000012
 
  // GNU_HASH 相关
  *(_QWORD *)(struct_libc_info + 480);
  *(_QWORD *)(struct_libc_info + 488);
  *(_QWORD *)(struct_libc_info + 496);
  *(_QWORD *)(struct_libc_info + 504);
  *(_DWORD *)(struct_libc_info + 512);
  *(_DWORD *)(struct_libc_info + 516);
  *(_QWORD *)(struct_libc_info + 520);
 
  ++*(_QWORD *)(struct_libc_info + 416);            // #define DT_NEEDED 1   依赖的so的数量
  *(_QWORD *)(struct_libc_info + 472) = v12 / 0x18; // #define DT_PLTRELSZ 2
 
  // HASH 相关
  *(_OWORD *)(struct_libc_info + 320)               // nbucket nchain
  *(_QWORD *)(struct_libc_info + 336)               // p_nbucket
  *(_QWORD *)(struct_libc_info + 344)               // p_nchain
 
  // 其他节信息
  *(_QWORD *)(struct_libc_info + 368)               // #define DT_STRTAB 5
  *(_QWORD *)(struct_libc_info + 376)               // #define DT_SYMTAB 6
  *(_QWORD *)(struct_libc_info + 392)               // #define DT_STRSZ 10
  *(_QWORD *)(struct_libc_info + 456)               // #define DT_JMPREL 23
  *(_QWORD *)(struct_libc_info + 464)               // #define DT_RELA 7
  *(_QWORD *)(struct_libc_info + 528)               // #define DT_PREINIT_ARRAY 32
  *(_QWORD *)(struct_libc_info + 536)               // #define DT_INIT_ARRAY   25
  *(_QWORD *)(struct_libc_info + 552)               // #define DT_INIT 12
  *(_QWORD *)(struct_libc_info + 560)               // #define DT_FINI 13 
 
  *(_QWORD *)(struct_libc_info + 632)               // .bss section addr
  *(_QWORD *)(struct_libc_info + 640)               // .bss section size
 
  *(_QWORD *)(*(_QWORD *)(struct_libc_info + 424) + 8LL * v28++) = v31; // 保存了依赖的so的名称
 
  // 还会解析libc.so的section,来获取一些信息
};
  • 通过上面获取到的信息,在方法sub_139FC中获取了一些libc的方法的地址,并保存到对应的全局变量中
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
qword_2058E8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v3);// mprotect
qword_2058F0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v4);// gettimeofday
qword_2058F8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v5);// pthread_mutex_lock
qword_205900 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v6);// pthread_mutex_unlock
qword_205908 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v7);// pthread_cond_timedwait
qword_205A80 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v8);// pthread_cond_wait
qword_205910 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v9);// pthread_create
qword_205918 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v10);// pthread_cond_signal
qword_205960 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v11);// unlink
qword_205970 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v12);// calloc
qword_205978 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v13);// malloc
qword_205980 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v14);// free
qword_205988 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v15);// pthread_join
qword_205940 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v16);// fopen
qword_205948 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v17);// fgets
qword_205950 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v18);// fclose
qword_205958 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v19);// exit
qword_205990 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v20);// pipe
qword_205998 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v21);// fork
qword_2059A0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v22);// getpid
qword_2059A8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v23);// getppid
qword_2059B0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v24);// read
qword_2059B8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v25);// write
qword_2059C0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v26);// close
qword_2059C8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v27);// waitpid
qword_2059D0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v28);// kill
qword_2059D8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v29);// ptrace
qword_2059E0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v30);// nanosleep
qword_2059E8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v31);// time
qword_2059F0 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v32);// opendir
qword_2059F8 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v33);// readdir
qword_205A00 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v34);// closedir
qword_205A08 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v35);// fread
qword_205A10 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v36);// fseek
qword_205A18 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v37);// ftell
qword_205A20 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v38);// fwrite
qword_205A28 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v39);// fflush
qword_205A30 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v40);// inotify_init
qword_205A38 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v41);// inotify_add_watch
qword_205A40 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v42);// inotify_rm_watch
qword_205A50 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v43);// getpwuid
qword_205A58 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v44);// raise
qword_205A60 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v45);;// ferror
qword_205A68 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v46);// open
qword_205A70 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v47);// lseek
qword_205A78 = get_symbol_addr_by_hash_sub_9FB64(struct_libc_info, v48);// lseek64
  • 随后会申请多块内存地址,会用一些随机数填充,具体用处暂时未知
1
2
3
4
5
6
7
8
9
10
11
// 申请了一些内存,将内存地址存放在了off_1FFE78
*off_1FFE78_v377 = mmap_svc_sub_163F30(0LL, 0x78uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 64) = mmap_svc_sub_163F30(0LL, 0x30uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 72) = mmap_svc_sub_163F30(0LL, 0x10uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 80) = mmap_svc_sub_163F30(0LL, 0x24uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 88) = mmap_svc_sub_163F30(0LL, 0x1FuLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 96) = mmap_svc_sub_163F30(0LL, 0x1FuLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 104) = mmap_svc_sub_163F30(0LL, 0x10uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 40) = mmap_svc_sub_163F30(0LL, 0x200uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 112) = mmap_svc_sub_163F30(0LL, 0x28uLL, 3, 34, -1, 0LL);
*(_QWORD *)(*off_1FFE78_v377 + 40) = calloc(1uLL, 0x200uLL);
  • 到此init_proc运行结束

分析JNI_OnLoad

  • 从上面的分析可以知道,JNI_OnLoad方法的地址位于0x52ABC

  • 首先是使用svc调用了mprotect(module_base+0x20000, 0x33000, 7),对0x20b90处的方法进行解密

  • 开始对手机中是否存在模拟器相关的库进行检测,若存在也会设置对应的全局标志off_1FFA38
1
2
3
4
5
检测方式也就是获取到maps_info后,遍历maps中的路径比较是否存在对应的so
lib3btrans.so
libhoudini.so
/lib/arm/nb/libc.so
/lib/arm64/nb/libc.so
  • 上面检测到模拟器的库后,会注册一个jni方法,用于获取模拟器的全局标志off_1FFA38
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// com/inca/security/Proxy/iIiIiIiIii
v14->RegisterNatives(env, v13, (const JNINativeMethod *)off_200AF8, 1LL);
 
LOAD:0000000000200AF8 2C 5B 19 00+off_200AF8      DCQ aIiiiiiiiii_150    
LOAD:0000000000200AF8 00 00 00 00                                         ; "iiIiIIIiII"
LOAD:0000000000200B00 37 5B 19 00+                DCQ aJLjavaLangObje     ; "(J)Ljava/lang/Object;"
LOAD:0000000000200B08 F0 20 02 00+                DCQ sub_220F0
 
__int64 __fastcall sub_220F0(__int64 a1, __int64 a2, _BYTE *a3)
{
  __int64 result; // x0
 
  result = 0LL;
  *a3 = *is_emulator_off_1FFA38;
  return result;
}
  • 同时通过反射调用com.inca.security.Proxy->Load方法,加载compatible_x86.so

  • 对应的java方法:

  • 调用sub_B56CC进行jni方法的动态注册,sub_B56CC根据传入的参数不同,注册不同的jni方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// com/inca/security/Service/AppGuardService
v8->RegisterNatives(env, AppGuardService_class, (const JNINativeMethod *)off_200738, 1LL);
 
// com/inca/security/Service/AppGuardServiceCaller
v20->RegisterNatives(env, v19, (const JNINativeMethod *)off_200750, 1LL);
 
// com/inca/security/DexProtect/SecureApplication
v24->RegisterNatives(env, v23, (const JNINativeMethod *)off_200768, 14LL);
 
// com/inca/security/Proxy/AppGuardProxyApplication
v27->RegisterNatives(env, v26, (const JNINativeMethod *)off_2008B8, 2LL);
 
// com/inca/security/Proxy/AppGuardFrontApplication
v30->RegisterNatives(env, v29, (const JNINativeMethod *)off_2008E8, 1LL);
 
// com/inca/security/Proxy/AppGuardProxyHandler
v33->RegisterNatives(env, v32, (const JNINativeMethod *)off_200AE0, 1LL);
 
// com/inca/security/Proxy/iIiIiIiIii
v11->RegisterNatives(env, v10, (const JNINativeMethod *)off_200900, 20LL);
  • 使用svc调用了mprotect(module_base+0x85000, 0x1000, 7)
  • 使用svc调用了mprotect(module_base+0x1000, 0x6000, 7),并对0x189c方法进行解密,解密后会立即进行调用

0x189C方法分析

  • 在方法KM4PI0Z7J8QMILO5G6P6()中,获取了手机文件中的数据,做为标志
1
2
3
4
5
6
7
8
目标文件:
  /storage/emulated/0/Music
  /storage/emulated/0/Download
  /storage/emulated/0/Documents
  /storage/emulated/0/Android
 
调用了 newfstatat 获取上述文件的stat结构体
  flag = Music_stat[72] + Download_stat[72] + Documents_stat[72] + Android_stat[72] (array-type -> char)
  • 调用了_system_property_get方法
1
2
3
-> ro.build.version.sdk 获取了手机sdk    : off_1FF300 -> sdk
-> ro.product.cpu.abi   获取了手机cpu架构 : 将 "arm64-v8a" 保存到off_1FF7D0
-> ro.product.brand     获取了手机型号    : 和 "tencent"字符串进行比较
  • 定义了一个小结构体,用来做一些jni的操作
1
2
3
4
5
6
7
8
struct env_info{
  +0  off_1FCAA8
  +8  env
  +16 CPU_ABI_obj
  +24 obj_clazz
  +32 <init>_method_id
  +40 0
};
  • 使用svc调用了mprotect(module_base+0x14a000, 0xc000, 7),并对0x14a410方法进行解密,解密后会立即进行调用

0x14A410方法分析

  • 进入方法后会先解密拼接出字符串/system/lib64/libc.so,拷贝到 off_1FFC60
  • 与在init_proc中一样,解析了libc.so的ELF文件格式,同样调用了方法sub_139FC获取了部分libc的方法的地址
  • 使用svc调用了mprotect(module_base+0x164000, 0x1000, 7),并对0x1644a8方法进行解密,解密后会立即进行调用

  • 0x1644A8方法中初始化了结构体struct_libc_info_2,并保存了libc.sostruct_module_info的相关信息

  • 紧接着调用sub_167814

0x167814方法分析

  • 0x167814方法中,首先对libc的ELF文件格式做了解析,并将相关信息保存到了结构体中

  • 结构体的大致结构如下:

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
size = 0x160
struct libc_info_2{
  *(_DWORD *)a1 = result;          // fd
  *(_QWORD *)(a1 + 8) = mmap_svc_sub_163F30(0LL, 0x3000uLL, 3, 34, -1, 0LL); // section_data
                                                                   // 对应的节解析完成后,会将该数据 munmap
  *(_DWORD *)(a1 + 16) =           // section_sz + shtr_sec_sz 节的大小 + 节名称节大小
  *(_BYTE  *)(a1 + 20) = 0;        // 文件是否openat打开成功的flag,打开成功为0,失败为1
  *(_DWORD *)(a1 + 24) =           // 保存了ELF文件的 header,size = 0x40,后面又会设置为0
      {        
        struct ELF_Header elf_header; 
      
 
  // 在调用sub_166494之前,会将下面的成员设置为0
  *(_QWORD *)(struct_libc_info_2_ + 24) = 0LL;
  *(_QWORD *)(struct_libc_info_2_ + 32) = 0LL
  *(_QWORD *)(struct_libc_info_2_ + 40) = 0LL;
  *(_QWORD *)(struct_libc_info_2_ + 48) = 0LL;
  *(_QWORD *)(struct_libc_info_2_ + 56) = 0LL;
  *(_QWORD *)(struct_libc_info_2_ + 64) = 0LL;
  *(_QWORD *)(struct_libc_info_2_ + 72) = 0LL;
  *(_QWORD *)(struct_libc_info_2_ + 80) = 0LL;
 
  *(_QWORD *)(a1 + 88) = v4 + v2;  // *(_QWORD *)(a1 + 8) 这个字段会重复使用,用于保存临时数据
  *(_QWORD *)(a1 + 96) = v4 + v2;  // section_addr
  *(_QWORD *)(a1 + 104) = v15;     // section-str: init_array finit_array....
  *(_QWORD *)(a1 + 112) = v129;    // dynsym_data
  *(_QWORD *)(a1 + 120) =          // dynstr_data
  *(_QWORD *)(a1 + 128) =          // dynamic_data
  *(_QWORD *)(a1 + 136) = 0        // 应该是标志so文件是使用gnu.hash还是hash1表示是hash0是gnu.hash
  *(_QWORD *)(a1 + 144) =          // gnu.hash_data
  *(_QWORD *)(a1 + 152) =          // rela.dyn_data
  *(_QWORD *)(a1 + 160) =          // rela.dyn_size
  *(_QWORD *)(a1 + 168) =          // rela.plt_data
  *(_QWORD *)(a1 + 176) =          // rela.plt_size
 
  *(_DWORD *)(a1 + 264) = 0;
  *(_DWORD *)(a1 + 268) =          // 文件映射的末尾,后面加了 0x3000
  *(_QWORD *)(a1 + 272) = v17;     // 文件映射的起始,即0
  *(_QWORD *)(a1 + 288) = mmap_svc_sub_163F30(0LL, v43, 7, 34, -1, 0LL); // 拷贝了 elf_header
  *(_QWORD *)(a1 + 296) = 0LL;
 
  *(_QWORD *)(a1 + 304) = mmap_svc_sub_163F30(0LL, v128, 3, 34, -1, 0LL);
  *(_DWORD *)(a1 + 312) = v128;    // 从节表拷贝数据到 *(_QWORD *)(a1 + 288) 的大小
  *(_QWORD *)(a1 + 328) = 0LL;     // module_base
  *(_QWORD *)(a1 + 336) = 0LL      // .got.plt_vaddr
  *(_DWORD *)(a1 + 344) = 0;       // .got.plt_size
 
 
  // gnu.hash相关的成员
   v273 = *gnu_hash_addr;
  *(_QWORD *)(struct_libc_info_2_ + 216) = v273;
  v274 = gnu_hash_addr[2];
  *(_DWORD *)(struct_libc_info_2_ + 240) = v274;
  *(_DWORD *)(struct_libc_info_2_ + 244) = gnu_hash_addr[3];
  *(_QWORD *)(struct_libc_info_2_ + 248) = gnu_hash_addr + 4;
  v275 = (__int64)&gnu_hash_addr[2 * v274 + 4];
  *(_QWORD *)(struct_libc_info_2_ + 224) = v275;
  *(_QWORD *)(struct_libc_info_2_ + 232) = v275 + 4 * v273 - 4LL * gnu_hash_addr[1];
  if ( (-(int)v274 | ~(_DWORD)v274) == -1 )
    *(_DWORD *)(struct_libc_info_2_ + 240) = v274 - 1;
};
  • 调用sub_16765C方法,将libc.so在内存中已经完成重定位的plt数据拷贝到*(_QWORD *)(a1 + 288) + .got.plt_vaddr

  • 使用svc调用了mprotect(module_base+0x16d000, 0x2000, 7),并对0x16d9cc方法进行解密

  • 使用svc调用了mprotect(module_base+0x16e000, 0x3000, 7),并对0x16ed28方法进行解密

  • 第一次调用sub_16DA04,传入了rela.dyn_datarela.dyn_size,目的是对拷贝的libc的数据进行重定位

  • 某些全局变量的重定位数据,还会额外调用sub_16ED8C去获取

  • 调试截图

  • 第二次调用sub_16DA04,传入了rela.plt_datarela.plt_size,目的是对拷贝的libc的数据进行重定位

  • 0x167814方法运行结束,方法主要是解析文件格式,拷贝对应的数据到申请的内存中并对拷贝过来的数据进行重定位,最后再填充对应的结构体

继续0x14A410方法分析

  • 调用完sub_167814,会将libc内存中的.bss-section的数据拷贝到自己申请的内存处

  • 使用svc调用了mprotect(module_base+0x170000, 0x3000, 7),并对0x170638方法进行解密

  • 继续分析,发现该游戏保护将内存中libc的数据全部拷贝了一份,并全部给进行了重定位,来作为libc的替代
  • sub_171E3C方法中,发现了上述操作

  • 创建了一个保存函数地址的结构体

1
2
3
4
5
6
7
8
9
10
struct my_libc_pfun{
  +0   sleep
  +8   nanosleep
  +240 pthread_create
  +712 malloc
  +720 calloc
  +728 realloc
  +736 free
  +744 pthread_getschedparam
};
  • 紧接着使用inline-hook对这些方法进行了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
30
31
32
33
34
35
36
37
38
39
40
struct inited{
  result = (_QWORD *)operator new(0x30uLL);
  result[1] = 0LL;
  result[2] = 0LL;
  result[3] = result + 1;
  result[4] = result + 1;
  result[5] = 0LL;
};
 
sub_9C778 参数:
  arg1:0xB4000074CA498AE0 //新创建的结构体 struct_inited
  arg2:0x00000074DA52BBF0 //拷贝libc数据后,fake的malloc的方法地址,也是要hook的函数地址
  arg3:0x0000007570B30BF0 //真实的malloc方法地址,也是hook中的钩子函数
  arg4:0                  //用来保存被hook前函数信息的地址,不保存则是0
 
size:0x48
struct hook_info{  addr = 0xB4000074CA2BB670
  *v14 = dst_fun;
  v14[1] = src_fun;
  *(_QWORD *)(struct_hook_info + 16) = v19;{
    v19 = operator new[](0x14uLL);
    *(_DWORD *)v19 = 0x58000051;                // 0x58000051 = ldr x17, #8                                
    *(_DWORD *)(v19 + 4) = 0xD61F0220;          // 0xD61F0220 = 20 02 1f d6
    *(_QWORD *)(v19 + 8) = src_fun_addr;
  }
  v14[4] = 0LL;
  *((_DWORD *)v14 + 10) = 0;
  *(_DWORD *)(struct_hook_info + 24) = 4 * v18;      // v18 = 5 or 4
  *(_QWORD *)(struct_hook_info + 48) = jmp_fun_addr; // jmp_addr
  v14[7] = 0LL;
  *(unsigned __int64 *)((char *)v14 + 60) = 3LL;
};
 
sub_E545C(fake_fun_addr, src_fun_addr, (__int64)v7, 0x32uLL, (__int64)a2);
  MALLOC:
  0x00000074D952DBF0 fake_fun_addr
  0x0000007570B30BF0 src_fun_addr
  0x000000747905E000 jmo_fun_addr
  0x0000000000000032
  0xB40000747BCA28B0
  • 进入一个混淆很严重的循环中,在循环中重复调用sub_171E3C来获取方法的地址,保存到结构体中
  • 后续不少函数的调用,都是先从下面的结构体中获取到函数地址,再进行调用

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
struct my_libc_pfun{
  +0    sleep
  +8    nanosleep
  +16   gettimeofday
  +24   exit
  +32   time
  +40   raise
  +48   opendir
  +56   readdir
  +64   closedir
  +72   fopen
  +80   fgets
  +88   fread
  +96   ftell
  +104  fseek
  +112  fwrite
  +120  fflush
  +128  fclose
  +136  fstat
  +144  fdopen
  +152  fcntl
  +160  ferror
  +168  fseeko
  +176  ftello
  +184  inotify_init
  +192  inotify_add_watch
  +200  inotify_rm_watch
  +208  kill
  +216  pthread_mutex_lock
  +224  pthread_mutex_unlock
  +232  pthread_cond_timedwait
  +240  pthread_create
  +256  pthread_join
  +264  pthread_cond_signal
  +272  pthread_cond_dwait
  +280  pthread_detach
  +288  pthread_getspecific
  +296  pthread_once
  +304  pthread_key_create
  +312  pthread_setspecific
  +320  pthread_key_delete
  +328  pthread_key_exit
  +336  getpid
  +344  open
  +352  read
  +360  write
  +368  close
  +376  lseek
  +384  lseek64
  +392  stat
  +400  socket
  +408  connect
  +416  send
  +424  recv
  +432  bind
  +440  accept4
  +448  listen
  +456  mprotect
  +464  access
  +472  pipe
  +480  lstat
  +488  fchdir
  +496  rmdir
  +504  mkdir
  +512  remove
  +520  unlink
  +528  vfork
  +536  fork
  +544  gettid
  +552  ptrace
  +560  waitpid
  +568  dlopen
  +576  fstatfs
  +584  fsync
  +592  getsockopt
  +600  usleep
  +608  rand
  +616  pthread_kill
  +624  open
  +632  poll
  +640  popen
  +648  pclose
  +656  off_1FFC00
  +664  sigaction
  +672  signal
  +680  abort
  +688  pwrite
  +696  pread
  +704  ftruncate
  +712  malloc
  +720  calloc
  +728  realloc
  +736  free
  +744  pthread_getschedparam
};
  • 同时准备了一个与信号相关的结构体,并将相关的信息保存到一个全局变量中qword_205AC8
1
2
3
4
5
6
7
8
9
10
11
12
13
struct signal_info{
  +0  exit
  +8  exit
  +32 signal
  +64 sigaction
  +96 abort
};
 
qword_205AC8{
  +96  signal_addr
  +152 sigaction_addr
  +160 0xa8
};
  • 最后调用了pthread_create创建了线程sub_B8B6C

  • 至此0x14A410方法分析完成

继续0x189C方法分析

  • 使用svc调用了mprotect(module_base+0x15000, 0xc000, 7),并对0x15740方法进行解密
  • 调用方法sub_1882FC简单的检测了一下xposed

  • 流程走到sub_15F74,该方法主要是启动多进程,使用信号来进行一些反调试的操作

  • 调用sub_4DBE8来进行一些方法的解密,就不赘述了

  • 到此JNI_OnLoad方法运行结束

总结:

  • 会动态生成SVC的调用代码
  • 会拷贝一份内存中的libc,并重新进行重定位,一些方法的调用会使用拷贝的libc的代码
  • 有使用混淆,不算太严重,勉强还是能看出代码逻辑
  • 许多线程函数、多进程的代码,各种反调试和检测的代码都还没看,以后再说

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞13
打赏
分享
最新回复 (15)
雪    币: 3266
活跃值: (4355)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
乐子人 3 2023-5-16 18:55
2
0
666
雪    币: 22
活跃值: (3629)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
New对象处 2023-5-17 09:23
3
0
66666666
雪    币: 22
活跃值: (3629)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
New对象处 2023-5-17 09:24
4
0
乐子人 666
大佬,不搞篇国外np加固?
雪    币: 303
活跃值: (213392)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 1 2023-5-17 09:51
5
0
tql
雪    币: 74
活跃值: (215)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
troublebao 2023-5-17 11:47
6
0
tql, fork那里,是直接patch掉接着向下调试吗
雪    币: 40
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
YoHa 2023-5-17 15:59
7
0
大佬
雪    币: 499
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
菜鸟泽 2023-5-17 16:59
8
0
大佬tql
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_天凉好个秋_163 2023-5-19 11:51
9
0
我想找个好用的 安卓手机里 用的 文本编辑器, 类似 windows记事本,能够识别 ansi格式还是 utf-8格式,  能识别 unix还是 dos收尾, 轻便好用的 记事本编辑器, 怎么没有呢? 大佬谁能推荐一个给我.
雪    币: 19381
活跃值: (29004)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-7-5 09:23
10
1
感谢分享
雪    币: 337
活跃值: (432)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
mb_kbkqyusp 2023-7-6 14:20
11
0
感谢大佬的分享,大佬牛呀
雪    币: 155
活跃值: (411)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_zdkihfht 2023-7-6 16:00
12
0
tqlllllllllllll
雪    币: 2391
活跃值: (2252)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
gtict 2023-7-13 11:12
13
0
很久以前魔灵保护好像是xcode,
雪    币: 2391
活跃值: (2252)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
gtict 2023-7-13 11:14
14
0
国内能玩魔灵么,很久不玩了
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
阿嚏 2023-8-16 17:17
15
0
section 怎么修复的呢, 能详细说一下吗
雪    币: 3266
活跃值: (4355)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
乐子人 3 2023-8-16 21:39
16
0
蹲,nprotect看了几次都放弃了,加密太恶心,各种虚假分支混淆。。
游客
登录 | 注册 方可回帖
返回