mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/common-binary-protections-and-bypas
This commit is contained in:
parent
b1a7068b44
commit
278569a4d3
@ -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** 항목을 덮어쓰고 실행을 전환합니다.<br>2. **ret2dlresolve** – 쓰기 가능한 세그먼트에서 가짜 `Elf64_Rela` 및 `Elf64_Sym`을 작성하고 `_dl_runtime_resolve`를 호출합니다.<br>3. **.fini_array** / **atexit()** 목록의 함수 포인터를 덮어씁니다. |
|
||||
| 전체 | GOT는 읽기 전용 | 1. **다른 쓰기 가능한 코드 포인터** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, 사용자 정의 `.data` 섹션의 콜백, JIT 페이지)를 찾습니다.<br>2. *상대 읽기* 원시를 악용하여 libc를 유출하고 **SROP/ROP into libc**를 수행합니다.<br>3. **DT_RPATH**/`LD_PRELOAD`를 통해 악성 공유 객체를 주입합니다 (환경이 공격자 제어 하에 있는 경우) 또는 **`ld_audit`**.<br>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}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user