历时一个月之久,hackme的一道有点难度的逆向题,学习了两个新工具的搭建与使用。
拿到题之后,发现汇编指令除了call之外,基本都是mov。惊呆了!这才想起题目描述中的“mov是图灵完备的”是什么意思。
谷歌了一下,这是一种名为movfuscator的混淆。
通过别人的类似题目的题解,尝试了一下demovfuscator的反混淆。
demovfuscator的环境搭建极其困难,至少对我来说是这样的。我浪费了好多天时间。最后制作了一个docker镜像,喜大普奔!
有关demovfuscator的笔记请参考我之前的博文:demovfuscator docker镜像
然鹅反混淆后的代码依旧难以看懂。
又谷歌了一下,得知了qira这个工具。
qira是一个动态调试器,能够记录每一个状态。
这玩意也不好搭建,项目本身就有很多bug,比如因为flask(qira的web界面的UI是依赖于python的flask)的版本更新而导致的qira初始化失败,比如UI的左侧显示问题,等等。
好不容易做了个镜像。详情请戳我的docker hub。
qira的中文资料很少,好不容易找到一个中文的命令介绍,这里摘录一下,以防丢失。
一个神奇的调试器,由geohot编写,安装:
cd ~/
git clone https://github.com/BinaryAnalysisPlatform/qira.git
cd qira/
./install.sh
注意,他需要Flask-SocketIO==2.9.1
,将requirements.txt文件对应项改掉就行啦~
用法:
usage: qira.py [-h] [-s] [-t] [--gate-trace ADDRESS] [--flush-cache] [--pin]
[--host HOST] [--web-port PORT] [--socat-port PORT] [-S]
[--engine ENGINE]
binary [args [args ...]]
Analyze binary. Like "qira /bin/ls /"
positional arguments:
binary 要分析的程序地址
args 程序参数
optional arguments:
-h, --help
-s, --server 类似使用socat搭建服务,监听4000端口
-t, --tracelibraries 跟入所有libraries
--gate-trace ADDRESS 从指定位置开始trace
--flush-cache flush all QIRA caches
--pin 用pin做后端,需要安装:./pin_build.sh
--host HOST 设置web和socat的监听地址,默认 0.0.0.0
--web-port PORT web端口,默认 3002
--socat-port PORT socat端口,默认4000
-S, --static enable static2
--engine ENGINE static engine to use with static2 (builtin or r2)
看着一脸懵逼,最简单的就是./qira /bin/ls
啦,此时它会监听3002,使用浏览器打开即可(若打开为空白,结束命令再启动,多试几次就好啦):
- 该区域为垂直时间线,从上至下记录着整个程序从开始追踪到当前执行到的位置(若程序已经结束则指向结束位置),它本来是绿色,颜色越深代表函数调用深度越深,当数据区(下面会提,即7区域)的光标指向某个地址时,该区域也会有不同的颜色变化,亮黄色代表该地址在那个时候被读过,暗黄色代表该在那个时候被写过,而代码区(即4区域)的光标指向的地址在这里以蓝色表示。通过鼠标左键拖动选中一块时间线可以缩小查看的范围,使用
z
键可以恢复该区大小,另外右键双击该区域能隐藏当前进程的时间线。 - 该区域为内存属性区域。
该区域有四个框:
- 第一个代表指令序号,qira会为按执行顺序为每一条执行到的指令编号,数字以蓝色标记,可以在这个框里输入数据快速跳转到相应序号处。
- 第二个代表子线程/进程编号,数字以灰色标记,通过它可以可以切换线程编号,当然左右方向键也有此功能。
- 第三个代表指令地址,数字以红色标记,可以在此键入地址快速跳转,当然
g
键也可以快速跳转。 - 第四个代表数据地址,数字以黄色标记,可以在此键入地址快速跳转。
- 该区域为指令区,第一列为指令序号,前面有提,调用者与被调用者左右不对齐,可以看出明显的层次关系,第二列为指令地址,第三列为反汇编语句,可以在这个框里使用很多按键:
j -- 跳转到下次对当前指令调用的地址
k -- 跳转到上次对当前指令调用的地址
shift-j -- 跳转到下次对当前数据访问的地址
shift-k -- 跳转到上次对当前数据访问的地址
m -- 到当前函数结束处
, -- 到当前函数开始处
z -- 恢复垂直时间线
left -- -1 切换前一个子进程
right -- +1 切换后一个子进程
up -- -1 查看前一条指令 鼠标滚轮上滑
down -- +1 查看后一条指令 鼠标滚轮下滑
esc -- 回到上一次光标所在位置
shift-c -- 清除所有进程
n -- 对指令重命名,一般用在函数入口处
shift-n -- 对数据重命名
: --对指令添加注释,其实就是';'符号
shift-: -- 对数据添加注释,其实就是':'符号
g -- 快速切换位置,可以输入地址,指令序号或者指令名
- 此区域为通用寄存器区域,不同的寄存器用了不同的颜色标记,里面的数据属于指令地址的用红色标记,属于数据地址的用黄色标记,其他事黑色,红色高亮的寄存器是上条指令改变的寄存器,最下行是当前指令的读写操作,暗黄色代表读,亮黄色为将右侧数据写入左侧地址。
- 此区域为当前所进行的系统调用。
- 此区域为数据dump。
另外它也支持与ida连接,由于它自己编译的插件是plw的,我这里就用ida6.8来配套使用,将./ida/bin
下的windows下的两个插件放入ida的插件目录,启动ida,启动插件:
接着ida和qira就同步了,另外F5的伪代码也能定位,美滋滋:
需要注意:ida.js里面ida地址写的是localhost,若浏览器不在那台电脑上将会失败,改一下即可
以上内容摘自调试器与反编译工具
然后经过极其漫长的分析,猜测算法是输入字符串,按照程序中自带的字符重新计算偏移量,找到对应字符。
strings='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{}_/*-+'
maps='\x3e\x3d\x3c\x3b\x3a\x39\x38\x36\x37\x34\x35\x32\x33\x30\x31\x2d\x2c\x2f\x2e\x29\x28\x2b\x2a\x24\x25\x26\x1a\x19\x18\x1f\x1e\x1d\x1c\x12\x13\x10\x11\x16\x17\x14\x15\x09\x08\x0b\x0a\x0d\x0c\x0f\x0e\x00\x01\x02\x41\x40\x43\x42\x45\x44\x47\x46\x48\x49\x03\x05\x23\x5d\x58\x5f\x59'
miwen='\x39\x32\x3e\x38\x03\x33\x41\x2b\x39\x0c\x0a\x18\x3e\x0d\x15\x2f\x23\x40\x44\x23\x1a\x14\x14\x41\x01\x13\x14\x1c\x05'
flag = ''
for i in miwen:
o = maps.find(i)
if o:
flag += strings[o]
print(flag)
其实之前看到过一个大佬的题解,他说他无意间发现本题可以爆破flag,因为输入不完整的flag时,程序也会输出cerrent。这里摘录他的爆破脚本。
我感觉这个漏洞是出题人故意留的。
import subprocess
import string
baopo = lambda : subprocess.Popen('./mov', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
flag = "F"
while flag[-1] != '}':
for i in string.printable:
p = baopo()
p.stdin.write(flag + i)
if 'Good' in p.stdout.read():
flag += i
break
print flag