CTF/PicoCTF2019

Client side again - Web Exploitation

SamIT 2020. 1. 1. 22:26

 

접속화면이다.

아무것도 볼게 없기 때문에 소스코드를 확인한다.

 

 

 

<html>
<head>
<title>Secure Login Portal V2.0</title>
</head>
<body background="barbed_wire.jpeg" >
<!-- standard MD5 implementation -->
<script type="text/javascript" src="md5.js"></script>

<script type="text/javascript">
  var _0x5a46=['25df2}','_again_b','this','Password\x20Verified','Incorrect\x20password','getElementById','value','substring','picoCTF{','not_this'];(function(_0x4bd822,_0x2bd6f7){var _0xb4bdb3=function(_0x1d68f6){while(--_0x1d68f6){_0x4bd822['push'](_0x4bd822['shift']());}};_0xb4bdb3(++_0x2bd6f7);}(_0x5a46,0x1b3));var _0x4b5b=function(_0x2d8f05,_0x4b81bb){_0x2d8f05=_0x2d8f05-0x0;var _0x4d74cb=_0x5a46[_0x2d8f05];return _0x4d74cb;};function verify(){checkpass=document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];split=0x4;if(checkpass[_0x4b5b('0x2')](0x0,split*0x2)==_0x4b5b('0x3')){if(checkpass[_0x4b5b('0x2')](0x7,0x9)=='{n'){if(checkpass[_0x4b5b('0x2')](split*0x2,split*0x2*0x2)==_0x4b5b('0x4')){if(checkpass[_0x4b5b('0x2')](0x3,0x6)=='oCT'){if(checkpass[_0x4b5b('0x2')](split*0x3*0x2,split*0x4*0x2)==_0x4b5b('0x5')){if(checkpass['substring'](0x6,0xb)=='F{not'){if(checkpass[_0x4b5b('0x2')](split*0x2*0x2,split*0x3*0x2)==_0x4b5b('0x6')){if(checkpass[_0x4b5b('0x2')](0xc,0x10)==_0x4b5b('0x7')){alert(_0x4b5b('0x8'));}}}}}}}}else{alert(_0x4b5b('0x9'));}}
</script>
<div style="position:relative; padding:5px;top:50px; left:38%; width:350px; height:140px; background-color:gray">
<div style="text-align:center">
<p>New and Improved Login</p>

<p>Enter valid credentials to proceed</p>
<form action="index.html" method="post">
<input type="password" id="pass" size="8" />
<br/>
<input type="submit" value="verify" onclick="verify(); return false;" />
</form>
</div>
</div>
</body>
</html>

JavaScript의 내용을 알아보기 어렵게 난독화 해놓은 것을 확인할 수 있다.

 

 

난독화를 보기 쉽게 정리하는 방법에는 스스로 하는 방법도 있지만 

아래의 사이트를 사용해 난독화된 내용을 정리할 수 있다.

http://www.jsnice.org

 

JS NICE: Statistical renaming, Type inference and Deobfuscation

// Put your JavaScript here that you want to rename, deobfuscate, // or infer types for: function chunkData(e, t) { var n = []; var r = e.length; var i = 0; for (; i < r; i += t) { if (i + t < r) { n.push(e.substring(i, i + t)); } else { n.push(e.substring

www.jsnice.org

 

다음은 난독화된 내용을 정리한 코드이다.

'use strict';
/** @type {!Array} */
var _0x5a46 = ["25df2}", "_again_b", "this", "Password Verified", "Incorrect password", "getElementById", "value", "substring", "picoCTF{", "not_this"];
(function(data, i) {
  /**
   * @param {number} isLE
   * @return {undefined}
   */
  var write = function(isLE) {
    for (; --isLE;) {
      data["push"](data["shift"]());
    }
  };
  write(++i);
})(_0x5a46, 435);
/**
 * @param {string} level
 * @param {?} ai_test
 * @return {?}
 */
var _0x4b5b = function(level, ai_test) {
  /** @type {number} */
  level = level - 0;
  var rowsOfColumns = _0x5a46[level];
  return rowsOfColumns;
};
/**
 * @return {undefined}
 */
function verify() {
  checkpass = document[_0x4b5b("0x0")]("pass")[_0x4b5b("0x1")];
  /** @type {number} */
  split = 4;
  if (checkpass[_0x4b5b("0x2")](0, split * 2) == _0x4b5b("0x3")) {
    if (checkpass[_0x4b5b("0x2")](7, 9) == "{n") {
      if (checkpass[_0x4b5b("0x2")](split * 2, split * 2 * 2) == _0x4b5b("0x4")) {
        if (checkpass[_0x4b5b("0x2")](3, 6) == "oCT") {
          if (checkpass[_0x4b5b("0x2")](split * 3 * 2, split * 4 * 2) == _0x4b5b("0x5")) {
            if (checkpass["substring"](6, 11) == "F{not") {
              if (checkpass[_0x4b5b("0x2")](split * 2 * 2, split * 3 * 2) == _0x4b5b("0x6")) {
                if (checkpass[_0x4b5b("0x2")](12, 16) == _0x4b5b("0x7")) {
                  alert(_0x4b5b("0x8"));
                }
              }
            }
          }
        }
      }
    }
  } else {
    alert(_0x4b5b("0x9"));
  }
}
;

 

 

처음에는 어떤 내용인지 제대로 이해를 못했는데 구글링하면서 모르는 부분을 공부하니까 

어떤식으로 함수가 구현이 되어있고 돌아가는지를 파악할 수 있었다.

 

 

우선 verify()함수는 onclick으로 input태그의 중 id가 pass인 태그의 value값을 가져옴을 유추할 수 있다.

왜냐하면 함수중에 html 태그에 값이 정의되어 있는 함수가 없기 때문이다.

그럼 어떤 값이 값을 받아오는 함수인지를 찾아야하는데 checkpass에 값을 리턴하는 문장이 수상하다.

document[_0x4b5b("0x00")]("pass")[_0x4b5b("0x1")];

 

위의 배열에 선언되어 있는 값을 통해 유추하면 

[0x4b5b("0x0")]은 getElementByID라고 생각되고

[0x4b5b("0x1")]은 value라고 생각되었다.

 

 

결과적으로 배열의 순서가 다음과 같이 바뀌었음을 예상했다.

 

 

 

그리고 verify() 함수에서 조건문으로 정리된 내용을 조합해보면 다음과 같이 플래그 값을 찾을 수 있다.

 

결과적으로 정답을 찾게 되었다. 하지만 그 과정이 불확실했기 때문에 난독화 과정을 분석해 보았다.

 

 

 

 

 

분석과정

 

우선 function(data, i) 함수를 알아보았다. 이 함수는 (  ) 안에

먼저 선언되고 선언부 뒤에 바로 (_0x5a46, 435)가 와서 사용됨을 알 수 있다.

 

(function(data, i) 
{
  var write = function(isLE) {
    for (; --isLE;) {
      data["push"](data["shift"]());
    }d
  };
  write(++i);
})
(_0x5a46, 435);

이름없는 함수안에서는 다시 write함수가 선언되어 

데이터의 shift와 push가 반복적으로 일어나 순서를 뒤바꾸고 있다.

 

처음 이 코드를 봤을 때 data["push"]( data["shift"]() );

형식을 이해하지 못해 많이 헤매서 찾아봤다. 

위 코드든 data.push(data.shift()) 와 같은 의미이다.

 

shift는 data의 맨 앞 배열을 잘라서 리턴하고 배열에는 맨 앞 인덱스를 제외한 나머지가 남는다.

Ex) arr[3] = { "hello" , "world", "it's me!" }
      var show = arr["shift"]();
     show : "hello""
     arr  : {"world", "it's me!"}

 

 

push는 배열의 뒤에 인자값으로 받은 배열을 붙여주는 역할을 한다.

Ex) arr [100] = ["1", "2", "3"];
    arr2[5] = ["4"];
arr[push("arr2")];

--> arr : ["1", "2", "3", "4"]

 

따라서 write 함수는 문자열의 값들을 바퀴처럼 계속해서 위치를 하나씩 바꿔주는 함수이다.

 

 

 

 

마지막으로 _0x4b5b는 인자로 받은 데이터를 반환해주는 함수이다.

var _0x4b5b = function(level, ai_test) {
  /** @type {number} */
  level = level - 0;
  var rowsOfColumns = _0x5a46[level];
  return rowsOfColumns;
};

그저 알아보기 어렵게 이름을 바꾸어 사용하도록하는 함수인 것 같은데 

왜 굳이  level = level - 0;을 하여 쓸모없는 코드를 넣었는지 모르겠다.

만약 다른 이유가 있다면 알아봐야할 것 같다.

 

 

+ 수정

구글의 개발자 모드로 Javascript 디버깅을 할시에 더 쉽게 풀 수 있는 문제였다.

다음처럼 console.log(_ox4b5b('0x2'))을 입력한다.

그럼 값으로 substring이 나오는 것을 알 수 있다.

이렇게 필요할 때마다 console.log 로 javascript의

변수값을 찍어서 확인하면 아무리 난독화각 되어 있더라고 확인할 수 있다.