赞
踩
各位聪明绝顶,才高八斗的读者们你们好!今天我们主要讨论编译之后的RC4算法识别。
题外话,之前看到一个蛋疼的小知识,说“势”这个字最好不好查词典释义。我是很好奇的,果然后来无法直视势不可挡这个成语。
言归正传,我们将上一篇的Java与C的代码都编译一下,分别反编译看看。
Java版的就很好识别,几乎与源码没啥区别。
这里我们先给出RC4加密算法在逆向分析过程中的快速识别经验:
首先判断明文和密文长度是否相等,等长则代表是序列密码。
接下来判断是否是RC4。RC4算法中的初始化算法(KSA)中有两轮非常显著的长度为256的循环体,用于根据给定的key生成s盒。伪随机子密码生成算法(PRGA)会根据上一步得到的扰乱的s盒,进一步生成子密钥流,最终和给定的明文进行逐字节的异或。
由于 so 是可以加 ollvm 的,而且还可以做字符混淆,所以逆向起来不会像Java那么容易,在我们找到一个怀疑对象函数之后,就可以通过编写frida脚本或xposed插件完成对相应函数的主动调用,判断当输入明文为任意长度时的密文长度。
关于 ndk 的混淆是有很多文章的,自己可以尝试一下控制流混淆与字符串混淆。
我们看一个混淆之后的例子:
sub_F658(&unk_38008, dest);
unk_38008
实际上是一个字符串,不过这个字符串被混淆了,在 init
阶段后才会被解密。
看里面的逻辑:
- sub_F0AC((__int64)v7, (__int64)a1, v4);
- v2 = strlen(a2);
- return sub_F3C4(v7, a2, v2);
有2个函数比较可疑,先看 sub_F0AC
:
先看1处,有一个很明显的交换数组元素的逻辑。
再看2处,只有当 v7 = 915845509 的时候才会交换逻辑,循环次数是 256 次,这些都与 RC4 的 init 阶段是一样。
当然这里也只是大胆猜测,而且混淆级别开的没那么高,所以还能看清楚一定的逻辑。实际上这里就已经很难看出来第一个给 S 盒赋值的循环在哪里了,只能根据 result 的位置来看。
后面我们可以使用hook来验证我们的想法。
再看 sub_F3C4
函数:
看 1,2处,发现循环会执行的次数与第3个参数一样,看前面的逻辑:
这就是说,循环次数是第二个字符串的长度。配合第3处的异或,也可以合理怀疑这里就是加密函数。
sub_F0AC((__int64)v7, (__int64)a1, v4);
通过上面的分析,第一个参数是返回值,第二个参数是密钥,第三个参数是密钥长度。所以返回的是一个密钥流。
sub_F3C4(v7, a2, v2);
第一个参数是密钥流,第二个参数是明文,第三个参数是明文的长度。返回值(第一个参数)是加密后的结果。
sub_F658()
所以,这个函数就是一个 RC4 算法,我们 hook 这些函数,观察参数与结果。
- export function invoke_sub_F658(arg1: string, arg2: string) {
- var offset = 0xF658;
- var module = Process.getModuleByName("libnative-lib.so");
- var sub_address = module.base.add(offset);
-
- var arg1_address = Memory.alloc(10);
- let arg2_address = Memory.alloc(10);
- arg1_address.writeUtf8String(arg1);
- arg2_address.writeUtf8String(arg2);
-
- var hooked_sub_f658 = new NativeFunction(sub_address, 'pointer', ['pointer', 'pointer']);
- var result = hooked_sub_f658(arg1_address, arg2_address);
- console.log("result:", hexdump(result));
- }
-
- function hook_libnativelib() {
- var nativelibmodule = Process.getModuleByName("libnative-lib.so");
- var subf0ac = nativelibmodule.base.add(0xf0ac);
- var subf3c4 = nativelibmodule.base.add(0xf3c4);
- Interceptor.attach(subf0ac, {
- onEnter: function (args) {
- console.log("secret key: ", hexdump(args[1]), '\n length:', args[2]);
- }, onLeave: function () {
- }
- });
- Interceptor.attach(subf3c4, {
- onEnter: function (args) {
- this.arg1 = args[1];
- console.log("input: ", hexdump(args[1]), args[2]);
- }, onLeave: function () {
- console.log("output: ", hexdump(this.arg1));
- }
- });
-
- }
-
- function main() {
- if (Java.available) {
- Java.perform(function () {
- console.log("go into main");
- var RuntimeClass = Java.use('java.lang.Runtime');
- RuntimeClass.loadLibrary0.implementation = function (arg0: object, arg1: string) {
- var result = this.loadLibrary0(arg0, arg1);
- console.log("Runtime.loadLibrary0:", arg1);
- if (arg1.indexOf('native-lib') != -1) {
- hook_libnativelib();
- }
-
- return result;
- }
- })
- }
- }
-
- rpc.exports = {
- invokesubf658: invoke_sub_F658
- };
-
- setImmediate(main);
-
注意,我们hook so 的加载选择的是 Runtime
类,这是因为直接hook System
类会导致 Reflection.getCallerClass()
的值出问题。还有就是我的手机系统换到了 Android 10,注意自行查看源码区别。
hook 逻辑也很简单,就不细说了,看一下就明白。
注入脚本,看一下输出:
- ┌──(root㉿kali)-[~/workspace/frida-agent-example]
- └─# frida -U -f com.kanxue.rc4 --no-pause -l _agent.js
- secret key:
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 724f339008 72 63 34 74 65 73 74 00 31 32 33 34 35 36 37 38 rc4test.12345678
可以看到,secret key 是 rc4test
。
- input:
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 7fe568aa10 31 32 33 34 35 36 37 38 00 00 00 00 00 00 00 00 12345678........
加密的明文是 12345678
。
加密后的结果是:
- output:
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 7fe568aa10 a6 df 7a 5d dd ea 97 47 00 00 00 00 00 00 00 00 ..z]...G........
从 a6 到 47 这8个字节。
我们也可以主动调用函数,反复确认输入与输出是否一样长。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。