Câu chuyện về GDB
Dạo gần đây mình bắt đầu nghiêm túc tìm hiểu về heap exploitation. Ắt hẳn bạn nào mới tìm hiểu về exploit trên linux đều thường sử dụng GDB. Tuy nhiên, heap exploitation khá là chua so với stack exploitation :)) Bạn phải biết sử dụng GDB nâng cao hơn chút, ngoài việc chỉ biết các lệnh b
, c
, x
, n
, s
, p
. Bài viết của mình nhằm mục đích bôi trơn, giảm độ chua khi debug một chương trình (bao gồm glibc). Tất nhiên mình chỉ là một newbie nên mình cũng chỉ chia sẻ được một chút thôi 😀
Hiện tại có rất nhiều plugin hỗ trợ cho gdb như peda, pwndbg, gef,... Mình thì chỉ sử dụng mỗi peda, nhưng gần đây mình muốn tìm hiểu thêm heap exploitation nên cài thêm pwngdb. Cả pwndbg và gef cũng đều hỗ trợ cho việc phân tích heap nhưng 2 cái đó có nhiều tính năng quá, trình mình cùi nên không biết xài 🙁
Debug with source
GDB có tính năng ánh xạ lệnh asm tại địa chỉ 0xAAAA sẽ tương ứng với dòng lênh C nào trong file source. Tuy nhiên không phải lúc nào GDB cũng có thể ánh xạ được, bạn phải có source code và debug symbols của chương trình. Nếu bạn có source code của chương trình, khi compile bằng gcc
hoặc g++
thêm option -g
để có debug symbols.
Ví dụ mình có chương trình sau:
1 2 3 4 5 6 |
#include <stdio.h> int main() { printf("Hello world!"); return 0; } |
Tiến hành compile và debug bằng gdb
1 2 |
gcc -m32 -g -o helloworld helloworld.c gdb helloworld -ex "b hellworld.c:4" -ex "r" |
GDB sẽ hiển thị kiểu như này
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
[----------------------------------registers-----------------------------------] EAX: 0xf7fb6dbc --> 0xffffcf6c --> 0xffffd170 ("LC_PAPER=de_CH.UTF-8") EBX: 0x0 ECX: 0xffffced0 --> 0x1 EDX: 0xffffcef4 --> 0x0 ESI: 0xf7fb5000 --> 0x1afdb0 EDI: 0xf7fb5000 --> 0x1afdb0 EBP: 0xffffceb8 --> 0x0 ESP: 0xffffceb0 --> 0xf7fb53dc --> 0xf7fb61e0 --> 0x0 EIP: 0x804841c (<main+17>: sub esp,0xc) EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048416 <main+11>: mov ebp,esp 0x8048418 <main+13>: push ecx 0x8048419 <main+14>: sub esp,0x4 => 0x804841c <main+17>: sub esp,0xc 0x804841f <main+20>: push 0x80484c0 0x8048424 <main+25>: call 0x80482e0 <[email protected]> 0x8048429 <main+30>: add esp,0x10 0x804842c <main+33>: mov eax,0x0 [------------------------------------stack-------------------------------------] 0000| 0xffffceb0 --> 0xf7fb53dc --> 0xf7fb61e0 --> 0x0 0004| 0xffffceb4 --> 0xffffced0 --> 0x1 0008| 0xffffceb8 --> 0x0 0012| 0xffffcebc --> 0xf7e1d637 (<__libc_start_main+247>: add esp,0x10) 0016| 0xffffcec0 --> 0xf7fb5000 --> 0x1afdb0 0020| 0xffffcec4 --> 0xf7fb5000 --> 0x1afdb0 0024| 0xffffcec8 --> 0x0 0028| 0xffffcecc --> 0xf7e1d637 (<__libc_start_main+247>: add esp,0x10) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, main () at helloworld.c:4 4 printf("Hello world!"); gdb-peda$ |
Để ý dòng thứ 16, 33 và 34. Lệnh tại địa chỉ 0x804841c
tương ứng với printf("Hello world!")
(tất nhiên còn một số lệnh phía sau nó cũng tương ứng với printf
)
Còn nếu không có source code thì sao? Ví dụ như bạn muốn debug glibc như mình :v Đơn giản thôi, Ubuntu cung cấp sẵn gói debug symbols và source code luôn rồi, bạn chỉ cần cài đặt là được.
1 2 3 |
$ sudo apt install libc6-dbg $ sudo apt install libc6-dbg:i386 # cài gói debug symbols của 32-bit nếu máy bạn 64-bit $ sudo apt install libc6-source |
Giờ thử debug glibc của fastbin_dup_consolidate.c
trong https://github.com/shellphish/how2heap
1 |
gdb fastbin_dup_consolidate -ex "b fastbin_dup_consolidate.c:12" -ex "r" -ex "s" |
Thì sẽ ra thế này:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
[----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7b04200 (<__openat_2+16>: cmp eax,0x410000) RDX: 0x0 RSI: 0x7ffff7dd1b40 --> 0x602000 --> 0x0 RDI: 0x400 RBP: 0x7fffffffdc60 --> 0x400790 (<__libc_csu_init>: push r15) RSP: 0x7fffffffdc28 --> 0x4006c0 (<main+122>: mov QWORD PTR [rbp-0x18],rax) RIP: 0x7ffff7a91130 (<__GI___libc_malloc>: push rbp) R8 : 0x602010 --> 0x0 R9 : 0x7ffff7dd2500 --> 0x7ffff7b9b997 --> 0x636d656d5f5f0043 ('C') R10: 0x8b8 R11: 0x7ffff7a914f0 (<__GI___libc_free>: push r13) R12: 0x400550 (<_start>: xor ebp,ebp) R13: 0x7fffffffdd40 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x7ffff7a91120 <__malloc_check_init+96>: mov DWORD PTR [rip+0x34269e],0x0 # 0x7ffff7dd37c8 <disallow_malloc_check> 0x7ffff7a9112a <__malloc_check_init+106>: ret 0x7ffff7a9112b: nop DWORD PTR [rax+rax*1+0x0] => 0x7ffff7a91130 <__GI___libc_malloc>: push rbp 0x7ffff7a91131 <__GI___libc_malloc+1>: push rbx 0x7ffff7a91132 <__GI___libc_malloc+2>: sub rsp,0x8 0x7ffff7a91136 <__GI___libc_malloc+6>: mov rax,QWORD PTR [rip+0x33fdb3] # 0x7ffff7dd0ef0 0x7ffff7a9113d <__GI___libc_malloc+13>: mov rax,QWORD PTR [rax] [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdc28 --> 0x4006c0 (<main+122>: mov QWORD PTR [rbp-0x18],rax) 0008| 0x7fffffffdc30 --> 0x0 0016| 0x7fffffffdc38 --> 0x602010 --> 0x0 0024| 0x7fffffffdc40 --> 0x602060 --> 0x0 0032| 0x7fffffffdc48 --> 0x400550 (<_start>: xor ebp,ebp) 0040| 0x7fffffffdc50 --> 0x7fffffffdd40 --> 0x1 0048| 0x7fffffffdc58 --> 0x0 0056| 0x7fffffffdc60 --> 0x400790 (<__libc_csu_init>: push r15) [------------------------------------------------------------------------------] Legend: code, data, rodata, value __GI___libc_malloc (bytes=0x400) at malloc.c:2902 2902 malloc.c: No such file or directory. gdb-peda$ |
Dòng 40, 41 báo lỗi vì mình chưa chỉ cho GDB biết file malloc.c
nằm ở đâu. Giả sử source code glibc của mình ở ~/glibc-2.23
thì chỉ cần thêm lệnh directory ~/glibc-2.23/malloc
cho GDB tìm file malloc.c
1 |
gdb fastbin_dup_consolidate -ex "b fastbin_dup_consolidate.c:12" -ex "r" -ex "directory ~/glibc-2.23/malloc" -ex "s" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
[----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7b04200 (<__openat_2+16>: cmp eax,0x410000) RDX: 0x0 RSI: 0x7ffff7dd1b40 --> 0x602000 --> 0x0 RDI: 0x400 RBP: 0x7fffffffdc60 --> 0x400790 (<__libc_csu_init>: push r15) RSP: 0x7fffffffdc28 --> 0x4006c0 (<main+122>: mov QWORD PTR [rbp-0x18],rax) RIP: 0x7ffff7a91130 (<__GI___libc_malloc>: push rbp) R8 : 0x602010 --> 0x0 R9 : 0x7ffff7dd2500 --> 0x7ffff7b9b997 --> 0x636d656d5f5f0043 ('C') R10: 0x8b8 R11: 0x7ffff7a914f0 (<__GI___libc_free>: push r13) R12: 0x400550 (<_start>: xor ebp,ebp) R13: 0x7fffffffdd40 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x7ffff7a91120 <__malloc_check_init+96>: mov DWORD PTR [rip+0x34269e],0x0 # 0x7ffff7dd37c8 <disallow_malloc_check> 0x7ffff7a9112a <__malloc_check_init+106>: ret 0x7ffff7a9112b: nop DWORD PTR [rax+rax*1+0x0] => 0x7ffff7a91130 <__GI___libc_malloc>: push rbp 0x7ffff7a91131 <__GI___libc_malloc+1>: push rbx 0x7ffff7a91132 <__GI___libc_malloc+2>: sub rsp,0x8 0x7ffff7a91136 <__GI___libc_malloc+6>: mov rax,QWORD PTR [rip+0x33fdb3] # 0x7ffff7dd0ef0 0x7ffff7a9113d <__GI___libc_malloc+13>: mov rax,QWORD PTR [rax] [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdc28 --> 0x4006c0 (<main+122>: mov QWORD PTR [rbp-0x18],rax) 0008| 0x7fffffffdc30 --> 0x0 0016| 0x7fffffffdc38 --> 0x602010 --> 0x0 0024| 0x7fffffffdc40 --> 0x602060 --> 0x0 0032| 0x7fffffffdc48 --> 0x400550 (<_start>: xor ebp,ebp) 0040| 0x7fffffffdc50 --> 0x7fffffffdd40 --> 0x1 0048| 0x7fffffffdc58 --> 0x0 0056| 0x7fffffffdc60 --> 0x400790 (<__libc_csu_init>: push r15) [------------------------------------------------------------------------------] Legend: code, data, rodata, value __GI___libc_malloc (bytes=0x400) at malloc.c:2902 2902 { gdb-peda$ |
Vậy là xong, chúng ta đã biết tại địa chỉ ứng với dòng source code nào, sau đó mở lên đối chiếu thôi, dễ debug hơn hẳn :v
Reverse debug
Đây là một tính năng cực bá của GDB từ bản 7.0 :)) Có khi nào trong lúc debug bạn lỡ tay ấn c
, n
hoặc s
liên tục rồi tự dưng chương trình kết thúc. Và bạn vừa nhận ra bạn đã bỏ qua những đoạn code quan trọng :)) Cái này mình gặp hoài, và thường thì mình sẽ set thêm breakpoint và ấn r
để chương trình chạy từ đầu :v Nhưng không, GDB có khả năng chạy ngược, tức là bạn có thể trở về địa chỉ trước đó mà bạn đã lở bỏ qua. Chi tiết nằm ở http://www.sourceware.org/gdb/wiki/ProcessRecord/Tutorial Vì cái này đơn giản nên mình chẳng giải thích gì, các bạn tự làm theo là được :))
UPDATE: mình mới biết được một số tool cho phép record và replay debugging như Mozilla’s rr, Microsoft’s TTD, MIT Lincoln Lab’s PANDA và GeoHot’s QIRA từ bài viết Timeless Debugging of Complex Software
Ngoài ra gdb có thể chạy python script (chắc ai cũng biết :p), bạn có thể tham khảo các API tại đây. Mình cũng từng sử dụng API để tự động đặt toàn bộ breakpoint trong bảng GOT để xem có ai gọi đến để mình ghi đè địa chỉ đó :v