分析样例下载:链接: https://pan.baidu.com/s/13iMgWwFattwNS6PmEA16Bg 密码: avtb

练习algorithmbase_70.apk

老样子直接ida打开libnative-lib.so然后frida打印一下输入和输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook_java(){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
native_lib.encodeFromJni_70.implementation=function(input){
var res=this.encodeFromJni_70(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function main(){
hook_java();
}
setImmediate(main)

下面是打印的结果。加密的结果的长度是96。

1
input: dIQoDwUgEuDPctWyFTlCGoenlVCGJWsQkazyUtgkP output: 4EAE672D1C1F5CDB05A90C4D5AEAA417057BF6D0E0082777EEB83F15DD591F54303BF937B7E5F298266D412BAE430DC0

然后我们修改一下这个字符串的特征。例如长度修改一下看看加密的结果有什么不同

1
2
3
input: 000000                                     output: 1B0F4D1A2743DAC43AE630208005759B
input: 000000000000 output: D19C887F85B85FA7718D07ACE365762F
input: 000000000000000000000000 output: 559CFDDC0EFB9178E92DEFAD02ACA99DECB4EB6F7C6A3C2AF24E34BE3615498A

测试了一下后。发现特征。当长度不超过0x10时。加密结果的长度不会变。当长度超过0x10时。则长度发生变化。这个特征和aes、rc2的特征非常像。我们用aes和rc2的加密随便测试一下看看特征

1
2
3
4
5
6
7
8
#rc2
input: 000000 output: 6d31ec02d637b158
input: 000000000000 output: 28c39f84769cabe1df0cbae8524aba1b
input: 000000000000000000000000 output: 28c39f84769cabe1c82b75ff9fd684274ab4dcbf91c63296c4e12ee98c721fc7
#aes
input: 000000 output: a2ddb2109958049280e4b4f1da0ed884
input: 000000000000 output: e4a62440cf35c675a2afc6c0373c526e
input: 000000000000000000000000 output: 7f81b6fe31ddef2804cd2430b950d124523f6f448be52698c1986b956fd26ea2

从特征上面看。这个可能是aes加密。这个时候就要继续静态分析找更多的特征来确定是aes。

先从返回值设置的地方向上找。一路找到sub_177F8函数。然后我们frida来打印下这个函数的参数和返回值。然后发现这个函数最后一次调用的时候。返回值是我们看到的加密结果

1
2
3
4
5
6
onLeave sub_177F8
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77be7952e0 35 35 39 43 46 44 44 43 30 45 46 42 39 31 37 38 559CFDDC0EFB9178
77be7952f0 45 39 32 44 45 46 41 44 30 32 41 43 41 39 39 44 E92DEFAD02ACA99D
77be795300 45 43 42 34 45 42 36 46 37 43 36 41 33 43 32 41 ECB4EB6F7C6A3C2A
input: 000000000000000000000000 output: 559CFDDC0EFB9178E92DEFAD02ACA99DECB4EB6F7C6A3C2AF24E34BE3615498A

然后为了确定是这个函数计算出来的。还是说只是刚好这个时机是有结果的。我们改造一下frida脚本。打印堆栈。并且在入参的指针里面找到同一块内存的位置。在函数进入前就hexdump看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hook_native(){
var base_addr=Module.getBaseAddress("libnative-lib.so");
var sub_177F8=base_addr.add(0x177F8);
Interceptor.attach(sub_177F8,{
onEnter:function(args){
console.log("onEnter sub_177F8");
this.arg0=args[0];
console.log(hexdump(this.arg0.add(0x10).readPointer(),{length:0x30}))
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
},onLeave:function(retval){
console.log("onLeave sub_177F8");
console.log(hexdump(ptr(retval),{length:0x30}))
}
})
}

最后打印的结果可以看到。这个函数进入前。结果就已经有值了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
onEnter sub_177F8
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77be7952e0 35 35 39 43 46 44 44 43 30 45 46 42 39 31 37 38 559CFDDC0EFB9178
77be7952f0 45 39 32 44 45 46 41 44 30 32 41 43 41 39 39 44 E92DEFAD02ACA99D
77be795300 45 43 42 34 45 42 36 46 37 43 36 41 33 43 32 41 ECB4EB6F7C6A3C2A
0x77bea02098 libnative-lib.so!Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_170+0x278
0x77bec1e0cc base.odex!oatexec+0xcc

onLeave sub_177F8
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77be7952e0 35 35 39 43 46 44 44 43 30 45 46 42 39 31 37 38 559CFDDC0EFB9178
77be7952f0 45 39 32 44 45 46 41 44 30 32 41 43 41 39 39 44 E92DEFAD02ACA99D
77be795300 45 43 42 34 45 42 36 46 37 43 36 41 33 43 32 41 ECB4EB6F7C6A3C2A
input: 000000000000000000000000 output: 559CFDDC0EFB9178E92DEFAD02ACA99DECB4EB6F7C6A3C2AF24E34BE3615498A

不过这里同时也看到了调用堆栈。并且也知道了结果值修改估计是通过第一个参数来修改的。因为第一个参数指向的内存里面是有加密结果的指针。所以接着找关于第一个参数v18的使用的地方。接着找到函数sub_12580。继续frida分析一下先。下面贴上frida的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
var sub_12580=base_addr.add(0x12580);
Interceptor.attach(sub_12580,{
onEnter:function(args){
console.log("onEnter sub_12580");
this.arg0=args[0];
this.arg1=args[1];
console.log(hexdump(this.arg0.add(0x10).readPointer(),{length:0x40}));
},onLeave:function(retval){
console.log("onLeave sub_12580");
console.log(hexdump(this.arg0.add(0x10).readPointer(),{length:0x40}));
console.log(hexdump(retval.add(0x10).readPointer(),{length:0x40}));
}
})

然后输出结果如下。可这里猜测第一个参数可能是一个0x20长度的key。然后返回值是加密结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
onEnter sub_12580
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77be3bb740 55 9c fd dc 0e fb 91 78 e9 2d ef ad 02 ac a9 9d U......x.-......
77be3bb750 ec b4 eb 6f 7c 6a 3c 2a f2 4e 34 be 36 15 49 8a ...o|j<*.N4.6.I.
77be3bb760 00 6f 6d 55 74 69 6c 73 2e 6e 65 78 74 49 6e 74 .omUtils.nextInt
77be3bb770 28 69 6e 74 2c 20 69 6e 74 29 00 6c 65 28 29 00 (int, int).le().
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77be7a6f00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77be7a6f10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77be7a6f20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77be7a6f30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
onLeave sub_12580
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77be3bb740 55 9c fd dc 0e fb 91 78 e9 2d ef ad 02 ac a9 9d U......x.-......
77be3bb750 ec b4 eb 6f 7c 6a 3c 2a f2 4e 34 be 36 15 49 8a ...o|j<*.N4.6.I.
77be3bb760 00 6f 6d 55 74 69 6c 73 2e 6e 65 78 74 49 6e 74 .omUtils.nextInt
77be3bb770 28 69 6e 74 2c 20 69 6e 74 29 00 6c 65 28 29 00 (int, int).le().
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77d583f120 35 35 39 43 46 44 44 43 30 45 46 42 39 31 37 38 559CFDDC0EFB9178
77d583f130 45 39 32 44 45 46 41 44 30 32 41 43 41 39 39 44 E92DEFAD02ACA99D
77d583f140 45 43 42 34 45 42 36 46 37 43 36 41 33 43 32 41 ECB4EB6F7C6A3C2A
77d583f150 46 32 34 45 33 34 42 45 33 36 31 35 34 39 38 41 F24E34BE3615498A
input: 000000000000000000000000 output: 559CFDDC0EFB9178E92DEFAD02ACA99DECB4EB6F7C6A3C2AF24E34BE3615498A

然后继续分析上一个函数sub_57514。这里可能是生成key的地方。先hook分析参数和返回值

1
2
3
4
5
6
7
8
9
10
11
12
var sub_57514=base_addr.add(0x57514);
Interceptor.attach(sub_57514,{
onEnter:function(args){
console.log("onEnter sub_57514");
hexdumpMem(args[0])
hexdumpMem(args[1])
hexdumpMem(args[2])
},onLeave:function(retval){
console.log("onLeave sub_57514");
console.log(retval);
}
})

然后贴上结果。看到了另外一串kanxue_imyang___字符串。并且发现我们加密的结果后面填充到了0x20的长度。尾数是08

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
onEnter sub_57514
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bea70080 6b 61 6e 78 75 65 5f 69 6d 79 61 6e 67 5f 5f 5f kanxue_imyang___
77bea70090 00 00 00 00 00 00 00 00 62 61 73 69 63 5f 73 74 ........basic_st
77bea700a0 72 69 6e 67 00 00 00 00 00 00 00 00 00 00 00 00 ring............
77bea700b0 61 6c 6c 6f 63 61 74 6f 72 3c 54 3e 3a 3a 61 6c allocator<T>::al
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bea70080 6b 61 6e 78 75 65 5f 69 6d 79 61 6e 67 5f 5f 5f kanxue_imyang___
77bea70090 00 00 00 00 00 00 00 00 62 61 73 69 63 5f 73 74 ........basic_st
77bea700a0 72 69 6e 67 00 00 00 00 00 00 00 00 00 00 00 00 ring............
77bea700b0 61 6c 6c 6f 63 61 74 6f 72 3c 54 3e 3a 3a 61 6c allocator<T>::al
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77ca65b240 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000
77ca65b250 30 30 30 30 30 30 30 30 08 08 08 08 08 08 08 08 00000000........
77ca65b260 00 6f 6d 55 74 69 6c 73 2e 6e 65 78 74 49 6e 74 .omUtils.nextInt
77ca65b270 28 69 6e 74 2c 20 69 6e 74 29 00 6c 65 28 29 00 (int, int).le().
onLeave sub_57514
0x0
input: 000000000000000000000000 output: 559CFDDC0EFB9178E92DEFAD02ACA99DECB4EB6F7C6A3C2AF24E34BE3615498A

这个函数就很明显像是加密函数了。正好和我们猜测的aes需要的字段相近。参数1应该是key。参数2应该是iv。参数3是输入数据。那么验证一下看看。结果和上面的能对上了。搞定了。

image-20210126224654619