# Фокусы с printf > Фактически printf существует сегодня лишь для того, чтобы скрыть, что его больше нет. @Жан Бодрийяр Очень часто на CTF соревнованиях приходится реверсить разнообразные виртуальные машины. И однажды я встретил интересную реализацию через printf.В этой статье попытаюсь обьяснить как это работает и как такое писать. ## Что такое printf? Начну из далека, Функция printf() записывает в stdout аргументы из списка arg-list под управлением строки, на которую указывает аргумент format. Примерно такое определение дают многие сайты и так же предоставляют примерно такой cheatsheet: |Код|Формат| |--- |--- | |%с|Символ типа char| |%d|Десятичное число целого типа со знаком| |%i|Десятичное число целого типа со знаком| |%е|Научная нотация (е нижнего регистра)| |%Е|Научная нотация (Е верхнего регистра)| |%f|Десятичное число с плавающей точкой| |%g|Использует код %е или %f — тот из них, который короче (при использовании %g используется е нижнего регистра)| |%G|Использует код %Е или %f — тот из них, который короче (при использовании %G используется Е верхнего регистра)| |%о|Восьмеричное целое число без знака| |%s|Строка символов| |%u|Десятичное число целого типа без знака| |%х|Шестнадцатиричное целое число без знака (буквы нижнего регистра)| |%Х|Шестнадцатиричное целое число без знака (буквы верхнего регистра)| |%р|Выводит на экран значение указателя| |%n|Ассоциированный аргумент — это указатель на переменную целого типа, в которую помещено количество символов, записанных на данный момент| |%%|Выводит символ %| Но очень мало людей знают, что ***можно добавить свой собственный спецификатор*** форматирования для printf (Или же как иногда её называют конверсии,данное определение я и буду использовать далее). ## Добавление своей конверсии Конверсия добавляется при помощи функции `int register_printf_function`. Пример добавления конверсии: `register_printf_function('Q',quit_handler,&print_arginfo);` Где: 1. Код конверсии 2. Функция обрабатывающая конверсию 3. Бесполезная функция нужная лишь для `parse_printf_format`, который вы никогда не будете использовать, а если и используете - что-то пошло явно не так. (Знания что нужно возращать 1,если все хорошо, вам хватит) ```c= #include <stdio.h> #include <stdlib.h> #include "printf.h" #pragma GCC diagnostic ignored "-Wdeprecated-declarations" int secret = 54; int print_arginfo (){ return 1; } int quit_handler(FILE *stream, const struct printf_info *info, const void *const *args) { exit(0); } void slonser(){ printf("I know how to keep my secrets\n%Q"); printf("My secret: %d",secret); } void main(){ register_printf_function('Q',quit_handler,&print_arginfo); slonser(); } ``` `%Q` в данной программе вызовит `exit`, так что `printf("My secret: %d",secret);` не отработает. ### Загадка Вы реверсите VM, вот псевдокод который вы смогли получить: ```c= void admin_panel(){ printf("You are admin!"); } void main(){ printf("I love %d\n",54); admin_panel(); } ``` Что выведет программа? Наивный читатель ответит,что: ```Haskell I love 54 You are admin! ``` Когда догадливый читатель без труда догадается, что на самом деле будет выведено: ```Haskell I love 1337 You are user! ``` Но как? Все дело в том,что ***мы можем переписывать стандартные коды конверсии***, и сделав это перед функцией main,конкретно озадачить начинающего реверсера, ведь кто в здравом уме будет смотреть куда идет функция printf. Конкретно в этом случае это было сделано так: ```c= int quit_handler(FILE *stream, const struct printf_info *info, const void *const *args) { puts("1337\nYou are user!"); exit(0); } void __attribute__ ((constructor)) premain(){ register_printf_function('d',quit_handler,&print_arginfo); } ``` ***Важно отметить,что к сожалению нельзя перезаписать любой символ***, нельзя использовать символы зарезервированные за модификаторами, о которых будет упомянуто чуть позже. ## А как с данными то работать? Конечно очень полезно уметь работать с аргументами которые передаются в printf после форматной строки, делается это очень просто по средствам доступа к массиву args ( не забыв закастить указатель).Давайте сделаем так,чтобы код конверсии работал конкретно не смотря на изменение строки Выглядит это как то так: ```c= #include <stdio.h> #include <stdlib.h> #include "printf.h" #pragma GCC diagnostic ignored "-Wdeprecated-declarations" int check = 0; char *status = "admin"; char* itoa(int value, char* result, int base) { if (base < 2 || base > 36) { *result = '\0'; return result; } char* ptr = result, *ptr1 = result, tmp_char; int tmp_value; do { tmp_value = value; value /= base; *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; } while ( value ); if (tmp_value < 0) *ptr++ = '-'; *ptr-- = '\0'; while(ptr1 < ptr) { tmp_char = *ptr; *ptr--= *ptr1; *ptr1++ = tmp_char; } return result; } void admin_panel(){ printf("You are %s!\n",status); } int print_arginfo (){ return 1; } int quit_handler(FILE *stream, const struct printf_info *info, const void *const *args) { if(!check){ check = 1; status = "user"; } char chr[32]; int num = *((int**)args[0]); itoa(num,chr,10); fprintf(stream,"%s",chr); return 0; } void __attribute__ ((constructor)) premain(){ register_printf_function('d',quit_handler,&print_arginfo); } void main(){ printf("You personal code is %d\n",54); printf("You personal code is %d\n",123123); admin_panel(); } ``` Как видно с помощью такого метода ***можно незаметно менять константы, или даже функции в runtime***,что может сыграть злую шутку с реверсером. ## Модификаторы Когда мы работаем с printf, то у нас существуют модификаторы (Например `l` для длинных типов данных) и специальные флаги, если кратко то: | Член структуры | Название в форматной строке | Смысл | |-----------------------------|-----------------------------|------------------------------------------------------------------------| | int prec | * | 1 если указана точность | | int width | * | Минимальный размер вывода | | wchar_t spec | None | Можете использовать в своих целях | | unsigned int is_long_double | q,L,ll | - | | unsigned int is_char | hh | - | | unsigned int is_short | h | - | | unsigned int is_long | l | - | | unsigned int alt | # | - | | unsigned int space | пробел | - | | unsigned int left | - | - | | unsigned int showsign | + | - | | unsigned int group | ' | - | | unsigned int extra | None | Всегда ноль при стандартном вызове printf,можете использовать свободно | | unsigned int wide | None | Если wstream то 1 | | wchar_t pad | None | Символ отступа, которым строка добивается до width | Например изменим перевод числа из прошлого пункта на ```c itoa(num * ( info -> left ? -1 : 1 ),chr,10); ``` Тогда такой вызов выведет -54. ```c printf("%-d",54); ``` ## Пример VM на эту тему Вообще у меня есть таск написанный мной на эту тему, если кто-то проявит интерес,то наверное скину в канал.И когда все поковыряют, запилю врайтап и отдам исходники. ```c= printf("%R%S%A"); ```