Как стать автором
Обновить

WCC: Гримуар колдуна

Уровень сложностиСложный
Время на прочтение7 мин
Количество просмотров1.5K

Рассказываю о еще одном необычном проекте, способном удивить даже опытных и подготовленных разработчиков. Добро пожаловать, снова.

Вдумчивое чтение содержимого двух консолей на этом скриншоте может привести в дурдом, я предупредил.
Вдумчивое чтение содержимого двух консолей на этом скриншоте может привести в дурдом, я предупредил.

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 binary или отделяемую библиотеку.

Ниже я расскажу про ключевые возможности этого замечательного тулкита, с примерами.

Помимо исходников на 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

К сожалению в новых версиях линукса это уже не работает, поэтому был использовал альтернативный вариант:

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

Пример дальнейшего использования такой библиотеки показан автором на одном из слайдов презентации:

Получение логина пользователя самым радикальным способом - через рефлексию метода у загруженного бинарника VLC (!)
Получение логина пользователя самым радикальным способом - через рефлексию метода у загруженного бинарника VLC (!)

Колдунство второе: рефлексия для С

Следующий уровень веселья показан на скриншоте выше, но разумеется нуждается в пояснениях:

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 тут очень далеко и реально вызывать столь интересным образом получается мягко говоря далеко не все.

Рассказываю как этот цирк с конями работает.

Интерпретатору wsh скармливается разделяемая библиотека или ELF-бинарник:

wsh /usr/sbin/apache2

К сожалению стабильность работы вызывает вопросы, поэтому тут 50/50 — может сработать, может нет. Если был передан бинарник — автоматически произойдет его «библификация», описанная выше.

Если сработало, появится сообщение:

loading of libified binary succeeded

и можно будет что-то пытаться вызывать:

a = ap_get_server_banner()
print(a)

Код выше — это такая помесь С и Lua с поэтическим названием:

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.

На практике же речь идет только о подключаемых библиотеках:

Ключи сборки mplayer - похоже на правду?
Ключи сборки mplayer - похоже на правду?

Для сравнения вывод стандартного 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, думаю стоит ознакомиться с этой штукой поближе.

P.S.

Оригинал статьи в нашем блоге.

0x08 Software

Мы небольшая команда ветеранов ИТ‑индустрии, создаем и дорабатываем самое разнообразное программное обеспечение, наш софт автоматизирует бизнес‑процессы на трех континентах, в самых разных отраслях и условиях.

Оживляем давно умершеечиним никогда не работавшее и создаем невозможное — затем рассказываем об этом в своих статьях.

Теги:
Хабы:
+11
Комментарии1

Публикации

Истории

Ближайшие события

25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область