本文详细解析了JavaScript代码混淆与加解密方式,通过Python程序对网页POST请求进行抓包和代码审计,发现sign字段采用DES加密算法。文章提供了利用CryptoJS库进行加密及Python进行解密的完整代码示例,并总结了多种常见JavaScript混淆代码的解密技巧,包括颜文字、特殊字符编码及eval()编码的处理方法。
JavaScript代码混淆与加解密技术解析
开发Python程序,向指定网页发送POST请求,解析响应数据并存储至数据库以支持后续分析。
网络抓包分析
使用Burp Suite进行抓包,获取网页原始POST请求结构如下:
出于隐私保护考虑,部分字段值已做脱敏处理
uid=487f764baab641be902f4dxxxxxxxxx×tamp=1596436610125&sign=iNtdr75WKc7VHmbn8pu4VLqddoLEhupmin6LSi8vj%2Bn%2FyJ0zHlu34LVqQojiN4K6OoB6ggz0Q5NSx6rxxxxxxx%3D%3Duid代表用户唯一标识,timestamp为当前时间戳,sign字段含义待进一步分析。
在Burp中使用Repeat模块重发请求,修改timestamp字段后无法获取响应,推测sign为验证标识。
保持原时间戳发送可获取响应,但随时间推移,旧时间戳失效,无法再获取响应。
逻辑分析如下:
网站为防止DDoS攻击实施请求验证,通过加密算法对用户ID和时间戳进行加密后填入sign字段。服务器端解密验证,通过则返回响应,否则丢弃请求。
timestamp字段用于判断sign有效期,超时后sign失效。
前端代码审计
查看网页源代码,发现加密后的JavaScript代码:
<!-- 加密start-->
<script src="js/httpUtil.min.js"></script>
<script type="text/javascript" src="plugins/CryptoJSv3.1.2/rollups/tripledes.js"></script>
<script type="text/javascript" src="plugins/CryptoJSv3.1.2/components/mode-ecb-min.js"></script>
<script src="plugins/bootstrap-table-1.11.0/bootstrap-table.min.js"></script>
<script src="plugins/bootstrap-table-1.11.0/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="plugins/jquery.tmpl.min.js"></script>
<script src="plugins/DESUtil.min.js"></script>定位到最后一个JS文件,内容如下:
eval(function(p, a, c, k, e, d) {
e = function(c) {
return (c < a ? "": e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
};
if (!''.replace(/^/, String)) {
while (c--) d[e(c)] = k[c] || e(c);
k = [function(e) {
return d[e]
}];
e = function() {
return '\\w+'
};
c = 1;
};
while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
return p;
} ('3 T(e,t,n){t=G==t||""==t||D 0===t?{A:(9 b).B()}:t;5 r=(9 b).x();t.v=r;5 o=f(d(t));t.u=o,$.s({w:"l",i:t,J:e,P:"R",m:3(e){},p:3(e){n(e)},k:3(e,t){},z:3(e){}})}3 Q(e,t,n){t=G==t||""==t||D 0===t?{A:(9 b).B()}:t;5 r=(9 b).x();t.v=r;5 o=f(d(t));t.u=o,$.s({w:"l",i:t,J:e,14:!1,10:!1,11:!1,m:3(e){},p:3(e){n(e)},k:3(e,t){},z:3(e){}})}5 c="12",7="Z";3 f(e){5 t=2.4.8.6(c),n=2.4.8.6(7);h y=2.j.13(e,t,{7:n,a:2.a.E,I:2.L.O}),2.4.q.U(y.K)}3 W(e){e=2.4.q.6(e).N();5 t=2.4.8.6(c),n=2.4.8.6(7);h 2.j.S({K:2.4.V.6(e)},t,{7:n,a:2.a.E,I:2.L.O}).N(2.4.8)}3 d(e){5 t="",n=[];H(M C e)n[n.F]=M;H(g C n.Y(),n)t=t+n[g]+"="+e[n[g]]+",";h t=t.X(0,t.F-1)}', 62, 67, '||CryptoJS|function|enc|var|parse|iv|Utf8|new|mode|Date|key|mapSortToString||encryptByDESModeCBC|value|return|data|DES|complete|post|beforeSend|||success|Base64||ajax||sign|timestamp|type|valueOf|encrypted|error|date|getTime|in|void|CBC|length|null|for|padding|url|ciphertext|pad|thisKey|toString|Pkcs7|dataType|ajaxDESWebUploadImg|json|decrypt|ajaxDESWeb|stringify|Hex|decryptByDESModeCBC|substring|sort|asdewqrf|processData|contentType|keycansr|encrypt|cache'.split('|'), 0, {}))观察到以下关键代码片段
while(c–)if(k[c])p=p.replace(new RegExp('\b'+e©+'\b',‘g'),k[c]);return p;此为标准的JavaScript混淆、加密及压缩处理
使用document.getElementById(‘textareaID’).innerText=p; 替换原return p;语句
将代码嵌入普通HTML文件的body部分打开,还原出JavaScript代码如下:
function ajaxDESWeb(e, t, n) {
t = null == t || "" == t || void 0 === t ? {
date: (new Date).getTime()
}: t;
var r = (new Date).valueOf();
t.timestamp = r;
var o = encryptByDESModeCBC(mapSortToString(t));
t.sign = o,
$.ajax({
type: "post",
data: t,
url: e,
dataType: "json",
beforeSend: function(e) {},
success: function(e) {
n(e)
},
complete: function(e, t) {},
error: function(e) {}
})
}
function ajaxDESWebUploadImg(e, t, n) {
t = null == t || "" == t || void 0 === t ? {
date: (new Date).getTime()
}: t;
var r = (new Date).valueOf();
t.timestamp = r;
var o = encryptByDESModeCBC(mapSortToString(t));
t.sign = o,
$.ajax({
type: "post",
data: t,
url: e,
cache: !1,
processData: !1,
contentType: !1,
beforeSend: function(e) {},
success: function(e) {
n(e)
},
complete: function(e, t) {},
error: function(e) {}
})
}
var key = "keycansr",
iv = "asdewqrf";
function encryptByDESModeCBC(e) {
var t = CryptoJS.enc.Utf8.parse(key),
n = CryptoJS.enc.Utf8.parse(iv);
return encrypted = CryptoJS.DES.encrypt(e, t, {
iv: n,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}),
CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
}
function decryptByDESModeCBC(e) {
e = CryptoJS.enc.Base64.parse(e).toString();
var t = CryptoJS.enc.Utf8.parse(key),
n = CryptoJS.enc.Utf8.parse(iv);
return CryptoJS.DES.decrypt({
ciphertext: CryptoJS.enc.Hex.parse(e)
},
t, {
iv: n,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8)
}
function mapSortToString(e) {
var t = "",
n = [];
for (thisKey in e) n[n.length] = thisKey;
for (value in n.sort(), n) t = t + n[value] + "=" + e[n[value]] + ",";
return t = t.substring(0, t.length - 1)
}注意以下代码段
o = encryptByDESModeCBC(mapSortToString(t));
t.sign = o,sign值由此处生成
继续追踪encryptByDESModeCBC和mapSortToString函数定义
mapSortToString函数将uid和timestamp以逗号分隔拼接,并使用CryptoJS库进行DES加密
从JS代码中可提取加解密所需的key和IV参数
var key = "keycansr",
iv = "asdewqrf";解密验证测试
为验证代码审计结论,使用Python对抓取的sign进行DES解密
Python实现代码如下:
文件名为myDes.py
from Crypto.Cipher import DES
import base64
class DESUtil:
__BLOCK_SIZE_8 = BLOCK_SIZE_8 = DES.block_size
__IV = "asdewqrf" # __IV = chr(0)*8
@staticmethod
def encryt(str, key):
cipher = DES.new(key, DES.MODE_CBC, DESUtil.__IV)
x = DESUtil.__BLOCK_SIZE_8 - (len(str) % DESUtil.__BLOCK_SIZE_8)
if x != 0:
str = str + chr(x)*x
msg = cipher.encrypt(str)
# msg = base64.urlsafe_b64encode(msg).replace('=', '')
msg = base64.b64encode(msg)
return msg
@staticmethod
def decrypt(enStr, key):
cipher = DES.new(key, DES.MODE_CBC,DESUtil.__IV)
# enStr += (len(enStr) % 4)*"="
# decryptByts = base64.urlsafe_b64decode(enStr)
decryptByts = base64.b64decode(enStr)
msg = cipher.decrypt(decryptByts)
print(msg)
print(len(msg))
print(msg[len(msg)-1])
#paddingLen = ord((msg[len(msg)-1]))
paddingLen = msg[len(msg) - 1]
return msg[0:-paddingLen]
if __name__ == "__main__":
mySign = 'iNtdr75WKc7VHmbn8pu4VLqddoLEhupmin6LSi8vj%2Bn%2FyJ0zHlu34LVqQojiN4K6OoB6ggz0Q5NSx6rxxxxxxx=='
key = 'keycansr'
print(DESUtil.decrypt(mySign, key)程序执行结果如下:
b'timestamp=1596436610125,uid=487f764baab641be902f4dxxxxxxxxx'
证实了前述代码审计结论的正确性
构造POST请求流程
明确加解密算法后,构造POST请求变得简单
实现代码如下:
sendHttps.py
import requests
import time
from myDes import *
from requests.packages import urllib3
urllib3.disable_warnings()
uuid = '487f764baab641be902f4dxxxxxxxxx'
ttime = time.time()*1000
key='keycansr'
sstr = "timestamp="+str(ttime)+',uid='+uuid
ssign = DESUtil.encryt(sstr, key)
#print(ssign)
data = {'uid':uuid, 'timestamp':ttime, 'sign':ssign}
req = requests.post('https://xxxx.com/api/user/userAccount/list',data=data,verify=False)
print(req.text)后续对req.text内容进行JSON解码及正则分析,并将结果插入PostgreSQL数据库进行分析。
JavaScript代码混淆处理策略
将JavaScript代码转换为颜文字网络表情编码以实现混淆目的
原理:此类混淆通常利用构造函数将字符串作为代码执行
示例如下:
const sum = new Function('a','b','return a+b');
console.log(sum(2,6));应对方法:
1. 直接将混淆代码粘贴至控制台,通过VM查看源代码,此法仅对报错代码有效
2. 对于控制台输出不报错的代码,
第一种方法:删除代码末尾的“(’_’);”部分
第二种方法:删除后替换为“toString()”方法输出,再将修改后代码粘贴至控制台运行
将JavaScript代码转换为仅含6种字符([, ], (, ), !, +)的编码形式
以此达到混淆目的
示例如下:
‘0':'[+[]]'
‘1':'[+!+[]]'
…应对方法:
1. 直接将混淆代码粘贴至控制台,通过查看VM查看源代码
2. 若代码末尾有成对括号,删除结尾的‘()’;或替换为‘toString()’或将修改后代码粘贴至控制台运行。
3. 若代码末尾无成对括号,仅有如‘)’等字符,将代码通过https://beautifier.io/ 网站美化后,复制至编辑器,通过最后一个括号定位前括号,提取中间代码在控制台输出即可得源码
通过eval()编码的代码处理
应对方法:
将eval中的代码复制出来,更改为alert / document.write / console.log在控制台输出即可解密
总结
以上为个人经验分享,希望能提供参考,也希望大家多多支持本站。