mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
474 lines
28 KiB
Markdown
474 lines
28 KiB
Markdown
# ELF 기본 정보
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## 프로그램 헤더
|
||
|
||
로더에게 **ELF**를 메모리에 로드하는 방법을 설명합니다:
|
||
```bash
|
||
readelf -lW lnstat
|
||
|
||
Elf file type is DYN (Position-Independent Executable file)
|
||
Entry point 0x1c00
|
||
There are 9 program headers, starting at offset 64
|
||
|
||
Program Headers:
|
||
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
|
||
PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0001f8 0x0001f8 R 0x8
|
||
INTERP 0x000238 0x0000000000000238 0x0000000000000238 0x00001b 0x00001b R 0x1
|
||
[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
|
||
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x003f7c 0x003f7c R E 0x10000
|
||
LOAD 0x00fc48 0x000000000001fc48 0x000000000001fc48 0x000528 0x001190 RW 0x10000
|
||
DYNAMIC 0x00fc58 0x000000000001fc58 0x000000000001fc58 0x000200 0x000200 RW 0x8
|
||
NOTE 0x000254 0x0000000000000254 0x0000000000000254 0x0000e0 0x0000e0 R 0x4
|
||
GNU_EH_FRAME 0x003610 0x0000000000003610 0x0000000000003610 0x0001b4 0x0001b4 R 0x4
|
||
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
|
||
GNU_RELRO 0x00fc48 0x000000000001fc48 0x000000000001fc48 0x0003b8 0x0003b8 R 0x1
|
||
|
||
Section to Segment mapping:
|
||
Segment Sections...
|
||
00
|
||
01 .interp
|
||
02 .interp .note.gnu.build-id .note.ABI-tag .note.package .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
|
||
03 .init_array .fini_array .dynamic .got .data .bss
|
||
04 .dynamic
|
||
05 .note.gnu.build-id .note.ABI-tag .note.package
|
||
06 .eh_frame_hdr
|
||
07
|
||
08 .init_array .fini_array .dynamic .got
|
||
```
|
||
이전 프로그램에는 **9개의 프로그램 헤더**가 있으며, **세그먼트 매핑**은 **각 섹션이 위치한** 프로그램 헤더(00에서 08까지)를 나타냅니다.
|
||
|
||
### PHDR - 프로그램 헤더
|
||
|
||
프로그램 헤더 테이블과 메타데이터 자체를 포함합니다.
|
||
|
||
### INTERP
|
||
|
||
바이너리를 메모리에 로드하는 데 사용할 로더의 경로를 나타냅니다.
|
||
|
||
> 팁: 정적으로 링크된 또는 정적-PIE 바이너리는 `INTERP` 항목이 없습니다. 이러한 경우에는 동적 로더가 관여하지 않으므로 이를 기반으로 하는 기술(예: `ret2dlresolve`)이 비활성화됩니다.
|
||
|
||
### LOAD
|
||
|
||
이 헤더는 **바이너리를 메모리에 로드하는 방법**을 나타내는 데 사용됩니다.\
|
||
각 **LOAD** 헤더는 **메모리**의 영역(크기, 권한 및 정렬)을 나타내고 ELF **바이너리에서 복사할 바이트**를 나타냅니다.
|
||
|
||
예를 들어, 두 번째 헤더는 크기가 0x1190이며, 0x1fc48에 위치해야 하고 읽기 및 쓰기 권한을 가지며 오프셋 0xfc48에서 0x528로 채워집니다(모든 예약된 공간을 채우지 않습니다). 이 메모리에는 섹션 `.init_array .fini_array .dynamic .got .data .bss`가 포함됩니다.
|
||
|
||
### DYNAMIC
|
||
|
||
이 헤더는 프로그램을 라이브러리 종속성과 연결하고 재배치를 적용하는 데 도움을 줍니다. **`.dynamic`** 섹션을 확인하세요.
|
||
|
||
### NOTE
|
||
|
||
이것은 바이너리에 대한 공급업체 메타데이터 정보를 저장합니다.
|
||
|
||
- x86-64에서 `readelf -n`은 `.note.gnu.property` 내의 `GNU_PROPERTY_X86_FEATURE_1_*` 플래그를 표시합니다. `IBT` 및/또는 `SHSTK`가 보이면, 바이너리는 CET(간접 분기 추적 및/또는 섀도우 스택)으로 빌드되었습니다. 이는 ROP/JOP에 영향을 미치며, 간접 분기 대상은 `ENDBR64` 명령어로 시작해야 하고 반환은 섀도우 스택에 대해 확인됩니다. 자세한 내용과 우회 노트는 CET 페이지를 참조하세요.
|
||
|
||
{{#ref}}
|
||
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
|
||
{{#endref}}
|
||
|
||
### GNU_EH_FRAME
|
||
|
||
디버거와 C++ 예외 처리 런타임 함수에서 사용하는 스택 언와인드 테이블의 위치를 정의합니다.
|
||
|
||
### GNU_STACK
|
||
|
||
스택 실행 방지 방어의 구성을 포함합니다. 활성화되면 바이너리는 스택에서 코드를 실행할 수 없습니다.
|
||
|
||
- `readelf -l ./bin | grep GNU_STACK`로 확인하세요. 테스트 중에 강제로 전환하려면 `execstack -s|-c ./bin`을 사용할 수 있습니다.
|
||
|
||
### GNU_RELRO
|
||
|
||
바이너리의 RELRO(재배치 읽기 전용) 구성을 나타냅니다. 이 보호는 프로그램이 로드된 후 실행되기 전에 메모리의 특정 섹션(예: `GOT` 또는 `init` 및 `fini` 테이블)을 읽기 전용으로 표시합니다.
|
||
|
||
이전 예에서는 0x3b8 바이트를 0x1fc48로 읽기 전용으로 복사하여 섹션 `.init_array .fini_array .dynamic .got .data .bss`에 영향을 미칩니다.
|
||
|
||
RELRO는 부분적 또는 전체적일 수 있으며, 부분 버전은 **지연 바인딩**에 사용되는 섹션 **`.plt.got`**를 보호하지 않으며, 라이브러리의 주소를 처음 검색할 때 이 메모리 공간에 **쓰기 권한**이 필요합니다.
|
||
|
||
> 익스플로잇 기술 및 최신 우회 노트에 대한 정보는 전용 페이지를 확인하세요:
|
||
|
||
{{#ref}}
|
||
../common-binary-protections-and-bypasses/relro.md
|
||
{{#endref}}
|
||
|
||
### TLS
|
||
|
||
스레드 로컬 변수에 대한 정보를 저장하는 TLS 항목의 테이블을 정의합니다.
|
||
|
||
## 섹션 헤더
|
||
|
||
섹션 헤더는 ELF 바이너리에 대한 보다 자세한 뷰를 제공합니다.
|
||
```
|
||
objdump lnstat -h
|
||
|
||
lnstat: file format elf64-littleaarch64
|
||
|
||
Sections:
|
||
Idx Name Size VMA LMA File off Algn
|
||
0 .interp 0000001b 0000000000000238 0000000000000238 00000238 2**0
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
1 .note.gnu.build-id 00000024 0000000000000254 0000000000000254 00000254 2**2
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
2 .note.ABI-tag 00000020 0000000000000278 0000000000000278 00000278 2**2
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
3 .note.package 0000009c 0000000000000298 0000000000000298 00000298 2**2
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
4 .gnu.hash 0000001c 0000000000000338 0000000000000338 00000338 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
5 .dynsym 00000498 0000000000000358 0000000000000358 00000358 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
6 .dynstr 000001fe 00000000000007f0 00000000000007f0 000007f0 2**0
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
7 .gnu.version 00000062 00000000000009ee 00000000000009ee 000009ee 2**1
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
8 .gnu.version_r 00000050 0000000000000a50 0000000000000a50 00000a50 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
9 .rela.dyn 00000228 0000000000000aa0 0000000000000aa0 00000aa0 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
10 .rela.plt 000003c0 0000000000000cc8 0000000000000cc8 00000cc8 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
11 .init 00000018 0000000000001088 0000000000001088 00001088 2**2
|
||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||
12 .plt 000002a0 00000000000010a0 00000000000010a0 000010a0 2**4
|
||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||
13 .text 00001c34 0000000000001340 0000000000001340 00001340 2**6
|
||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||
14 .fini 00000014 0000000000002f74 0000000000002f74 00002f74 2**2
|
||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||
15 .rodata 00000686 0000000000002f88 0000000000002f88 00002f88 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
16 .eh_frame_hdr 000001b4 0000000000003610 0000000000003610 00003610 2**2
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
17 .eh_frame 000007b4 00000000000037c8 00000000000037c8 000037c8 2**3
|
||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||
18 .init_array 00000008 000000000001fc48 000000000001fc48 0000fc48 2**3
|
||
CONTENTS, ALLOC, LOAD, DATA
|
||
19 .fini_array 00000008 000000000001fc50 000000000001fc50 0000fc50 2**3
|
||
CONTENTS, ALLOC, LOAD, DATA
|
||
20 .dynamic 00000200 000000000001fc58 000000000001fc58 0000fc58 2**3
|
||
CONTENTS, ALLOC, LOAD, DATA
|
||
21 .got 000001a8 000000000001fe58 000000000001fe58 0000fe58 2**3
|
||
CONTENTS, ALLOC, LOAD, DATA
|
||
22 .data 00000170 0000000000020000 0000000000020000 00010000 2**3
|
||
CONTENTS, ALLOC, LOAD, DATA
|
||
23 .bss 00000c68 0000000000020170 0000000000020170 00010170 2**3
|
||
ALLOC
|
||
24 .gnu_debugaltlink 00000049 0000000000000000 0000000000000000 00010170 2**0
|
||
CONTENTS, READONLY
|
||
25 .gnu_debuglink 00000034 0000000000000000 0000000000000000 000101bc 2**2
|
||
CONTENTS, READONLY
|
||
```
|
||
It also indicates the location, offset, permissions but also the **type of data** it section has.
|
||
|
||
### Meta Sections
|
||
|
||
- **String table**: ELF 파일에 필요한 모든 문자열을 포함하고 있습니다(하지만 프로그램에서 실제로 사용되는 문자열은 아닙니다). 예를 들어, `.text` 또는 `.data`와 같은 섹션 이름을 포함합니다. 그리고 만약 `.text`가 문자열 테이블에서 오프셋 45에 있다면, **name** 필드에서 숫자 **45**를 사용할 것입니다.
|
||
- 문자열 테이블이 어디에 있는지 찾기 위해, ELF는 문자열 테이블에 대한 포인터를 포함합니다.
|
||
- **Symbol table**: 이름(문자열 테이블의 오프셋), 주소, 크기 및 기호에 대한 더 많은 메타데이터와 같은 기호에 대한 정보를 포함합니다.
|
||
|
||
### Main Sections
|
||
|
||
- **`.text`**: 실행할 프로그램의 명령어입니다.
|
||
- **`.data`**: 프로그램에서 정의된 값을 가진 전역 변수입니다.
|
||
- **`.bss`**: 초기화되지 않은 전역 변수(또는 0으로 초기화됨)입니다. 여기의 변수는 자동으로 0으로 초기화되므로 이진 파일에 쓸모없는 0이 추가되는 것을 방지합니다.
|
||
- **`.rodata`**: 상수 전역 변수(읽기 전용 섹션)입니다.
|
||
- **`.tdata`** 및 **`.tbss`**: 스레드 로컬 변수가 사용될 때 .data 및 .bss와 같습니다(`__thread_local` in C++ 또는 `__thread` in C).
|
||
- **`.dynamic`**: 아래를 참조하십시오.
|
||
|
||
## Symbols
|
||
|
||
Symbols는 프로그램 내의 이름이 있는 위치로, 함수, 전역 데이터 객체, 스레드 로컬 변수 등이 될 수 있습니다.
|
||
```
|
||
readelf -s lnstat
|
||
|
||
Symbol table '.dynsym' contains 49 entries:
|
||
Num: Value Size Type Bind Vis Ndx Name
|
||
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
|
||
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
|
||
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
|
||
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
|
||
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
|
||
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
|
||
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
|
||
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
|
||
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
|
||
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
|
||
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
|
||
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
|
||
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
|
||
[...]
|
||
```
|
||
각 심볼 항목은 다음을 포함합니다:
|
||
|
||
- **이름**
|
||
- **바인딩 속성** (약한, 로컬 또는 글로벌): 로컬 심볼은 프로그램 자체에서만 접근할 수 있으며, 글로벌 심볼은 프로그램 외부에서 공유됩니다. 약한 객체는 예를 들어 다른 함수에 의해 재정의될 수 있는 함수입니다.
|
||
- **유형**: NOTYPE (유형 지정되지 않음), OBJECT (글로벌 데이터 변수), FUNC (함수), SECTION (섹션), FILE (디버거용 소스 코드 파일), TLS (스레드 로컬 변수), GNU_IFUNC (재배치를 위한 간접 함수)
|
||
- **섹션** 인덱스 (위치)
|
||
- **값** (메모리 내 주소)
|
||
- **크기**
|
||
|
||
#### GNU 심볼 버전 관리 (dynsym/dynstr/gnu.version)
|
||
|
||
현대 glibc는 심볼 버전을 사용합니다. `.gnu.version` 및 `.gnu.version_r`에서 항목을 볼 수 있으며, `strlen@GLIBC_2.17`와 같은 심볼 이름이 있습니다. 동적 링커는 심볼을 해결할 때 특정 버전을 요구할 수 있습니다. 수동 재배치를 만들 때 (예: ret2dlresolve) 올바른 버전 인덱스를 제공해야 하며, 그렇지 않으면 해결이 실패합니다.
|
||
|
||
## 동적 섹션
|
||
```
|
||
readelf -d lnstat
|
||
|
||
Dynamic section at offset 0xfc58 contains 28 entries:
|
||
Tag Type Name/Value
|
||
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
|
||
0x0000000000000001 (NEEDED) Shared library: [ld-linux-aarch64.so.1]
|
||
0x000000000000000c (INIT) 0x1088
|
||
0x000000000000000d (FINI) 0x2f74
|
||
0x0000000000000019 (INIT_ARRAY) 0x1fc48
|
||
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
|
||
0x000000000000001a (FINI_ARRAY) 0x1fc50
|
||
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
|
||
0x000000006ffffef5 (GNU_HASH) 0x338
|
||
0x0000000000000005 (STRTAB) 0x7f0
|
||
0x0000000000000006 (SYMTAB) 0x358
|
||
0x000000000000000a (STRSZ) 510 (bytes)
|
||
0x000000000000000b (SYMENT) 24 (bytes)
|
||
0x0000000000000015 (DEBUG) 0x0
|
||
0x0000000000000003 (PLTGOT) 0x1fe58
|
||
0x0000000000000002 (PLTRELSZ) 960 (bytes)
|
||
0x0000000000000014 (PLTREL) RELA
|
||
0x0000000000000017 (JMPREL) 0xcc8
|
||
0x0000000000000007 (RELA) 0xaa0
|
||
0x0000000000000008 (RELASZ) 552 (bytes)
|
||
0x0000000000000009 (RELAENT) 24 (bytes)
|
||
0x000000000000001e (FLAGS) BIND_NOW
|
||
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
|
||
0x000000006ffffffe (VERNEED) 0xa50
|
||
0x000000006fffffff (VERNEEDNUM) 2
|
||
0x000000006ffffff0 (VERSYM) 0x9ee
|
||
0x000000006ffffff9 (RELACOUNT) 15
|
||
0x0000000000000000 (NULL) 0x0
|
||
```
|
||
NEEDED 디렉토리는 프로그램이 계속 진행하기 위해 **언급된 라이브러리를 로드해야 함**을 나타냅니다. NEEDED 디렉토리는 공유 **라이브러리가 완전히 작동하고 사용 준비가 되었을 때** 완료됩니다.
|
||
|
||
### 동적 로더 검색 순서 (RPATH/RUNPATH, $ORIGIN)
|
||
|
||
항목 `DT_RPATH` (사용 중단됨) 및/또는 `DT_RUNPATH`는 동적 로더가 종속성을 검색하는 위치에 영향을 미칩니다. 대략적인 순서:
|
||
|
||
- `LD_LIBRARY_PATH` (setuid/sgid 또는 기타 "보안 실행" 프로그램에 대해 무시됨)
|
||
- `DT_RPATH` (단, `DT_RUNPATH`가 없을 경우)
|
||
- `DT_RUNPATH`
|
||
- `ld.so.cache`
|
||
- `/lib64`, `/usr/lib64` 등과 같은 기본 디렉토리
|
||
|
||
`$ORIGIN`은 RPATH/RUNPATH 내에서 주요 객체의 디렉토리를 참조하는 데 사용할 수 있습니다. 공격자의 관점에서 이는 파일 시스템 레이아웃이나 환경을 제어할 때 중요합니다. 강화된 바이너리(AT_SECURE)의 경우 대부분의 환경 변수는 로더에 의해 무시됩니다.
|
||
|
||
- 검사 방법: `readelf -d ./bin | egrep -i 'r(path|unpath)'`
|
||
- 빠른 테스트: `LD_DEBUG=libs ./bin 2>&1 | grep -i find` (검색 경로 결정 표시)
|
||
|
||
> 권한 상승 팁: 귀하가 소유한 쓰기 가능한 RUNPATH 또는 잘못 구성된 `$ORIGIN` 상대 경로를 악용하는 것을 선호하십시오. LD_PRELOAD/LD_AUDIT는 보안 실행(setuid) 컨텍스트에서 무시됩니다.
|
||
|
||
## 재배치
|
||
|
||
로더는 종속성을 로드한 후에도 재배치해야 합니다. 이러한 재배치는 REL 또는 RELA 형식의 재배치 테이블에 표시되며, 재배치 수는 동적 섹션 RELSZ 또는 RELASZ에 제공됩니다.
|
||
```
|
||
readelf -r lnstat
|
||
|
||
Relocation section '.rela.dyn' at offset 0xaa0 contains 23 entries:
|
||
Offset Info Type Sym. Value Sym. Name + Addend
|
||
00000001fc48 000000000403 R_AARCH64_RELATIV 1d10
|
||
00000001fc50 000000000403 R_AARCH64_RELATIV 1cc0
|
||
00000001fff0 000000000403 R_AARCH64_RELATIV 1340
|
||
000000020008 000000000403 R_AARCH64_RELATIV 20008
|
||
000000020010 000000000403 R_AARCH64_RELATIV 3330
|
||
000000020030 000000000403 R_AARCH64_RELATIV 3338
|
||
000000020050 000000000403 R_AARCH64_RELATIV 3340
|
||
000000020070 000000000403 R_AARCH64_RELATIV 3348
|
||
000000020090 000000000403 R_AARCH64_RELATIV 3350
|
||
0000000200b0 000000000403 R_AARCH64_RELATIV 3358
|
||
0000000200d0 000000000403 R_AARCH64_RELATIV 3360
|
||
0000000200f0 000000000403 R_AARCH64_RELATIV 3370
|
||
000000020110 000000000403 R_AARCH64_RELATIV 3378
|
||
000000020130 000000000403 R_AARCH64_RELATIV 3380
|
||
000000020150 000000000403 R_AARCH64_RELATIV 3388
|
||
00000001ffb8 000a00000401 R_AARCH64_GLOB_DA 0000000000000000 _ITM_deregisterTM[...] + 0
|
||
00000001ffc0 000b00000401 R_AARCH64_GLOB_DA 0000000000000000 __cxa_finalize@GLIBC_2.17 + 0
|
||
00000001ffc8 000f00000401 R_AARCH64_GLOB_DA 0000000000000000 stderr@GLIBC_2.17 + 0
|
||
00000001ffd0 001000000401 R_AARCH64_GLOB_DA 0000000000000000 optarg@GLIBC_2.17 + 0
|
||
00000001ffd8 001400000401 R_AARCH64_GLOB_DA 0000000000000000 stdout@GLIBC_2.17 + 0
|
||
00000001ffe0 001e00000401 R_AARCH64_GLOB_DA 0000000000000000 __gmon_start__ + 0
|
||
00000001ffe8 001f00000401 R_AARCH64_GLOB_DA 0000000000000000 __stack_chk_guard@GLIBC_2.17 + 0
|
||
00000001fff8 002e00000401 R_AARCH64_GLOB_DA 0000000000000000 _ITM_registerTMCl[...] + 0
|
||
|
||
Relocation section '.rela.plt' at offset 0xcc8 contains 40 entries:
|
||
Offset Info Type Sym. Value Sym. Name + Addend
|
||
00000001fe70 000300000402 R_AARCH64_JUMP_SL 0000000000000000 strtok@GLIBC_2.17 + 0
|
||
00000001fe78 000400000402 R_AARCH64_JUMP_SL 0000000000000000 strtoul@GLIBC_2.17 + 0
|
||
00000001fe80 000500000402 R_AARCH64_JUMP_SL 0000000000000000 strlen@GLIBC_2.17 + 0
|
||
00000001fe88 000600000402 R_AARCH64_JUMP_SL 0000000000000000 fputs@GLIBC_2.17 + 0
|
||
00000001fe90 000700000402 R_AARCH64_JUMP_SL 0000000000000000 exit@GLIBC_2.17 + 0
|
||
00000001fe98 000800000402 R_AARCH64_JUMP_SL 0000000000000000 __libc_start_main@GLIBC_2.34 + 0
|
||
00000001fea0 000900000402 R_AARCH64_JUMP_SL 0000000000000000 perror@GLIBC_2.17 + 0
|
||
00000001fea8 000b00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@GLIBC_2.17 + 0
|
||
00000001feb0 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 putc@GLIBC_2.17 + 0
|
||
00000001fec0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 fputc@GLIBC_2.17 + 0
|
||
00000001fec8 001100000402 R_AARCH64_JUMP_SL 0000000000000000 snprintf@GLIBC_2.17 + 0
|
||
00000001fed0 001200000402 R_AARCH64_JUMP_SL 0000000000000000 __snprintf_chk@GLIBC_2.17 + 0
|
||
00000001fed8 001300000402 R_AARCH64_JUMP_SL 0000000000000000 malloc@GLIBC_2.17 + 0
|
||
00000001fee0 001500000402 R_AARCH64_JUMP_SL 0000000000000000 gettimeofday@GLIBC_2.17 + 0
|
||
00000001fee8 001600000402 R_AARCH64_JUMP_SL 0000000000000000 sleep@GLIBC_2.17 + 0
|
||
00000001fef0 001700000402 R_AARCH64_JUMP_SL 0000000000000000 __vfprintf_chk@GLIBC_2.17 + 0
|
||
00000001fef8 001800000402 R_AARCH64_JUMP_SL 0000000000000000 calloc@GLIBC_2.17 + 0
|
||
00000001ff00 001900000402 R_AARCH64_JUMP_SL 0000000000000000 rewind@GLIBC_2.17 + 0
|
||
00000001ff08 001a00000402 R_AARCH64_JUMP_SL 0000000000000000 strdup@GLIBC_2.17 + 0
|
||
00000001ff10 001b00000402 R_AARCH64_JUMP_SL 0000000000000000 closedir@GLIBC_2.17 + 0
|
||
00000001ff18 001c00000402 R_AARCH64_JUMP_SL 0000000000000000 __stack_chk_fail@GLIBC_2.17 + 0
|
||
00000001ff20 001d00000402 R_AARCH64_JUMP_SL 0000000000000000 strrchr@GLIBC_2.17 + 0
|
||
00000001ff28 001e00000402 R_AARCH64_JUMP_SL 0000000000000000 __gmon_start__ + 0
|
||
00000001ff30 002000000402 R_AARCH64_JUMP_SL 0000000000000000 abort@GLIBC_2.17 + 0
|
||
00000001ff38 002100000402 R_AARCH64_JUMP_SL 0000000000000000 feof@GLIBC_2.17 + 0
|
||
00000001ff40 002200000402 R_AARCH64_JUMP_SL 0000000000000000 getopt_long@GLIBC_2.17 + 0
|
||
00000001ff48 002300000402 R_AARCH64_JUMP_SL 0000000000000000 __fprintf_chk@GLIBC_2.17 + 0
|
||
00000001ff50 002400000402 R_AARCH64_JUMP_SL 0000000000000000 strcmp@GLIBC_2.17 + 0
|
||
00000001ff58 002500000402 R_AARCH64_JUMP_SL 0000000000000000 free@GLIBC_2.17 + 0
|
||
00000001ff60 002600000402 R_AARCH64_JUMP_SL 0000000000000000 readdir64@GLIBC_2.17 + 0
|
||
00000001ff68 002700000402 R_AARCH64_JUMP_SL 0000000000000000 strndup@GLIBC_2.17 + 0
|
||
00000001ff70 002800000402 R_AARCH64_JUMP_SL 0000000000000000 strchr@GLIBC_2.17 + 0
|
||
00000001ff78 002900000402 R_AARCH64_JUMP_SL 0000000000000000 fwrite@GLIBC_2.17 + 0
|
||
00000001ff80 002a00000402 R_AARCH64_JUMP_SL 0000000000000000 fflush@GLIBC_2.17 + 0
|
||
00000001ff88 002b00000402 R_AARCH64_JUMP_SL 0000000000000000 fopen64@GLIBC_2.17 + 0
|
||
00000001ff90 002c00000402 R_AARCH64_JUMP_SL 0000000000000000 __isoc99_sscanf@GLIBC_2.17 + 0
|
||
00000001ff98 002d00000402 R_AARCH64_JUMP_SL 0000000000000000 strncpy@GLIBC_2.17 + 0
|
||
00000001ffa0 002f00000402 R_AARCH64_JUMP_SL 0000000000000000 __assert_fail@GLIBC_2.17 + 0
|
||
00000001ffa8 003000000402 R_AARCH64_JUMP_SL 0000000000000000 fgets@GLIBC_2.17 + 0
|
||
```
|
||
### 정적 재배치
|
||
|
||
프로그램이 선호하는 주소(보통 0x400000)와 다른 위치에 로드되면(주소가 이미 사용 중이거나 ASLR 또는 기타 이유로 인해) 정적 재배치는 선호하는 주소에 바이너리가 로드될 것으로 예상했던 값을 가진 포인터를 수정합니다.
|
||
|
||
예를 들어, `R_AARCH64_RELATIV` 유형의 섹션은 재배치 편향에 주소를 수정하고 추가 값(addend value)을 더해야 합니다.
|
||
|
||
### 동적 재배치 및 GOT
|
||
|
||
재배치는 외부 기호(종속성의 함수와 같은)를 참조할 수도 있습니다. 예를 들어, libC의 malloc 함수입니다. 그런 다음 로더는 libC를 로드할 때 malloc 함수가 로드된 주소를 확인하고, 이 주소를 GOT(전역 오프셋 테이블) 테이블에 기록합니다(재배치 테이블에 표시됨).
|
||
|
||
### 절차 링크 테이블
|
||
|
||
PLT 섹션은 지연 바인딩을 수행할 수 있게 해주며, 이는 함수의 위치 해석이 처음 접근할 때 수행된다는 것을 의미합니다.
|
||
|
||
따라서 프로그램이 malloc을 호출할 때, 실제로는 PLT의 `malloc`에 해당하는 위치를 호출합니다(`malloc@plt`). 처음 호출될 때 `malloc`의 주소를 해석하고 저장하므로 다음에 `malloc`이 호출될 때는 PLT 코드 대신 그 주소가 사용됩니다.
|
||
|
||
#### 익스플로잇에 영향을 미치는 현대 링크 동작
|
||
|
||
- `-z now` (전체 RELRO)는 지연 바인딩을 비활성화합니다. PLT 항목은 여전히 존재하지만 GOT/PLT는 읽기 전용으로 매핑되므로 **GOT 덮어쓰기** 및 **ret2dlresolve**와 같은 기술은 주요 바이너리에 대해 작동하지 않습니다(라이브러리는 여전히 부분적으로 RELRO일 수 있습니다). 참조:
|
||
|
||
{{#ref}}
|
||
../common-binary-protections-and-bypasses/relro.md
|
||
{{#endref}}
|
||
|
||
- `-fno-plt`는 컴파일러가 PLT 스텁을 거치지 않고 **GOT 항목을 통해 외부 함수를 직접 호출**하게 만듭니다. `call func@plt` 대신 `mov reg, [got]; call reg`와 같은 호출 시퀀스를 보게 될 것입니다. 이는 추측 실행 남용을 줄이고 PLT 스텁 주위의 ROP 가젯 탐색을 약간 변경합니다.
|
||
|
||
- PIE vs static-PIE: PIE(ET_DYN 및 `INTERP` 포함)는 동적 로더가 필요하며 일반적인 PLT/GOT 기계를 지원합니다. Static-PIE(ET_DYN 및 `INTERP` 없음)는 커널 로더에 의해 재배치가 적용되며 `ld.so`가 없습니다. 런타임에서 PLT 해석이 없을 것으로 예상됩니다.
|
||
|
||
> GOT/PLT가 옵션이 아닌 경우, 다른 쓰기 가능한 코드 포인터로 전환하거나 libc로 고전적인 ROP/SROP를 사용하십시오.
|
||
|
||
{{#ref}}
|
||
../arbitrary-write-2-exec/aw2exec-got-plt.md
|
||
{{#endref}}
|
||
|
||
## 프로그램 초기화
|
||
|
||
프로그램이 로드된 후 실행할 시간입니다. 그러나 실행되는 첫 번째 코드는 항상 `main` 함수가 아닙니다. 예를 들어 C++에서 **전역 변수가 클래스의 객체인 경우**, 이 객체는 main이 실행되기 **전에** **초기화**되어야 합니다.
|
||
```cpp
|
||
#include <stdio.h>
|
||
// g++ autoinit.cpp -o autoinit
|
||
class AutoInit {
|
||
public:
|
||
AutoInit() {
|
||
printf("Hello AutoInit!\n");
|
||
}
|
||
~AutoInit() {
|
||
printf("Goodbye AutoInit!\n");
|
||
}
|
||
};
|
||
|
||
AutoInit autoInit;
|
||
|
||
int main() {
|
||
printf("Main\n");
|
||
return 0;
|
||
}
|
||
```
|
||
이 전역 변수들은 `.data` 또는 `.bss`에 위치하지만, `__CTOR_LIST__`와 `__DTOR_LIST__` 목록에는 초기화 및 소멸할 객체들이 저장되어 이들을 추적할 수 있습니다.
|
||
|
||
C 코드에서는 GNU 확장을 사용하여 동일한 결과를 얻을 수 있습니다:
|
||
```c
|
||
__attributte__((constructor)) //Add a constructor to execute before
|
||
__attributte__((destructor)) //Add to the destructor list
|
||
```
|
||
컴파일러 관점에서, `main` 함수가 실행되기 전과 후에 이러한 작업을 실행하기 위해 `init` 함수와 `fini` 함수를 생성할 수 있으며, 이는 동적 섹션에서 **`INIT`** 및 **`FIN`**으로 참조됩니다. 그리고 ELF의 `init` 및 `fini` 섹션에 배치됩니다.
|
||
|
||
언급된 다른 옵션은 동적 섹션의 **`INIT_ARRAY`** 및 **`FINI_ARRAY`** 항목에서 **`__CTOR_LIST__`** 및 **`__DTOR_LIST__`** 목록을 참조하는 것입니다. 이들의 길이는 **`INIT_ARRAYSZ`** 및 **`FINI_ARRAYSZ`**로 표시됩니다. 각 항목은 인수 없이 호출될 함수 포인터입니다.
|
||
|
||
또한, **`INIT_ARRAY`** 포인터 **이전**에 실행될 **포인터**가 있는 **`PREINIT_ARRAY`**를 가질 수도 있습니다.
|
||
|
||
#### 익스플로잇 노트
|
||
|
||
- 부분 RELRO 하에서는 이러한 배열이 `ld.so`가 `PT_GNU_RELRO`를 읽기 전용으로 전환하기 전에 여전히 쓰기 가능한 페이지에 존재합니다. 충분히 이른 시점에 임의 쓰기를 얻거나 라이브러리의 쓰기 가능한 배열을 타겟팅할 수 있다면, 원하는 함수로 항목을 덮어써서 제어 흐름을 탈취할 수 있습니다. 전체 RELRO 하에서는 런타임에 읽기 전용입니다.
|
||
|
||
- 런타임에 임의 기호를 해결하기 위해 동적 링커의 지연 바인딩 남용에 대한 내용은 전용 페이지를 참조하십시오:
|
||
|
||
{{#ref}}
|
||
../rop-return-oriented-programing/ret2dlresolve.md
|
||
{{#endref}}
|
||
|
||
### 초기화 순서
|
||
|
||
1. 프로그램이 메모리에 로드되고, 정적 전역 변수가 **`.data`**에서 초기화되며 초기화되지 않은 변수는 **`.bss`**에서 0으로 설정됩니다.
|
||
2. 프로그램 또는 라이브러리의 모든 **종속성**이 **초기화**되고 **동적 링크**가 실행됩니다.
|
||
3. **`PREINIT_ARRAY`** 함수가 실행됩니다.
|
||
4. **`INIT_ARRAY`** 함수가 실행됩니다.
|
||
5. **`INIT`** 항목이 있으면 호출됩니다.
|
||
6. 라이브러리인 경우, dlopen은 여기서 끝나고, 프로그램인 경우 **실제 진입점**(`main` 함수)을 호출할 시간입니다.
|
||
|
||
## 스레드 로컬 저장소 (TLS)
|
||
|
||
C++에서 **`__thread_local`** 키워드 또는 GNU 확장 **`__thread`**를 사용하여 정의됩니다.
|
||
|
||
각 스레드는 이 변수에 대해 고유한 위치를 유지하므로 오직 해당 스레드만 자신의 변수를 접근할 수 있습니다.
|
||
|
||
이것이 사용될 때, ELF에서는 **`.tdata`** 및 **`.tbss`** 섹션이 사용됩니다. 이는 TLS를 위한 `.data` (초기화됨) 및 `.bss` (초기화되지 않음)와 유사합니다.
|
||
|
||
각 변수는 TLS 헤더에 항목을 가지며, 크기와 TLS 오프셋을 지정합니다. 이는 스레드의 로컬 데이터 영역에서 사용할 오프셋입니다.
|
||
|
||
`__TLS_MODULE_BASE`는 스레드 로컬 저장소의 기본 주소를 참조하는 데 사용되는 기호이며, 모듈의 모든 스레드 로컬 데이터를 포함하는 메모리 영역을 가리킵니다.
|
||
|
||
## 보조 벡터 (auxv) 및 vDSO
|
||
|
||
Linux 커널은 런타임에 유용한 주소와 플래그를 포함하는 보조 벡터를 프로세스에 전달합니다:
|
||
|
||
- `AT_RANDOM`: glibc가 스택 카나리 및 기타 PRNG 시드를 위해 사용하는 16바이트의 랜덤 바이트를 가리킵니다.
|
||
- `AT_SYSINFO_EHDR`: vDSO 매핑의 기본 주소 (유용하게 `__kernel_*` 시스템 호출 및 가젯을 찾는 데 사용됨).
|
||
- `AT_EXECFN`, `AT_BASE`, `AT_PAGESZ` 등.
|
||
|
||
공격자로서, `/proc` 아래의 메모리나 파일을 읽을 수 있다면, 대상 프로세스에서 정보 유출 없이 종종 이러한 정보를 유출할 수 있습니다:
|
||
```bash
|
||
# Show the auxv of a running process
|
||
cat /proc/$(pidof target)/auxv | xxd
|
||
|
||
# From your own process (helper snippet)
|
||
#include <sys/auxv.h>
|
||
#include <stdio.h>
|
||
int main(){
|
||
printf("AT_RANDOM=%p\n", (void*)getauxval(AT_RANDOM));
|
||
printf("AT_SYSINFO_EHDR=%p\n", (void*)getauxval(AT_SYSINFO_EHDR));
|
||
}
|
||
```
|
||
`AT_RANDOM`을 누출하면 해당 포인터를 역참조할 수 있다면 카나리 값을 얻을 수 있습니다; `AT_SYSINFO_EHDR`는 가젯을 찾거나 빠른 시스템 호출을 직접 호출하기 위한 vDSO 베이스를 제공합니다.
|
||
|
||
## References
|
||
|
||
- ld.so(8) – 동적 로더 검색 순서, RPATH/RUNPATH, 보안 실행 규칙 (AT_SECURE): https://man7.org/linux/man-pages/man8/ld.so.8.html
|
||
- getauxval(3) – 보조 벡터 및 AT_* 상수: https://man7.org/linux/man-pages/man3/getauxval.3.html
|
||
{{#include ../../banners/hacktricks-training.md}}
|