当前位置:   article > 正文

常见加解密算法03 - RC4逆向认识

常见加解密算法03 - RC4逆向认识

各位聪明绝顶,才高八斗的读者们你们好!今天我们主要讨论编译之后的RC4算法识别。

题外话,之前看到一个蛋疼的小知识,说“势”这个字最好不好查词典释义。我是很好奇的,果然后来无法直视势不可挡这个成语。

言归正传,我们将上一篇的Java与C的代码都编译一下,分别反编译看看。

Java版

Java版的就很好识别,几乎与源码没啥区别。

C版

这里我们先给出RC4加密算法在逆向分析过程中的快速识别经验:

  1. 首先判断明文和密文长度是否相等,等长则代表是序列密码。

  2. 接下来判断是否是RC4。RC4算法中的初始化算法(KSA)中有两轮非常显著的长度为256的循环体,用于根据给定的key生成s盒。伪随机子密码生成算法(PRGA)会根据上一步得到的扰乱的s盒,进一步生成子密钥流,最终和给定的明文进行逐字节的异或。

由于 so 是可以加 ollvm 的,而且还可以做字符混淆,所以逆向起来不会像Java那么容易,在我们找到一个怀疑对象函数之后,就可以通过编写frida脚本或xposed插件完成对相应函数的主动调用,判断当输入明文为任意长度时的密文长度。

关于 ndk 的混淆是有很多文章的,自己可以尝试一下控制流混淆与字符串混淆。

我们看一个混淆之后的例子:

  sub_F658(&unk_38008, dest);

unk_38008实际上是一个字符串,不过这个字符串被混淆了,在 init 阶段后才会被解密。

看里面的逻辑:

  1.   sub_F0AC((__int64)v7, (__int64)a1, v4);
  2.   v2 = strlen(a2);
  3.   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处的异或,也可以合理怀疑这里就是加密函数。

Hook验证

 sub_F0AC((__int64)v7, (__int64)a1, v4);

通过上面的分析,第一个参数是返回值,第二个参数是密钥,第三个参数是密钥长度。所以返回的是一个密钥流。

sub_F3C4(v7, a2, v2);

第一个参数是密钥流,第二个参数是明文,第三个参数是明文的长度。返回值(第一个参数)是加密后的结果。

sub_F658()

所以,这个函数就是一个 RC4 算法,我们 hook 这些函数,观察参数与结果。

  1. export function invoke_sub_F658(arg1string, arg2string) {
  2.     var offset = 0xF658;
  3.     var module = Process.getModuleByName("libnative-lib.so");
  4.     var sub_address = module.base.add(offset);
  5.     var arg1_address = Memory.alloc(10);
  6.     let arg2_address = Memory.alloc(10);
  7.     arg1_address.writeUtf8String(arg1);
  8.     arg2_address.writeUtf8String(arg2);
  9.     var hooked_sub_f658 = new NativeFunction(sub_address'pointer', ['pointer''pointer']);
  10.     var result = hooked_sub_f658(arg1_address, arg2_address);
  11.     console.log("result:", hexdump(result));
  12. }
  13. function hook_libnativelib() {
  14.     var nativelibmodule = Process.getModuleByName("libnative-lib.so");
  15.     var subf0ac = nativelibmodule.base.add(0xf0ac);
  16.     var subf3c4 = nativelibmodule.base.add(0xf3c4);
  17.     Interceptor.attach(subf0ac, {
  18.         onEnter: function (args) {
  19.             console.log("secret key: ", hexdump(args[1]), '\n length:', args[2]);
  20.         }, onLeave: function () {
  21.         }
  22.     });
  23.     Interceptor.attach(subf3c4, {
  24.         onEnter: function (args) {
  25.             this.arg1 = args[1];
  26.             console.log("input: ", hexdump(args[1]), args[2]);
  27.         }, onLeave: function () {
  28.             console.log("output: ", hexdump(this.arg1));
  29.         }
  30.     });
  31. }
  32. function main() {
  33.     if (Java.available) {
  34.         Java.perform(function () {
  35.             console.log("go into main");
  36.             var RuntimeClass = Java.use('java.lang.Runtime');
  37.             RuntimeClass.loadLibrary0.implementation = function (arg0object, arg1string) {
  38.                 var result = this.loadLibrary0(arg0, arg1);
  39.                 console.log("Runtime.loadLibrary0:", arg1);
  40.                 if (arg1.indexOf('native-lib') != -1) {
  41.                     hook_libnativelib();
  42.                 }
  43.                 return result;
  44.             }
  45.         })
  46.     }
  47. }
  48. rpc.exports = {
  49.     invokesubf658invoke_sub_F658
  50. };
  51. setImmediate(main);

注意,我们hook so 的加载选择的是 Runtime 类,这是因为直接hook System 类会导致 Reflection.getCallerClass() 的值出问题。还有就是我的手机系统换到了 Android 10,注意自行查看源码区别。

hook 逻辑也很简单,就不细说了,看一下就明白。

注入脚本,看一下输出:

  1. ┌──(root㉿kali)-[~/workspace/frida-agent-example]
  2. └─# frida -U -f com.kanxue.rc4 --no-pause -l _agent.js 
  1. secret key:
  2.               0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
  3. 724f339008  72 63 34 74 65 73 74 00 31 32 33 34 35 36 37 38  rc4test.12345678

可以看到,secret key 是 rc4test

  1. input:
  2.               0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
  3. 7fe568aa10  31 32 33 34 35 36 37 38 00 00 00 00 00 00 00 00  12345678........

加密的明文是 12345678

加密后的结果是:

  1. output:
  2.               0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
  3. 7fe568aa10  a6 df 75d dd ea 97 47 00 00 00 00 00 00 00 00  ..z]...G........

从 a6 到 47 这8个字节。

我们也可以主动调用函数,反复确认输入与输出是否一样长。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/600334
推荐阅读
相关标签
  

闽ICP备14008679号