加固版本:2025/2/11 最新免费版
第一步 :使用frida(最好魔改 不然有检测) 查看加载的so文件
function main (){
Java.perform(
function(){
hook_dlopen();
}
);
}
setImmediate(main)
//刚注入的时候这个so还没加载,需要hook dlopen
function inline_hook() {
var base_hello_jni = Module.findBaseAddress("libxx.so");
console.log("base_hello_jni:", base_hello_jni);
if (base_hello_jni) {
console.log(base_hello_jni);
//inline hook
var addr_07320 = base_hello_jni.add(0x2E637);//指令执行的地址,不是变量所在的栈或堆
Interceptor.attach(addr_07320, {
onEnter: function (args) {
console.log("addr_07320 R0 R3:", this.context.r0,this.context.r3);//注意这里是怎么得到寄存器值的
}, onLeave: function (retval) {
}
});
}
}
//8.0以下所有的so加载都通过dlopen
function hook_dlopen() {
var dlopen = Module.findExportByName(null, "dlopen");
Interceptor.attach(dlopen, {
onEnter: function (args) {
this.call_hook = false;
var so_name = ptr(args[0]).readCString();
console.log("dlopen:", ptr(args[0]).readCString());
}, onLeave: function (retval) {
if (this.call_hook) {//dlopen函数找到了就hook so
inline_hook();
}
}
});
// 高版本Android系统使用android_dlopen_ext
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
this.call_hook = false;
var so_name = ptr(args[0]).readCString();
console.log("dlopen:", ptr(args[0]).readCString());
}, onLeave: function (retval) {
if (this.call_hook) {
inline_hook();
}
}
});
}
function log(msg){
console.log(msg);
}
打印出来所有加载的so文件了:
[Calvin designers::com.calvin.check_tset ]-> dlopen: /data/data/com.calvin.check_tset/.jiagu/libjiagu_64.so
dlopen: liblog.so
dlopen: libz.so
dlopen: libc.so
dlopen: libm.so
dlopen: libstdc++.so
dlopen: libdl.so
dlopen: libjiagu_64.so
dlopen: libjiagu_64.so
dlopen: libart.so
dlopen: libjiagu_64.so
dlopen: libjiagu_64.so
dlopen: libjiagu_64.so
dlopen: libjiagu_64.so
dlopen: libjiagu_64.so
dlopen: libjiagu_64.so
dlopen: libjgdtc.so
dlopen: /data/app/gUGk1sgfebPev2a5rxao2w==/com.calvin.check_tset-Kj54dsV2miW8iYUVx-eTmA==/lib/arm64/libcheck_tset.so
dlopen: /vendor/lib64/hw/android.hardware.graphics.mapper@4.0-impl-qti-display.so
dlopen: /vendor/lib64/hw/gralloc.lito.so
dlopen: libadreno_app_profiles.so
dlopen: libEGL_adreno.so
dlopen: /vendor/lib64/hw/android.hardware.graphics.mapper@4.0-impl-qti-display.so
dlopen: libadreno_utils.so
dlopen: libandroid.so
一眼观望全部so文件 , 接下来 把要分析的so文件 dump下来 看看 怎么回事
function dump_so(so_name) {
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 = "/data/data/"+get_self_process_name()+"/" + 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);
}
}
setImmediate(function() {
setTimeout(function() {
dump_so("libjiagu_64.so");
}, 5000); // 等待5秒后执行
});
function get_self_process_name() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
var readPtr = Module.getExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);
var closePtr = Module.getExportByName('libc.so', 'close');
var close = new NativeFunction(closePtr, 'int', ['int']);
var path = Memory.allocUtf8String("/proc/self/cmdline");
var fd = open(path, 0);
if (fd != -1) {
var buffer = Memory.alloc(0x1000);
var result = read(fd, buffer, 0x1000);
close(fd);
result = ptr(buffer).readCString();
return result;
}
return "-1";
}
dump 下来的so是需要修复滴 使用so_fixer
https://github.com/F8LEFT/SoFixer
修复完毕后 就可以显示 全部的符号信息了
现在 我们需要了解程序运行 调用的流程 也就是 函数 trace
这里使用 oacia 佬的方案
https://github.com/oacia/stalker_trace_so
打印 so函数的流程:
call1:JNI_OnLoad
call2:j_interpreter_wrap_int64_t
call3:interpreter_wrap_int64_t
call4:_Znwm
call5:sub_1362C
call6:_Znam
call7:sub_10F54
call8:memset
call9:sub_9C50
call10:sub_E114
call11:calloc
call12:malloc
call13:free
call14:sub_E37C
call15:_ZdaPv
call16:sub_C680
call17:sub_CB38
call18:sub_9800
call19:sub_97DC
call20:sub_CCA8
call21:sub_C86C
call22:sub_993C
call23:sub_1591C
call24:sub_16094
call25:sub_16160
call26:sub_15C94
call27:sub_16954
call28:sub_15D14
call29:sub_159F0
call30:sub_1595C
call31:sub_9778
call32:sub_CB90
call33:sub_CD8C
call34:sub_CAD8
call35:sub_91E0
call36:dladdr
call37:strstr
call38:setenv
call39:_Z9__arm_a_1P7_JavaVMP7_JNIEnvPvRi
call40:sub_9CD0
call41:sub_9814
call42:sub_10698
call43:j__ZdlPv_1
call44:_ZdlPv
call45:sub_9558
call46:sub_7D00 //这里存在 so字符串
call47:__strncpy_chk2
call48:sub_5ACC
call49:sub_5F30
call50:sub_46A0
call51:sub_5B14
call52:_ZN9__arm_c_19__arm_c_0Ev
call53:sub_A228
call54:sub_9844
call55:sub_97BC
call56:sub_CF24
call57:sub_5E70
call58:sub_5F7C
call59:memcpy
call60:sub_6084 //疑似 加密函数
call61:sub_5974
call62:j__ZdlPv_3
call63:j__ZdlPv_2
call64:j__ZdlPv_0
call65:sub_A1DC
call66:sub_9908
call67:sub_59CC
call68:sub_5A24 //疑似 rc4
call69:sub_9E58
call70:sub_307C
call71:uncompress //经典解压环节
call72:sub_CBF4
call73:sub_4538 // 这里好像 linker load
call74:sub_4D34
call75:sub_4DAC
call76:sub_543C //这里存在 异或加密
call77:sub_4F84
call78:sub_5140 // 存在 内存拷贝 权限修改
call79:mprotect
call80:__strlen_chk
call81:strncpy
call82:sub_379C // linker 加载
call83:dlopen
call84:sub_446C // 这里纯在 将 内存 区域 清零
call85:sub_3B54 // 链接有关
call86:sub_3D08 // 这里出现一些 疑似奇怪的检测
call87:sub_30B4
call88:dlsym
call89:strcmp
call90:sub_57A0 // 这里也是修改内存权限 为 可执行吧?
call91:sub_4D78
call92:sub_5D28
call93:sub_7E38
call94:sub_47BC
call95:sub_7F64
call96:sub_8854
call97:sub_8BE0
call98:sub_8138
call99:interpreter_wrap_int64_t_bridge
call100:sub_9BD8
call101:sub_15C0C
call102:puts
call103:_Z9__arm_a_2PcmS_Rii
这里 我一个步骤一个步骤的看 发现函数 sub_9558() 调用 kill() 自己 进程
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
|
v4 = sub_6F9C();
if ( (v4 & 1) != 0 )
{
v5 = getpid();
v4 = kill(v5, 9);
}
v6 = sub_70A8(v4);
v7 = sub_7204(v6);
if ( (v7 & 1) != 0 )
{
v8 = getpid();
v7 = kill(v8, 9);
}
result = sub_70A8(v7);
v1 = qword_276180;
|
第一个函数 :
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
|
FILE *sub_6F9C()
{
FILE *result;
FILE *v1;
char s[512];
char v3[16];
char filename[24];
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
*(_QWORD *)&filename[6] = '\xA5\xD5\xC6\xD1\x8A\xD1\xC0\xCB' ;
*(_QWORD *)filename = '\xC0\xCB\x8A\xC6\xCA\xD7\xD5\x8A' ;
*(_QWORD *)&v3[6] = 0xA5E49DE1909F9595LL;
*(_QWORD *)v3 = 0x9595959595959595LL;
sub_6554(filename, 14LL);
sub_6554(v3, 14LL);
memset (s, 0, sizeof (s));
result = fopen (filename, "r" );
if ( result )
{
v1 = result;
while ( ! feof (v1) )
{
fgets (s, 512, v1);
if ( strstr (s, v3) )
{
fclose (v1);
return ( FILE *)(&dword_0 + 1);
}
memset (s, 0, sizeof (s));
}
fclose (v1);
return 0LL;
}
return result;
}
|
字符串为加密 我们编写frida 打印一下
发现 frida 未打印 这个函数也未运行那就不管啦
在 sub_7D00 函数 里面居然出现了 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
42
|
__int64 sub_7D00()
{
__int64 v0;
_QWORD v2[7];
_QWORD v3[2];
__int128 v4;
_OWORD v5[8];
__int128 v6;
__int128 v7;
__int128 v8;
__int128 v9;
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v8 = 0u;
v9 = 0u;
v6 = 0u;
v7 = 0u;
memset (v5, 0, sizeof (v5));
v4 = 0u;
_strncpy_chk2(( char *)v5 + 4, "*.so" , 128LL, 188LL, 5LL);
v3[0] = &qword_2D260;
v3[1] = 752309LL;
*(_QWORD *)&v7 = off_2D178;
LODWORD(v5[0]) = 1;
*((_QWORD *)&v6 + 1) = &qword_E5178;
DWORD2(v9) = 1;
*((_QWORD *)&v7 + 1) = 0x400000002LL;
LODWORD(v8) = 5;
*((_QWORD *)&v8 + 1) = 0LL;
*(_QWORD *)&v9 = 0LL;
sub_5ACC(v2);
v2[0] = ( char *)off_2CF48 + 16;
v0 = 0LL;
if ( (sub_5F30(v2, ( char *)&qword_E4D10 + 5, 1062LL) & 1) != 0 )
{
v0 = sub_46A0(v3, v2);
if ( *((_QWORD *)&v8 + 1) )
free (*(( void **)&v8 + 1));
}
sub_5D28(v2);
return v0;
}
|
在之后 还遇到 一道类似加密算法的sub_6084函数:
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
|
_BYTE *__fastcall sub_6084( __int64 a1)
{
unsigned int v2;
_BYTE *result;
_BYTE *v4;
unsigned int v5;
int v6;
int v7;
int v8;
int v9;
int v10;
int v11;
int v12;
int v13;
int v14;
__int64 v15;
v15 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v2 = *(_DWORD *)(a1 + 4);
result = (_BYTE *)operator new [](v2);
v4 = *(_BYTE **)(a1 + 8);
v5 = 0;
v6 = 7;
*(_QWORD *)(a1 + 24) = result;
do
{
*(&v7 + v6) = ((unsigned __int8 )(*v4 ^ (*v4 >> 2) ^ (*v4 >> 4)) ^ (*v4 >> 6)) & 1;
if ( v6 )
{
--v6;
}
else
{
++v5;
v6 = 7;
*result++ = ((_BYTE)v13 << 6) + ((_BYTE)v14 << 7) + 32 * v12 + 16 * v11 + 8 * v10 + 4 * v9 + 2 * v8 + v7;
v4 = *(_BYTE **)(a1 + 8);
v2 = *(_DWORD *)(a1 + 4);
}
*(_QWORD *)(a1 + 8) = ++v4;
}
while ( v5 <= v2 - 1 );
return result;
|
[注意]看雪招聘,专注安全领域的专业人才平台!
最后于 2025-2-12 13:53
被逆天而行编辑
,原因: 修改标题