hellojni_2.0.7.apk
目标:还原signs的加密算法。
点击SIGN2按钮后,下面这个签名会发生改变。
通过定位,发现这个签名是由JNI函数Java_com_example_hellojni_HelloJni_sign2生成的。
进一步,对其中的sub_1CFF0进行hook,发现其第3个参数(从1开始算)便是签名。
hook的代码如下,将其中的addr赋值为1CFF0即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function hook_find_target_address(addr){ var base = Module.findBaseAddress("libhello-jni.so"); console.log("\r\nlibhello-jni.so Baseaddr: " + base);
var target_addr = base.add(addr);
console.log("\r\nTarget Address: " + target_addr);
Interceptor.attach(target_addr, { onEnter(args){ this.arg0 = args[0]; this.arg1 = args[1]; this.arg2 = args[2];
console.log("\r\nsub_" + addr.toString(16) + "args: "); console.log("\r\ninput_str: \r\n" + hexdump(this.arg0)); console.log("\r\ninput_str_length: " + this.arg1); }, onLeave(retval){ console.log("\r\noutput_str: \r\n" + hexdump(this.arg2)); } }) }
|
打印的结果如下图所示。
为了方便分析,这里需要将input_str和input_str_length进行固定。
如图所示,我将输入字符串固定为”1234567890abcdefg”。
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
| function hook_find_target_address(addr){ var base = Module.findBaseAddress("libhello-jni.so"); console.log("\r\nlibhello-jni.so Baseaddr: " + base); var target_addr = base.add(addr); console.log("\r\nTarget Address: " + target_addr); Interceptor.attach(target_addr, { onEnter(args){ this.arg0 = args[0]; this.arg1 = args[1]; this.arg2 = args[2]; console.log("\r\nsub_" + addr.toString(16) + " args: "); console.log("\r\n原始input_str: \r\n" + hexdump(this.arg0, { length: parseInt(this.arg1) })); console.log("\r\ninput_str_length: " + this.arg1); var new_str = "1234567890abcdefg"; Memory.writeUtf8String(args[0], new_str); var length = new_str.length; args[1] = ptr(length); this.args1 = args[1];
console.log("\r\n修改后的长度: " + this.args1);
console.log("\r\n修改后input_str: \r\n" + hexdump(args[0], { length: parseInt(this.args1) })); }, onLeave(retval){ console.log("\r\noutput_str: \r\n" + hexdump(this.arg2, { length: parseInt(this.args1) })); } }); }
|
在确保了输入字符串不变后,尝试使用IDA的trace,追踪函数sub_1CFF0。
先注入frida、再注入IDA server——似乎不按这个顺序,先注入IDA再注入Frida,Frida会注入失败。
我决定先退出Frida的hook,再进行追踪,因为我发现Frida要是已经hook了sub_1CFF0,在sub_1CFF0的函数开始的地方,会存在inline hook,跳转到Frida的跳床函数。
通过IDA trace,走过的汇编指令会变成黄色。

追踪到的内容如下图所示,第一栏的36EE是线程id,第二栏是地址,第三栏开始就是汇编指令了,如果汇编指令修改了寄存器,则会在汇编指令后面,显示寄存器被修改后的值。

之后,我重新生成了一个trace记录,并记录了一下签名值。
之后进行算法逆向。
已知算法的输入是input_str / input_str_length / output_str,分别代表着X0、X1、X2。

因为X2代表着output_str的内存地址,所以追踪X2,判断有哪些指令对地址0x00000079E1720D90上的内容进行了修改。
查找到了34个对0x00000079E1720D90进行读写的位置。
对其中一些指令进行还原,可以猜到。
X8代表:当前正在操作output的第X8个字节,也代表循环次数;
X5代表output的内存地址;
X29[var_64]是一个固定字节数组。
X29[var_68]也是一个固定字节数组。
至于var_6C,最后还是追踪到var_68上。

做了一些补充。

一直追踪[X29,#var_64]。

而一直追踪[X29,#var_68],会发现需要追踪X2,继而追踪X2和X3,继而追踪X3和X7……
最后追踪到[X29,#var_88]。

最后发现,var_88这个变量是一个指针,它将地址给了X2,由X2去获取全局变量,也就是图中的xmmword_79A31EE7B0。

至此,可以将全局变量及涉及到的寄存器改写成c。
(解密的时候前8个字节和后8个字节的处理方式不同,可能还得逆,这里我直接贴别人逆好的代码)
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
| #include <iostream> #include <cstring>
void enc_function(const char* input_str, int input_len, char* result) { const char* table_key1 = "9d9107e02f0f07984956767ab1ac87e5"; const unsigned char table_key2[] = {0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB, 0xF, 0xD9, 0x88, 0xEE, 0x9A, 0xE9, 0x5A}; for (int i = 0; i < input_len; ++i) { unsigned char X2 = input_str[i]; unsigned char key2 = table_key2[(i & 0xF) & 0xFFFFFFFF]; unsigned char W8 = 0xDA; unsigned char W30 = 0x25; unsigned char W2 = X2; unsigned char W7 = W8 & (~W2); W2 = W2 & 0x25; W2 = W7 | W2; unsigned char W3 = key2; W7 = W8 & (~W3); W3 = W3 & W30; W3 = W7 | W3; W2 = W2 ^ W3; W3 = W2;
unsigned char key1 = table_key1[(i ^ 0xFFFFFFF8) & i ]; W2 = key1; W7 = key2; W30 = key2; unsigned char W1 = W2 & (~W3); W3 = W3 & (~W2); unsigned char W5 = W30 & (~W2); W2 = W2 & (~W30); W1 = W1 | W3; W2 = W5 | W2; W1 = W1 + W7; W3 = W1 & (~W2); W1 = W2 & (~W1); W1 = W3 | W1; result[i] = W1; } }
bool test_eq(const char* buf1, const char* buf2, int buf_len) { for (int i = 0; i < buf_len; ++i) { if (buf1[i] != buf2[i]) { return false; } } return true; }
int main() { const char* input = "0123456789abcdef0123456789abcdef"; int len = strlen(input); char* result = (char*)malloc(len); memset(result, 0, len); enc_function(input, len, result); for (int i = 0; i < len; ++i) { printf("%02x", (unsigned char)result[i]); } printf("\r\n%x", test_eq(result, result, len)); free(result); return 0; }
|