首页
社区
课程
招聘
13
[原创]某加固so层脱壳
发表于: 2025-2-12 13:52 22496

[原创]某加固so层脱壳

2025-2-12 13:52
22496

加固版本:2025/2/11 最新免费版

第一步查看 加载so

第一步 :使用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

第二步 dump 要分析的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 打印运行流程

现在 我们需要了解程序运行 调用的流程 也就是 函数 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; // x0
  FILE *v1; // x19
  char s[512]; // [xsp+8h] [xbp-248h] BYREF
  char v3[16]; // [xsp+208h] [xbp-48h] BYREF
  char filename[24]; // [xsp+218h] [xbp-38h] BYREF
 
  _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; // x19
  _QWORD v2[7]; // [xsp+10h] [xbp-140h] BYREF
  _QWORD v3[2]; // [xsp+48h] [xbp-108h] BYREF
  __int128 v4; // [xsp+58h] [xbp-F8h]
  _OWORD v5[8]; // [xsp+68h] [xbp-E8h] BYREF
  __int128 v6; // [xsp+E8h] [xbp-68h]
  __int128 v7; // [xsp+F8h] [xbp-58h]
  __int128 v8; // [xsp+108h] [xbp-48h]
  __int128 v9; // [xsp+118h] [xbp-38h]
 
  _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; // w20
  _BYTE *result; // x0
  _BYTE *v4; // x10
  unsigned int v5; // w8
  int v6; // w11
  int v7; // [xsp+8h] [xbp-48h]
  int v8; // [xsp+Ch] [xbp-44h]
  int v9; // [xsp+10h] [xbp-40h]
  int v10; // [xsp+14h] [xbp-3Ch]
  int v11; // [xsp+18h] [xbp-38h]
  int v12; // [xsp+1Ch] [xbp-34h]
  int v13; // [xsp+20h] [xbp-30h]
  int v14; // [xsp+24h] [xbp-2Ch]
  __int64 v15; // [xsp+28h] [xbp-28h]
 
  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 被逆天而行编辑 ,原因: 修改标题
上传的附件:
收藏
点赞 13
支持
分享
赞赏记录
参与人
雪币
留言
时间
mb_ecuabpmq
非常支持你的观点!
6天前
52niu
为你点赞!
2025-2-19 12:15
七星舞月
+1
这个讨论对我很有帮助,谢谢!
2025-2-19 11:00
Shangwendada
+1
为你点赞!
2025-2-18 15:57
wogao
+1
谢谢你的细致分析,受益匪浅!
2025-2-17 19:52
Fanoteria
这个讨论对我很有帮助,谢谢!
2025-2-17 12:42
sinker_
谢谢你的细致分析,受益匪浅!
2025-2-14 22:59
你瞒我瞒
期待更多优质内容的分享,论坛有你更精彩!
2025-2-14 14:25
mb_bkkdeflr
谢谢你的细致分析,受益匪浅!
2025-2-13 10:20
wuzhouzcx
这个讨论对我很有帮助,谢谢!
2025-2-13 09:21
ngiokweng
感谢你分享这么好的资源!
2025-2-12 15:37
账号已注销1
+3
感谢你的积极参与,期待更多精彩内容!
2025-2-12 14:27
Ram98
你的分享对大家帮助很大,非常感谢!
2025-2-12 14:25
最新回复 (7)
雪    币: 102
活跃值: (2455)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
mark
2025-2-14 11:51
0
雪    币: 48
活跃值: (2248)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
膜拜大佬出一篇分析native onCreate的文章
2025-2-14 14:04
0
雪    币: 101
活跃值: (147)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
2025-2-17 15:13
0
雪    币: 366
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
可以 接下来可以分析签名校验了
2025-2-21 18:11
0
雪    币: 84
活跃值: (2168)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
厉害了,我的盆友
2025-2-22 00:26
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
大佬,能否重新上传附件,现在下不了。
2025-2-25 21:43
0
雪    币: 109
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
学习了
2025-3-1 20:51
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册