网上看到一篇不错的介绍shellcode的入门文章,我就大致翻译一下,算是自己真正跨入二进制安全相关领域的学习吧。原文地址:http://www.primalsecurity.net/0x0-shellcoding-tutorial-introduction-to-asm/
以下为翻译内容:(非逐句翻译)
汇编代码介绍:
汇编语言是一种为了方便与微处理器交互而设计的低级编程语言。该语言是与处理器系列相关联的,如Intel、ARM等。在理解汇编的时候,体系结构发挥了重要的作用,因为在32位和64位之间存在很大的不同。在这里我们主要集中到Linux下的Intel(IA-32)。
今天我们看到的CPU寄存器为EAX、EBX、ECX和EDX。在最初设计时,这些寄存器拥有一般的功能。但是基于我们的目的,我们可以在每个时序存储任何我们喜欢的数据。这些寄存器的标准用法如下:
EAX |
“累加器”通常用于算术运算 |
EBX |
基址寄存器,作为数据指针 |
ECX |
“计算器”,用于循环的索引 |
EDX |
数据寄存器,充当一个I/O指针 |
在后续文章中,我们会介绍其他一些寄存器。
操作我们的寄存器:
首先,我们会利用之前提到的寄存器创建一个基本的“hello world”的汇编语言脚本。要做到这一点,我们先创建一个名为“helloworld.asm”的新文件(可以取任何你想取的名字),然后在文本编辑器中创建‘.text’和‘.data’两个段,如下所示:
section .text global _start ;default entry point for linking _start: ; entry point for commands section .data
.data段我们将用于存储字符串(这可以用于变量等),.text段将创建ELF链接的入口,我们的指令用于操作寄存器设置我们的系统调用(多个),以及我们的指令给内核执行我们的系统调用。
首先,我们需要使用define byte或者db把我们的字符串添加到.data段中:
msg: db “Hello World!:,0x0a ; the string, followed by a new line character
接下来,我们需要决定什么系统调用将用于我们的汇编指令。为了查看可用的系统调用,我们需要查看“uninstd_32.h”文件,一般存在于“/usr/include/i386-linux-gnu/asm/”或者可能在其他位置。我们可以打开这个文件查看可用的调用:
立即看到两个我们利用的系统调用,exit函数(#define __NR_exit 1)和write函数(#define __NR_write 4)。注意着两个系统调用号因为我们会在后面使用到。我们可以使用“man 2”来查看关于这些系统调用的细节。(例如:man 2 write):
查看man文件,看到我们需要使用多个字段,‘int fd’(字段描述符),‘const void *buf’(缓冲区),‘size_t count’(字符串大小)。在这个例子中,我们的字段描述符指示我们将要写入的位置(0代表标准输入,1代表标准输出,2代表标准错误)。在这里,我们的缓冲区,就是‘Hello World!’字符串,计数器就是缓冲区的长度。总括来说,我们有几下几点:
- syscall:4;系统调用号代表我们的write命令
- fd:1;字段描述符指示我们的字符串将被写到标准输出
- *buf:msg;我们在.data段中创建的hello world字符串
- count:13;我们缓冲区的长度12加上一个换行符
现在,我们已经标识的必要的信息,我们可以开始操作寄存器了。要做到这一点,我们将使用Intel系统结构的寄存器操作的mov命令:
mov [destination],
我们将重复mov与四个字段的每一个,依次为EAX,EBX,ECX和EDX寄存器,后面再加上”int 0x80”命令来执行系统调用。
section .text global _start ;default entry point for linking _start: ; entry point for commands ; use the write syscall to print 'Hello world!' to stdout mov eax, 4 ; move syscall 4(write) to the eax register mov ebx, 1 ; move field descriptor for stdout to ebx mov ecx, msg ; move the memory address of our string to ecx mov edx, 13 ; move the length of the string to edx int 0x80 ; execute the syscall section .data msg: db “Hello world!”, 0x0a ; the string, followed by a new line character
现在,我们已经小心的编写了write系统调用。我们需要遵循相同的步骤,干净执行程序。要做到这一点,我们将使用前面提到的“exit”的系统调用.这一次,我们仅需要利用”int status“,下面的步骤用于exit系统调用后,你的代码将和下面类似:
section .text global _start ;default entry point for linking _start: ; entry point for commands ; use the write syscall to print 'Hello world!' to stdout mov eax, 4 ; move syscall 4(write) to the eax register mov ebx, 1 ; move field descriptor for stdout to ebx mov ecx, msg ; move the memory address of our string to ecx mov edx, 13 ; move the length of the string to edx int 0x80 ; execute the syscall ; use the exit syscall to exit the program with a status code of 0 mov eax, 1 ; mov syscall 1(exit) to the eax register) mov ebx, 0 ; move status code to ebx int 0x80 ; execute the syscall section .data msg: db “Hello world!”, 0x0a ; the string, followed by a new line character
创建我们的可执行程序:
现在,我们的汇编代码已经创建了,接下来将要把它编译称为目标文件,然后使用链接器创建我们的ELF可执行文件,我们使用如下的NASM命令来创建我们的目标文件:
nasm -f elf32 -o <output object file> <input assembly file>
现在我们有了一个成功的目标文件,我们可以使用ld来链接它,然后创建最后的执行文件。我们使用如下命令:
ld -o <output file> <input object file>
假设这种情况成功了,我们应该有了一个全功能的ELF可执行程序。现在,我们可以执行我们的文件,并且保证正确执行。
附上NASM的下载地址:http://www.nasm.us/pub/nasm/releasebuilds/2.11.08/