Finding Functions, Part 2

Picking up where we left off, we now have the signature and imagebase address of the function. On Windows, the ImageBase of a DLL is almost always obeyed, and you can be assured that server.dll will always be loaded at 0×22000000. However, on Linux, this is not the case. And either way, assuming this is a bad design (not to say these ‘hacks’ are good design, but it helps make things more stable).

So, we need to find the DLL in memory. Luckily this isn’t too hard. Both Window and Linux come with native API for finding the base address of any given address. Respectively, these function calls are

VirtualQuery()
and
dladdr()
. Do we know of an address that will always exist in the GameDLL? Of course! This would be the GameDLL’s factory: gameServerFactory for server plugins and g_SMAPI->serverFactory(false) for Metamod:Source plugins. Let’s pretend we have this address in
void *pDllAddr
. First, we need to get the base address of the DLL in memory.

bool GetDllMemInfo(void *pAddr, unsigned char **pBaseAddr, size_t *memLength)
{
#ifdef WIN32
	MEMORY_BASIC_INFORMATION mem;
	if (!VirtualQuery(pAddr, &mem, sizeof(mem)))
		return false;
	if (*pBaseAddr)
		*pBaseAddr = (unsigned char *)mem.AllocationBase;
	IMAGE_DOS_HEADERS *dos = (IMAGE_DOS_HEADERS *)(mem.AllocationBase);
	IMAGE_NT_HEADERS *pe = (unsigned long)dos + (unsigned long)dos->e_lfanew;
	if (pe->Signature != IMAGE_NT_SIGNATURE)
		return false;
	if (memLength)
		*memLength = (size_t)(pe->OptionalHeader.SizeOfImage);
	return true;
#else
	Dl_info info;
	struct stat buf;

	if (!dladdr(pAddr, &info))
		return false;

	if (!info.dli_fbase || !info.dli_fname)
		return false;

	if (stat(info.dli_fname, &buf) != 0)
		return false;

	if (pBaseAddr)
		*pBaseAddr = (unsigned char *)info.dli_fbase;
	if (memLength)
		*memLength = buf.st_size;

	return true;
#endif
}

unsigned char *base;
size_t len;
bool success = GetDllMemInfo(pDllAddr, &base, &len);

This code is pretty straight forward. For Windows, we use VirtualQuery() and cast the base address to PE headers. That base address and the PE headers give us the information we need. In Linux, there is nothing easy to parse ELF files like there is in Windows. We rely on getting the file size of the shared object filename.

Tomorrow, the easiest part: Scanning memory and using your function!

Leave a Reply

You must be logged in to post a comment.