Impersonating

Impersonating a process identity is an essential feature that Windows users rely on daily.

This capability spans from Windows Services running local or domain accounts to the use of runas.exe, as well as other “magical” solutions that allow processes to run on behalf of other users.

When a child process needs to be created on behalf of another user and no suitable token is available, one method to achieve this is by using the CreateProcessWithLogonW() API function.

According to the official documentation, this function requires a username and password as parameters. This means that regardless of how the parent process stores or obtains the password, it must eventually be passed to the API function.

Consequently, an attacker or researcher does not need to reverse engineer the storage method; they can simply wait for the function execution and intercept the passwords when they are ready for use.

When it becomes necessary to observe function parameters, two practical methods can be employed. First, one can hook the function by creating a detour through custom code, which allows the parameters to be captured.

Second, a debugger can be used to set a breakpoint, halting the execution of the parent (debugged) process right before the function call, which means that all parameters are prepared for passing. Of course, the second method is useful only if you know how to locate parameters in the process memory.

The entire theory of parameter passing is generally referred to as the “calling convention,” which varies between systems, architectures, and other factors. In this case, it pertains to the x64 calling convention, which utilizes four registers and the stack for storing data. If the data fits into a register, it is stored there; otherwise, a pointer to the data is used when a parameter is too large to fit into a CPU register. The syntax of CreateProcessWithLogonW() includes the following parameters in order: username, domain, and password. The calling convention for LPCWSTR (Long Pointer to Constant Wide STRing) parameters passes the memory addresses in the RCX, RDX, and R8 registers, respectively.

And now, it’s time to put everything together:

1. Run your debugger and launch your application with the appropriate parameters.

2. The execution will break at the very initial phase of the process life.

3. Define a breakpoint using the “bp” command followed by the module (as specified in the documentation) and function names. In this case: “bp advapi32!CreateProcessWithLogonW”.

4. Now, let the program run (using “g” command) with the hope that it will prepare the password for use. Some programs (e.g. runas.exe) may require some interaction, so please interact the same way you usually do without debugger.

Keep an eye on the process, and when it hits your defined breakpoint, you should be able to observe the parameters and capture the password.

To read your password from the memory pointed to by R8 (the third parameter passed), you can use any of the following methods:

1. Generic: Use the command “db r8” to display the bytes at the memory address stored in R8.

2. More sophisticated, appropriate for Unicode Strings: Use the command “du r8” to display the Unicode string at the memory address stored in R8.

3. Manual: Use the command “rr8” to examine the contents of the R8 register; open the memory window in your debugger and copy the address from R8.

By utilizing any of these methods, you should be able to access and read the password stored in the memory pointed to by R8. This approach will help you observe the parameters being passed to the “CreateProcessWithLogonW()” function and read the password.

Good luck!