> For the complete documentation index, see [llms.txt](https://yutewiyof.gitbook.io/intro-rev-ida-pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yutewiyof.gitbook.io/intro-rev-ida-pro/chast-65.md).

# Часть 65

[\[Используемые материалы\]](https://github.com/yutewiyof/intro-rev-ida-pro/tree/e1367e11cc661f3d69c02ae5f733b7dd168bc5ab/.gitbook/assets/files/65.zip)

## ТУТОРИАЛ ДЛЯ ЗАДАНИЯ NICO ДЛЯ EKOPARTY 2018 - ЧАСТЬ 1

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

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

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

![](/files/-Ljtu1vnzy5Tb6SkbKxy)

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

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

![](/files/-Ljtdx2lPjmAy54axcHx)

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

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

![](/files/-Ljtdx2uXy3GFxsYRfBR)

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

![](/files/-Ljtu1wSR-WvcMYxwWlw)

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

![](/files/-Ljtdx3I2GxdMZlDDZqL)

![](/files/-Ljtu1x8mbAqOddirsZV)

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

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

![](/files/-Ljtdx3Ub5DPSam27uJd)

Здесь программа копирует переменную **CONTADOR\_GUITA** в другую переменную.

![](/files/-Ljtdx3aND13wHj-5_FN)

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

![](/files/-Ljtu1xrPdRwzwzlhYlZ)

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

![](/files/-Ljtu1y2EuouFLUNzQY3)

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

![](/files/-Ljtu1yHWKXjKyomkSZm)

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

![](/files/-Ljtu1yVUi-0aX_z-Gxa)

С переменной **134** происходит то же самое, поэтому мы переименуем сейчас их в **CONST\_0x10** и **CONST\_0x18**.

![](/files/-Ljtdx48YuPwuZm3B5c1)

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

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

![](/files/-Ljtdx4FIzj8NxLvFnjD)

```
__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@&lt;ECX&gt;
INT CONST\_0X18@&lt;EDX&gt;
CHAR \*DEST@&lt;R8&gt;
```

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

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

![](/files/-Ljtdx4Rd9KETeF4Lu_Q)

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

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

![](/files/-Ljtu1zAPGth_vl87joB)

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

![](/files/-Ljtu1zXktkpvHRWGFNG)

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

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

![](/files/-Ljtdx4gFDMgAKxcWw-b)

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

![](/files/-Ljtdx4kith4jTQbrA4E)

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

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

![](/files/-Ljtu2-GLW-6PIHeAazv)

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

![](/files/-Ljtdx4wFzcdolJZHY4o)

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

![](/files/-Ljtu2-fTRyq3YWHW2eM)

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

В статическом представлении стека. ![](/files/-Ljtu2-xzwLAHDw0vpAQ)

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

![](/files/-Ljtdx59xOATKF1TOwMM)

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

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

![](/files/-Ljtu20J5yKd3DKNpvf9)

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

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

![](/files/-Ljtu20ezEJR592IQQV1)

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

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

![](/files/-Ljtdx5JeZeXcAY_-PkV)

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

![](/files/-Ljtdx5Mku-lkrS7IpDY)

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

![](/files/-Ljtdx5QEUCWwGRd3jaK)

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

![](/files/-Ljtdx5cSj3VFtHgefcQ)

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

![](/files/-Ljtdx5iRbFIc3j_lRlR)

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

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

![](/files/-Ljtu22OV6eCyFYNay2y)

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

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

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

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

![](/files/-Ljtdx5vjxeZUDQb8Cy-)

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

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

![](/files/-Ljtu22zanJ3-nOYYuPU)

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

![](/files/-Ljtdx629B9mxwmSPHkE)

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

![](/files/-Ljtu23PtLpxeLCM2gjO)

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

![](/files/-Ljtu23ZE8l7cPSFYwDb)

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

![](/files/-Ljtu23nXzmQGcqB05eo)

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

![](/files/-Ljtdx6OVepZGXRoch6X)

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

![](/files/-Ljtu246pqYW9QiPqkDt)

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

![](/files/-Ljtu24HiOu-COljyDZg)

Затем есть переменная, которая, сохраняется здесь. Я называю ее **CONST\_1**, также переименуем **CONST\_0x10** и **0x18** в имя **COORD\_X** и **Y**.

![](/files/-Ljtdx6gtUEeOQfXEmsz)

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

![](/files/-Ljtu24Ye62qPTGtZwBd)

Таким образом, мы можем изменить имя на **FLAG\_IMPRIMIR\_STOP**.

![](/files/-Ljtu24eo5lNvRd2-D6a)

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

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

![](/files/-Ljtu24mwrl0kTPy8IPb)

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

Это было бы эквивалентно **MOV RCX, \[0x14000D088]**

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

![](/files/-Ljtu24w6JE0fJbEAkZB)

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

![](/files/-Ljtu2583kA8yn3t3loM)

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

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

![](/files/-Ljtu25IIBjpvbgjUz5k)

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

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

![](/files/-Ljtdx78VpdZNXuSIfut)

Регистр **RCX** был адресом **STRING\_EN\_DATA**.

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

![](/files/-Ljtdx7Hue4MiX2fkgaM)

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

![](/files/-Ljtu25s3J0VFI-DAk-U)

С этим я могу продолжить реверсинг, но если я захочу распространить переменные. **\_\_INT64 \_\_USERCALL SUB\_140001580@\<RAX>(CHAR \*STRING\_EN\_DATA@\<RCX>, INT CONST\_0XA@\<EDX>, INT CONST\_0X18@\<R8D>, INT CONST\_0X90@\<R9D>);**

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

![](/files/-Ljtdx7UtP_NspVMisJt)

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

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

![](/files/-Ljtdx7ZhrqS4lcfKnt8)

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

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

![](/files/-Ljtdx7eMDPyNbv4JwLB)

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

![](/files/-Ljtdx7k5DaKM8kfC85C)

![](/files/-Ljtdx7nRqOax8RfN1gx)

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

![](/files/-Ljtdx7qQmTSQBxfLNUF)

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

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

![](/files/-Ljtdx7uK_tBzxDcp3iU)

Тем не менее, **STRING\_EN\_DATA\_2** в начале аналогична **STRING\_EN\_DATA**, но есть доступ на запись к указанной переменной, поэтому она изменит свое значение.

![](/files/-Ljtu27W9fqs2shCssoG)

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

![](/files/-Ljtu27i0pSriq1SyI3d)

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

Поэтому я переименую его в **P\_LINEA\_STRING\_EN\_DATA**, а другой изменю на **P\_SIGUIENTE\_0XA**, поскольку он всегда будет указывать на **0xA**, как это выглядит в **STRCHR**.

![](/files/-Ljtdx8AwwSevVprM7pl)

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

![](/files/-Ljtu2849JgsvxlWG5OZ)

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

![](/files/-Ljtu28NRljhgH1IoHv5)

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

![](/files/-Ljtu28ZZtFwJM3mFYOb)

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

![](/files/-Ljtu28gZVBpo4e55SoC)

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

![](/files/-Ljtu28y3w3jZo1N9Yn-)

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

![](/files/-Ljtu29EdAcBP_LCaF-O)

Но также счетчик добавляется к тому, что он читает из переменной **CONST\_0xA**, а затем передает это значение в качестве второго аргумента, поэтому в регистре **EDX** будет **CONST\_0XA\_MAS\_COUNTER**.

![](/files/-Ljtu29UnbTKREN37vXF)

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

![](/files/-Ljtdx91_mkAlhYKIppC)

Если я хочу распространить переменные с помощью с **SET TYPE**. **\_\_INT64 \_\_USERCALL SUB\_140001580@\<RAX>(CHAR \* P\_DEST@\<RCX>, INT CONST\_0XA\_MAS\_CONTADOR@\<EDX>, INT CONST\_0X18@\<R8D>, INT NLENGHT@\<R9D>);**

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

![](/files/-Ljtdx97yRCA70rI8RLT)

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

![](/files/-Ljtu2AD2h2VUYIiBwTi)

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

![](/files/-Ljtu2AQavfZg-kb_wWC)

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

![](/files/-Ljtu2Ah6Eh_ldDlEvdd)

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

![](/files/-Ljtdx9bcjcAfZjYwU8F)

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

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

![](/files/-Ljtdx9hf0lhEe1E1AaW)

![](/files/-LkxkXOuOV7GaCg7t05I)

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

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

![](/files/-Ljtdx9zAfF86LyQkMGu)

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

![](/files/-Ljtu2BarN0anwZl5FOq)

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

![](/files/-Ljtu2BkdYZ0M6d_Gn7k)

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

![](/files/-LjtdxAHDRvsDuvwBJvT)

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

На следующем рисунке я определил больше пространства, чем создала отцовская функция при выполнении **SUB RSP**, **XXX**, поверх той, которая ему нужна для локальных переменных, например, **4QWORDS** (**VAR**\_**168**, **VAR\_160**, **VAR\_158** и **VAR\_150**)

![](/files/-LjtdxANYttRUP9ne1OU)

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

![](/files/-Ljtu2CKwzn3sg_ksxqI)

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

![](/files/-LjtdxAfGmaPlRo3cipT)

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

![](/files/-Ljtu2CnCTrwMpiBAAqG)

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

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

![](/files/-LjtdxAxHL9lsztnQzDR)

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

![](/files/-Ljtu2DALlOcMN3BmNAi)

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

![](/files/-Ljtu2DSpaK5hxy-pOEm)

![](/files/-Ljtu2DbPZbc2rYhGA8h)

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

![](/files/-LjtdxBYUler4ptn1AkQ)

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

![](/files/-Ljtu2Dxkzjf_RlzyjvX)

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

![](/files/-LjtdxBij_A32svYx6SX)

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

![](/files/-Ljtu2EO-wJt2spkNqnu)

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

![](/files/-Ljtu2EZvN3zPFR5kYH3)

![](/files/-LjtdxC0FYzqO7QW36r9)

![](/files/-Ljtu2F2-1xJb2JTDySg)

![](/files/-LjtdxCFPDu6tYIGiKwd)

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

```cpp
#define BACKGROUND_BLUE 0x0010
#define BACKGROUND_INTENSITY 0x0080
```

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

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

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

![](/files/-Ljtu2FiQ9HU5N3M-3Nz)

![](/files/-Ljtu2FxeVbs14AbajCn)

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

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

```cpp
#define BACKGROUND_RED 0x0040
#define BACKGROUND_INTENSITY 0x0080
```

![](/files/-Ljtu2GD4mhnDxUSP4E7)

Помните, что **CONTADOR\_GUITA** и **CONTADOR\_GUITA\_2** равны в начале цикла

![](/files/-Ljtu2GT-xB6FA1EY01P)

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

![](/files/-Ljtu2Ge6ctfRSJOIDnJ)

Но мы можем считать, что программа возвращает значение, которое является случайным и увеличивается, и это значение делится на константу **0x7D0**.

![](/files/-LjtdxCrltHPr0PCGgxp)

И результат сохраняется в **EAX**, остаток - в **EDX**.

![](/files/-LjtdxCvHxHxbcL0sx2Z)

Поэтому **COUNTER\_GUITA** на этом этапе будет меньше, чем **CONTADOR\_GUITA₂**.

![](/files/-Ljtu2HCX59SnicxybVk)

И когда **COUNTER\_GUITA** будет меньше **30000**, а **COUNTER\_GUITA\_2** будет больше, программа изменит вывод на желтый, то же самое произойдет, когда **COUNTER\_GUITA** будет меньше **10000**, а **COUNTER\_GUITA\_2** больше **10000**.

До тех пор пока **COUNTER\_GUITA** не станет равным нулю, и программа перейдет к красному блоку, который завершает программу.

![](/files/-LjtdxD2QdIaMCBvc8Gx)

Мы видим, что все это заставляет программу просто запускаться. Это является ее потоком. Теперь мы должны увидеть главную функцию, то есть ту, которая создает этот поток, чтобы увидеть, есть ли у нас возможность повлиять на что-то здесь, чтобы остановить счетчик, потому что здесь "легально" мы видели, что не можем ни на что повлиять.

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

![](/files/-Ljtu2HsgtMIS5MULb9W)

Здесь мы видим, что с помощью **SUB RSP, RAX**, резервируются **0x1088** байтов пространства в стеке для локальных переменных, буферов и т.д. Мы также переименовываем переменную, в которой хранится **COOKIE**.

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

![](/files/-LjtdxDCxSf_aVxSec28)

Первое, что делаете программа в первом вызове, это вызывает функцию **WSASTARTUP**.

![](/files/-Ljtu2ILXO-EV4Qg-I2Y)

IDA показывает нам аргументы. Первый из которых передается в **ECX**, и это версия. Здесь он устанавливается в 0x202 и сохраняется в указанной переменной **WVERSIONREQUESTED**, затем **ECX**читается как первый аргумент.

Второй аргумент, который передается через **EDX**, является структурой типа **WSADATA**.

![](/files/-LjtdxDN88hzpBCZWL7A)

Если мы перейдем на вкладку структуры, мы увидим это

![](/files/-LjtdxDRwjRGrAhxiTi7)

Также в **LOCAL TYPES**.

![](/files/-LjtdxDU-U9y2dEmWmoC)

Мы видим, что в начале переменная устанавливает в ноль. Мы будем называть ее **FLAG\_CORRECT**, потому что если она равна **1**, то это потому, что все в порядке.

![](/files/-LjtdxDX-Qqqs24BCEjH)

Переменная равна **1**, если поле **WVERSION**, которое было заполнено при вызове **WSASTARTUP**, равна переменной **WVERSIONREQUESTED**, которую мы передали ему в качестве аргумента.

Если они равны, программа устанавливает переменную **FLAG\_CORRECT** в единицу, отмечая, что ошибки нет, и возвращает ее в **EAX** при выходе из этой функции в качестве возвращаемого значения.

Я переименовал функцию в нечто более близкое к тому, что она делает

![](/files/-LjtdxDdW_cbl2vzP9cg)

Если переменная равно **1**, все в порядке, и программа продолжается здесь

![](/files/-Ljtu2JksnvleJanqMTO)

Первый аргумент в **RCX** - это адрес строки "**0.0.0.0**"

![](/files/-Ljtu2JyOa_LFE_3qf7o)

Это произойдет на моей машине по адресу **0X13F7ED238**. Не путайте с именем **A0000**.

**IDA** всегда помещает строки в имя, которое начинается с **A**, а затем имеет некоторое описание строки, например, означающее, что оно указывает "**A 0 0 0 0**"

![](/files/-Ljtu2KF4swA1NF4s18K)

В опциях есть префикс **А**, и он создаст имя. Даже если эта опция не может быть изменена и имеет немедленный эффект, это будет сделано при следующем анализе.

Мы видим, что я могу использовать имя строки, которое **IDA** дает мне для удобства, и если я напишу это в комментариях, двойной щелчок по тому же комментарию приведет меня к строке. Это будет очень удобно.

![](/files/-Ljtu2KSkZ0iOb7wMDj0)

![](/files/-LjtdxDvLyWJ2I0hH-S2)

Я делаю двойной щелчок здесь.

![](/files/-Ljtu2KoEnWWv0IDf3Jh)

Отлично, продолжаем.

**EDX** будет иметь значение **41414**, которое будет портом, в котором программа будет прослушиваться.

![](/files/-LjtdxE3fWIvNiPovIIY)

![](/files/-LjtdxE5i4_qM9YvxB-J)

**NETSTAT** -ano в консоли с правами администратора покажет нам процессы и порты. В этом случае вы видите **41414**.

И третий аргумент в **R8** это указатель на **QWORD** с именем **S**

![](/files/-LjtdxE7i4mJbgcc9L-L)

Мы видим другую переменную, которая сохранит флаг, если программа может выполнить успешное прослушивание. Поэтому мы переменовываем переменную в **FLAG\_LISTEN**

![](/files/-LjtdxEALNEKinCRVfK_)

Затем вызовите **HTONS**, чтобы перевести значение порта в **BIG\_ENDIAN** (darlo vuelta en criollo)

![](/files/-Ljtu2Lp3ImVo1v_Ygag)

Существует также переменная имени типа **SOCKADDR**.

![](/files/-Ljtu2M08v5A4bZP9cqK)

В структурах.

![](/files/-Ljtu2MBEE3lxXWCze84)

И в **LOCAL TYPES**.

![](/files/-LjtdxEN49wGLxGZC5Yv)

Затем вызывается сокет.

![](/files/-Ljtu2MXSlEko94Z0Vsx)

![](/files/-Ljtu2MiuYn_8Y_MtVUR)

Мы видим, что в поле **SA\_FAMILY** сохраняется **2**, и затем три аргумента для сокета передаются непосредственно как константы, а в **AF**, что является семейством адресов, программа передает **2** напрямую. (**IPV4**)

![](/files/-Ljtu2MzVuA_nrYHlNaC)

Тип это **1** (**SOCK**\_**STREAM**) что соответствует **TCP**.

![](/files/-LjtdxEatZu3KfSKgNc6)

И протокол будет нулевым (**НЕ ОПРЕДЕЛЕН**)

![](/files/-Ljtu2NNyzE2OpGdn5MU)

И возвращаемое значение будет дескриптором сокета. (То, что мы обычно называем дескриптором сокета)

![](/files/-Ljtu2NWzzH6DFj3NvUy)

Затем вызывается **BIND** с тремя аргументами: длина имени структуры **SOCKADDR**, затем указатель на то же имя структуры и сокет **S**.

![](/files/-LjtdxEjNmckVCC6Nen7)

![](/files/-Ljtu2Ny-AyC9zBI3D9D)

Если вы поместите **BIND** в этот порт, вы получите ноль. Если вы не получите ошибку, которая видна в таблице с информацией.

Если программа получит ноль, она вызовет **LISTEN**.

![](/files/-Ljtu2OCPG0g7AdcYS4y)

Здесь мы видим информацию, что один из аргументов - это сокет, а другой - число, называемое **BACKLOG**, которое в нашем случае будет равно нулю.

![](/files/-Ljtu2OVmNJpj-Iedmqu)

Здесь есть нулевое значение, что делает

![](/files/-Ljtu2Ogf61PeLtxW64G)

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

![](/files/-LjtdxF5GoD5hOUcs_cj)

Сокет, который был в локальной переменной **S**, сохранит его в содержимом **P\_S** для использования в родительских функциях

![](/files/-Ljtu2P9eSSiSw68NFNE)

Я дал имя функции.

![](/files/-LjtdxFEAvhO04ZpEft2)

Если возвращается **1**, программа продолжает выполнение здесь.

![](/files/-LjtdxFHwfPyT4UyNOcK)

Первый вызов - эта функция без аргументов.

![](/files/-Ljtu2Pwzzq44_SAY4bM)

Посмотрим что делает эта функция.

![](/files/-Ljtu2Q6xLBEF6aM7xG8)

Мы видим, что она не имеет аргументов, непосредственно резервирует пространство для переменных с помощью **Sub RSP, 0x168**.

![](/files/-Ljtu2QFMdQ0pqvQrJNx)

Мы видим вызов **MEMSET** с аргументом **0x20**. Другими словами должен заполниться буфер, длина которого в аргументе **SIZE** равна **0x100**. Также в **IDA**, если я перехожу к статическому представлению стека, я могу сделать это, дважды щелкнув по **CHARACTER** или любой переменной.

![](/files/-Ljtu2QWyQaTOfcKRmur)

Я вижу пустое пространство в буфере. Если щелкнуть правой кнопкой мыши и выбрать **ARRAY** мне говорится, что длина равна **256**.

![](/files/-Ljtu2QiRynE485tj7da)

Здесь существует буфер с именем **CHARACTER** размером **256** байт или **0x100H**.

![](/files/-LjtdxFhf6QNXkH2RRDG)

Мы видим, что цикл будет повторяться, пока счетчик не достигнет **0x50**.

![](/files/-Ljtu2R7I8l4teSbN26t)

![](/files/-Ljtu2RHvz3KUB5hBkbN)

Ещё раз получается хэндл вывода и вызывается **WRITECONSOLEOUTPUTCHARACTERA**,

Если я запускаю программа, в этом я не вижу записи буфера со значением **0x2020** в каждом слове, так как буфер инициализируется пробелами **0x20**. Это просто нужно для рисования в консоли, Здесь нет ничего интересного нет. Продолжаем.

![](/files/-Ljtu2RSTUTIohu1_Nlq)

Затем устанавливается позиция курсора, обнуляя поля **X** и **Y**, передавая их как **DWORD**, поскольку они являются последовательными, и каждое из них является словом.

![](/files/-LjtdxG5bLs9KVWg7W5h)

Затем создаётся поток, который мы уже проанализировали с помощью **CREATETHREAD**, передав его в качестве начального адреса **STARTADDRESS**. Эта функция уже проанализирована.

![](/files/-Ljtu2RlUlwl9paz4jYt)

После запуска потока, программа напечатает **TCP SERVER ACTIVATED**.

![](/files/-Ljtu2S-7L3Pj7GY-Smx)

Y ya empezaría la parte interesante, normalmente uno en un server buscaría el recv y empezaría a reversear desde allí, pero acá la idea es aprender y ser detallado para practicar, así que seguimos.

![](/files/-LjtdxGJc8YKQYnJCEGL)

Перемещается курсор, так как **Y** теперь равен **1**.

![](/files/-Ljtu2STz7XyRhXQscnN)

Происходит вывод "**WAITING FOR CLIENT CONNECTIONS**" и вызывается функция **ACCEPT**.

![](/files/-LjtdxGSu99jIoT_5s9t)

В этом случае **ACCEPT** разрешает входящее соединение и еще не назначает дескриптор сокета, который прослушивает, как мы уже видели, и здесь будет первый аргумент, но в регистре **RAX** вернется другой дескриптор, который будет дескриптором соединения с этим конкретным клиентом.

![](/files/-LjtdxGYVKXNK99D1OvP)

Здесь находятся 3 аргумента: указатель на **ADDRLEN**, в данном случае **0x10**, указатель на **ADDR**, который получает адрес того, что подключается, и последний - сокет **S**. В регистре **RAX** возвращается дескриптор этого соединения, который был установлен.

![](/files/-Ljtu2T83KVNZTRIjWAV)

Мы видим, что если я запущу сервер и установлю **BP** при возврате **ACCEPT**, то **IDA** не остановится, если я не отправлю правильный пакет на правильный порт, как в этом случае. Здесь отправил пакет и отладчик остановился.

![](/files/-LjtdxGhAYWUI0lO9Yo_)

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

![](/files/-LjtdxGlggjBmqXSGbQ7)

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

![](/files/-LjtdxGoAjHPoH3MWzoh)

Здесь печатается **NEW CONECTION ACCEPTED** и идет переход в функцию в **RECV**, где начинается то что нужно, поскольку, когда она получает данные, которые я могу отправить ей, если в программе есть какая-либо уязвимость, она может на нее повлиять.

Мы видим, что длина того, что вы можете получить, будет максимум **0x1000**, флаги будут равны нулю, то, что я отправлю в буфере **BUF**, будет сохранено, и первым аргументом в **RCX** будет **HANDLE\_CONNECTION**.

![](/files/-LjtdxGuj8l5Z1o5AjR5)

Если все прошло хорошо, возвращаемым значением будет количество полученных байтов.

![](/files/-LjtdxGy-JkuPwuHTWrm)

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

![](/files/-Ljtu2UHfVHUzHHWpaVE)

![](/files/-Ljtu2UUY000xVBdXcVG)

Так что все в порядке, буфер не переполнится.

![](/files/-Ljtu2UfItwVti3_JgHR)

Мы видим, что переменная **ADDRLEN**, которая в **ACCEPT** использовала ее для длины **ADDR**, теперь повторно использует ее для получения количества полученных байтов.

![](/files/-LjtdxHKFw1cygY6M4Gd)

![](/files/-Ljtu2VCm3ycPNItCGd4)

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

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

![](/files/-LjtdxHUaPvWHxmkzvKN)

Она имеет два аргумента. Первый в **ECX** - количество полученных байтов, а второй в **RDX** - указатель на буфер, в котором я храню данные, которые отправляю.

![](/files/-LjtdxHXeakzvNc_XWyr)

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

Если бы я хотел распространить с **SET TYPE**, то делаю так.

> \_\_INT64 \_\_USERCALL FUNCION\_2@\<RAX>(INT CANTIDAD\_BYTES\_RECIBIDOS@\<RCX>, CHAR \* P\_BUF@\<EDX>);

Здесь нужно распространить переменные.

![](/files/-LjtdxHd5qra-9KxbQ4X)

Мы видим, что программа сравнивает с помощью **STRNCMP** первые **6** байтов данных, которые вы отправляете, со строкой "**Hello**".

![](/files/-LjtdxHgyKBtDW8AClh6)

![](/files/-Ljtu2WHy3H1GAk_2L5S)

Если функция возвращает ноль, то они равны, и флаг установлен в **1**.

Поскольку при выходе из этой функции, если она возвращает **1**, происходит рукопожатие, я переименую функцию в **CHECK\_HANDSHAKE**.

![](/files/-LjtdxHorh0RYpLd_uxX)

![](/files/-LjtdxHs5hk7J9Tuw1GG)

Хорошо, как мы видим, если мы передали "**Hello**" будет действительным рукопожатие.

Затем программе возвращает в **SEND** строку, которая начинается с "**Hi** и несколько пробелов" длиной **8**, чтобы не путать с другим **BUF**, который находится в секции данных. Я собираюсь переименовать его, чтобы избежать проблем.

![](/files/-Ljtu2Wy0JPoYaEjJzwd)

![](/files/-LjtdxHz1OcZMPDiVPif)

Теперь смотрится лучше. Длина данных, которые вы мне отправите, будет **8**. Здесь нет проблем.

![](/files/-LjtdxI2U5NfliaraKmz)

Здесь программа сохраняет отправленные байты, затем выводит "**WAITING FOR REQUEST**", происходит вызов функции, а затем соединение с клиентом закрывается, и программа возвращается, чтобы принять ожидание другого, поэтому все должно быть приготовлено в этой последней функции, которая находится до **CLOSESOCKET**.

![](/files/-Ljtu2XbZxxZlKpn-EJG)

Мы видим, что у него есть единственный аргумент - **HANDLE\_CONNECTION**.

После сохранения аргумента резервируется место для переменных

![](/files/-LjtdxIA400dRR6BAv-N)

Тот, кто хочет распространять нужно написать так

> \_\_INT64 \_\_USERCALL FUNCION\_2@\<RAX>(\_\_INT64 HANDLE\_CONEXION@\<RCX>);

![](/files/-Ljtu2Y1Fd2EB9RqbeLk)

Затем вызывается другой **RECV** с тем же **HANDLE\_CONNECTION**, но длиной **0x10**. Давайте посмотрим буфер, в который поступают данные.

![](/files/-Ljtu2YDFBpFYbPUj0tl)

Поскольку размер буфера составляет **0x10** т.е. **16** байтов, он будет включать переменную **BUF** и три переменные **224**, **220** и **21C**.

![](/files/-LjtdxIKM33kEL4yZu6M)

Если я хочу, я могу создать структуру для этого буфера.

Здесь у меня есть **16** байтов длиной с **4 DWORDS**, тогда я увижу, какое конкретное имя поставить каждому из них.

![](/files/-LjtdxIMHxM5gVu30QJf)

И переменную переименую в **MY\_BUF**.

![](/files/-LjtdxIP7CskpFTVMJyI)

Здесь сохраняется количество полученных байтов, которых может быть не более **0x10** т.е. **16**.

![](/files/-Ljtu2Z2sUZxRRqKSdHc)

Сравнивается количество полученных байтов с **0x10**, и оно должно быть именно этим значением, потому что, если оно не меньше, оно будет равно или больше, и выше оно не может быть, потому что у recv было максимум **0x10**, поэтому он принимает только **0x10**.

![](/files/-Ljtu2ZDA2hlPOlkMKwE)

Мы видим, что программа собирается выполнить еще одно **RECV** с тем же **HANDLE\_CONNECTION**, но в этом случае размер равен **CAMPO\_0** того, что я отправил в предыдущем пакете из **16** байтов, и программа сохранит результат в **VAR\_218**, которая будет буфером для этого **RECV**.

![](/files/-Ljtu2ZfNeoPp-sPebg-)

Мы видим, что этот буфер составляет **512** байт, и я мог бы отправить ему больше данных, так как размер **RECV**, который я обрабатываю через **CAMPO\_0**. Проблема заключается в том, что вы перезаписываете **COOKIE**, и это приводит к закрытию программы, и в **64**-битных файлах нет исключения в стеке, так что это пока не приведет к переполнению.

![](/files/-LjtdxIftrDshaCBg5Ms)

![](/files/-LjtdxIoTEYbidLl1bWG)

Также, я переименую переменную в **BUF\_512\_TERCER\_RECV**.

![](/files/-LjtdxItzkiuKgqapZQj)

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

![](/files/-Ljtu2_bbtFVzaDu_22T)

Y bueno quedo largo pero yo me entiendo jeje, con las variables reusadas pasa esto.

![](/files/-Ljtu2_m5hc-TR1sz6xW)

Затем выполняется сравнение со знаком, просто следите, если **FIELD\_3** меньше нуля.

![](/files/-Ljtu2_xMt_faVOnARfX)

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

Затем идет сравнение без знака, только если **CAMPO\_2** меньше или равно **0x200**.

![](/files/-LjtdxJNGRusZbHHvz3b)

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

![](/files/-LjtdxJQKcyXJ-v38Lhj)

Кроме того, сообщения об ошибках дают мне представление об именах полей, **CAMPO\_3** будет смещением, а **CAMPO\_2** будет равно уровню.

![](/files/-LjtdxJXTtdNXi_e9iFp)

Сейчас смотрится лучше.

![](/files/-Ljtu2albkyJt103fABW)

Поле **1** является операцией сравнения с **0x11111111** или **0x22222222**, и если , то программа идет на **INVALID OPERATION**, поэтому мы переименовываем его. Мы видим структуру второго пакета.

Мы видим структуру второго пакета.

![](/files/-Ljtu2awsQtrSfL_Tdlh) Мы видим, что есть две разные операции и **SEND**. Мы должны изучить обе операции, чтобы увидеть, какая из них работает и почему.

![](/files/-LjtdxJlQ2vG00NuA-4y)

В этом компиляторе резервируются **4 QWORDS** над пространством переменных, так что дочерняя функция хранит аргументы, которые передаются через регистры.

![](/files/-Ljtu2bHwveoM4RSgmw8)

Таким образом, поскольку дочерняя функция имеет **5** аргументов, **5**-й будет **VAR\_248**.

Я переименую переменную в **CONST\_0**, так как программа пишет туда ноль.

![](/files/-LjtdxJuOJ6HoqGPVp_6)

Давайте сначала проанализируем операцию **0x22222222**.

![](/files/-Ljtu2bk_kl51Y-BxiQG)

Другие **4** аргумента такие.

> \_\_INT64 \_\_USERCALL OP\_0X22222222@\<RAX>(INT LEVEL\_MENOR\_O\_IGUAL\_QUE\_0X200 @\<ECX>, CHAR \* P\_BUF\_512\_TERCER\_RECV@\<RDX>, INT BYTES\_RECIBIDOS\_3ER\_RECV@\<R8D>, INT OFFSET\_VALOR\_NEGATIVO@\<R9D>);

Здесь распространяются аргументы.

![](/files/-LjtdxK8oiLfJ92eFGxy)

Дело в том, что это рекурсивная функция, одинаковая в каждом вызове.

![](/files/-Ljtu2cB3WuIZdCz1Jbo)

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

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

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

![](/files/-LjtdxKKxY7xGM0BIbpz)

Это всё будет повторяться, ничего не делая, пока уровень не уменьшится и не достигнет нуля.

![](/files/-LjtdxKO0Pen73S4ZEje)

В этом случае программа перейдёт к этому блоку, где вы перепишите с помощью **OK** на некоторую часть стека выше **BUFFER\_512\_TERCER\_RECV**, так как указатель на него добавляет отрицательное смещение.

Мы видим, что **OK** - это строка из **2** байтов и конечного нуля.

![](/files/-Ljtu2cs43YNdnqo51Q4)

Мы видим, что над буфером находится переменная **CONST\_0** в родительской функции.

![](/files/-LjtdxKY1V2d7o0ZRU9z)

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

![](/files/-LjtdxKa371PZvGfQtxv)

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

Дело в том, что переменная **CONST\_0** всегда будет равна **0** и программа не будет переходить к функции **STRNCAT**, которая будет пропущена, до уровня **0X1FF**, который был выполнен первым и который имеет переменную **CONST\_0**, и которую мы перезаписали с помощью **OK**, поэтому существует только один раз перейти к функции **STRNCAT**.

![](/files/-Ljtu2d_wCWd7cmOE7j8)

Этот уровень будет **0x200**, так как это максимальный уровень и он будет счетчиком **STRNCAT**, поэтому вы должны попытаться отправить максимально возможное значение, в данном случае **0x200**, чтобы объединить много данных.

![](/files/-Ljtu2dhToD6jFbxsge0)

**SOURCE** - это указатель на пакет, отправленный **RECV** ему в третьем параметре, который будет скопирован после строки **OK**, поэтому я могу перейти к переменным в стеке, которые находятся ниже **CONST\_0**, включая очень ценную переменную, такую как размер **SEND**.

![](/files/-Ljtu2dqIH6_EDQlflsK)

Если я перезапишу этот размер с большим значением, чем у меня, я мог бы получить утечку из стека, включая **COOKIE**, и адрес возврата, который позволит мне в следующем пакете отправить длину, поскольку исполняемый файл имеет **ASLR**.

Прежде чем мы начнем делать скрипт, я должен изучить другую функцию **OP\_0X11111111**, чтобы увидеть, что произойдет, если я выберу её вместо **OP\_0X22222222**.

![](/files/-Ljtu2e3tAlbEpNIg8l4)

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

> \_\_INT64 \_\_USERCALL OP\_0X11111111@\<RAX>(INT LEVEL\_MENOR\_O\_IGUAL\_QUE\_0X200 @\<ECX>, CHAR \* P\_BUF\_512\_TERCER\_RECV@\<RDX>, INT BYTES\_RECIBIDOS\_3ER\_RECV@\<R8D>, INT OFFSET\_VALOR\_NEGATIVO@\<R9D>);

Здесь распространяются значения.

![](/files/-LjtdxKwacjRcLUNy5Ne)

Мы видим, что так же, как в **OP\_0X22222222** программа будет повторять ту же функцию, только изменяя уровень, который уменьшается. В то время как он будет повторяться до **0x200** раз в зависимости от уровня, который мы установили, и по мере его снижения счетчик достигнет ноль. Когда он это сделает, программа перейдет к **STRCPY**.

![](/files/-LjtdxL4c8XQato-nYhB)

Здесь программа достигает функции **STRCPY** с уровнем равным **0**.

![](/files/-Ljtu2ef7uPslmXUVj8a)

В этом случае программа не добавляет отрицательное смещение к **DESTINO**, поэтому программа будет писать только **OK** в начале третьего буфера пакета.

Также следует отметить, что строка **OK** не совпадает с функцией **0x22222222**, она имеет длину пять символов и имеет три точки после "**OK…**".

Здесь есть обе строки.

![](/files/-LjtdxLF-Tw6eww8ATrp)

![](/files/-Ljtu2f8rxkP09XSDrRr)

Затем, возвращаясь к адресу возврата при каждом повторения, программа переходит к функции **STRNCAT**.

![](/files/-LjtdxLR3QEzl4GXW6WJ)

![](/files/-LjtdxLVi4lM1PqkGje8)

**DESTINATION** будет буфером **THIRD\_RECV**, где в начале будет "**OK…**" куда я копирую в функцию **strcpy**, но после того, как эта строка заканчивается, это будет зависеть от того, что мы указываем в **SOURCE**, который будет тем же указателем плюс отрицательное смещение. Другими словами, мы можем добавить данные из верхней части буфера и вставить их в конце.

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

Также может случиться утечка, так как строка, которая будет в **DEST** в буфере, будет маленькой (для строки "**OK ..**.", которую я копирую, будет только **5** байтов, и она заканчивается нулем, что сделает ее зависимой от того, что находится над буфером, например, адрес возврата повторений, хотя не **COOKIE**, он не будет выше буфера.

Мы также помним, что в случае функции **OP\_0X22222222** программа только один раз прибыла бы в функцию **STRNCAT**. В случае **OP\_0X11111111** необходимо помнить, что когда она уменьшается и достигает уровня **0**, программа идет в функцию **STRCPY** и оттуда, поскольку она не зависит ни от каких **CONST\_0** во всех выходах всех уровней программа переходит в функцию **STRNCAT**.

![](/files/-Ljtu2fm3aVk56F38wpi)

Кроме того, поскольку количество, которое копирует в **COUNT**, является значением текущего уровня, и оно будет уменьшено, скопировано на уровень **1** будет только **1** байт. Далее, когда программа выйдет из **LEVEL2\_COPIARA** и так далее, до последнего уровня, который скопирует **0x1ff** и затем уже перейдет к отцовской функции.

Очевидно, что скопируется это количество байтов если нет нулей, потому что копируются строки, если уровень равен **0x150**, и есть ноль, копируюся только из **SOURCE** в ноль, а не в полный размер.

Хорошо. Таким образом, существует много возможностей. Мы можем сделать утечку данных на первом этапе и получить **COOKIE** и адреса исполняемых файлов, которые позволят нам переполнить во втором пакете и выполнить **ROP** и записать флаг, чтобы избежать утечки средств и запустить **CALC**.

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