Skip to content

Commit fddc043

Browse files
piectgies
andauthored
add vulnerable binary that checks if system is vulnerable (#3)
* feat: add vulnerable binary that checks if system is vulnerable without influencing it * refactor: extract patch_chunk as utils, cleanup * docs: update READMEs for vulnerable.c --------- Co-authored-by: Tony Gies <tgies@tgies.net>
1 parent beb0b1f commit fddc043

11 files changed

Lines changed: 347 additions & 272 deletions

File tree

Makefile

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ PAYLOAD_CFLAGS ?= $(PAYLOAD_BASE_CFLAGS) $(PAYLOAD_PACK_LDFLAGS)
6060

6161
.PHONY: all clean info musl-shim musl-static zig-musl-static FORCE
6262

63-
all: exploit exploit-passwd
63+
all: exploit exploit-passwd vulnerable
6464

6565
# musl-static: tiny (~55 KB exploit + ~1.5 KB payload), no glibc dependency.
6666
# Requires musl-tools and linux-libc-dev. musl-gcc isolates its include path
@@ -116,13 +116,24 @@ payload.o: payload $(PAYLOAD_OBJ_DEPS)
116116
payload-abi.o: FORCE
117117
$(PAYLOAD_ABI_OBJ_CMD)
118118

119-
exploit: exploit.c payload.o
119+
# Shared AF_ALG/splice page-cache mutation primitive. Linked into all three
120+
# binaries; declaration in utils.h, definition in utils.c.
121+
utils.o: utils.c utils.h
122+
$(CC) $(CFLAGS) -c $< -o $@
123+
124+
exploit: exploit.c utils.o payload.o
125+
$(CC) $(CFLAGS) $(LDFLAGS) -static -o $@ $^
126+
127+
# Non-destructive vulnerability checker. Mutates a local testfile's page
128+
# cache and reads it back to detect kernel susceptibility without touching
129+
# any system file.
130+
vulnerable: vulnerable.c utils.o
120131
$(CC) $(CFLAGS) $(LDFLAGS) -static -o $@ $^
121132

122133
# /etc/passwd UID-flip variant. No payload needed; mutates four ASCII bytes
123134
# of /etc/passwd's page cache to flip the user's UID to "0000", then execs su.
124-
exploit-passwd: exploit-passwd.c
125-
$(CC) $(CFLAGS) $(LDFLAGS) -static -o $@ $<
135+
exploit-passwd: exploit-passwd.c utils.o
136+
$(CC) $(CFLAGS) $(LDFLAGS) -static -o $@ $^
126137

127138
info: payload payload.o
128139
@echo "=== payload size ==="
@@ -136,4 +147,4 @@ info: payload payload.o
136147
@readelf -S payload | grep -E 'Name|\.text|\.rodata|\.data|\.bss' | head -10
137148

138149
clean:
139-
rm -rf exploit exploit-passwd payload payload.o payload-abi.o .musl-shim
150+
rm -rf exploit vulnerable exploit-passwd payload payload.o payload-abi.o utils.o testfile .musl-shim

README.ja.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Copy Fail Linux LPE (CVE-2026-31431) のクロスプラットフォームC言語
1515
copy-fail-c/
1616
├── exploit.c ドロッパー (バイナリ書き換えバリアント)
1717
├── exploit-passwd.c ドロッパー (/etc/passwd UID書き換えバリアント)
18+
├── vulnerable.c 非破壊的な脆弱性チェッカー
1819
├── payload.c ドロップされる本体 (setgid+setuid+execve sh)
20+
├── utils.c, utils.h AF_ALG/splice ページキャッシュ書き換えプリミティブ (共有)
1921
├── Makefile ビルドのオーケストレーション
2022
├── nolibc/ torvalds/linux の tools/include/nolibc からのベンダーコード
2123
└── README.md このファイル
@@ -27,7 +29,8 @@ copy-fail-c/
2729
├── payload ドロッパーにバイト列として埋め込まれる小さな静的ELF
2830
├── payload.o `ld -r -b binary` によって再配置可能な .o としてラップされたペイロード
2931
├── exploit ドロッパー、バイナリ書き換えバリアント
30-
└── exploit-passwd ドロッパー、/etc/passwd UID書き換えバリアント
32+
├── exploit-passwd ドロッパー、/etc/passwd UID書き換えバリアント
33+
└── vulnerable 非破壊的な脆弱性チェッカー
3134
```
3235

3336
`exploit.c` は対象バイナリを読み取り専用で開き、埋め込まれたペイロードの4バイトのウィンドウごとにAF_ALGを介してダミーのAEAD復号を1回実行します。このときの暗号文入力は、ターゲットのページキャッシュページからsplice()を介して提供されます。authencesnテンプレートのインプレース最適化により、spliceされたソースページは暗号文入力と平文出力先のアドレスの両方として扱われるため、認証検証がリクエストを拒否する頃には、(失敗する)復号によってすでにページキャッシュページが上書きされています。4 * N回の反復後、ターゲットのキャッシュされたイメージはペイロードとバイト単位で完全に置き換えられます。ターゲットをexecve()すると、変異したページがロードされます。ディスク上のinodeは依然としてsetuid rootであるため、カーネルはroot権限を付与し、ペイロードを実行します。
@@ -36,6 +39,8 @@ copy-fail-c/
3639

3740
2番目のバリアントである `exploit-passwd.c` は、setuidバイナリのイメージの代わりに /etc/passwd のページキャッシュの4バイトを書き換えます。埋め込みペイロードを必要とせず、バイナリ書き換えのルートがブロックされているシステムでも機能しますが、キャッシュアウトの(権限昇格を達成する)サーフェスははるかに狭くなります。
3841

42+
`vulnerable.c` はエクスプロイトではありません。文字列 `init` を含むローカルの `testfile` を作成し、そのファイル自身のページキャッシュに対して同じ `patch_chunk()` プリミティブを実行して、バイトを `vulnerable` に上書きしようとします。読み戻したファイル内容が一致すれば、実行中のカーネルは CVE-2026-31431 の影響範囲内です。ディスク上のinodeは変更されません。`testfile` は終了時に削除され、ページキャッシュの書き換えもそれと共に消えます。特権なしで実行できます。脆弱な場合は終了コード100、そうでない場合は0を返します。
43+
3944
## ビルド
4045

4146
デフォルト (ホストアーキテクチャ向けネイティブ):

README.ko.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Copy Fail 리눅스 로컬 권한 상승(LPE) 취약점(CVE-2026-31431)을 C 언
1515
copy-fail-c/
1616
├── exploit.c 드로퍼 (바이너리 변조 변형)
1717
├── exploit-passwd.c 드로퍼 (/etc/passwd UID 변조 변형)
18+
├── vulnerable.c 비파괴적 취약점 검사기
1819
├── payload.c 드롭되는 본체 (setgid+setuid+execve sh)
20+
├── utils.c, utils.h 공유 AF_ALG/splice 페이지 캐시 변조 원리
1921
├── Makefile 빌드 오케스트레이션
2022
├── nolibc/ torvalds/linux의 tools/include/nolibc에서 가져온 벤더 코드
2123
└── README.md 이 파일 (설명서)
@@ -27,7 +29,8 @@ copy-fail-c/
2729
├── payload 작은 정적 ELF, 드로퍼에 바이트 형태로 내장됨
2830
├── payload.o `ld -r -b binary`를 통해 재배치 가능한 .o로 래핑된 페이로드
2931
├── exploit 드로퍼, 바이너리 변조 변형
30-
└── exploit-passwd 드로퍼, /etc/passwd UID 변조 변형
32+
├── exploit-passwd 드로퍼, /etc/passwd UID 변조 변형
33+
└── vulnerable 비파괴적 취약점 검사기
3134
```
3235

3336
`exploit.c`는 대상 바이너리를 읽기 전용으로 연 다음, 내장된 페이로드의 4바이트 단위마다 AF_ALG를 통해 가짜 AEAD 복호화를 한 번 실행합니다. 이때 암호문 입력은 대상의 페이지 캐시 페이지에서 splice()를 통해 제공됩니다. authencesn 템플릿의 제자리(in-place) 최적화는 splice된 소스 페이지를 암호문 입력이자 평문 출력 대상으로 동시에 처리하므로, 인증 검증이 요청을 거부할 때쯤에는 이미 (실패할) 복호화가 페이지 캐시 페이지를 덮어쓴 상태가 됩니다. 4 * N번의 반복 후, 대상의 캐시된 이미지는 페이로드와 바이트 단위로 완전히 교체됩니다. 대상을 execve()하면 변조된 페이지가 로드됩니다. 디스크상의 inode는 여전히 setuid root이므로 커널은 root 권한을 부여하고 페이로드를 실행합니다.
@@ -36,6 +39,8 @@ copy-fail-c/
3639

3740
두 번째 변형인 `exploit-passwd.c`는 setuid 바이너리의 이미지 대신 /etc/passwd의 페이지 캐시 중 4바이트를 변조합니다. 내장된 페이로드가 필요하지 않으며 바이너리 변조 경로가 차단된 시스템에서도 작동하지만, 이로 인해 권한 상승(cashout)을 달성할 수 있는 공격 표면(attack surface)은 훨씬 좁아집니다.
3841

42+
`vulnerable.c`는 익스플로잇이 아닙니다. 문자열 `init`이 들어 있는 로컬 `testfile`을 생성한 후, 동일한 `patch_chunk()` 원리를 해당 파일의 페이지 캐시에 적용하여 바이트를 `vulnerable`로 덮어쓰려고 시도합니다. 읽어 들인 내용이 일치하면 실행 중인 커널은 CVE-2026-31431의 영향 범위 내에 있습니다. 디스크상의 inode는 변경되지 않습니다. `testfile`은 종료 시 제거되며, 페이지 캐시의 변조도 함께 사라집니다. 권한 없이 실행할 수 있습니다. 취약하면 종료 코드 100, 그렇지 않으면 0을 반환합니다.
43+
3944
## 빌드
4045

4146
기본 (호스트 아키텍처 네이티브):

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ Discovery and original disclosure: Theori / Xint.
2121
copy-fail-c/
2222
├── exploit.c the dropper (binary-mutation variant)
2323
├── exploit-passwd.c the dropper (/etc/passwd UID-flip variant)
24+
├── vulnerable.c non-destructive vulnerability checker
2425
├── payload.c the body that gets dropped (setgid+setuid+execve sh)
26+
├── utils.c, utils.h shared AF_ALG/splice page-cache mutation primitive
2527
├── Makefile build orchestration
2628
├── nolibc/ vendored from torvalds/linux tools/include/nolibc
2729
└── README.md this file
@@ -33,7 +35,8 @@ After `make`:
3335
├── payload tiny static ELF, embedded into the dropper as bytes
3436
├── payload.o payload wrapped as a relocatable .o by `ld -r -b binary`
3537
├── exploit dropper, binary-mutation variant
36-
└── exploit-passwd dropper, /etc/passwd UID-flip variant
38+
├── exploit-passwd dropper, /etc/passwd UID-flip variant
39+
└── vulnerable non-destructive vulnerability checker
3740
```
3841

3942
`exploit.c` opens the target binary read-only, then for each 4-byte window of
@@ -56,6 +59,14 @@ cache instead of a setuid binary's image. It needs no embedded payload and
5659
works on systems where the binary-mutation route is blocked, but its cashout
5760
surface is much narrower.
5861

62+
`vulnerable.c` is not an exploit. It creates a local `testfile` containing the
63+
string `init`, then runs the same `patch_chunk()` primitive against that file's
64+
own page cache to overwrite the bytes with `vulnerable`. If the read-back
65+
contents match, the running kernel is in-window for CVE-2026-31431. The
66+
on-disk inode is never modified; `testfile` is removed on exit; the page-cache
67+
mutation evaporates with it. Runs unprivileged. Exits 100 if vulnerable, 0
68+
otherwise.
69+
5970

6071
## Build
6172

README.ru.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
copy-fail-c/
1616
├── exploit.c дроппер (вариант с мутацией бинарного файла)
1717
├── exploit-passwd.c дроппер (вариант с подменой UID в /etc/passwd)
18+
├── vulnerable.c неразрушающая проверка уязвимости
1819
├── payload.c сбрасываемая полезная нагрузка (setgid+setuid+execve sh)
20+
├── utils.c, utils.h общий примитив мутации page cache через AF_ALG/splice
1921
├── Makefile оркестрация сборки
2022
├── nolibc/ код, заимствованный из torvalds/linux tools/include/nolibc
2123
└── README.md этот файл
@@ -27,7 +29,8 @@ copy-fail-c/
2729
├── payload крошечный статический ELF, встроенный в дроппер в виде байтов
2830
├── payload.o полезная нагрузка, обернутая в перемещаемый .o с помощью `ld -r -b binary`
2931
├── exploit дроппер, вариант с мутацией бинарного файла
30-
└── exploit-passwd дроппер, вариант с подменой UID в /etc/passwd
32+
├── exploit-passwd дроппер, вариант с подменой UID в /etc/passwd
33+
└── vulnerable неразрушающая проверка уязвимости
3134
```
3235

3336
`exploit.c` открывает целевой бинарный файл только для чтения, затем для каждого 4-байтового окна встроенной полезной нагрузки запускает одну фиктивную операцию AEAD-расшифровки через AF_ALG, где зашифрованный ввод поставляется через splice() из страниц кэша (page-cache) целевого файла. Оптимизация "на месте" (in-place) шаблона authencesn рассматривает исходные страницы, переданные через splice, одновременно как ввод шифротекста и место назначения для открытого текста, поэтому обреченная на неудачу расшифровка уже перезаписывает страницу кэша к тому моменту, когда проверка подлинности отклоняет запрос. После 4 * N итераций закэшированный образ целевого файла побайтово заменяется полезной нагрузкой. Выполнение execve() для целевого файла загружает мутировавшие страницы; inode на диске по-прежнему имеет бит setuid root, поэтому ядро предоставляет root-привилегии и запускает полезную нагрузку.
@@ -36,6 +39,8 @@ copy-fail-c/
3639

3740
Второй вариант, `exploit-passwd.c`, мутирует четыре байта в кэше страниц файла /etc/passwd вместо образа setuid-бинарника. Ему не нужна встроенная полезная нагрузка, и он работает на системах, где путь с мутацией бинарного файла заблокирован, но его поверхность для успешной эксплуатации (cashout) гораздо уже.
3841

42+
`vulnerable.c` не является эксплойтом. Он создаёт в рабочей директории локальный файл `testfile` со строкой `init`, после чего запускает тот же примитив `patch_chunk()` против собственного кэша страниц этого файла, пытаясь перезаписать байты строкой `vulnerable`. Если прочитанное содержимое совпадает, работающее ядро находится в зоне действия CVE-2026-31431. Inode на диске не модифицируется; `testfile` удаляется при выходе, и мутация кэша страниц исчезает вместе с ним. Запускается без привилегий. Возвращает код выхода 100 при наличии уязвимости и 0 в противном случае.
43+
3944
## Сборка
4045

4146
По умолчанию (нативно для архитектуры хоста):

README.zh-cn.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Copy Fail Linux 本地提权 (CVE-2026-31431) 的跨平台 C 语言重写版。
1515
copy-fail-c/
1616
├── exploit.c 释放器 (二进制文件变异变体)
1717
├── exploit-passwd.c 释放器 (/etc/passwd UID 翻转变体)
18+
├── vulnerable.c 非破坏性漏洞检查器
1819
├── payload.c 被释放的有效载荷主体 (setgid+setuid+execve sh)
20+
├── utils.c, utils.h 共享的 AF_ALG/splice 页缓存变异原语
1921
├── Makefile 构建编排
2022
├── nolibc/ 从 torvalds/linux tools/include/nolibc 引入的代码
2123
└── README.md 本文件
@@ -27,7 +29,8 @@ copy-fail-c/
2729
├── payload 微小的静态 ELF,作为字节嵌入到释放器中
2830
├── payload.o 通过 `ld -r -b binary` 包装为可重定位 .o 的有效载荷
2931
├── exploit 释放器,二进制文件变异变体
30-
└── exploit-passwd 释放器,/etc/passwd UID 翻转变体
32+
├── exploit-passwd 释放器,/etc/passwd UID 翻转变体
33+
└── vulnerable 非破坏性漏洞检查器
3134
```
3235

3336
`exploit.c` 以只读方式打开目标二进制文件,然后对于嵌入的有效载荷的每个 4 字节窗口,通过 AF_ALG 运行一次伪造的 AEAD 解密。其密文输入是通过 splice() 从目标的页缓存页面提供的。authencesn 模板的原地优化将 splice 的源页面同时视为密文输入和明文目标地址,因此在身份验证验证拒绝该请求时,(注定失败的)解密已经覆盖了页缓存页面。经过 4 * N 次迭代后,目标的缓存映像已被有效载荷逐字节替换。对目标执行 execve() 会加载已变异的页面;磁盘上的 inode 仍然设置了 setuid root,因此内核授予 root 凭据并运行有效载荷。
@@ -36,6 +39,8 @@ copy-fail-c/
3639

3740
第二个变体 `exploit-passwd.c` 修改了 /etc/passwd 页缓存的四个字节,而不是 setuid 二进制文件的映像。它不需要嵌入有效载荷,可以在二进制变异路线被阻止的系统上工作,但其变现(达到提权目的)面要窄得多。
3841

42+
`vulnerable.c` 不是漏洞利用程序。它在工作目录中创建一个包含字符串 `init` 的本地 `testfile`,然后对该文件自身的页缓存运行相同的 `patch_chunk()` 原语,尝试将字节覆写为 `vulnerable`。如果读回的内容匹配,则当前运行的内核处于 CVE-2026-31431 的受影响范围内。磁盘上的 inode 从未被修改;`testfile` 会在退出时被删除,页缓存的变异也随之消失。可以在无特权的情况下运行。如果存在漏洞则退出码为 100,否则为 0。
43+
3944
## 构建
4045

4146
默认 (宿主架构原生):

0 commit comments

Comments
 (0)