WCC: Гримуар колдуна
Рассказываю об (еще одном) крайне необычном проекте, способном «снести башню» даже опытным и подготовленным разработчикам.
Добро пожаловать в п#здец, снова.
The Witchcraft Compiler Collection
Этот замечательный проект — отличное доказательство тому как мало мы знаем о внутреннем устройстве собственных инструментов, используемых каждый день для работы.
По крайней мере автор достаточно смутно представляет как эта штука вообще работает:
WCC is a collection of compilation tools to perform binary black magic on the GNU/Linux and other POSIX platforms.
Если вкратце и по-русски, WCC это набор крайне специфичных утилит, предназначенных для препарирова.. ээм исследования чужих программ.
Нативных программ, собранных компилятором.
Настоящим компилятором а не .NET/JVM-порнографией, который порождает настоящий ELF64 бинарник или настоящую же отделяемую библиотеку.
Ниже я расскажу про ключевые возможности этого замечательного тулкита и постараюсь не предаться греху рукоблудия в процессе, с примерами.
Помимо исходников на Github, существуют еще интересная презентация данного проекта, аж для Defcon, материал из которой тоже будет мелькать ниже.
Сборка
К сожалению проект успел немного устареть и разложиться, хотя и присутствует в виде готовых пакетов во множестве дистрибутивов, в Mageia которую я использовал в качестве тестовой среды его не оказалось.
Так что пришлось собирать руками.
Необходимо установить следующие пакеты:
capstone, glibc, libbfd, libdl, zlib, libelf, libreadline, libgsl, make
Самое важное изменение для сборки в современной системе — libbfd ныне часть пакета binutils и отдельно больше не поставляется.
git clone https://github.com/endrazine/wcc.git git submodule init git submodule update
make
При попытке сборки появится следующая ошибка:
Недолгое гугление актуальной ошибки, выдранной из трассировки выше:
undefined reference to `sframe_encoder_write'
привело к вот такому багрепорту в трекере Debian, к которому был приложен diff с патчем:
diff -Nru wcc-0.0.2+dfsg/debian/patches/binutils_shared.patch wcc-0.0.2+dfsg/debian/patches/binutils_shared.patch --- wcc-0.0.2+dfsg/debian/patches/binutils_shared.patch 2020-03-21 19:02:12.000000000 +0200 +++ wcc-0.0.2+dfsg/debian/patches/binutils_shared.patch 2023-01-18 16:09:29.000000000 +0200 @@ -12,7 +12,7 @@ -all:: - $(CC) $(CFLAGS) wcc.c -o wcc -lbfd -lelf -lcapstone +all: -+ $(CC) $(CFLAGS) wcc.c -o wcc -l:libbfd.a -lz -ldl -liberty -lelf -lcapstone ++ $(CC) $(CFLAGS) wcc.c -o wcc -l:libbfd.a -l:libsframe.a -lz -ldl -liberty -lzstd -lelf -lcapstone # $(CC) $(CFLAGS) -m32 -Wl,-rpath /home/jonathan/solution-exp/unlinking/awareness/self/wcc/src/wcc/lib32/ wcc.c -o wcc32 -lelf ./lib32/libbfd-2.24-system.so ./lib32/libcapstone.so.3 cp wcc ../../bin/
Как видно из этих двух строчек:
-+ $(CC) $(CFLAGS) wcc.c -o wcc -l:libbfd.a -lz -ldl -liberty -lelf -lcapstone ++ $(CC) $(CFLAGS) wcc.c -o wcc -l:libbfd.a -l:libsframe.a -lz -ldl -liberty -lzstd -lelf -lcapstone
для сборки необходимо изменить паметры линковщика, что я и сделал в src/wcc/Makefile:
После исправления сборка заканчивается успешно и в каталоге bin появляются готовые к использованию бинарники:
Колдунство первое: превращение приложения в библиотеку
Согласно описанию автора все достаточно просто, хотя и необычно:
Transforming an ELF executable binary into an ELF shared library.
Вся цепочка работы выглядит следующим образом:
Но самое интересное как это технически работает, ниже скриншот из презентации с демонстрацией изменений в файле:
Тут видно что по факту изменился лишь один байт, если точнее то вот это поле заголовка ELF64:
Причем эта информация не является секретной или малоизвестной, коль присутствует даже в Википедии и в официальных руководствах, но до столь интересного применения еще никто почему-то не доходил.
Первым шагом происходит откат работы линковщика:
The primary use of wcc is to "unlink" (undo the work of a linker) ELF binaries, either executables or shared libraries, back into relocatable shared objects. The following command line attempts to unlink the binary /bin/ls (from GNU binutils) into a relocatable file named /tmp/ls.o
wcc -c /bin/ls -o /tmp/ls.o
Уже на этой стадии должен был быть получен «relocable file», который может быть прочитан стандарным gcc, т.е. должна отработать команда:
gcc /tmp/ls.o -o /tmp/ls.so -shared
К сожалению в новых версиях Linux так просто это уже не работает и я использовал альтернативный вариант:
cp /bin/ls /tmp/ls.o wld --libify /tmp/ls.o
Результат выполнения команды wld уже спокойно цепляется gcc:
gcc /tmp/ls.o -o /tmp/ls.so -shared
и в результате работы действительно получается отделяемая библиотека, которую можно спокойно подключить в ваш проект:
file /tmp/ls.so /tmp/ls.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=68d93a1d888eb560b8842 55a59c37cd6be0adddd, not stripped
Пример дальнейшего использования такой библиотеки показан автором на одном из слайдов презентации:
Чем может потенциально грозить превращение любого приложения в библиотеку с последующим использованием снаружи догадаться несложно:
все права и контроль доступа заканчиваются снаружи приложения, внутри же ничего такого нет и быть не может, а значит можно спокойно вызывать любой функционал.
Особое веселье приходит в дом к любителям статической линковки — когда в один бинарник запихиваются вообще все используемые библиотеки, что превращает любое крупное и статически собранное приложение в натуральный швейцарский нож, где есть все:
- работа с криптографией
- скачивание файлов из сети
- работа с различными форматами данных
- работа с файловой системой
Так что вы можете спокойно удалить wget и curl из системы или навешать сколь угодно жесткие правила запуска на бинарник — все это пойдет пешим строем прямо нах#й.
Колдунство второе: рефлексия для С
Следующий уровень веселья показан на скриншоте выше, но разумеется нуждается в пояснениях:
The witchcraft shell accepts ELF shared libraries, ELF ET_DYN executables and Witchcraft Shell Scripts written in Punk-C as an input. It loads all the executables in its own address space and makes their API available for programming in its embedded interpreter.
Хотя автор на голубом глазу заявляет:
This provides for binaries functionalities similar to those provided via reflection on languages like Java.
до настоящей рефлексии уровня Java/.NET тут очень далеко и реально вызывать столь интересным образом получается мягко говоря далеко не все.
Все же нативные приложения сильно отличаются по внутреннему устройству от «виртуальных», выполняемых в полностью контролируемом окружении.
Так что фундаментальные труды по программированию на С и C++ выкидывать пока не стоит.
Рассказываю как этот цирк с конями работает.
Интерпретатору wsh скармливается разделяемая библиотека или ELF-бинарник:
wsh /usr/sbin/apache2
Тут 50/50 — может сработать, может нет.
Если был передан бинарник — автоматически произойдет его «библификация», описанная выше.
Если сработало, появится сообщение:
loading of libified binary succeeded
и можно будет что-то пытаться вызывать:
a = ap_get_server_banner() print(a)
Код выше — это такая помесь С и Lua с поэтическим названием Punk-C:
The resulting API, a powerful combination of lua and C API is called Punk-C
Автор решил не заморачиваться с типами данных, поэтому и родилась на свет эта дикая помесь.
Более сложный пример использования:
jonathan@blackbox:/usr/share/wcc/scripts$ cat md5.wsh #!/usr/bin/wsh -- Computing a MD5 sum using cryptographic functions from foreign binaries (eg: sshd/OpenSSL) function str2md5(input) out = calloc(33, 1) ctx = calloc(1024, 1) MD5_Init(ctx) MD5_Update(ctx, input, strlen(input)) MD5_Final(out, ctx) free(ctx) return out end input = "Message needing hashing\n" hash = str2md5(input) hexdump(hash,16) exit(0) jonathan@blackbox:/usr/share/wcc/scripts$
Для работы необходимо сначала загрузить одну из библиотек, реализующих функции MD5.
Что и было немедленно проверено в действии:
Как видите все работает и действительно отображается MD5-хеш введенной строки, вычисленный с помощью функции из бинарника sshd.
К сожалению пришлось отключить вызов функции free(), поскольку она порождала ошибку с неверным указателем.
Но это еще не все варианты волшебства, которые дает WCC.
Колдунство третье: флаги сборки
Мегафича для тех кто не знает про ldd, для всех остальных — просто немного удобнее чем стандартный вывод.
Хотя автор опять вы#бывается и рассказывает басни про абсолютно все флаги:
When compiling C code, it is often required to pass extra arguments to the compiler to signify which shared libraries should be explicitly linked against the compile code. Figuring out those compilation parameters can be cumbersome. The wldd commands displays the shared libraries compilation flags given at compile time for any given ELF binary.
На практике же речь идет только о подключаемых библиотеках:
Для сравнения вывод стандартного ldd:
[alex@illuminati wcc]$ ldd /usr/bin/mplayer linux-vdso.so.1 (0x00007ffe9508b000) libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007f2568252000) libpng16.so.16 => /lib64/libpng16.so.16 (0x00007f2567da3000) libz.so.1 => /lib64/libz.so.1 (0x00007f2567d85000) libmng.so.2 => /lib64/libmng.so.2 (0x00007f2567d14000) libjpeg.so.8 => /lib64/libjpeg.so.8 (0x00007f2567c82000) libgif.so.7 => /lib64/libgif.so.7 (0x00007f2568247000) libasound.so.2 => /lib64/libasound.so.2 (0x00007f2567b7e000) libsndio.so.7 => /lib64/libsndio.so.7 (0x00007f2567b6a000) libbluray.so.2 => /lib64/libbluray.so.2 (0x00007f2567b1d000) libdvdread.so.8 => /lib64/libdvdread.so.8 (0x00007f2567afb000) libcdio_cdda.so.2 => /lib64/libcdio_cdda.so.2 (0x00007f2567af1000) libcdio.so.19 => /lib64/libcdio.so.19 (0x00007f2567ac4000) ...
Так что речь про удобство использования, но не про уникальный функционал.
Колдунство четвертое: генератор заголовков
Наконец последним в статье (но далеко не последним по важности) идет вот такая замечательная фича:
The wcch command takes an ELF binary path as a command line, and outputs a minimal C header file declaring all the exported global variables and functions from the input binary. This automates prototypes declaration when writing C code and linking with a binary for which C header files are not available.
Тут действительно респект и уважение, поскольку такое колдунство сильно помогает в реальной разработке:
Но все же стоит помнить про ограничения технологии и 100% корректность генерации заголовков никто не гарантирует:
cat /tmp/sshd.h |head -c 500 ** libifying /usr/sbin/sshd to //tmp/.wsh-754916/sshd (1086400 bytes) ** loading of libified binary succeeded /** * * Automatically generated by the Witchcraft Compiler Collection 0.0.9 * * 21:07:08 Feb 17 2025 * */ /** * Imported objects **/ extern void *optind; extern void *obstack_alloc_failed_handler; extern void *error_message_count; extern void *argp_err_exit_status; extern void *_IO_2_1_stderr_; extern void *stdin; extern void *program_invocation_short_name; [alex@illuminati wcc]$
Думаю очевидно, что подобные артефакты:
extern void *_IO_2_1_stderr_; extern void *stdin;
придется вычищать вручную, до момента реального использования.
Резюмируя
WCC это действительно редкий и необычный проект, к сожалению малоизвестный широкой публике.
Его использование требует определенной подготовки, а работа утилит временами нестабильна (что ожидаемо).
Если вы занимаетесь разработкой на C/C++ под Linux думаю стоит ознакомиться с этой штукой поближе.