Skip to main content

0x07 소프트웨어 보안 3 (Advanced Software Security)

1. DEP 복습 및 한계

1.1 DEP(Data Execution Prevention) 개요

하드웨어 지원 (64비트 x86):
• NX (No-eXecute) 비트 도입
• 페이지 테이블 엔트리 플래그:
- P bit: 페이지 접근 가능 여부
- R/W bit: 페이지 수정 가능 여부
- XD bit: 페이지 코드 실행 가능 여부 (Execute Disable)

1.2 W⊕X 정책 적용

운영체제의 메모리 보호:
0x00000000
├─ .text : R-X (읽기, 실행 가능)
├─ .data : RW- (읽기, 쓰기 가능)
├─ .bss : RW- (읽기, 쓰기 가능)
├─ heap : RW- (읽기, 쓰기 가능) ← 실행 불가
└─ stack : RW- (읽기, 쓰기 가능) ← 실행 불가
0xFFFFFFFF

예외 사항:
• JIT (Just-In-Time Compilation): JavaScript 등

1.3 스택 기반 코드 주입 공격의 종료

DEP 적용 전: 스택에 셸코드 주입 → 실행 → 공격 성공
DEP 적용 후: 스택에 셸코드 주입 → 실행 시도 → Segmentation Fault

결과: 스택 기반 코드 주입 공격 완전 차단

2. 해커의 대응: 코드 재사용 공격

2.1 공격자의 새로운 전략

DEP 도입 (1997년) → 스택 실행 불가 → 코드 주입 불가능

공격자의 통찰: "기존 코드를 재사용하자!"

코드 재사용 공격 (Code Reuse Attack) 개발

💡 핵심 아이디어: 새로운 코드를 주입할 수 없다면, 이미 메모리에 존재하는 합법적인 코드를 악용하자.

3. ret2libc 공격

3.1 ret2libc 기본 개념

ret2libc (Return-to-libc):
• 최초의 코드 재사용 공격
• 목표: C 라이브러리 함수 악용
• 원리: 버퍼 오버플로우로 libc 함수 호출

주요 타겟 함수들

  • system("path"): 셸 명령 실행
  • open("path", flags): 파일 열기
  • read("fd", buf, sz): 파일 읽기

3.2 LIBC 라이브러리

LIBC (C 라이브러리):
• 대부분 프로그램이 공통으로 사용하는 표준 라이브러리
• printf, malloc, system 등 "내장" 함수들의 실제 구현체
• 거의 모든 프로그램에 링크되어 있음

공격에 유용한 함수들

  • EXEC 계열: execl, execlp, execve - 새 파일 실행
  • system(): 주어진 셸 명령 실행
  • mprotect(): 메모리 권한 변경
  • mmap(): 메모리 매핑

3.3 ret2libc 공격 구조

기본 공격 스택 레이아웃

공격 페이로드:
┌─────────────────┐ ← 높은 주소
│ P │
├─────────────────┤
│ Q │
├─────────────────┤
│ "/bin/sh" │ ← 주입된 문자열
├─────────────────┤
│ Overwritten │ ← 덮어씀
├─────────────────┤
│ Addr of system()│ ← Return Address
├─────────────────┤
│ Dummy Ret Addr │ ← system() 반환 주소
├─────────────────┤
│ PTR to "/bin/sh"│ ← system() 인자
└─────────────────┘ ← ESP (낮은 주소)

3.4 x86 호출 규약 복습

함수 호출 과정

void foo() {
bar(a, b, c);
}

void bar() {
char buf[40];
return;
}

어셈블리 코드

foo():
push c ; 3번째 인자
push b ; 2번째 인자
push a ; 1번째 인자
call bar ; bar() 호출

bar():
push ebp ; 함수 프롤로그
mov ebp, esp
sub esp, 0x28

함수 진입 시 스택 구조

┌─────────────────┐
│ Return Address │ ← 함수 종료 후 돌아갈 주소
├─────────────────┤
│ A (1st arg) │ ← 첫 번째 인자
├─────────────────┤
│ B (2nd arg) │ ← 두 번째 인자
├─────────────────┤
│ C (3rd arg) │ ← 세 번째 인자
└─────────────────┘ ← ESP

3.5 ret2libc 실행 과정

1단계: 취약 함수에서 반환

victim_func():
...
ret ← EIP가 여기 위치

2단계: system() 함수 진입

스택 상태:
┌─────────────────┐
│ Addr of system()│ ← 덮어쓴 Return Address
├─────────────────┤
│ Dummy Ret Addr │ ← system() 종료 후 가는 곳
├─────────────────┤
│ PTR to "/bin/sh"│ ← system() 인자
└─────────────────┘ ← ESP

system() 관점에서:
• Return Address = Dummy Ret Addr
• 첫 번째 인자 = PTR to "/bin/sh"

3.6 다중 함수 호출

연속된 libc 함수 호출

스택 구조:
┌─────────────────┐
│ 1st LIBC Func │ ← 첫 번째 함수 주소
├─────────────────┤
│ 2nd LIBC Func │ ← 두 번째 함수 주소 (첫 번째 반환 주소)
├─────────────────┤
│ Arg to 1st │ ← 첫 번째 함수 인자
├─────────────────┤
│ Arg to 2nd │ ← 두 번째 함수 인자
└─────────────────┘

스택 포인터 조정

문제: 첫 번째 함수가 여러 인자를 가질 때 스택 정렬 문제

해결책: 스택 포인터 리프팅 (Stack Pointer Lifting)

; 프로그램 어딘가에 있는 가젯
pop
pop
ret

복잡한 다중 호출 예시

스택 레이아웃:
┌─────────────────┐
│ Addr of open() │ ← 첫 번째 함수
├─────────────────┤
│ Addr of POP/POP/│ ← 스택 정리 가젯
│ RET │
├─────────────────┤
│ 1st ARG for open│ ← open() 첫 번째 인자
├─────────────────┤
│ 2nd ARG for open│ ← open() 두 번째 인자
├─────────────────┤
│ Addr of read() │ ← 두 번째 함수
├─────────────────┤
│ Dummy Ret Addr │
├─────────────────┤
│ 1st ARG for read│ ← read() 첫 번째 인자
└─────────────────┘

실행 흐름:

  1. open(path, flags) 호출
  2. POP/POP/RET 가젯 실행으로 스택 정리
  3. read(fd, buf, count) 호출

💡 추가 정보: 스택 포인터 리프팅은 ROP 공격의 기초가 되는 기법으로, 함수 호출 후 스택을 올바른 상태로 복원하는 역할을 합니다.

4. Return-Oriented Programming (ROP)

4.1 ROP의 진화

ret2libc의 한계: 
• libc 함수들만 사용 가능
• 제한적인 기능

ROP의 등장:
• 임의의 코드 조각 재사용
• 튜링 완전한 프로그래밍 가능

4.2 가젯 (Gadget) 개념

가젯의 정의

가젯 (Gadget):
• ret 명령어로 끝나는 짧은 코드 조각
• 기존 프로그램의 코드를 재해석하여 생성
• 각 가젯은 작은 작업 수행 후 다음 가젯으로 제어 전달

가젯 예시 1: 함수 끝부분 활용

long ab_plus_c(long a, long b, long c) {
return a*b + c;
}
00000000004004d0 <ab_plus_c>:
4004d0: 48 0f af fe imul %rsi,%rdi
4004d4: 48 8d 04 17 lea (%rdi,%rdx,1),%rax ← 가젯 시작
4004d8: c3 retq ← 가젯 끝

가젯 주소: 0x4004d4
기능: rax ← rdi + rdx

가젯 예시 2: 바이트코드 재해석

void setval(unsigned *p) {
*p = 3347663060u; // 0xc78948d4
}
<setval>:
4004d9: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
4004df: c3 retq

가젯 추출:
주소 0x4004dc부터:
48 89 c7 movq %rax, %rdi ← 가젯!
c3 retq

4.3 ROP 실행 메커니즘

ROP 체인 실행:
┌─────────────────┐
│ Gadget 1 │ ← ret 명령어로 여기로 점프
│ ... │
│ ret │ ← 다음 가젯 주소 pop
├─────────────────┤
│ Gadget 2 │ ← 두 번째 가젯 실행
│ ... │
│ ret │ ← 또 다음 가젯으로
├─────────────────┤
│ Gadget n │ ← 마지막 가젯
│ ... │
│ ret │
└─────────────────┘

%rsp (스택 포인터)

4.4 ROP 체인 패턴화

패턴 1: 레지스터에 값 할당

unsigned int REG = VALUE;

ROP 체인:

┌─────────────────┐
│ &(pop reg; ret;)│ ← 가젯 주소
├─────────────────┤
│ VALUE │ ← 할당할 값
└─────────────────┘

패턴 2: 메모리 쓰기

unsigned int *PTR = ADDR;
*PTR = VALUE;

ROP 체인:

┌─────────────────┐
│ &(pop reg1; ret)│ ← 주소를 reg1에 로드
├─────────────────┤
│ ADDR │
├─────────────────┤
│ &(pop reg2; ret)│ ← 값을 reg2에 로드
├─────────────────┤
│ VALUE │
├─────────────────┤
│&(mov [reg1],reg2│ ← 메모리에 쓰기
│ ; ret) │
└─────────────────┘

패턴 3: 함수 호출 (x86-64)

func(arg1, arg2, ...);

x86-64 호출 규약:

  • 첫 번째 인자: %rdi
  • 두 번째 인자: %rsi
  • 세 번째 인자: %rdx
  • 네 번째 인자: %rcx

ROP 체인:

┌─────────────────┐
│ &(pop rdi; ret) │ ← 첫 번째 인자 설정
├─────────────────┤
│ ARG1 │
├─────────────────┤
│ &(pop rsi; ret) │ ← 두 번째 인자 설정
├─────────────────┤
│ ARG2 │
├─────────────────┤
│ &func │ ← 함수 호출
└─────────────────┘

다중 함수 호출

func1(arg1, arg2);
func2(arg1);

ROP 체인:

┌─────────────────┐
│ &(pop rdi; ret) │ ← func1 첫 번째 인자
├─────────────────┤
│ ARG1 of func1 │
├─────────────────┤
│ &(pop rsi; ret) │ ← func1 두 번째 인자
├─────────────────┤
│ ARG2 of func1 │
├─────────────────┤
│ &func1 │ ← func1 호출
├─────────────────┤
│ &(pop rdi; ret) │ ← func2 첫 번째 인자
├─────────────────┤
│ ARG1 of func2 │
├─────────────────┤
│ &func2 │ ← func2 호출
└─────────────────┘

5. 주소 공간 레이아웃 무작위화 (ASLR)

5.1 ASLR 개념과 목적

ASLR (Address Space Layout Randomization):
• 코드 재사용 공격은 함수/가젯 주소를 알아야 함
• ASLR은 세그먼트의 기본 주소를 무작위화
• 공격자가 정확한 주소 예측을 어렵게 만듦

ASLR이 무작위화하는 영역

  • 공유 라이브러리: mylib.so
  • 스택 세그먼트: 지역 변수 영역
  • 힙 세그먼트: 동적 할당 메모리

5.2 ASLR 메모리 레이아웃

ASLR 적용 전:                  ASLR 적용 후:
0x00000000 0x00000000
├─ .text (고정 주소) ├─ .text (고정 주소)
├─ .data (고정 주소) ├─ .data (고정 주소)
├─ libc (고정 주소) ├─ libc (무작위 주소) ← 변화
├─ heap (고정 주소) ├─ heap (무작위 주소) ← 변화
└─ stack (고정 주소) └─ stack (무작위 주소) ← 변화
0xFFFFFFFF 0xFFFFFFFF

5.3 ASLR 우회 기법

1. 무작위화되지 않은 부분 악용

  • Ret2PLT: PLT(Procedure Linkage Table) 활용
  • 실행 가능 코드 세그먼트의 가젯들: .text 영역 가젯 사용

2. 메모리 노출 버그와 연계

공격 단계:
1. 메모리 노출 버그로 주소 정보 유출
2. 유출된 주소로 ASLR 오프셋 계산
3. 계산된 주소로 ROP 체인 구성
4. 실제 공격 수행

3. 기타 상황별 우회 기법

  • 프로그램별 특성 활용
  • 부분 덮어쓰기 공격
  • 브루트 포스 공격 (32비트 시스템)

6. PIE (Position Independent Executable)

6.1 PIE 개념

PIE (Position Independent Executable):
• 실행 파일 자체도 메모리 어디든 배치 가능하게 컴파일
• PC 상대 주소 지정 방식 사용
• 컴파일 옵션: gcc -pie -o prog prog.c

6.2 PIE의 구현

절대 주소 → 상대 주소 변환

// 현재 %RIP 값 얻기
0x565566db <+6>: call 0x56556784 <__x86.get_pc_thunk.di>

// %RIP를 %RDI에 저장
0x56556784 <__x86.get_pc_thunk.di+0>: mov edi, DWORD PTR [esp]
0x56556787 <__x86.get_pc_thunk.di+3>: ret

6.3 ASLR + PIE 조합

완전한 주소 무작위화:
• ASLR: 라이브러리, 스택, 힙 무작위화
• PIE: 실행 파일 코드 영역도 무작위화
• 결과: 모든 메모리 영역이 예측 불가능

6.4 ASLR + PIE 우회

남은 공격 벡터들

  1. 무작위화되지 않은 부분: 여전히 일부 고정 주소 존재
  2. 메모리 노출 버그: 더욱 중요해진 정보 유출 공격
  3. 상황별 우회 기법: 각 프로그램의 특성을 활용한 맞춤형 공격

💡 추가 정보: ASLR과 PIE는 공격을 막는 것이 아니라 어렵게 만드는 방어 기법입니다. 여전히 정보 유출 버그와 결합하면 우회가 가능하므로, 추가적인 방어 기법이 필요합니다.

예상 시험문제

1. ret2libc 공격 원리와 구현

문제: ret2libc 공격이 DEP를 우회하는 원리를 설명하고, system("/bin/sh")를 호출하는 공격 페이로드의 스택 구조를 그려서 설명하시오.

모범답안:

DEP 우회 원리:

  • DEP는 데이터 영역(스택, 힙)에서 코드 실행을 금지
  • ret2libc는 새로운 코드를 주입하지 않고 기존 라이브러리 함수 재사용
  • 이미 실행 가능한 영역(.text)에 있는 libc 함수들을 악용

스택 구조:

공격 전 정상 스택:          공격 후 조작된 스택:
┌─────────────────┐ ┌─────────────────┐
│ Return Address │ │ &system() │ ← 덮어쓴 반환 주소
├─────────────────┤ ├─────────────────┤
│ Saved EBP │ │ Dummy Address │ ← system() 종료 후 주소
├─────────────────┤ ├─────────────────┤
│ buffer[128] │ │ &"/bin/sh" │ ← system() 인자
└─────────────────┘ └─────────────────┘

문자열 영역:
"/bin/sh\0" ← 버퍼 오버플로우로 주입된 문자열

실행 과정:

  1. 버퍼 오버플로우로 Return Address를 system() 주소로 덮어씀
  2. 함수 종료 후 system() 함수로 점프
  3. system()은 스택에서 인자 "/bin/sh"를 찾아 셸 실행
  4. 공격자가 시스템 권한 획득

2. ROP 가젯의 개념과 활용

문제: ROP 공격에서 가젯(Gadget)의 개념을 설명하고, 다음 코드에서 사용 가능한 가젯을 찾아 그 기능을 설명하시오.

004004d0: 48 0f af fe    imul %rsi,%rdi
004004d4: 48 8d 04 17 lea (%rdi,%rdx,1),%rax
004004d8: c3 retq
004004d9: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
004004df: c3 retq

모범답안:

가젯의 개념:

  • ROP에서 사용하는 짧은 코드 조각
  • 반드시 ret 명령어로 끝남
  • 기존 프로그램의 코드를 재해석하여 생성
  • 각 가젯은 작은 연산 수행 후 다음 가젯으로 제어 전달

발견된 가젯들:

가젯 1: 주소 0x4004d4

48 8d 04 17    lea (%rdi,%rdx,1),%rax
c3 retq
  • 기능: %rax ← %rdi + %rdx (두 레지스터 값 더하기)
  • 용도: 주소 계산, 산술 연산

가젯 2: 주소 0x4004dc (바이트코드 재해석)

48 89 c7       movq %rax, %rdi  
c3 retq
  • 기능: %rdi ← %rax (레지스터 간 값 복사)
  • 용도: 함수 인자 준비 (x86-64에서 첫 번째 인자는 %rdi)

활용 예시:

ROP 체인으로 func(value) 호출:
┌─────────────────┐
│ &(pop rax; ret) │ ← value를 %rax에 로드
├─────────────────┤
│ value │
├─────────────────┤
│ 0x4004dc │ ← %rax → %rdi 복사 가젯
├─────────────────┤
│ &func │ ← 함수 호출
└─────────────────┘

3. ASLR의 동작 원리와 한계

문제: ASLR(Address Space Layout Randomization)이 코드 재사용 공격을 방어하는 원리를 설명하고, ASLR의 한계점과 우회 방법들을 서술하시오.

모범답안:

ASLR 방어 원리:

공격자의 필요 조건: 정확한 함수/가젯 주소 정보
ASLR의 대응: 메모리 세그먼트 기본 주소 무작위화

무작위화 대상:
• 공유 라이브러리 (libc.so): 매 실행시 다른 주소에 로드
• 스택 세그먼트: 지역 변수와 함수 호출 스택 위치 변경
• 힙 세그먼트: malloc() 등 동적 할당 메모리 위치 변경

결과: 공격자가 ret2libc, ROP 체인 구성 시 주소 예측 불가능

ASLR의 한계점:

  1. 부분적 무작위화:

    • 실행 파일 자체(.text 영역)는 기본적으로 고정
    • PLT(Procedure Linkage Table)는 여전히 예측 가능
  2. 엔트로피 부족:

    • 32비트: 무작위화 범위가 작아 브루트포스 가능
    • 주소 정렬 요구사항으로 실제 엔트로피 감소
  3. 정보 유출 취약점에 무력:

    • 메모리 노출 버그 시 무작위화 무효화
    • 포인터 하나만 유출되어도 전체 주소 계산 가능

주요 우회 방법들:

  1. 정보 유출 버그 활용:
공격 단계:
1. 메모리 읽기 버그로 libc 함수 주소 유출
2. 알려진 오프셋으로 다른 함수 주소 계산
3. 계산된 주소로 ROP 체인 구성
4. 실제 익스플로잇 수행
  1. 부분 덮어쓰기:

    • 주소의 하위 바이트만 덮어써서 확률적 공격
    • 여러 번 시도하여 성공 확률 높임
  2. 고정 영역 활용:

    • PIE 미적용 시 .text 영역 가젯 사용
    • PLT 테이블의 고정 주소 활용

4. PIE와 ASLR의 차이점

문제: PIE(Position Independent Executable)와 ASLR의 차이점을 설명하고, 두 기법을 모두 적용했을 때의 보안 효과를 분석하시오.

모범답안:

PIE와 ASLR의 차이점:

구분ASLRPIE
대상라이브러리, 스택, 힙실행 파일 자체
적용 시점런타임 (OS 기능)컴파일 타임 (컴파일러 옵션)
주소 지정기존 절대 주소 유지PC 상대 주소로 변환
무작위화 영역동적 세그먼트만.text, .data 등 모든 영역

개별 적용 시 한계:

ASLR만 적용:

보호됨: libc, 스택, 힙
취약점: .text 영역 (실행 파일 코드)
공격 가능: .text 영역의 가젯을 이용한 ROP

PIE만 적용:

보호됨: 실행 파일 코드 영역
취약점: 라이브러리, 스택, 힙
공격 가능: 고정 주소의 libc 함수 호출

ASLR + PIE 조합 효과:

완전한 주소 무작위화 달성:
┌─────────────────┐
│ .text (무작위) │ ← PIE로 보호
├─────────────────┤
│ .data (무작위) │ ← PIE로 보호
├─────────────────┤
│ libc (무작위) │ ← ASLR로 보호
├─────────────────┤
│ heap (무작위) │ ← ASLR로 보호
├─────────────────┤
│ stack (무작위) │ ← ASLR로 보호
└─────────────────┘

결과: 모든 메모리 영역이 예측 불가능

남은 공격 벡터:

  1. 정보 유출 공격: 여전히 가장 효과적인 우회 방법
  2. 부분 덮어쓰기: 확률적 공격 여전히 가능
  3. 사이드 채널 공격: 캐시, 타이밍 등을 통한 주소 추측

5. 코드 재사용 공격의 진화 과정

문제: 스택 기반 코드 주입 공격부터 ROP 공격까지의 진화 과정을 시간순으로 정리하고, 각 단계에서 도입된 방어 기법과 그에 대한 공격자의 대응을 설명하시오.

모범답안:

공격과 방어의 진화 과정:

1단계: 스택 기반 코드 주입 시대 (1980년대-1990년대)

공격 기법:
• 버퍼 오버플로우로 스택에 셸코드 주입
• Return Address를 셸코드 위치로 덮어씀
• NOP Sled로 정확한 주소 예측 문제 해결

방어 기법: 없음 (무방비 상태)

2단계: DEP 도입 (1997년-2000년대)

방어 기법: DEP (Data Execution Prevention)
• W⊕X 정책: 쓰기 가능한 메모리는 실행 불가
• 하드웨어 지원 (NX 비트)
• 스택, 힙에서 코드 실행 금지

효과: 스택 기반 코드 주입 공격 완전 차단

3단계: ret2libc 등장 (2000년대 초)

공격자 대응: ret2libc 공격
• 새 코드 주입 대신 기존 라이브러리 함수 재사용
• system(), execve() 등 유용한 함수 호출
• 스택 조작으로 함수 인자 전달

특징: DEP 완전 우회, 코드 실행 없이 시스템 장악

4단계: ASLR 도입 (2000년대 중반)

방어 기법: ASLR (Address Space Layout Randomization)  
• 라이브러리, 스택, 힙 주소 무작위화
• 공격자의 주소 예측 능력 제한
• ret2libc 공격 어려움 증가

한계: 실행 파일 자체는 여전히 고정 주소

5단계: ROP 공격 완성 (2000년대 후반)

공격자 대응: Return-Oriented Programming
• 임의의 코드 조각(가젯) 재사용
• 튜링 완전한 프로그래밍 능력
• ASLR 우회를 위한 정보 유출 공격 결합

혁신점:
• 단순한 함수 호출을 넘어 복잡한 로직 구현 가능
• 가젯 체이닝으로 임의 연산 수행

6단계: PIE 추가 (2010년대)

방어 기법: PIE (Position Independent Executable)
• 실행 파일 코드 영역도 무작위화
• ASLR + PIE로 완전한 주소 무작위화
• 모든 메모리 영역 예측 불가능

현재 상황:
• 여전히 정보 유출 공격으로 우회 가능
• 더욱 정교한 공격 기법 필요
• 하드웨어 기반 새로운 방어 기법 연구 중

핵심 패턴:

방어 기법 도입 → 공격자 적응 → 새로운 공격 기법 → 강화된 방어 기법 → ...

특징:
• 각 방어 기법은 특정 공격만 차단
• 공격자는 항상 새로운 우회 방법 개발
• 완벽한 방어는 존재하지 않음
• 다층 방어(Defense in Depth) 전략 필요

6. ROP 체인 설계 문제

문제: 다음 조건을 만족하는 ROP 체인을 설계하시오.

  • mprotect(addr, len, PROT_READ|PROT_WRITE|PROT_EXEC) 호출
  • addr = 0x601000, len = 0x1000, prot = 7
  • 사용 가능한 가젯: pop rdi; ret, pop rsi; ret, pop rdx; ret

모범답안:

x86-64 호출 규약:

함수 인자 전달 순서:
1st 인자: %rdi
2nd 인자: %rsi
3rd 인자: %rdx

mprotect() 함수 시그니처:

int mprotect(void *addr, size_t len, int prot);
// addr = 0x601000 (메모리 주소)
// len = 0x1000 (페이지 크기 4KB)
// prot = 7 (READ|WRITE|EXEC)

ROP 체인 설계:

스택 레이아웃:
┌─────────────────┐
│ &(pop rdi; ret) │ ← 첫 번째 인자 설정 가젯
├─────────────────┤
│ 0x601000 │ ← addr 값 (첫 번째 인자)
├─────────────────┤
│ &(pop rsi; ret) │ ← 두 번째 인자 설정 가젯
├─────────────────┤
│ 0x1000 │ ← len 값 (두 번째 인자)
├─────────────────┤
│ &(pop rdx; ret) │ ← 세 번째 인자 설정 가젯
├─────────────────┤
│ 7 │ ← prot 값 (세 번째 인자)
├─────────────────┤
│ &mprotect │ ← mprotect() 함수 호출
└─────────────────┘

실행 흐름:

  1. pop rdi; ret 실행: %rdi ← 0x601000
  2. pop rsi; ret 실행: %rsi ← 0x1000
  3. pop rdx; ret 실행: %rdx ← 7
  4. mprotect() 호출: mprotect(0x601000, 0x1000, 7)

결과:

  • 주소 0x601000부터 4KB 영역이 읽기/쓰기/실행 가능하게 변경
  • 이후 해당 영역에 셸코드 주입하여 실행 가능
  • DEP 우회 완료

핵심 요약

  • DEP 도입으로 스택 기반 코드 주입 공격이 차단되었지만, 공격자들은 코드 재사용 공격으로 대응
  • ret2libc는 최초의 코드 재사용 공격으로, 기존 라이브러리 함수를 악용하여 DEP 우회
  • **ROP(Return-Oriented Programming)**는 임의의 코드 가젯을 체이닝하여 튜링 완전한 프로그래밍 실현
  • 가젯은 ret로 끝나는 짧은 코드 조각으로, 기존 프로그램 코드를 재해석하여 생성
  • ASLR은 메모리 주소 무작위화로 코드 재사용 공격을 어렵게 만들지만 완전히 막지는 못함
  • PIE + ASLR 조합으로 모든 메모리 영역 무작위화 가능하지만 정보 유출 공격으로 여전히 우회 가능
  • 공격과 방어의 진화는 지속적인 군비 경쟁 양상으로, 각 방어 기법마다 새로운 우회 기법 등장
  • 다층 방어 전략이 필요하며, 단일 방어 기법으로는 완벽한 보안 불가능

💡 중요: 코드 재사용 공격은 현대 소프트웨어 보안의 핵심 위협으로, 메모리 안전성과 주소 무작위화만으로는 완전한 방어가 어렵습니다. Control Flow Integrity(CFI) 등 추가적인 방어 기법과 함께 메모리 안전 언어로의 전환이 근본적 해결책으로 주목받고 있습니다.