Author Topic: Writing a Loader  (Read 6002 times)

0 Members and 1 Guest are viewing this topic.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Writing a Loader
« on: April 07, 2007, 01:27:56 am »
I added a page to my Assembly tutorial about how to write a loader, which includes sample code for a generic loader, which can load any program, disabling a couple security functions, and can also inject a .dll automatically. I thought it might be helpful to any of you who are interested in writing hacks.

Here is the article and here is the code.

Have fun!

Offline abc

  • Hero Member
  • *****
  • Posts: 576
    • View Profile
Re: Writing a Loader
« Reply #1 on: April 07, 2007, 08:57:29 pm »
<3

Offline Skywing

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • Nynaeve
Re: Writing a Loader
« Reply #2 on: April 14, 2007, 09:00:22 pm »
You should not rely on LoadLibrary'ing arbitrary modules and assuming that their base addresses will be the same cross-process.  Build an offset from the module base by manipulating the module in the current process, and then traverse the loaded module list in the target process to find the remote module base.  Then, perform the addition to build a complete VA.

Better yet would be to simply traverse the export directory of the module in the remote address space, as this does not involve loading the module into the current process and running initializer code / mapping dependencies.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Writing a Loader
« Reply #3 on: April 14, 2007, 09:23:38 pm »
That's a good point, and something I thought about, albeit briefly, while writing it. At the time, however, I didn't know how to walk the loaded module list, I actually learned how to do that very recently.

Thanks for the suggestion!

Offline Skywing

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • Nynaeve
Re: Writing a Loader
« Reply #4 on: April 14, 2007, 09:36:43 pm »
If you don't want to do it manually, there are some high level APIs (PSAPI and toolhelp32, as well as the DbgEng APIs) that can help.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Writing a Loader
« Reply #5 on: April 14, 2007, 10:33:43 pm »
I believe toolhelp32 is the one I learned how to use.    Here's the code I was playing with:

Code: [Select]
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);

if(hSnapshot)
{
MODULEENTRY32 me = { sizeof(me) };
BOOL isFound = FALSE;
BOOL isMoreMods = Module32First(hSnapshot, &me);

while(isMoreMods && !isFound)
{
isMoreMods = Module32Next(hSnapshot, &me);

printf("Checking %s [0x%p]\n", me.szModule, me.modBaseAddr);
isFound = (_strcmpi(me.szModule, fnDll) == 0 || _strcmpi(me.szExePath, fnDll) == 0);
}

I'm assuming that's a safe way to walk through loaded libraries, right?

Offline Skywing

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • Nynaeve
Re: Writing a Loader
« Reply #6 on: April 14, 2007, 10:40:35 pm »
Potentially.  If you don't have the process frozen in a debugger, then it's possible (though arguably unlikely) that the loaded module list could be mutating while you are traversing it, without synchronization.  In either case, it would be good practice to guard against things like infinite loops in the module list by keeping track of visited nodes or the like when you are working with a foreign process.

For your purposes, however, the race condition may not be something you would expect to see enough to explicitly guard against.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Writing a Loader
« Reply #7 on: April 14, 2007, 10:45:32 pm »
Although that's true, it would be good, academically, to implement it "properly".

In that situation, though, I had just spawned the process using CreateProcess() with the CREATE_SUSPENDED flag, so that actually shouldn't be an issue in this instance.

I'm unfamiliar with the toolkit I'm using for this, but it strikes me that CreateToolhelp32Snapshot() would give me a frozen view of the modules, so I wouldn't have to worry about them changing (except after I've found the one I want). Do you know if that's accurate assumption?

Offline Skywing

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • Nynaeve
Re: Writing a Loader
« Reply #8 on: April 14, 2007, 11:15:37 pm »
The loader will not have initialized its data structures if you inspect the address space and no threads have begun to run yet, so trying to enumerate loaded modules after creating the process suspended will not yield anything meaningful.

As far as I know, CreateToolhelp32Snapshot doesn't provide any guarantee that while it is copying the loaded module list to its internal section view for later enumeration by Module32*, the remote process will remain suspended.

If you ensure that the process is truly frozen while CreateToolhelp32Snapshot is executing, you should be fine.  The only way to truly ensure that, that I know of, is to be inspecting a debug event as a debugger.  Otherwise, even if you suspend all running threads in the process, some other process could call CreateRemoteThread or an equivalent to create a thread in the process that would begin to run immediately.  Being frozen in a debugger has kernel level support to ensure that no code runs period within the address space (at least in user mode).

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Writing a Loader
« Reply #9 on: April 14, 2007, 11:31:13 pm »
Well, freezing it in a debugger isn't really an option. That's a bit overboard.

But creating the process suspended seems to initialize data structures properly, I do obtain meaningful results when walking the list:

Code: [Select]
[....]
if(!CreateProcess(lpAppPath, lpCmdLine, 0, 0, FALSE, CREATE_NEW_CONSOLE | CREATE_SUSPENDED, 0, lpWorkingDirectory, &startupInfo, &processInformation))
error("Failed to create the process");

*hThread = processInformation.hThread;
    *hProcess = processInformation.hProcess;

if(*hProcess)
{
HANDLE hSnapshot = NULL;
int dwProcessId = GetProcessId(hProcess);

hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if(hSnapshot)
{
MODULEENTRY32 me = { sizeof(me) };
BOOL isMoreMods = Module32First(hSnapshot, &me);
while(isMoreMods)
{
isMoreMods = Module32Next(hSnapshot, &me);
printf("Found %s [0x%p]\n", me.szModule, me.modBaseAddr);
}
}
}

printf("Successfully created the process!\n\n");
exit(1);

Outputs:
Code: [Select]
Found ntdll.dll [0x7C900000]
Found kernel32.dll [0x7C800000]
Found MSVCR80D.dll [0x10200000]
Found msvcrt.dll [0x77C10000]
Found ADVAPI32.DLL [0x77DD0000]
Found RPCRT4.dll [0x77E70000]
Found RPCRT4.dll [0x77E70000]

I'm not sure why RPCRT4.dll is there twice, but I'd bet I'm looping once too many in that hastily-written code. [edit: yep, I'm printing at the beginning of the loop. Not really a big deal. ]

But seriously, if I suspend all the threads, that's good enough for me. I'm not really that worried about race conditions occurring.

Offline Skywing

  • Full Member
  • ***
  • Posts: 139
    • View Profile
    • Nynaeve
Re: Writing a Loader
« Reply #10 on: April 14, 2007, 11:57:12 pm »
If you're *really* creating the process suspended and aren't injecting a thread into it, then that shouldn't be the output you get.  Until the first thread runs, the only two images mapped in the address space are ntdll and the main exe, and the PEB_LDR_DATA entries are not yet created.

If you inject a thread into the process (such as by attaching a debugger to it - the debugger breakin thread counts), you will perturb the state of the target.

Using the following:

Code: [Select]
int
__cdecl
wmain(
        int ac,
        wchar_t **av
        )
{
        PROCESS_INFORMATION pi;
        STARTUPINFO         si;
        HANDLE              h;
        MODULEENTRY32       m;
        BOOL                success;

        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);

        if (!CreateProcess(
                L"C:\\Windows\\System32\\Cmd.exe",
                0,
                0,
                0,
                FALSE,
                CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
                0,
                0,
                &si,
                &pi))
                return 0;

        wprintf(L"Pid: %x\n", pi.dwProcessId);

        if ((h = CreateToolhelp32Snapshot(
                TH32CS_SNAPMODULE,
                pi.dwProcessId)) == INVALID_HANDLE_VALUE)
        {
                wprintf(L"Error in snapshot: %lu\n", GetLastError());
                TerminateProcess(pi.hProcess, 0);
                return 0;
        }

        wprintf(L"Snapshot is %p\n", h);

        ZeroMemory(&m, sizeof(m));
        m.dwSize = sizeof(m);

        for (success = Module32First(h, &m);
                 success;
                 success = Module32Next(h, &m))
        {
                wprintf(L"%p %p     %s\n",
                        m.modBaseAddr,
                        m.modBaseAddr + m.modBaseSize,
                        m.szModule);
        }

        wprintf(L"Last error was %lu\n", GetLastError());

        CloseHandle(h);

        getc(stdin);

        ResumeThread(pi.hThread);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);

        return 0;
}

I get:

Code: [Select]
Pid: 59c
Error in snapshot: 299

As long as you are running on an nt-based system (i.e. not 9x), you should never get any data back from trying to enum modules like that on an uninitialized process.  If you are on an nt-based system, then something is running a thread before you perform the enumeration.

BTW, CreateToolhelp32Snapshot returns INVALID_HANDLE_VALUE and not NULL when it fails.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Writing a Loader
« Reply #11 on: April 15, 2007, 02:15:12 am »
Hmm, I've run it outside a debugger before and it seems to work fine. Strange!

I'll have to blame Jeffrey Richter for checking the wrong return value, my code was originally lifted from his book, although I've changed most of it now.