# Фокусы с 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");
```