记一道Android-CrackMe
App 信息

Jadx 解包 app 得到文件

逻辑很清晰,得到文本框的内容转为字符串赋值给 data
接着调用本类下的 a 方法

b-1 方法检测字符串长度是否是 36 且必须在 A-Z a-z 0-9 范围内。
并且调用 a2 方在进行检测

native 层检测,库文件是:native-lib
查看 JNI_OnLoad
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| jint JNI_OnLoad(JavaVM *vm, void *reserved) { __int64 v4; _QWORD v5[2];
v5[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); sub_1430((__int64)vm, (__int64)reserved); v5[0] = 0LL; if ( (*vm)->GetEnv(vm, (void **)v5, 65540LL) ) return -1; v4 = (*(__int64 (__fastcall **)(_QWORD, const char *))(*(_QWORD *)v5[0] + 48LL))(v5[0], "com/j/swag/MainActivity"); if ( !v4 ) return -1; else return ((*(int (__fastcall **)(_QWORD, __int64, char **, __int64))(*(_QWORD *)v5[0] + 1720LL))( v5[0], v4, off_12098, 1LL) >> 31) | 0x10004; }
|
其中:
1
| v4 = (*(__int64 (__fastcall **)(_QWORD, const char *))(*(_QWORD *)v5[0] + 48LL))(v5[0], "com/j/swag/MainActivity");
|
是在调用:FindClass 函数
1 2 3 4 5
| ((*(int (__fastcall **)(_QWORD, __int64, char **, __int64))(*(_QWORD *)v5[0] + 1720LL))( v5[0], v4, off_12098, 1LL) >> 31) | 0x10004;
|
是在调用:RegisterNatives 函数
其中 RegisterNatives 函数的第 2 个参数表示为注册函数的函数签名信息。
所以我们看 off_12098 就可以找到目标函数。

在这里我们可以得到,这个函数的参数是一个字符串,返回值是布尔类型。
sub_11B4 就是函数的实现
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
| __int64 __fastcall sub_11B4(__int64 a1, __int64 a2, __int64 a3) { unsigned __int8 *v5; _QWORD *v6; _QWORD *v7; unsigned __int64 v8; _DWORD *i; _DWORD *v10; _BYTE v13[4]; __int64 v14;
v14 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); v5 = (unsigned __int8 *)(*(__int64 (__fastcall **)(__int64, __int64, _BYTE *))(*(_QWORD *)a1 + 1352LL))(a1, a3, v13); v6 = sub_D60(v5); sub_F1C(); v7 = sub_10BC((__int64)v6, (__int64)&byte_12008); v8 = 0LL; for ( i = &unk_1674; ; i += 6 ) { v10 = (_DWORD *)v7[v8]; if ( *(i - 3) != *v10 || *(i - 2) != v10[1] || *(i - 1) != v10[2] || *i != v10[3] || i[1] != v10[4] || i[2] != v10[5] ) { break; } if ( v8++ >= 5 ) { (*(void (__fastcall **)(__int64, __int64, unsigned __int8 *))(*(_QWORD *)a1 + 1360LL))(a1, a3, v5); return 1LL; } } return 0LL; }
|
可以改一下函数的数据类型,使得 IDA 能够正常识别到 JNI 函数
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
| __int64 __fastcall sub_11B4(JNIEnv *a1, jobject a2, jstring *a3) { unsigned __int8 *v5; _QWORD *v6; _QWORD *v7; unsigned __int64 v8; _DWORD *i; _DWORD *v10; _BYTE v13[4]; __int64 v14;
v14 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); v5 = (unsigned __int8 *)(*a1)->GetStringUTFChars(a1, a3, v13); v6 = sub_D60(v5); sub_F1C(v6); v7 = sub_10BC((__int64)v6, (__int64)&byte_12008); v8 = 0LL; for ( i = &unk_1674; ; i += 6 ) { v10 = (_DWORD *)v7[v8]; if ( *(i - 3) != *v10 || *(i - 2) != v10[1] || *(i - 1) != v10[2] || *i != v10[3] || i[1] != v10[4] || i[2] != v10[5] ) { break; } if ( v8++ >= 5 ) { (*a1)->ReleaseStringUTFChars(a1, a3, (const char *)v5); return 1LL; } } return 0LL; }
|
v5 就是输入的字符串,由于 Java 层的字符串不能在 c 中直接使用,所以需要使用 GetStringUTFChars 转为 c 字符串。
sub_D60 对 v5 进行操作
函数有点长,函数功能就是对 v5 进行分割,组成一个 6*6 的矩阵,每 6 个元素为一组。
sub_F1C 对 v6 进行操作,v6 就是 sub_D60 函数的结果,是一个 6*6 的矩阵。
sub_F1C 也有点长,函数功能就是对这个 6*6 的矩阵 转置一下,我说白了就是把原来的行转为新的列。
例如:
{ 1, 2, 3, 4, 5, 6}, { 1, 7, 13, 19, 25, 31},
{ 7, 8, 9, 10, 11, 12}, { 2, 8, 14, 20, 26, 32},
{13, 14, 15, 16, 17, 18}, { 3, 9, 15, 21, 27, 33},
{19, 20, 21, 22, 23, 24}, { 4, 10, 16, 22, 28, 34},
{25, 26, 27, 28, 29, 30}, { 5, 11, 17, 23, 29, 35},
{31, 32, 33, 34, 35, 36} { 6, 12, 18, 24, 30, 36}
sub_10BC 函数对这个转置后的矩阵再一次变换
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
| _QWORD *__fastcall sub_10BC(__int64 a1, __int64 a2) { _QWORD *v4; char *v5; void *x9_V8; __int64 v7; __int64 x9_V8_1; _DWORD *v9; __int64 n24;
v4 = malloc(0x30uLL); v5 = (char *)malloc(0x18uLL); *v4 = v5; v4[1] = malloc(0x18uLL); v4[2] = malloc(0x18uLL); v4[3] = malloc(0x18uLL); v4[4] = malloc(0x18uLL); x9_V8 = malloc(0x18uLL); v7 = 0LL; v4[5] = x9_V8; while ( 1 ) { v9 = *(_DWORD **)(a1 + 8 * v7); for ( n24 = 0LL; n24 != 24; n24 += 4LL ) { x9_V8_1 = a2 + 0x48; *(_DWORD *)&v5[n24] = *(_DWORD *)(x9_V8_1 + n24 - 0x48) * *v9 + *(_DWORD *)(x9_V8_1 + n24 - 0x30) * v9[1] + *(_DWORD *)(x9_V8_1 + n24 - 0x18) * v9[2] + *(_DWORD *)(x9_V8_1 + n24) * v9[3] + *(_DWORD *)(x9_V8_1 + n24 + 0x18) * v9[4] + *(_DWORD *)(x9_V8_1 + n24 + 0x30) * v9[5]; } if ( ++v7 == 6 ) break; v5 = (char *)v4[v7]; } return v4; }
|
看不懂,ai 启动!!!

不得不说有 ai 就是方便昂。这搁在以前肯定是做不出来的。
剩下的就是校验了,看看经过两次变换后的矩阵是否与预设的一致。
唯一需要注意的就是取密文的时候要往后 3 个 DWORD

从这里往下 36 个 DWORD。
反解的话交给 ai 就好了
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
| import numpy as np
C = np.array([ [262, 581, 156, 482, 548, 634], [274, 174, 803, 964, 880, 220], [903, 30, 182, 984, 861, 314], [697, 354, 131, 549, 87, 396], [265, 539, 793, 238, 705, 469], [570, 410, 325, 606, 810, 470] ], dtype=np.int64)
R = np.array([ [302999, 215646, 248498, 397573, 404205, 258602], [301119, 208961, 236950, 386884, 392903, 252558], [324152, 225885, 248502, 411075, 413376, 271265], [301955, 220516, 256600, 390644, 405407, 260647], [302954, 218407, 251586, 387387, 402758, 259069], [326574, 234419, 262070, 424569, 426884, 279006], ], dtype=np.int64)
C_inv_float = np.linalg.inv(C) print("C⁻¹ (浮点):\n", C_inv_float)
M_prime_float = np.dot(R, C_inv_float) print("\nM' (浮点):\n", M_prime_float)
M_prime = np.round(M_prime_float).astype(np.int64) print("\nM' (整数,四舍五入):\n", M_prime)
M = M_prime.T print("\nM (原始矩阵):\n", M)
result = ''.join(chr(c) for row in M for c in row) print(result)
|
得到字符串
ningdunbeictfkjbdccbsdcudbcusdcbsdiu
输入进去验证
