某乎x-zse-96逆向
zsk Lv4

app的版本是 10.37.0

定位

抓包啥的没啥好说的,参数定位直接jadx搜索,很快定位到,直接上图最终调用so加密的函数

image

该app有frida检测,怎么绕过看上一篇绕过libmsaoaidsec.so的Frida检测

从函数看加密跟aes相关,但其实魔改了多很,其中laesEncryptByteArr的三个参数,bArr是明文,str可以理解为扩展后的key,bArr2是iv,str和bArr2都是固定的

unidng分析

直接搭起框架,函数调用

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
package com.zhihu;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.StringObject;
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.virtualmodule.android.AndroidModule;
import com.github.unidbg.virtualmodule.android.JniGraphics;
import com.github.unidbg.virtualmodule.android.MediaNdkModule;
import com.github.unidbg.virtualmodule.android.SystemProperties;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
* Created by zsk
* on 2025/03/06 02:00
*/
public class encrypt extends AbstractJni{
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

public encrypt() {
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.zhihu.android").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
// 模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/zhihu/zhihu10.37.0.apk"));
// 设置JNI
vm.setJni(this);
// 打印日志
vm.setVerbose(true);
// 加载基本库
new AndroidModule(emulator, vm).register(memory);
new MediaNdkModule(emulator, vm).register(memory);
new JniGraphics(emulator, vm).register(memory);
new SystemProperties(emulator, null).register(memory);
// 加载so到虚拟内存
DalvikModule dm = vm.loadLibrary("bangcle_crypto_tool", true);
module = dm.getModule();

dm.callJNI_OnLoad(emulator);
}

public static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}

public void callAesEncryptByteArr() {
// args list
List<Object> list = new ArrayList<>(10);
// jnienv
list.add(vm.getJNIEnv());
// jclazz
list.add(0);

// hex 8b8d8089212822818d23298d23298180218229882280238883808a2a2a238082
byte[] input = {-117, -115, -128, -119, 33, 40, 34, -127, -115, 35, 41, -115, 35, 41, -127, -128, 33, -126, 41, -120, 34, -128, 35, -120, -125, -128, -118, 42, 42, 35, -128, -126};
// hex 99303a3a32343a3992923a3b3a999292
byte[] iv = {-103, 48, 58, 58, 50, 52, 58, 57, -110, -110, 58, 59, 58, -103, -110, -110};
String key = "541a3a5896fbefd351917c8251328a236a7efbf27d0fad8283ef59ef07aa386dbb2b1fcbba167135d575877ba0205a02f0aac2d31957bc7f028ed5888d4bbe69ed6768efc15ab703dc0f406b301845a0a64cf3c427c82870053bd7ba6721649c3a9aca8c3c31710a6be5ce71e4686842732d9314d6898cc3fdca075db46d1ccf3a7f9b20615f4a303c5235bd02c5cdc791eb123b9d9f7e72e954de3bcbf7d314064a1eced78d13679d040dd4080640d18c37bbde";

ByteArray arr1 = new ByteArray(vm, input);
ByteArray arr2 = new ByteArray(vm, iv);

list.add(vm.addLocalObject(arr1));
list.add(vm.addLocalObject(new StringObject(vm, key)));
list.add(vm.addLocalObject(arr2));

Number number = module.callFunction(emulator, 0x9e98, list.toArray());
ByteArray resultArr = vm.getObject(number.intValue());
System.out.println("result: " + bytesToHex(resultArr.getValue()));
}

public static void main(String[] args) {
encrypt e = new encrypt();
e.callAesEncryptByteArr();
}
}

运行报错了,该问题内存未分配,调用free释放内存导致的问题

image

这里可以直接在调用free的地方patch掉,不执行free就可以了

把traceCode开启,看报错的位置

1
emulator.traceCode(module.base, module.base + module.size);
1
2
3
4
5
6
7
8
9
10
[00:19:30 167][libbangcle_crypto_tool.so 0x048c8] [a01740f9] 0x120048c8: "ldr x0, [x29, #0x28]" x29=0xe4fff5b0 => x0=0x12052000
[00:19:30 168][libbangcle_crypto_tool.so 0x048cc] [1f001feb] 0x120048cc: "cmp x0, xzr" => nzcv: N=0, Z=0, C=1, V=0 x0=0x12052000
[00:19:30 168][libbangcle_crypto_tool.so 0x048d0] [20010054] 0x120048d0: "b.eq #0x120048f4" nzcv: N=0, Z=0, C=1, V=0
[00:19:30 169][libbangcle_crypto_tool.so 0x048d4] [a01740f9] 0x120048d4: "ldr x0, [x29, #0x28]" x29=0xe4fff5b0 => x0=0x12052000
[00:19:30 169][libbangcle_crypto_tool.so 0x048d8] [52f6ff97] 0x120048d8: "bl #0x12002220"
[00:19:30 177][libbangcle_crypto_tool.so 0x02220] [f00000b0] 0x12002220: "adrp x16, #0x1201f000" => x16=0x1201f000
[00:19:30 179][libbangcle_crypto_tool.so 0x02224] [11ba47f9] 0x12002224: "ldr x17, [x16, #0xf70]" x16=0x1201f000 => x17=0x1218bac0
[00:19:30 182][libbangcle_crypto_tool.so 0x02228] [10c23d91] 0x12002228: "add x16, x16, #0xf70" x16=0x1201f000 => x16=0x1201ff70
[00:19:30 184][libbangcle_crypto_tool.so 0x0222c] [20021fd6] 0x1200222c: "br x17" x17=0x1218bac0Invalid address 0x12052000 passed to free: value not allocated
[crash]A/libc: Invalid address 0x12052000 passed to free: value not allocated

程序是在0x0222c这个位置奔溃的,ida调到对应位置

1
2
3
4
void free(void *p)
{
free(p);
}

free函数,可以往上一点调到调用它的位置,0x048d8,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall check_package_name(__int64 a1)
{
char *pkgname; // [xsp+28h] [xbp+28h]

if ( aComZhihuAndroi[0] )
{
pkgname = get_pkgname(a1);
if ( !strcmp(aComZhihuAndroi, pkgname) )
dword_20024 = 1;
if ( pkgname )
free(pkgname);
}
else
{
dword_20024 = 1;
}
}

直接在 0x48d8 调转到free那里把指令改为npo,就不会执行了。

1
2
3
4
5
6
7
public void patch() {
try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);) {
KeystoneEncoded encoded = keystone.assemble("nop");
byte[] patchCode = encoded.getMachineCode();
emulator.getMemory().pointer(module.base + 0x48d8).write(patchCode);
}
}

很顺利不用补环境直接跑出来了结果

1
result: 0f02c75267dd7becc6b32f941837721f6f89610d4c49c3b5a8ddd649767e953de0a02e505b8e684fb5654137c266f12e

开始ida分析函数

ida分析

image

整个函数看下来,只有 sub_8B2C 这个函数最可疑跟进查看

image

好多aes加密相关的函数,怎么判断程序调用了哪个

记录函数对内存的访问以及发起访问的地址(PC指针),绘制成折线图,可以看函数的调用情况

使用Unidbg的ReadHook,监控这个函数的地址范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void traceAESRead() {
ReadHook readHook = new ReadHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
int now = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_PC).intValue();
if((now > module.base) & (now < (module.base + module.size))){
System.out.println(now - module.base);
}
}
@Override
public void onAttach(UnHook unHook) {}

@Override
public void detach() {}
};
emulator.getBackend().hook_add_new(readHook, module.base+0x8b2c, module.base+module.size+0x9080, null);
}

trade函数放在调用函数前执行,将打印出来的地址放到一个文件里,用python执行

image

1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt
import numpy

input = numpy.loadtxt("trace.txt", int)

plt.plot(range(len(input)), input)
# 限制Y
plt.ylim(23500,29000)
plt.plot(range(len(input)), input)
plt.show()

image

三次分组,每次分组执行都一样10轮调用,跳到24000即 0x5dc0 的位置

image

调用的函数是 Bangcle_WB_LAES_encrypt ,这里就是aes的加密逻辑了,查找引用看参数来源

image

把 Bangcle_WB_LAES_encrypt; 作为参数传给了 Bangcle_CRYPTO_cbc128_encrypt,

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
__int64 __fastcall Bangcle_CRYPTO_cbc128_encrypt(
__int64 input_data,
__int64 buff,
int input_data_len,
__int64 iv,
__int64 key,
void (__fastcall *Bangcle_WB_LAES_encrypt)(__int64, __int64, __int64, unsigned int *))
{
unsigned int v12; // [xsp+4Ch] [xbp+4Ch] BYREF
__int64 iv_1; // [xsp+50h] [xbp+50h]
int i; // [xsp+5Ch] [xbp+5Ch]

v12 = 0;
iv_1 = iv;
while ( input_data_len > 15 )
{
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(buff + i) = *(_BYTE *)(input_data + i) ^ *(_BYTE *)(iv_1 + i);
Bangcle_WB_LAES_encrypt(buff, buff, key, &v12);
iv_1 = buff;
input_data_len -= 16;
input_data += 16LL;
buff += 16LL;
}
return v12;
}

unidbg调试函数

使用unidbg对 Bangcle_CRYPTO_cbc128_encrypt 进行hook查看参数

参数一跟我们一开始前面的明文hex对比
8b8d8089212822818d23298d23298180218229882280238883808a2a2a238082

后面填充了16字节的 9b,这里填充是固定的,有兴趣的可以自己探索

1
2
3
4
5
6
7
8
9
10
11
12
13
mx0

>-----------------------------------------------------------------------------<
[01:24:35 310]x0=RW@0x12353060, md5=630d2fa2c2528055ad2374d1d4f9419b, hex=8b8d8089212822818d23298d23298180218229882280238883808a2a2a2380829b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
size: 112
0000: 8B 8D 80 89 21 28 22 81 8D 23 29 8D 23 29 81 80 ....!("..#).#)..
0010: 21 82 29 88 22 80 23 88 83 80 8A 2A 2A 23 80 82 !.).".#....**#..
0020: 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B 9B ................
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
^-----------------------------------------------------------------------------^

参数二是空的,用来存放结果的

参数三是 0x30,明文填充后的长度

参数四是 iv

1
2
3
4
5
6
7
8
9
10
11
12
13
mx3

>-----------------------------------------------------------------------------<
[01:34:12 007]x3=RW@0x12054000[libc++.so]0x4000, md5=d2b01352c3f52a790b79eb18ddc8938c, hex=99303a3a32343a3992923a3b3a999292000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
size: 112
0000: 99 30 3A 3A 32 34 3A 39 92 92 3A 3B 3A 99 92 92 .0::24:9..:;:...
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
^-----------------------------------------------------------------------------^

参数五看不出是啥,也没跟key对应,需要在往上看来源,这里注意参数五出来的是指针,需要打印指针的内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
m0x123580c0 176

>-----------------------------------------------------------------------------<
[01:56:59 956]RW@0x123580c0, md5=070a52023ae19a58ba6af416776beb5f, hex=8cc1bbc96bc566b80528b0777044afe8475bb7b8d7f563bb1d906c77817f05f1ee0c4b61cf4fd3619a744038a4b0f887036de86538dacfb2d951843df75d3cf5fb0ead3988157a3f2a2211ba9c18e9fe73d212241f0183a05d757ea66e80f0d8260b251051b1d44bb07252166917c70eecdd96f9a9d03d09ae5748d5002b811a35457064266861a73891d7fdc5f1286f87a52a68d300c4019fede9401c704ad4edd9095dc91e3780123c14cbb663a1e4
size: 176
0000: 8C C1 BB C9 6B C5 66 B8 05 28 B0 77 70 44 AF E8 ....k.f..(.wpD..
0010: 47 5B B7 B8 D7 F5 63 BB 1D 90 6C 77 81 7F 05 F1 G[....c...lw....
0020: EE 0C 4B 61 CF 4F D3 61 9A 74 40 38 A4 B0 F8 87 ..Ka.O.a.t@8....
0030: 03 6D E8 65 38 DA CF B2 D9 51 84 3D F7 5D 3C F5 .m.e8....Q.=.]<.
0040: FB 0E AD 39 88 15 7A 3F 2A 22 11 BA 9C 18 E9 FE ...9..z?*"......
0050: 73 D2 12 24 1F 01 83 A0 5D 75 7E A6 6E 80 F0 D8 s..$....]u~.n...
0060: 26 0B 25 10 51 B1 D4 4B B0 72 52 16 69 17 C7 0E &.%.Q..K.rR.i...
0070: EC DD 96 F9 A9 D0 3D 09 AE 57 48 D5 00 2B 81 1A ......=..WH..+..
0080: 35 45 70 64 26 68 61 A7 38 91 D7 FD C5 F1 28 6F 5Epd&ha.8.....(o
0090: 87 A5 2A 68 D3 00 C4 01 9F ED E9 40 1C 70 4A D4 ..*h.......@.pJ.
00A0: ED D9 09 5D C9 1E 37 80 12 3C 14 CB B6 63 A1 E4 ...]..7..<...c..
^-----------------------------------------------------------------------------^

image

就sub_3BA8和sub_47BC调用了v19,既上面的参数五

其实是在 sub_3BA8 对key做了一个异或

1
2
3
for ( i = 4; i < str_data_len; ++i )
*(_BYTE *)(*(_QWORD *)buff + i - 4LL) = str_data[i] ^ str_data[i % 3];
*(_DWORD *)(buff + 8) = str_data_len - 4;

key的长度是180字节,这里出来的结果是176个字节,所以这里就是密钥编排了,可以对这个函数hook,在返回前打印,因为key是固定的,所以可以直接拿编排后的使用。这里出来的结果就是上面的参数五了

好了,参数来源有了,回到上面继续看 Bangcle_CRYPTO_cbc128_encrypt 的逻辑,明文长度填充后是48,刚好3个分组,每次明文加密后会作为iv进行下一分组的加密

1
2
3
4
5
6
7
8
9
10
11
v12 = 0;
iv_1 = iv;
while ( input_data_len > 15 ){
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(buff + i) = *(_BYTE *)(input_data + i) ^ *(_BYTE *)(iv_1 + i);
Bangcle_WB_LAES_encrypt(buff, buff, key, &v12);
iv_1 = buff;
input_data_len -= 16;
input_data += 16LL;
buff += 16LL;
}

跟进Bangcle_WB_LAES_encrypt

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
__int64 __fastcall Bangcle_WB_LAES_encrypt(__int64 input_data, __int64 buff, __int64 *key_state)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

key_state_1 = *key_state;
round_count = *((_DWORD *)key_state + 4) / 32 + 6;
for ( i = 0; i <= 15; ++i )
*(&v36 + i) = (16
* (byte_B248[(unsigned __int8)(16 * (*(_BYTE *)(input_data + i) >> 4)) ^ (*(unsigned __int8 *)(key_state_1 + i) >> 4) & 0xF] >> 4)) ^ (byte_B248[(unsigned __int8)(16 * *(_BYTE *)(input_data + i)) ^ *(_BYTE *)(key_state_1 + i) & 0xF] >> 4);
for ( j = 1; j < round_count; ++j )
{
v20 = HIBYTE(dword_B548[v36]);
v21 = BYTE2(dword_B548[v36]);
v22 = BYTE1(dword_B548[v36]);
v23 = dword_B548[v36];
v24 = HIBYTE(dword_B548[v40]);
v25 = BYTE2(dword_B548[v40]);
v26 = BYTE1(dword_B548[v40]);
v27 = dword_B548[v40];
v28 = HIBYTE(dword_B548[v44]);
v29 = BYTE2(dword_B548[v44]);
v30 = BYTE1(dword_B548[v44]);
v31 = dword_B548[v44];
v32 = HIBYTE(dword_B548[v48]);
v33 = BYTE2(dword_B548[v48]);
v34 = BYTE1(dword_B548[v48]);
v35 = dword_B548[v48];
v4 = HIBYTE(dword_B948[v41]);
v5 = BYTE2(dword_B948[v41]);
v6 = BYTE1(dword_B948[v41]);
v7 = dword_B948[v41];
v8 = HIBYTE(dword_B948[v45]);
v9 = BYTE2(dword_B948[v45]);
v10 = BYTE1(dword_B948[v45]);
v11 = dword_B948[v45];
v12 = HIBYTE(dword_B948[v49]);
v13 = BYTE2(dword_B948[v49]);
v14 = BYTE1(dword_B948[v49]);
v15 = dword_B948[v49];
v16 = HIBYTE(dword_B948[v37]);
v17 = BYTE2(dword_B948[v37]);
v18 = BYTE1(dword_B948[v37]);
v19 = dword_B948[v37];
for ( i = 0; i <= 15; ++i )
*(&v20 + i) = (16
* (byte_B348[(unsigned __int8)(16 * ((unsigned __int8)*(&v20 + i) >> 4)) ^ ((unsigned __int8)*(&v4 + i) >> 4) & 0xF] >> 4)) ^ (byte_B348[(unsigned __int8)(16 * *(&v20 + i)) ^ *(&v4 + i) & 0xF] >> 4);
v4 = HIBYTE(dword_BD48[v46]);
v5 = BYTE2(dword_BD48[v46]);
v6 = BYTE1(dword_BD48[v46]);
v7 = dword_BD48[v46];
v8 = HIBYTE(dword_BD48[v50]);
v9 = BYTE2(dword_BD48[v50]);
v10 = BYTE1(dword_BD48[v50]);
v11 = dword_BD48[v50];
v12 = HIBYTE(dword_BD48[v38]);
v13 = BYTE2(dword_BD48[v38]);
v14 = BYTE1(dword_BD48[v38]);
v15 = dword_BD48[v38];
v16 = HIBYTE(dword_BD48[v42]);
v17 = BYTE2(dword_BD48[v42]);
v18 = BYTE1(dword_BD48[v42]);
v19 = dword_BD48[v42];
for ( i = 0; i <= 15; ++i )
*(&v20 + i) = (16
* (byte_B348[(unsigned __int8)(16 * ((unsigned __int8)*(&v20 + i) >> 4)) ^ ((unsigned __int8)*(&v4 + i) >> 4) & 0xF] >> 4)) ^ (byte_B348[(unsigned __int8)(16 * *(&v20 + i)) ^ *(&v4 + i) & 0xF] >> 4);
v4 = HIBYTE(dword_C148[v51]);
v5 = BYTE2(dword_C148[v51]);
v6 = BYTE1(dword_C148[v51]);
v7 = dword_C148[v51];
v8 = HIBYTE(dword_C148[v39]);
v9 = BYTE2(dword_C148[v39]);
v10 = BYTE1(dword_C148[v39]);
v11 = dword_C148[v39];
v12 = HIBYTE(dword_C148[v43]);
v13 = BYTE2(dword_C148[v43]);
v14 = BYTE1(dword_C148[v43]);
v15 = dword_C148[v43];
v16 = HIBYTE(dword_C148[v47]);
v17 = BYTE2(dword_C148[v47]);
v18 = BYTE1(dword_C148[v47]);
v19 = dword_C148[v47];
for ( i = 0; i <= 15; ++i )
*(&v20 + i) = (16
* (byte_B348[(unsigned __int8)(16 * ((unsigned __int8)*(&v20 + i) >> 4)) ^ ((unsigned __int8)*(&v4 + i) >> 4) & 0xF] >> 4)) ^ (byte_B348[(unsigned __int8)(16 * *(&v20 + i)) ^ *(&v4 + i) & 0xF] >> 4);
for ( i = 0; i <= 15; ++i )
*(&v36 + i) = (16
* (byte_B348[(unsigned __int8)(16 * ((unsigned __int8)*(&v20 + i) >> 4)) ^ (*(unsigned __int8 *)(key_state_1 + 16 * j + i) >> 4) & 0xF] >> 4)) ^ (byte_B348[(unsigned __int8)(16 * *(&v20 + i)) ^ *(_BYTE *)(key_state_1 + 16 * j + i) & 0xF] >> 4);
}
v20 = byte_C548[v36];
v21 = byte_C548[v41];
v22 = byte_C548[v46];
v23 = byte_C548[v51];
v24 = byte_C548[v40];
v25 = byte_C548[v45];
v26 = byte_C548[v50];
v27 = byte_C548[v39];
v28 = byte_C548[v44];
v29 = byte_C548[v49];
v30 = byte_C548[v38];
v31 = byte_C548[v43];
v32 = byte_C548[v48];
v33 = byte_C548[v37];
v34 = byte_C548[v42];
v35 = byte_C548[v47];
for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i > 15 )
break;
*(_BYTE *)(buff + i) = (16
* (byte_B448[(unsigned __int8)(16 * ((unsigned __int8)*(&v20 + i) >> 4)) ^ (*(unsigned __int8 *)(key_state_1 + 16 * j + i) >> 4) & 0xF] >> 4)) ^ (byte_B448[(unsigned __int8)(16 * *(&v20 + i)) ^ *(_BYTE *)(key_state_1 + 16 * j + i) & 0xF] >> 4);
}
return result;
}

看到里面的dword_B548,dword_B948等等都是一些大表

image

在还原算法时候可以直接拿来用。

既然 Bangcle_WB_LAES_encrypt 是加密的地方了,那应该或多或少有aes影子

image

可以先看以下round_count是不是真的是10

round_count = *((_DWORD *)data_ptr + 4) / 32 + 6;

用unidbg查看,这里是_DWORD类型,每4个字节一组,查看第4*4的字节位置截取4字节

1
2
3
4
5
6
7
8
9
10
11
12
13
mx2

>-----------------------------------------------------------------------------<
[02:20:49 310]x2=unidbg@0xe4fff530, md5=70dda4f32687556b499d15b8cca17ed6, hex=c080351200000000b0000000040000008000000001000000000000000000000000000000000000007c5d00120000000000000000000000001000000030000000603035120000000000000000ffffffffe0f5ffe400000000000000000000000010f6ffe4000000004c8f001200000000
size: 112
0000: C0 80 35 12 00 00 00 00 B0 00 00 00 04 00 00 00 ..5.............
0010: 80 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 ................
0020: 00 00 00 00 00 00 00 00 7C 5D 00 12 00 00 00 00 ........|]......
0030: 00 00 00 00 00 00 00 00 10 00 00 00 30 00 00 00 ............0...
0040: 60 30 35 12 00 00 00 00 00 00 00 00 FF FF FF FF `05.............
0050: E0 F5 FF E4 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060: 10 F6 FF E4 00 00 00 00 4C 8F 00 12 00 00 00 00 ........L.......
^-----------------------------------------------------------------------------^

80 00 00 00 就是我们要的,记得转为小端序 00 00 00 80,对应的十进制是128,计算结果是 128/32+6 = 10

for ( j = 1; j < round_count; ++j ) 刚好9轮

整个看下来多多少少能看出来,一开始就使用input_data和key_state进行异或,可以标记为初始轮密钥加

1
2
for ( i = 0; i <= 15; ++i )
*(&v36 + i) = (16 * (byte_B248[(unsigned __int8)(16 * (*(_BYTE *)(input_data + i) >> 4)) ^ (*(unsigned __int8 *)(key_state_1 + i) >> 4) & 0xF] >> 4)) ^ (byte_B248[(unsigned __int8)(16 * *(_BYTE *)(input_data + i)) ^ *(_BYTE *)(key_state_1 + i) & 0xF] >> 4);

每个for循环结束前的异或也是轮密钥加,中间整块就是字节替换,循环左移,列混淆

for循环结束后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v20 = byte_C548[v36];
v21 = byte_C548[v41];
v22 = byte_C548[v46];
v23 = byte_C548[v51];
v24 = byte_C548[v40];
v25 = byte_C548[v45];
v26 = byte_C548[v50];
v27 = byte_C548[v39];
v28 = byte_C548[v44];
v29 = byte_C548[v49];
v30 = byte_C548[v38];
v31 = byte_C548[v43];
v32 = byte_C548[v48];
v33 = byte_C548[v37];
v34 = byte_C548[v42];
v35 = byte_C548[v47];

编排后就是 字节替换和循环左移了

1
2
3
4
v36	v40	v44	v48  	v36	v40	v44	v48
v37 v41 v45 v49 ==> v41 v45 v49 v37
v38 v42 v46 v50 v46 v50 v38 v42
v39 v43 v47 v51 v51 v39 v43 v47

最后结束前就是轮密钥加

这个加密看下来很顺滑,没有混淆过,扔给gpd就能让它给出对应的python代码了。

其中几个点,比如

1
2
3
4
v20 = HIBYTE(dword_B548[v36]);
v21 = BYTE2(dword_B548[v36]);
v22 = BYTE1(dword_B548[v36]);
v23 = dword_B548[v36];

假设一个 <font style="color:rgb(64, 64, 64);">DWORD</font> 的值为 <font style="color:rgb(64, 64, 64);">0xAABBCCDD</font>,它的字节结构如下:

字节位置 字节值
字节 3 AA 最高有效字节
字节 2 BB
字节 1 CC
字节 0 DD 最低有效字节

还有v20,v21,v22等都是连续存储的,通过v20的指针按偏移取后面的值

上结果
0f02c75267dd7becc6b32f941837721f6f89610d4c49c3b5a8ddd649767e953de0a02e505b8e684fb5654137c266f12e

跟unidbg跑出来的一样

image

 评论