Writeups SwampCTF 2018
Giải này mình cảm thấy khá hay, phù hợp với mình (chắc tại trình mình cùi 🙁 ). Mình chỉ làm được tổng 4 bài RE và Pwn (đáng lẽ là 5)
Dragon's Horde (RE)
Bài đầu tiên trong RE và cũng là bài nhiều người làm được nhất. Mình không hiểu tại sao nhiều người làm được đến vậy trong khi mình còn không hiểu program flow của nó như thế nào 😐 Tại mới vào mình quăng nó vào IDA thì mấy vài hảm khả nghi
Xem thử mã giả của một trong số đó
1 2 3 4 |
int foo1(void) { return std::__cxx11::basic_string<char,std::char_traits,std::allocator>::operator+=(&f, 102); } |
Để ý đến số 102
thì nó chính là kí tự f
trong bảng mã ASCII nên mình đoán luôn mỗi hàm foo1
đến foo16
sẽ chứa một kí tự của flag.
1 2 3 |
a = [102, 108, 97, 103, 123, 114, 51, 118, 95, 49, 116, 95, 117, 112, 125] print ''.join(map(chr,a)) # flag{r3v_1t_up} |
Journey (RE)
Đầu tiên phải kiểm tra nó là file gì đã
1 2 3 |
$ file journey journey: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped |
Kéo vào IDA xem thử có gì hot ở trỏng
Chương trình gì mà có mỗi 2 hàm, đã vậy newbie như mình nhìn cái graph mà muốn khóc. Minh nghe các bro bảo RE hơn nhau ở sự kiên trì nên mình cứ debug F8 liên tục, rồi bất ngờ phát hiện ra điều thú vị ở thanh ghi EIP
Thấy ngay giá trị 0x0804...
là hơi quen quen. Thường virtual address của mấy file ELF cũng bắt đầu bằng 0x0804...
Nghi nghi chương trình này bị packed rồi mà lúc đó mình không nghĩ đến việc tìm tên packer mà lại lọ mọ debug tiếp mới vcl :'(. Ấn thử Ctrl+G
xem nó có sinh ra những segment nào và nhảy đến để kiếm tra :))
Thấy cái signature là biết ngay mà nó bị packed rồi. Lúc đó mình hồn nhiên dump memory theo segments window của IDA mới vcl lần 2 :))
Sau khi dump thì mình dùng lệnh readelf -a dump.bin
thì chỉ có được nhiêu đây thông tin
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 |
$ readelf -a dump.bin ELF Header: Magic: 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - GNU ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048736 Start of program headers: 52 (bytes into file) Start of section headers: 724056 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 6 Size of section headers: 40 (bytes) Number of section headers: 31 Section header string table index: 28 readelf: Error: Reading 0x4d8 bytes extends past end of file for section headers readelf: Error: Section headers are not available! Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0xa08c7 0xa08c7 R E 0x1000 LOAD 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x01024 0x01e28 RW 0x1000 NOTE 0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R 0x4 TLS 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x00010 0x00028 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x000a4 0x000a4 R 0x1 There is no dynamic section in this file. Displaying notes found at file offset 0x000000f4 with length 0x00000044: Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 2.6.32 GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: a8fa0fd0707985f001f0ccb144fd599c3d8f6387 |
Mất mát một số thứ nhưng nó vẫn để lại Entry point address: 0x8048736
là ngon rồi. Mình nhảy đến entry point xem có gì (trong IDA ấn g
để nhảy đến địa chỉ mong muốn, rồi ấn c
để disassemble nó)
Để ý ngay lúc này có 4 cái địa chỉ là loc_80487F0
, loc_8049750
, loc_804887C
và unk_8049020
. Bằng yếu tố tâm linh mình xác định được ngay loc_804887C
chính là hàm main
(thực ra là mình click vào từng cái rồi kiểm tra string =)))
Nhảy đến địa chỉ 0x804887C
rồi ấn c
rồi ấn p
rồi ấn F5
ta được mã giả hàm main
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 |
int sub_804887C() { char v0; // ST18_1 signed int v1; // edx int result; // eax int v3; // ecx unsigned int v4; // et1 int i; // [esp+0h] [ebp-38h] int v6; // [esp+4h] [ebp-34h] int v7; // [esp+10h] [ebp-28h] signed int v8; // [esp+14h] [ebp-24h] char v9[18]; // [esp+1Ah] [ebp-1Eh] unsigned int v10; // [esp+2Ch] [ebp-Ch] v10 = __readgsdword(0x14u); ((void (__cdecl *)(const char *))unk_804F640)("As you play this game, there will be many adventures for you to take, quests on the side of this great journey."); ((void (__cdecl *)(const char *))unk_804F640)( "This is one of the quests here, and you may not know it yet. You will know when you complete the test, because all o" "f the die will have been cast in your favor."); ((void (__cdecl *)(const char *))unk_804F640)("Prove your worth, enter a password to continue!"); ((void (*)(const char *, ...))unk_804F070)("%17s", v9); v6 = ((int (__cdecl *)(char *))unk_805B8C0)(v9); v7 = 1741837201; v8 = 3313467; for ( i = 0; i < v6; ++i ) { v0 = ((int (__cdecl *)(int, signed int, signed int, _DWORD))unk_8048B60)(v7, v8, 10, 0); v7 = ((int (__cdecl *)(int, signed int, signed int, _DWORD))unk_80489F0)(v7, v8, 10, 0); v8 = v1; v9[i] -= v0; } if ( ((int (__cdecl *)(char *, const char *))unk_8048280)(v9, "theresanotherstep") ) { ((void (__cdecl *)(const char *))unk_804F640)("Mission failed! You must try again, giving up was never an answer if you have gotten this far!"); } else { ((void (__cdecl *)(const char *))unk_804F640)( "Congratulations, you have reached the next stage in the mission. As close as you are, you can almost smell the perfect role!"); ((void (__cdecl *)(const char *))unk_804F640)( "However, no tricky business is allowed. It's about the journey, not the destination, so the password you entered does matter."); ((void (__cdecl *)(const char *))unk_804F640)("In fact, the password is the answer! Submit in the form flag{...}"); } result = 0; v4 = __readgsdword(0x14u); v3 = v4 ^ v10; if ( v4 != v10 ) result = ((int (__fastcall *)(int))unk_806F6F0)(v3); return result; } |
Hình như file này sử dụng static linking thì phải, mình thử ấn vào hàm in chuỗi unk_804F640
thì lại lòi ra một đống mã assembly khác. Khá là bối rối, không biết tên hàm thì làm sao mà biết hàm đó làm gì, giờ chỉ còn mỗi một cách là đoán thôi :))
Để ý dòng từ dòng 32, if (unk_8048280(v9, "theresanotherstep"))
thì đây chính là điều kiện để mình có được flag. Thực hiện trace ngược từ dòng 32 trở lên thì v9
phụ thuộc vào v0
và v9
cũng chính là input, v0 = unk_8048B60(v7, v8, 10, 0)
với v7
và v8
có giá trị ban đầu là hằng số. Vậy cách giải quyết khá đơn giản, lưu hết lần lượt các giá trị của v0
rồi sau đó lấy mã ASCII của từng kí tự trong chuỗi theresanotherstep
cộng với các giá trị v0
tương ứng
Code:
1 2 3 4 5 6 7 8 9 |
text = 'theresanotherstep' v0_list = [3, 3, 4, 2, 1, 2, 3, 4, 1, 4, 3, 2, 1, 3, 2, 4, 1] flag = '' for i in range(len(text)): flag += chr(ord(text[i]) + v0_list[i]) print(flag) # wkitfudrpxkgsvviq |
2 bài Pwn mình hơi lười viết (tại trình cùi) nên chỉ nói sơ sơ qua thôi.
Apprentice's Return (Pwn)
Chương trình cho mình sẵn mình lỗi buffer overflow ngay tại hàm doBattle
để có thể control được return address của hàm đó. Tuy nhiên, khi overwrite return address thì phải thỏa điều kiện retaddr < 0x8048595
. Để ý ngoài việc có thể control được return address thì mình có thể điều khiển luôn của cả saved EBP
Để ý dòng có gạch đỏ, từ việc điều được EBP thì mình có thể điều khiển được địa chỉ lưu buffer của hàm read
. Vậy mình sẽ ghi đè giá trị của puts
trong bảng GOT với giá trị là 0x8046812
để in ra flag.
Power QWORD (Pwn)
Bài này bắt phải dùng ropgadget rồi. Lúc bắt đầu làm bài này tự dưng mình lại muốn đi tắm vì nóng quá, tự dưng trong lúc tắm lại nhớ đến one_gadget Và thế là xong :)) giải bài này một cách nhanh gọn :))
Note:
Sau khi mình đọc lại writeup của các đội khác thì thấy câu journey là packer là upx =)) gõ lệnh 1 phát là unpack được, cay =))