Exploration of svchost.exe /P flag

Hey there!

In this blog post, we are gonna take a look at the mysterious “/P” flag of svchost.exe. TL;DR: P flag enforces different policies: DynamicCodePolicy, BinarySignaturePolicy and ExtensionPolicy.

So first of all, what is svchost.exe? Following Wikipedia “svchost.exe (Service Host, or SvcHost) is a system process that can host from one to many Windows services in the Windows NT family of operating systems“. It’s basically the loader of your different services.

Before diving into svchost with Cutter (or your favorite disassembler/decompiler), it’s good to have the PDB of executable beforehand. PDB is short for “Program DataBase” and is storing the debug information of an executable. You can have it with PDBDownloader.exe (which you can grab here https://techcommunity.microsoft.com/t5/iis-support-blog/pdb-downloader/ba-p/342969) Microsoft lets you access some of the debug information for its different component so you can debug them with Windbg or import them in IDA which is cool. It’s important to note that you don’t have access to ALL the debug information. Only the symbols that Microsoft made public.

Now can begin the fun part. So, this static analysis was made with Cutter (GUI for radare2). Beforehand, you have an option to select a PDB file to load when you select advanced analysis. If you don’t do that, no worries, you can add it after.

We are first landing in the entrypoint of our executable. You can see some common functions for an PE executable. A lot of them are just boilerplate like initialization of the arguments, security cookie, … We can go ahead and take a look at wmain.

There is not a lot going in wmain. The only function that could be interesting is InitializeSvcHostLib. The others seem to register a new service, set up some timer and wait for it.

Now, this begins to be a lot more interesting. We see multiple calls there but what seems to be relevant for our goal is the GetCommandLineW call followed by BuildCommandOption. Surely BuildCommandOption should help us.

BuildCommandOption is a lot more dense than the other functions so we need to take it piece by piece. From the previous function, we could see that the parameter passed to it is the return value of GetCommandLineW which from https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getcommandlinew is the command-line string for the current process (surprising for a function called GetCommandLineW). Now, when we follow the parameter inside BuildCommandOption, we could see something interesting

The parameter is copied with memcopy to “ppiVar5 + 0xf” and with ppivar5 the result of HeapAlloc. From these two elements, we can say that ppiVar5 is a structure or an object. So we can try to create a structure in the type menu. Moreover, when looking at the end of the function, we can see that ppiVar5 is returned.

Because I’m leazy, I won’t reverse engineer the whole function. By looking roughly at the function we can see some interesting things. We see a bunch of operations with familiar values like “0x20” (space character), “0x2d” (minus character) or “0x50” (P character). Normally, you would think that you will find a switch case or multiple “if else if” comparing a character with different hexadecimal values. But not here. Weirdly, we find subtraction followed by a comparison with 0. At the end, it’s still a comparison but not as straightforward as one would imagine.

Because of these values, we can make an educated guess that iVar2 (which comes from piVar8) is pointing to a character from command line and this loop is parsing it.

Taking a closer look at this loop and particularly at the action made when the flag is found, we can see that our ppiVar5 is modified (at least different fields of ppiVar5 are modified). Here are the different cases:
– if K (0x4B) is set: *(ppiVar5 + 2) = 1
– else if S (0x53) is set: ppiVar5 is not modified, counter increases
– else if P (0x50) is set: *(ppiVar5 + 0x5c) = 1
We know that ppiVar5’s fields are set following the different flags that we put in our command lines. Officially we know that K is the service group and S the service from this group. But no information about P. At the end of this function, our ppiVar5 is a structure looking like that:

struct cmd_parsed {
    int64_t field_0;
    int64_t field_1;
    char k_flag;
    char gap_0[75];
    char p_flag;
    char gap_1[27];
    char *argv;
};

So know, we can step back a little bit and come back to InitializeSvcHostLib. We see that the return value of BuildCommandOption is iVar4 and it’s passed to CallPerInstanceInitFunctions and so is our next stop.

The first thing that we see is that Ghidra plugin for Cutter couldn’t decompile this function so we need to rely on the other plugin r2dec and disassembly. The second thing is that no cmd_parsed field set from BuildCommandOption are used but totally new ones… Hum weird. Maybe it’s time to step back again and look at our code.

In InitializeSvcHostLib, we can see that the pointer to the object returned by BuildCommandOption (so in rax) is immediately copied to rbx. Now, if we follow rbx a little bit, we can see that its content is copied to rdx just after BuildServiceArray. Meaning that it’s in fact a parameter that Ghidra didn’t recognize.

Let’s dig into BuildServiceArray. By modifying the function in order to use the ms calling convention (passing the parameter to rcx, rdx, r8 and r9 as described here https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019), we can follow param2 which is rdx (our cmd_parsed object).

We won’t be long in this function because param2 is quickly used by ReadPerInstanceRegistryParameters as the third parameter.

From this new function, we can see that other fields are set and used from our main structure depending on different values queries from the registry key Software\Microsoft\Windows NT\CurrentVersion\Svchost like CoInitializeSecurityParam or CoInitializeSecurityAllowLowBox. But more importantly, we see that this function accesses our field set by P flag (cmd_parsed.p_flag or ppiVar5 + 0x5c).

And as you can see, if the flag is set and the handle to the registry key is 0 (iStack664 holds the handle to the registry key), *(ppiVar5 + 0x60) is set. It’s interesting to note in what other occasion this field is modified. It is later set if the value DynamicCodePolicy exists. That’s interesting. So, maybe our P flag from the command line enforces the DynamicCodePolicy?

In the next reference to uVar17 (holding the value of our P flag), we see that the field *(ppiVar5 + 0x64) is set to 1. And this field is also set when BinarySignaturePolicy exists.

And last one, for *(ppiVar5 + 0x68) which is set when ExtensionPointsPolicy is set.

What does it mean for us? It means that if P flag is set, the different fields related to DynamicCodePolicy, BinarySignaturePolicy and ExtensionPolicy are set to 1. If it’s not the case, it depends of the different values found in the Registry Key.

At the end of this function, our structure should be like this:

struct cmd_parsed {
	int64_t field_0;
	int64_t field_1;
	char k_flag;
	char gap_0;
	char gap_1;
	char gap_2;
	char gap_3;
	char gap_4;
	char gap_5;
	char gap_6;
	int64_t lpValue_reg;
	int64_t field_3;
	int64_t field_4;
	int64_t field_5;
	int64_t field_6;
	int64_t field_7;
	int64_t field_8;
	int64_t field_9;
	char gap_7;
	char gap_8;
	char gap_9;
	char gap_9_0;
	char p_flag;
	char gap_10;
	char gap_11;
	char gap_12;
	char DynamicCodePolicy_flag;
	char gap_13;
	char gap_14;
	char gap_15;
	char BinarySignaturePolicy_flag;
	char gap_16;
	char gap_17;
	char gap_18;
	char ExtensionPointsPolicy_flag;
	char gap_19;
	char gap_20;
	char gap_21;
	char gap_22;
	char gap_23;
	char gap_24;
	char gap_25;
	char gap_26;
	char gap_27;
	char gap_28;
	char gap_29;
	char gap_30;
	char gap_31;
	char gap_32;
	char gap_33;
	char *argv;
};

Finally, we can go out of this function. BuildServiceTable doesn’t access our structure and CallPerInstanceInitFunctions is. When analyzing it and applying the different field offsets to rbx, we can see that they are checked and followed by a call to SetProcessMitigationPolicy which confirms that these fields set up the different Mitigation Policies! And this is the end of our journey into svchost!

If there is any question, remark or mistake, don’t hesitate to contact me on Twitter @redmed666

Leave a Reply