1

I need the base Address of the exe "tibia.exe". This is what I got so far but it doesn't work. It always returns 0.

What's wrong?

DWORD MainWindow::getBaseAddress(DWORD dwProcessIdentifier)
{
    TCHAR lpszModuleName[] = {'t','i','b','i','a','.','e','x','e','\0'}; //tibia.exe
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
                                                dwProcessIdentifier);
    DWORD dwModuleBaseAddress = 0;
    if(hSnapshot != INVALID_HANDLE_VALUE)
    {
        MODULEENTRY32 ModuleEntry32;
        ModuleEntry32.dwSize = sizeof(MODULEENTRY32);
        if(Module32First(hSnapshot, &ModuleEntry32))
        {
            do
            {
                if( wcscmp(ModuleEntry32.szModule, lpszModuleName) == 0)
                {
                    dwModuleBaseAddress = (DWORD)ModuleEntry32.modBaseAddr;
                    break;
                }
            }
            while(Module32Next(hSnapshot, &ModuleEntry32));
        }
        CloseHandle(hSnapshot);
    }
    return dwModuleBaseAddress;
}

//Call it here
tibiaWindow = FindWindow( L"TibiaClient", NULL);

DWORD PID;
GetWindowThreadProcessId( tibiaWindow, &PID );
DWORD baseAddress = getBaseAddress( PID );

if( baseAddress == 0 )
    return false ;
Davlog
  • 2,162
  • 8
  • 36
  • 60
  • 2
    `lpszModuleName` is missing the `'\0'` at the end. – halex Nov 23 '13 at 21:50
  • @halex thanks, gonna check it now – Davlog Nov 23 '13 at 21:53
  • @halex it's still 0 ! – Davlog Nov 23 '13 at 21:55
  • Step through in the debugger, work out what is failing. – Jonathan Potter Nov 23 '13 at 21:56
  • @JonathanPotter when compaing lpszModuleName to szModule, it never becomes true. – Davlog Nov 23 '13 at 21:59
  • 2
    To those who downvote, please tell me why so I can improve this question... – Davlog Nov 23 '13 at 22:02
  • You are using a mixture of explicit UNICODE invocations (`wcscmp`) and types that depend on preprocessor macros (`TCHAR`, `FindWindow`, etc.). Consider using consistent encodings: `TCHAR lpszModuleName[] = _T( "tibia.exe" );` for example. Also, you should use case-insensitive comparison: [`_tcsicmp`](http://msdn.microsoft.com/en-us/library/k59z8dwe.aspx). The latter is likely your problem. – IInspectable Nov 23 '13 at 22:06
  • @IInspectable i cannot find _T and _tcsicmp, i can only use wcsmp – Davlog Nov 23 '13 at 22:09
  • `#include `, as explained in the documentation I linked to. – IInspectable Nov 23 '13 at 22:11
  • @IInspectable Already included it an hour ago, did not work – Davlog Nov 23 '13 at 22:12
  • @IInspectable do i need the visual studio compiler? I'm using mingw – Davlog Nov 23 '13 at 22:13
  • If you want to use the Visual Studio CRT you have to have Visual Studio installed. Visual Studio Express is free to download and use. If you want to use MinGW you have to work out the name of the case-insensitive string comparison for UNICODE strings yourself. – IInspectable Nov 23 '13 at 22:14
  • @IInspectable well it doesnt work, any other ideas? – Davlog Nov 23 '13 at 22:24
  • Using a debugger is always a good idea. Get yourself Visual Studio 2013 Express for Windows Desktop. It comes with a good debugger that will give you an idea of what is going on, and where things go wrong. – IInspectable Nov 23 '13 at 22:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/41779/discussion-between-davlog-and-iinspectable) – Davlog Nov 23 '13 at 22:29

1 Answers1

2

Perhaps it's just because I was using them before ToolHelp32 was available (at least on the NT-based operating systems), but I tend to use the PSAPI functions for this kind of task. Using them, the code would look like this:

#include <windows.h>
#include <string>
#include <psapi.h>
#include <iostream>

int main(int argc, char **argv) {

    HANDLE process = GetCurrentProcess();

    if (argc != 1)
        process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, atoi(argv[1]));

    HMODULE handles[2048];
    DWORD needed;
    EnumProcessModules(process, handles, sizeof(handles), &needed);

    for (int i = 0; i < needed / sizeof(handles[0]); i++) {
        MODULEINFO info;
        char name[1024];
        GetModuleBaseName(process, handles[i], name, sizeof(name));
        if (std::string(name).find(".exe") != std::string::npos) {
            GetModuleInformation(process, handles[i], &info, sizeof(info));
            std::cout << name << ": " << info.lpBaseOfDll << "\n";
            break;
        }
    }
}

As it stands right now, this will let you enter a process ID on the command line, and show the load address of the first module it finds in that process with a name that includes ".exe". If you don't specify a process ID, it'll search through its own process (demos how the functions work, but otherwise pretty much useless).

Using either ToolHelp32 or PSAPI, you end up with a similar limitation: you need to compile this into a 64-bit executable for it to be able to "see" other 64-bit processes (i.e., when compiled as 32-bit code, they see only other 32-bit processes).

There are also some processes (e.g., CSRSS.exe) that neither will be able to open/enumerate successfully. As far as I know, the same processes will succeed/fail with PSAPI vs. ToolHelp32.

PSAPI does have one bit of clumsiness compared to ToolHelp32: dealing (well) with processes that have lots of modules is clumsy (at best). You call EnumProcessModules, and if you haven't given room for enough modules, the "Needed" parameter will be set to the space needed for the number of modules it contains. There's a race condition though: between the time that returns and the time you call EnumProcessModules again, the process could have loaded more DLLs, so that second call could fail the same way.

For the moment, I've just assumed that no process will use more than 2048 modules. To be really correct, you should have a while loop (or maybe a do/while loop) that starts with zero space, calls EnumProcessModules to find out how much space is needed, allocate that (perhaps with a little extra in case it loads more DLLs) and repeat until it succeeds.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • The string comparison is somewhat brittle. It matches any module name containing `.exe`, like `myapp.exe.resources.dll`. UNICODE support may not hurt either. – IInspectable Nov 24 '13 at 13:41
  • @IInspectable: Yes -- the rest is intended almost exclusively as a demonstration of how to use these functions. – Jerry Coffin Nov 24 '13 at 17:41
  • One more thing, which is probably more important to mention: The string comparison is case-sensitive, i.e. it will not match `MYAPP.EXE`. I presume this is what the OP was having difficulty with. – IInspectable Nov 24 '13 at 17:51