← 목록으로

ASLR과 정보 유출(Leak)

[Pwn] Pwnable · 작성: 2026-07-19 19:31:03 · 수정: 2026-07-21 23:11:54 · 조회 22

ASLR(Address Space Layout Randomization)을 "주소를 랜덤화하는 보호 기법"이라고만 알면, 그 랜덤화가 정확히 커널의 어느 단계에서 이뤄지는지, 그리고 왜 정보 유출 취약점 단 하나가 이 보호 전체를 무의미하게 만드는지를 놓치게 된다.

커널 레벨: ASLR은 언제, 어떻게 주소를 정하는가

프로세스가 execve()로 새로 실행될 때, 리눅스 커널의 로더(binfmt_elf.c의 ELF 로딩 경로)는 스택·힙(정확히는 brk/mmap 영역)·공유 라이브러리(mmap으로 매핑되는 libc 등)·(PIE인 경우) 실행 파일 자신의 베이스 주소를 결정한다. ASLR이 켜져 있으면(/proc/sys/kernel/randomize_va_space), 커널은 이 각 영역의 시작 주소에 get_random_bytes()로 얻은 커널 난수를 이용해 계산한 오프셋을 더해서 매 실행마다 다른 위치에 배치한다. 중요한 건 **"완전히 무작위"가 아니라 "정렬 제약과 엔트로피 비트 수 안에서의 무작위"**라는 점이다 — x86-64 리눅스에서 힙/스택/mmap 영역의 랜덤화 엔트로피는 각각 수십 비트 수준으로, 이론상 무차별 대입은 비현실적이지만 **"주소 전체를 다시 계산할 필요 없이, 베이스 주소 하나만 알면 나머지 오프셋은 고정된 값을 더하기만 하면 된다"**는 성질이 그대로 남는다 — 바이너리 내부 함수 간 상대 거리, libc 내부 함수 간 상대 거리는 ASLR과 무관하게 항상 고정되어 있기 때문이다.

왜 "주소 하나만 새면 전부 무너지는가"

libc가 로드되는 시작 주소(베이스)가 랜덤화되어도, system() 함수가 그 베이스로부터 몇 바이트 떨어져 있는지, "/bin/sh" 문자열이 몇 바이트 떨어져 있는지는 같은 libc 바이너리라면 항상 동일하다. 그래서 공격자가 **libc 안의 아무 함수 주소 하나(예: printf의 GOT 엔트리에 이미 채워진 실제 런타임 주소)**만 유출시키면, 거기서 고정 오프셋을 빼서 libc 베이스 주소를 역산하고, 그 베이스에 system의 고정 오프셋을 더해 정확한 system() 주소를 계산해낼 수 있다. ASLR은 "베이스 주소"라는 단 하나의 값만 감추고 있을 뿐이고, 그 값 하나가 새어나가는 순간 그 프로세스의 나머지 모든 주소 계산이 결정론적으로 풀린다는 게 이 보호 기법의 근본적인 한계다.

페이로드가 실제로 작동하는 순서 (Partial Overwrite + 주소 유출)

  1. Format String이나 별도의 버퍼 오버리드로, 스택이나 GOT에 남아있는 이미 resolve된 libc 함수의 실제 런타임 주소를 한 번 읽어냄
  2. 유출된 주소에서 그 함수의 (알려진, 고정된) libc 내부 오프셋을 빼서 libc 베이스 주소 계산
  3. 베이스 주소에 system/execve, 그리고 "/bin/sh" 문자열의 오프셋을 각각 더해 정확한 절대 주소 확보
  4. 이후 ROP 글에서 다룬 방식대로 pop rdi; ret gadget과 조합해 리턴 주소를 이 계산된 주소로 덮어씀 → ASLR이 켜져 있어도 정확히 원하는 함수를 호출

PIE(Position Independent Executable)와의 관계

PIE는 "바이너리 자기 자신"에도 ASLR을 적용하는 컴파일 옵션이다. PIE가 없으면 실행 파일 자신의 코드/데이터 주소는 고정이라, 바이너리 안의 gadget이나 win() 같은 함수는 ASLR과 무관하게 항상 같은 주소에 있어서 익스플로잇이 훨씬 쉬워진다. PIE가 걸려있으면 이 주소들도 매번 바뀌므로, 바이너리 자신의 주소 하나도 별도로 유출시켜야 한다 — checksec 결과에서 PIE 여부를 확인하는 이유가 여기 있다.

여기서 끝나지 않는다: 체이닝

ASLR을 "주소를 숨기는 기법"이 아니라 **"베이스 주소라는 단 하나의 비밀에 나머지 모든 계산이 의존하도록 만든 구조"**로 이해하면, 왜 실전 익스플로잇에서 "메모리 손상 취약점 하나"보다 오히려 "정보 유출 취약점 하나"의 가치가 더 높게 평가되는 경우가 많은지가 같은 논리로 설명된다.

Pwnable 카테고리의 글 (6/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: 심볼 정보 없이 임의 함수 호출하기
← NX/DEP 우회와 Return-to-libc ROP (Return-Oriented Programming) 완전 정복 →