Akamai本地字符串解密

admin 2025-12-22 04:22:25 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了Akamai的字符串混淆机制,包括入口函数初始化过程、6个解混淆函数的工作原理以及YO数组作为关键解密元素的作用。作者指出akm采用首次调用解密并缓存的方式,一旦解密失败会导致整个系统崩溃。文章提供了通过AST快速提取特征函数的思路,特别强调了Cp8等于11的切入点以及YO.push操作的重要性,为实现自动化解密提供了技术路径。 综合评分: 85 文章分类: 逆向分析,WEB安全,漏洞分析,代码审计,安全工具


cover_image

Akamai本地字符串解密

原创

OOO

Frida and So

2025年12月8日 16:06 上海

声明

本文仅供学习交流,如有侵权联系删除!!

目标

实现akm本地解字符串混淆。

预处理

Akm的混淆文件除了变量名,逻辑基本不变。

先对常量进行赋值还原,然后对入口函数控制流还原,再将RS函数内常量赋值还原,完成以上操作后便可以正式跟踪分析了。

初步分析

入口函数其实就是在进行解混淆的初始化,RS前声明的函数基本都要在下面的call内重写为真正的解混淆方法,下面6个混淆字符串数组意味着存在6个解混淆函数,sl8是一个100多位的函数名字符串数组,YO贯穿全文解混淆的关键数组,call调用给每个解混淆迭代器绑定sl8内函数名,并且为每一个解混淆函数生成一个独特的字符串,异或的key现在看不懂,跟着走一遍就懂了。

hS的解混淆函数初始化为例,首先进入case 45分支,在vB中完成解混淆函数的初始化,然后遍历sl8()数组完成解混淆函数绑定。

vB调用进入Yccase 331分支,给YO放入关键数,重写vB,进入kD函数。

kD调用进入了Yccase 43分支,MK函数的case 6分支也只是做了些判断进行String.fromCharCode,最后将生成的字符串传入f2

重写kD方法,取出字符串,传入重写后的vB,也就是进入dlcase 46分支。

对字符串进行了重排序操作,将其赋值给了kD函数的Tk字段,完成后,一路返回,来到CP8循环处。

如下代码段,首次调用时解密字符串并缓存,之后直接返回缓存结果。这就是为什么akm一旦解密失败一次,整个就完蛋了。

var Cp8 = 0;
while&nbsp;(Cp8 < Dp8.length) {
&nbsp; &nbsp;&nbsp;if(Cp8 ==&nbsp;11){
&nbsp; &nbsp; &nbsp; &nbsp; Q6()[Dp8[Cp8]]=&nbsp;function&nbsp;()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;SZ.apply(this, [15,&nbsp;arguments]);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }else{
&nbsp; &nbsp; &nbsp; &nbsp; Q6()[Dp8[Cp8]] =&nbsp;function&nbsp;()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;var&nbsp;kv8 = Dp8[Cp8];
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnfunction&nbsp;(WQ8, Y98)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//重点,每次解混淆函数,如Q6()[YB(27)].call(null, 129, 1255),YB(27)返回索引,只有第一次 &nbsp; &nbsp;调用该索引触发使用kD函数进行解密
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;var&nbsp;hr8 = kD(WQ8, Y98);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Q6()[kv8] =&nbsp;function&nbsp;()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//缓存结果
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;hr8;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;hr8;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp; }();
&nbsp; &nbsp; }
&nbsp; &nbsp; ++Cp8;
}
//Q6用闭包创建并返回一个全局唯一的空对象(缓存容器),之后再次调用 Q6 时直接返回该同一个对象。
function&nbsp;Q6()&nbsp;{
&nbsp; &nbsp;&nbsp;var&nbsp;SR1 = []['keys']();
&nbsp; &nbsp; Q6 =&nbsp;function&nbsp;()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;SR1;
&nbsp; &nbsp; };
&nbsp; &nbsp;&nbsp;return&nbsp;SR1;
}

解混淆分析

kD在初始化的时候被重写了,直接跟入L78case 40分支,用到了前面的kD.Tk,也用到了YO数组的最后一位。

思路

到这里,akm怎么解混淆的已经被解读完了,通过AST快速提取这些特征函数,前面的Cp8 == 11就是重新初始化kD.Tk这种字符串的,是一个不错的切入点,点进去后,第一句kD.Tk,而且你直接搜xor刚好6个,每一个都是xx.xx = 混淆数组[xx],除此之外,还有不同解密函数%的值是不同的,最恶心其实是解密前一定会YO.push(值),毕竟YO起名都是global_subkey

Q6()[Dp8[Cp8]]=&nbsp;function&nbsp;()&nbsp;{
&nbsp; &nbsp;&nbsp;return&nbsp;SZ.apply(this, [15,&nbsp;arguments]);
}

比如下面这段,我演示一下YO.push与不push的情况,很明显不去搞这个YO一定解密失败,要实现AST自动解,就得完成上面的操作,剩下的就是如何去编写AST了,额外注意一下索引 11 这个根本不会调用,思路有了,编写还不简单吗 ?。


查看原文:《Akamai本地字符串解密》

评论:0   参与:  2