写了两道 PWN 的题,记录一下。
Extremely Lame Filters 1
题目给了两个文件 fairy.py
和 elf.py
,elf.py
就是一个 elf 文件解析器,fairy.py
是主程序。
1 | #!/usr/bin/python3 |
可以看出它通过输入获得一个 base64 编码的字符串,解码成一个 elf 文件,检验其是否存在可执行的 section,若不存在则执行它。
这里涉及一个 elf 文件结构方面的知识:section 和 segment 是并列的,加载器加载 elf 时不会关注 section 的相关信息。即使使用 strip --strip-section-headers
命令将 section 相关的信息全删了,程序仍然可以正常运行。
因此这题直接用十六进制编辑器手动更改 elf 文件的 section 信息。写一个类似下面的程序,编译成 elf,对照 elf 结构,将 section header 里表示可执行权限的 06 全改成 02 就行了。
1 |
|
Extremely Lame Filters 2
前一道题的升级版。这题的 fairy.py
的逻辑变为了:仅当程序内不存在内容非零的可执行 segment 时,才会运行此程序。
1 | #!/usr/bin/python3 |
和前一题检测的 section 不同,加载器在加载 elf 时会根据 segment 信息设置内存权限,如果机器码所在的内存没有可执行权限的话,会报错段错误。因此这题不能像前一题那样直接更改权限。
一个可行的,也是我所使用的思路是段重叠,即在 elf 内构建多个 segment,其中机器码所在的段没有可执行权限,且在 program header 表中先出现;另有一全空白的可执行段,在 program header 表中后出现。两段的虚拟地址相同,非可执行段中机器码入口在段内第 n 个字节处,同时空白段的大小也为 n(n!=0)。段的大小非零,段权限才能成功覆盖已存在的段。
这样一来,在程序加载时,加载器便会帮我们复制机器码,并设置好可执行权限,使得程序可以正常运行。同时满足 fairy.py
中的检测逻辑。
考虑 gcc 默认生成的文件段信息有点复杂,我选择写汇编:
1 | BITS 64 |
并单独编写了链接器脚本:
1 | ENTRY(_start) |
对于生成的可执行文件,依旧使用十六进制编辑器完成剩余的修改:去掉 .text
的可执行权限,为 .text_exe
增加可执行权限。
下面这张图大概解释了修改的内容:
随后简单用 python base64encode 和 pwntools 将文件上传到比赛平台,就能获取到 shell 了。cat ./flag.txt
即得到 flag。
写完第二题后和 c10uds 哥讨论了一下,他给出了一种更简单的办法:写一个普通的类似第一题的程序,让可执行段的前 n 个字节是 0,然后直接把可执行段的段大小设为 n。这样既可以通过 elf.py
的检测,也可以让程序被加载器正确加载并执行。
我震惊。