shellcode编写

1.理解系统调用
shellcode是一组可注入的指令,可以在被攻击的程序中运行。由于shellcode要直接操作寄存器和函数,所以必须是十六进制的形式。
那么为什么要写shellcode呢?因为我们要让目标程序以不同于设计者预期的方式运行,而操作的程序的方法之一就是强制它产生系统调用(system,call,syscall)。通过系统调用,你可以直接访问系统内核。
在Linux里有两个方法来执行系统调用,间接的方法是c函数包装(libc),直接的方法是用汇编指令(通过把适当的参数加载到寄存器,然后调用int 0x80软中断)

废话不多说,我们先来看看最常见的系统调用exit(),就是终止当前进程。
(注:本文测试系统是32位的ubuntu-17.04)
(编译时使用static选项,防止使用动态链接,在程序里保留exit系统调用代码)

用gdb反汇编生成的二进制文件:
30238500.png
_exit+0行是把系统调用的参数加载到ebx。
 _exit+4和_exit+15行是把对应的系统调用编号分别被复制到eax。
最后的int  0x80指令把cpu切换到内核模式,并执行我们的系统调用。

2.为exit()系统调用写shellcode
在基本了解了一下exit()系统调用后,就可以开始写shellcode了~
要注意的是我们的shellcode应该尽量地简洁紧凑,这样才能注入更小的缓冲区(当你遇到n字节长的缓冲区时,你不仅要把整个shellcode复制到缓冲区,还要加上调用shellcode的指令,所以shellcode必须比n小)。
在实际环境中,shellcode将在没有其他指令为它设置参数的情况下执行,所以我们必须自己设置参数。这里我们先通过将0放入ebx中的方法来设置参数。
步骤大概是:
  • 把0存到ebx
  • 把1存到eax
  • 执行int 0x80指令来产生系统调用

根据这三个步骤来写汇编指令:
36297267.png
 然后用nasm编译,生成目标文件,再用gun ld来连接:
然后objdump就能显示相应的opcode了:
36534763.png
 
看起来好像是成功了。但是很遗憾,这个shellcode在实际攻击中可能会无法使用。
可以看到,这串shellcode中还有一些NULL(\x00)字符,当我们把shellcode复制到缓冲区时,有时候会出现异常(因为字符数组用null做终止符)。要编写真正有用的shellcode我们还要想办法把\x00消去。

首先我们看第一条指令(mov ebx, 0)将0放入ebx中。熟悉汇编的话就会知道,xor指令在操作数相等的情况下返回0,也就是可以在指令里不使用0,但是结果返回0,那么我们就可以用xor来代替mov指令了。

再看第二条指令(mov ax, 1)为什么这条指令也会有null呢?我们知道,eax是32位(4个字节)的寄存器,而我们只复制了1个字节到了寄存器,而剩下的部分,系统会自动用null填充。熟悉eax组成的就知道,eax分为两个16位区域,用ax可以访问第一个区域,而ax又分为al和ah两个区域。那么解决方法就是只要把1复制到al就行了。
至此,我们已经将所有的null都清除了。
37823191.png
 
同上编译连接,用objdump查看:
37914404.png
 嗯,已经没有\x00了。接下来就可以编写个c程序来测试这个shellcode了。
编译后用strace来查看系统调用:
38425230.png
 可以看到我们成功使用exit()退出了。

 

3.shellcode-execve
exit()可能没什么意思,接下来我们做点更有趣的事情-派生root shell-控制整个目标系统。
在Linux里,有两种方法创建新进程:一是通过现有的进程来创建,并替换正在活动的;二是利用现有的进程来生成它自己的拷贝,并在它的位置运行这个新进程。而execve()系统调用就可以在现有的进程空间里执行其他的进程。
接下来我们开始一步步写execve的shellcode:

1.查找execve的系统调用号码:
8202828.png
 可以在如图的系统目录中找到execve的系统调用号码:11

2.接下来我们需要知道它作为输入的参数,用man手册就可以查看:
8418001.png
 
3个参数必须包含以下内容:
  • filename必须指向包含要执行的二进制文件的路径的字符串。在这个栗子中,这将是字符串[/ bin / sh]。
  • argv []是程序的参数列表。大多数程序将使用强制性/选项参数运行。而我们只想执行“/ bin / sh”,而没有任何更多的参数,所以参数列表只是一个NULL指针。但是,按照惯例,第一个参数是我们要执行的文件名。所以,argv []就是[‘/ bin / sh’,00000000]
  • envp []是要以key:value格式传递给程序的任何其他环境选项的列表。为了我们的目的,这将是NULL指针\0x00000000

3.和exit()一样,我们使用int 0x80的系统调用。注意要在eax中包含execve的系统调用号“11”。

4.接下来就可以开始编写shellcode了,节约时间,我在这直接放上写好的shellcode并加上了注释:
9236117.png
 需要解释的是向堆栈中反向推送//bin/sh。我们知道在x86堆栈中是从高地址到低地址的,所以要输入反向的字符串。同样,使用为4的倍数的最短指令会更容易些。
而/bin/sh是7个字节,怎么把它变成8个字节呢?很简单,加个/就ok了。因为在Linux中,多几个/都不会有问题的,像这样:p
9845160.png
 然后用python来生成hs/nib//的十六进制吧:
9938917.png
 然后将它们入栈就好。其他的看注释应该都能懂,就不多说了。


5.编译运行成功后用objdump查看:
11287561.png
 
6.shellcode已经提取成功了,接下来用c程序来验证一下:
编译运行
11622386.png
 成功:D

```