0

Let's say I have contents of an executable (or a bat script, doesn't matter) in memory and want to run it as a new process. That is easy.

File.WriteAllBytes(filePath, contents);
// gap
Process.Start(filePath)

But I want to make sure that the executed file is not tampered by any other process. And there is a gap between file creation and execution. It gives a chance to tamper the file with the right tools.

So, instead of File.WriteAllBytes, I went with opening a FileStream FileShare.Read and keeping it open until the execution has finished.

using(var fileStream = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
{
    Process.Start(filePath)
}

But this doesn't work. Process.Start fails with:

System.ComponentModel.Win32Exception (32): The process cannot access the file because it is being used by another process.

This question and its answer explains why I think. In a nutshell, Process.Start will attempt to open the file with FileShare.Read and fail because the open FileStream already has Write Access, hence failing the FileShare.Read attempt of the process.

Is there a way to do this cleanly?

A workaround I can think of is to save the file, close it, open a new FileStream with FileShare.Read and FileAccess.Read, make sure the content is still the same before executing it. But that's not pretty.

Natan
  • 2,816
  • 20
  • 37
  • 1
    How long do you think that `// gap` is? I mean, you could add a `FileWatcher` on that file after creation, and if anything other than the created process opens it, throw an exception, but it seems kind of paranoid. But I guess, "just because you're not paranoid doesn't mean they're not out to get you" :). – Heretic Monkey Jan 12 '21 at 17:23
  • 3
    "tampered" is vague in your question. What kind of tampering are you hoping to prevent? For example, to make sure you can write a file and execute it before the virus scanner gets a chance to scan it for malware? Maybe you can see that this is not intended to be possible. – Wyck Jan 12 '21 at 17:25
  • You could try to execute a process from a byte array: https://stackoverflow.com/questions/3553875/load-an-exe-file-and-run-it-from-memory – Dmitry Jan 12 '21 at 17:26
  • 1
    You could launch the process with its main thread suspended by calling CreateProcess with `CREATE_SUSPENDED`. Then compute the checksum of the image file. If it matches, then resume the main thread. If it doesn't, then terminate the process. The operating system prevents the image from being changed once the process is executing making this atomic. – Wyck Jan 12 '21 at 17:35
  • I suggest an alternative strategy: add a cryptographic signature to the file, so you can verify it has not been tampered with, regardless of how much time has passed. You could, for example, write the SHA-2 hash of the file to a second file, then cryptographically sign that second file. You can then verify the hash matches. –  Jan 12 '21 at 19:19
  • @Wyck sorry for being vague about tampered. It is the paranoia Heretic Monkey mentioned. It is to protect against a malicious process watching and changing the file during the gap and making our process run something else than expected. There are of course many layers to this "vulnerability" but that would be a bigger discussion, probably better suited to "Information Security Stack Exchange". – Natan Jan 13 '21 at 10:15

1 Answers1

1

What you are describing is a classic case of Time of check to time of use vulnerability.

Any solution that involves checking something and then executing it, and where those two operations are not atomic, will still leave you vulnerable. For example:

make sure the content is still the same before executing it. But that's not pretty

There's still a (smaller) gap (timing window) between the "make sure the content is still the same" and "executing it".

In 2004, an impossibility result was published,showing that there was no portable, deterministic technique for avoiding TOCT-TOU race conditions

- https://web.cecs.pdx.edu/~markem/CS333/handouts/tocttou.pdf

You can do a couple of things to mitigate it:

  • Don't use files! You say you have some code in memory that you need to execute: can you execute it yourself in the same process?

  • Reduce the window of time.

  • Make the file name random and hard to predict for other processes.

  • Run your program as a separate user where there's less likelyhood an attacker (or malicious program) is running, and restrict the file read/write to the new user only.

omajid
  • 14,165
  • 4
  • 47
  • 64
  • Hi. Thanks for summarizing the problem and providing some good alternatives. Unfortunately not suitable to my use case for various reasons, but still quite educative. Regarding the gap between "make sure the content is still the same" and "executing it"; in that case I can execute the program while keeping the FileStream with FileShare.Read from the beginning. So there can't be any tampering or am I missing something? – Natan Jan 13 '21 at 10:22