1. Если вы только начинаете программировать на ассемблере и не знаете с чего начать, тогда попробуйте среду разработки ASM Visual IDE
    (c) на правах рекламы
    Скрыть объявление

Введение в реверсинг с нуля, используя IDA PRO. Часть 65. Часть 1.

Дата публикации 18 апр 2019 | Редактировалось 4 июл 2019
ТУТОРИАЛ ДЛЯ ЗАДАНИЯ NICO ДЛЯ EKOPARTY 2018 - ЧАСТЬ 1

Давайте отреверсим шаг за шагом задание NICO для EKOPARTY 2018. Это сервер скомпилированный 64-битным компилятором и работающий конечно на WINDOWS.

Для начала я посмотрю на него в WINDOWS 7. В любом случае, часть статического реверсинга будет похожей.

При запуске мы видим следующее.

1.png

Описание находится здесь.

https://labs.bluefrostsecurity.de/blog/2018/09/11/bfs-ekoparty-2018-exploitation-challenge/

2.png

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

В окне строк мы ищем TOTAL RESERVES и получаем два результата.

3.png

Давайте посмотрим, где они используются.

4.png

И нажав клавишу X мы можем увидеть ссылки.

5.png

6.png

Здесь мы видим цикл со счетчиком. Когда он достигает нуля, программа переходит к TOTAL RESERVES : U$0, а если он больше нуля, программа переходит налево, чтобы вывести сумму, иначе программа идет туда, где находится строка THE CAPITAL FLIGHT HAS STOPPED.

Здесь мы видим десятичное значение 50000, которым инициализируется переменная CONTADOR_GUITA.

7.png

Здесь программа копирует переменную CONTADOR_GUITA в другую переменную.

8.png

Я переименую её.

9.png

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

10.png

Есть две переменные 130 и 134, которые передаются в качестве аргумента, и третья переменная, которая передается через регистр R8, которая является указателем на строку, которую я создал для печати.

11.png

Здесь мы видим начало функции и как переменная инициализируется константой 0x10 и остальные строки читают переменную, значение больше не изменяется внутри функции.

12.png

С переменной 134 происходит то же самое, поэтому мы переименуем сейчас их в CONST_0x10 и CONST_0x18.

13.png

В 64х битных приложениях, если мы хотим, чтобы имена аргументов распространялись в родительскую функцию, мы должны установить тип с помощью SET TYPE в адресе функции.

Делаем правый щелчок и выбираем SET TYPE или клавишу Y. Мы можем определить функцию как USERCALL, так как вызов FASTCALL позволяет нам только устанавливать регистры в качестве аргументов функции.

14.png

__INT64 __USERCALL A_IMPRIMIR@<RAX>(INT CONST_0X10@<ECX>, INT CONST_0X18@<EDX>, CHAR *DEST@<R8>);

Мы видим, что программа изменила функцию, которая была __FASTCALL на USERCALL. Тип возвращаемого значения, я оставляю равным __INT64. Я добавляю после нового имени A_IMPRIMIR @<EAX>, что является регистром, в которой будет возвращать возвращаемое значение. Оно должно быть равно @<RAX>, но я уже сделал это, и это не влияет на анализ, так как программа не возвращает полезные значения только для печати, а затем три аргумента:

INT CONST_0X10@<ECX>
INT CONST_0X18@<EDX>
CHAR *DEST@<R8>

Два целых числа и указатель на строку DEST.

Если в родительской функции все в порядке, должны появиться имена аргументов.

15.png

Мы видим, что в некоторых вызовах функции A_IMPRIMIR программа добавляет к постоянным переменным 0x10 и 0x18 значения перед вызовом, как в случае зеленого блока, который увеличивает регистр ECX и вычитает 4 из регистра EAX перед вызовом. Также, пока не станет ясно, что это значение мы не будем его переименовывать.

Мы также видим, что резервирование пространства для локальных переменных выполняется с помощью инструкции SUB RSP, 48.

16.png

Программа сохраняет в стеке значения аргументов через регистры ECX, EDX и R8 в пространство, зарезервированное для родительской функцией, поверх ее локальных переменных. Я резервирую еще 4 QWORDS для передачи аргументов, и, поскольку они находятся ниже адреса возврата, они ведут себя как если аргументы были бы переданы через стек.

17.png

Здесь есть адрес возврата. Ниже как всегда находятся аргументы, а выше переменные. Все пространство под адресом возврата, где я сохраняю аргументы, было зарезервировано родительской функцией через её инструкцию SUB RSP, XXX Для этого я добавил больше места, чем нужно для локальных переменных.

Если мы добавим опцию указателя стека.

18.png

Мы видим, что стек не изменяется. Нет ни PUSHа ни POPа, и вход и выход из функции не были изменены.

19.png

Мы видим, что это функции, относящиеся к RSP, не сохраняется регистр RBP в любое время, и все отсчитывается относительно RSP + XXX вместо RBP + XXX.

Мы видим, что щелкнув правой кнопкой мыши по одному из этих трех аргументов, который расположен ниже адреса возврата, мы подтверждаем, что это RSP+18h. (они находятся ниже адреса возврата).

20.png

Таким образом, отсюда это похоже на известную функцию. Аргументы ниже R и переменные выше. Регистр RBP не сохраняется, потому что это всё относительно RSP.

21.png

Мы видим, что аргументы CONST_0x10 и 0x18 являются частью структуры, которую обнаружила IDA.

22.png

Структура имеет тип COORD, а переменная этого типа называется DWWRITECOORD.

В статическом представлении стека.
23.png

Мы можем дважды щелкнуть на COORD. Это приведет нас к определению.

24.png

Размер структуры равен 4 байта, и у неё есть два поля: WORD X и Y.

И в LOCAL TYPES также есть определение.

25.png

Т.е. теперь мы можем правильно переименовать аргументы.

Теперь смотрится красивее.

26.png

Мы знаем, что это не будет важной частью упражнения, но мы собираемся сделать всё это подробно.

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

27.png

Затем вызывается функция GETSTDHANDLE, чтобы получить дескриптор стандартного устройства, которое может быть одним из трех в списке. (-10, -11 или -12 в зависимости от того, является ли оно вводом, выводом или ошибкой)

28.png

Также в IDA при правом щелчке и выборе пункта - USE STANDARD SYMBOLIC CONSTANT показывает в возможном списке значения, поэтому мы выбрали его оттуда.

29.png

В регистре RAX программа возвращает дескриптор HCONSOLEOUTPUT.

30.png

Это первый аргумент функции WRITECONSOLEOUTPUTCHARACTER. Справка поясняет, что функция копирует символы из буфера в выходные данные консоли.

31.png

Как мы уже видели, первый аргумент передается через регистр RCX и является дескриптором HCONSOLEOUTPUT.

Второй находится в регистре RDX и является указателем на буфер для печати.

32.png

Третий через регистр R8D - это количество символов для печати NLENGTH.

Четвертый аргумент это структура COORD. Здесь программа показывает, что это поле X, но поскольку оно является первым полем, оно совпадает с началом того же поля, и при чтении DWORD читает 4 байта одного и того же поля, т.е. оба поля.

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

После выхода программа восстанавливает стек, который создала инструкция SUB RSP, 0x48 в начале. Теперь программа возвращает его в ноль с помощью инструкции ADD RSP, 0x48.

33.png

Хорошо. Эта функция уже завершена. Мы видим, что то, что вы добавляете в некоторых вызовах переменных X и Y начальных значений 0x10 и 0x18, - это запись в другую позицию.

Возвращаясь к основной функции, мы видим, что есть глобальная переменная, которая, если мы наведем курсор мыши, мы увидим, что она инициализирована 1. Если бы она была равна нулю, программа перенесла бы нас в зеленые блоки, где она не уменьшит значение счетчика, и выведет THE CAPITAL FLIGHT HAS STOPPED.

34.png

Существует значение 1, которое изначально имеет глобальная переменную.

35.png

Мы переименуем переменную в FLAG_FUGA, потому что, если она равна 1 т.е. если она истинна, то запасы уменьшаются, а если она равна нулю, то запасы восстанавливаются.

36.png

Мы также видим, что, если мы нажимаем X в указанной глобальной переменной, нет никакой ссылки на LEGAL, где она должна быть установлена в ноль. Проблема состоит в том, чтобы увидеть, как это сделать.

37.png

Здесь мы видим, что переменная находится в секции данных, что делает её доступным для записи, и мы увидим, как это сделать.

38.png

А сейчас давайте реверсить функцию STARTADDRESS. Мы видим, что она не использует аргументы, так как первое, что она делает, это SUB RSP, 158. Мы помним, что если у нее есть аргументы, она сохраняет их в стеке, прежде чем резервировать место для переменных.

39.png

Также, если мы нажимаем X на имени, чтобы увидеть ссылки.

40.png

Мы видим, что это поток, созданный в основной функции. Мы закончим его полный анализ до этой функции STARTADDRESS, а затем продолжим здесь.

41.png

Затем есть переменная, которая, сохраняется здесь. Я называю ее CONST_1, также переименуем CONST_0x10 и 0x18 в имя COORD_X и Y.

42.png

Мы видим, что эта переменная CONST_1 lo que hace es que una vez que ya se detuvo запасы уменьшаются так как это цикл, который будет продолжать исполняться, измениться на нуль и не будет бесконечно повторять печать THE CAPITAL FLIGTH HAS STOPPED.

43.png

Таким образом, мы можем изменить имя на FLAG_IMPRIMIR_STOP.

44.png

Мы помним что в IDA есть префиксы.

https://www.hex-rays.com/products/ida/support/idadoc/609.shtml


45.png

Эти префиксы, за которыми следуют подчеркивание (как OFF_) и затем адрес, эквивалентны скобкам [], а OFF указывает мне тип значения который находится в скобках.

Это было бы эквивалентно.

MOV RCX, [0x14000D088]

За исключением того, что программа добавляет, что содержимым является смещение.

46.png

Таким образом, вы должны увидеть содержимое, которое будет помещено в регистр RCX.

47.png

Здесь мы это видим. По адресу 0x14000D088 добавляется префикс OFF_, поскольку его содержимое является смещением или указателем. В этом случае его значение равно 0x14000D000, содержимое которого является строкой ASC, поэтому этот адрес имеет префикс ASC_ впереди.

Т.е., проще говоря, у нас есть строка, и этот другой адрес хранит смещение или её адрес.

48.png

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

Здесь также, прежде чем резервировать место в стеке для переменных, сохраните 4 аргумента ниже адреса возврата.

49.png

Регистр RCX был адресом STRING_EN_DATA.

Другие три аргумента являются константами.

50.png

Я изменил эти имена.

51.png

С этим я могу продолжить реверсинг, но если я захочу распространить переменные.
__INT64 __USERCALL SUB_140001580@<RAX>(CHAR *STRING_EN_DATA@<RCX>, INT CONST_0XA@<EDX>, INT CONST_0X18@<R8D>, INT CONST_0X90@<R9D>);

И у меня получается ссылка.

52.png

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

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

53.png

И также есть локальная переменная COOKIE.

Перед входом в цикл скопируется адрес строки STRING_EN_DATA в другую переменную и инициализируется счетчик в ноль.

54.png

Затем программа вызывает функцию STRCHR. Она ищет байт 0xA. Функция возвращает указатель на первое вхождение этого символа в строке или ноль, если она не находит его.

55.png

56.png

Мы видим, что строка имеет несколько символов 0xA, другими словами это строка с несколькими строками.

57.png

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

Также мы видим, что STRING_EN_DATA всегда указывает на начало строки и никогда не меняется, поскольку она читает только после инициализации указанной переменной.

58.png

Тем не менее, STRING_EN_DATA_2 в начале аналогична STRING_EN_DATA, но есть доступ на запись к указанной переменной, поэтому она изменит свое значение.

59.png

Мы видим, что перед выходом повторяется цикл.

60.png

Читается указателя на следующую строку увеличивает его, поскольку он указывает на 0xA, чтобы пропустить этот символ и сохранить его в STRING_EN_DATA_2, так что последний в каждом цикле будет увеличиваться, сохраняя указатель, который увеличивается построчно.

Поэтому я переименую его в P_LINEA_STRING_EN_DATA, а другой изменю на P_SIGUIENTE_0XA, поскольку он всегда будет указывать на 0xA, как это выглядит в STRCHR.

61.png

Итак, мы видим, что цикл будет повторять строку за строкой, а поскольку P_LINEA_STRING_EN_DATA всегда указывает на следующую строку, когда строки заканчиваются и больше нет 0xA в строке программа выходит из цикла. Теперь давайте посмотрим, что программа делает в цикле.

62.png

Мы видим, что есть вызов функции STRNCPY. COUNT или количество копируемых байтов происходит из вычитания двух адресов. Из P_LINEA_STRING_EN_DATA и из следующего адреса 0xA. Т.е программа скопировала строку. Поскольку источник - это то же самое P_LINEA_STRING_EN_DATA и назначение это DEST, который является буфером назначения.

63.png

Если мы сделаем правой кнопкой мыши и выберем - ARRAY в DEST в представлении стека.

64.png

Здесь мы видим целевой буфер длиной 256 байт.

65.png

Снова пересчитывается размер строки, вычитая адрес 0xA из следующей строки от её начала и перемещая результат в регистр R9D.

66.png

Мы видим, что счетчик увеличивается при каждом цикле.

67.png

Но также счетчик добавляется к тому, что он читает из переменной CONST_0xA, а затем передает это значение в качестве второго аргумента, поэтому в регистре EDX будет CONST_0XA_MAS_COUNTER.

68.png

Здесь есть 4 аргумента внутри функции.

69.png

Если я хочу распространить переменные с помощью с SET TYPE.
__INT64 __USERCALL SUB_140001580@<RAX>(CHAR * P_DEST@<RCX>, INT CONST_0XA_MAS_CONTADOR@<EDX>, INT CONST_0X18@<R8D>, INT NLENGHT@<R9D>);

И у меня получается ссылка.

70.png

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

71.png

Этот блок увеличивает счетчик на один внутри цикла.

72.png

И поскольку переменная считается от нуля и увеличивается на один каждый раз, выходной результат равен NLENGHT, т. е. длине строки. (JNB, если результат не ниже, т.е. если он равен или больше)

73.png

Внутри цикла программа берет начальный адрес строки и прибавляет счетчик, т.е. берет первый байт строки и сравнивает его со значением 0x20, чтобы увидеть, является ли символ пробелом.

74.png

Мы помним, что каждая строка состоит из пробелов и звездочек.

В HEX_DUMP я вижу строку. Это 0x20 (пробелы) и 0X2A (звездочки)

75.png

102.png

Если символ является пробелом, программа переходит к зеленому блоку, иначе к другому.

Мы помним, что когда ищем 0xA и сохраняем указатель INC EAX, чтобы пропустить 0xA, поэтому, если символ не является пробелом, он будет звездочкой, поскольку 0xA от начала пропускается путем увеличения указателя.

76.png

Таким образом, мы можем думать, что если это не пробел, то это звездочка.

77.png

Осталось также увидеть, что такое ARG_20, поскольку существует только 4 аргумента, а у дочерней функции только 5, 5тый - это ARG_20.

78.png

Напомним, что в этом компиляторе, родительская функция исполняет SUB RSP, 168 чтобы освободить место для собственных переменных, а также освободить место для аргументов, которым необходимо передать регистры в дочернюю функцию (4QWORDS),

79.png

Если в родительской функции я определяю 4 QWORDS выше зарезервированного пространства, чтобы использовать в качестве аргументов, а следующая функция читает 5 аргументов, 5-й будет локальной переменной родительской функции, которая выше, в этом случае это CONST_0x90_B.

На следующем рисунке я определил больше пространства, чем создала отцовская функция при выполнении SUB RSP, XXX, поверх той, которая ему нужна для локальных переменных, например, 4 QWORDS (VAR_168, VAR_160, VAR_158 и VAR_150)

80.png

Следовательно, мы передали еще один аргумент, который будет переменной родительской функции CONST_0X90.

81.png

Мы также видим, что существует массив слов с именем ATTRIBUTE. Я преобразую его в массив длиной 0x256 слов.

82.png

Мы также видим, что когда это пробел, программа записывает ноль в массив ATTRIBUTE, а когда это звездочка, программа записывает 0x90.

83.png

Другими словами для каждой строки запишется слово 0x00 в пробелах и слово 0x90, где были звездочки.

Мы видим, что программа собирается снова записать в консоль. Программа возвращается, чтобы найти дескриптор OUTPUT. Она передает координаты X и Y и в виде строки для печати передает указатель на ATTRIBUTE,

84.png

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

85.png

И создают цветную строку.

86.png

87.png

Так что я могу переименовать функцию в DIBUJAR_STRING.

88.png

Хотя я могу уточнить, что в этом вызове подтягивается BCRA.

89.png

Мы также видим, что если вы запустите программа, она поменяет цвет на желтый.

90.png

А чуть ниже на красный.

91.png

Мы видим, что среди аргументов функции WRITECONSOLEOUTPUTATTRIBUTE, атрибуты символов указывают в другое место.

92.png

93.png

94.png

95.png

Хорошо, 0x90 как мы писали в начало, это сумма этих двух констант

#define BACKGROUND_BLUE 0x0010
#define BACKGROUND_INTENSITY 0x0080

Вот почему это дает синий цвет.

Чтобы получить желтый цвет, нужно сочетание красного и зеленого.

#define BACKGROUND_GREEN 0x0020
#define BACKGROUND_RED 0x0040
#define BACKGROUND_INTENSITY 0x0080

96.png

97.png

Здесь мы видим, что программа вызывает все те же аргументы, кроме 0xE0 от Ox90, чтобы изменить цвет на желтый.

И красный цвет получается так

#define BACKGROUND_RED 0x0040
#define BACKGROUND_INTENSITY 0x0080

98.png

Помните, что CONTADOR_GUITA и CONTADOR_GUITA_2 равны в начале цикла

99.png

Далее вызывается GETTICKCOUNT.

0 2.448
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
91