CTF-WEB:Bugku-江湖魔头(Javascript 审计 + 逆向解密)

这是个啥?#

打开网页,首先先了解下这个网页的正确打开方式。根据描述应该是个游戏,试着玩玩看,首先需要选择初始属性(有金庸群侠传内味了,哈哈)。

接下来根据提示,我们应该是要把蒙老魔干掉,但是每一次练功或者赚钱都需要花 5 秒时间,这段时间会卡一下很影响体验。

打开商店,根据提示应该要学习如来神掌,但是这需要一笔巨款。同时学如来神掌之前要先把所有属性都刷满,可以练功也可以直接氪金,但是无论怎么搞都很费时间。

讨伐蒙老魔需要学会如来神掌,解决这个问题可以氪命,但是这并不是高效的方式。

突破口#

首先还是先进行常规的操作,F12、抓包和后台扫描都没有什么有价值的信息,想要直接修改数值把钱搞上去也证明无效。不过现在看到的页面已经是经过 GET 方法传参之后的页面了,考虑在 url 把传参删掉,成功看到一个新页面。

F12 打开新页面的源码,看到了还有 3 个 JavaScript 文件。

先打开第一个文件,能看得出是代码,但是里面不知道是什么东西。

Copy Highlighter-hljs
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 中指定变量的值并返回。

Copy Highlighter-hljs
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() 函数进行解码,接下来就得到了游戏中的面板数据。后续的代码就是游戏的一些操作了,例如判断能不能打魔头之类的。

Copy Highlighter-hljs
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 长什么样。

Copy Highlighter-hljs
var test = getCookie("user") var test = decodeURIComponent(test) var test = decode_create(test);

得到的字符串如下,看样子这个字符串是 PHP 中的 human 对象序列化字符串。里面存储了玩家的各个属性,其中里面的钱数量为 0。同时我们也看到了 flag 的值也为 0,也就是说想直接用解码的方式得到 flag 是没有用的。

Copy Highlighter-hljs
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 字段改得很高。

Copy Highlighter-hljs
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() 的逆过程。

Copy Highlighter-hljs
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)”。我们在还原的时候要把顺序逆过来,先实现表达式的逆运算,然后再做异或运算。

Copy Highlighter-hljs
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 文件中。

Copy Highlighter-hljs
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; }

但是如果我们直接调用这个函数会出问题,原因我们得审计一下解码的代码。

Copy Highlighter-hljs
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() 函数,对字符串进行最后一次编码即可。

Copy Highlighter-hljs
output = encodeURIComponent(output)

到此为止,我们终于获得了用于替代原来变量的字符串了。

提交 cookie#

根据对代码的审计,我们应该那这个字符串和 cookie 中的 user 参数替换,可以用 HackBar 也可以抓包。

提交成功之后,会发现我们的钱瞬间花不完了,赶紧把神功全部学了然后讨伐魔头获得 flag。

posted @   乌漆WhiteMoon  阅读(2636)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS