From 278569a4d30d49c2da560381d84500e152a5d163 Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 17 Jul 2025 00:13:14 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/common-binary-protections-and-bypas --- .../relro.md | 92 ++++++++++++++++--- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md b/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md index ba2b8a89e..11c8c443d 100644 --- a/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md +++ b/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md @@ -4,30 +4,96 @@ ## Relro -**RELRO**는 **Relocation Read-Only**의 약자로, **GOT (Global Offset Table)** 오버라이트와 관련된 위험을 완화하기 위해 바이너리에서 사용되는 보안 기능입니다. **RELRO** 보호에는 두 가지 유형이 있습니다: (1) **Partial RELRO**와 (2) **Full RELRO**. 두 가지 모두 ELF 파일의 **GOT**와 **BSS**를 재정렬하지만, 결과와 의미는 다릅니다. 구체적으로, **GOT** 섹션을 **BSS**보다 _앞에_ 배치합니다. 즉, **GOT**는 **BSS**보다 낮은 주소에 위치하므로, **BSS**에서 변수를 오버플로우하여 **GOT** 항목을 덮어쓰는 것이 불가능합니다 (메모리에 쓰는 것은 낮은 주소에서 높은 주소로 진행됩니다). +**RELRO**는 **Relocation Read-Only**의 약자로, 링커(`ld`)에 의해 구현된 완화 조치로, ELF의 데이터 세그먼트의 하위 집합을 **모든 재배치가 적용된 후 읽기 전용으로 전환**합니다. 목표는 공격자가 프로그램 실행 중에 역참조되는 **GOT (Global Offset Table)** 또는 기타 재배치 관련 테이블의 항목을 덮어쓰는 것을 방지하는 것입니다 (예: `__fini_array`). -명확성을 위해 이 개념을 두 가지 유형으로 나누어 보겠습니다. +현대 링커는 **GOT** (및 몇 가지 다른 섹션)을 **.bss**보다 **앞서** 위치시키고, 가장 중요한 것은 동적 로더가 재배치를 적용한 후 `R–X`로 다시 매핑되는 전용 `PT_GNU_RELRO` 세그먼트를 생성하여 RELRO를 구현합니다. 따라서 **.bss**에서의 전형적인 버퍼 오버플로우는 더 이상 GOT에 도달할 수 없으며, 임의 쓰기 원시를 사용하여 RELRO로 보호된 페이지 내의 함수 포인터를 덮어쓸 수 없습니다. -### **Partial RELRO** +링커가 생성할 수 있는 보호 수준은 **두 가지**가 있습니다: -**Partial RELRO**는 바이너리의 성능에 큰 영향을 주지 않으면서 보안을 강화하는 더 간단한 접근 방식을 취합니다. Partial RELRO는 **.got을 읽기 전용으로 만듭니다 (GOT 섹션의 비-PLT 부분)**. 나머지 섹션(예: .got.plt)은 여전히 쓰기가 가능하므로 공격의 대상이 될 수 있습니다. 이는 **임의 쓰기** 취약점으로 인해 GOT가 악용되는 것을 **막지 않습니다**. +### Partial RELRO -참고: 기본적으로 GCC는 Partial RELRO로 바이너리를 컴파일합니다. +* 플래그 `-Wl,-z,relro` (또는 `ld`를 직접 호출할 때 `-z relro`)로 생성됩니다. +* **GOT**의 **비-PLT** 부분(데이터 재배치에 사용되는 부분)만 읽기 전용 세그먼트에 배치됩니다. 런타임에 수정해야 하는 섹션 – 가장 중요한 **.got.plt**는 **지연 바인딩**을 지원하며 여전히 쓰기가 가능합니다. +* 이로 인해 **임의 쓰기** 원시가 PLT 항목을 덮어쓰거나 **ret2dlresolve**를 수행하여 실행 흐름을 리디렉션할 수 있습니다. +* 성능 영향은 미미하며, 따라서 **거의 모든 배포판이 수년간 최소 Partial RELRO로 패키지를 제공해왔습니다 (2016년부터 GCC/Binutils의 기본값입니다)**. -### **Full RELRO** +### Full RELRO -**Full RELRO**는 **전체 GOT (both .got and .got.plt)와 .fini_array** 섹션을 완전히 **읽기 전용**으로 만들어 보호를 강화합니다. 바이너리가 시작되면 모든 함수 주소가 해결되고 GOT에 로드된 후, GOT는 읽기 전용으로 표시되어 런타임 중에 수정이 효과적으로 방지됩니다. +* **두 가지** 플래그 `-Wl,-z,relro,-z,now` (일명 `-z relro -z now`)로 생성됩니다. `-z now`는 동적 로더가 **모든** 기호를 미리 해결하도록 강제하여 (즉각적 바인딩) **.got.plt**가 다시 쓰일 필요가 없고 안전하게 읽기 전용으로 매핑될 수 있도록 합니다. +* 전체 **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** 및 몇 가지 추가 내부 glibc 테이블이 읽기 전용 `PT_GNU_RELRO` 세그먼트에 포함됩니다. +* 측정 가능한 시작 오버헤드를 추가하지만 (모든 동적 재배치가 시작 시 처리됨) **런타임 오버헤드는 없습니다**. -그러나 Full RELRO의 단점은 성능과 시작 시간 측면에서 발생합니다. GOT를 읽기 전용으로 표시하기 전에 모든 동적 기호를 시작 시 해결해야 하므로, **Full RELRO가 활성화된 바이너리는 더 긴 로드 시간을 경험할 수 있습니다**. 이 추가 시작 오버헤드는 Full RELRO가 모든 바이너리에서 기본적으로 활성화되지 않는 이유입니다. +2023년부터 여러 주요 배포판이 **기본적으로 Full RELRO로 시스템 툴 체인** (및 대부분의 패키지)을 컴파일하는 것으로 전환했습니다 – 예: **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** 및 **Fedora 35+**. 따라서 펜테스터로서 **모든 GOT 항목이 읽기 전용인** 바이너리를 만날 것으로 예상해야 합니다. -Full RELRO가 **활성화**되어 있는지 확인하는 방법은 다음과 같습니다: +--- + +## How to Check the RELRO status of a binary ```bash -readelf -l /proc/ID_PROC/exe | grep BIND_NOW +$ checksec --file ./vuln +[*] '/tmp/vuln' +Arch: amd64-64-little +RELRO: Full +Stack: Canary found +NX: NX enabled +PIE: No PIE (0x400000) +``` +`checksec` (부분 [pwntools](https://github.com/pwncollege/pwntools) 및 많은 배포판) 는 `ELF` 헤더를 파싱하고 보호 수준을 출력합니다. `checksec`를 사용할 수 없는 경우, `readelf`에 의존하세요: +```bash +# Partial RELRO → PT_GNU_RELRO is present but BIND_NOW is *absent* +$ readelf -l ./vuln | grep -E "GNU_RELRO|BIND_NOW" +GNU_RELRO 0x0000000000600e20 0x0000000000600e20 ``` -## 우회 -Full RELRO가 활성화되어 있으면, 임의 실행을 얻기 위해 GOT 테이블에 쓰지 않는 다른 방법을 찾아야만 우회할 수 있습니다. +```bash +# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag +$ readelf -d ./vuln | grep BIND_NOW +0x0000000000000010 (FLAGS) FLAGS: BIND_NOW +``` +이진 파일이 실행 중인 경우(예: set-uid root 헬퍼), **`/proc/$PID/exe`**를 통해 실행 파일을 여전히 검사할 수 있습니다: +```bash +readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO +``` +--- -**LIBC의 GOT는 일반적으로 Partial RELRO이므로**, 임의 쓰기로 수정할 수 있습니다. 더 많은 정보는 [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.** +## 자신의 코드를 컴파일할 때 RELRO 활성화하기 +```bash +# GCC example – create a PIE with Full RELRO and other common hardenings +$ gcc -fPIE -pie -z relro -z now -Wl,--as-needed -D_FORTIFY_SOURCE=2 main.c -o secure +``` +`-z relro -z now`는 **GCC/clang**( `-Wl,` 뒤에 전달됨)와 **ld** 모두에서 작동합니다. **CMake 3.18+**를 사용할 때는 내장 프리셋을 통해 전체 RELRO를 요청할 수 있습니다: +```cmake +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO +set(CMAKE_ENABLE_EXPORTS OFF) +set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro,-z,now") +``` +--- + +## 우회 기술 + +| RELRO 수준 | 일반적인 원시 | 가능한 악용 기술 | +|-------------|-------------------|----------------------------------| +| 없음 / 부분 | 임의 쓰기 | 1. **.got.plt** 항목을 덮어쓰고 실행을 전환합니다.
2. **ret2dlresolve** – 쓰기 가능한 세그먼트에서 가짜 `Elf64_Rela` 및 `Elf64_Sym`을 작성하고 `_dl_runtime_resolve`를 호출합니다.
3. **.fini_array** / **atexit()** 목록의 함수 포인터를 덮어씁니다. | +| 전체 | GOT는 읽기 전용 | 1. **다른 쓰기 가능한 코드 포인터** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, 사용자 정의 `.data` 섹션의 콜백, JIT 페이지)를 찾습니다.
2. *상대 읽기* 원시를 악용하여 libc를 유출하고 **SROP/ROP into libc**를 수행합니다.
3. **DT_RPATH**/`LD_PRELOAD`를 통해 악성 공유 객체를 주입합니다 (환경이 공격자 제어 하에 있는 경우) 또는 **`ld_audit`**.
4. **형식 문자열** 또는 부분 포인터 덮어쓰기를 악용하여 GOT에 손대지 않고 제어 흐름을 전환합니다. | + +> 💡 전체 RELRO가 있더라도 **로드된 공유 라이브러리의 GOT (예: libc 자체)**는 **부분 RELRO**만 있습니다. 왜냐하면 이러한 객체는 로더가 재배치를 적용할 때 이미 매핑되기 때문입니다. **임의 쓰기** 원시를 얻으면 다른 공유 객체의 페이지를 대상으로 하여 libc의 GOT 항목이나 `__rtld_global` 스택을 덮어써서 실행을 전환할 수 있습니다. 이는 현대 CTF 챌린지에서 정기적으로 악용되는 기술입니다. + +### 실제 우회 예시 (2024 CTF – *pwn.college “enlightened”*) + +이 도전 과제는 전체 RELRO와 함께 제공되었습니다. 이 악용은 **오프 바이 원**을 사용하여 힙 청크의 크기를 손상시키고, `tcache poisoning`으로 libc를 유출한 후, `__free_hook` (RELRO 세그먼트 외부)을 원가젯으로 덮어써서 코드 실행을 얻었습니다. GOT 쓰기는 필요하지 않았습니다. + +--- + +## 최근 연구 및 취약점 (2022-2025) + +* **glibc 2.40에서 `__malloc_hook` / `__free_hook` 비권장 (2025)** – 이러한 기호를 악용한 대부분의 현대 힙 악용은 이제 **`rtld_global._dl_load_jump`** 또는 C++ 예외 테이블과 같은 대체 벡터로 전환해야 합니다. 후크가 **RELRO 외부**에 존재하기 때문에 이들의 제거는 전체 RELRO 우회 난이도를 증가시킵니다. +* **Binutils 2.41 “최대 페이지 크기” 수정 (2024)** – 버그로 인해 RELRO 세그먼트의 마지막 몇 바이트가 일부 ARM64 빌드에서 쓰기 가능한 데이터와 페이지를 공유할 수 있어 `mprotect` 이후에 쓸 수 있는 작은 **RELRO 갭**이 남았습니다. 업스트림은 이제 `PT_GNU_RELRO`를 페이지 경계에 정렬하여 해당 엣지 케이스를 제거합니다. + +--- + +## 참조 + +* Binutils 문서 – *`-z relro`, `-z now` 및 `PT_GNU_RELRO`* +* *“RELRO – 전체, 부분 및 우회 기술”* – 블로그 게시물 @ wolfslittlered 2023 {{#include ../../banners/hacktricks-training.md}}