0x00 前言
本人是新接触mips下的逆向,本文参考了网上众多大佬的文章,权当是个人的学习总结,如有问题还请斧正。
本文主要是借助4道ctf里的题目来阐述。
参考文章:
https://blog.csdn.net/hit_shaoqi/article/details/53559914
https://blog.csdn.net/ck404/article/details/21403203
http://www.elecfans.com/d/1213039.html
https://blog.csdn.net/phunxm/article/details/8938123
https://bbs.pediy.com/thread-259892.htm
https://www.anquanke.com/post/id/169503#h2-4
0x01 静态分析的问题与总结
在x86下IDA的反编译非常的给力,但是在mips下,IDA即使有Retdec插件的帮助,但是局限也比较大(mips64此插件就反编译不了),所以推荐用ghidra。
我们用一道ctf举例(RCTF-cipher)
静态——RCTF-cipher
一位看雪的师傅写的wp:https://bbs.pediy.com/thread-259892.htm
1.首先查看文件的信息
在虚拟机里用readelf指令查看;
readelf -h cipher
可以看出来是 mips64 大端,起始位置为0x120000c00(暂时只用这些信息)
2.我们可以到ghidra看一下:
我们可以非常简单的寻找到main函数:
和在x86下申请内存的函数相似:
还有setvbuf
(截的别的文章的图,找不到了)
第一句“&_gp”这是gp寄存器的重定位,之后会详细说明,暂时不管
main函数大致的意思是读入一个文件,然后继续进入cipher()函数,我们跟进:
cipher函数:
首先,第一个奇怪的地方就是CONCAT44,可以看一下帮助文档:
在这个位置:
我i们出现的问题是concat44:
CONCATxy(x,y的值可以变)系列的函数其实就是拼接函数,比如说CONCAT31(0xaabbcc, 0xdd) = 0xaabbccdd
至于为什么会出现这种东西?
内部反编译器的功能偶尔,反编译器可能会使用几个内部反编译器函数中的一个,这些函数不会被转换成更像“C”的表达式。使用这些参数可以指示pcode不正确,或者需要“调优”以使反编译器的输出更好。这也可能意味着反编译器需要一个额外的简化规则来处理特定的情况。
这就是我们静态时第一个要注意的问题:内部反编译器函数(稍后会有总结)
所以我们这句话的意思:就是把ciphertxt的数 / 16,之后的组数,(先减一再移位最后再加一的作用是让不足一组的算作一组)
之后往下看:
得到了两个随机数,然后扔到param[0]和param[1]的位置
这时要注意一下param的定义是undefined,也就是说,这块的定义反编译时有问题,这是我们静态时需要注意的第二个问题
Ps:指令的主要任务就是对操作数进行运算,操作数有不同的类型和长度,MIPS32 提供的基本数据类型如下:
(1)位(b):长度是 1bit。
(2)字节(Byte):长度是 8bit。
(3)半字(Half Word):长度是 16bit。
(4)字(Word):长度是 32bit。
(5)双字(Double Word):长度是 64bit。
(6)此外,还有 32 位单精度浮点数、64 位双精度浮点数等。
我们注意到这两个随机数都是单字节(8位):
seb: seb指令是Sign-Extend Byte 单字节拓展(这里有猫腻)
当把第一个数存到栈里时之后,栈帧加的是0x01,在压入第二个,相当于把这两个数组合到了一起。静态比较难读懂,推荐动调。(动调在下面说)。
(我的理解:然而后面的指令ld是双字(64位)移入,把ghidra搞蒙了)具体mips汇编,会在下文总结
之后进入循环,调用cipher,每次循环处理一组数据(16个字节,2个无符号长整型)。
我们来看cipher传入的参数
第一个参数:acStack104+偏移--->逆向老司机一猜就知道是我们的flag
第二个参数:就是刚刚分组的ciphertxt
(这个时候有个奇怪的地方,就是刚刚的随机数哪去了,发现好像没用过,其实这里就是ghidra反编译的错误,估计是因为上面对param的定义有错误造成的,参数不正确是静态时要注意的第三个问题)
带着问题,我们先进encrypt函数:
逻辑非常简单,但是很明显uStack32不知道是什么
这时就考虑看汇编或者动调,动调在下一个阶段来讲,这里看一下汇编:
(用的wp里的图)
我们得知:in_a2在栈中0x08的位置,即stack[-0x8],结合划线处,我们可以判断,encrypt是有三个参数的,第三个参数就是in_a2,是一个指针。根据指令ld(load double word)判断,应该是个
longlong*
或者ulonglong*
类型的指针
发现其实栈里存了三个参数,我们可以修改函数:
往前翻一下,可以看出就是随机数的地址:
捋一下逻辑:(这里wp里的)
def decrypt(byte16, fc, fd):
v48 = struct.unpack('>Q', byte16[:8])[0]
v40 = struct.unpack('>Q', byte16[8:])[0]
v32, v24 = fc, fd
for i in range(0x1e, -1, -1):
v48 = rol64(v48 ^ v40, 0x3d)
v40 = rol64(ull((v40 ^ v32) - v48), 8)
v32 = rol64(v32 ^ v24, 0x3d)
v24 = rol64(ull((v24 ^ i) - v32), 8)
v48 = rol64(v48 ^ v40, 0x3d)
v40 = rol64(ull((v40 ^ v32) - v48), 8)
return v48, v40
之后脚本参考看雪大佬里的wp
问题总结:
小小总结一下ghidra静态碰到的问题
1. 内部反编译器函数
2.当遇到 undefined定义时,要注意看汇编和动调,有可能影响下面的传参
3.修改函数
4. 有时ghidra里的got表反编译有问题,在这道题里没有体现。
0x02 动态分析的问题与总结
用IDA反编译
确实可以用ida来反编译,但是会有许多问题
依然那刚刚那道题(rctf-cipher)来举例
首先我们先装好qemu和qemu里对应的mips64的库
apt install qemu-user-static
sudo apt install libc6-mips64-cross
之后可以尝试运行:
qemu-mips64-static -L /usr/mips64-linux-gnuabi64/ cipher
-static参数,显示更多的调试信息
-L 因为是动态链接所以要指定libc库的路径
不过运行时会出现如下报错,这问题我们和下一道题一起解释。
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
段错误 (核心已转储)
ida远程动态调试
动态——rctf_cipher
这个我们用wp里的方法https://bbs.pediy.com/thread-259892.htm
服务端:
qemu-mips64 -L /usr/mips64-linux-gnuabi64/ -g 23946 ./cipher > out
-g在23946端口开一个gdb调试
客户端:
Debugger->attach->remote GDB debugger
之后我们勾选
之后可以跑了
IDA动调问题:
1.在看雪那篇里提到了:
2.当我们继续往下调时,会出现这个情况,这也是刚刚我们运行不了的问题
这个我们先放在这,这个和们下一道题出的问题相似,我们一起说
(之后cipher如何动调的,请参考看雪的那篇wp)
动态——ddctf_babymips
这道题的wp参考:https://kabeor.cn/DDCTF2018%20Reverse%20writeup(1)%20baby_mips/
我们查一下文件信息:
mips32小端
悄悄静态看一下:
- 在ida里搜索,很轻松就定位为main函数
- 可以在Ghidra里找到对应位置看反编译
可以判断出主要的加密在FUN_00400420里(不过此函数里对比汇编发现有很多都没有反编译出来)
我们尝试跑一下:因为是静态链接,静态链接的程序可以像正常程序一样运行
同样发现了“段错误”的报错
我们用同样的方法把它连到ida上:
qemu-mipsel -g 23946 baby_mips
插一句:qemu-你需要的指令集 -g 端口 文件名
原理:qemu -g port指令开启一个gdbserver。port另一端可以由IDA或gdb连接调试。
连上之后我们一路单步,来到运行时报错的位置
ida跳出:
和上面一道的错误如出一辙
我们来看一下机器码:
因为是小端,所以这块就是 “EB 02 0A E7”
而mips下一条指令无论如何都是4字节,所以我们尝试把这个地方的eb02 nop掉
之后会发现,依然有这个报错
我们再次查看汇编,发现还是机器码在eb02处出相同的问题
那我们来仔细看一下解析一下这个“EB02”
因为我们是在x86下模拟的mips,而在x86指令级下/xEB是jmp的意思
而02则是相对的偏移
举个例子:
地址 机器码
00 EB02 #jmp 0x04
02 0AE7 # 又因为一条mips指令为4字节 EB 02 0A E7 相当于跳到
#下一条指令
04 .......
‘02’机器码是在这个02地址基础上偏移,所以jmp的地址为 02 + 02 = 04
而mips下一条指令固定是4字节,所以造成了段错误(简而言之就是访问了不该访问的地址),姑且可以看作花指令,
这个东西不止导致动调失败,也会导致静态反编译失败
ps:上一道的段错误也应该是相似的原因(具体我还没有细看)
(这是cipher发生段错时的情况)
之后我们的办法就是把所有的eb02都nop掉
idapython:
‘cq674350529’
把它保存到baby_mips_Patch里,再运行,ok了
这时候再反编译,原来的400420里有了算法
太大了放不下了
(还有好多)
是16个方程求解:
果断z3:(当然方法很多)
from z3 import *
a = [BitVec("a%d"%i, 32) for i in range(16)]
s = Solver()
s.add(0xca6a*a[0] -0xd9ee*a[1] +0xc5a7*a[2] +0x19ee*a[3] +0xb223*a[4] +0x42e4*a[5] +0xc112*a[6] -0xcf45*a[7] +0x260d*a[8] +0xd78d*a[9] +0x99cb*a[10] -0x3e58*a[11] -0x97cb*a[12] +0xfba9*a[13] -0xdc28*a[14] +0x859b*a[15] == 0xaa2ed7)
s.add(0xf47d*a[0] +0x12d3*a[1] -0x4102*a[2] +0xcedf*a[3] -0xafcf*a[4] -0xeb20*a[5] -0x2065*a[6] +0x36d2*a[7] -0x30fc*a[8] -0x7e5c*a[9] +0xeea8*a[10] +0xd8dd*a[11] -0xae2*a[12] +0xc053*a[13] +0x5158*a[14] -0x8d42*a[15] =