首页
社区
课程
招聘
9
[原创]某加固onCreate的vmp分析
发表于: 2025-3-13 17:18 30629

[原创]某加固onCreate的vmp分析

2025-3-13 17:18
30629

复现完oacia师傅的文章,360加固dex解密流程分析 | oacia = oaciaのBbBlog~ = DEVIL or SWEET

想继续来看看onCreate怎么实现的,继续使用oacia师傅的加固app

onCreate函数被注册为native函数

Init函数,attachBaseContext函数,都没有什么可用的线索。发现static函数里有一个jni函数interface11,

在attachBaseContext执行之前。所有 static {} 代码块会在类加载时执行(Application 类的 constructor 之前)

使用yang神的脚本,拦截JNI注册函数RegisterNative找到函数的地址

直接来看汇编代码,前面都是一些线程检测,以及寄存器操作等,来到最后跳转的是x8寄存器的值,没法直接反编译

用stalker,跟踪x8寄存器的变化,来打印跳转

根据trcace发现了两处oncreate,看看的后一个地址干了什么

后一个oncreate,都是一些内存和栈的操作。

0x13a170函数之后出现了第一个onCreate,blr x8进入关键部分。

jnitrace看一下流程

接下来关键操作分析0x13a61c函数之后出现了第二个oncreate,同样是blr x8,进入jni函数了。

可以确定,interface11方法将onCreate注册某个native方法上

我们去hook一下 _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi找地址

输出了oncreate

但是是动态注册,找不到moudel的名字,无法直接打印地址。我们直接去so里面寻找字节码。在0x178c50+0xe7000=0x26FC50

在ida中找到onCreate地址

交叉引用onCreate,发现他被sub_1A1908调用。

再看看sub_1A1908的交叉引用

来到函数sub_12DFE4,这里应该就是函数绑定的地方。

参数0ff_265F20,指向sub_137978,进入

sub_137978这个函数是关键操作,可以Edit->other->specify switch idiom,修复。

sub_137978的逻辑大概是先保存栈帧,然后开辟新的空间,跳转跟之前一样,继续打印x8的变化,跟踪函数

定位到我们的解释器sub_160D7C

使用ida8.3进行动态调试

关键从0x1450fc处开始 ,从这里开始hook会遇到rtld_db_dlactivity反调试,而且会引导我们进入错误的分支。

tld_db_dlactivity函数默认情况下为空函数,当有调试器时将被改为断点指令。Android反调试对抗中,SIGTRAP信号反调试可通过判断该函数的数值来判断是否处于被调试状态

我们直接hook0x15a630,从这里开始调试,我们可以找到加密的opcode

image-20250313165951476

指向他地址的指针被存在x0寄存器之中

sub_16CE0C是第一个解密函数

存在花指令,直接nop掉,重新定义为函数,对数据进行异或操作。

步入跟踪,来到花指令后的代码

ADR(Address of Label) 是 ARM64 特有的伪指令,用于将当前 PC 相对的某个地址加载到寄存器

这里,它将 loc_16D09C 的地址加载到 X8,然后让 X8 的值减少 236(0xEC)

然后从栈中保存x30的地址,X30 通常用于存储返回地址,把 X8赋值给 X30,ret时就会返回我们设定的地址

进入真正的解密,解密逻辑为

从加密的opcode读取第一字节给w8,再从存加密opcode地址的地方读取第八个字节给w13 (c5)

w9存取的是加密的opcode读取第二字节,再减去w12(存加密opcode地址的地方读取第八个字节,和w13相等),这里结果为7

赋值,比较,与取W9低八位(07),相减,如果 EQ(ZF=1,前面 TST 结果是 0)W8 = W11(0xE2)否则W8 = W10(0xE7)

然后异或得w9=e5,保留 W13 低 8 位,来到左边分支,异或得到w10=6B

然后和之前一样的操作来到另外一部分解密操作,w8恢复到c5,把w9插入到w10的高位,然后把w8的低位复制到高位

然后w8和w10异或得到解密的opcode 20ae

把刚刚解密的opcode,和加密的opcode传入函数sub_15FC34,这里应该是下一步解密,来看看关键部分

x8赋值为主dex文件的地址,w9是存加密opcode地址+18的值(A0AF),然后从主dex文件加载值w10 w8

W11 = W9 / W10 (整数除法)
W9 = W9 - (W11 * W10)(求余数)
W9 = W9 << 8 (左移 8 位)
然后X9 的低 8 位用 X19 的低 8 位替换得到最后值23AE,得到偏移

最后再从他查询得到值 D2赋值给w0

接着会根据w0的值来进行二分查找。且这个值是会变化的,如果从前面的函数开始调试,会出现错误的值,正确查找来到解释器的位置。

解释器函数入参如下,重点关注a5 a6(加密的opcode)和a7 (已经解密的opcode),并且这里的a4就是我们之前得到的解密的主dex文件地址

sub_16CB4C 存在同样的花指令,去除,函数和sub_16CE0C,套路都是一样的,直接从最后的真实逻辑开始分析

X8 = X20 << 1(2),读取 X19+8 处的字节到 W13(c5),从 X26 + X8 处读取 1 字节到 W8(就是第三个加密的opcode21)

W9 = W9 - W12(c7-c5=2),赋值,测试 W9 的 bit 5 是否为 1,W12 = W9 & 0xFF,W13 = W8 - W13,W8 = (Z == 1) ? W11 : W10

W9 = W8 XOR W12,W8 = W13 & 0xFF,W10 = W8 XOR W11(8E)

再来到,和之前的逻辑差不多,最后返回0x154b=5451

而5451对应method_id 正是onCreate

sub_16C918发现也存在异或,分析关键部分

w8读取第五六位的加密opcode,(3718)w9取其高位。

这里w12依旧是之前的c5,四处解密都是用的同一个密钥。

W9 = W9 - W12,W12 = W8 - W12,W8 = W9 & 0xFF,W9 = (Z == 1) ? W11 : W10,W8 = W9 XOR W8

W9 = W12 & 0xFF,后面的逻辑跟前面基本上一样,最后返回0x0021。

以上我们得到最后解密的opcode,逆序一下

ae20(d2)4b152100,这里d2是二分查找的关键。

与源apk的字节码对比,只有操作数不同。剩下的以此类推。

参考:

360加固dex解密流程分析 | oacia = oaciaのBbBlog~ = DEVIL or SWEET

[原创]app加固分析狗尾续貂之dex vmp还原-Android安全-看雪-安全社区|安全招聘|kanxue.com

[分享]某vmp壳原理分析笔记-Android安全-看雪-安全社区|安全招聘|kanxue.com

[原创] 某DEX_VMP安全分析与还原-Android安全-看雪-安全社区|安全招聘|kanxue.com

vmp入门(一):android dex vmp还原和安全性论述

[RegisterNatives] java_class: com.stub.StubApp name: interface11 sig: (I)V fnPtr: 0x73fd0bb9d8 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x1399d8
[RegisterNatives] java_class: com.stub.StubApp name: interface11 sig: (I)V fnPtr: 0x73fd0bb9d8 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x1399d8
function hookTargetFunc() {
    var baseAddr = Module.findBaseAddress(TargetLibName)
    //console.log(TargetLibName + " Module Base: " + baseAddr);
    if (baseAddr == null) {
        console.log('Please makesure ' + TargetLibName + ' is Loaded, by setting extractNativeLibs true.');
        return;
    }
    // hook指定的函数地址
    Interceptor.attach(baseAddr.add(TargetFuncOffset), {
        onEnter: function (args) {
            console.log('\nCall ' + TargetFuncOffset.toString(16).toUpperCase() + ' In')
            this.tid = Process.getCurrentThreadId();
            var so_addr = Module.findBaseAddress(so_name);
            var so_size = Process.getModuleByName(so_name).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
                },
                onReceive: function () {
                  },
 
                transform: function (iterator) {
                    var instruction = iterator.next();
                    const startAddress = instruction.address;
                    if (startAddress){
                        do{
                               if (instruction.mnemonic.startsWith('br')||instruction.mnemonic.startsWith('blr')||instruction.mnemonic.startsWith('bl')) {
 
                                    try {
 
                                        iterator.putCallout(function (context) {
                                            var pc = context.pc; //获取pc寄存器,当前地址
                                            var lr = context.lr;
                                            var x8 = context.x8; //获取x8寄存器,
                                            var module = Process.findModuleByAddress(pc);
                                            if (module) {
                                                try{
                                                  console.log(module.name + "!" +Memory.readCString(ptr(x8)));  
                                                }
                                                catch (e){
                                                }                                            
                                                console.log("x8 ====" + DebugSymbol.fromAddress(ptr(x8)));
                                            }
                                        });
                                } catch (e) {
                                    console.log("error", e)
                                }
                             }
                            iterator.keep();
                        } while ((instruction = iterator.next()) !== null);
                    }
              }, 
                onCallSummary(summary) {
                }
            })
        }, onLeave: function (retVal) {
            console.log('Call ' + TargetFuncOffset.toString(16).toUpperCase() + ' Out\n')
            Stalker.unfollow(this.tid)
        }
    })
}
function hookTargetFunc() {
    var baseAddr = Module.findBaseAddress(TargetLibName)
    //console.log(TargetLibName + " Module Base: " + baseAddr);
    if (baseAddr == null) {
        console.log('Please makesure ' + TargetLibName + ' is Loaded, by setting extractNativeLibs true.');
        return;
    }
    // hook指定的函数地址
    Interceptor.attach(baseAddr.add(TargetFuncOffset), {
        onEnter: function (args) {
            console.log('\nCall ' + TargetFuncOffset.toString(16).toUpperCase() + ' In')
            this.tid = Process.getCurrentThreadId();
            var so_addr = Module.findBaseAddress(so_name);
            var so_size = Process.getModuleByName(so_name).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
                },
                onReceive: function () {
                  },
 
                transform: function (iterator) {
                    var instruction = iterator.next();
                    const startAddress = instruction.address;
                    if (startAddress){
                        do{
                               if (instruction.mnemonic.startsWith('br')||instruction.mnemonic.startsWith('blr')||instruction.mnemonic.startsWith('bl')) {
 
                                    try {
 
                                        iterator.putCallout(function (context) {
                                            var pc = context.pc; //获取pc寄存器,当前地址
                                            var lr = context.lr;
                                            var x8 = context.x8; //获取x8寄存器,
                                            var module = Process.findModuleByAddress(pc);
                                            if (module) {
                                                try{
                                                  console.log(module.name + "!" +Memory.readCString(ptr(x8)));  
                                                }
                                                catch (e){
                                                }                                            
                                                console.log("x8 ====" + DebugSymbol.fromAddress(ptr(x8)));
                                            }
                                        });
                                } catch (e) {
                                    console.log("error", e)
                                }
                             }
                            iterator.keep();
                        } while ((instruction = iterator.next()) !== null);
                    }
              }, 
                onCallSummary(summary) {
                }
            })
        }, onLeave: function (retVal) {
            console.log('Call ' + TargetFuncOffset.toString(16).toUpperCase() + ' Out\n')
            Stalker.unfollow(this.tid)
        }
    })
}
x8 ====0x7bfee7fe0c libjiagu_64.so!0x139e0c
x8 ====0x7bfee7fe64 libjiagu_64.so!0x139e64
x8 ====0x7bfee7fe7c libjiagu_64.so!0x139e7c//似乎是重点的
x8 ====0x7bfee80460 libjiagu_64.so!0x13a460//拷贝操作
x8 ====0x7bfee7fe8c libjiagu_64.so!0x139e8c
x8 ====0x7bfee7ff60 libjiagu_64.so!0x139f60 //来到复杂函数 sub_19DCA4
x8 ====0x7bfee7ffa0 libjiagu_64.so!0x139fa0//拷贝
x8 ====0x7bfee7ffc4 libjiagu_64.so!0x139fc4
x8 ====0x7bfee8010c libjiagu_64.so!0x13a10c
x8 ====0x7bfee80140 libjiagu_64.so!0x13a140
x8 ====0x7bfee80170 libjiagu_64.so!0x13a170
//oncreate
x8 ====0x7bfee80194 libjiagu_64.so!0x13a194
x8 ====0x7bfee801fc libjiagu_64.so!0x13a1fc
x8 ====0x7bfee804e4 libjiagu_64.so!0x13a4e4
x8 ====0x7bfee8024c libjiagu_64.so!0x13a24c
x8 ====0x7bfee80264 libjiagu_64.so!0x13a264//进程检测mutex_lock
//前面似乎一些栈的操作,然后寄存器发生变化
x8 ====0x7bfee802e8 libjiagu_64.so!0x13a2e8//
x8 ====0x7bfee80328 libjiagu_64.so!0x13a328//内存
x8 ====0x7bfee7fe64 libjiagu_64.so!0x139e64
x8 ====0x7bfee80600 libjiagu_64.so!0x13a600
x8 ====0x7bfee8061c libjiagu_64.so!0x13a61c
//然后出现下一个oncreate
x8 ====0x7bfee7fe0c libjiagu_64.so!0x139e0c
x8 ====0x7bfee7fe64 libjiagu_64.so!0x139e64
x8 ====0x7bfee7fe7c libjiagu_64.so!0x139e7c//似乎是重点的
x8 ====0x7bfee80460 libjiagu_64.so!0x13a460//拷贝操作
x8 ====0x7bfee7fe8c libjiagu_64.so!0x139e8c
x8 ====0x7bfee7ff60 libjiagu_64.so!0x139f60 //来到复杂函数 sub_19DCA4
x8 ====0x7bfee7ffa0 libjiagu_64.so!0x139fa0//拷贝
x8 ====0x7bfee7ffc4 libjiagu_64.so!0x139fc4
x8 ====0x7bfee8010c libjiagu_64.so!0x13a10c
x8 ====0x7bfee80140 libjiagu_64.so!0x13a140
x8 ====0x7bfee80170 libjiagu_64.so!0x13a170
//oncreate
x8 ====0x7bfee80194 libjiagu_64.so!0x13a194
x8 ====0x7bfee801fc libjiagu_64.so!0x13a1fc
x8 ====0x7bfee804e4 libjiagu_64.so!0x13a4e4
x8 ====0x7bfee8024c libjiagu_64.so!0x13a24c
x8 ====0x7bfee80264 libjiagu_64.so!0x13a264//进程检测mutex_lock
//前面似乎一些栈的操作,然后寄存器发生变化
x8 ====0x7bfee802e8 libjiagu_64.so!0x13a2e8//
x8 ====0x7bfee80328 libjiagu_64.so!0x13a328//内存
x8 ====0x7bfee7fe64 libjiagu_64.so!0x139e64
x8 ====0x7bfee80600 libjiagu_64.so!0x13a600
x8 ====0x7bfee8061c libjiagu_64.so!0x13a61c
//然后出现下一个oncreate
x8 ====0x7bfee807ac libjiagu_64.so!0x13a7ac//下一步跳转
x8 ====0x7bfee807b8 libjiagu_64.so!0x13a7b8
x8 ====0x7bfee807f4 libjiagu_64.so!0x13a7f4
x8 ====0x7bfee80808 libjiagu_64.so!0x13a808
x8 ====0x7bfee80810 libjiagu_64.so!0x13a810
x8 ====0x7bfee8082c libjiagu_64.so!0x13a82c
x8 ====0x7bfee80854 libjiagu_64.so!0x13a854
x8 ====0x7bfee80854 libjiagu_64.so!0x13a854
x8 ====0x7bfee80838 libjiagu_64.so!0x13a838
x8 ====0x7bfee807e0 libjiagu_64.so!0x13a7e0
x8 ====0x7bfee807f4 libjiagu_64.so!0x13a7f4
x8 ====0x7bfee80870 libjiagu_64.so!0x13a870
x8 ====0x7bfee8087c libjiagu_64.so!0x13a87c
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee808c4 libjiagu_64.so!0x13a8c4
x8 ====0x7bfee808dc libjiagu_64.so!0x13a8dc
x8 ====0x7bfee808e8 libjiagu_64.so!0x13a8e8
x8 ====0x7bfee807ac libjiagu_64.so!0x13a7ac//下一步跳转

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2025-3-13 17:28 被海带编辑 ,原因:
收藏
免费 9
支持
分享
赞赏记录
参与人
雪币
留言
时间
jmzqwh
谢谢你的细致分析,受益匪浅!
2025-3-18 15:14
墨穹呢
感谢你的积极参与,期待更多精彩内容!
2025-3-17 18:44
sinker_
期待更多优质内容的分享,论坛有你更精彩!
2025-3-17 18:29
软件君子
为你点赞!
2025-3-16 16:06
vVv一
+1
感谢你的贡献,论坛因你而更加精彩!
2025-3-16 11:25
mb_bkrfuwvx
+2
为你点赞!
2025-3-15 22:30
Arahat0
感谢你分享这么好的资源!
2025-3-15 21:25
iamshy
+1
非常支持你的观点!
2025-3-14 10:26
逆天而行
为你点赞!
2025-3-13 17:28
最新回复 (10)
雪    币: 2888
活跃值: (3542)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
太强了!
2025-3-13 17:28
0
雪    币: 424
活跃值: (363)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

图片好像寄了,图层要挂梯子

最后于 2025-3-13 17:38 被海带编辑 ,原因:
2025-3-13 17:35
0
雪    币: 0
活跃值: (140)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
tql
2025-3-15 20:25
0
雪    币: 339
活跃值: (662)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
海带 图片好像寄了,图层要挂梯子
最后于 2025-3-15 22:14 被蓝色之冰编辑 ,原因:
2025-3-15 22:10
0
雪    币: 446
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
厉害的
2025-3-16 16:19
0
雪    币: 446
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
有个思路 主动调用加固so+文件重定向 能不能实现免修复vmp 相当于过签名校验
2025-3-16 16:21
0
雪    币: 424
活跃值: (363)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
软件君子 有个思路 主动调用加固so+文件重定向 能不能实现免修复vmp 相当于过签名校验
复杂一点的应该就不行了
2025-3-17 13:34
0
雪    币: 2855
活跃值: (4092)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
感谢分享
2025-3-17 18:44
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
1
2025-3-17 19:20
0
雪    币: 335
活跃值: (185)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
感谢分享
2025-3-24 19:21
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

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