<<

funjs

wargame, web

문제

입력 폼에 올바른 데이터를 입력하여 플래그를 찾는 문제이다.

문제 풀이

문제 파일을 다운로드 받아 열어보면, 폼이 일정 주기마다 이동하는 것을 볼 수 있다.

개발자 툴을 열어보았다.

debugger

Paused in debugger 라면서 웹사이트가 멈춘 걸 확인할 수 있다.

Paused in debugger: 코드 실행이 일시 중지된 것을 의미한다. 개발자 도구가 열려있고 코드에서 debugger; 문을 만나면 코드 실행을 일시 중지한다.

코드를 보니 이 부분에서 폼을 이동시킴과 동시에 debugger; 를 실행하여 코드 실행을 일시중지 하는 것을 확인할 수 있었다.

function init() {
    box = document.getElementById("formbox"); // formbox 라는 id를 가진 요소를 가져와 box변수에 할당
    setInterval(moveBox,1000); // 1초마다 moveBox 함수를 실행
}

function moveBox() {
    // formbox의 위치를 랜덤하게 변경
    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; // 코드 실행을 일시 중지한다 ( 개발자 도구가 켜져있을 경우 )
}

일단 폼이 움직이는게 불편하기 때문에, HTML 파일을 열어 위 setInterval 코드를 주석 처리 후 진행하였다.

다음으로 main 함수를 봐보자

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));
    // flag 변수는 document[_0x374fd6(0x183)](_0x374fd6(0x182))['value'] 를 담고 있다
    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, 단순한 더하기 빼기 곱하기 XOR 연산 함수인것 같다
        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);
}

일단 flag 의 값을 알아보기 위해, flag 문의 var flag = document[_0x374fd6(0x183)](_0x374fd6(0x182))['value']
이 부분을 개발자 콘솔에서 _0x374fd6(0x183) _0x374fd6(0x182) 로 함수를 나눠서 실행해보았다.

> _0x374fd6(0x183) // FLAG 선언하기 전, 위 부분은 생략
'getElementById'
> _0x374fd6(0x182)
'flag'

getElementById flag 문자열이 반환되었다.
난독화된 코드를 평문으로 바꿔보면

var flag = document['getElementById']('flag')['value']

document.getElementById('flag').value 와 동일하다는 것을 알 수 있다. 그렇다면 flag 를 쓰는 다른 곳도 평문으로 바꿔보자.

if (flag[_0x374fd6(0x17c)] != 0x24) {
    text2img(_0x374fd6(0x185));
    return;
}

_0x374fd6(0x185)_0x374fd6(0x17c) 를 평문으로 바꿔보았다.

> _0x374fd6(0x17c)
'length'
> _0x374fd6(0x185)
'NOP !'

main 함수의 if (flag[_0x374fd6(0x17c)] != 0x24) { 코드에서 flag 의 길이 flag['length'] 가 0x24 (10진수: 36) 이 아닐 경우
text2img('NOP !') 함수를 실행하고 return 문을 만나 동작이 멈춘다는 것과, flag 값의 길이가 36자라는 사실을 알 수 있다.

flag 값의 길이가 36자일 경우 실행되는 코드를 평문으로 바꿔보자.

// 코드를 보기 편하게 풀었다.
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; // NOP ! 표시 후 종료
    }
}
text2img(flag) // 입력된 플래그가 맞을 경우 입력된 플래그를 그대로 출력한다.

이 코드도 마찬가지로 난독화 되어있는 부분을 개발자 콘솔에서 풀어보았다.

> _0x374fd6(0x176)
'charCodeAt'

charCodeAt 이 나왔다. 이전 코드를 확인하면서 0x17c 값은 ‘length’ 라는 것을 알고 있기 때문에 이를 통해 소스를 난독화된 코드에서 평문으로 치환해 보았다.

for (var i = 0x0; i < flag.length; i++) { // FLAG 의 길이만큼 반복 (36번)
    // 입력한 플래그의 한 글자씩 검사하는 코드
    if (flag.charCodeAt(i) == operator[i % operator.length](_0x4949[i], _0x42931[i])) {
        // 아무것도 하지 않음
    } else {
        text2img(_0x374fd6(0x185));
        return;
    }
}

이렇게 치환하니 훨씬 보기 편해졌다.

코드를 보니 플래그의 길이 (36) 만큼 반복하며 _0x4949_0x42931 배열의 0번째부터 35번째까지 연산자의 피연산자로 입력해 반복하며 입력의 charCode 와 비교해 확인하는 것을 알수 있다.

입력된 플래그를 charCode 로 바꿔서 검증하는 것의 반대로 연산한 flag를 String.fromCharCode 를 통해 charCode를 문자로 바꿔 플래그를 얻을 수 있다.

let _0x4949 = [...],
    _0x42931 = [...],
    operator = [...],
    myFlag = ""
for (let i = 0; i < 36; i++) {
    myFlag += String.fromCharCode(operator[i % operator.length](_0x4949[i], _0x42931[i]))
}
console.log(`The flag is: ${myFlag}`)

플래그 획득 사진