rc4算法 | jsvmp日志分析并还原

【公众号】:小鱼神1024

【作者主页】:小鱼神1024

【知识星球】:小鱼神的逆向编程圈

【擅长领域】:JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等

前言

快速掌握纯算还原的技巧,就是要熟悉标准算法日志特点。上一篇讲到 魔改base64 日志分析,想必伙伴们应该对日志有了一定的熟悉感,接下来我们分析 rc4 算法。

插桩

在js代码中,找到 运算符 位置以及 apply 插桩点,结合插桩日志框架,代码插桩如下:

jsvmp源码:https://t.zsxq.com/oUnbQ

rc4算法 | jsvmp日志分析并还原

日志分析

随着分析的算法难度越来越高,日志的长度也越来越长,这里我们只贴出关键日志,方便大家分析,完整日志可以运行jsvmp源码生成

代码执行后,部分日志如下:

1 :  函数: [Function: apply] 调用者: [Function: fromCharCode] 参数: [ null, [ 0.00390625, 1, 8 ] ] 结果: 
第 2 :  0 + 0 ====> 03 :  0 % 3 ====> 04 :  函数: [Function: charCodeAt] 调用者:  参数: [ 0 ] 结果: 05 :  0 + 0 ====> 06 :  0 % 256 ====> 07 :  0 + 1 ====> 18 :  1 % 3 ====> 19 :  函数: [Function: charCodeAt] 调用者:  参数: [ 1 ] 结果: 110 :  1 + 1 ====> 211 :  2 % 256 ====> 212 :  2 + 1 ====> 313 :  2 % 3 ====> 214 :  函数: [Function: charCodeAt] 调用者:  参数: [ 2 ] 结果: 815 :  3 + 8 ====> 1116 :  11 % 256 ====> 1117 :  11 + 3 ====> 1418 :  3 % 3 ====> 019 :  函数: [Function: charCodeAt] 调用者:  参数: [ 0 ] 结果: 020 :  14 + 0 ====> 1421 :  14 % 256 ====> 1422 :  14 + 4 ====> 1823 :  4 % 3 ====> 124 :  函数: [Function: charCodeAt] 调用者:  参数: [ 1 ] 结果: 125 :  18 + 1 ====> 1926 :  19 % 256 ====> 1927 :  19 + 5 ====> 2428 :  5 % 3 ====> 229 :  函数: [Function: charCodeAt] 调用者:  参数: [ 2 ] 结果: 830 :  24 + 8 ====> 3231 :  32 % 256 ====> 3232 :  32 + 6 ====> 3833 :  6 % 3 ====> 034 :  函数: [Function: charCodeAt] 调用者:  参数: [ 0 ] 结果: 035 :  38 + 0 ====> 3836 :  38 % 256 ====> 3837 :  38 + 7 ====> 4538 :  7 % 3 ====> 139 :  函数: [Function: charCodeAt] 调用者:  参数: [ 1 ] 结果: 140 :  45 + 1 ====> 4641 :  46 % 256 ====> 4642 :  46 + 8 ====> 5443 :  8 % 3 ====> 244 :  函数: [Function: charCodeAt] 调用者:  参数: [ 2 ] 结果: 845 :  54 + 8 ====> 6246 :  62 % 256 ====> 6247 :  62 + 9 ====> 7148 :  9 % 3 ====> 049 :  函数: [Function: charCodeAt] 调用者:  参数: [ 0 ] 结果: 050 :  71 + 0 ====> 7151 :  71 % 256 ====> 7152 :  71 + 10 ====> 8153 :  10 % 3 ====> 154 :  函数: [Function: charCodeAt] 调用者:  参数: [ 1 ] 结果: 1

其中:

1 :  函数: [Function: apply] 调用者: [Function: fromCharCode] 参数: [ null, [ 0.00390625, 1, 8 ] ] 结果: 

这个日志,轻松还原代码如下:

const key = String.fromCharCode.apply(null, [0.00390625,1,8])

然后往下分析:

3 :  0 % 3 ====> 08 :  1 % 3 ====> 113 :  2 % 3 ====> 218 :  3 % 3 ====> 023 :  4 % 3 ====> 1
...1273 :  254 % 3 ====> 21278 :  255 % 3 ====> 0

从这几条日志里,可以推断出,这部分代码是一个循环,而且每次递增 1, 从 0 循环到 255

继续分析:

6 :  0 % 256 ====> 07 :  0 + 1 ====> 111 :  2 % 256 ====> 212 :  2 + 1 ====> 316 :  11 % 256 ====> 1117 :  11 + 3 ====> 1421 :  14 % 256 ====> 1422 :  14 + 4 ====> 18

从这几条日志里,可以发现,每次 % 256 后作为循环的结束,同时得到的值作为下一次循环的起始值。综合这些,尝试写出循环代码:

  var s = [];
  for (var i = 0; i < 256; i++) {
    s[i] = i;
  }
  var j = 0;
  for (var i = 0; i < 256; i++) {
    j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
    var temp = s[i];
    s[i] = s[j];
    s[j] = temp;
  }

继续分析:

1282 :  0 + 1 ====> 11283 :  1 % 256 ====> 11284 :  0 + 21 ====> 211285 :  21 % 256 ====> 211286 :  216 + 21 ====> 2371287 :  237 % 256 ====> 2371288 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 0 ] 结果: 771289 :  58 ^ 77 ====> 1191290 :  函数: [Function: fromCharCode] 调用者: [Function: String] 参数: [ 119 ] 结果: w
第 1291 :  函数: [Function: push] 调用者: [ 'w' ] 参数: [ 'w' ] 结果: 11292 :  1 + 1 ====> 21293 :  2 % 256 ====> 21294 :  21 + 11 ====> 321295 :  32 % 256 ====> 321296 :  81 + 11 ====> 921297 :  92 % 256 ====> 921298 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 1 ] 结果: 1111299 :  224 ^ 111 ====> 1431300 :  函数: [Function: fromCharCode] 调用者: [Function: String] 参数: [ 143 ] 结果: 
第 1301 :  函数: [Function: push] 调用者: [ 'w', '\x8F' ] 参数: [ '\x8F' ] 结果: 21302 :  2 + 1 ====> 31303 :  3 % 256 ====> 31304 :  32 + 14 ====> 461305 :  46 % 256 ====> 461306 :  70 + 14 ====> 841307 :  84 % 256 ====> 841308 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 2 ] 结果: 1221309 :  211 ^ 122 ====> 1691310 :  函数: [Function: fromCharCode] 调用者: [Function: String] 参数: [ 169 ] 结果: ©
第 1311 :  函数: [Function: push] 调用者: [ 'w', '\x8F', '©' ] 参数: [ '©' ] 结果: 31312 :  3 + 1 ====> 41313 :  4 % 256 ====> 41314 :  46 + 100 ====> 1461315 :  146 % 256 ====> 1461316 :  47 + 100 ====> 1471317 :  147 % 256 ====> 1471318 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 3 ] 结果: 1051319 :  37 ^ 105 ====> 761320 :  函数: [Function: fromCharCode] 调用者: [Function: String] 参数: [ 76 ] 结果: L1321 :  函数: [Function: push] 调用者: [ 'w', '\x8F', '©', 'L' ] 参数: [ 'L' ] 结果: 41322 :  4 + 1 ====> 51323 :  5 % 256 ====> 51324 :  146 + 138 ====> 2841325 :  284 % 256 ====> 281326 :  193 + 138 ====> 3311327 :  331 % 256 ====> 751328 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 4 ] 结果: 1081329 :  198 ^ 108 ====> 1701330 :  函数: [Function: fromCharCode] 调用者: [Function: String] 参数: [ 170 ] 结果: ª
第 1331 :  函数: [Function: push] 调用者: [ 'w', '\x8F', '©', 'L', 'ª' ] 参数: [ 'ª' ] 结果: 51332 :  5 + 1 ====> 61333 :  6 % 256 ====> 61334 :  28 + 38 ====> 661335 :  66 % 256 ====> 661336 :  33 + 38 ====> 711337 :  71 % 256 ====> 711338 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 5 ] 结果: 108

当上述循环结束后,好像又进入了另外一个循环:

1288 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 0 ] 结果: 771298 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 1 ] 结果: 1111308 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 2 ] 结果: 122

...2388 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 110 ] 结果: 54

从参数 0110 可以看出,它也是一个循环,而且每次递增 1

110 代表什么呢?为啥会循环到 110 呢?

经过测试发现,Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 的长度刚好为 111,因为从 0 开始,所以循环到 110 就结束了。

我们发现,分析纯算时,不要盲目按部就班直接运算还原,一定要看是否有循环,如果有,找到循环开始和结束尤其重要。

继续分析:

1282 :  0 + 1 ====> 11283 :  1 % 256 ====> 11284 :  0 + 21 ====> 211285 :  21 % 256 ====> 211286 :  216 + 21 ====> 2371287 :  237 % 256 ====> 2371288 :  函数: [Function: charCodeAt] 调用者: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 参数: [ 0 ] 结果: 771289 :  58 ^ 77 ====> 1191290 :  函数: [Function: fromCharCode] 调用者: [Function: String] 参数: [ 119 ] 结果: w
第 1291 :  函数: [Function: push] 调用者: [ 'w' ] 参数: [ 'w' ] 结果: 1

这个就是第一次完整循环的日志,这个也比较简单,唯一比较难理解的地方是:

1284 :  0 + 21 ====> 21

其中 21 从哪里来的呢?

对于这种情况,对于缺少经验的伙伴,是非常头疼的了。

还有上述数组交换位置,也是比较难理解的。

这就需要用到监听数组访问和赋值的技巧了。

为了解决这个普遍的通用问题,我封装一个函数配合插桩框架使用,很容易就能找到出 21 是从哪里来的以及如何交换位置的。

多维数组代理-监听数组访问和赋值:https://t.zsxq.com/HQqm9

好了,基于上述日志,我们就可以还原出整个 rc4 加密函数了:

function rc4(plaintext, key) {
  var s = [];
  for (var i = 0; i < 256; i++) {
    s[i] = i;
  }
  var j = 0;
  for (var i = 0; i < 256; i++) {
    j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
    var temp = s[i];
    s[i] = s[j];
    s[j] = temp;
  }

  var i = 0;
  var j = 0;
  var cipher = [];
  for (var k = 0; k < plaintext.length; k++) {
    i = (i + 1) % 256;
    j = (j + s[i]) % 256;
    var temp = s[i];
    s[i] = s[j];
    s[j] = temp;
    var t = (s[i] + s[j]) % 256;
    cipher.push(String.fromCharCode(s[t] ^ plaintext.charCodeAt(k)));
  }
  return cipher.join("");
}

const key = String.fromCharCode.apply(null, [0.00390625, 1, 8]); // 密钥
const plaintext =
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; // 明文

// 加密
const ciphertext = rc4(plaintext, key);
console.log("Ciphertext:", ciphertext);

rc4日志特点总结

  • 有两个循环,第一个循环是从 0255,第二个循环是从 0明文长度

  • 第一个循环中,每次结束都是以 % 256 结束,得到的值作为下一次循环的起始值。

  • 第二个循环中,每次结束都是以两个数字 _ ^ _ 结束。

满足这几个条件,基本可以尝试套用 rc4 算法模版了。

为了加深 rc4 算法和其变种算法特点,下一篇,我们再分析一个 rc4 变种算法。

在这里插入图片描述

03-26
### 逆向工程与反编译概述 逆向工程是一种通过对软件的目标代码进行分析,将其转化为更高级别的表示形式的过程。这一过程通常用于研究现有系统的内部结构、功能以及实现细节。在Java和Android领域,反编译工具被广泛应用于逆向工程中。 #### Java逆向工程中的Jad反编译工具 Jad是一款经典的Java反编译工具,能够将`.class`字节码文件转换为可读的`.java`源代码[^1]。虽然它可能无法完全恢复原始源代码,但它提供了足够的信息来帮助开发者理解已编译的Java程序逻辑。Jad支持多种反编译模式,允许用户自定义规则以适应不同的需求。此外,其命令行接口和图形界面使得复杂代码的分析变得更加便捷。 #### Android逆向工程中的JEB反编译工具 针对Android应用的逆向工程,JEB是由PNF Software开发的一款专业级工具[^2]。相较于其他同类产品,JEB不仅具备强大的APK文件反编译能力,还能对Dalvik字节码执行高效而精准的操作。它的核心优势在于以下几个方面: - **广泛的平台兼容性**:除Android外,还支持ARM、MIPS等多种架构的二进制文件反汇编。 - **混淆代码解析**:内置模块能有效应对高度混淆的代码,提供分层重构机制以便于深入分析。 - **API集成支持**:允许通过编写Python或Java脚本来扩展功能完成特定任务。 #### APK反编译流程及其意义 当涉及到具体的APK包时,可以通过一系列步骤提取其中的信息来进行全面的安全评估或者学习目的的研究工作[^3]。这些步骤一般包括但不限于获取资产目录(`assets`)内的资源数据;解密XML配置文档如`AndroidManifest.xml`定位应用程序启动点;最后利用上述提到的各种专用软件重现整个项目框架供进一步探讨。 ```bash # 使用apktool反编译APK示例 apktool d your_app.apk -o output_directory/ ``` 以上命令展示了如何借助开源工具ApkTool轻松拆卸目标安卓档案至易于探索的状态下。 ### 结论 无论是传统的桌面端还是现代移动端环境里头,恰当运用合适的反编译解决方案都是达成逆向工程项目成功不可或缺的一环。每种工具有各自专精之处,在实际应用场景当中应当依据具体需求做出明智的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值