Вызываем демонов с printf

от автора

Один из источников настоящего открытия — это способность сомневаться в очевидных вещах

Предисловие

Начинающие реверс-инженеры часто сталкиваются с многочисленными препятствиями. Эта статья описывает определённый метод, который, как полагает автор, может вызвать замешательство у тех, кто только начинает изучать область анализа приложений. Стоит подчеркнуть, что цель данного материала не в представлении инновационного подхода или оказании значительной практической пользы, а в рассмотрении показательного случая.

Edited: Стоит отметить, что ниже изложенное в первую очередь относится к приложениям компилируемых при помощи GCC.

Что такое printf?

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

Функция printf() перенаправляет аргументы из списка arg-list в стандартный вывод (stdout) под контролем строки формата, на которую указывает аргумент 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. ‘Q’ — код конверсии.

  2. quit_handler — функция, обрабатывающая конверсию.

  3. &print_arginfo — функция, которая необходима только для parse_printf_format, которую вы, вероятно, никогда не будете использовать. Если же вы все же решите её использовать, то, вероятно, что-то пошло не так. Обратите внимание, что знания о том, что нужно возвращать 1, если все прошло успешно, будут вам достаточно.

#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); не будет выполнено.

Перезапись

Позвольте сделать небольшое лирическое отступление. Представим ситуацию, когда вы проводите обратную разработку приложения и получили следующий простой псевдокод:

void admin_panel(){ printf("You are admin!"); } void main(){     printf("I love %d\n",54); admin_panel(); }

В данной ситуации очевидным кажется такой вывод программы:

I love 54 You are admin!

Но запустив приложение вы увидите:

Hidden text

I love 1337 You are user!

Но как это возможно? Все дело в том, что мы можем переопределить стандартные спецификаторы форматирования. Сделав это перед функцией main, можно заметно запутать начинающего реверс-инженера, так как вряд ли кто-то придет в голову анализировать, куда ведет функция printf. В данном конкретном случае, это было реализовано следующим образом:

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 (не забывая выполнить приведение указателя). Давайте модифицируем код конверсии так, чтобы он продолжал работать независимо от изменения строки. Это может выглядеть примерно так:

#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, сохранив при этом видимое поведение функции неизменным. Это может создать серьезные затруднения для реверс-инженера. В данном конкретном случае, мы незаметно изменим переменную status, используемую в последующей функции.

Модификаторы

Когда мы работаем с функцией 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

Например, изменим преобразование числа из предыдущего пункта на следующее:

itoa(num * ( info -> left ? -1 : 1 ),chr,10);

Тогда вызов printf("%-d",54); выведет нам -54 соответсвенно.

Заключения

Очевидно, что данная техника обфускации и усложнения кода сложна для применения в реальных условиях. Как минимум, опытный реверс-инженер проверит вызовы функций до входа в функцию main. Однако, этот подход хорошо демонстрирует, что нет такого понятия как «очевидность» при попытках понять, как работает приложение. Всегда стоит заглядывать глубже.

P.S.

Я приветствую любые комментарии, указывающие на недостатки и упущения в моем материале. Буду рад обсуждению в комментариях.


ссылка на оригинал статьи https://habr.com/ru/articles/739764/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *