Jump to content
Moopler
Sign in to follow this  
Roast

Help Hooking ws2_32.dll send/recv functions

Recommended Posts

tl;dr I'm trying to hook ws2_32.dll send and recv functions, but upon injecting my DLL it just crashes. I'm not sure what I'm fucking up here, please help.

It's been a hell of a long time since I've worked with C++ at all. It looks like I have too many gaps in my knowledge to jump straight in to Maplestory, so I've decided to opt for exploring a simple MMORPG with no anti cheat and no encryption that I'm aware of yet. I'm getting to grips with the freeware of IDA 7.0 and ollydbg and working through the struggles as best I can.

I've gone in to IDA Pro and had a look in the imports tab to see if I can find anything useful. Sure enough there's an import for send and receive from ws2_32.dll

image.png.a124bc5b17aa1d5ca366b93e53eca2de.png

That lists the address as .idata:00D339FC. If I look for it in ollydbg while the app is running, it lists the address of the start of the send function as 0x752B5E40.image.png.d918d1af2422893ce2d0d9458b8e6c3c.png 

Just to confirm, if I go to that address in Cheat Engine's memory viewer, it shows that address as WS2_32.send:

image.png.91f4a6742860059965a4ae1ccaf0305d.png

So I have the address of the send function in memory and I've been trying to get to grips with hooking the function. There's a lot of resources online that I've been reading through but can't seem to get it right and I'm too noob to figure it out.

My project is a simple barebones C++ dll. Basic entry point.

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID reserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		HookApiFunction("ws2_32.dll", "send", SendHook, hook);

		return 0;
	}
}

SendHook is super basic, all I want to do for now is to log packets to file. Once I get my head around this I'll work on making GUI for displaying the inbound/outbound packets.

int WINAPI SendHook(SOCKET s, const char* buf, int len, int flags)
{
	std::ofstream myfile;
	myfile.open(<PathToFile>);
	myfile << len << " " << buf << " " << "\n";
	myfile.close();
	return send(s, buf, len, flags);
}

In HookApiFunction, I load the library and call GetProcAddress to find the address of the function in memory.

HINSTANCE library = LoadLibrary(Module);
DWORD FunctionAddress = (DWORD)GetProcAddress(library, Function);
DWORD MyFunctionAddress = (DWORD)MyFunction;

Then attempt to redirect the send function to my SendHook function and then continue with the original function.

DWORD jumpAddress = (MyFunctionAddress - FunctionAddress) - 5;
memcpy(&jumpBytes[1], &jumpAddress, 4);

DWORD dwProtect;
VirtualProtect((LPVOID)FunctionAddress, 6, PAGE_EXECUTE_READWRITE, &dwProtect);
WriteProcessMemory(GetCurrentProcess(), (LPVOID)FunctionAddress, jumpBytes, 6, 0);
VirtualProtect((LPVOID)FunctionAddress, 6, dwProtect, &dwProtect);

Everything compiles fine, but judging by when I crash, it happens when the send function is called. I can inject at the login screen for example, be fine for a few seconds until what I assume is some keepalive packet is sent.

Thanks in advance for any help.

Edited by Roast

Share this post


Link to post

I didn't actually bother reading through the code-snippets to check for minor mistakes, but another good method is to use Cheat Engine to read the address you're trying to hook ; Does it do what you want it to do?

If not, you've got an issue in the hooking logic. If it does in fact do what you want (turn the instructions into a jmp to the function), then you can check the hook for errors.

Upon a quick glance on the function you use, I see the error pretty clearly: 

You hook "send" (the exported api from ws2_32), but in your hook you call 'send'. In other words, you're calling your own hook from inside the hook, resulting in a never-ending loop.

  • Like 2

Share this post


Link to post
18 minutes ago, NewSprux2.0? said:

I didn't actually bother reading through the code-snippets to check for minor mistakes, but another good method is to use Cheat Engine to read the address you're trying to hook ; Does it do what you want it to do?

If not, you've got an issue in the hooking logic. If it does in fact do what you want (turn the instructions into a jmp to the function), then you can check the hook for errors.

Upon a quick glance on the function you use, I see the error pretty clearly: 

You hook "send" (the exported api from ws2_32), but in your hook you call 'send'. In other words, you're calling your own hook from inside the hook, resulting in a never-ending loop.

Thanks for the quick response!

If I'm understanding correctly, because I'm calling this winsock function

return send(s, buf, len, flags);

inside SendHook, it then retriggers my hook because I've modified send to trigger my SendHook. Would I not then expect to see at least something in my text file before it crashes? The text file remains empty, so I didn't consider this. What you've said makes sense to me though, so thank you :)

I'll revisit this when I'm home again and I'll take a look at it through CheatEngine to make sure it's doing what I think it's doing.

Share this post


Link to post

So I've revisited this tonight and had a look at it through Cheat Engine as @NewSprux2.0? suggested.

It looks like the changes are written roughly as I'd expect.

 image.png.2669fb30f98f8a1f5559690d31a31589.png

But I've cocked up the bytes that I'm writing? Going to the address 0x6E501120, there's just nothing there.

image.png.b6e54c4ec456afa8c75d8ca9a9d3e1b1.png

So the jump is invalid, there's no memory there to jump to and so it instantly crashes... So the first problem has to be in here somewhere I assume.

HINSTANCE library = LoadLibrary(Module);
DWORD FunctionAddress = (DWORD)GetProcAddress(library, Function);
DWORD MyFunctionAddress = (DWORD)MyFunction;

BYTE jumpBytes[6] = { 0xE9,0x00,0x00,0x00,0x00,0xC3 };

DWORD jumpAddress = (MyFunctionAddress - FunctionAddress) - 5;
memcpy(&jumpBytes[1], &jumpAddress, 4);

The only thing that comes to mind so far is that I'm miscalculating jumpAddress, but I don't know how I've messed that up. I'll make some further attempts at debugging in the morning.

EDIT: I've just realised I'm overwriting either too many or too few bytes... I'm too tired to tell which.

Edited by Roast

Share this post


Link to post

The problem is that you calculate the address for a local function then write the jump to a remote process where the local function doesn't exist.

Edited by NewSprux2.0?

Share this post


Link to post

I too am too lazy to actually read through your posts, well except for the fact that you're writing 6 bytes when only 5 is needed, I suggest using detours as it will do everything for you in this context without you needing to think at all.

BOOL SetHook(__in BOOL bInstall, __inout PVOID* ppvTarget, __in PVOID pvDetour)
{
	if (DetourTransactionBegin() != NO_ERROR)
		return FALSE;

	if (DetourUpdateThread(GetCurrentThread()) == NO_ERROR)
		if ((bInstall ? DetourAttach : DetourDetach)(ppvTarget, pvDetour) == NO_ERROR)
			if (DetourTransactionCommit() == NO_ERROR)
				return TRUE;

	DetourTransactionAbort();
	return FALSE;
}

 

Example detour from Benny's bypass loader, just replace the API in question:

BOOL Detour__CreateMutexA()
{
	static decltype(&CreateMutexA) _CreateMutexA = &CreateMutexA;

	decltype(&CreateMutexA) CreateMutexA_hook = [](LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName) -> HANDLE
	{
		if (lpName)
		{
			static std::string mutex_name = "WvsClientMtx" + std::to_string(GetCurrentProcessId());

			if (!strcmp(lpName, "WvsClientMtx"))
			{
				lpName = mutex_name.c_str();

				std::vector<char> file_path(MAX_PATH);

				if (GetModuleFileNameA(GetModuleHandle(NULL), &file_path[0], MAX_PATH))
				{
					std::string temp = std::string(&file_path[0]);

					if (!LoadLibraryA((temp.substr(0, temp.find_last_of('\\') + 1) + "NexonGameThreat.dll").c_str()))
						MessageBoxA(NULL, "Failed to load NexonGameThreat.dll", "Loading failure", MB_OK | MB_ICONERROR | MB_TOPMOST | MB_SETFOREGROUND);
				}
			}
		}

		return _CreateMutexA(lpMutexAttributes, bInitialOwner, lpName);
	};

	return SetHook(TRUE, reinterpret_cast<void**>(&_CreateMutexA), CreateMutexA_hook);
}

 

Or an example hooking a maple function:

typedef void(__fastcall *CWndMan_t)(void *ecx, void *edx, HWND m_hWnd);
auto CWndMan = reinterpret_cast<CWndMan_t>(0x024EFC40);

void __fastcall CWndMan_Hook(void *ecx, void *edx, HWND m_hWnd)
{
	std::cout << "\r >>> CWndMan::CWndMan() called. Logging Maple hWnd. \n [$] ";
	m_hWnd_Maple = m_hWnd;
	
  	/* also grabbing thread id and writing some shit to a pipe but example is simplified */
  
	CWndMan(ecx, edx, m_hWnd);
}

/* -- */
SetHook(true, reinterpret_cast<PVOID*>(&CWndMan), &CWndMan_Hook);

 

Share this post


Link to post
41 minutes ago, NewSprux2.0? said:

@Erotica he uses WriteProcessMemory so I think he's writing from a remote procesS

Why inject a dll and call the function in dllmain then o.o

 

plus he writes to same process

WriteProcessMemory(GetCurrentProcess()
Edited by Erotica

Share this post


Link to post
1 hour ago, Erotica said:

Why inject a dll and call the function in dllmain then o.o

 

plus he writes to same process


WriteProcessMemory(GetCurrentProcess()

Haha didn't notice, my bad ;)

Share this post


Link to post
8 hours ago, Erotica said:

I too am too lazy to actually read through your posts, well except for the fact that you're writing 6 bytes when only 5 is needed, I suggest using detours as it will do everything for you in this context without you needing to think at all.

Ok, sweet. I'm set up with Microsoft's detours library, but I'm having a similar problem to before where the jump is being set to empty memory.

image.png.4a9862915fa4daf0001197808b607fcd.pngimage.png.6234a76a62a6cd00a8ef6683759600ee.png

I ended up trying to apply the 2nd example first as it was a lot simpler to read and understand. Here's what I've tried to do, which is very similar to before.

typedef int(*WINAPI ws2Send_t)(SOCKET s, const char* buf, int len, int flags);
auto ws2Send = reinterpret_cast<ws2Send_t>(0x752B5E40); // I assumed this is meant to be the address of the function I'm hooking.
void WINAPI SendHook(SOCKET s, const char* buf, int len, int flags)
{
	std::ofstream myfile;
	myfile.open(<FilePathHere>);
	myfile << len << " " << buf << " " << "\n";
	myfile.close();
	ws2Send(s, buf, len, flags);
}

In DllMain, I'm calling the SetHook function you posted like this

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID reserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		SetHook(true, reinterpret_cast<PVOID*>(&ws2Send), &SendHook);
		return 0;
	}
}

I'm stuck as to what the problem is, but I'll keep trying.

Edited by Roast

Share this post


Link to post

@Roast Go by the CreateMutex example (in above code you're not using the correct return type and so on, not sure why your hook is not correctly allocated though):
 

#include <winsock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

bool Detour__Send()
{
	static decltype(&send) _send = &send;

	decltype(&send) send_hook = [](SOCKET s, const char *buf, int len, int flags) -> int
	{
		/* your code */

		return _send(s, buf, len, flags);
	};

	return SetHook(true, reinterpret_cast<void**>(&_send), send_hook);
}

 

  • Like 1

Share this post


Link to post
36 minutes ago, Roast said:

Ok, sweet. I'm set up with Microsoft's detours library, but I'm having a similar problem to before where the jump is being set to empty memory.

image.png.4a9862915fa4daf0001197808b607fcd.pngimage.png.6234a76a62a6cd00a8ef6683759600ee.png

I ended up trying to apply the 2nd example first as it was a lot simpler to read and understand. Here's what I've tried to do, which is very similar to before.


typedef int(*WINAPI ws2Send_t)(SOCKET s, const char* buf, int len, int flags);
auto ws2Send = reinterpret_cast<ws2Send_t>(0x752B5E40); // I assumed this is meant to be the address of the function I'm hooking.
void WINAPI SendHook(SOCKET s, const char* buf, int len, int flags)
{
	std::ofstream myfile;
	myfile.open(<FilePathHere>);
	myfile << len << " " << buf << " " << "\n";
	myfile.close();
	ws2Send(s, buf, len, flags);
}

In DllMain, I'm calling the SetHook function you posted like this


BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID reserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		SetHook(true, reinterpret_cast<PVOID*>(&ws2Send), &SendHook);
		return 0;
	}
}

I'm stuck as to what the problem is, but I'll keep trying.

It is because you are returning 0 upon DLL_PROCESS_ATTACH, refer https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx
 

Quote

When the system calls the DllMain function with the DLL_PROCESS_ATTACH value, the function returns TRUE if it succeeds or FALSE if initialization fails. If the return value is FALSE when DllMain is called because the process uses the LoadLibrary function, LoadLibrary returns NULL. (The system immediately calls your entry-point function with DLL_PROCESS_DETACH and unloads the DLL.)


 

Edited by XShade
  • Like 2

Share this post


Link to post
1 hour ago, XShade said:

It is because you are returning 0 upon DLL_PROCESS_ATTACH, refer https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx

Awesome, turns out this was the main problem. I can now see my process loaded in to memory!

image.png.676886ca9fc927395068a72eaed977c6.png

1 hour ago, Erotica said:

@Roast Go by the CreateMutex example (in above code you're not using the correct return type and so on, not sure why your hook is not correctly allocated though):

I've managed to get both examples working in the end, thank you again for the code snippets you've posted! I'm now able to log packets out to file but it looks like they're encrypted.

I'm logging out to file like so

decltype(&send) send_hook = [](SOCKET s, const char *buf, int len, int flags) -> int
{
	std::ofstream myfile;
	myfile.open("C:\\Users\\jamie\\Desktop\\test.txt", std::ios_base::app | std::ios_base::out);
	myfile << len << " " << buf << " " << "\n";
	myfile.close();

	return _send(s, buf, len, flags);
};

I've made the assumption that writing them out like this doesn't require me to do anything like construct a string with the characters coming in. Here's a snippet of what I see in my file:

14 ÚK)ʯú¶E�E\ÕŸë{k{7�–il.com� 
3 bZŽÊ¯ú¶E�E\ÕŸë{k{7�–il.com� 
3 y�Âʯú¶E�E\ÕŸë{k{7�–il.com� 
3 �´ ʯú¶E�E\ÕŸë{k{7�–il.com� 
3 �L¦Ê¯ú¶E�E\ÕŸë{k{7�–il.com� 
3 Á(êʯú¶E�E\ÕŸë{k{7�–il.com� 
3 �»uʯú¶E�E\ÕŸë{k{7�–il.com� 
3 ÏàÞʯú¶E�E\ÕŸë{k{7�–il.com� 
3 ÿ{õʯú¶E�E\ÕŸë{k{7�–il.com� 

Judging by the characters at the end, could that contain my email address? 😕 There was a packet that didnt look encrypted at the beginning which was solely my login email address.

Looks like the next step is to try and find the encryption/decryption functions with IDA.

Edited by Roast

Share this post


Link to post
2 hours ago, NewSprux2.0? said:

Could also look like some kind of wrong encoding (utf8 vs unicode vs ascii).

I did consider that, but I wouldn't have expected packets to be coming in with different encodings in the same packet right? (EDIT: I think what I said is retarded, it's been a long day...) I can see my email in plaintext in the first logged packet surrounded by what look like encrypted bytes.

I've had a look online for information around the packet encryption in the game I'm working with and I already understand the methods involved, just need to work on a C++ implementation. I think my main struggle will be figuring out the correct types to use for the information, for example byte arrays would be a vector of chars?

Edited by Roast

Share this post


Link to post

Yes, if it is raw packets then byte arrays are just ... well... a sequence of bytes 😛

Strings can be both char-strings and wchar-strings. In a regular string (char-string) every character is 1 byte long. In a wchar-string (wide char), every character is 2 bytes.

Share this post


Link to post
12 minutes ago, NewSprux2.0? said:

Yes, if it is raw packets then byte arrays are just ... well... a sequence of bytes 😛

Strings can be both char-strings and wchar-strings. In a regular string (char-string) every character is 1 byte long. In a wchar-string (wide char), every character is 2 bytes.

Sweet, I keep running in to typing issues that are all alien to me and having to stop to do some reading. Progress is slow, but it's been a lot faster thanks to you guys :)

Having been a C# engineer for years I'm amazed by all the things that I've taken for granted. Right now I'm comparing Cryptography libraries for C++, whereas I'm so used to just using System.Security.Cryptography.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×