x64dbg / ScyllaHide

Advanced usermode anti-anti-debugger. Forked from https://bitbucket.org/NtQuery/scyllahide
GNU General Public License v3.0
3.41k stars 423 forks source link

NtGetNextThread / NtGetNextProcess #33

Closed reliasn closed 6 years ago

reliasn commented 6 years ago

ScyllaHide's ntdll.h and .lib's are the most complete that I know of. After searching extensively, I found that most ntdll libraries would rely on a bunch of calls to GetProcAddress in order to initialize the function pointers while ScyllaHide is able to call the Nt* functions directly, thanks to the provided .lib's.

I'm currently trying to implement a "custom" NtSuspendProcess where the function would also be able to skip the current thread:

#define OBJ_KERNEL_EXCLUSIVE           0x00010000L
#define OBJ_VALID_PRIVATE_ATTRIBUTES   0x00010000L
#define OBJ_ALL_VALID_ATTRIBUTES (OBJ_VALID_PRIVATE_ATTRIBUTES | OBJ_VALID_ATTRIBUTES)

void SuspendThreads(HANDLE procHandle, bool suspend, bool skipSelf){
    DWORD curThreadId = GetCurrentThreadId();
    HANDLE hThread = NULL;
    while(1) {
        NTSTATUS ntStat = NtGetNextThread(procHandle, hThread, STANDARD_RIGHTS_ALL, OBJ_ALL_VALID_ATTRIBUTES, 0, &hThread);
        if (!NT_SUCCESS(ntStat)) {
            break;
        }
        if (skipSelf && curThreadId == GetThreadId(hThread)) {
            continue;
        }
        if (suspend) {
            SuspendThread(hThread);
        } else {
            ResumeThread(hThread);
        }
    }
}

I really need to use NtGetNextThread because the CreateToolHelp32Snapshot + Thread32First/Next combination is just too slow for what I'm doing. From what I read, this function is available on Windows Vista onwards.

Would it be possible to add NtGetNextThread and NtGetNextProcess to the static libraries? Since they are already declared in ntdll.h, I thought it would be nice to have them included in the libraries as well.

At the moment, the following linker error is thrown:

Mattiwatti commented 6 years ago

First, I think you meant to pass (OBJ_VALID_ATTRIBUTES & OBJ_KERNEL_EXCLUSIVE) to NtGetNextThread, otherwise you will get very few results ;) In fact, you probably want to pass 0 (no attributes at all) instead, otherwise you are asking for the handle to have OBJ_INHERIT | OBJ_PERMANENT | OBJ_EXCLUSIVE | ..., which you probably don't actually want.

Similarly, I would request the minimum necessary access rights (I don't know what STANDARD_RIGHTS_ALL translates to, but it's probably either too much or not enough). In this case that would be THREAD_QUERY_LIMITED_INFORMATION | THREAD_SUSPEND_RESUME.

Anyway, to address the question: the reason ntdll.lib/ntdllp.lib don't export these functions is because ScyllaHide has a minimum target version of XP. That means any ntdll.dll functions not present in XP aren't in the import libraries. The import libraries are also forward compatible up to Windows 10, so the total set of exports is restricted to the subset of ntdll.dll exports that have existed since XP or before without ever being removed in a future version.

NtGetNextProcess and NtGetNextThread are an odd pair: ntdll.h requires a minimum target version of Server 2003 for them, which I probably got from the Process Hacker SDK, or else the fact that the Server 2003 kernel source code contains implementations of both. However that would mean the x64 version would have the exports, since XP x64 is actually based on the Server 2003 kernel. However I checked the ntdll files for both Server 2003 x86 and XP x64, and neither exports these functions. So this version requirement seems to be a mistake; it should actually be Vista or higher. Maybe someone wrote these functions for Server 2003 and then forgot to add them to the syscall list. (Side note: for some reason the kernel mode ZwGetNextProcess actually wasn't exported until Windows 10, and ZwGetNextThread is only exported in Windows 10 RS3!)

So, no, NtGetNextProcess/NtGetNextThread won't be added to the import libraries as long as XP is still supported. However, the good news is that you don't have to be a wizard to generate your own import libraries! For example, check out dumplib which is what I used to create the import libraries for ScyllaHide/x64dbg. If you're not super concerned about guaranteed compatibility with every Windows version, you can just use the output from dumpbin /EXPORTS %SYSTEMROOT%\System32\ntdll.dll as input to get an import library containing every function your system's ntdll.dll exports. If you only want to add a few exports, I'd recommend using the exports from ScyllaHide's .lib files as a base instead since I already did the work to make them forwards and backwards compatible with XP through 10.

If the above is too much work, there is also the option of using the import libraries directly from the Windows SDK. The Windows 10 SDK installs a heavily censored ntdll.lib. ntdllp.lib is only available from the WDK. There are some issues with these .lib files though: (1) they are targeted to match the Windows version of the SDK specifically, (2) many exports are missing, and (3) if you want to remove exports (not uncommon if you're mixing e.g. ntdllp.lib and the CRT and getting linker collisions), you have to restort to hex editing the lib file to get rid of unwanted names.

reliasn commented 6 years ago

Wow... that's a complete answer!

I will apply your suggestions to how I'm calling NtGetNextThread and will definitely check out your dumplib. I am familiar with dumpbin.exe and lib.exe but I've never customized static libraries like that. All this sounds a lot of work, but maybe in the end I find it easier than how I'm thinking now 😅

Out of curiosity: why didn't you take the GetProcAddress approach? For example, you would have ntdll.cpp with many declarations like this:

HMODULE ntdllMdl = GetModuleHandle("ntdll.dll");

t_NtGetNextThread NtGetNextThreadFn = t_NtGetNextThread(GetProcAddress(ntdllMdl, "NtGetNextThread"));
NTSTATUS NTAPI NtGetNextThread(HANDLE ProcessHandle, HANDLE ThreadHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, 
                               ULONG Flags, PHANDLE NewThreadHandle) 
{
    if (NtGetNextThreadFn)
        return NtGetNextThreadFn(ProcessHandle, ThreadHandle, DesiredAccess, HandleAttributes, Flags, NewThreadHandle);
    SetLastError(ERROR_PROC_NOT_FOUND);
    return STATUS_PROCEDURE_NOT_FOUND;
}

Don't you think it's easier to maintain the library for both platforms this way?

Thank you so much for taking the time to explain and for making such a complete NT library! Even though my question was related to a "3rdparty", the anti-anti-debugging techniques implemented in ScyllaHide have also been very useful for what I'm doing.

Mattiwatti commented 6 years ago

Why not use GetProcAddress? Interesting question, and so many answers, perspectives, philosophies... to choose from... where to begin? :)

OK, that's enough for now I think ;) There's probably a few other things I forgot to mention, but those are the main reasons why I prefer static imports to a GetProcAddress style approach. That doesn't mean GetProcAddress is useless however, but you should limit its usage to cases where being able to call a function would be nice, but isn't critical. Personally, I have the luck of being able to work on pretty much whatever I want. Since I'm a simple man, my philosophy is: if it's optional, it's probably not interesting. If it's not interesting, I'll go work on something that is. Hence I can't remember the last time I actually used GetProcAddress, ha.

Re: dumplib: yes, it sounds like a lot of work, but it really isn't since you only have to do it once. But I'm guessing you're running a 64 bit OS? If so, start by ignoring all of the source code in the repository and just copy/paste the shitty hack batch file from the README into a new file. Run dumplib.bat C:\Windows\System32\ntdll.dll ntdll.dll This will produce a .lib file, but more importantly the .def file which you can now customise completely for your needs. When you're done, simply run lib again (exact command is in the batch file) and you get the new import library. 32 bit files are more of a PITA due to extern "C" name mangling; you'll want to use dumplib or similar to generate x86 import libs.