how PIE work
이어서 정리 왜 PIE에서는 0x55~~ 로 뜨는지
결론 먼저
- PIE(= ET_DYN + PT_INTERP) 실행 파일은 load_elf_binary() 안에서 첫 번째 PT_LOAD를 처리할 때 베이스가 정해짐.
커널이 load_bias = ELF_ET_DYN_BASE로 시작해서 ASLR이면 load_bias += arch_mmap_rnd()를 더하고, 그 다음 정렬/보정 후 MAP_FIXED_NOREPLACE로 고정 매핑한다.
- x86-64에선 ELF_ET_DYN_BASE가 DEFAULT_MAP_WINDOW의 2/3로 정의되어 있고, DEFAULT_MAP_WINDOW = (1<<47) - PAGE_SIZE = 0x7ffffffff000 이므로
2/3 ≈ 0x00005555_55554xxx 부근이 “시작 위치”가 된다. 그래서 **PIE 베이스가 대체로 0x55…**로 보이는 것.
- 이후 load_bias는 첫 PT_LOAD의 p_vaddr를 반영해 페이지 경계로 정렬되고(ELF_PAGESTART(load_bias - vaddr)), 그 주소를 기준으로 모든 LOAD 세그먼트를 맵핑한다. 그래서 흔히 보이는 값이 0x00005555_55554000 같은 형태가 된다.
커널 코드로 단계별 근거
1) PIE 처리 분기 (fs/binfmt_elf.c)
load_elf_binary()의 첫 번째 LOAD 처리에서 ET_DYN이면 “PIE/로더” 분기를 타고, PIE(ET_DYN+PT_INTERP) 인 경우 이렇게 진행함:
/* ET_DYN + PT_INTERP => PIE */
load_bias = ELF_ET_DYN_BASE;
if (current->flags & PF_RANDOMIZE)
load_bias += arch_mmap_rnd();
if (alignment)
load_bias &= ~(alignment - 1);
elf_flags |= MAP_FIXED_NOREPLACE;
/* ... 나중에 vaddr를 보정 */
load_bias = ELF_PAGESTART(load_bias - vaddr);
/* 이후 모든 LOAD를 (load_bias + vaddr)로 매핑 */
error = elf_load(file, load_bias + vaddr, ...);→ 핵심: 베이스는 ELF_ET_DYN_BASE에서 시작 + arch_mmap_rnd()로 랜덤 오프셋, 정렬 후 MAP_FIXED_NOREPLACE로 고정 주소 매핑.
(참고) ET_DYN이지만 PT_INTERP가 없는 “로더 자체”(예: 동적 링커)를 직접 실행하는 경우는 **mmap 영역(0 load_bias)**을 사용하도록 분리되어 있음. 커멘트로도 “프로그램과 로더가 서로 충돌하지 않게 떨어뜨려야 한다”고 설명.
2)
ELF_ET_DYN_BASE
값 (x86-64)
x86-64용 arch/x86/include/asm/elf.h:
/* 64-bit에선 PIE 베이스 */
#define ELF_ET_DYN_BASE (mmap_is_ia32() ? 0x000400000UL : (DEFAULT_MAP_WINDOW / 3 * 2))- DEFAULT_MAP_WINDOW는 arch/x86/include/asm/page_64_types.h에
#define DEFAULT_MAP_WINDOW ((1UL << 47) - PAGE_SIZE) → 0x7ffffffff000.
- 따라서 ELF_ET_DYN_BASE ≈ (0x7ffffffff000 / 3 * 2) = 약 0x00005555_55554xxx.
(페이지 정렬/p_vaddr 반영 후 0x00005555_55554000 같은 값으로 보이는 이유.)
역사적으로 2017년경 “PIE 베이스를 4 GiB”로 옮겼다가(테스트/호환 이슈) 되돌리는 논의가 있었지만,
현재 커널 라인
2/3 윈도우
3) ASLR 오프셋:
arch_mmap_rnd()
x86-64의 arch/x86/mm/mmap.c:
static unsigned long arch_rnd(unsigned int rndbits) {
if (!(current->flags & PF_RANDOMIZE))
return 0;
return (get_random_long() & ((1UL << rndbits) - 1)) << PAGE_SHIFT;
}
unsigned long arch_mmap_rnd(void) {
return arch_rnd(mmap_is_ia32() ? mmap32_rnd_bits : mmap64_rnd_bits);
}- mmap64_rnd_bits(sysctl: /proc/sys/vm/mmap_rnd_bits)으로 페이지 단위의 큰 랜덤 오프셋을 만든 뒤 ELF_ET_DYN_BASE에 더해서 PIE 베이스를 위쪽으로 이동. (랜덤 비트 수는 아키/배포판에 따라 다르지만 수십 비트 수준이 일반적.)
4) 최종 베이스 보정
PIE는 보통 PT_LOAD의 p_vaddr가 0 혹은 작은 값이므로, 위에서 만든 load_bias에 대해
load_bias = ELF_PAGESTART(load_bias - vaddr);
error = elf_load(..., load_bias + vaddr, ...);https://elixir.bootlin.com/linux/v6.8/source/fs/binfmt_elf.c#L1086
이 링크에서 부터 이어서 분석하면 될듯