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。

posted @ 2020-08-23 00:06  乌漆WhiteMoon  阅读(1939)  评论(0编辑  收藏  举报