---
# System prepended metadata

title: Фокусы с printf

---

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