2016HCTF giligili writeup

        題目網址:http://re4js.hctf.io/

        本題主要考察js及編程基本知識,包括js函數、進制轉換、異或等。

        打開網頁看到一張gif圖片,並聽到那首非常有名的神曲,下面有個輸入框,寫着hctf{xxxxxx},點上去可以輸入內容,但一回車就顯示“Sorry,you are wrong...”

按慣例先打開源碼,看到下面的一段js代碼,答案就全在裏面了,扒下來放在自己的電腦上慢慢研究。爲此隆重請出這段代碼:

       

//Come on and get flag:>

       var_ = { 0x4c19cff: "random", 0x4728122: "charCodeAt",0x2138878: "substring", 0x3ca9c7b: "toString", 0x574030a:"eval", 0x270aba9: "indexOf", 0x221201f: function(_9) { var_8 = []; for (var _a = 0, _b = _9.length; _a < _b; _a++) {_8.push(Number(_9.charCodeAt(_a)).toString(16)); } return "0x" +_8.join(""); }, 0x240cb06: function(_2, _3) { var _4 =Math.max(_2.length, _3.length); var _7 = _2 + _3; var _6 = "";for(var _5=0; _5<_4; _5++) { _6 += _7.charAt((_2.charCodeAt(_5%_2.length) ^_3.charCodeAt(_5%_3.length)) % _4); } return _6; }, 0x5c623d0: function(_c, _d){ var _e = ""; for(var _f=0; _f<_d; _f++) { _e += _c; } return _e;} };
       var$ = [ 0x4c19cff, 0x3cfbd6c, 0xb3f970, 0x4b9257a, 0x1409cc7, 0x46e990e,0x2138878, 0x1e1049, 0x164a1f9, 0x494c61f, 0x490f545, 0x51ecfcb, 0x4c7911a,0x29f7b65, 0x4dde0e4, 0x49f889f, 0x5ebd02c, 0x556f342, 0x3f7f3f6, 0x11544aa,0x53ed47d, 0x697a, 0x623f21c1, 0x5c623d0, 0x32e8f8b, 0x3ca9c7b, 0x367a49b,0x360179b, 0x5c862d6, 0x30dc1af, 0x7797d1, 0x221201f, 0x5eb4345, 0x5e9baad, 0x39b3b47,0x32f0b8f, 0x48554de, 0x3e8b5e8, 0x5e4f31f, 0x48a53a6, 0x270aba9, 0x240cb06,0x574030a, 0x1618f3a, 0x271259f, 0x3a306e5, 0x1d33b46, 0x17c29b5, 0x1cf02f4,0xeb896b ];
       vara, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
       function check() {
              var answer = document.getElementById("message").value;
              var correct = (function() {
                     try{
                            h= new MersenneTwister(parseInt(btoa(answer[_[$[6]]](0, 4)), 32));
                            e= h[_[$[""+ +[]]]]()*(""+{})[_[0x4728122]](0xc); for(var _1=0;_1<h.mti; _1++) { e ^= h.mt[_1]; }
                            l= new MersenneTwister(e), v = true;
                            l.random();l.random(); l.random();
                            o= answer.split("_");
                            i= l.mt[~~(h.random()*$[0x1f])%0xff];
                            s= ["0x" + i[_[$[$.length/2]]](0x10), "0x" + e[_[$[$.length/2]]](0o20).split("-")[1]];
                            e=- (this[_[$[42]]](_[$[31]](o[1])) ^ s[0]); if (-e != $[21]) return false;
                            e^= (this[_[$[42]]](_[$[31]](o[2])) ^ s[1]); if (-e != $[22]) return false; e -=0x352c4a9b;
                            t= new MersenneTwister(Math.sqrt(-e));
                            h.random();
                            a= l.random();
                            t.random();
                            y= [ 0xb3f970, 0x4b9257a, 0x46e990e ].map(function(i) { return $[_[$[40]]](i)++1+ -1- +1; });
                            o[0]= o[0].substring(5); o[3] = o[3].substring(0, o[3].length - 1);
                            u= ~~~~~~~~~~~~~~~~(a * i); if (o[0].length > 5) return false;
                            a= parseInt(_[$[23]]("1", Math.max(o[0].length, o[3].length)), 3) ^eval(_[$[31]](o[0]));
                            r= (h.random() * l.random() * t.random()) / (h.random() * l.random() *t.random());
                            e^= ~r;
                            r= (h.random() / l.random() / t.random()) / (h.random() * l.random() *t.random());
                            e^= ~~r;
                            a+= _[$[31]](o[3].substring(o[3].length - 2)).split("x")[1]; if(parseInt(a.split("84")[1], $.length/2) != 0x4439feb) return false;
                            d= parseInt(a, 16) == (Math.pow(2, 16)+ -5+ "") +o[3].charCodeAt(o[3].length - 3).toString(16) + "53846" + (newDate().getFullYear() - 1 + "");
                            i= 0xffff;
                            n= (p = (f = _[$[23]](o[3].charAt(o[3].length - 4), 3)) == o[3].substring(1,4));
                            g= 3;
                            t= _[$[23]](o[3].charAt(3), 3) == o[3].substring(5, 8) &&o[3].charCodeAt(1) * o[0].charCodeAt(0) == 0x2ef3;
                            h= ((31249*g) & i).toString(16);
                            i= _[$[31]](o[3].split(f).join("").substring(0,2)).split("x")[1];
                            s= i == h;
                            return (p & t & s & d) === 1 || (p & t & s & d) === true;
                     }catch (e) {
                            console.log("gg");
                            return false;
                     }
              })();
              document.getElementById("message").placeholder= correct ? "correct" : "wrong";
              if(correct) {
                     alert("Congratulations!you got it!");
              } else {
                     alert("Sorry,you are wrong...");
              }
       };              

        第一眼看得我頭都暈了。這是啥?!好多十六進制數,還有各種$啊_啊一堆奇怪符號……注意到如果把try裏面的賦值變量豎着看,可以看到:

hello is ee that you are reading this

        可以看到出題人滿滿的惡意啊……不過這樣也就知道,變量名爲什麼有26個字母,就是要組合這句話的。那麼最後達到了correct,輸出了Congratulations,應該就出結果了吧。然後看到check()函數返回的是p&t&s&d的值,顯然必須要四個變量都爲真,才能輸出true。那麼接下來就要找到滿足這四個變量爲真的字符串。

 

       p

       關於p的代碼如下:

n = (p = (f =_[$[23]](o[3].charAt(o[3].length - 4), 3)) == o[3].substring(1, 4));

       _[$[23]]是啥?alert一下就看到是

function(_c, _d) { var _e =""; for(var _f=0; _f<_d; _f++) { _e += _c; } return _e; } };
這個函數,作用是將字符串_c重複_d次輸出,這就意味着o[3]下標1,2,3這三個位的字符必須相等,且等於倒數第四位的字符。再看下o的代碼:

o = answer.split("_");
o[0] = o[0].substring(5); o[3] =o[3].substring(0, o[3].length - 1);

        這兩句說明o是將我們輸入的內容以_分開,且掐頭去尾得到的數組,掐掉的頭恰好是hctf{,掐掉的尾剛好是},由此可以確定,我們輸入的answer恰好是flag,且flag有四個單詞。


        t

       再來看關於t的代碼:


t = _[$[23]](o[3].charAt(3), 3) ==o[3].substring(5, 8) && o[3].charCodeAt(1) * o[0].charCodeAt(0) ==0x2ef3;
 

        t的值由兩個語句組合而成,前一個和p一樣,說明o[3]下標5,6,7三個位的字符是一樣的且等於下標爲3的字符。而後一句話則非常關鍵,告訴我們o[3]下標爲1的字符和o[0]下標爲0字符的ascii碼的乘積爲0x2ef3(十進制12019),因式分解得到12019=7*17*101,那麼這兩個字符的ascii碼只能分別是119和101,對應字符是w和e,但哪個對應哪個字符還不知道。再結合p的代碼就知道0[3]的1,2,3,5,6,7位都是w或e。

 

        s

        然後是關於s的代碼:


                            h= ((31249*g) & i).toString(16);
                            i= _[$[31]](o[3].split(f).join("").substring(0, 2)).split("x")[1];
                            s= i == h;


        這段比較簡單,toString(16)是將整數轉換爲16進制的字符串,算一下得到h="6e33",而_[$[31]]則是

function(_9) {var _8 = []; for (var _a = 0, _b = _9.length; _a < _b; _a++) {_8.push(Number(_9.charCodeAt(_a)).toString(16)); } return "0x" +_8.join(""); }
,任務是將字符串_9的每一個字符化成對應的ascii碼數值,再連成成字符串,帶上0x的頭輸出。f由剛纔的判斷是"eee"或"www",截斷並取前兩個字符,恰好是o[0][0]和o[0][3],分別對應的ascii十六進制碼爲6e和33,這樣就得到o[0][0]='n'和o[0][3]='3'。

 

         d

        d要爲真算起來比較複雜,還是先看代碼:

  

       a = parseInt(_[$[23]]("1", Math.max(o[0].length,o[3].length)), 3) ^ eval(_[$[31]](o[0]));
       a+= _[$[31]](o[3].substring(o[3].length - 2)).split("x")[1]; if(parseInt(a.split("84")[1], $.length/2) != 0x4439feb) return false;
       d= parseInt(a, 16) == (Math.pow(2, 16)+ -5+ "") +o[3].charCodeAt(o[3].length - 3).toString(16) + "53846" + (newDate().getFullYear() - 1 + "");

       d的前半截和a有關,先不去管,後半截是字符串拼接,Math.pow(2,16)+ -5=65531,newDate().getFullYear() - 1=2015,還有o[3][length-4]的ascii碼16進制值是未知的(假設爲XX),合起來是"65531XX538462015",然後再看a,$.length是50,這句

if(parseInt(a.split("84")[1], $.length/2) != 0x4439feb) return false;
就是將a截掉84的後半部分作爲25進制數,其值要等於0x4439feb,算得a.split("84")[1]="783f3f",因爲最後兩個3f就是o[3]最後兩個字符ascii碼的16進制,於是得到o[3]最後兩個字符是"??"。

       接着就要敲定XX是多少。字符不多,試出來XX是64,對應的16進制是17481184783f3f,那麼o[3]的倒數第三個字符就是d,第一行執行後的a就應該是1748118478,但是o[0]只知道第一個字符,o[3]和o[0]的長度均未知,因爲代碼中有一句

if (o[0].length > 5) return false; 
也就是說o[0]的長度不會超過5,而o[3]的已知字符就有10個了,那麼max的值至少是10。

       接下來到第一句,異或的逆運算還是異或,因爲o[3]比o[0]長,根據o[3]長度得到兩個答案:如果o[3]長度是11,o[0]就是"h3r3";如果o[3]長度是12,o[0]就是"h6&6"。但是o[0]第一個字符不是e或w嗎!這困擾了我一陣,後來查資料發現,js的異或運算只計算32位或以下的數,大於32位部分的數字將會捨去。h3r3和h6r6的16進制碼都恰好是8位,即2進制的32位,所以前面再加字符不影響a的值了。因此可以把e或w加在前面得到的o[0]上。

 

      這樣就得到了o[0]和o[3]的值。接下來要看幾個return false的語句,這些也會導致程序無法走到correct,前面已經看了幾個,就剩下e的兩個:

 

s = ["0x" +i[_[$[$.length/2]]](0x10), "0x" + e[_[$[$.length/2]]](0o20).split("-")[1]];
e =- (this[_[$[42]]]this[_[$[42]]](_[$[31]](o[1]))^ s[0]); if (-e != $[21]) return false;
e ^= (this[_[$[42]]](_[$[31]](o[2])) ^s[1]); if (-e != $[22]) return false;


        這裏涉及到了s的值,s又和i有關,i和l有關,l是MersenneTwister函數產生的,一查這是個僞隨機數生成算法,能夠相當科學地產生隨機數……但是並沒有任何卵用,因爲一測發現s是常量,值是[0x-51a6949d,0x6e5ef2dc],["0x" +i[_[$[$.length/2]]](0x10)和"0x"+ e[_[$[$.length/2]]](0o20).split("-")[1]]是一個意思,都是toString(16),也就是兩個16進制數。

       有了s,中間兩個單詞就比較簡單了,_[$[42]]是eval,e=-_[$[31]](o[1])^s[0]=-$[21],那麼_[$[31]](o[1])=$[21]=0x697a,算得o[1]="iz",下一行要注意的是e的值已變爲$[21],算得_[$[31]](o[2]) =0x79307572,o[2]="y0ur"。最終得到四個答案:

hctf{wh3r3_iz_y0ur_neee3eeed??}

hctf{eh3r3_iz_y0ur_nwww3wwwd??}

hctf{wh6&6_iz_y0ur_neee3eeeed??}

hctf{eh6&6_iz_y0ur_nwww3wwwwd??}

       四個答案中顯然第一個最有可能,輸進去顯示正確,題目就此結束。


       做完題目後,最後我要嚴厲批判出題人,在題目中公然散播毒品,循環播放神曲,題目還要長時間面對頁面,這是在藉機傳教!下次再見到這種題……我還會繼續做!




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章