ShellCode执行代码iptables -P INPUT ACCEPT

近期我跟着OpenSecurityTraining.info的教学资料开始去了解应用程序的底层安全,在本文将向大家分享我第一次写的ShellCode执行代码iptables -P INPUT ACCEPT

背景

在实践学习如何利用一个简单的堆栈溢出之后,跃跃欲试想看看能否自己写ShellCode。刚好朋友推荐shell-storm中的shellcode军火库,于是想自己动手写一个里面军火库中还没有的shellcode
有多种方式执行iptables -F,然而就我所知只有一种方法,可以在不改变默认策略的情况下清空表单中的规则。因此它可能情况所有规则,如果服务端默认策略为DROP,那么也就切断了机器与互联网间的通信。
我的想法则是,编写一条shellcode在默认策略的INPUT参数后加上ACCEPT参数,即运行iptables -P INPUT ACCEPT

编写ShellCode

首先声明我不是大牛,我也是通过阅读基础文档之后,尝试理解其他人编写的shellcode以及其中的技巧一路走来。
我们的目标则是运行/sbin/iptables -P INPUT ACCEPT,此刻假设被利用的应用拥有足够的权限来执行该命令,此外你可能还想要增加一些setuid(0)代码。
如果你想要执行带有具体参数的外部二进制文件execve是一个常见的选择,执行man 3 execve命令获取更多信息:

我们不需要envp变量,因此将其传递给一个NULL指针 脑中已经有了想法之后,我们就可以从汇编代码入手了。shellcode.asm从两个定义开始: – section .text : 定义代码部分 – global _start :定义了应用程序的入口点 对于shellcode来说两者都不是重点,但对于之后的编译,链接以及测试执行却会带来一定便利。

 

接下来便是将我们的参数集成到execve:

使用静态字符串需要在shellcode中使用硬编码地址,因此我们将在堆栈中构建动态字符串
所以,我们首先就需要一个NULL字节来结束字符串。最简单的方法便是通过xor运算让寄存器内的值为0,a ^ a = 0:

使用edx寄存器来完成我们的NULL字节,是因为我们可以通过execve的第三个参数进行重用。
继续在堆栈上构建/sbin/iptables:

细心的读者应该发现 /sbin///iptables字符串中多处的/,这是因为我们需要多加注意shellcode中的nullbytes(后面会详细讲解)。Linux会忽略路径中连续的斜杠,并将其视为一个斜杠。
我们都知道,堆栈是从上往下压数据的,但是execve将从下往上读取数据。这也是我们为什么需要首先压入NULL-byte (edx),以下Python函数可以帮助我们完成任务:

该16进制字符串可以分割为2部分然后压入栈中,通过复制esp的当前地址我们将指针保存到ebx的第一个参数。之所以使用ebx是因为可执行文件路径是execve的第二个参数 对所有参数执行此步骤。然而,我们需要一个技巧来解决参数不能被4整除的问题,以下为-P参数的情况:

出现这个问题是因为编译器会默认给缺少的位填充0:

Nullbytes ( 0x0)或者换行( 0xa)在C语言中可直接用于字符串结束,然而在shellcode中这不行。 这也是为什么不想使用push 0x502d的原因,这里有个小技巧可以解决这个问题,直接在数据压入堆栈之前向eax写入0x502d就行了。在我们的16进制编码字符串后面附加4个随机16进制值(e.g. 0xffff, 2 bytes),之后将其移动到eax:

之后获取ffff的rid:

检测是否全部都正确:

之后

现在可以在不破坏我们shellcode的前提下将正确的值压入栈中了 将两部分数据合在一起:

此时我们已将-P ( esp)的地址保存到eax中 最后两个参数( INPUT, ACCEPT)遵循类似以下步骤:

INPUT参数的地址保存在ecx寄存器,ACCEPT可通过esi寄存器访问。 至此已完成大部分工作,看看到目前为止我们都做了些啥吧:

– edx中的NULL-byte (execve的第三个参数)

– eax指向-P (iptables的第一个参数)

– ebx指向/sbin///iptables (execve的第一个参数)

– ecx指向INPUT (iptables的第二个参数)

– esi指向ACCEPT (iptables的第三个参数)

之后我们需要构建用于终止NULL的argv数组,将地址按正确的顺序压入堆栈:

当我们需要引用INPUT时,可以使用argv数组的地址来覆盖ecx,因为这将是execve的第二个参数
最后一步就是结上以下参数进行execve系统调用:

以上我们结合使用push和pop,通过清理eax寄存器中的高位字节并将0xb(execve)syscall-ID放入其中,避免在我们的shellcode中碰到NULL字节。
你可以在/usr/include/asm/unistd32.h头文件中寻找合适的syscall-ID

define _NRexecve 11

以下为完整的shellcode:

我们可以在底部增加以下指令,以方便我们彻底退出测试程序:

测试ShellCode

如果一切正常,我们便可以编译运行该汇编程序了:

由于我实在64位Linux机器上编译x86汇编程序,所以我加上了-m elf_i386参数
之后可以使用objdump -d shellcode来验证shellcode中是否包含字符串结束符:

当然你也可以直接执行编译完成的二进制文件:

通过谷歌搜索”objdump to shellcode”,我们获知了一个非常棒的[commandlinefu技巧](http://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump),帮助我们更方便的切换指令:

字节统计

通常一个payload不会有太大的空间,所以我们要完成的另一项任务便是尽最大可能缩小shellcode的字节量。我们当前版本就有83字节:
`

接下来就看看在哪些地方可以进行优化。我们可以对所有的uneven参数-P, INPUT 以及 ACCEPT的push指令进行优化:

最终获得的shellcode有点短且不包含nullbytes:

计算结果显示比之前的shellcode小17字节:

分享shellcode代码最常见的方法便是将其放入一个C程序中:

include <stdio.h>

之后进行编译:

 

总结

shellcode的编写比我想象之中还要简单一些,然而我认为优化shellcode代码的长度的确是一项有趣的挑战。

作者:Sebastian Neef – 0day.work

翻译:ak@91ri-team