
I think that most of the local inhabitants are familiar with the concept of a
sniffer . Despite the fact that they have the same goal (interception of packets that meet certain criteria), they achieve it in a completely different way. Some software listens to the specified network interface (for example,
Wireshark , where it is implemented using the
Pcap library), and some - intercepts calls of those responsible for interacting with the network of WinAPI-functions. Both that and the other method have their pros and cons, however, if the task requires the interception of packets from a specific previously known application, then the second option is usually tritely more convenient. In this case, there is no need to find out the IP addresses and ports that this program uses (especially considering the fact that there can be quite a lot of them), and you can simply say “I want to intercept all the packages of this application”. Convenient, isn't it?
Perhaps the most popular sniffer to date, working on the principle of interception of calls of certain WinAPI-functions, is
WPE Pro . Perhaps, many of you have heard about it in various forums dedicated to online games, because this sniffer is used in most cases to get advantages in various games. It performs its task perfectly, but it has one unpleasant drawback - it does not know how to work with 64-bit applications. It just so happened that according to one of the arisen tasks, I just needed to intercept packets from a 64-bit application, and I looked in the direction of Wireshark. Unfortunately, it was not very convenient to use it in this situation - the application under study sent data to different IP addresses, each time opening a new port. Googling a bit, I found that there are no ready-made analogues of WPE Pro with x64 support (if there are any, I would appreciate links in the comments - note that we are talking about Windows). The WPE Pro author did not leave any contact details on the official website and in the sniffer itself, so I decided to sort out this issue on my own.
')
How was the process and what came of it, read under the cut.
So what needs to be done first? Right, download WPE Pro yourself. This can be done on the
official website of the sniffer, where two versions are offered for download at once - 0.9a and 1.3. We will consider version 0.9a, because it works on the latest versions of Windows.
Downloaded? Now let's check if he is covered by some kind of packer or protector:


Looks like we won't have to shoot anything this time. Then we take
OllyDbg and download “WpePro.net.exe”. For example, let's run the 64-bit version of
Dependency Walker and find out why WPE Pro cannot display it in the list of processes available for interception.
Get a list of current processes in WinAPI in two main ways:
If you wish, you can read about the difference between these methods, for example,
here .
We look at the intermodular calls of the “WpePro.net.exe” module and see that there is neither
CreateToolhelp32Snapshot nor
EnumProcesses here. Perhaps the application gets their address at run-time using the WinAPI function
GetProcAddress , so let's look at the referenced text strings. At this time, everything is exactly the opposite - it was found as the string “CreateToolhelp32Snapshot” and “EnumProcesses”. Place the breakpoints in the places where the data lines are accessed, click on the “Target program” button in WPE Pro and look at the place where we stopped:

If you press F9, then we will see that the same breakout is triggered several more times before finally a window appears with a list of current processes. Let's see why Dependency Walker was not in it. We close the window, press the “Target program” button again and perform step-by-step debugging, starting from the same bryak. Shortly after the calls to
GetProcAddress, we find ourselves in a loop in which the following functions are sequentially called -
OpenProcess ,
EnumProcessModules , hiding behind the
CALL DWORD PTR DS instruction
: [EDI + 10] ,
GetModuleFileNameEx (
CALL DWORD PTR DS instruction
: [ECX + 14] ) and
CloseHandle :

Let's put the breakpoint at
0x0042A910 , where the last argument of the
OpenProcess function, PID, is
pushed onto the stack and try to wait until the
EAX register takes on a value equal to the Dependency Walker process identifier. On my machine at that moment it was equal to 6600, i.e. 0x19C8.
If we run through the code, we will see that in the case of a Dependency Walker, the
TEST EAX, EAX instruction located at
0x0042A942 and following the call to the WinAPI function
EnumProcessModules causes the following conditional jump operator to jump to
0x0042A9E7 where the call is located
CloseHandle , which, of course, is not the normal course of the application, because we have not even reached the call to the
GetModuleFileNameEx function:

Note the error code - 0x12B (ERROR_PARTIAL_COPY). Let's see why this can happen. Open the documentation for the function
EnumProcessModules and see the following:
If this is a 32-bit application running on a WOW64, it can only be enumerate. If this is a process, it’s ERROR_PARTIAL_COPY (299)
Yes, this is our case.
Despite the fact that the documentation for the function
GetModuleFileNameEx does not say anything similar, it behaves in a similar way, as described, for example,
here . One solution to this problem is to use the
QueryFullProcessImageName function, which will work fine even if called from a 32-bit application running in WOW64, if applied to a 64-bit process.
Now we can set the function call
EnumProcessModules0042A93F . 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
and write code cave to replace
GetModuleFileNameEx with the
QueryFullProcessImageName function:
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
Now WPE Pro displays many new processes, including our Dependency Walker:

However, when trying to attach to this process, WPE Pro gives an unpleasant message for us:

Here we, unfortunately, can do nothing.
DLL injection is performed by
injecting its DLL into the target-process address space, and the following is
stated on MSDN:
On 64-bit Windows, a 64-bit process cannot be loaded a 32-bit dynamic-link library (DLL). Additionally, a 32-bit process cannot be loaded a 64-bit DLL
It is easy to guess that WPE Pro comes only with a 32-bit library.
What to do? Write your WinSock interceptor? Why not?
But how will we do this? The first thing that comes to mind is to use
Detours , but the official site immediately states that support for 64-bit applications is only in the Professional Edition:
Detours Express is limited to 32-bit processes on x86 processors
Then let's try
EasyHook . Among other things, this library just allows you to work with 64-bit applications:
You must be able to compile any code, and you will be able to
With minor changes to the example shown in the official
tutorial , we get code that intercepts calls to the
recv and
send functions from WinSock and outputs the intercepted messages byte-by-byte in hexadecimal:
The code of the program making the DLL injection 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); } } } }
The code of the DLL itself that will be injected into the target process 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); } } }
Of course, there is still something to work on:
- First, there is no interception of WSARecv , WSASend and some other functions that can be used in target applications.
- Second, the lack of a GUI and “string” representation of intercepted messages
- etc
However, the functionality demonstrated here was quite enough to solve the task set before me, and the rest is left to the discretion of the readers.
Afterword
Research and modification of binary files does not always give an instant result, as was observed in my previous articles (if interested, read about it
here and
here ), so sometimes you have to go back to the very beginning and change your approach to solving a problem. But there is absolutely nothing abnormal in this - as you know, any experience is useful, and reverse engineering is no exception.
Thank you for your attention, and again I hope that the article was useful to someone.