附录3-1: 指令集分析-MIPS
一、MIPS 指令集逆向技巧
在逆向分析一个 mips 指令集架构的二进制程序时,可以使用敏感函数定位的方法,快速定位敏感函数,如 system、sprintf、strcpy 等命令执行和容易发生栈溢出的函数。
1.1 常见敏感函数类别
栈溢出敏感函数
在 MIPS 指令集中,特别是智能设备,一般来说栈溢出漏洞较为常见,也是比较容易利用的一类漏洞,发生栈溢出可能的函数有 strcpy,sprintf,snprint, strchr 等。
1) strcpy 类函数如下所示,直接从 http 数据包参数中的数据内容,直接复制到栈上,没有经过任何的判断与处理,因此可以通过栈溢出越界的 buffer 覆盖当前函数的返回地址,可以进一步利用 ROP 技术来获取目标程序的 shell。
strcpy(stack, buf_from_http);
2)sprintf类,如果格式化中有“%s”格式化字符串,同时没有对输入的数据进行长度判断的话,则也有可能造成栈溢出漏洞。
sprintf(stack, "%s", buf_from_http);
snprintf类,snprintf 的返回值是输入的长度,而不是输出的长度,因此下面的代码则有可能存在漏洞,大致的利用原理因为,第一个snprinf返回值是输入的长度,一般输入的长度大于sizeof(stack),则第二个 snprintf 的 size 则变为负数,snprintf 的大小是无符号的,因此变成了一个超大的size,导致第二个可以用来覆盖返回地址。值得注意的是,这样类型的 overflow 还可以用来bypass canary。
int left = snprintf(stack, sizeof(stack),"%s", buf_from_http1); snprintf(stack+left, sizeof(stack)-left, "%s", buf_from_http2);
4) strchr类,如下所示,乍一看好像使用了strncpy规定了复制的长度,但仔细看就会发现,复制的长度也是由输入的字符串来决定的,因此直接在?前面输入超长的字符即可实现overflow
char *query = strchr(url, '?'); strncpy(stack, url, quey - url -1);
如:index.phpaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?a=1
。只要 QURTY_STRING 够长就可以导致栈溢出。
注入类型的敏感函数(逻辑)
注入类型的漏洞相对来说就简单很多,只要看数据流的处理流程,确定输入能否控制敏感函数即可。 常见的敏感函数如system、popen、exec、execve等,在注入类型漏洞中,对于过滤的关键词绕过是比较关键的,例如没有空格的时候可以使用 $IFS进行绕过。也可以通过一些编码比如xxd,base64等。
1.2 IDA 自动定位敏感函数插件
这里推荐一个比较方便定位二进制程序敏感函数的 python 插件:MipsAduit。
该工具是一个 MIPS 静态汇编审计辅助脚本,通过敏感函数回溯的方法,可以较方便的审计出 C 语言中的危险函数。
插件的安装方法
在 IDA -> file -> Script File 中加载即可,加载完成后会在控制台中输出相应的信息。
点击相应的地址就可以跳转过去,对应的位置会被高亮显示:
1.3 使用 IDAPython 自带函数来定位敏感函数
IDAPython 自带很多的 API,可以使用这这些 API 函数来辅助我们进行函数的定位。
如,定位出调用 sprintf 函数的地址列表的代码:
可以直接在 IDA 中,File -> Script command... 的输入框中输入这些代码,点击 run 就可以执行:
运行完成之后的结果使用 print 函数输出之后,会打印在 Output window 中:
这些输出的地址就是引用了 sprintf 方法的函数,同样双击函数名可以直接跳转到相应的地址。
读者可以在自行在
for addr in XrefsTo(loc):
语句下加入其他过滤语句,以达到更准确定制自己想要的功能。
如这里想要排除 sytem 敏感函数第一个参数为 .data 段中的字符,且不包含 %s 字符的话(说明格式化参数不可控),如我们需要排除这种情况:
system("rm -f /tmp/auth_engineer");
条件可以写成这样:
二、MIPS shellcode 编写
Shellcode 是一段可以执行特定功能的特殊汇编代码,在设备漏洞利用过程中,尤其是栈溢出漏洞,我们一般都会使用调用 shellcode 的方法来进行攻击(ret2shellcode)。
MIPS 架构的 shellcode 和 x86 架构下的 shellcode 也会有一些差异,同时在实际利用 MIPS 的 shellcode 时可能会有坏字符的问题,因此还是需要掌握一些 shellcode 编写的技巧,这样在实际利用时才能比较灵活的运用。
2.1 MIPS 系统调用
在写 shellcode 过程中,都会用到系统调用。和 x86 的系统调用相似,MIPS 系统调用也会用到系统调用号。
使用系统调用的过程依旧是先赋值好参数(a0、a1、$a2),然后使用 syscall 指令触发中断,来调用相应函数:如这里如果需要调用 exit(1)
函数,可以表示成以下的汇编代码:
与 x86 指令不同的是,这里的系统调用号是存储在 v0 寄存器中。
MIPS 的系统调用号可以在 /usr/mips-linux-gnu/include/asm/unistd.h
中看到,调用号是从 4000开始:
关于 mips 交叉环境,可以直接使用下面的命令安装:
sudo apt-get install libc6-mips-cross
其他架构的环境安装方法类似。
2.2 MIPS 指令汇编/反汇编
在将我们写好的 MIPS 汇编转换成 shellcode 时,可以使用 rasm2
工具进行转换,这个工具是 radare
工具的一个专门进行汇编/反汇编的工具,关于工具的安装方法见参考链接。
例如,我们可以使用命令下面的命令对 MIPS 指令集进行汇编:
也可以进行反汇编:
注意这里每一句汇编语句后面都需要加上分号。C 参数还可以生成 shellcode 格式,使用起来比较方便:
举个例子,在 C 语言中执行 execve
函数来获取 shell 的代码如下:
对应的汇编代码为:
在第一、第二行中,lui 和 ori 指令配合使用可以赋值一个 4 字节空间,lui 指令赋值高位 2 字节,ori 指令赋值低位 2 字节。
2.3 反弹 shell 的 shellcode 汇编代码编写
在实际使用 shellcode 进行利用的过程中,一般是编写、使用能够反弹 shell 的 shellcode 来 getshell 而不使用直接执行 execve
函数的方法。针对于反弹 shell 的 shellcode 汇编代码,编写起来会更加复杂,但是系统调用的过程步骤都是不变的:
那么这里就对几个函数调用的步骤进行分解,依次写出系统调用的汇编代码。
socket 系统调用
这里我们使用 TCP reverse shell 的方式来反弹 shell。那么调用 socket
函数以 C 语言来表示的话如下:
socket(AF_INET,SOCK_STREAM, 0)
在 xxx 中,我们可以查到 AF_INET
、SOCK_STREAM
常量对应的数值为 2 和 1。同样可以知道 socket
的系统调用号为 4183。
第一步先给三个参数(a0、a1、a2)赋值,即:
使用 rasm2
进行汇编转换为 shellcode:
在将汇编代码写进文件时,因为 rasm2
无法识别 $,所以需要手动去掉 $ 符号。
2.4 dup2 系统调用
dup2 函数的作用是复制文件描述符,将 socket 描述符复制 stdin、stdout、stderr 描述符中,这里我们就能在远程与本地交互。
以 C 语言来表示的话如下:
dup2 的系统调用号为 4063。
对应的汇编表示为:
shellcode 表示:
经常在这里,我们会加上一个循环,使得最终生成的 shellcode 会短一些,如:
2.5 connect 系统调用
connect
函数的作用是通过 socket 连接到指定的 ip 地址监听的端口。函数原型为:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数为 socket 函数返回的 sock 对象,第二个参数为指定服务端 ip 和端口的结构体,第三个参数为结构体的大小。
示例的 C 语言源代码:
这里重点是 connect
函数的第二个参数,这个参数为 sockaddr
结构体,这个结构体的原型如下:
但是一般在这里,我们会先使用 sockaddr_in
结构体,将 ip 和端口进行赋值,再将其强制类型转换为 sockaddr
。因为 sockaddr
结构体的 IP 地址和端口段都包含在了 sa_data
段,不太容易直接赋值。
sockaddr
结构体的原型如下:
整个结构体大小固定为 16 字节。
根据这个结构体的格式,我们将示例代码编译成可执行程序,在 gdb 中调试到相应位置,查看相关的的内存表示。查看 sockaddr_in
结构体的内存值:
0x0002
:表示 TCP 协议族,大小为 2 字节。0x7a69
:表示端口号,大小为 2 字节。0x7f000001
:表示 IP 地址,大小为 2 字节,在这里表示的 IP 为 127.0.0.1。
相应的汇编代码如下:
在调试中类似这种情况就是对的:
2.6 execve 系统调用
这里的 execve
的系统调用同样是执行 execve("/bin/sh",0,0);
函数,写法参考上文,不在赘述。
2.7 调试方法
在编写 shellcode 的过程中,可以对每一部分的汇编代码进行调试,调试方法如下。
将汇编语句加上 main 符号
汇编、链接
qemu 调试
在一个终端执行命令:
另外一个终端:
就可以在 gdb 中进行正常的调试。
将上述三段汇编语句连起来就可以得到最终的 reverse shell 的汇编语句,同样的使用 rasm2
将其汇编成 shellcode 格式即可。对于最终得到的 shellcode
,我们经常会进行指令的优化,也就是将一些指令进行替换或者将 shellcode 进行编码,从而避免一些坏字符。
来源: 海特实验室
Last updated