← 목록으로

NX/DEP 우회와 Return-to-libc

[Pwn] Pwnable · 작성: 2026-07-19 19:31:03 · 조회 4

NX(No-eXecute, DEP라고도 부름)는 스택이나 힙처럼 데이터가 저장되는 영역을 실행 불가능하게 표시하는 보호 기법이다. 이게 걸리면 스택에 셸코드를 올려서 바로 실행시키는 옛날 방식이 안 통한다. Return-to-libc(ret2libc)는 이걸 우회하는 가장 기본적인 방법이다.

왜 셸코드 직접 실행이 막히는가

NX 이전에는 버퍼에 셸코드(기계어)를 직접 써넣고, 리턴 주소를 그 버퍼 주소로 덮어서 셸코드로 바로 점프시키는 게 표준적인 공격이었다. NX가 걸리면 스택 영역은 "쓰기는 되지만 실행은 안 되는" 상태가 되어서, 이 방식 자체가 원천 차단된다.

Return-to-libc의 아이디어

새 코드를 심는 대신, 이미 실행 가능하다고 표시된 영역에 있는 기존 코드를 재사용하면 어떨까? libc는 어차피 실행 가능한 라이브러리이고, 그 안에는 system() 같은 강력한 함수가 이미 들어있다. 리턴 주소를 system()의 주소로 덮고, 그 다음 스택에 인자로 쓸 "/bin/sh" 문자열의 주소를 준비해두면 system("/bin/sh")가 호출된 것과 같은 효과를 낸다.

[ 리턴 주소 자리 ] = system() 주소
[ system이 끝난 뒤 돌아갈 주소 ] = 아무거나 (exit 주소 등)
[ "system"의 인자 ] = "/bin/sh" 문자열 주소

x86(32비트)에서는 인자가 스택에 그대로 쌓이는 방식이라 이렇게 간단하게 되지만, x86-64에서는 함수 인자를 레지스터(RDI, RSI...)로 넘기기 때문에 리턴 주소만 덮어서는 안 되고, "RDI에 원하는 값을 넣고 ret 하는" 가젯이 하나 더 필요하다. 이게 바로 ROP의 가장 단순한 형태이고, 다음 글에서 본격적으로 다룬다.

ASLR이 켜져 있다면

libc 자체도 매 실행마다 다른 주소에 로드되므로, system()의 절대 주소를 미리 알 수 없다. 이 경우 먼저 libc의 실제 로드 베이스 주소를 알아내는 leak 과정이 선행되어야 한다 (다음 글 "ASLR과 정보 유출" 참고). GOT(Global Offset Table)에 이미 채워진 libc 함수 주소를 읽어내는 게 대표적인 방법이다.

pwntools 예시 (ASLR 꺼진 상태 가정)

from pwn import *

elf = context.binary = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process()

offset = 72
system_addr = libc.symbols['system']
binsh_addr = next(libc.search(b'/bin/sh'))
pop_rdi = 0x0000000000401234  # ROPgadget으로 찾은 가젯

payload = b'A' * offset
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)

p.sendline(payload)
p.interactive()

핵심은 "새 코드를 심을 수 없다면, 이미 있는 코드를 원하는 순서로 실행시킨다"는 발상이고, 이 발상을 훨씬 정교하게 확장한 게 ROP다.

Pwnable 카테고리의 글 (5/14)

  1. 메모리 구조 기초: 스택, 힙, 레지스터부터 이해하기
  2. 스택 버퍼 오버플로우: 가장 기본적인 메모리 손상 공격
  3. Format String 취약점: printf가 위험해지는 순간
  4. Stack Canary: 원리와 우회 방법
  5. NX/DEP 우회와 Return-to-libc
  6. ASLR과 정보 유출(Leak)
  7. ROP (Return-Oriented Programming) 완전 정복
  8. 힙 익스플로잇 기초: Use-After-Free와 Double Free
  9. pwntools 실전 활용법
  10. UDF(User-Defined Function) 기반 DB 권한 상승: 원리부터 실전까지
  11. Pass-the-Hash / Pass-the-Ticket: 원리와 실전
  12. eBPF 기반 지속성(Persistence) 기법
  13. Full RELRO 환경에서의 GOT Overwrite와 우회
  14. ret2dlresolve: 심볼 정보 없이 임의 함수 호출하기
← Stack Canary: 원리와 우회 방법 ASLR과 정보 유출(Leak) →