第一届北京大学信息安全综合能力竞赛,校外选手参加玩玩。挑着觉得好玩的题目做了做。再此分享Writeup。
签到
给出了一个PDF,PDF里面的字就是flag,但是其中的字是异形字体。使用Ctrl+A把字全部选中,复制出来。可以得到如下内容:
fa{aeAGetTm@ekaev!
lgHv__ra_ieGeGm_1}
显然可见,是2-栅栏加密。利用下面的脚本解密得到Flag:
诡异的网关
首先尝试用IDA分析,发现太复杂,字符串搜索也没搜索出来啥。于是开始探索GUI。
在登录界面,发现这个客户端有个记住账号的功能。有个用户名是flag。点击切换一下,成了下图的状态。
显然,密码被存在Password文本框里了。但是我想复制的时候。系统会提示:不允许:您不能从密码字段中复制。
于是,我想起了Windows的GDI。下面是来自百度百科对GDI的解释:
GDI是图形设备接口的英文缩写,主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形和图像输出。GDI的出现使程序员无需要关心硬件设备及设备正常驱动,就可以将应用程序的输出转化为硬件设备上的输出和构成,实现了程序开发者与硬件设备的隔离,大大方便了开发工作。
我已经不记得我对GDI的了解是从哪本书里来的了。但我知道的是,绝大多数窗口及控件的绘制工作都由操作系统完成。Windows操作系统通过句柄唯一标志一个GDI对象,并且通过Windows API完成对控件的各种事件及操作。因此,我们只需要监听对该控件对象的事件,即可捕捉由用户态应用程序向操作系统内核发送的文本框中的信息,而无需完整的逆向程序。
那么“监听控件对象”的工具是什么,从哪里来呢?微软有一个Microsoft Spy++软件,专门用来做这个事情。它的简介如下:
Spy++ (SPYXX.EXE) is a Win32-based utility that gives you a graphical view of the system’s processes, threads, windows, and window messages. With Spy++, you can:
– Display a graphical tree of relationships among system objects, including processes, threads, and windows.
– Search for specified windows, threads, processes, or messages.
– View the properties of selected windows, threads, processes, or messages.
– Select a window, thread, process, or message directly from the view.
– Use the Finder Tool to select a window by mouse positioning.
– Set message options using complex message log selection parameters.
我们打开Microsoft Spy++,选择菜单:监视——日志消息。利用“查找程序工具”,将定位图标拖放到对应的密码框上,如图所示。
点击确定。回到客户端里,重新点击用户名的下拉文本框并选择flag。此时Spy++里的Messages窗口会不停的收到消息。待操作完成后,我们在Messages窗口中寻找WM_SETTEXT事件。即得到了Flag。
射水鱼
这是一道很有意思的题目,需要阅读大量的英文参考资料,国内讲这些的很少。以类似于Pwn的形式出出来了。
首先我们阅读serve.py:
简要来说,整个流程是从客户端读一个长度恰为0x1024的hello.debug
,和hello
程序放在一起。然后在gdb中执行命令,依次为:
- 在main处下断点
- 运行程序
- 打印变量a的值
- 继续运行
- 退出
简单猜测一下,hello.debug文件应该是与hello相分离的符号文件。于是我就在网上搜索,查找到了资料Separate Debug Files (Debugging with GDB)[1]。阅读完毕之后,我抓住的最重要的信息是:
- 采用Debug Link方式单独存放符号文件时(本题目就采用该种方法),原ELF文件中
.gnu_debuglink
节中存放了对应的分离符号文件名以及单独符号文件的校验和。 - 校验和采用IEEE 802.3定义的标准CRC32算法,多项式为 ,对整个符号文件内容进行校验和的计算。
- 建立分离的调试文件的示例命令如下:
一开始的时候我还没有头绪,非常疑惑。调试文件又不能执行代码,虽然题面中给出flag文件存放的路径是/flag
,我怎么执行命令把它打出来呢?我查了一通ELF对应的符号文件格式,发现是DWARF,又去尝试翻DWARF的手册[2],什么都没查找到,无果。后来我索性想着按照他的脚本自己执行一遍来尝试一下,结果发现了如何打印信息的方法。
原来,使用-ex选项执行gdb的方式,会让其在给main函数下断时,在CLI里面打印出源码文件的对应行。
而相关信息(包括源码文件路径、行号信息)都存储在符号文件中。只要我们去修改符号文件,更改对应的源码路径及行号信息,就可以巧妙地让gdb替我们读出/flag
的第1行.从而得到flag。
但这其中有非常多需要我们注意的点,我下面来一个一个讲述:
- 编译新的.debug文件时,必须严格使用与原可执行ELF相同版本的编译器,否则编译了之后压根也对不上。我们可以通过如下命令进行查看:
这个版本恰好是Ubuntu 20.04的GCC编译器。所以我直接用了Ubuntu 20.04完成这一系列操作。
- 使用如下命令查看.debug符号文件的详细内部结构:
注意我们要更改的地方包括:.debug_line
节的行基数、文件名表和行号语句。大致的更改方法为:首先通过objdump定位节偏移。然后手动去定位我上面所提到的二进制数据项并进行修改。再放到gdb里面验证修改是否正确。
更改后的Dump如下:
- 修改后的.debug文件必须为0x1024个字节长,因为Pwn服务端精确读入0x1024个字节,不能多也不能少。所以:
– 利用参考资料[1]中给出的符号文件生成命令,生成的符号文件大小超出了限制要求。因此使用objcopy --remove-section [Section] hello.debug
命令将符号文件中无关紧要的非符号节直接去掉,以减小占用空间。其中[Section]
是节名。
– 由于还要兼顾到CRC32校验和碰撞的问题(后面详述),所以在减小.debug文件大小后,应该用0x00
补齐文件大小到0x101D
,再填充6个字节的校验和碰撞填充凑成0x1024个字节。可以这样做的原因是,ELF文件有专门的节表定位每一个节的地址,在文件最后填充内容不会影响对ELF的正常操作。
- 由于gdb采用CRC32方法验证分离符号文件的校验和,我们必须把修改后符号文件的CRC32校验和更改为和原hello文件中一致。利用如下命令读取其CRC32的值:
可知,分离符号文件的CRC32文件应为0x6751fc53。
CRC32是一个相对简单的校验算法,有现成的碰撞工具[3]。我就使用了该碰撞工具。在对符号文件手动修改完成、填充完成后,再将碰撞填充的哈希值填进去,凑成0x1024个字节即可。
最终的Pwn脚本如下:
在线解压网站
非常有意思的一道题目,但是做起来不难。
上传一个压缩包,网站会替你解压。解压完了之后你可以读到压缩包里的东西。你需要读到/flag
。
解决办法:上传一个到/flag
的软链接的压缩包,就这么简单。[捂脸]
这道题一做完,我就想起来,我之前做了个网站,也可以上传压缩包。赶紧去查了查有没有毛病。还好,我那个项目部署时Constraint足够多,所以没有问题。外面还有一层Docker套着。
给自己发了个issue,欢迎参观:https://github.com/bjrjk/LinuxASMCallGraph/issues/6
附件
为方便大家学习使用,我已将所有的源码及二进制程序信息压缩打包上传到此处,欢迎下载学习:PKUGeekGame2021
参考资料:
[1] Separate Debug Files (Debugging with GDB) https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
[2] https://dwarfstd.org/doc/DWARF5.pdf
[3] https://github.com/theonlypwner/crc32