freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

JS逆向:RSA加密
2023-12-29 11:34:39

例1

URL:https://www.gm99.com/

对登录界面的password进行逆向分析。

1703752067_658d31835d09521ea8a58.png!small?1703752069356

在这个例子中直接搜索"password:"就可以定位到关键代码,当然也可用其它方法进行定位:

1703752197_658d32050aa00a361b10d.png!small?1703752199139

password就是o,o是通过a.encode(t.password, s)方法产生的,打上断点重新登录,查看参数具体值,其中t.password就是输入的密码,s是时间戳:

1703752437_658d32f5e5d35b43a065f.png!small?1703752439758

查看encode具体代码,发现代码在3模块中:

1703752484_658d33240dba830a9f3af.png!small?1703752486056

1703752517_658d3345bebd75f425311.png!small?1703752519788

可以看到使用了 jsencrypt,并且有 setPublicKey 设置公钥方法,很明显采用的加密方法是RSA加密。在进行逆向分析时需要了解常见加密方法加密过后的密文长度,这样有利于对代码的快速定位。MD5加密后的密文长度是16位或是32位;SHA1加密后的密文长度是40位;如果是其它长度的大概率是RSA加密,但是也有可能是其它加密方法或者编码。在没有混淆代码的情况下,大部分的RSA加密是通过三个步骤进行的:

var o = new JSEncrypt;
o.setPublicKey(""),
o.encrypt();

所以本例子可以通过搜索encrypt,setPublicKey进行快速定位。打上断点继续跟踪encrypt具体代码,该方法是在4模块中:

1703752588_658d338ce7842fd72b22a.png!small?1703752590637

1703752617_658d33a962d86708a5927.png!small?1703752619093

这就是password加密的的大致过程,了解加密过程后就是扒取代码了,先去看JS代码最开始的一部分,发现是代码是模块化的,通过一个加载器加载。call()或者 apply()方法的就是模块加载器,可以通过它们调用模块的方法:

1703753110_658d3596bf530b50ab5fa.png!small?1703753112677

直接复制加载器的相关代码:

1703753273_658d3639c6348da9bf74c.png!small?1703753275728

然后用到哪个模块就复制该模块到后面的大括号中,encode是在3模块中,在3模块中又调用了4模块,所以要复制两个模块。这里的i就是函数加载器,这里的模块是数字,不需要加双引号,如果是字母等形式需要加上,如var r = i("a"):

1703753419_658d36cb73cb3c0576845.png!small?1703753421349

直接复制两个模块,然后创建一个全局变量en导出自执行函数中的方法:

1703753614_658d378e3921e613afd59.png!small?1703753616014

然后创建一个自定义函数进行调用:

1703753960_658d38e8a5d69be793d39.png!small?1703753962367

补全window和navigator不然会报错:

1703754031_658d392f6492dbb2f078e.png!small?1703754033050

补全之后运行,报错ASN1 is not defined:

1703754099_658d3973bfb215eb5de8e.png!small?1703754101599

ASN1是一个JS解码器,一般浏览器内置的,大部分js调试器是没有的。在浏览器里面 window 其实就是 nodejs中的global,在 nodejs 里没有 window,所以在 nodejs 环境中可以将 window 定义为 global,如果定义为空,可能会报错。所以在这里将var window = this改为var window = global,再次运行就不会报错了:

1703755439_658d3eaf47e839e073886.png!small?1703755441438


例2

URL:https://ec.minmetals.com.cn/open/home/purchase-info
刷新页面(采购信息)就可看到param:

1703818408_658e34a80a44a0d4f8938.png!small?1703818409131

可以看到加密过后的数据很长,大概率是RSA加密,尝试搜索setPublicKey定位关键代码:

1703818458_658e34da9d3a65ed6b25f.png!small?1703818460232

给代码尾部打上断点(方便定位代码和观察各个对象的值),重新刷新界面,断点生效,可以判断搜到的代码就是加密代码:

1703818551_658e3537d9d4b893725e1.png!small?1703818553309

在console输出t.encryptLong(JSON.stringify(a)),可以看到每次的结果都是不一样的,输出的值就是密文值:

1703818588_658e355cecd3c1979b086.png!small?1703818590095

t.encryptLong(JSON.stringify(a))中有两个未对象t和a,这时候需要跟踪t和a是怎么来的,其中t涉及两步:

t = new v["a"]

t.setPublicKey(r)

t.setPublicKey(r)是设置公钥,r就是公钥的值,可以在console中输出,需要的时候直接复制:

1703818656_658e35a03a9b5ba31dafe.png!small?1703818657283

t是由v产生,而v= t("9816"):

1703818823_658e3647817b20a907f04.png!small?1703818824734

a是由m方法产生,可以在console输出m方法,点击代码快速定位到m方法:

1703818888_658e36885b2271ac158a6.png!small?1703818889628

1703818936_658e36b86d6ea689b2643.png!small?1703818937628

其中m方法中需要解决e、sign、timeStamp的来源,直接在console中输出e,发现e是一个对象,需要用的时候直接复制即可:

1703819017_658e370924117ec199333.png!small?1703819018326

Sign是由f()(JSON.stringify(e))产生的,JSON.stringify()就是将对象转换成一个字符串,在控制台查看f()(JSON.stringify(e))具体是什么,可以发现是一个32位长度的密文,很可能是一个MD5加密:

1703819168_658e37a0127052066afc0.png!small?1703819169301

随便去网上找一个MD5加密方法对JSON.stringify(e)进行加密,发现加密结果和上面一样,可以确定方法就是一个MD5加密:

1703819234_658e37e2f3ce284ede793.png!small?1703819236315

1703819251_658e37f38b745a1656e8e.png!small?1703819252587

timeStamp就是一个时间戳,可以在console中输出:

1703819299_658e38236608454ea37fd.png!small?1703819300457

接下来需要扒取代码,给t打上断点,刷新页面,选中t直接跟进去:

1703819394_658e38820990cdb89cb38.png!small?1703819395207

发现是一个加载器,很明显是一个webpack,通过加载器加载9816模块:

1703819416_658e3898d3a4e9f2974bb.png!small?1703819417947

查看9816模块的具体代码,发现9816模块又使用到a524模块:

1703819450_658e38bae6e04daa5dac7.png!small?1703819452067

首先把加载器所在的方法复制下来,然后补全模块代码,通过一个全局变量gb导出自执行方法中的方法:

1703819475_658e38d365c8b73178a48.png!small?1703819476634

1703819483_658e38db154f152b49144.png!small?1703819484380

然后在创建一个自定义方法模拟加密过程,其中e是一个对象可以直接从console复制;r是公钥也可以从console复制;f方法是MD5加密方法;这里的gb相当于加载器;然后在复制关键步骤代码:

1703819552_658e39200377fc4b5f6b5.png!small?1703819553315

运行之后报错显示m方法未定义,复制m方法到自定义方法中:

1703819668_658e3994357cea93d8603.png!small?1703819669460

1703819588_658e3944c73a277f80eef.png!small?1703819590133

m方法调用了d方法和b方法需要继续补全:

1703819703_658e39b7456b9c90e782d.png!small?1703819704735

1703819812_658e3a249d39a170d6bc1.png!small?1703819814030

运行过后显示encryptLong方法未定义,定位到encryptLong方法后继续补全:

1703819823_658e3a2f3f61e32406909.png!small?1703819824392

encryptLong方法中又调用了w方法,继续补全w方法,成功运行:

1703819909_658e3a8577c3717477206.png!small?1703819910780

1703819869_658e3a5d52d642df4fbce.png!small?1703819871188


总体代码:


window = this;
var gb;
(function(A){}({
a524: function(e) {},
9816: function(e, t, n) {}
})
function getparem(){
e ={
"inviteMethod": "",
"businessClassfication": "",
"mc": "",
"lx": "ZBGG",
"dwmc": "",
"pageIndex": 1
};
r = '';
function f(string,bit) {}
function b(A, e, t) {}
function w(A) {}
function d(A, e) {}
function m(A) {}

v = gb("9816");
t = new v["a"];
v["a"].prototype.encryptLong = function(A) {}
t.setPublicKey(r);
a = m(m({}, e), {}, {
sign: f(JSON.stringify(e),32),
timeStamp: +new Date
});
s = t.encryptLong(JSON.stringify(a));
return s;
}
a=getparem();
console.log(a)
# web安全 # 逆向分析 # JS加密算法
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录