Разбираемся с поддержкой x64 в WPE Pro

от автора

image

Думаю, что большинство из местных обитателей знакомы с понятием сниффера. Несмотря на то, что конечная цель у них одна и та же (перехват пакетов, соответствующих определённым критериям), достигают они её совершенно разным образом. Какой-то софт слушает указанный сетевой интерфейс (например, Wireshark, где это реализовано при помощи библиотеки Pcap), а какой-то — перехватывает вызовы ответственных за взаимодействие с сетью WinAPI-функций. И у того, и у другого метода есть свои плюсы и минусы, однако если по задаче необходим перехват пакетов от конкретного заранее известного приложения, то второй вариант, как правило, банально удобнее. В этом случае нет нужды узнавать IP-адреса и порты, которые использует данная программа (особенно учитывая тот факт, что их может быть довольно много), и можно просто сказать «я хочу перехватывать все пакеты вот этого приложения». Удобно, не правда ли?

Пожалуй, самым популярным на сегодняшний день сниффером, работающим по принципу перехвата вызовов определённых WinAPI-функций, является WPE Pro. Возможно, многие из вас слышали о нём на различных форумах, посвящённых онлайн-играм, ведь именно для получения преимуществ в различных играх этот сниффер в большинстве случаев и используется. Свою задачу он выполняет прекрасно, однако у него есть один неприятный недостаток — он не умеет работать с 64-битными приложениями. Так уж вышло, что по одной из возникших задач мне как раз понадобилось перехватывать пакеты от 64-битного приложения, и я посмотрел в сторону Wireshark. К сожалению, использовать его в данной ситуации было не очень удобно — исследуемое приложение отправляло данные на разные IP-адреса, каждый раз открывая новый порт. Погуглив немного, я обнаружил, что готовых аналогов WPE Pro с поддержкой x64 нет (если они всё же есть, буду признателен за ссылки в комментариях — обратите внимание, что речь идёт о Windows). Автор WPE Pro не оставил никаких контактных данных на официальном сайте и в самом сниффере, так что я принял решение разобраться в этом вопросе самостоятельно.

Как протекал процесс и что из этого вышло, читайте под катом.

Итак, что необходимо сделать в первую очередь? Верно, скачать сам WPE Pro. Сделать это можно на официальном сайте сниффера, где предлагаются для загрузки сразу две версии — 0.9a и 1.3. Мы будем рассматривать версию 0.9a, потому что именно она работает на последних версиях Windows.

Скачали? Теперь давайте проверим, не накрыт ли он каким-нибудь паковщиком или протектором:

image

image

Похоже, на этот раз снимать нам ничего не придётся. Тогда берём в руки OllyDbg и загружаем «WpePro.net.exe». Давайте для примера запустим 64-битную версию Dependency Walker’а и узнаем, почему WPE Pro не может отобразить его в списке доступных для перехвата процессов.

Получить список текущих процессов в WinAPI можно двумя основными путями:

При желании можете почитать о разнице между данными способами, например, тут.

Смотрим на intermodular calls модуля «WpePro.net.exe» и видим, что ни CreateToolhelp32Snapshot, ни EnumProcesses тут нет. Возможно, приложение получает их адрес в run-time при помощи WinAPI-функции GetProcAddress, так что давайте посмотрим на referenced text strings. На этот раз всё с точностью наоборот — нашлась как строка «CreateToolhelp32Snapshot», так и «EnumProcesses». Ставим бряки на места, где происходит обращение к данным строкам, нажимаем на кнопку «Target program» в WPE Pro и смотрим на место, на котором мы остановились:

image

Если понажимать F9, то мы увидим, что этот же бряк срабатывает ещё несколько раз, прежде чем наконец появится окно со списком текущих процессов. Давайте посмотрим, почему в нём не оказалось Dependency Walker’а. Закрываем окно, снова нажимаем на кнопку «Target program» и выполняем пошаговую отладку, начиная с того же самого бряка. Вскоре после вызовов GetProcAddress мы попадаем в цикл, в котором последовательно вызываются следующие функции — OpenProcess, EnumProcessModules, скрывающаяся за инструкцией CALL DWORD PTR DS:[EDI+10], GetModuleFileNameEx (инструкция CALL DWORD PTR DS:[ECX+14]) и CloseHandle:

image

Давайте поставим бряк по адресу 0x0042A910, где происходит занесение на стек последнего аргумента функции OpenProcess — PID, и попробуем дождаться момента, когда регистр EAX примет значение, равное идентификатору процесса Dependency Walker’а. На моей машине в этот момент он был равен 6600, т.е. 0x19C8.

Если пробежаться по коду, то мы увидим, что в случае Dependency Walker’а инструкция TEST EAX,EAX, находящаяся по адресу 0x0042A942 и следующая за вызовом WinAPI-функции EnumProcessModules, заставляет следующий за ней оператор условного перехода прыгнуть по адресу 0x0042A9E7, где находится вызов CloseHandle, что, разумеется, не является нормальным ходом работы приложения, ведь мы ещё даже не дошли до вызова функции GetModuleFileNameEx:

image

Обратите внимание на код ошибки — 0x12B (ERROR_PARTIAL_COPY). Давайте посмотрим, почему такое может происходить. Открываем документацию к функции EnumProcessModules и видим следующее:

If this function is called from a 32-bit application running on WOW64, it can only enumerate the modules of a 32-bit process. If the process is a 64-bit process, this function fails and the last error code is ERROR_PARTIAL_COPY (299)

Да, это как раз наш случай.

Несмотря на то, что в документации к функции GetModuleFileNameEx не сказано ничего похожего, ведёт она себя аналогичным образом, что описано, например, тут. Одним из вариантов решения этой проблемы является использование функции QueryFullProcessImageName, которая будет отрабатывать нормально даже в случае вызова из 32-битного приложения, запущенного в WOW64, в случае применения её к 64-битному процессу.

Теперь мы можем занопить вызов функции EnumProcessModules

0042A93F   .  FF57 10       CALL DWORD PTR DS:[EDI+10]               ;  EnumProcessModules 0042A942   .  85C0          TEST EAX,EAX 0042A944      90            NOP 0042A945      90            NOP 0042A946      90            NOP 0042A947      90            NOP 0042A948      90            NOP 0042A949      90            NOP 0042A94A   .  8B4424 14     MOV EAX,DWORD PTR SS:[ESP+14] 0042A94E   .  BE 00000000   MOV ESI,0 

и написать code cave для замены GetModuleFileNameEx функцией QueryFullProcessImageName:

0042A971     /E9 DA3F0600   JMP WpePro_n.0048E950 0042A976     |90            NOP 0042A977     |90            NOP 0042A978     |90            NOP 0042A979     |90            NOP                                      ;  GetModuleFileNameEx 0042A97A     |90            NOP 0042A97B     |90            NOP 0042A97C     |90            NOP 0042A97D     |90            NOP 0042A97E     |90            NOP 0042A97F     |90            NOP 0042A980     |90            NOP 0042A981     |90            NOP 0042A982     |90            NOP 0042A983     |90            NOP 0042A984     |90            NOP 0042A985     |90            NOP 0042A986     |90            NOP 0042A987     |90            NOP 0042A988     |90            NOP 0042A989     |90            NOP 0042A98A     |90            NOP 0042A98B     |90            NOP 0042A98C     |90            NOP 0042A98D     |90            NOP 0042A98E   > |6A 14         PUSH 14 0042A990   . |E8 7FCF0300   CALL WpePro_n.00467914 

0048E950      60            PUSHAD 0048E951      9C            PUSHFD 0048E952      8D5C24 AC     LEA EBX,DWORD PTR SS:[ESP-54] 0048E956      C74424 AC 040>MOV DWORD PTR SS:[ESP-54],104 0048E95E      53            PUSH EBX 0048E95F      50            PUSH EAX 0048E960      6A 00         PUSH 0 0048E962      55            PUSH EBP 0048E963      E8 777CB675   CALL KERNEL32.QueryFullProcessImageNameA 0048E968      9D            POPFD 0048E969      61            POPAD 0048E96A    ^ E9 1FC0F9FF   JMP WpePro_n.0042A98E 

Теперь WPE Pro отображает множество новых процессов, в том числе и наш Dependency Walker:

image

Однако при попытке приаттачиться к данному процессу WPE Pro выдаёт неприятное для нас сообщение:

image

Тут мы уже, к сожалению, ничего не можем поделать. DLL-инъекция осуществляется путём внедрения своей DLL в адресное пространство target-процесса, а на MSDN сказано следующее:

On 64-bit Windows, a 64-bit process cannot load a 32-bit dynamic-link library (DLL). Additionally, a 32-bit process cannot load a 64-bit DLL

Несложно догадаться, что WPE Pro поставляется только с 32-битной библиотекой.

Что же делать? Писать свой перехватчик WinSock? А почему бы и нет?

Но как мы это будем осуществлять? Первое, что приходит на ум — это воспользоваться Detours, однако на официальном сайте сразу же сообщается, что поддержка 64-битных приложений есть только в Professional Edition:

Detours Express is limited to 32-bit processes on x86 processors

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

You will be able to write injection libraries and host processes compiled for AnyCPU, which will allow you to inject your code into 32- and 64-Bit processes from 64- and 32-Bit processes by using the very same assembly in all cases

С небольшими изменениями примера, продемонстрированного в официальном туториале, получаем код, перехватывающий вызовы функций recv и send из WinSock и выводящий перехваченные сообщения побайтово в шестнадцатеричном виде:

Код программы, совершающей DLL-инъекцию

using EasyHook; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.Remoting; using System.Text; using System.Threading.Tasks;  namespace Sniffer {     public enum MsgType { Recv, Send };      [Serializable]     public class Message     {         public byte[] Buf;         public int Len;         public MsgType Type;     }      public class InjectorInterface : MarshalByRefObject     {         public void OnMessages(Message[] messages)         {             foreach (Message message in messages)             {                 switch (message.Type)                 {                     case MsgType.Recv:                         Console.WriteLine("Received {0} bytes via recv function", message.Len);                         break;                      case MsgType.Send:                         Console.WriteLine("Sent {0} bytes via send function", message.Len);                         break;                      default:                         Console.WriteLine("Unknown action");                         continue;                 }                  foreach (byte curByte in message.Buf)                 {                     Console.Write("{0:x2} ", curByte);                 }                  Console.WriteLine("=====================");             }         }          public void Print(string message)         {             Console.WriteLine(message);         }          public void Ping()         {          }     }      class Program     {         static void Main(string[] args)         {             if (args.Length != 1)             {                 Console.WriteLine("Usage: Sniffer.exe [pid]");                 return;             }              int pid = Int32.Parse(args[0]);              try             {                 string channelName = null;                 RemoteHooking.IpcCreateServer<InjectorInterface>(ref channelName, WellKnownObjectMode.SingleCall);                  RemoteHooking.Inject(                    pid,                    InjectionOptions.DoNotRequireStrongName,                    "WinSockSpy.dll",                    "WinSockSpy.dll",                     channelName);                  Console.ReadLine();             }             catch (Exception ex)             {                 Console.WriteLine("An error occured while connecting to target: {0}", ex.Message);             }         }     } } 

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

using EasyHook; using Sniffer; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks;  namespace WinSockSpy {     public class Main : EasyHook.IEntryPoint     {         public Sniffer.InjectorInterface Interface;         public LocalHook RecvHook;         public LocalHook SendHook;         public LocalHook WSASendHook;         public Stack<Message> Queue = new Stack<Message>();          public Main(RemoteHooking.IContext InContext, String InChannelName)         {             Interface = RemoteHooking.IpcConnectClient<Sniffer.InjectorInterface>(InChannelName);         }          public void Run(RemoteHooking.IContext InContext, String InChannelName)         {             try             {                 RecvHook = LocalHook.Create(                     LocalHook.GetProcAddress("Ws2_32.dll", "recv"),                     new DRecv(RecvH),                     this);                  SendHook = LocalHook.Create(                     LocalHook.GetProcAddress("Ws2_32.dll", "send"),                     new DSend(SendH),                     this);                  RecvHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });                 SendHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });             }             catch (Exception ex)             {                 Interface.Print(ex.Message);                 return;             }              // Wait for host process termination...             try             {                 while (true)                 {                     Thread.Sleep(500);                      if (Queue.Count > 0)                     {                         Message[] messages = null;                         lock (Queue)                         {                             messages = Queue.ToArray();                             Queue.Clear();                         }                                                  Interface.OnMessages(messages);                     }                     else                     {                         Interface.Ping();                     }                 }             }             catch (Exception)             {                 // NET Remoting will raise an exception if host is unreachable             }         }          //int recv(         //  _In_  SOCKET s,         //  _Out_ char   *buf,         //  _In_  int    len,         //  _In_  int    flags         //);         [DllImport("Ws2_32.dll")]         public static extern int recv(             IntPtr s,             IntPtr buf,             int len,             int flags         );          //int send(         //  _In_       SOCKET s,         //  _In_ const char   *buf,         //  _In_       int    len,         //  _In_       int    flags         //);         [DllImport("Ws2_32.dll")]         public static extern int send(             IntPtr s,             IntPtr buf,             int len,             int flags         );          [UnmanagedFunctionPointer(CallingConvention.StdCall,             CharSet = CharSet.Unicode,             SetLastError = true)]         delegate int DRecv(             IntPtr s,             IntPtr buf,             int len,             int flags         );          [UnmanagedFunctionPointer(CallingConvention.StdCall,             CharSet = CharSet.Unicode,             SetLastError = true)]         delegate int DSend(             IntPtr s,             IntPtr buf,             int len,             int flags         );          static int RecvH(             IntPtr s,             IntPtr buf,             int len,             int flags)         {             Main This = (Main)HookRuntimeInfo.Callback;             lock (This.Queue)             {                 byte[] message = new byte[len];                 Marshal.Copy(buf, message, 0, len);                 This.Queue.Push(new Message { Buf = message, Len = len, Type = MsgType.Recv });             }              return recv(s, buf, len, flags);         }          static int SendH(             IntPtr s,             IntPtr buf,             int len,             int flags)         {             Main This = (Main)HookRuntimeInfo.Callback;             lock (This.Queue)             {                 byte[] message = new byte[len];                 Marshal.Copy(buf, message, 0, len);                 This.Queue.Push(new Message { Buf = message, Len = len, Type = MsgType.Send });             }              return send(s, buf, len, flags);         }     } } 

Конечно, тут ещё есть, над чем поработать:

  • Во-первых, отсутствует перехват WSARecv, WSASend и некоторых других функций, которые могут использоваться в целевых приложениях
  • Во-вторых, отсутствие GUI и «строкового» представления перехваченных сообщений
  • etc

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

Послесловие

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

Спасибо за внимание, и снова надеюсь, что статья оказалась кому-нибудь полезной.

ссылка на оригинал статьи http://habrahabr.ru/post/259459/


Комментарии

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

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