首页
社区
课程
招聘
[原创]对某某漫画的算法还原测试
发表于: 2天前 484

[原创]对某某漫画的算法还原测试

2天前
484

前言:文章仅供学习 严禁用于非法用途

关键点:360脱壳修复 字节系列抓包 dump so修复 idapython使用 MD5 unidbg Frida

查壳修复

MT查壳可以看到是360加固,直接使用在线网站可以进行脱壳,也可以找到360加固的脱壳点进行手动dump,都可以,只是这篇文章更偏向于算法还原,所以直接使用在线网站进行脱壳了

8b1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1j5#2y4W2)9J5k6h3q4D9i4K6u0r3

可以得到脱下来的dex文件

image-20251110162749719

放到MT里面进行修复就行,MT没有会员也可以使用NP进行修复

然后将这些dex全部放到原始的APK里面,把原来dex的删掉就行,这样只是可以让我们把他放大jadx进行分析了,因为他还有一个oncreate被抽取了,以及许多stub的特征没有删除,这里要具体往下分析的话,大家使用Android Studio进行分析就行,看他的log日志哪里报错了,不过会有许多,建议大家找到一处,然后直接进行MT正则匹配全部替换为null,有的地方也许不能为null,尽量看就行,这里不再具体说明

抓包分析:

抓包配置:

我采用的方式是Reqable转发Burpsuit的方式,具体就是

image-20251110163300989

image-20251110163321662

然后在bp里面开一个端口转发监听

image-20251110163345465

这样就可以抓到了,还是蛮好用的

抓包检测:

这个APP是没有检测的,只不过我最开始没有抓到,最后还是重启手机抓到的,也可以具体分析,因为我之前在抓了几个字节系列的包,也是不行,看了一些网上的教程手动过了一下,这里大家可以参考一下

字节就是会魔改这个libsscronet.so,我是在内存种dump的,因为他的lib里面没有,那么其实大概率也是没有魔改,这里只是给大家分享一下

image-20251110163758204

dump_so的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function dump_so(so_name) {
    Java.perform(function () {
        let currentApplication = Java.use('android.app.ActivityThread').currentApplication()
        let dir = currentApplication.getApplicationContext().getFilesDir().getPath()
        let 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)
        let file_path = dir + '/' + libso.name + '_' + libso.base + '_' + ptr(libso.size) + '.so'
        let file_handle = new File(file_path, 'wb')
        if (file_handle && file_handle != null) {
            Memory.protect(ptr(libso.base), libso.size, 'rwx')
            let libso_buffer = ptr(libso.base).readByteArray(libso.size)
            file_handle.write(libso_buffer)
            file_handle.flush()
            file_handle.close()
            console.log('[dump]:', file_path)
        }
    })
}

有两种把,第二种就是遍历内存了

大家搜索一下这个函数SSL_CTX_set_custom_verify()

image-20251110164037509

交叉引用一下

image-20251110164107509

其实它是有参数的,只不过没有显示出来,大家可以网上搜索一下,找到他的回调,也可以看汇编找

image-20251110164157205

也就是这一个函数,大家写一个frida脚本,把他返回值写成0x0就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ssl_pass2() {
    var offest = 0x301184;
    var soName = 'libsscronet.so';
    console.log("==")
    const module = Process.findModuleByName(soName)
    Interceptor.attach(module.base.add(offest), {
        onEnter: function (args) {
        }, onLeave: function (retval) {
            retval.replace(0x0)
            console.log("返回值", retval)
            return retval
        }
    })
 
}

这样就可以过掉了,一般会有两个地方出现这个函数,大家可以都测试一下,有的是第一个交叉引用,有的是第二个交叉引用出现

Java请求位置1分析:

因为我也是第一次进行这样的抓包分析,我就把能抓到的包的值全部进行加密解密了

先看这个吧,这两个参数先还原了一下:

Java层定位

image-20251110164858979

搜索关键词就行,可以定位到基本的

image-20251110165021607

image-20251110165050153

基本就是这个,只不过就是对请求类型进行判断了一下罢了

image-20251110165140623

Java层其实就是这些,一个jmd,调用了So层的函数,不过so层函数有点不同寻常

S层md函数分析

image-20251110165304971

image-20251110165432123

这个其实隐藏起来了,看汇编找到这个注册函数表

image-20251110165456894

image-20251110165506212

继续看

我其实没看到有什么像加密的地方,不过这里是重点

image-20251110165813729

加载了一些文件什么的,我这里没分析清楚,不过我用其他方法,主动调用这个函数,然后进行测试

1
2
3
4
5
let JNISecurity = Java.use("com.kanman.JNISecurity");
var str1 = "1"
var str2 = "2"
var ret1 = JNISecurity.jmd(str1, str2)
console.log("jmd加密:", ret1)

image-20251110170114917

得到这个,md大家可以用cmd解一下发现可以解密出来,但是我没次数,只能用别的方法,就是算法自吐脚本

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
function hook_tess() {
    Java.perform(function () {
        //Base64
        var base64 = Java.use('android.util.Base64');
        var string = Java.use('java.lang.String');
        /*base64.encode.overload('[B', 'int', 'int', 'int').implementation = function(){
            send("=================base64 encode====================");
            send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            send(arguments[0]);
            send(arguments[1]);
            send(arguments[2]);
            send(arguments[3]);
            var data=this.encode(arguments[0],arguments[1],arguments[2],arguments[3])
            send("base64:"+string.$new(data));
            return data;
        }*/
 
        /*base64.decode.overload('[B', 'int', 'int', 'int').implementation = function(){
            send("=================base64 decode====================");
            send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            send(arguments[0]);
            send(arguments[1]);
            send(arguments[2]);
            send(arguments[3]);
            var data=this.decode(arguments[0],arguments[1],arguments[2],arguments[3])
            send("base64:"+string.$new(data));
            return data;
        }*/
 
 
        // MD SHA
        var messageDigest = Java.use('java.security.MessageDigest');
        // update
        for (var i = 0; i < messageDigest.update.overloads.length; i++) {
            messageDigest.update.overloads[i].implementation = function () {
                var name = this.getAlgorithm()
                send("=================" + name + "====================");
                send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
                if (arguments.length == 1) {
                    send(arguments[0]);
                    this.update(arguments[0]);
                } else if (arguments.length == 3) {
                    send(arguments[0]);
                    send(arguments[1]);
                    send(arguments[2]);
                    this.update(arguments[0], arguments[1], arguments[2]);
                }
            }
        }
        // digest
        for (var i = 0; i < messageDigest.digest.overloads.length; i++) {
            messageDigest.digest.overloads[i].implementation = function () {
                var name = this.getAlgorithm()
                send("=================" + name + "====================");
                send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
                if (arguments.length == 0) {
                    var data = this.digest();
                    send(data);
                    return data;
                } else if (arguments.length == 1) {
                    send(arguments[0]);
                    var data = this.digest(arguments[0]);
                    send(data);
                    return data;
                } else if (arguments.length == 3) {
                    send(arguments[0]);
                    send(arguments[1]);
                    send(arguments[2]);
                    var data = this.digest(arguments[0], arguments[1], arguments[2]);
                    send(data);
                    return data;
                }
            }
        }
 
        //MAC
        // var mac = Java.use('javax.crypto.Mac');
        // for (var i = 0; i < mac.doFinal.overloads.length; i++) {
        //     mac.doFinal.overloads[i].implementation = function () {
        //         var name = this.getAlgorithm()
        //         send("=================" + name + "====================");
        //         send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        //         if (arguments.length == 0) {
        //             var data = this.doFinal();
        //             send(data);
        //             return data;
        //         } else if (arguments.length == 1) {
        //             send(arguments[0]);
        //             var data = this.doFinal(arguments[0]);
        //             send(data);
        //             return data;
        //         } else if (arguments.length == 2) {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             var data = this.doFinal(arguments[0], arguments[1]);
        //             send(data);
        //             return data;
        //         }
        //     }
        // }
 
        // DES DESede AES PBE RSA
        var cipher = Java.use('javax.crypto.Cipher');
        // for (var i = 0; i < cipher.doFinal.overloads.length; i++) {
        //     cipher.doFinal.overloads[i].implementation = function () {
        //         var name = this.getAlgorithm()
        //         send("=================" + name + "====================");
        //         send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        //         if (arguments.length == 0) {
        //             var data = this.doFinal();
        //             send(data);
        //             return data;
        //         } else if (arguments.length == 1) {
        //             send(arguments[0]);
        //             var data = this.doFinal(arguments[0]);
        //             send(data);
        //             return data;
        //         } else if (arguments.length == 2) {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             var data = this.doFinal(arguments[0], arguments[1]);
        //             send(data);
        //             return data;
        //         } else if (arguments.length == 3) {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             send(arguments[2]);
        //             var data = this.doFinal(arguments[0], arguments[1], arguments[2]);
        //             send(data);
        //             return data;
        //         } else if (arguments.length == 5) {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             send(arguments[2]);
        //             send(arguments[3]);
        //             send(arguments[4]);
        //             var data = this.doFinal(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
        //             send(data);
        //             return data;
        //         } else {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             send(arguments[2]);
        //             send(arguments[3]);
        //             var data = this.doFinal(arguments[0], arguments[1], arguments[2], arguments[3]);
        //             send(data);
        //             return data;
        //         }
        //     }
        // }
 
        // //KEY
        // var secretKey = Java.use('javax.crypto.spec.SecretKeySpec');
        // for (var i = 0; i < secretKey.$init.overloads.length; i++) {
        //     secretKey.$init.overloads[i].implementation = function () {
        //         var name = this.getAlgorithm()
        //         send("=================KEY====================");
        //         //send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        //         if (arguments.length == 2) {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             this.$init(arguments[0], arguments[1]);
        //         } else if (arguments.length == 4) {
        //             send(arguments[0]);
        //             send(arguments[1]);
        //             send(arguments[2]);
        //             send(arguments[3]);
        //             this.$init(arguments[0], arguments[1], arguments[2], arguments[3]);
        //         }
        //     }
        // }
        //IV
        //DES KEY 
        //DESede KEY
        //PBE KEY salt
    });
}

可以根据需要来使用

image-20251110170303616

可以发现有一个

image-20251110170332681

这不就是我们的输入+一个字符串吗,这个字符串其实也在so层,大家搜索一下也可以看到,而且交叉引用也能定到md哪个函数里面,

image-20251110170441426

这里就对上了

大家也可以写一个python代码来实现

Java请求位置2分析:

m-request-did参数的分析

也是根据这个jadx搜索

image-20251110170701124

获取设备码加密

image-20251110170743114

可以定位到这里

分析就完事了

image-20251110170816112

这样的

image-20251110170854441

还是蛮清楚的

分析还原代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let Utils = Java.use("com.kanman.allfree.ext.utils.Utils");
        Utils["getDeviceId"].implementation = function () {
            console.log('getDeviceId is called');
            let ret = this.getDeviceId();
            console.log('getDeviceId ret value is ' + ret);
            return ret;
        };
 
let InfoUtils = Java.use("com.kanman.allfree.utils.InfoUtils");
        InfoUtils["getDeviceId"].implementation = function () {
            console.log('getDeviceId is called');
            let ret = this.getDeviceId();
            console.log('getDeviceId ret value is ' + ret);
            return ret;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Cryptodome.Cipher import AES
import base64
from Cryptodome.Util.Padding import pad, unpad
def encryaes(data,key):
    cipher=AES.new(key.encode('utf-8'),AES.MODE_ECB)
    # padded = cipher.pad(data,AES.block_size)
    encrypted=cipher.encrypt(data.encode('utf-8'))
    return base64.b64encode(encrypted).decode('utf-8')
 
def decryaes(base_enc,key):
    enc=base64.b64decode(base_enc)
    cipher=AES.new(key.encode('utf-8'),AES.MODE_ECB)
    dec=cipher.decrypt(enc)
    return dec.decode('utf-8')
 
s='S5aTaQe22BdwsExgr1ftHPPGb9Sxq69Pue/WdH1HcHI='
key='xujikmlioksjoped'
inp="4efa5850dab58086"
print(decryaes(s,key))
print(encryaes(inp,key))

就可以得到了

Java层请求位置3分析:

下面这个处理起来还是蛮好玩的

Java层定位

image-20251110171655757

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
function hook_sig1() {
    Java.perform(function () {
 
        let Orange = Java.use("com.yxcorp.kuaishou.addfp.android.Orange");
        let KWEGIDDFP = Java.use("com.yxcorp.kuaishou.addfp.KWEGIDDFP");
        KWEGIDDFP["doSign"].implementation = function (context, str) {
            console.log('doSign is called' + ', ' + 'context: ' + context + ', ' + 'str: ' + str);
            let ret = this.doSign(context, str);
            console.log('doSign ret value is ' + ret);
            return ret;
        };
 
        //获取活动的 Context
        var current_application = Java.use('android.app.ActivityThread').currentApplication();
        var context = current_application.getApplicationContext();
 
        // 构造 byte[] 数组
        let bArr = Java.array('byte', [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38]);  // 可以根据需要构造适当的 byte[] 数组
        let i = 4;  // 整数参数
 
        // 使用 overload 来调用 getClock 函数
        let result = Orange.getClock(context, bArr, i);
        console.log("[*] 参数:", "bArr", bArr, "i:", i)
 
        // 打印返回值
        console.log('[*] 返回值: ' + result);
 
        console.log("=====")
 
    })
}
hook_sig1()

image-20251110171848120

这里继续搜索就行

image-20251110171915994

可以找到这个函数

image-20251110171942059

一层层分析下去,可以看到这个so,以及so的代码

So层代码分析:

他就是静态注册的,但是有许多花指令

image-20251110172231427

image-20251110172305259

存在许多的垃圾代码,死代码,这时候咱们就得nop修复了,这个花指令也挺简单,就是插入了一段无用代码,永远不会执行,影响静态看

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
import ida_bytes
import idc
import idaapi
def find_and_patch(start,end):
    pattern=['STP', 'STP', 'ADR', 'SUBS', 'MOV', 'ADDS', 'STR','LDP','LDP','BR']
    res=[]
    while start<end:
        ea=start
        cnt=0
        subs_offset=0
        adds_offset=0
        for i in range(len(pattern)):
            if idc.print_insn_mnem(ea) =="SUBS":
                subs_offset=get_offset(ea)
            if idc.print_insn_mnem(ea) =="ADDS":
                adds_offset=get_offset(ea)
            if pattern[i] != idc.print_insn_mnem(ea):
                break
            else:
                cnt+=1
                ea=idc.next_head(ea,end)
        if cnt==10:
            res.append((start+8,start+0x24+adds_offset-subs_offset))
            #print(hex(0x20+adds_offset-subs_offset))
            start+=0x20+adds_offset-subs_offset
        else:
            start+=4
    return res
 
def get_offset(addr):
    raw_ins=ida_bytes.get_bytes(addr,4)
    instr=int.from_bytes(raw_ins,byteorder='little')
    imm_val=(instr>>10)&0xff
    #print('===',hex(imm_val))
    return imm_val
     
     
def set_color(start,end):
    for addr in range(start,end):
        idc.set_color(addr,idc.CIC_ITEM,0xd0ffc0)
 
def do_patch(start,end):
    nop_bytes = (0x1F2003D5).to_bytes(4, "big")
    while start<=end:
        ida_bytes.patch_bytes(start,nop_bytes)
        start+=4
 
def upc(begin,end):
    for i in range(begin,end):
        idc.del_items(i)
 
    for i in range(begin,end):
        idc.create_insn(i)
 
    for i in range(begin,end):
        idaapi.add_func(i)
    print("Finish!!!")
         
begin=0x4360
end=0x4A54
patchs=find_and_patch(begin,end)
print(patchs)
for i in range(len(patchs)):
    set_color(patchs[i][0],patchs[i][1])
    do_patch(patchs[i][0],patchs[i][1])
upc(begin,end)

所以我写了一个idapython来帮助我们静态分析

这个使用就是这个

image-20251110172538623

然后run就行

image-20251110172613172

image-20251110172625881

上面的就是成果,可以看了

接下来就是静态了,我使用frida脚本辅助我们静态看

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
function hook_so_sig1() {
    var offest = 0x3F48;
    var soName = 'libsgcore.so';
 
    const module = Process.findModuleByName(soName)
    console.log(module.base.add(offest))
    Interceptor.attach(module.base.add(offest), {
        onEnter: function (args) {
            console.log("原始值", (this.context.x0))
            // this.context.x1 = 0;
            // console.log("修改成功", this.context.x1)
            console.log("开始调用参数")
            console.log(args[0])
            console.log(args[1])
            console.log(args[2])
            console.log(args[3])
            console.log(Memory.readByteArray(this.context.x1, 0x40))
            console.log(Memory.readByteArray(this.context.x2, 0x40))
            // console.log(Memory.readByteArray(this.context.x2, 0x40))
            // console.log(this.context.x0)
            // console.log(this.context.x1)
            // console.log(this.context.x22)
        }, onLeave: function (retval) {
            // retval.replace(0x0)
            console.log("返回值", retval)
            // return retval
        }
    })
 
}
function hook_findso() {
 
    var offest = 0x22D0;
    var soName = 'libsgcore.so';
    const module = Process.findModuleByName(soName)
    console.log("模块基址", module.base.add(offest))
    Interceptor.attach(module.base.add(offest), {
        onEnter: function (args) {
            // console.log("原始值", (this.context.x8))
            // this.context.x1 = 0;
            // console.log("修改成功", this.context.x1)
            console.log("开始调用参数==============================")
            console.log('[*] 参数一', (args[0]))
            console.log('[*] 参数二', args[1])
            console.log('[*] 参数三', args[2])
            //var s = Memory.readPointer(args[0])
            this.keys = args[0]
 
            console.log('[*] 参数一数组', Memory.readByteArray(this.context.x0, 0x40))
            console.log('[*] 参数二数组', Memory.readByteArray(this.context.x1, 0x40))
            // console.log('[*] 参数三数组', Memory.readByteArray(this.context.x2, 0x40))
            // console.log(Memory.readByteArray(this.context.x2, 0x20))
            // console.log(this.context.x0)
            // console.log(this.context.x1)
            // console.log(this.context.x22)
        }, onLeave: function (retval) {
            // retval.replace(0x0)
            console.log('[*] 返回值2:', Memory.readByteArray(retval, 0x40))
            console.log('[*] 返回值:', Memory.readByteArray(this.keys, 0x40))
 
            // return retval
        }
    })
}
 
function hook_enctypart() {
 
    var offest = 0x24A0;
    var soName = 'libsgcore.so';
    const module = Process.findModuleByName(soName)
    console.log("模块基址", module.base.add(offest))
    Interceptor.attach(module.base.add(offest), {
        onEnter: function (args) {
            // console.log("原始值", (this.context.x8))
            // this.context.x1 = 0;
            // console.log("修改成功", this.context.x1)
            console.log("开始调用参数==============================")
            // console.log('[*] 参数一', (args[0]))
            // console.log('[*] 参数二', args[1])
            // console.log('[*] 参数三', args[2])
            //var s = Memory.readPointer(args[0])
            this.keys = args[0]
            console.log('[*] 中间值', this.context.x9, 0x40)
            //console.log('[*] 中间值', Memory.readByteArray(this.context.x9, 0x40))
            // console.log('[*] 参数二数组', Memory.readByteArray(this.context.x1, 0x40))
            // console.log('[*] 参数三数组', Memory.readByteArray(this.context.x2, 0x40))
            // console.log(Memory.readByteArray(this.context.x2, 0x20))
            // console.log(this.context.x0)
            // console.log(this.context.x1)
            // console.log(this.context.x22)
        }, onLeave: function (retval) {
            // retval.replace(0x0)
            // console.log('[*] 返回值2:', Memory.readByteArray(retval, 0x40))
            // console.log('[*] 返回值:', Memory.readByteArray(this.keys, 0x40))
 
            // return retval
        }
    })
}

下面附上我的分析的代码

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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
jstring __fastcall Java_com_kwai_sgcore_SGCore_getClock(JNIEnv *JNIEnv, __int64 a2, __int64 a3, void *a4)
{
  int n10; // w8
  int i; // w9
  jstring result; // x0
  jbyte *v10; // x0
  jbyte *v11; // x21
  jsize v13; // w0
  int n10_1; // w8
  unsigned int v15; // w23
  int v16; // w9
  __int64 s_1; // x0
  __int64 s_2; // x0
  __int64 ptr_1; // x0
  _BOOL4 v20; // w8
  const char *s_3; // x8
  int j_1; // w22
  int n10_2; // w8
  int v24; // w9
  __int64 j; // x23
  bool v26; // w8
  unsigned __int64 n0x10; // x9
  bool v28; // w8
  char v29; // w12
  unsigned int n0xFF; // w9
  unsigned __int64 n0xF; // x10
  int v32; // w12
  __int64 idx; // x8
  int v34; // w23
  __int64 v35; // x0
  __int64 v36; // x0
  unsigned __int8 *ptr; // x22
  int n10_3; // w8
  int v39; // w9
  __int64 k; // x23
  int m; // w2
  int v42; // w9
  size_t v43; // [xsp+0h] [xbp-160h]
  size_t v44; // [xsp+0h] [xbp-160h]
  __int128 v45; // [xsp+20h] [xbp-140h] BYREF
  __int128 v46; // [xsp+30h] [xbp-130h]
  __int128 v47; // [xsp+40h] [xbp-120h]
  char v48; // [xsp+50h] [xbp-110h]
  char v49[104]; // [xsp+60h] [xbp-100h] BYREF
  char s[8]; // [xsp+C8h] [xbp-98h] BYREF
  __int64 v51; // [xsp+D0h] [xbp-90h]
  __int64 v52; // [xsp+D8h] [xbp-88h]
  JNIEnv *JNIEnv_1; // [xsp+140h] [xbp-20h]
  __int64 v54; // [xsp+148h] [xbp-18h]
  __int64 v55; // [xsp+150h] [xbp-10h]
 
  JNIEnv_1 = JNIEnv;
  v54 = a2;
  v55 = a3;
  v52 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  n10 = ::n10;
  i = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
  if ( !a4 )
  {
    result = 0LL;
    goto LABEL_9;
  }
  while ( 1 )
  {
    v10 = (*JNIEnv)->GetByteArrayElements(JNIEnv, a4, 0LL);
    n10 = ::n10;
    v11 = v10;
    if ( (::n10 & 0x80000000) != 0 || (((HIDWORD(::n10) - 1) * HIDWORD(::n10)) & 0x80000000) == 0 )
      break;
    (*JNIEnv)->GetByteArrayElements(JNIEnv, a4, 0LL);
  }
  if ( !v10 )
  {
    result = 0LL;
    i = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
    if ( (::n10 & 0x80000000) == 0 )
      goto LABEL_21;
    goto LABEL_9;
  }
  if ( (*JNIEnv)->ExceptionCheck(JNIEnv) )
  {
    while ( 1 )
    {
      (*JNIEnv)->ExceptionClear(JNIEnv);
      n10 = ::n10;
      result = 0LL;
      i = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
      if ( (::n10 & 0x80000000) != 0 || (i & 0x80000000) == 0 )
        break;
      (*JNIEnv)->ExceptionClear(JNIEnv);
    }
    goto LABEL_20;
  }
  if ( (::n10 & 0x80000000) == 0 && (HIDWORD(::n10) - 1) * HIDWORD(::n10) < 0 )
    goto LABEL_25;
  while ( 1 )
  {
    v13 = (*JNIEnv)->GetArrayLength(JNIEnv, a4);
    n10_1 = ::n10;
    v15 = v13;
    v16 = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
    if ( (::n10 & 0x80000000) != 0 || (v16 & 0x80000000) == 0 )
      break;
LABEL_25:
    (*JNIEnv)->GetArrayLength(JNIEnv, a4);
  }
  if ( !::s )
  {
    while ( 1 )
    {
      s_1 = AES_encrypt_(byte_F8AB, 16LL);      // 0xC3,0xD9,0x4,0x91,0x81,0xAF,0x94,0x9,0x9F,0x39,0xC2,0xFC,0xB,0xCA,0x33,0xC7有点像AES加密,但是应该是把表改了
      n10_1 = ::n10;
      ::s = s_1;
      v16 = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
      if ( (::n10 & 0x80000000) != 0 || (v16 & 0x80000000) == 0 )
        break;
      ::s = AES_encrypt_(byte_F8AB, 16LL);
    }
  }
  while ( n10_1 >= 0 && v16 < 0 )
    ;
  if ( !s_0 )
  {
    s_2 = (sub_2710)(JNIEnv, a3);
    n10_1 = ::n10;
    s_0 = s_2;
    v16 = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
  }
  if ( (n10_1 & 0x80000000) == 0 && v16 < 0 )
  {
    while ( 1 )
      ;
  }
  ptr_1 = ::ptr;
  if ( !::ptr )
  {
    while ( 1 )
    {
      ptr_1 = (sub_2A28)(JNIEnv, a3);           // 一堆java层的读取操作
      n10_1 = ::n10;
      ::ptr = ptr_1;
      v16 = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
      if ( (::n10 & 0x80000000) != 0 || (v16 & 0x80000000) == 0 )
        break;
      ::ptr = (sub_2A28)(JNIEnv, a3);
    }
  }
  v20 = n10_1 < 10 || (v16 & 1) == 0;
  while ( !v20 )
    ;
  if ( !s_0 || (s_3 = ::s) == 0LL || !ptr_1 )   // 从这里开始执行了已经
  {
    result = (JNIEnv[167])(JNIEnv, &byte_1046F);
    n10 = ::n10;
    for ( i = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
          (::n10 & 0x80000000) == 0 && i < 0;
          i = (HIDWORD(::n10) - 1) * HIDWORD(::n10) )
    {
      (JNIEnv[167])(JNIEnv, &byte_1046F);
      result = (JNIEnv[167])(JNIEnv, &byte_1046F);
      n10 = ::n10;
    }
    goto LABEL_49;
  }
  while ( 1 )                                   // 从这里执行,但是没找到哪里进行的输入,等会trace一下
  {
    *s = 0LL;
    v51 = 0LL;
    j_1 = strlen(s_3);                          // 读取长度是0xc
    MD5_state(v49);                             // 这个可能是MD5 state初始化
    MD5_update(v49, v11, v15);                  // MD5 update
                                                // 第二个参数就是我们输入的那个数组值
                                                // 参数三是5
    n10_2 = ::n10;
    v24 = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
    if ( (::n10 & 0x80000000) != 0 || (v24 & 0x80000000) == 0 )
      break;
    *s = 0LL;
    v51 = 0LL;
    MD5_state(v49);                             // 这里没有执行,前面那个MD5不知道有没有加盐
    MD5_update(v49, v11, v15);                  // 这里拼接了一些数值ca8e86efb32e,应该是加盐了
    s_3 = ::s;
  }
  for ( j = 0LL; ; j += 2LL )
  {
    v26 = n10_2 >= 0 && v24 < 0;
    if ( j >= j_1 )
      break;
    if ( v26 )
      goto LABEL_60;
    while ( 1 )
    {
      sprintf(s, "%c%c", *(::s + j), *(::s + j + 1));
      MD5_update(v49, s, 2LL);
      n10_2 = ::n10;
      v24 = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
      if ( (::n10 & 0x80000000) != 0 || (v24 & 0x80000000) == 0 )
        break;
LABEL_60:
      sprintf(s, "%c%c", *(::s + j), *(::s + j + 1));
      MD5_update(v49, s, 2LL);
    }
  }
  if ( v26 )
    (sub_6A2C)(v49);                            // 这里也会被调用
  (sub_6A2C)(v49);
  n0x10 = 0LL;
  v28 = ::n10 >= 0 && (HIDWORD(::n10) - 1) * HIDWORD(::n10) < 0;
  do
  {
    v29 = v49[n0x10 + 88];
    if ( v28 )
      s[n0x10] = v29;
    s[n0x10++] = v29;
  }
  while ( n0x10 < 0x10 );
  if ( v28 )
  {
    while ( 1 )
LABEL_72:
      ;
  }
  n0xFF = 0;
  n0xF = 0LL;
  while ( n0xF < 0xF )                          // 这里是对MD5的值进行扰动,把前15个字节的值加起来
  {
    v32 = s[n0xF++];
    n0xFF += v32;
    if ( v28 )
      goto LABEL_72;
  }
  idx = 0LL;
  if ( n0xFF <= 0xFF )
    LOBYTE(v34) = n0xFF;
  else
    v34 = -n0xFF;
  do
  {
    s[idx] ^= v34 ^ idx;                        // 这里是一个扰动,原来的值^idx^sums
    ++idx;
  }
  while ( idx != 15 );
  while ( 1 )
  {
    HIBYTE(v51) = v34;
    v44 = strlen(s_0);                          // 这里也是一个加密的地方,而且应该也是16次
    v36 = MD5_func2(0, s_0, v44);               // 这里的第四个参数是0x12
    LODWORD(v44) = MD5_func2(v36, ::ptr, 32);   // 这个函数在主动调用的时候会执行,并且第二个参数还是一个定值22e875c50d9a3fda6c2898eaf0a4d314
    ptr = encryfinal_xor(s, 16LL, v44);
    (*JNIEnv)->ReleaseByteArrayElements(JNIEnv, a4, v11, 2LL);
    n10_3 = ::n10;
    v39 = HIDWORD(::n10);
    v48 = 0;
    v46 = 0u;
    v47 = 0u;
    v45 = 0u;
    if ( (::n10 & 0x80000000) != 0 || (((HIDWORD(::n10) - 1) * HIDWORD(::n10)) & 0x80000000) == 0 )
      break;
    HIBYTE(v51) = v34;
    v43 = strlen(s_0);
    v35 = MD5_func2(0, s_0, v43);
    LODWORD(v43) = MD5_func2(v35, ::ptr, 32);
    encryfinal_xor(s, 16LL, v43);               // 最后的一个异或扰动加密,这里的参数三应该是上面那个定值的MD5
    (*JNIEnv)->ReleaseByteArrayElements(JNIEnv, a4, v11, 2LL);
    v48 = 0;
    v46 = 0u;
    v47 = 0u;
    v45 = 0u;
  }
  for ( k = 0LL; ; ++k )                        // 输出、环境处理
  {
    v42 = (v39 - 1) * v39;
    while ( n10_3 >= 0 && v42 < 0 )
      ;
    if ( k == 24 )
      break;
    for ( m = ptr[k]; ; m = ptr[k] )
    {
      sprintf(&v45 + 2 * k, "%02x", m);
      n10_3 = ::n10;
      v39 = HIDWORD(::n10);
      if ( (::n10 & 0x80000000) != 0 || (((HIDWORD(::n10) - 1) * HIDWORD(::n10)) & 0x80000000) == 0 )
        break;
      sprintf(&v45 + 2 * k, "%02x", ptr[k]);
    }
  }
  free(ptr);
  result = (*JNIEnv)->NewStringUTF(JNIEnv, &v45);
  n10 = ::n10;
  i = (HIDWORD(::n10) - 1) * HIDWORD(::n10);
LABEL_49:
  if ( (n10 & 0x80000000) == 0 && i < 0 )
  {
    while ( 1 )
      ;
  }
LABEL_20:
  if ( (n10 & 0x80000000) == 0 )
  {
LABEL_21:
    if ( i < 0 )
    {
      while ( 1 )
        ;
    }
  }
LABEL_9:
  if ( (n10 & 0x80000000) == 0 && i < 0 )
  {
    while ( 1 )
      ;
  }
  return result;
}

image-20251110172840893

这里有一个输入,我的frida代码应该也有记录,就直接分析代码了

首先就是输入拼接了一下这个ca8e86efb32e字符串然后进行MD5操作

image-20251110173947168

接下来就是一个异或扰动,具体就是先加和(前15字节)再异或扰动

image-20251110174118096

异或扰动完,就是另外一个加密

image-20251110174316636

也就是encryfinal_xor这个函数,代码如下:

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
void __fastcall encryfinal_xor(const void *src, size_t num_16, int key)
{
  _WORD *input; // x20
  _BOOL4 v7; // w24
  size_t v8; // x8
  size_t v9; // x10
  unsigned int n0xFF; // w9
  int v11; // w11
  __int64 v12; // x11
  bool v13; // zf
 
  nullsub_7();
  input = malloc(num_16 + 8);
  v7 = n10 >= 0 && (HIDWORD(n10) - 1) * HIDWORD(n10) < 0;
  if ( input )
  {
    if ( n10 < 0 || (HIDWORD(n10) - 1) * HIDWORD(n10) >= 0 )
      goto LABEL_6;
    do
    {
      memset(input, 0, num_16 + 8);
      *(input + 3) = key;
      *input = 0x101;
      *(input + 2) = 1;
      memcpy(input + 7, src, num_16);
LABEL_6:
      memset(input, 0, num_16 + 8);             // 其实是执行这里
      *(input + 3) = key;
      *input = 257;
      *(input + 2) = 1;
      memcpy(input + 7, src, num_16);
    }
    while ( v7 );
    v8 = num_16 + 7;
    if ( num_16 == -7LL )
    {
      *(input + v8) = 0;
    }
    else
    {
      v9 = 0LL;
      n0xFF = 0;
      do
      {
        v11 = *(input + v9++);
        n0xFF += v11;
      }
      while ( v8 > v9 );
      if ( n0xFF > 0xFF )
        n0xFF = -n0xFF;
      *input = n0xFF ^ 1;
      if ( num_16 != -6LL )
      {
        v12 = 0LL;
        do
        {
          v13 = num_16 + 6 == v12 + 1;
          *(input + v12 + 1) ^= n0xFF ^ (v12 + 1);
          ++v12;
        }
        while ( !v13 );
      }
      *(input + v8) = n0xFF;
      if ( v7 )
      {
        while ( 1 )
          ;
      }
    }
  }
  else if ( n10 >= 0 && (HIDWORD(n10) - 1) * HIDWORD(n10) < 0 )
  {
    while ( 1 )
      ;
  }
}

这里我写了一个unidbg的模拟执行

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
package com.suanfahuanyuan.kanman;
 
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.qwb2025clw.whiteboxaes;
 
import java.io.File;
 
public class kanmanenc {
    public final AndroidEmulator emulator;
    public final VM vm;
    public final Memory memory;
    public final Module module;
    DvmClass cNative;
    public int hitCount = 0;
 
    public kanmanenc() {
        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.kanman.allfree")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        emulator.getSyscallHandler().setEnableThreadDispatcher(true);
        vm = emulator.createDalvikVM(new File("/aa.apk")); // 创建Android虚拟机
        ///vm.setVerbose(true);
        new AndroidModule(emulator, vm).register(memory);
        // 加载并初始化动态库
        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/suanfahuanyuan/kanman/libsgcore.so"), true);
        module = dalvikModule.getModule();
 
        vm.getJNIEnv();
        //emulator.attach(DebuggerType.CONSOLE).addBreakPoint(module.base + 0xF7E8);
        //emulator.attach(DebuggerType.CONSOLE).addBreakPoint(module.base + 0xF43C);
        debugger();
        vm.callJNI_OnLoad(emulator, module);
        byte[] in16 = new byte[]{
                (byte)0x47,(byte)0xEB,(byte)0x2F,(byte)0xA6,
                (byte)0x47,(byte)0x33,(byte)0x98,(byte)0x86,
                (byte)0x02,(byte)0xA6,(byte)0x8A,(byte)0x38,
                (byte)0xCD,(byte)0xED,(byte)0x56,(byte)0x01
        };
        MemoryBlock pSrc = memory.malloc(in16.length, true);
        UnidbgPointer srcPtr = pSrc.getPointer();
        srcPtr.write(0, in16, 0, in16.length);
        pSrc.getPointer();
        long num_16 = 16L;      //
        int  num_2  = 0xb0da86aa;
        module.callFunction(emulator,0x22D0,srcPtr,num_16,num_2);
        //install();
 
    }
    public static void main(String[] args) {
        kanmanenc mainActivity = new kanmanenc();
 
        mainActivity.debugger();
    }
 
    private void debugger() {
        emulator.attach(DebuggerType.CONSOLE).addBreakPoint(module.base + 0x22D0);
        module.callFunction(emulator, 0x22D0);
    }
 
 
 
 
 
 
}

然后就是通过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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#sig1这个签名的还原
import hashlib
def sig1(inp:str):
    inp+="ca8e86efb32e"#这个值就是那个MD5的盐
    md5_str=hashlib.md5(inp.encode('utf-8')).hexdigest()
    # print('md5',md5_str)
    md5_byte=bytes.fromhex(md5_str)
    # print('md5_byte',md5_byte)
    s=list(md5_byte)
    #第一次异或扰动
    tmp1=[]
    sums1 = sum(s[:15]) if (sum(s[:15]) < 0xff) else (-sum(s[:15])) & 0xff
    for i in range(16):
        if i < 15:
            tmp1.append(s[i] ^ i ^ sums1)
            # print(hex(s[i] ^ i ^ sums1), end=' ,')
        else:
            tmp1.append(sums1&0xff)
            # print(hex(sums1))
    #第二次异或扰动
    '''
    前八字节基本是固定的
    input 0x1  1 1   key=0xb0da86aa   inp[16]
    前23个字符取和   sum(inp[:23])
    imp[0]^=1
    再次进行异或
    '''
    # key = 0xb0da86aa  这个经过hook应该是一个定值,就是参数三
    key1 = [0x1, 0x1, 1, 0xaa, 0x86, 0xda, 0xb0]
    tmp2 = key1 + tmp1
    # print(tmp2)
    for i in range(len(tmp2)):
        tmp2[i] &= 0xff
        # print(hex(tmp2[i]), end=' ,')
    print('============================sig1==========================')
    sums2 = sum(tmp2[:23]) & 0xff if (sum(tmp2[:23]) < 0xff) else (-sum(tmp2[:23])) & 0xffff
    # print(hex(sums2))
    tmp2[0] = (sums2 ^ 1) & 0xff
    # print(hex(tmp2[0]), end=' ,')
    final=[]
    final.append(tmp2[0])
    for i in range(1, 23):
        if i!=23:
            final.append((tmp2[i] ^ (i) ^ sums2) & 0xff)
        else:
            final.append(sums2 & 0xff)
        # print(hex((tmp2[i] ^ (i) ^ sums2) & 0xff), end=' ,')
    # print(hex(sums2 & 0xff), end=' ,')
    final_str="".join(f'{byte:02x}' for byte in final)
    print("sig1:",final_str)
    print('============================sig1==========================')
 
inp=''
sig1(inp)

就是这样

登录分析请求还原

image-20251110175243341

Java层定位:

搜索这个x_data就行,定位到下面的函数

image-20251110175141534

So层算法分析:

主要是下面这个,但是不知道为什么上面的咱们的哪个erciyuan2020这个出现在了咱们的地方,可能也是AES加密吧

image-20251110175541293

image-20251110175532109

image-20251110175603981

可以看到so层又调用了java层的加密,应该也是标准的只不过加了参数罢了

hook一下就行

image-20251110175704536

最终定位到这里,一个AES cbc模式的

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
function hook_enc() {
    Java.perform(function () {
        let JNISecurity = Java.use("com.kanman.JNISecurity");
        var str1 = "112233445566778899"
        var ret1 = JNISecurity.jEnc(str1)
        console.log("加密值", str1, "加密返回值", ret1)
 
        let KanManAES = Java.use("com.kanman.KanManAES");
        KanManAES["encryptString"].implementation = function (str, str2) {
            console.log('encryptString is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2);
            let ret = this.encryptString(str, str2);
            console.log('encryptString ret value is ' + ret);
            return ret;
        };
        //let JNISecurity = Java.use("com.kanman.JNISecurity");
        console.log("====================================")
 
        JNISecurity["jFileEncrypt"].implementation = function (str, str2) {
            console.log('jFileEncrypt is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2);
            let ret = this.jFileEncrypt(str, str2);
            console.log('jFileEncrypt ret value is ' + ret);
            return ret;
        };
        console.log("====================================")
        JNISecurity["jEnc"].implementation = function (str) {
            console.log('jEnc is called' + ', ' + 'str: ' + str);
            let ret = this.jEnc(str);
            console.log('jEnc ret value is ' + ret);
            return ret;
        };
        //let JNISecurity = Java.use("com.kanman.JNISecurity");
        var str2 = ""
        var ret2 = JNISecurity.dec(str2)
        console.log("解密值", ret2)
        // JNISecurity["dec"].implementation = function (str) {
        //     console.log('dec is called' + ', ' + 'str: ' + str);
        //     let ret = this.dec(str);
        //     console.log('dec ret value is ' + ret);
        //     return ret;
        // };
        //let KanManAES = Java.use("com.kanman.KanManAES");
        KanManAES["getHash"].overload('java.lang.String', '[B').implementation = function (str, bArr) {
            console.log('getHash is called' + ', ' + 'str: ' + str + ', ' + 'bArr: ' + bArr);
            let ret = this.getHash(str, bArr);
            console.log('getHash ret value is ' + ret);
            return ret;
        };
 
 
    })
}
function hook_key() {
    Java.perform(function () {
        // Hook SecretKeySpec 构造函数
        var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
        SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (keyBytes, algorithm) {
            console.log("SecretKeySpec constructor called:");
            console.log("Algorithm: " + algorithm);
            console.log("Key bytes: " + bytesToHex(keyBytes));
 
 
            // 调用原始构造函数
            return this.$init(keyBytes, algorithm);
        };
 
        // Hook IvParameterSpec 构造函数
        var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec");
        IvParameterSpec.$init.overload('[B').implementation = function (ivBytes) {
            console.log("IvParameterSpec constructor called:");
            console.log("IV bytes: " + bytesToHex(ivBytes));
 
            // 调用原始构造函数
            return this.$init(ivBytes);
        };
 
        // 辅助函数:将字节数组转换为十六进制字符串
        function bytesToHex(byteArray) {
            var hexStr = "";
            for (var i = 0; i < byteArray.length; i++) {
                hexStr += ("00" + (byteArray[i] & 0xFF).toString(16)).slice(-2);
            }
            return hexStr;
        }
    });
 
}
function hook_dec() {
    Java.perform(function () {
        let C2488a = Java.use("com.comic.common.b.a.p.a");
        C2488a["c"].overload('android.widget.RelativeLayout').implementation = function (relativeLayout) {
            console.log('c is called' + ', ' + 'relativeLayout: ' + relativeLayout);
            let ret = this.c(relativeLayout);
            console.log('c ret value is ' + ret);
            return ret;
        };
    });
}

通过上面的这个frida以及上面的算法自吐脚本可以得到key和iv,就是这样的

具体算法如下:(我没有进行对齐操作,需要进行填充,大家可以再完善一下)

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
#登录验证加密
#输入就是传递的参数,密钥就是第二个参数,是so里面的定值,iv是一个定值"1992360ee9bc4f8f"
import base64
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import unpad
from Cryptodome.Protocol.KDF import PBKDF2
 
def login_decry(inp:str):
    key="4548ded8c9e02690"
    iv="1992360ee9bc4f8f"
    enc_bytes=base64.b64decode(inp)
    cipher=AES.new(key.encode('utf-8'),AES.MODE_CBC, iv.encode('utf-8'))
    enc=cipher.decrypt(enc_bytes)
    print(enc)
 
def login_encry(inp:str):
    key="4548ded8c9e02690"
    iv="1992360ee9bc4f8f"
    cipher=AES.new(key.encode('utf-8'),AES.MODE_CBC, iv.encode('utf-8'))
    enc=cipher.encrypt(inp.encode('utf-8'))
    enc_base64=base64.b64encode(enc)
    print(enc_base64)
 
 
print()
inp='0OFm2t/mYjPIhqlECGE+Hs+SYycvu+AgBVK3vgmV3QtL3pJ67TP0cSXyVnNQGs1Q82xyniZkG3agTPOfR5ARlHBmdLAA3h2w7UFhhBVVMJllD4TMg7f1cP+GQa3/nGzpQUtS56MMWCL65Y5rf01xhKVUzNS+FCnp9cXkpK3AjHtHIJDqQxGpSBJbF35CnepDvPZoUwrmJS1UksXRHj3aoB0xuu/G4bLNIf+GMCLd3Bk3vB/p6Wrr2ZMMyMY2Q5SlxFhOSGQ1cwgLa5XLedsj1w=='
 
print(len(inp)%16)
login_decry(inp)
enc='{"client-channel":"qihoo","service":"qmmh","countryCode":"","mobile":"xxxxxxxxxxx","refresh":"0","productname":"qmmh","client-type":"android","imgCode":"","client-version":"1.5.4","platformname":"android"}\x03\x03\x03'
login_encry(enc)

就得了登录请求的所有参数,然后他的返回包就是一个png图片的base64,大家cyberchef搞一下就行

VIP分析:

这一块我没具体分析,只是找到了一些关键位置

定位位置

image-20251110180103675

image-20251110180135185

这里分析就行,具体的我也没做

最后:

这次在这个APP上面收获了不少东西,frida使用,unidbg使用,idapython去除花指令等等

后面需要加强的就是学习一个360加固的修复运行这种,提高一下自己对壳修复的能力

如果上面文章有侵权行为,请联系我删除


[培训]科锐软件逆向54期预科班、正式班开始火爆招生报名啦!!!

收藏
免费 2
支持
分享
最新回复 (2)
雪    币: 104
活跃值: (6838)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
tql
2天前
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2天前
0
游客
登录 | 注册 方可回帖
返回