Hitcon 2023 Blade WP
不太会,只做了这一道
表面行为分析
首先运行程序,会进入一个交互界面,然后提示可以进入 server 模式,并且 run
之后打印出来一串 opcode
nc 连 server,server 提示连接成功,help
查看指令
随后尝试运行这些指令,server 这边直接卡住了,但是 nc 这边出现了乱码
如图是运行 pwd 出现的乱码:
由于看不出来是什么,所以上 pwntools 提一下数据得到
1 | b'T]1\xf6\x81\xc6\x00\x10\x00\x00H)\xf4T_jOX\x0f\x05PH\x92H\x83\xc2\x08S_T^j\x01X\x0f\x05U\\A\xff\xe7' |
有点抽象,难道是 pwd
执行结果加密后的数据表现吗?那么 flag 有可能和这个是同一套加密,那么也许可以先找到这套加密代码,再看看程序中有无藏匿的 flag 加密后数据,从而得到原 flag。但是这个猜想不一定对,我又尝试执行了 ls
,得到的数据和上面的差不多,而且比较短,感觉文件名加密后不可能才这么点长。于是我将其复制到 IDA 里 c 一下,发现这玩意儿是 opcode,疑似是实现其列出的操作的代码,但是这样打印出来不知道干嘛的。
到这里程序表象已经看不出来什么了,接下来是逆向环节
程序逆向分析
IDA 打开能直接找到 main
函数 seccomp_shell::main::hef7e76ec97275895()
main
函数中直接能找到关键函数 seccomp_shell::cli::prompt::h56d4b6fe2f13f522(&v5)
由函数名可以推断,这个函数负责处理 cli 界面与指令,有可能是通过类似 switch
的方式来处理指令
根据经验,这种处理 cli 的函数前面可能会根据终端的环境变量来设置或打印一些初始化内容,所以前面的代码大概率没用,往后翻看看有无 switch
事实证明也确实有:
上图这种 switch
字符串的比较方式不知道是 Rust 编译器干的还是出题人故意这样写的,总之我看着值有点像 ascii 范围就 R 了一下,结果确实是字符。应该是根据用户输入进入相应的函数,那么进 seccomp_shell::shell::prompt::h76cecfe7bd3bdf50(v33)
看看吧
然后就发现了刚刚打印出来的字符串,看来 server 之后进入的交互就是这个函数负责的:
然后继续往下看,发现了爆点
上图中出现了 flag
命令,我随即启动程序测试
如图直接输 flag
会提示 Incorrect ,随后我在后面加参数运行也不会有报错,猜测就是把 flag
后面的参数拿去验证了。先进 seccomp_shell::shell::verify::h898bf5fa26dafbab(v154, v175[3], v175[5])
函数看看
verify有点长就不截图了,挑几个部分说一下
1 | memcpy(dest, &unk_55DF6DF58920, 0x200uLL); |
tmp_addr
是用户输入,长度为 64。这整个循环是在按照 dest
中的值来将 tmp_addr
的顺序打乱,而这样的循环一共有 5 个,除了每次复制到 dest
中的值不同以外,代码基本相同。并且复制到 dest
的值是常量,而代码的行为也与用户输入值没有任何关系。也就是说,可以将这 5 次打乱看作一次,我们只需要输入一串已知顺序的 64 字节字符串,然后动调得到输出的字符串,就能得到 64 位对应表,在还原 flag 时直接根据表就能还原。
1 | v58 = 0LL; // exchange end |
打乱后是如上的加密,静态分析或动调都可以看出这个加密是以字节为单位的,并且这个加密的运算数似乎也与 flag 的其他位置值没有任何关系。那么有没有可能,我们可以得到一份加密前后的字节对应表,按照表就能将加密后的字节翻译为加密前的字节。
生成 0~255 的 hex 序列,然后就能粘进IDA里提取加密后的值:
1 | for i in range(0, 256, 64): |
本来以为就这点,但是拉到后面一看还有个 while ( v8 != 256 );
,而作用范围是刚才的打乱和加密,也就是说这个过程会循环 256 次,问题不大。
得到的字节对应表与还原脚本:
1 | # 动调得到的表 |
得到顺序表与还原的脚本:
1 | # get order table |
256 次就写个 for 循环就行了
程序如何比较
如图是 256 次循环后的开头代码,这次 memcpy
加载了一块 255 字节的数据到堆中,但不是 dest
。经过查看,这个数据又是 opcode
经过调试发现 dest
加载了 64 字节的数据,那么这个数据很有可能是比较数据,直接拿到脚本里去跑,解出来却是乱码。一开始以为脚本写错了,但拿个明文加密的提取的数据去跑,确实是能跑出明文的,说明比对部分还有细节。
在上图中,opcode 的某些部分被修改了,并且与加密后的输入有关,随后我又偶然发现 if
中对索引 [223, 224, 225, 226]
赋的值和 dest
开头的 4 字节一致,但仍然没看出来有什么比较。
继续向后看,发现会读取 TcpStream io 之类的,不然会卡住等待输入,在 nc 那边输点东西就可以继续跑
然后找到了这个函数
我一开始以为这个就是比较函数,但是仔细看发现根本没有比较的操作,而是又是在对 opcode 进行修改操作,并且将 opcode 数据发送到 nc 端,与之前那个 if
里的代码行为一致,应该是编译优化导致的。
看了半天确定这个程序里确实没有任何比较操作,我将目光转向那个 opcode。经过辨认发现 opcode 被修改的值是如下两个地方:
1 | .text:0000564D3A41F1FD mov eax, 1015CB52h # 加密的用户输入 |
程序中的行为是取加密后的用户输入与 dest
中的值写入 opcode 这两处,而这个 opcode 也很明显能看出来是在验证。但是这其中 r12 r13 r14
寄存器的值都是未知的,我的猜测是 opcode 前面执行的代码会影响这三个寄存器,使其在运行到此处时为定值。所以我运行了该 opcode,得到三个寄存器的值:
1 | r12 = 0x0000000464C457F |
逆该过程脚本:
1 | # 由于需要模拟汇编中的位运算,所以需要使用 numpy 库 |
总的来看,整个程序其实根本没有验证 flag 的正确性,也没有执行这些 opcode,这些 opcode 只是被作为数据发送到了 nc 端
EXP
1 | import numpy as np |
- 标题: Hitcon 2023 Blade WP
- 作者: Hexrotor
- 创建于 : 2023-09-11 01:22:27
- 更新于 : 2024-10-23 22:07:49
- 链接: https://hexrotor.github.io/2023/09/10/hitcon-2023-blade-wp/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。