CTF-WEB:Bugku-江湖魔头(Javascript 审计 + 逆向解密)
这是个啥?#
打开网页,首先先了解下这个网页的正确打开方式。根据描述应该是个游戏,试着玩玩看,首先需要选择初始属性(有金庸群侠传内味了,哈哈)。
接下来根据提示,我们应该是要把蒙老魔干掉,但是每一次练功或者赚钱都需要花 5 秒时间,这段时间会卡一下很影响体验。
打开商店,根据提示应该要学习如来神掌,但是这需要一笔巨款。同时学如来神掌之前要先把所有属性都刷满,可以练功也可以直接氪金,但是无论怎么搞都很费时间。
讨伐蒙老魔需要学会如来神掌,解决这个问题可以氪命,但是这并不是高效的方式。
突破口#
首先还是先进行常规的操作,F12、抓包和后台扫描都没有什么有价值的信息,想要直接修改数值把钱搞上去也证明无效。不过现在看到的页面已经是经过 GET 方法传参之后的页面了,考虑在 url 把传参删掉,成功看到一个新页面。
F12 打开新页面的源码,看到了还有 3 个 JavaScript 文件。
先打开第一个文件,能看得出是代码,但是里面不知道是什么东西。
eval(function(p,a,c,k,e,r){e=function(c){return(c<62?'':e(parseInt(c/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'[57-9abd-hj-zAB]'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('7 s(t){5 m=t+"=";5 8=9.cookie.n(\';\');o(5 i=0;i<8.d;i++){5 c=8[i].trim();u(c.v(m)==0)p c.substring(m.d,c.d)}p""}7 w(a){5 x=new Base64();5 q=x.decode(a);5 r="";o(i=0;i<q.d;i++){5 b=q[i].charCodeAt();b=b^i;b=b-((i%10)+2);r+=String.fromCharCode(b)}p r}7 ertqwe(){5 y="user";5 a=s(y);a=decodeURIComponent(a);5 z=w(a);5 8=z.n(\';\');5 e="";o(i=0;i<8.d;i++){u(-1<8[i].v("A")){e=8[i+1].n(":")[2]}}e=e.B(\'"\',"").B(\'"\',"");9.write(\'<img id="f-1" g="h/1-1.k">\');j(7(){9.l("f-1").g="h/1-2.k"},1000);j(7(){9.l("f-1").g="h/1-3.k"},2000);j(7(){9.l("f-1").g="h/1-4.k"},3000);j(7(){9.l("f-1").g="h/6.png"},4000);j(7(){alert("你使用如来神掌打败了蒙老魔,但不知道是真身还是假身,提交试一下吧!A{"+md5(e)+"}")},5000)}',[],38,'|||||var||function|ca|document|temp|num||length|key|attack|src|image||setTimeout|jpg|getElementById|name|split|for|return|result|result3|getCookie|cname|if|indexOf|decode_create|base|temp_name|mingwen|flag|replace'.split('|'),0,{}))
这是一个被压缩过的 JavaScript 代码,拿去解码网页解码得到源码。
代码审计#
得到源码后首先查看 getCookie() 函数,这个函数会在游戏开始之后获取 cookie 中指定变量的值并返回。
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) == 0)
return c.substring(name.length, c.length)
}
return ""
}
ertqwe() 函数是个关键的函数,首先得到的值会经过 decodeURIComponent() 函数和 decode_create() 函数进行解码,接下来就得到了游戏中的面板数据。后续的代码就是游戏的一些操作了,例如判断能不能打魔头之类的。
function ertqwe() {
var temp_name = "user";
var temp = getCookie(temp_name);
temp = decodeURIComponent(temp);
var mingwen = decode_create(temp);
var ca = mingwen.split(';');
var key = "";
for (i = 0; i < ca.length; i++) {
if (-1 < ca[i].indexOf("flag")) {
key = ca[i + 1].split(":")[2]
}
}
key = key.replace('"', "").replace('"', "");
document.write('<img id="attack-1" src="image/1-1.jpg">');
setTimeout(function() {
document.getElementById("attack-1").src = "image/1-2.jpg"
}, 1000);
setTimeout(function() {
document.getElementById("attack-1").src = "image/1-3.jpg"
}, 2000);
setTimeout(function() {
document.getElementById("attack-1").src = "image/1-4.jpg"
}, 3000);
setTimeout(function() {
document.getElementById("attack-1").src = "image/6.png"
}, 4000);
setTimeout(function() {
alert("你使用如来神掌打败了蒙老魔,但不知道是真身还是假身,提交试一下吧!flag{" + md5(key) + "}")
}, 5000)
}
我们可以在 F12 的控制器中调试代码,查看现在的 cookic 长什么样。
var test = getCookie("user")
var test = decodeURIComponent(test)
var test = decode_create(test);
得到的字符串如下,看样子这个字符串是 PHP 中的 human 对象序列化字符串。里面存储了玩家的各个属性,其中里面的钱数量为 0。同时我们也看到了 flag 的值也为 0,也就是说想直接用解码的方式得到 flag 是没有用的。
O:5:"human":10:{s:8:"xueliang";i:857;s:5:"neili";i:950;s:5:"lidao";i:68;s:6:"dingli";i:56;s:7:"waigong";i:0;s:7:"neigong";i:0;s:7:"jingyan";i:0;s:6:"yelian";i:0;s:5:"money";i:0;s:4:"flag";s:1:"0";}
构造变量并逆向封装#
因为各项属性和如来神掌都可以氪金习得,而且我们也不知道属性的上限是多少,这时可以只把 money 字段改得很高。
O:5:"human":10:{s:8:"xueliang";i:857;s:5:"neili";i:950;s:5:"lidao";i:68;s:6:"dingli";i:56;s:7:"waigong";i:0;s:7:"neigong";i:0;s:7:"jingyan";i:0;s:6:"yelian";i:0;s:5:"money";i:100000000;s:4:"flag";s:1:"0";}
这个时候如果我们用这个字符串替代掉原来的变量,就可以直接购买商店的所有东西。但是这段字符串穿过去之后又会经历一系列解码,如果直接把这段字符串传上去会导致发生混乱。所以我们要把这段字符串按照前面解码的代码方向封装回去,首先要进行 decode_create() 的逆过程。
function decode_create(temp) {
var base = new Base64();
var result = base.decode(temp);
var result3 = "";
for (i = 0; i < result.length; i++) {
var num = result[i].charCodeAt();
num = num ^ i;
num = num - ((i % 10) + 2);
result3 += String.fromCharCode(num)
}
return result3
}
审计这段代码,传入的字符串中的每个字符先进行异或运算,然后计算表达式 “num = num - ((i % 10) + 2)”。我们在还原的时候要把顺序逆过来,先实现表达式的逆运算,然后再做异或运算。
var result = "";
for (i = 0; i < test.length; i++) {
var num = test[i].charCodeAt();
num = num + ((i % 10) + 2);
num = num ^ i;
result += String.fromCharCode(num)
}
现在 result 变量就是 test 序列化字符串的编码了,接下来要进行 base.decode() 的逆过程。这个函是也是进行解码,它的编码函数被放在 base64.js 文件中。
this.encode = function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = _utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
}
else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
}
return output;
}
但是如果我们直接调用这个函数会出问题,原因我们得审计一下解码的代码。
this.decode = function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
//output = _utf8_decode(output);
return output;
}
观察到该函数的 “output = _utf8_decode(output);” 这句代码被注释掉了,但是网页提供的编码函数却有 “input = _utf8_encode(input);”,因此我们执行代码的时候不能用这句。
最后进行 decodeURIComponent() 函数的逆过程,这个函数是 JavaScript 的内置函数,用于对 encodeURIComponent() 函数编码的 URI 进行解码。因此我们这里只需要直接调用 encodeURIComponent() 函数,对字符串进行最后一次编码即可。
output = encodeURIComponent(output)
到此为止,我们终于获得了用于替代原来变量的字符串了。
提交 cookie#
根据对代码的审计,我们应该那这个字符串和 cookie 中的 user 参数替换,可以用 HackBar 也可以抓包。
提交成功之后,会发现我们的钱瞬间花不完了,赶紧把神功全部学了然后讨伐魔头获得 flag。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)