Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Open process with debug privileges and read/write memory

Short version:

I'm trying to open a process handle with debug privileges and define a pointer which points to an object in the memory of the debuggee.

Long version

I'm a computer science university student in my final year of graduation and got tasked to build an application which should be used for educational purposes for the next generation of students.

Why am I here asking for help, you might ask? Well, the target platform is Windows and I have unfortunately no knowledge of the WinAPI whatsoever...

Okay, here is the basic requirement:

  • Programming language: C++
  • Platform: Windows (7 Professional)
  • Used IDE: Visual Studio 2012
  • No additional libraries if they aren't essential to ease the development

What will the application be used for?

Using this application the students shall learn to handle addresses, in this case static ones: the debuggee process will have some static pointers, which lead to other pointers themself to form a multi-dimensional pointer. The students have to find these base addresses using some debugging techniques (which is not part of my work!) and try to find the values at the end of these pointers.

My application will be used by the tutors to randomly change the values and/or structures in the debuggee process.

Some search did yield the first answer: using ReadProcessMemory and WriteProcessMemory one can easily change values in the memory of another process without any need to get debug privileges.

What my tutors want, however, is to have the ability to define pointers (let's say unsigned int) which should point into the memory space of the debuggee process, effectively holding the base addresses I wrote about earlier. They really want this and I couldn't even talk this out of them so I'm stuck to do this at the end...

And what exactly should work?

Well, I'd have accomplished my task if the following (pseudo) code works:

grantThisProcessDebugPrivileges();
openAnotherProcessWhileItsRunning("targetProcess.exe");

unsigned int * targetValue = (unsigned int*) 0xDE123F00;
// or even
myCustomClass * targetClass = (myCustomClass*) 0xDE123F00;

where the address 0xDE123F00 lies in the memory space of targetProcess.exe.

I know this is possible, else there wouldn't be debuggers which could show this information.

What I did so far (or tried...)

Okay, the thing is: I'm really confused whether I have to activate debug privileges for my application prior opening the target process, doing it after opening or rather giving the target process these privileges.

So I found an example in MSDN and tried to implement it:

    BOOL SetPrivilege(
    HANDLE hToken,          // token handle
    LPCTSTR Privilege,      // Privilege to enable/disable
    BOOL bEnablePrivilege   // TRUE to enable.  FALSE to disable
    )
{
    TOKEN_PRIVILEGES tp;
    LUID luid;
    TOKEN_PRIVILEGES tpPrevious;
    DWORD cbPrevious=sizeof(TOKEN_PRIVILEGES);

    if(!LookupPrivilegeValue( NULL, Privilege, &luid )) return FALSE;

    // 
    // first pass.  get current privilege setting
    // 
    tp.PrivilegeCount           = 1;
    tp.Privileges[0].Luid       = luid;
    tp.Privileges[0].Attributes = 0;

    AdjustTokenPrivileges(
            hToken,
            FALSE,
            &tp,
            sizeof(TOKEN_PRIVILEGES),
            &tpPrevious,
            &cbPrevious
            );

    if (GetLastError() != ERROR_SUCCESS) return FALSE;

    // 
    // second pass.  set privilege based on previous setting
    // 
    tpPrevious.PrivilegeCount       = 1;
    tpPrevious.Privileges[0].Luid   = luid;

    if(bEnablePrivilege) {
        tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
    }
    else {
        tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED &
            tpPrevious.Privileges[0].Attributes);
    }

    AdjustTokenPrivileges(
            hToken,
            FALSE,
            &tpPrevious,
            cbPrevious,
            NULL,
            NULL
            );

    if (GetLastError() != ERROR_SUCCESS) return FALSE;

    return TRUE;
};

And in my main:

HANDLE mainToken;

// I really don't know what this block of code does :<
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &mainToken))
{
    if (GetLastError() == ERROR_NO_TOKEN)
    {
        if (!ImpersonateSelf(SecurityImpersonation))
        return 1;

        if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &mainToken)){
            cout << GetLastError();
        return 1;
        }
     }
    else
        return 1;
}

if (!SetPrivilege(mainToken, SE_DEBUG_NAME, true))
{
    CloseHandle(mainToken);
    cout << "Couldn't set DEBUG MODE: " << GetLastError() << endl;
    return 1;
};

unsigned int processID = getPID("targetProcess.exe");
HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);

if (hproc == NULL)
{
    cout << "Couldn't open the process" << endl;
    return 1;
};

    unsigned int * theValue = (unsigned int*) 0xDE123F;

Okay, this code runs without any errors, SetPrivilege returns TRUE so I guess it really did set SE_DEBUG_NAME, which I think is the flag I need to set.

But after - for example - outputting the dereferenced value of theValue, the application crashes with an access violation message, which shows that my approach didn't work. I did especially pay attention to start the VisualStudio Debugger with admin rights (SetPrivilege failed otherwise).

I am really clueless here, the fact that I don't know whether setting SE_DEBUG_NAME is the right approach adds to my overall confusion.

I hope you can help me out :) My hands are tied concerning the specific requests of the application , if you have ideas to achieve my goal using an entire dfferent approach, you're free to enlight me, but I won't be able to present it to my superiors so it will only add to my knowledge :D

like image 635
Ahnihmuhs Avatar asked Jun 21 '13 21:06

Ahnihmuhs


3 Answers

From you description, it appears that you have gotten to the point where you can open the process with SE_DEBUG. At this point you now have a handle to the target process.

What your code appears to be missing is the use of ReadProcessMemory.

First we need to look at the definition of ReadProcessMemory:

BOOL WINAPI ReadProcessMemory(
          _In_   HANDLE hProcess,
          _In_   LPCVOID lpBaseAddress,
          _Out_  LPVOID lpBuffer,
          _In_   SIZE_T nSize,
          _Out_  SIZE_T *lpNumberOfBytesRead);

This function essentially gives you the ability to copy a block of memory from one process space into your process space. So you need to use this method to read a block of memory the size of the data structure you wish to read into your process space, then you can reinterpret the memory block as that data type.

So semi pseudocode for reading an unsigned int from your target process looks like this:

unsigned int ReadUInt(HANDLE process, const void * address)
{
    // Add parameter validation

    unsigned char buffer[sizeof(unsigned int)] = {};
    size_t bytesRead = 0;

    BOOL res = ::ReadProcessMemory(process,  // The handle you opened with SE_DEBUG privs
                                   address,  // The location in the other process
                                   buffer,   // Where to transfer the memory to
                                   sizeof(unsigned int), // The number of bytes to read
                                   &bytesRead); // The number of bytes actually read

    if (!res)
    {
        // Deal with the error
    }

    if (bytesRead != sizeof(unsigned int))
    {
        // Deal with error where we didn't get enough memory
    }

   return *reinterpret_cast<unsigned int *>(buffer);
}

Instead of using this line:

unsigned int * theValue = (unsigned int*) 0xDE123F00;

You would do this:

unsigned int theValue = ReadUInt(hproc, 0xDE123F00);

Keep in mind that this requires that you know the size and memory layout of the types you are trying to read. Simple types that are contained in contiguous memory can be retrieved in a single ReadProcessMemory call. Types that contain pointers as well as values will require you to make extra calls to ReadProcessMemory to find the values referenced by the pointers.

like image 144
Mark Tempel Avatar answered Nov 14 '22 11:11

Mark Tempel


Each process has its own virtual address space. An address in one process only has meaning in that process. De-referencing a pointer in C++ code will access the virtual address space of the executing process.

When you de-referenced the pointer in your code you were actually attempting to access memory in your process. No amount of wishful thinking on the part of your tutors can make pointer de-reference access memory in another process.

If you wish to read and write memory from other processes then you must use ReadProcessMemory and WriteProcessMemory.

I don't think you really need to go to all those lengths with tokens and privileges. If I recall correctly you add the debug privilege, call OpenProcess and go straight to it. And I think you can typically skip adding the privilege.

Some search did yield the first answer: using ReadProcessMemory and WriteProcessMemory one can easily change values in the memory of another process without any need to get debug privileges. What my tutors want, however, is to have the ability to define pointers (let's say unsigned int) which should point into the memory space of the debuggee process, effectively holding the base addresses I wrote about earlier. They really want this and I couldn't even talk this out of them so I'm stuck to do this at the end...

What they want is impossible. I suggest you tell them to get a better understanding of virtual memory before making impossible requirements!


@Cody Gray helpfully mentions memory mapped files. If debuggee and debugger co-operate then they can use memory mapped files to share a common region of memory. In that situation then both process can map the memory into their virtual address space and access it in the normal manner.

I rather assumed that your debuggee was an unwilling victim, but if it is prepared to co-operate then sharing memory could be an option.

Even then you'd need to be careful with any pointers in that shared memory because the memory would, in general, be mapped onto different virtual addresses in each process.

like image 40
David Heffernan Avatar answered Nov 14 '22 10:11

David Heffernan


I think you are trying to access kernel land memory range and hence the exception.

The user land range is from 0x00000000 - 7FFFFFFF, so try accessing in this range, as anything above is kernel space.

I am assuming you are on a 32-bit machine.

Check User Space and System Space (Microsoft Docs).

like image 1
NiladriBose Avatar answered Nov 14 '22 10:11

NiladriBose