[Dreamhack Web - Lv 1] funjs

문제 정보

입력 폼에 데이터를 입력하여 맞으면 플래그, 틀리면 NOP !을 출력하는 HTML 페이지입니다.
main 함수를 분석하여 올바른 입력 값을 찾아보세요 !

풀이 힌트

1. Java Script

2. JS 난독화

문제 풀이

더보기
더보기

문제 파일을 다운로드하여 브라우저로 열어보니, input이 위치가 계속 변경되고 있었다.
나는 일단 간단하게 tab 키를 이용하여 input에 커서를 넣어 test를 입력하여 submit 버튼을 클릭하였다.

 

 그 결과 NOP!이라는 문구가 출력되었다.

 

코드를 보기위해 개발자 도구를 살펴봤는데, NOP! 문구는 img 파일로 출력되고 있었다.

 

head 태그 내에 많은 JS 코드가 있어서 분석을 위해 개발자 도구의 소스에서 분석하기로 하였다.

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);
}

 위와 같은 JS 코드가 있었는데, 길이가 길어서 함수별로 나눠서 분석하였다.

 

var box;
window.onload = init;
function init() {
  box = document.getElementById("formbox");
  setInterval(moveBox,1000);
}

JS 코드의 초반 부분은 init 함수로 시작이 된다.
init 함수는 HTML 태그 중 id 속성 값이 formbox인 태그를 box 변수에 넣어주고, 1초마다 moveBox 함수를 실행한다.

 

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;
  }

moveBox 함수의 코드를 살펴보면, 브라우저의 가로/세로 각각의 길이 - 64한 값과 랜덤으로 뽑은 값을 곱하여 box.posX와 box.posY에 넣어준다.
그 후 해당 변수는 top와 left가 되어, 현재 input의 위치가 계속 변화는 것이 moveBox 함수 때문이라는 것을 알 수 있었다.

 

debugger는 디버깅을 할 때 사용하는 것인데, 현재 1초마다 실행되는 moveBox에 debugger가 있어서 분석이 안되고 있다.

이를 무시하기 위해 개발자 도구의 소스에서 debugger를 무시 목록에 추가하였다.
그러면 debugger가 1초마다 호출되어 계속 멈춰 있는 것이 풀리게 된다.

 

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);
};

인자 값으로 넣은 문자열을 img로 변환시켜주는 함수로 보인다.
마지막에 box.append(img)로 이미지를 id 속성 값이 formbox인 태그에 추가하는 것을 볼 수 있다.

 

그럼 여기서 알 수 있는 것은 flag는 이미지로 저장되어 있는 것이 아니라 코드 상에 존재하여 조건이 충족되면 text2img 함수를 통해 화면에 이미지로 출력된다는 것이다.

 

나는 마지막 main 함수에서 flag가 출력될 수 있는 조건을 찾아봤다.

 

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);
}

main 함수는 난독화가 되어 있어서 하나하나 살펴보면서 분석하는 것보다 조건문 부분만 살펴보는 방법으로 flag를 출력하는 조건을 찾았다.

 

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);

위와 같이 main 함수에는 2개의 조건문이 있었다.
2개의 if문을 분석하기 위해 개발자 도구의 디버깅 기능을 이용하였다.

 

2개의 if문에 중단점을 걸어준다.

 

그 후 main 함수를 실행시키기 위해 input에 test를 넣고 submit 버튼을 클릭하여 main 함수를 호출한다,

 

중단점을 걸어준 곳에서 멈추게 된다. 또한 주황색 박스로 변수의 값이 어떤 값인지 간단하게 표시하게 된다.

첫번째 조건문은 input에 입력한 문자열의 길이를 비교하는 기능이였다.
만약 틀리게 된다면, text2img 함수에 NOP!를 인자 값으로 넣어서 호출 후 return하게 된다.

 

나는 조건을 우회하기 위해 flag[_0x374fd6(0x17c)]의 값을 36으로 변경하여 조건문이 실행 안 되도록 하여, 두번째 if문으로 넘어갔다.

 

여기서 문제가 발생하였다......
for문의 if문이 중단점이 발생하지 않는 것이다.

 

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);

반복문은 0부터 flag의 길이-1까지 반복되고, input의 문자 인덱싱한 값이랑 operator의 값과 비교하여 틀렸을 때 Nop!를 출력하는 것을 알 수 있었다.

 

분석한 결과를 알고나니, operator의 값들을 직접 찍어봐서 변수에 저장하면 flag가 나오겠다는 생각으로 이어졌다.

 

개발자 도구의 콘솔에서 직접 찍어본 결과, 엄청나게 긴 숫자가 나왔다.
하지만 혹시 fromCharCode 함수를 사용한다면 flag가 나오지 않을까? 라는 생각에 사용하여 확인해보니, 예상이 딱 맞아 떨어졌다.

 

'Wargame > Dreamhack' 카테고리의 다른 글

[Dreamhack Web - Lv 1] session  (0) 2021.11.12
[Dreamhack Web - Lv 1] mongoboard  (0) 2021.11.10
[Dreamhack Web - Lv 1] Carve Party  (0) 2021.11.01
[Dreamhack Web - Lv 1] Mango  (0) 2021.11.01
[Dreamhack Web - Lv 1] php-1  (0) 2021.10.29