문제 정보
입력 폼에 데이터를 입력하여 맞으면 플래그, 틀리면 NOP !을 출력하는 HTML 페이지입니다.
main 함수를 분석하여 올바른 입력 값을 찾아보세요 !
풀이 힌트
1. Java Script
2. JS 난독화
문제 풀이
![](https://blog.kakaocdn.net/dn/pS8MJ/btrj2vgxJGZ/xlNe2whEcDYZK1fZBDr2i1/img.png)
문제 파일을 다운로드하여 브라우저로 열어보니, input이 위치가 계속 변경되고 있었다.
나는 일단 간단하게 tab 키를 이용하여 input에 커서를 넣어 test를 입력하여 submit 버튼을 클릭하였다.
![](https://blog.kakaocdn.net/dn/o0KuL/btrj6ctbGKn/mofbIQJkroxL3lyeshqrEk/img.png)
그 결과 NOP!이라는 문구가 출력되었다.
![](https://blog.kakaocdn.net/dn/1Dut7/btrj4nPwRvb/omODDdOMUukJfr0LtgoFx0/img.png)
코드를 보기위해 개발자 도구를 살펴봤는데, 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가 있어서 분석이 안되고 있다.
![](https://blog.kakaocdn.net/dn/txIv6/btrj4qZEpma/mzJAPAQ5OsDZwaGrdxHNDK/img.png)
이를 무시하기 위해 개발자 도구의 소스에서 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문을 분석하기 위해 개발자 도구의 디버깅 기능을 이용하였다.
![](https://blog.kakaocdn.net/dn/br6CVy/btrj3pfSnzW/RYAaz6wR3MZpwcKizcIK90/img.png)
2개의 if문에 중단점을 걸어준다.
![](https://blog.kakaocdn.net/dn/ckR7SY/btrj11NWmJc/0oOrCWdLAxZNk5olw4P4Z0/img.png)
그 후 main 함수를 실행시키기 위해 input에 test를 넣고 submit 버튼을 클릭하여 main 함수를 호출한다,
![](https://blog.kakaocdn.net/dn/dSsnvf/btrj2WZqtvv/krhNJFogAo5HyKzNjK36O1/img.png)
중단점을 걸어준 곳에서 멈추게 된다. 또한 주황색 박스로 변수의 값이 어떤 값인지 간단하게 표시하게 된다.
![](https://blog.kakaocdn.net/dn/ydSzN/btrj6b8TCVS/RTCH39YN2dZyy2HTfEFInK/img.png)
첫번째 조건문은 input에 입력한 문자열의 길이를 비교하는 기능이였다.
만약 틀리게 된다면, text2img 함수에 NOP!를 인자 값으로 넣어서 호출 후 return하게 된다.
![](https://blog.kakaocdn.net/dn/bPK0Vg/btrj3Mvgwqn/kmksCHEIQ0chJvIGFthrR1/img.png)
나는 조건을 우회하기 위해 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가 나오겠다는 생각으로 이어졌다.
![](https://blog.kakaocdn.net/dn/VdNEe/btrj4nWi1ki/EnGWKgJgkNcVXHPPYhTRQ1/img.png)
개발자 도구의 콘솔에서 직접 찍어본 결과, 엄청나게 긴 숫자가 나왔다.
하지만 혹시 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 |
Comment