요즘은 해킹 분야보다는 블록체인에 관심이 많이 생겨서 해킹 공부를 쉰 지 조금 되었다.. 이번에 처음으로 동아리 사람들과 모여서 함께 CTF 문제를 풀어보는 시간을 가졌는데, 뭔가 나 혼자 뒤쳐진 느낌이 들었다. 뭔가 스스로 한심하기도 하고 ~ 다시 열심히 해야겠다는 생각이 들어서 지금까지 했던 모든 포스팅을 지우고 나서 다시 초심을 찾아보려고 한다. 파이팅..!! 😊
🖲️ Code Analysis
크립토 문제답지 않게 PHP 로 작성된 웹 페이지를 제공해주었다. 먼저 HTML 소스를 보도록 하자.
( 내가 문제를 아카이빙 해서 comibear.kr 에 그대로 만들어 놓았는데, 직접 해보고 싶은 사람은 문제 사이트로 가서 직접 풀어보도록 하자 > < )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mt. Random</title>
</head>
<body>
<h1>Hiking Guide</h1>
<p>This mountain is boring, I'm going to sample alot of seeds!</p>
<a href="?generate_samples=1">Get a new sample</a>
</body>
</html>
일단 첫번째로 HTML 코드에는 별 게 없는 것 같다. 한 가지 기능을 살펴보자면, Get a new sample 이라는 링크를 클릭하면, 파라미터로 generate_samples 을 1 로 설정해주게 된다. generate samples 가 도대체 무슨 기능을 가졌을지 PHP 코드를 살펴보도록 하자.
<?php
session_start();
$flag = "midnight{***redacted***}";
function flag_to_numbers($flag) {
$numbers = [];
foreach (str_split($flag) as $char) {
$numbers[] = ord($char);
}
return $numbers;
}
function non_continuous_sample($min, $max, $gap_start, $gap_end) {
$rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += ($gap_end - $gap_start);
}
return $rand_num;
}
if(!str_starts_with($flag, "midnight{")){
echo "Come back later.\n";
exit();
}
$flag_numbers = flag_to_numbers($flag);
if (isset($_GET['generate_samples'])) {
header('Content-Type: application/json');
// Maybe we can recover these constants
$min = 0;
$max = 0;
$gap_start = 0;
$gap_end = 0;
$seed = mt_rand(0, 10000); // Varying seed
$samples = [];
foreach ($flag_numbers as $number) {
mt_srand($seed + $number);
$samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
}
echo json_encode(["samples" => $samples]);
exit();
}
?>
아까 보았던 generate_smaples 라는 파라미터가 정의되었을 때, if 문이 실행되는 구문이 있다. 여러 변수들을 정의하고, 각 flag 의 문자를 하나의 메르센 트위스터의 시드로써 작동하게 된다.
그 후에, non_continuous_smaple 이라는 함수를 실행하게 되는데, 그 함수에 대해서 간략하게 알아보자.
function non_continuous_sample($min, $max, $gap_start, $gap_end) {
$rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += ($gap_end - $gap_start);
}
return $rand_num;
}
잘 보면, min 부터 max - (gap_end - gap_start) 까지의 랜덤값을 rand_num 으로 정의하게 된다. 하지만, 이 rand_num 이라는 값이 gap_start 보다 크다면, gap_end - gap_start 를 더해주어 gap_end 보다 크도록 만들어준다.
이 말은, min 부터 max 까지의 랜덤값을 추출하지만, gap_start 부터 gap_end 까지의 범위에는 나오지 않도록 설정해주는 기능이라고 볼 수 있다.
결론은, 각 generate 를 할 때마다, min, max 등의 변수값 그리고 seed 값을 사용해서 반복문의 형태로 랜덤값을 출력해주는 기능을 가졌다고 판단할 수 있겠다. ( 여기서 flag 의 바이트들은 seed 에 순차적으로 더해지며 seed 를 랜덤화하게 된다. )
💡 Main Idea
문제 이름은 Mt.Random 이라고 되어 있어서 처음에는 메르센 트위스터를 이용해서 다음 랜덤값을 예측하는 형태의 문제라고 생각했었다. 하지만 다시 생각해보니 적당한 브루트포싱만으로 문제를 해결할 수 있겠다는 생각을 해서 코드를 작성해 보았다.
먼저 랜덤값이 정의될 때, 모든 랜덤값이 0 으로 정의되었기 때문에 정확한 값을 알아줄 필요가 있다. 따라서 1 부터 256 까지의 리스트를 형성하여 절대 나오지 않는 gap 들을 알아볼 것이다. ( 여기서 1 부터 256 이라는 min 과 max 를 정한 이유는, 엄밀하게 따지면 이 또한 코드로써 작성해야 하지만 귀납적인 이유로 생략했다. )
import requests
import json
URL = "http://comibear.kr/?generate_samples=1"
def get():
res = requests.get(URL)
res = json.loads(res.text)
return res['samples']
test = [i for i in range(256)]
while True:
for c in get():
if c in test:
test.remove(c)
print(f"maybe gap is {test[0]} to {test[-1]}")
이 코드를 실행해보면, 다음과 같은 결과가 나오게 되어 gap 은 100 부터 150 까지라고 판단할 수 있게 된다. 결국은 모든 변수를 알아냈기 때문에, flag 브루트 포싱이 가능하게 된다.
이렇게 gap 들을 알아낼 수 있어 결국 임의의 출력값을 통해 seed 를 알아내고, 그 seed 를 사용해 브루트 포싱을 할 수 있게 된다. 브루트 포싱을 통해 seed 를 알아내는 코드를 작성해보자.
<?php
$flag = "midnight{";
$flag_len = strlen($flag);
$ans = [21,7,42,71,7,220,238,154,163,21,252,71,42,57,154,238,185,57,220,35,1,156];
function non_continuous_sample($min, $max, $gap_start, $gap_end) {
$rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += ($gap_end - $gap_start);
}
return $rand_num;
}
function flag_to_numbers($flag) {
$numbers = [];
foreach (str_split($flag) as $char) {
$numbers[] = ord($char);
}
return $numbers;
}
$flag_numbers = flag_to_numbers($flag);
for($new_seed = 0; $new_seed <= 10000; $new_seed++) {
$min = 1;
$max = 256;
$gap_start = 100;
$gap_end = 150;
$dump = mt_rand(0,10000);
$seed = $new_seed;
$samples = [];
foreach ($flag_numbers as $number) {
mt_srand($seed + $number);
$samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
}
if (array_slice($samples,0,$flag_len) == array_slice($ans,0,$flag_len)) {
echo $seed . "\n";
}
}
?>
별 아이디어 없이 그냥 찐 브루트포싱이라 딱히 설명할 게 없다...
따라서 seed 는 8336 이라고 알아낼 수 있다 !!
📖 Exploit Code
<?php
$flag = "midnight{";
$valid = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_{}";
$length = strlen($valid);
$ans = [21,7,42,71,7,220,238,154,163,21,252,71,42,57,154,238,185,57,220,35,1,156];
function non_continuous_sample($min, $max, $gap_start, $gap_end) {
$rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += ($gap_end - $gap_start);
}
return $rand_num;
}
function flag_to_numbers($flag) {
$numbers = [];
foreach (str_split($flag) as $char) {
$numbers[] = ord($char);
}
return $numbers;
}
function dfs($flag) {
global $ans;
global $valid;
global $length;
$flag_len = strlen($flag);
if (strlen($flag) == 22) {
echo $flag; echo "\n";
return;
}
for($new = 0; $new < $length; $new++) {
$newflag = $flag.$valid[$new];
$flag_numbers = flag_to_numbers($newflag);
$min = 1;
$max = 256;
$gap_start = 100;
$gap_end = 150;
$dump = mt_rand(0,10000);
$seed = 8336;
$samples = [];
foreach ($flag_numbers as $number) {
mt_srand($seed + $number);
$samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
}
if ($samples[$flag_len] == $ans[$flag_len]) {
// echo $newflag;
dfs($newflag);
}
}
}
dfs($flag);
?>
따라서 이렇게 코드를 작성해주면, dfs 알고리즘을 이용한 브루트 포싱이 가능할 것이다. 하지만 한 가지 seed 만 가지고 실행했기 때문에 이 ans list 가 출력되는 flag 들이 여러 가지 나올 수 있겠다. 이 경우에는 앞선 seed 만 변화시켜주며 모든 seed 를 만족하는 flag 의 값을 찾아주면 된다.
예상대로 많은 결과가 나왔는데, CTF 에서는 meaningful 한 string 이 flag 일 확률이 높기 때문에
Flag : midnight{m1nd_th3_g4p} 이다.
'Cryptography > CTF' 카테고리의 다른 글
[Kaist-Postech 2020] - Baby Bubmi (0) | 2023.04.18 |
---|---|
[CodeGate 2022] - GIGA Cloud Storage (0) | 2023.04.17 |
[CodeGate 2022] - Hidden Command Service (0) | 2023.04.17 |
[HITCON 2022] - SuperPrime (0) | 2023.04.17 |
[HITCON 2022] - Babysss (0) | 2023.04.17 |