Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
page_inject — CVE-2026-31431-killed 페이지 캐시 익스플로잇 — 동일한 이미지 레이어를 공유하는 컨테이너로의 코드 실행 | Kitploit
도구/GitHubGitHub/sgkdev/page_inject
Vulnerability AnalysisExploitationPost-ExploitationPenetration TestingRed TeamingContainer EscapeBinary Exploitation
GitHubsgkdev/page_inject

page_inject

CVE-2026-31431-killed 페이지 캐시 익스플로잇 — 동일한 이미지 레이어를 공유하는 컨테이너로의 코드 실행

저장소 보기
741464개월 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

page_inject - AF_ALG aead 크로스 컨테이너 이스케이프

AF_ALG aead 취약점 크로스 컨테이너 익스플로잇 -- 하나의 손상된 컨테이너에서 동일한 libc.so.6 이미지 레이어를 공유하는 모든 형제 컨테이너로 피벗.

이것은 탈출 프리미티브입니다: 공격자가 이미 손상시킨 내부의 권한이 없는 컨테이너에서 실행되며, AF_ALG authencesn ESN-로테이션 4바이트 임의 쓰기 버그(CVE-2026-31431)를 사용하여 libc.so.6의 페이지 캐시 페이지에 지속적인 read() 훅을 심습니다. Docker / containerd가 overlayfs 하위 레이어 파일을 공유 inode로 백업하기 때문에, 해당 페이지는 동일한 이미지에서 인스턴스화된 모든 형제 컨테이너에서 보입니다. 훅은 그들의 프로세스에서도 발동하며, 공격자는 각 컨테이너 내에서 명령 실행 권한을 얻습니다.

위협 모델

  • 공격자는 victim과 동일한 이미지에서 실행되는 다른 컨테이너(siblings)가 있는 호스트에서 단일 컨테이너(victim라 칭함)에 대한 셸 접근 권한을 가지고 있습니다.
  • victim은 기본 Docker/k8s 포스처로 실행됩니다: 컨테이너의 사용자 네임스페이스 내의 권한 없는 uid, 기본 seccomp 프로필, 기본 AppArmor 프로필, 특수 능력 없음, 호스트 바인드 마운트 없음.
  • victim은 다음만 가지고 있습니다:
    • 자신의 libc에 대한 읽기 접근 (/usr/lib/x86_64-linux-gnu/libc.so.6 또는 배포판이 설치하는 위치)
    • 표준 socket(AF_ALG, ...) syscall 계열
    • 표준 splice / vmsplice syscall
    • chmod +x 할 수 있는 디렉토리에 대한 쓰기 접근 (예: /tmp)
  • 커널은 CVE-2026-31431에 취약해야 합니다 (업스트림 리버트 수정 이전의 algif_aead + authencesn 빌드).
  • 이게 전부입니다. 특별한 CAP_*도, 호스트 파일 시스템 접근도 필요 없습니다. 공격자는 자체 포함된 정적으로 링크된 바이너리를 컨테이너 내부에 떨어뜨리고 실행하면, 페이지 캐시 손상(따라서 훅)이 모든 형제에서 보이게 됩니다.

    익스플로잇 연결 방식

    1. 페이지 캐시 페이지 식별. overlayfs 컨테이너 내에서 /usr/lib/.../libc.so.6은 하위 이미지 레이어의 ext4 inode에 의해 제공됩니다. 동일한 이미지에서 시작된 모든 컨테이너는 해당 백킹 inode를 공유하며, 커널의 페이지 캐시는 overlay나 네임스페이스가 아닌 기본 inode에 의해 키가 지정됩니다. 따라서 페이지 캐시 페이지에 대한 단일 4바이트 쓰기는 해당 페이지를 mmap한 모든 형제 컨테이너의 프로세스에서 볼 수 있습니다.

    2. AF_ALG aead 취약점이 하나의 쓰기를 여러 개로 만듭니다. algif_aead는 사용자 RX iovec을 스플라이스된 TX SGL의 후행 authsize 바이트와 연결하고, authencesn의 ESN 로테이션은 AAD의 seq_high 필드 4바이트를 dst[assoclen + cryptlen]에 배치합니다. 이는 연결된 외부 꼬리의 첫 번째 바이트입니다. 스플라이스된 페이지는 공격자가 읽기 접근만 있는 파일의 페이지 캐시 페이지이지만, 암호는 더티 부기 없이 어쨌든 바이트를 복사합니다. (자세한 메커니즘은 crypto/algif_aead.c 및 crypto/authencesn.c를 참조하세요.)

    3. 호출 가능한 프리미티브 부트스트래핑. page_inject가 가장 먼저 하는 일은 Zone A를 부트스트래핑하는 것입니다. 이는 동일한 AF_ALG 동작(write_cache.asm)의 asm-코딩된 재구현으로, libc의 .text 동굴에 배치됩니다. 이렇게 하면 4바이트 쓰기가 모든 향후 훅 페이로드 내에서 정규 call이 되며, 호출당 소켓 설정이 필요 없습니다.

    4. 훅 설치. 그런 다음 인젝터는 Zone C(zone_c.asm)를 libc의 .text 동굴에 쓰고 read()의 처음 7-12바이트를 E9 disp32 점프로 패치합니다. 프롤로그의 대체된 바이트는 Zone C의 고속 경로에서 충실히 에뮬레이트됩니다(세 가지 다른 glibc 프롤로그 인식 -- 아래 "Prologue 처리" 참조). 이제 훅이 libc의 페이지 캐시에서 활성화됩니다.

    5. 훅 전파. 모든 형제 컨테이너는 read()를 지속적으로 호출하는 프로세스를 실행합니다(로깅 데몬, 상태 확인, cat /etc/hostname 등). 형제 컨테이너 내에서 첫 번째 그러한 호출 시, 하이재킹된 프롤로그는 Zone C로 점프합니다. Zone C는:

      • 컨테이너의 루트 inode(stat("/"))를 안정적인 네임스페이스 ID로 사용하여 컨테이너의 슬롯 키로 삼고,
      • 슬롯 테이블에서 해당 키가 있는 기존 항목을 스캔하고,
      • 없으면 키를 등록하고 fork()하여 CMD 영역에서 명령을 폴링하는 수명이 긴 명령-루프 자식을 생성하고,
      • read()+N으로 반환하여 호출자가 눈치채지 못하게 합니다. 원래 형제 프로세스는 계속 실행됩니다. 이제 공격자는 해당 컨테이너 내에 데몬을 가지게 됩니다.
    6. 명령 채널. 공격자는 동일한 page_inject 바이너리를 --shell 모드로 사용하여 슬롯 영역의 CMD 영역에 명령을 씁니다. 등록된 각 형제 컨테이너의 훅 자식은 폴링하고, /bin/sh -c <cmd>를 포크하고, stdout/stderr를 OUTPUT 영역에 캡처하고, 완료 신호를 보내고, 다시 폴링합니다. 셸은 출력을 표시합니다. 모든 CMD/OUTPUT 쓰기도 취약점 프리미티브를 통해 이루어지므로 특별한 권한이 필요하지 않습니다.

    7. 언훅. 완료되면 unhook는 read()의 원래 프롤로그 바이트를 복원하고 슬롯 테이블을 0으로 만듭니다. 훅 자식은 다음 반복에서 빈 슬롯을 보고 자체 종료됩니다. 페이지 캐시 수정 자체는 깨끗합니다(커널이 수정된 페이지를 더티로 표시한 적이 없음). 따라서 libc를 mmap한 모든 컨테이너가 중지되면 drop_caches는 캐시를 완전히 되돌립니다. 디스크에 남는 아티팩트는 없습니다.

    빌드

    인젝터는 피해자 컨테이너 외부에서 빌드됩니다. 일반적으로 공격자의 개발 머신에서 빌드합니다. 대부분의 프로덕션 컨테이너 이미지에는 컴파일러가 포함되어 있지 않기 때문입니다. gcc(-static-링크 지원 포함)와 nasm이 설치된 표준 Linux x86_64 개발 환경이면 충분합니다.

    root@kitploit:~
    make            # gen_arrays.sh를 통해 .asm 소스 조립, 정적 page_inject 링크
    make shellcode  # 검사 가능한 .bin 플랫 바이너리도 생성
    make clean      # 생성된 파일과 바이너리 제거
    

    출력물은 최신 x86_64 Linux 커널에서 실행되는 단일 정적 링크 ELF(./page_inject)입니다.

    전달 및 사용법 (피해자 컨테이너 내부에서)

    공격자가 victim에서 셸을 얻으면 쓰기 가능한 디렉토리(일반적으로 /tmp)에 바이너리를 업로드합니다:

    root@kitploit:~
    # 손상된 컨테이너 내부, 공격자 세션
    victim$ ./page_inject
    

    인수 없이 page_inject는 기본적으로 /usr/lib/x86_64-linux-gnu/libc.so.6(병합 후 Debian/Ubuntu 위치)을 사용합니다. 다른 배포판의 경우 libc가 다른 경로에 있습니다. 명시적으로 전달하거나 --root /를 사용하여 컨테이너의 루트에서 내장 조회 테이블을 스캔합니다:

    root@kitploit:~
    # Fedora / Rocky / CentOS
    victim$ ./page_inject /usr/lib64/libc.so.6
    
    # Arch
    victim$ ./page_inject /usr/lib/libc.so.6
    
    # 배포판에 관계없이 자동 감지:
    victim$ ./page_inject --root /
    

    두 호출 모두 동일한 작업을 수행합니다: 컨테이너 내 libc를 ELF 파싱하고, 페이지 캐시에 훅을 설치하고, 약 30초 동안 슬롯 테이블을 모니터링하여 형제가 등록할 때까지 기다린 후, 최초로 등록된 형제에 대해 일회성 id를 건전성 검사로 실행합니다.

    부트스트래핑 후, 등록된 모든 형제를 구동하기 위해 명령 셸로 들어갑니다:

    root@kitploit:~
    victim$ ./page_inject --shell --no-bootstrap
    === page-cache shell ===
    Containers (3):
      [0] 0x0018598d  <- target
      [1] 0x001859ab
      [2] 0x001859cd
    
    inject:0018598d> exec id
    uid=0(root) gid=0(root) groups=0(root)
    inject:0018598d> target 0x001859ab
    inject:001859ab> exec hostname
    1ccd66abee9d
    inject:001859ab> exec cat /etc/shadow
    root:$6$.....
    inject:001859ab> unhook
    ... read() prologue restored, slot table zeroed ...
    

    unhook는 모든 형제 컨테이너에서 한 번에 훅을 제거하고 훅 자식이 자체 종료되도록 합니다.

    root@kitploit:~
    Usage: page_inject [OPTIONS] [LIBC_PATH]
    
    Options:
      --root <prefix>   내장 고정 경로 조회 테이블을 사용하여 <prefix> 아래에서 libc.so.6 자동 해석.
                        피해자 컨테이너 내에선 일반적으로 --root / 입니다.
      --shell [0xKEY]   인젝션 후 대화형 명령 셸로 전환. 선택적 KEY는 대상을 미리 선택합니다.
      --no-bootstrap    인젝션 건너뛰기 (셸 전용; 훅이 페이지 캐시에 이미 활성화되어 있어야 함).
      --timeout SEC     --shell 모드의 슬롯 모니터링 타임아웃 (기본값 30초).
      --help, -h        도움말 표시.
    
    기본 libc (--root도 LIBC_PATH도 주어지지 않은 경우):
      /usr/lib/x86_64-linux-gnu/libc.so.6
    

    이중 인젝션 경로

    glibc 빌드에 따라 실행 가능 LOAD 세그먼트와 다음 읽기 전용 LOAD 사이의 .text 동굴 공간이 다릅니다. page_inject는 인젝션 시 두 가지 레이아웃 중 하나를 선택합니다:

    • 경로 A -- libc 전용 (기본값). Zone C와 Zone A 모두 libc의 .text 동굴에 있습니다. 슬롯 테이블 + CMD + OUTPUT 영역은 libc의 .hash 섹션에 있습니다. 이는 레거시 SysV 해시 데이터로, ld.so는 런타임에 .gnu.hash를 사용하기 때문에 읽지 않습니다. .hash가 없는 경우(Arch의 최신 툴체인), page_inject는 먼저 fde_count 필드를 축소한 후 .eh_frame_hdr의 꼬리에서 슬롯 영역을 조각합니다. 그러면 언와인더가 더 이상 해제된 바이트를 FDE 이진 검색 인덱스의 일부로 간주하지 않습니다(언와인더는 트리밍된 범위에 있던 IP의 FDE에 대해 투명하게 .eh_frame의 선형 스캔으로 폴백합니다 -- LSB 필수 동작).

    • 경로 B -- libc 트램펄린 + ld.so 페이로드. 일부 glibc 빌드는 libc 동굴을 전체 Zone C + Zone A 페이로드에 필요한 크기 아래로 축소합니다(Ubuntu 24.04 / glibc 2.39는 711 B 동굴 제공). 이 경우 page_inject는 libc의 동굴에 36 B 트램펄린을 씁니다. 이 트램펄린은 고속 경로 .bss-키 게이트 intra-libc를 수행하고, 느린 경로에서는 libc의 GOT 슬롯(모든 glibc가 비공개로 가져오는 ld.so 측 심볼)에서 _rtld_global의 ld.so 런타임 베이스를 계산하여 ld.so의 .text 동굴에 있는 Zone C의 베이스 레지스터 변형으로 점프합니다. 슬롯 테이블 + CMD + OUTPUT + .bss 키는 모두 libc에 남아 있습니다. ld.so 측 Zone C는 트램펄린이 rbp = libc_base를 시드한 후 rbp + offset을 통해 이들에 접근합니다.

    두 레이아웃 모두 맞지 않으면 page_inject는 디스크나 페이지 캐시의 libc 또는 ld.so에 아무것도 쓰지 않고 깔끔하게 거부합니다.

    read() 프롤로그 처리

    glibc 버전에 따라 read()에서 다른 시작 시퀀스가 발생합니다. 인젝터는 각각을 인식하고, 훅이 대체하는 바이트를 읽어 Zone C의 고속 경로에서 에뮬레이트하여 단일 스레드 read()가 read+N에서 올바르게 재개되도록 합니다:

    Glibc 범위프롤로그 (선택적 endbr64 이후)참고
    2.36 / 2.39cmpb $0x0, __libc_single_threaded(%rip)7바이트; 에뮬레이트된 cmpb는 원래 jne .Lthreaded를 위해 ZF를 설정합니다.
    2.43push rbp; movsxd rdi,edi; xor r9d,r9d7바이트; 바이트 단위로 에뮬레이트.
    2.31 / 2.35mov eax, fs:[0x18]8바이트; 바이트 단위로 에뮬레이트 (FS-접두사 [disp32]는 RIP-상대가 아닌 절대 주소이므로 바이트 복사가 충실함).

    Zone C의 고속 경로 에뮬레이션 슬롯은 알려진 가장 긴 프롤로그(8바이트)에 5바이트 rel32 점프를 더한 크기로 설정됩니다. 더 짧은 프롤로그는 후행 슬롯 바이트를 NOP 필러로 채워 총 슬롯 길이를 일정하게 유지합니다.

    파일 구조

    root@kitploit:~
    page_inject/
      page_inject.c           메인 인젝터: ELF 파싱, 취약점 프리미티브,
                              이중 경로 레이아웃 선택, 인젝트 + 언훅.
      zone_c.asm              경로-A 훅 디스패처 셸코드.
      zone_c_ld.asm           경로-B 훅 디스패처 (rbp-베이스 변형).
      trampoline.asm          경로-B 36바이트 libc 측 스텁.
      write_cache.asm         Zone A (취약점 쓰기 프리미티브 셸코드).
      gen_arrays.sh           .asm -> asm_bytecode.c 조립.
      asm_bytecode.c          [생성됨] 셸코드 바이트 배열.
      Makefile                빌드 시스템.
    

    테스트 및 지원 매트릭스

    이 익스플로잇은 다음 컨테이너 스냅 배포판에서 종단 간 검증되었습니다. 각 항목은 한 컨테이너 내에서 page_inject를 인젝션하고 동일한 이미지에서 시작된 형제 컨테이너에서 훅이 발동하는 것을 확인했습니다. 명령은 페이지 캐시 채널을 통해 올바르게 실행되었으며, 언훅은 libc 페이지 상태를 깨끗이 복원했습니다.

    이미지glibc인젝트 경로Read() 프롤로그슬롯 영역
    debian:bookworm2.36Acmpb.hash
    ubuntu:24.042.39Bcmpb.hash (libc 측, ld.so에서 rbp를 통해 주소 지정)
    ubuntu:22.042.35ATLS-fs.hash
    fedora:402.39Acmpb.hash
    archlinux:latest2.43Apush-rbp.eh_frame_hdr (트리밍된 꼬리)

    운영 참고 사항

    • page_inject는 의도적으로 정적으로 링크되어 있어 공격자 자신의 프로세스가 설치한 훅의 영향을 받지 않습니다.
    • page_inject는 "이미 훅이 걸린" libc를 인식합니다(read() 프롤로그가 E9 + nops인 경우) 그리고 재인젝션을 거부합니다. 테스트 환경에서 페이지 캐시가 해당 상태에 갇혀 있다면, 이미지를 사용하는 모든 컨테이너를 중지하고 drop_caches를 실행하여 재설정하세요.
    도구 다운로드