浅入浅出Liunx Shellcode

/*――――――――――――-
Author:旋木木【B.C.T】[xuanmumu@gmail.com]
Date:2008/05/12
Website:www.bugshower.org
――――――――――――*/

一:什么是shellcode
话说某天某爱国安全编译了一个Nday溢出利用程序来攻击CNN,输入IP并且enter之后发现目标服务器没有反应,于是拿出sniffer抓包分析… “Oh ,my dog!居然没有带shellcode!”为什么 shellcode对于一个exploit来说这么重要呢?Shellcode到底是什么东西呢?
简单的说,Shellcode是一段能够完成某种特定功能的二进制代码 。具体完成什么任务是由攻击者决定的,可能是开启一个新的shell或者下载某个特定的程序也或者向攻击者返回一个shell等等 。
因为shellcode将会直接操作寄存器和一些系统调用,所以对于shellcode的编写基本上是用高级语言编写一段程序然后编译,反汇编从而得到16进制的操作码,当然也可以直接写汇编然后从二进制文件中提取出16进制的操作码 。
接下来就一起来解开shellcode的神秘面纱吧~

二:Linux系统调用
为什么编写shellcode需要了解系统调用呢?因为系统调用是用户态和内核态之间的一座桥梁 。大多数操作系统都提供了很多应用程序可以访问到的核心函数,shellcode当然也需要调用这些核心函数 。Linux系统提供的核心函数可以方便的实现用来访问文件,执行命令,网络通信等等功能 。这些函数就被成为系统调用(System Call) 。
想知道系统上到底有哪些系统调用可以用,直接查看内核代码即可得到 。Linux的系统调用在以下文件中定义:/usr/include/asm-i386 /unistd.h,该文件包含了系统中每个可用的系统调用的定义,内容大概如下:
#ifndef _ASM_I386_UNISTD_H_
#define _ASM_I386_UNISTD_H_

/*
* This file contains the system call numbers.
*/

#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
.
.
.
.
每个系统调用都有一个名称和相对应的系统调用号组成,由于该文件很长就不一一列出了 。知道了linux系统调用是什么样子,下面就来了解下如何使用这些系统调用 。启动一个系统调用需要使用int指令,linux系统调用位于中断0×80 。当执行一个int 0×80指令后,发出一个软中断,强制内核停止当前工作来处理中断 。内核首先检查传入参数的正确性,然后将下面寄存器的值复制到内核的内存空间,接下来参照中断描述符表(IDT)来处理中断 。系统调用完成以后,继续执行int指令后的下一条指令 。
系统调用号是确定一个系统调用的关键数字,在执行int指令之前,它应当被传入EAX寄存器中,确定了一个系统调用号之后就要考虑给该系统调用传递什么参数来完成什么样的功能 。存放参数的寄存器有5个,他们是EBX,ECX,EDX,ESI和EDI,这五个寄存器顺序的存放传入的系统调用参数 。需要超过6 个输入参数的系统调用使用不同的方法把参数传递给系统调用 。EBX寄存器用于保护指向输入参数的内存位置的指针,输入参数按照连续的顺序存储 。系统调用使用这个指针访问内存位置以便读取参数 。
为了更好的说明一个系统调用的使用全过程,我们来看一个例子,这个例子中调用了write系统调用来将hello,syscall写入到终端,并最终调用exit系统调用安全退出 。
代码如下:
.section .data
output:
.ascii “hello,syscall!!!!/n”
output_end:
.equ len,output_end - output
.section .text

推荐阅读