📜 ⬆️ ⬇️

We intercept the launch of any application in Windows and try not to break anything.

If you do a lot of debugging Windows applications, you may have heard of such a wonderful mechanism as Image File Execution Options ( IFEO ). One of the opportunities it provides allows you to debug the application in conditions more close to combat. By writing a special key to the right place in the registry, we can automatically launch the debugger instead of the program, allowing it to do its own debugging tasks. However, who said that this mechanism (in fact, launch interception of anything) can only be used for such purposes? This article is not about using things as intended.


Under the cut there is a story about how I tried to squeeze more out of this mechanism for my good purposes, and what pitfalls I encountered on the way. And here I have good, selected stones.


Generally speaking, this idea is not new. I know at least three programs that use this mechanism for non-debugging purposes: the well-known Process Explorer and Process Hacker - to replace the standard task manager; and AkelPad - to replace the notebook. But I decided to go a little further.

So, what we have: we can run our program instead of any other pre-specified, if we pre-register ourselves in the registry. And it is not particularly important who, how, and with what rights will run it.


What can be useful to do with such capabilities? The first thing that came to my mind was to ask the user - does he really want this process to start? Having quickly figured out how to register myself in IFEO, I made a tiny utility with a dialog box and Yes / No buttons, which starts the intercepted program only with the user's consent. Immediately after the launch, I realized the depth of my naivety. Already guess what happened when choosing "Yes"? Of course, I launched myself again and received a new window asking for confirmation, and here you can continue indefinitely. Interception worked fine.


Not sure how useful this mechanism would be if it were not possible to bypass it: the debugger needs to start the process being debugged. So in Image File Execution Options there is one exception. Let's start with the fact that in user mode there are only two methods for starting processes: CreateProcess and ShellExecuteEx . And then, the second, in the end, also calls CreateProcess, but more on that later. And so, the exception is that process starts with the DEBUG_PROCESS flag are not intercepted. This solves the problem with debuggers, but, in my case, it also means that you cannot fully rely on IFEO. And yet, I do not know of any programs (except debuggers, of course) that would use this flag. You can not just put this flag and enjoy: you need an additional code to make it work.


Register file in IFEO


If you follow Process Monitor 'and what happens when you call the CreateProcess function, you will probably find a lot of interesting things. In addition to correcting errors in the file name, which we will talk about later, you will find access to the registry for IFEO settings. What it looks like:



If this key is found, then the presence of certain fields, including the Debugger string field, will also be checked. It is what we need. It stores the path to the debugger, or, in our case, to the program being launched instead of this one.


[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\YourExecutable.exe] "Debugger" = "C:\Path-to-debugger\Debugger.exe" 

It is worth noting that there are limitations here:



Details about UseFilter
To use filtering in the IFEO \ YourExecutable.exe branch, you need to create a DWORD field UseFilter with a nonzero value. In this case, when trying to create a process, it will bypass all the sub-keys in the given branch, and in each of them it will check the value of the FilterFullPath string field with the full path to the executable file. If there is a match, the executable found in the Debugger field will be launched. If no match is found, the default Debugger will be launched (that is, the one that would be used without any filters).

Note: any sub-key without the FilterFullPath field is treated as a match, and all settings are taken from it and take precedence over those that would be without a filter.

Assigning standard actions


The initial meaning of this venture for me was that, having learned about IFEO, I decided to write a program that would offer the user a set of typical actions (implemented in the form of small utilities) assigned to other programs. It was before I decided to write an article, realizing how many interesting things there can be told. Therefore, we use these utilities as a clear and practical example for narration. So, what was enough of my imagination:




To register these actions, two versions of the program presented in the screenshot were written: GUI and console. There is nothing to talk about here. Just read / write to the registry.


As you can guess from the title, the most interesting thing is how to substitute someone else's program during the execution, and without breaking anything. But first, a few obvious, though necessary things. If we even launch the intercepted process, then we need to reproduce as best we can the same conditions under which we launched ourselves. Which means:



Good. And how do we know what to run?


Evaluate the possibilities


Suppose that in IFEO as a debugger for the program A.exe the following is written:


 "C:\My-Path\B.exe" /param 

If someone tries to create the process "C: \ Folder \ A.exe " with the -a parameter, the B.exe process will be created with the following command line:


 "C:\My-Path\B.exe" /param "C:\Folder\A.exe" -a 

Since we ourselves write the code for B.exe , we will easily analyze the command line into its component parts and we can easily run A.exe with the same parameters with which this nameless someone wanted to launch it. This is where we have complete freedom. We want - we run with other privileges, we want - we can change the transmitted parameters.


The curious thing about all this is this: how do you think the control of user accounts responds to such an arbitrary appeal? The correct answer: but no way. If you select “Run as Administrator” in the explorer on the A.exe file, then the UAC message will display information about the A.exe file, and its digital signature will determine the window color. And the fact that B.exe will be launched instead is the tenth thing. No, there are no special security issues here: writing to IFEO itself requires administrative privileges. For us, this means something else: by writing our utilities in IFEO, we generally will not confuse the user with incorrect accounts control messages. After all, from his point of view - the way it should look.


How to start processes


With the advent of user account control in Windows Vista, starting processes has become more complex. The fact is that now the call to CreateProcess may be unsuccessful due to the lack of rights to run some programs. In this case, GetLastError returns ERROR_ELEVATION_REQUIRED . For such a case, even a special fix for compatibility issues is built into Windows, although I never noticed that it fixes at least something. Modern programs, in response to this error, should call ShellExecuteEx with the action "runas" to request elevation of privileges. This means that the typical code for creating a process now looks like this:


 if not CreateProcess(…) then else if GetLastError = ERROR_ELEVATION_REQUIRED then ShellExecuteEx(…) //   "runas" else //    

Since our utilities should always work, they will not require elevated privileges to run, which means that the process that tries to start A.exe (and instead starts our B.exe ) will never get ERROR_ELEVATION_REQUIRED. It seems to be: okay, okay, we ourselves can request elevation of rights for it, if necessary. Now imagine what happened. Someone started A.exe , we started instead, and since A.exe is demanding of privileges, we cannot start it with CreateProcess, and therefore we must use ShellExecuteEx. Already guessed? ShellExecuteEx is always intercepted by IFEO, there is no DEBUG_PROCESS flag that could save us. As a result, we will launch ourselves again. True, this time we already have enough privileges to use CreateProcess to run A.exe . And all this without any visible traces from the UAC! You can break the brain, is not it? I myself am not fully accustomed to the concept of "almost comprehensive interception of my own actions."



For this reason, in the Elevate.exe utility, you cannot do with ShellExecuteEx alone — we simply cannot get around IFEO. In the case of Ask.exe, this adds another problem. Here we asked the user, he confirmed the launch. And then ShellExecuteEx in combination with IFEO, we launched ourselves again. What, again we will ask? I had to add suppression of the secondary issue. But this is impossible to do by simply adding a special parameter: wherever we add it, we are still going to run A.exe , so that it will be indistinguishable from its own parameters. This is a very good exercise for the mind - to try to predict in advance all these difficulties.


Difference CreateProcess from ShellExecuteEx


Have you read the documentation for CreateProcess? There is one thing that many miss, potentially creating some vulnerability in the security of their applications. The same applies to the obsolete WinExec function, which is a wrapper over CreateProcess. The first two parameters of the function determine what will be launched with which arguments. These are lpApplicationName and lpCommandLine . Here is a translation of a piece of text from MSDN:


The lpApplicationName parameter can be NULL . Then the name of the executable file must be the first space separated by a substring in lpCommandLine . If you use long file names that may contain spaces, use quotation marks to indicate where the file name ends and the arguments begin. Otherwise, the file name is ambiguous.

Why do people keep forgetting to put quotes? This is how it works - an error correction mechanism is built into CreateProcess. Set lpApplicationName to NULL and try to start the program by passing the name of its executable file to lpCommandLine : C: \ Program Files \ Sub Dir \ Program Name . What will CreateProcess do? Search the line for what you can run until you find it, and also substituting the file extension:


C: \ Program Files \ Sub Dir \ Program Name
C: \ Program.exe Files \ Sub Dir \ Program Name
C: \ Program Files \ Sub Dir \ Program Name
C: \ Program Files \ Sub.exe Dir \ Program Name
C: \ Program Files \ Sub Dir \ Program Name
C: \ Program Files \ Sub Dir \ Program.exe Name
C: \ Program Files \ Sub Dir \ Program Name

If you want to experiment - create the file C: \ Program.exe on your computer and see if any of the programs fall into this. I caught up with Process Hacker (the fix is ​​already in the nightly build), Punto Switcher (I should write them too), and one of the plugins for Far Manager. By the way, Windows Explorer also knows about this problem:



Returning to the question: what does this mean for us? From IFEO, we get exactly lpCommandLine . Yes, we can simply pass it to CreateProcess - if there was such a problem there - it will remain, here we are powerless. But we may also need to pass it to ShellExecuteEx, and there, oops, there is no such error correction. There are separate lpFile and separately lpParameters . You will have to independently parse the string by spaces and search for the name of the first existing executable file, just as CreateProcess does. Super.


And now we are a little distracted and talk about other curious things. I'm going to talk about how one of the utilities works.


Lowering privileges


The principle “Run any program with the minimum necessary rights” is probably familiar to everyone here. But what to do if the author of the program you like has not heard about this principle - the question is already more complicated. Sometimes in these situations, mechanisms for resolving compatibility problems are saved:


 SetEnvironmentVariable('__COMPAT_LAYER', 'RunAsInvoker'); 

This significantly reduces the number of times CreateProcess cannot start the program and returns ERROR_ELEVATION_REQUIRED. But this does not always work. For example, over this method have the priority * .sdb patches .


There are also special utilities that can run processes with an Elevated token (that is, as if on behalf of the administrator), but cut out all the relevant privileges . These are DropMyRights , written by Michael Howard from Microsoft , and PsExec from the well-known Microsoft Sysinternals kit, launched with the –ℓ key. Oddly enough, but these utilities achieve their different ways.


I prefer the method used in DropMyRights. Yes, and its source code is open. The Windows Safer API is used there, which allows literally in a few lines of code to calculate a token with reduced privileges, which can be immediately used in CreateProcessAsUser . Eh, I would like all the installer writers to know how easy it is, and not to run programs with maximum permissions after the installation is complete ...


And now let's combine both mentioned approaches, combining them with IFEO. As a result, we get an automatic downgrade and a minimum of requests for account control. I do not know about you, but I really like it. And since elevation of rights by calling ShellExecuteEx is always intercepted, then, as far as I understand, the program under the influence of our utility has no chance to get out on its own until the user confirms UAC for another executable file that is in cooperation with the limited . But for really serious cases - use sandboxes like Sandboxie. Or virtual machines, for that matter.


IFEO bypass


Let's return to the main topic. I am saying everything, they say, we will launch the process with the flag DEBUG_PROCESS and we will be happy, we will not get ourselves into our rake. But in order to make it work, something else needs to be done. With this flag we will start the process under debugging, which means we will receive debug events. If they are not processed, the process will remain motionless. To process them, you need only two functions: WaitForDebugEvent and ContinueDebugEvent .


However, not all programs normally belong to debuggers, right? Not that I specifically made an exception for the sake of viruses, but disconnecting the debugger would really be a better idea. We are not here to write a debugger. And here, unexpectedly, comes the difficulty: this action probably refers to undocumented features, because I did not find it on MSDN. Therefore, we will use the Process Hacker documentation. So, we need the NtRemoveProcessDebug function from ntdll.dll . It also needs a DebugObjectHandle , which can be obtained using NtQueryInformationProcess , requesting ProcessDebugObjectHandle = 30 from it as a ProcessInformationClass . That's all. Although ... It is better not to do so with other people's processes: their debugger can wait for the debugging event at this moment, and for them the kill-on-close can be enabled.


UPD.
I was wrong, and therefore went the more difficult way. There is a documented function for this, this is DebugActiveProcessStop . It is recommended to use DebugSetProcessKillOnExit with it.

It is worth noting that there is a feature related to the bitness of the operating system: a 32-bit process cannot start a 64-bit process with the DEBUG_PROCESS flag. But 64-bit can run anyone.


Oh yeah, do you still remember? Above, I promised to tell you about another trick that allows you to achieve a slightly better compatibility with the subsequent launch of the intercepted processes.


Account Control Magic


I have already said that account control does not react at all to what is happening. And if you even then asked the question "Why?", Then you are definitely here.


How does UAC work and how does it allow privilege escalation? Well the answer to this question is given in the English article Vista UAC: The Definitive Guide . In short: ShellExecuteEx, making its way through the jungle of COM calls, calls the AppInfo service. That creates the consent.exe process, which displays the window with a request to confirm the launch, and, if necessary, enter a password. The creation of the process itself, naturally, occurs after all this. And there is used the most common CreateProcessAsUser . It is at this stage that the IFEO is triggered, and, intercepting the creation of the process, it launches us. That is why account control knows nothing about this.


The most shrewd ones should have asked: if the process is created by someone else, then how is it that his parent process still turns out to be the initiator of the request?


Starting from Windows Vista, you can use STARTUPINFOEX instead of STARTUPINFO in CreateProcess (if you use the EXTENDED_STARTUPINFO_PRESENT flag). It contains all the same information + lpAttributeList with a list of process attributes. This attribute list must be created by calling InitializeProcThreadAttributeList , and then updated by UpdateProcThreadAttribute . You can replace the parent process by specifying the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS flag and providing a handle to the desired process. What is there to remember:



A ready C # example is on StackOverflow .

Do you often when analyzing, for example, suspicious activity on a computer, proceed from who started what process? Oh, now you definitely won't do that.


There are some interesting features. I remember a humorous phrase from one cartoon, it sounds something like this: “Why, even at my birth, none of my parents were present ...”. Oddly enough, but something like this is possible here. If between the call to OpenProcess ' a and the creation of the process itself, the assigned parent will be completed - no problem. It is just that the child process will be “as if” created by the parent upon completion. Why not.


Everything is good, but this trick will not work with ShellExecuteEx - everything is simple: the one who caused it, that is the parent. However, since we have IFEO enabled, the final word is always left behind CreateProcess: without it, we cannot get out of the interception. So, walking along the process chain, you can find the one who started all this initially, and appoint him as the parent. Perhaps the result is now not so beautiful in Process Explorer and Process Hacker, but from the point of view of someone else's process, everything will be fine. Well, with the exception, perhaps, of "illegitimate" children who appeared at start-up and are awaiting the return code of the intercepted process. There's nothing to be done.


The picture shows how I run nsx.exe from Far.exe , to which the Ask.exe action is assigned , and which requires elevated privileges to run.



To be honest, I thought that the idea of ​​replacing the parent process is just another cool opportunity, not more. However, she did fix a few problems. So, the same Far Manager can create a second process to perform more privileged actions, and in the presence of interception, this functionality was broken. The substitution of the parent again fixed it.


What not to do


I didn’t dare to check it on my computer for a long time, because it was too lazy to deploy a virtual machine: what will happen if you install it yourself as a program launched during interception? Fortunately, no big deal. But then I decided to be prudent and did not test on my computer hypotheses like “oh, and if you try to create a cycle A → B → A ...”. Also, I think you should not try to intercept important system processes (I embedded their list in the registration utility with confirmation of the action). Although, I am curious about what will happen. For everything that I ran on behalf of SYSTEM was intercepted. So I added suppression of all dialog boxes in zero session, well, so as not to depend in such cases on the UI0Detect service.


One last thing: I would not recommend putting actions on programs that create many processes of the same type. One thing is a couple of additional processes, another is whole hordes. , . , . , : chrome.exe . : . .



Windows . Native (), Windows CUI ( ), Windows GUI ( ). — , . — : ? , , — . , GUI : svchost.exe , Windows GUI.


. , , . , . ? , . , Application Compatibility Toolkit -, , , , « ». , . , . , : , - , , . , . , Delphi — , : 02 03 0x15C.


Conclusion


, « 1001 Windows» . , Delphi, WinApi, , , .


. Windows 7 , Windows Vista — . — .


P. S. , - — , , . Thank.


')

Source: https://habr.com/ru/post/335736/


All Articles