워게임/dreamhack

[dreamhack] funjs 문제풀이

  • -
[WEB] funjs 문제풀이

자바스크립트(Javascript)는 아주 강력한 프로그래밍 언어이다. HTML은 웹페이지의 큰 뼈대를 제공하고, CSS는 색깔이나 글씨체, 모양과 같은 디자인적인 요소를 관리하며, 자바스크립트는 웹 페이지의 동작을 담당한다. 즉, 자바스크립트는 동적으로 콘텐츠를 바꾸고, 멀티미디어나 움직이는 이미지 등 웹페이지를 꾸며주도록 도와준다.

 

크롬 개발자 도구(DevTools)는 크롬 브라우저에 내장된 개발 관련 도구입니다. 웹 어플리케이션을 개발하고 수정/최적화하는데 필요한 다양한 기능을 제공한다다. 자바스크립트 디버깅뿐 아니라 모바일 기기 시뮬레이터, 네트워크 분석, 최적화에 대해 검사도 해줍니다. 전체 기능은 공식 홈페이지에서 확인할 수 있다.

 

디버깅에는 주로 Elements, Console, Sources 패널이 자주 사용된다.

1) Element는 주로 디자인을 수정하는 용도로 사용하는 패널이다. 

   DOM을 확인하고 CSS Style을 수정한다. 특정 DOM의 변화에 중단점을 걸 수 있는 기능도 존재한다.

2) Console은 로그를 확인하고 스크립트 명령어를 입력할 수 있는 패널이다.

   중단점을 건 시점의 변수를 확인할 수 있고, 값을 평가하거나 수정할 수 있다.

3) Sources는 왼쪽 파일 관리창, 가운데 소스 에디터, 오른쪽에 디버깅 관련 정보창이 있다.

   디버깅 관련 정보창 위 중단점 컨트롤을 이용하여 코드를 디버깅할 수 있다.

 

 

문제를 확인해보자.

 

 

입력 폼에 데이터를 입력하여 플래그를 획득하는 문제이다. 문제 파일을 다운로드해보자.

 

 

index.html이 보여서 브라우저로 띄워서 보니 정신없이 돌아다닌다.

뛰어난 운동신경으로 캐치해서 아무 값이나 입력해보자.

 

 

문제에서 설명한데로 틀린 값을 입력하면 NOP! 라는 이미지를 출력한다.

소스코드를 확인해보자.

 

<html>
    <head>
    <style>*{margin: 0;}</style>
    <script>
    var box;
    window.onload = init;
    function init() {
      box = document.getElementById("formbox");
      setInterval(moveBox,1000);
    }
    function moveBox() {
        box.posX = Math.random() * (window.innerWidth - 64); 
        box.posY = Math.random() * (document.documentElement.scrollHeight - 64); 
        box.style.marginLeft = box.posX + "px";
        box.style.marginTop  = box.posY + "px";
        debugger;
    }

    function text2img(text){
        var imglist = box.getElementsByTagName('img');
        while(imglist.length > 0) {imglist[0].remove();}
        var canvas = document.createElement("canvas");
        canvas.width = 620;
        canvas.height = 80;
        var ctx = canvas.getContext('2d');
        ctx.font = "30px Arial";
        var text = text;
        ctx.fillText(text,10,50);
        var img = document.createElement("img");
        img.src = canvas.toDataURL();
        box.append(img);
    };

    function main(){
        var _0x1046=['2XStRDS','1388249ruyIdZ','length','23461saqTxt','9966Ahatiq','1824773xMtSgK','1918853csBQfH','175TzWLTY','flag','getElementById','94hQzdTH','NOP\x20!','11sVVyAj','37594TRDRWW','charCodeAt','296569AQCpHt','fromCharCode','1aqTvAU'];
        var _0x376c = function(_0xed94a5, _0xba8f0f) {
            _0xed94a5 = _0xed94a5 - 0x175;
            var _0x1046bc = _0x1046[_0xed94a5];
            return _0x1046bc;
        };
        var _0x374fd6 = _0x376c;
        (function(_0x24638d, _0x413a92) {
            var _0x138062 = _0x376c;
            while (!![]) {
                try {
                    var _0x41a76b = -parseInt(_0x138062(0x17f)) + parseInt(_0x138062(0x180)) * -parseInt(_0x138062(0x179)) + -parseInt(_0x138062(0x181)) * -parseInt(_0x138062(0x17e)) + -parseInt(_0x138062(0x17b)) + -parseInt(_0x138062(0x177)) * -parseInt(_0x138062(0x17a)) + -parseInt(_0x138062(0x17d)) * -parseInt(_0x138062(0x186)) + -parseInt(_0x138062(0x175)) * -parseInt(_0x138062(0x184));
                    if (_0x41a76b === _0x413a92) break;
                    else _0x24638d['push'](_0x24638d['shift']());
                } catch (_0x114389) {
                    _0x24638d['push'](_0x24638d['shift']());
                }
            }
        }(_0x1046, 0xf3764));
        var flag = document[_0x374fd6(0x183)](_0x374fd6(0x182))['value'],
            _0x4949 = [0x20, 0x5e, 0x7b, 0xd2, 0x59, 0xb1, 0x34, 0x72, 0x1b, 0x69, 0x61, 0x3c, 0x11, 0x35, 0x65, 0x80, 0x9, 0x9d, 0x9, 0x3d, 0x22, 0x7b, 0x1, 0x9d, 0x59, 0xaa, 0x2, 0x6a, 0x53, 0xa7, 0xb, 0xcd, 0x25, 0xdf, 0x1, 0x9c],
            _0x42931 = [0x24, 0x16, 0x1, 0xb1, 0xd, 0x4d, 0x1, 0x13, 0x1c, 0x32, 0x1, 0xc, 0x20, 0x2, 0x1, 0xe1, 0x2d, 0x6c, 0x6, 0x59, 0x11, 0x17, 0x35, 0xfe, 0xa, 0x7a, 0x32, 0xe, 0x13, 0x6f, 0x5, 0xae, 0xc, 0x7a, 0x61, 0xe1],
            operator = [(_0x3a6862, _0x4b2b8f) => {
                return _0x3a6862 + _0x4b2b8f;
            }, (_0xa50264, _0x1fa25c) => {
                return _0xa50264 - _0x1fa25c;
            }, (_0x3d7732, _0x48e1e0) => {
                return _0x3d7732 * _0x48e1e0;
            }, (_0x32aa3b, _0x53e3ec) => {
                return _0x32aa3b ^ _0x53e3ec;
            }],
            getchar = String[_0x374fd6(0x178)];
        if (flag[_0x374fd6(0x17c)] != 0x24) {
            text2img(_0x374fd6(0x185));
            return;
        }
        for (var i = 0x0; i < flag[_0x374fd6(0x17c)]; i++) {
            if (flag[_0x374fd6(0x176)](i) == operator[i % operator[_0x374fd6(0x17c)]](_0x4949[i], _0x42931[i])) {} else {
                text2img(_0x374fd6(0x185));
                return;
            }
        }
        text2img(flag);
    }
    </script>
    </head>
    <body>
        <div id='formbox'>
            <h2>Find FLAG !</h2>
            <input type='flag' id='flag' value=''>
            <input type='button' value='submit' onclick='main()'>
        </div>
    </body>
</html>

 

함수를 난독화를 아주 그냥 야물딱지게 해놨다.

MAIN 함수를 보면 _0x1046[] 리스트에다가 문자열을 집어넣어놓고 연산을 통해 FLAG를 생성하는 것 같다.

오답일 때는 NOP! 라는 문자열이 포함된 이미지를 출력했는데 코드를 따라가보자.

 

        if (flag[_0x374fd6(0x17c)] != 0x24) {
            text2img(_0x374fd6(0x185));
            return;
        }
        for (var i = 0x0; i < flag[_0x374fd6(0x17c)]; i++) {
            if (flag[_0x374fd6(0x176)](i) == operator[i % operator[_0x374fd6(0x17c)]](_0x4949[i], _0x42931[i])) {} else {
                text2img(_0x374fd6(0x185));
                return;
            }
        }
        text2img(flag);

 

(1) if문과 for문 내의 if문을 통해 각각 flag를 조건문으로 검증하여 이미지를 출력하는 구문이다.

모든 구문이 정상적으로 종료된뒤 text2img(flag)를 출력하는 것으로 보아, 위의 조건문으로 함수가 return 되지 않아야

FLAG 이미지를 띄우는 것을 알 수 있다.

 

일단 첫번째 if문을 살려보자. flag[_0x374fd6(0x17c)] 값이 0x24 즉, 십진수 36이 아니면 함수를 리턴한다.

그럼 flag[] 리스트에는 어떤 값들이 저장되어 있는지 확인해보자.

var flag = document[_0x374fd6(0x183)](_0x374fd6(0x182))['value'],
            _0x4949 = [0x20, 0x5e, 0x7b, 0xd2, 0x59, 0xb1, 0x34, 0x72, 0x1b, 0x69, 0x61, 0x3c, 0x11, 0x35, 0x65, 0x80, 0x9, 0x9d, 0x9, 0x3d, 0x22, 0x7b, 0x1, 0x9d, 0x59, 0xaa, 0x2, 0x6a, 0x53, 0xa7, 0xb, 0xcd, 0x25, 0xdf, 0x1, 0x9c],
            _0x42931 = [0x24, 0x16, 0x1, 0xb1, 0xd, 0x4d, 0x1, 0x13, 0x1c, 0x32, 0x1, 0xc, 0x20, 0x2, 0x1, 0xe1, 0x2d, 0x6c, 0x6, 0x59, 0x11, 0x17, 0x35, 0xfe, 0xa, 0x7a, 0x32, 0xe, 0x13, 0x6f, 0x5, 0xae, 0xc, 0x7a, 0x61, 0xe1],
            operator = [(_0x3a6862, _0x4b2b8f) => {
                return _0x3a6862 + _0x4b2b8f;
            }, (_0xa50264, _0x1fa25c) => {
                return _0xa50264 - _0x1fa25c;
            }, (_0x3d7732, _0x48e1e0) => {
                return _0x3d7732 * _0x48e1e0;
            }, (_0x32aa3b, _0x53e3ec) => {
                return _0x32aa3b ^ _0x53e3ec;
            }],
            getchar = String[_0x374fd6(0x178)];

 

(2) flag[] 리스트에는 굉장히 복잡한 배열들이 들어가있다.

첫 번째에는 document[_0x374fd6(0x183)](_0x374fd6(0x182))['value'] 라는 녀석이 보인다.

이 값을 구해보자.

 

메인 소스코드 40번째 라인에 _0x374fd6() 함수가 정의되어 있다.

var _0x374fd6 = _0x376c;

 

그리고 _0x376c; 는 35번째 라인에 다음과 같이 정의되어 있다.

var _0x376c = function(_0xed94a5, _0xba8f0f) {
            _0xed94a5 = _0xed94a5 - 0x175;
            var _0x1046bc = _0x1046[_0xed94a5];
            return _0x1046bc;

 

그리고 34번째 라인을 보면 _0x1046[] 리스트에 저장된 문자열 배열은 다음과 같다.

var _0x1046=['2XStRDS','1388249ruyIdZ','length','23461saqTxt','9966Ahatiq','1824773xMtSgK','1918853csBQfH','175TzWLTY','flag','getElementById','94hQzdTH','NOP\x20!','11sVVyAj','37594TRDRWW','charCodeAt','296569AQCpHt','fromCharCode','1aqTvAU'];

 

_0x1046[] 리스트는 41번째 라인의 funtion()에 의해 push, shift 연산이 수행되고, 배열이 바뀐다.

한땀한땀 따라가려니 눈이 너무 아프다. 크롬 개발자도구에서 지원하는 브레이크포인트를 이용해 디버그를 수행해보자.

 

flag 값에 데이터가 삽입되기 전 부분에서 브레이크 포인트를 설정해, _0x1046[] 배열에 저장된 값을 확인해보자.

53번째 flag 변수를 선언하는 라인에 breakpoint를 설정한다.

 

이제 메인 페이지에서 flag에 "aaa"를 입력 후 버튼을 클릭해보자, 

 

개발자모드 Scope에서 0x1046[] 배열에 저장된 값을 확인해보자.

 

그럼 다시 (2)로 돌아가서 flag라는 변수에 어떤 값이 저장되어 있는지 확인해보자.

첫번째 배열인 document[_0x374fd6(0x183)](_0x374fd6(0x182))['value'] 값을 계산해보자.

 

_0x374fd6(0x183)는 _0x1046[0x183-0x175] 로 계산할 수 있다.

0x183-0x175는 십진수로 14이며, _0x1046[0x183-0x175]는 "getElementById"라는 문자열이다.

 

_0x374fd6(0x182)는 위와 동일하게 계산하면 "flag"라는 문자열이다.

 

즉 flag  값은 document.getElementById('flag').value 로 사용자가 입력한 문자열이 들어간다.

차례대로 두번째 값은 0x4949[] 라는 리스트, 세번째 값은 0x42931[] 리스트, 마지막은 oprator[] 리스트가 들어간다.

 

flag에 어떤 값이 들어가는지 확인했으니, (1)로 돌아가서 어떤 조건문을 우회해야 플래그를 획득할 수 있는지 보자.

올라가기가 귀찮아서 다시 가져왔다.

        if (flag[_0x374fd6(0x17c)] != 0x24) {
            text2img(_0x374fd6(0x185));
            return;
        }
        for (var i = 0x0; i < flag[_0x374fd6(0x17c)]; i++) {
            if (flag[_0x374fd6(0x176)](i) == operator[i % operator[_0x374fd6(0x17c)]](_0x4949[i], _0x42931[i])) {} else {
                text2img(_0x374fd6(0x185));
                return;
            }
        }
        text2img(flag);

 

첫번재 if문의 flag[_0x374fd6(0x17c)] != 0x24는 flag[_0x1046(7)]으로 바꿔볼 수 있으며,

이는 다시 flag['length']로 바꿔볼 수 있다. 즉 flag의 길이는 36임을 알 수 있다.

 

아래 for문에서 i를 0부터 36까지 증가시키며 각 문자 하나하나를 실제 flag 값과 비교하는 것으로 알 수 있다.

그럼 역으로 입력한 flag 값이 아닌 operator[] 값을 통해 flag를 구현해보자.

 

크롬 개발자도구를 이용해서 마지막 flag 검증 로직인 if 문이 구현되기 전 구간에서 breakpoint를 설정하여 

검증을 멈추고 값이 선언되어 있는 배열과 변수를 이용해서 flag 값을 출력해보자.

 

메인 페이지에서 "aaa"를 입력 후 버튼을 클릭하여 if 문 전까지 코드를 실행시킨다.

 

 

그리고는 Console 탭에서 임의의 코드를 실행시켜 operator[]를 이용하여 flag를 출력한다.

페이로드는 다음과 같다.

result='';
for (var i = 0x0; i < 36; i++) 
result+=String.fromCharCode(operator[i % operator[_0x374fd6(0x17c)]](_0x4949[i], _0x42931[i]));

 

그럼 실행해보자.

 

플래그가 나왔다 문제풀이 끗

 

사실 위에서 난독화된 함수를 따라갈 필요 없이 개발자모드의 breakpoint를 이용해 손쉽게 풀 수 있는 문제다.

최대한 내가 삽질하면서 문제를 풀어가는데 사용한 방법을 문제풀이에 담으려고 노력했다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.