Windows平台shellcode开发入门(一)

2016-01-22 +12 403857人围观 ,发现 11 个不明物体 系统安全

本文简要介绍shellcode开发技术及其特点。理解这些概念可以有助于我们编写自己的shellcode。更进一步讲,你可以修改现有的漏洞利用代码来执行自己所需要的定制功能。

一、介绍

比方说你手头上有一个IE或Flash Playerd现成的漏洞利用代码,但它只能够打开计算器calc.exe。但是这实际上并没有什么卵用,不是吗?你真正想要的是可以执行一些远程命令或实现其他有用的功能。

在这种情况下,你可能想要利用已有的标准shellcode,比如来自Shell Storm数据库或由Metasploit的msfvenom工具生成。不过,你必须先理解编写shellcode的基本原则,才可以在自己的漏洞利用代码中有效地使用它们。对于不熟悉这个术语的同学们,可以参考一下维基百科:

在计算机安全中,shellcode是一小段代码,可以用于软件漏洞利用的载荷。被称为“shellcode”是因为它通常启动一个命令终端,攻击者可以通过这个终端控制受害的计算机,但是所有执行类似任务的代码片段都可以称作shellcode。......Shellcode通常是以机器码形式编写的。

shellcode是一段可用于漏洞利用载荷的机器码。“机器码”又是什么?让我们以下面的C代码为例:

#include 
int main()
{
    printf("Hello, World!\n");
    return 0;
}

这段C代码会编译成如下汇编代码:

_main PROC
    push ebp
    mov ebp, esp
    push OFFSET HelloWorld ; "Hello, World!\n"
    call _printf
    add esp, 4
    xor eax, eax
    pop ebp
    ret 0
_main ENDP

此处,我们需要注意下main程序以及对printf函数的调用。正如调试器中突出显示的,这些代码已经编译成机器码:

所以,“55 8B EC 68 00 B0 33 01 … ”便是上述C代码的机器码。

二、shellcode如何应用到漏洞利用

举一个简单漏洞利用的示例,一个基于栈的缓冲区溢出漏洞。

void exploit(char *data)
{
    char buffer[20];      // 缓冲区位于栈上
    strcpy(buffer, data); // 使用strcpy复制数据
}

利用此漏洞的主要思路如下:(请注意本文目的不是详述缓冲区溢出的漏洞利用原理)

1)向应用程序发送长度超过20字节的字符串,其中包含shellcode。
2)由于写入数据越过静态分配缓冲区的边界,栈结构遭到破坏。同时,shellcode也会被放置在栈上。
3)字符串通过自定义的内存地址重写栈上某块重要数据(如保存的EIP或函数指针)
4)程序会从栈上跳转到你的shellcode,开始执行其中的机器码指令。

如果可以成功的利用此漏洞,你也能够运行自己的shellcode,并实际利用该漏洞做点有用的事情,而不仅仅是让程序崩溃。比如shellcode可以打开一个命令终端,下载并执行文件,重启计算机、启用远程桌面、或其他操作。

三、Shellcode特点

但是shellcode不能是任意的机器码。在编写自己的shellcode时,我们必须需要注意shellcode的一些限制:

1)不能使用字符串的直接偏移。
2)不能确定函数的地址(如printf)
3)必须避免一些特定字符(如NULL字节)
关于上述的每个问题,让我们进行一个简短的讨论。

1.字符串的直接偏移

即使你在C/C++代码中定义一个全局变量,一个取值为“Hello world”的字符串,或直接把该字符串作为参数传递给某个函数。但是,编译器会把字符串放置在一个特定的Section中(如.rdata或.data)。

由于需要与位置无关的代码,我们想把字符串作为代码的一部分,因此必须把字符串存储在栈上,正如你将在本文后续内容中所看到的。

2.函数地址

在C/C++中,调用某个函数非常简单。我们利用“#include<>”指定某个头文件,然后通过名称来调用某个函数。编译器和链接器会在背后帮你解决这个问题:解析函数的地址(例如,来自user32.dll的MessageBox函数),然后我们就可以轻而易举地通过名称调用函数。

在shellcode中,我们却不能以逸待劳了。因为我们无法确定包含所需函数的DLL文件是否已经加载到内存。受ASLR(地址空间布局随机化)机制的影响,系统不会每次都把DLL文件加载到相同地址上。而且,DLL文件可能随着Windows每次新发布的更新而发生变化,所以我们不能依赖DLL文件中某个特定的偏移。

我们需要把DLL文件加载到内存,然后直接通过shellcode查找所需要的函数。幸运的是,Windows API为我们提供了两个函数:LoadLibrary和GetProcAddress。我们可以使用这两个函数来查找函数的地址。

3.避免空字节

空字节(NULL)的取值为:0×00。在C/C++代码中,空字节被认为是字符串的结束符。正因如此,shellcode存在空字节可能会扰乱目标应用程序的功能,而我们的shellcode也可能无法正确地复制到内存中。

虽然不是强制的,但类似利用strcpy()函数触发缓冲区溢出的漏洞是非常常见的情况。该函数会逐字节拷贝字符串,直至遇到空字节。因此,如果shellcode包含空字节,strcpy函数便会在空字节处终止拷贝操作,引发栈上的shellcode不完整。正如你所料,shellcode当然也不会正常的运行。

上图中的两条指令从功能上来说是等价的,但你可以清楚地看到第一条指令包含空字节,而第二条指令却包含空字节。虽然空字节在编译后的代码中非常常见,但是我们可以很容易地避免。

还有,在一些特殊情况下,shellcode必须避免出现类似\r或\n的字符,甚至只能使用字母数字。

四、Linux平台与Windows平台的shellcode对比

相对于Windows平台,编写针对Linux平台的Shellcode可能更为简单。这是因为在linux平台上,我们可以轻松地通过0×80中断执行类似write、execve或send的系统调用。

例如,在linux平台上执行“Hello world”shellcode只需要以下几个步骤:

1)指定系统调用syscall序号(如“write”)。
2)指定系统调用syscall的参数(如,stdout,“Hellow, world”,字符串长度)
3)调用0x80中断来执行系统调用syscall。

这将会发起调用:write(stdout, “Hello, world”, length).

在Windows平台上,情况会更加复杂。为了生成更为可靠的shellcode,我们需要花费更多的步骤:

1)获取kernel32.dll 基地址;
2)定位 GetProcAddress函数的地址;
3)使用GetProcAddress确定 LoadLibrary函数的地址;
4)然后使用 LoadLibrary加载DLL文件(例如user32.dll);
5)使用 GetProcAddress查找某个函数的地址(例如MessageBox);
6)指定函数参数;
7)调用函数。

五、结论

本文是Windows平台shellcode编写入门系列的第1部分。本文介绍了什么是shellcode,shellcode存在哪些限制,以及Windows和Linux平台的Shellcode之间的区别。第2部分会包括简单介绍汇编语言、PE()文件格式、PEB(进程环境块)。接下来,你会了解这些内容如何帮助你编写自定义的shellcode。

*参考来源:securitycafe,FB资深作者Rabbit_Run翻译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

更多精彩
相关推荐
发表评论

已有 11 条评论

取消
Loading...

特别推荐

推荐关注

活动预告

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php