Chào mừng đến với con đường không lối thoát # Giới thiệu Pwn là một lĩnh vực trong các cuộc thi CTF chuyên về khai thác lỗ hổng bảo mật ở một chương trình. Khác với web, khi bắt đầu cần phải học cách đọc các ngôn ngữ lập trình dùng để làm web, muôn hình muôn vạn tùy theo bài, học thêm cả SQL...; để bắt đầu chơi pwn chỉ cần biết C (và hiếm lắm mới gặp C++), ngôn ngữ đã được dạy rất kỹ ở Nhập môn lập trình/Cơ sở lập trình, là đã có thể đọc hiểu được đề, còn lại có thể bổ sung sau trong quá trình chơi. ## Đặc Điểm Của Một Bài Pwn Điển Hình Trong một bài pwn, người ra challenge thường cung cấp: - Một file chương trình (thường là file ELF trong môi trường Linux) - Đôi khi author tốt bụng còn cho cả file code gốc của chương trình. - Có thể có thêm Dockerfile; hoặc các file thư viện libc, ld; các hướng dẫn môi trường - Một server chạy chương trình y hệt, và flag được giấu ở đó. ## Quy Trình Giải Bài 1. **Kết Nối Với Server** - Khi tạo instance, bạn sẽ nhận được một địa chỉ IP và port. VD: ![image](https://hackmd.io/_uploads/B10F7h3Ikx.png) - Dùng lệnh `nc <IP> <PORT>` để kết nối và tương tác với chương trình. - Lưu ý, khi bạn kết nối như vậy, chương trình chạy trên server, exploit được chương trình ấy thì có thể đọc các file trên server (có thể thấy nó tựa tựa như web, nhưng thay vì server cho bạn một service web, thì cho bạn service là một process file ELF) 2. **Khai Thác** - Phân tích chương trình trên máy local - Viết script python để exploit, sử dụng file binary trên máy mình để test trong khi viết script. - Sau khi hoàn thành, chạy script đó trên remote instance để lấy flag ## Công Cụ Cần Thiết Cho Người Mới Để bắt đầu với Pwn, bạn cần các công cụ sau: - `pwninit`: Công cụ hỗ trợ thiết lập môi trường - `pwntools`: Thư viện Python mạnh mẽ cho việc exploit - `pwndbg`: Tiện ích mở rộng cho GDB, hỗ trợ debug Và vì hầu hết pwn đều là file ELF, ta dùng WSL để chạy và exploit. # Pwninit `pwninit` thường là lệnh mọi người sẽ chạy đầu tiên sau khi tải challenge pwn về. ## Setup & config ### 1. Cài đặt `patchelf` ```bash sudo apt-get install patchelf ``` ### 2. Tải file binary `pwninit` pwninit bản gốc bị một số vấn đề đối với các bản libc gần đây, ta nên tải file của `@robbert1978` dùng cho tiện ```bash wget https://github.com/robbert1978/pwninit/releases/download/3.2.0.1/pwninit chmod +x pwninit sudo mv pwninit /usr/bin/ ``` ### 3. Chỉnh sửa template mặc định: ```bash mkdir -p ~/.config nano ~/.config/pwninit-template.py ``` Template mẫu mình hay dùng: ```python= #!python3 from pwn import * {bindings} context.binary = {bin_name} # Using tmux context.terminal = ['tmux', 'splitw', '-h', '-F' '#{{pane_pid}}', '-P'] # context.terminal = 'wt.exe sp -d . wsl.exe -d Ubuntu-22.04'.split() # Using windows terminal split, set "New Instance Behavior" to Attach to... remote_connection = "nc addr 5000".split() local_port = 1337 gdbscript = ''' ''' info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sna = lambda msg, data: p.sendlineafter(msg, str(data).encode()) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) sn = lambda data: p.sendline(str(data).encode()) s = lambda data: p.send(data) def start(): if args.REMOTE: return remote(remote_connection[1], int(remote_connection[2])) elif args.LOCAL: return remote("localhost", local_port) elif args.GDB: return gdb.debug({proc_args}, gdbscript=gdbscript) else: return process({proc_args}) def GDB(): if not args.LOCAL and not args.REMOTE: gdb.attach(p, gdbscript=gdbscript) pause() p = start() # your exploit here p.interactive() ``` ### 4. Thêm alias vào `.bashrc`: ```bash alias pwninit='pwninit --template-path ~/.config/pwninit-template.py --template-bin-name exe' ``` ## Dùng ### Sau khi tải challenge Mình sẽ chạy trên bài `babyrop`, nó có các file `babyrop`, `libc`, `ld`: ![image](https://hackmd.io/_uploads/S1loX23LJg.png) Sau khi chạy, bạn sẽ có: * file `babyrop_patched` sử dụng thư viện libc và loader giống với trên remote instance. * file solve.py để viết exploit script, dùng thư viện pwntools mà ta sẽ tìm hiểu ở dưới đây. ### Sửa script Xem lại dòng số 7 trong file solve.py mới tạo ra, dòng này chứa config remote instance. ```python remote_connection = "nc addr 5000".split() ``` Lúc ấn start instance, bạn sẽ nhận được IP và PORT, hay tốt hơn nữa là nguyên cả câu lệnh, như trong ảnh ở trên. Bạn copy lệnh đó paste vào thế chỗ `nc addr 5000`, chẳng hạn như ví dụ trên: ```python remote_connection = "nc 34.170.146.252 55229".split() ``` ### Chạy exploit Script template ở trên có các cách dùng sau: * Chạy local, không GDB, tức là chạy chính file binary trên máy mình luôn: ```bash python3 solve.py DEBUG ``` * Chạy local, có GDB, cách chạy này hay dùng nhất để debug script exploit: ```bash python3 solve.py GDB DEBUG ``` Nó chia đôi màn hình ra thế này, bên phải là pwndbg, bên trái xem send & receive. ![image](https://hackmd.io/_uploads/SkmXEhhL1l.png) * Chạy remote, giao tiếp với remote instance, thường chạy khi đã xong bài =)): ```bash python3 solve.py REMOTE DEBUG ``` Chữ `DEBUG` ở các lệnh trên để cho thấy script đã gửi và nhận những gì, cho dễ theo dõi tiến độ hơn, như thế này: ![image](https://hackmd.io/_uploads/r1BT73nL1l.png) Không muốn những thứ đó tràn màn hình thì ta bỏ đi cũng được. # Pwntools Đây là một thư viện của python3 gần như bài pwn nào cũng dùng. ## Cài đặt Cách cài đặt rất đơn giản https://docs.pwntools.com/en/dev/install.html ```bash= sudo apt-get update sudo apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential python3 -m pip install --upgrade pip python3 -m pip install --upgrade pwntools ``` ## Các hàm hay dùng ### Nhận - `p.recvline()`: Nhận một dòng, giá trị trả về bao gồm cả ký tự xuống dòng ```python data = p.recvline() ``` - `p.recvuntil(b'text')`: Nhận dữ liệu cho đến khi gặp một chuỗi nhất định, mặc định là giữ luôn chỗi text ấy, có thể thêm `drop=True` để bỏ. ```python # VD instance gửi "Blackpinker loves pwner\nWelcome: " response = p.recvuntil(b'Welcome: ') # response <-- b'Blackpinker loves pwner\nWelcome: ' response2 = p.recvuntil(b'Welcome: ', drop=True) # response2 <-- b'Blackpinker loves pwner\n' ``` - `p.recv(num_bytes)`: Nhận một số lượng byte cụ thể ```python data = p.recv(1024) # Nhận 1024 byte ``` ### Gửi - `p.sendlineafter(b'prompt', b'data')`: Gửi dòng dữ liệu sau một prompt cụ thể ```python p.sendlineafter(b'Enter name: ', b'pwner') # Gửi dòng 'pwner' sau prompt 'Enter name: ' ``` - `p.sendafter(b'prompt', b'data')`: Gửi dữ liệu sau một prompt cụ thể (không thêm ký tự xuống dòng) ```python p.sendafter(b'Input: ', b'payload') ``` - `p.sendline(b'data')`: Gửi dòng dữ liệu (có ký tự xuống dòng) ```python p.sendline(b'Hello World') ``` - `p.send(b'data')`: Gửi dữ liệu không thêm ký tự xuống dòng ```python p.send(b'payload') ``` ### Xử lý data - `string.encode()`, `bytestring.decode()`: Chuyển đổi giữa string và byte string, các hàm `send` và `recv` ở trên đều dùng tham số là byte string, và trả về byte string. ```python text = "Hello".encode() # Chuyển chuỗi sang byte string = text.decode() # Chuyển byte sang chuỗi ``` - `p64()`, `p32()`: Đóng gói số nguyên thành byte little-endian ```python packed_64 = p64(0x1234) # Đóng gói số 64-bit # --> b'\x34\x12\x00\x00\x00\x00\x00\x00' packed_32 = p32(0x1234) # Đóng gói số 32-bit # --> b'\x34\x12\x00\x00' ``` - `u64()`, `u32()`: Giải mã chuỗi byte thành số nguyên, mặc định của nó là little endian, rất hay dùng trong việc lấy địa chỉ bị leak ra dưới dạng chuỗi. ```python number_64 = u64(b'\x34\x12\x00\x00\x00\x00\x00\x00') number_32 = u32(b'\x34\x12\x00\x00') ``` - `flat()`: Tự động p64/p32 các tham số integer và nối chúng lại với nhau, đỡ viết `+= p64(...)` nhiều lần, hay dùng để tạo payload ROP. VD payload ROP của một challenge 32-bit: ```python payload = flat( b'A'*100, # Phần padding 0x08048500, # Địa chỉ return address b'/bin/sh' # Chuỗi để thực thi ) ``` ### Khác - `p.interactive()`: chuyển từ giao tiếp bằng script sang giao tiếp bằng bàn phím. - `cyclic(length, n=4/8)`: Thay vì spam 'AAAA...' thì tạo chuỗi cyclic để tính offset buffer overflow ```python payload = cyclic(100) # Tạo chuỗi cyclic 100 byte payload = cyclic(100, n=8) # Cho hệ thống 64-bit ``` Sau đó trong pwndbg: `cyclic -l <8 ký tự>` để tìm offset. # Pwndbg ## Cài đặt Mọi người cài theo [link repo gốc]( https://github.com/pwndbg/pwndbg ), hoặc copy mấy lệnh này: ```bash git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh ``` ## Các lệnh hay dùng Đây là các lệnh trong pwndbg, không phải ở ngoài bash shell thường nha. Trong pwndbg nó prompt `pwndbg>` như vầy nè: ![image](https://hackmd.io/_uploads/HkX072hUye.png) Sau đây là các lệnh đã sắp xếp từ trên xuống dưới theo mức độ quan trọng, không cần nhớ hết, khi nào cần bạn quay trở lại tra cứu cũng được. ### Lệnh xem thông tin chương trình `checksec` Chắc chắn phải chạy lệnh này để xem bài thế nào: NX?, PIE?, canary?, parital/full RELRO? ### Lệnh breakpoint - `b`: set a breakpoint - `brva`: set a breakpoint with PIE base - `i b`/`info breakpoint`: list breakpoints - `del [<breakpoint>]`: delete breakpoints ### Lệnh điều khiển chương trình hay dùng - `start`: run and stop program at the first found symbol from: main, _main, start, _start, init, _init or entry. Dùng để bắt đầu chương trình và dừng lại trước khi chạy các lệnh chính mà không phải đặt breakpoint. - `si`: step into - `c`: continue - `n`: next - `fin`/`finish`: run the program until the current function returns. Hay dùng khi step vào các hàm dài ngoằn của libc. ### Lệnh memory * `tel <where>`: examine memory dereferencing valid pointers. * `vmmap`: display memory mappings information * `search`: search memory for a given value ![image](https://hackmd.io/_uploads/HJgkNnhIyx.png) - `p`/`print`: Print value of expression EXP ```c! pwndbg> p 0xffffd152 $1 = 4294955346 pwndbg> p/x 0xffffd152 $2 = 0xffffd152 pwndbg> p (char*)0xffffd152 $3 = 0xffffd152 "/mnt/d/repo/CTF/pwnable.tw/calc/calc" pwndbg> p main $4 = {<text variable, no debug info>} 0x8049452 <main> ``` - `xi`/`xinfo`: show offsets of the specified address from various useful locations ![image](https://hackmd.io/_uploads/HkuyEhhIJe.png) * `p2p <mapping_names> <mapping_names>`: pointer to pointer chain search. ![image](https://hackmd.io/_uploads/HJgxEh381l.png) ### Lệnh ELF - `got`: print symbols in the .got.plt section - `plt`: print symbols in the .plt section ### Lệnh heap - `vis`: visualize chunks on a heap - `bins`: print contents of all arena bins and thread's tcache - `heap`: iteratively print chunks on heap (glibc only) ### Các lệnh khác - `canary`: print the global stack canary/cookie value and finds canaries on the stack. - `bt`/`backtrace`: print backtrace of all stack frames - `fr`/`frame`: Select a stack frame. Khi chương trình crash trong lúc debug, có thể dùng `bt` và `fr` để tìm hiểu nguyên nhân. - `handle <SIGNAL> <action>`: Specify how to handle signals. Thường dùng `handle SIGALRM ignore` vì đề CTF hay cho timeout trong binary. # Các tool khác ### [ROPgadget](https://github.com/JonathanSalwan/ROPgadget)/[ropr](https://github.com/Ben-Lichtman/ropr): tìm gadget ```sh! $ ropr food_store -nsj -R"^pop" 0x0000369b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret; 0x0000369c: pop r12; pop r13; pop r14; pop r15; ret; 0x0000369d: pop rsp; pop r13; pop r14; pop r15; ret; 0x0000369e: pop r13; pop r14; pop r15; ret; 0x0000369f: pop rbp; pop r14; pop r15; ret; 0x000036a0: pop r14; pop r15; ret; 0x000036a1: pop rsi; pop r15; ret; 0x000036a2: pop r15; ret; 0x000036a3: pop rdi; ret; ``` ### [seccomp-tools](https://github.com/david942j/seccomp-tools): seccomp analysis ```sh! $ seccomp-tools dump ./food_store line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0012 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0012: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0014 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0014: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0016 0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0016: 0x15 0x00 0x01 0x00000014 if (A != writev) goto 0018 0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0018: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0020 0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0020: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0022 0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0022: 0x15 0x00 0x01 0x0000000b if (A != munmap) goto 0024 0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0024: 0x06 0x00 0x00 0x00000000 return KILL ``` ### [decomp2dbg](https://github.com/mahaloz/decomp2dbg) A plugin to introduce interactive symbols into your debugger from your decompiler Thêm biến `PYTHONPATH` vì gdb mặc định sử dụng python từ `/usr/bin` ```sh! PYTHONPATH=<env_path>/lib/python3.12/site-packages gdb ``` <!-- # Demo ## cơ bản ## gdb.debug ## gdb.attach # nâng cao ## debug từ docker container ## debug cross platform -->