Some time ago, I noted that any sufficiently advanced uninstaller is indistinguishable from malware.¹
During one of our regular debugging chats, a colleague of mine mentioned that he was looking at a mysterious spike in Explorer crashes. He showed me one of the dumps, and as soon as I saw the register dump, I said, “Oh, I bet it’s a buggy uninstaller.”
The tell-tale sign: It’s a crash in 32-bit Explorer on a 64-bit system.
The 32-bit version of Explorer exists for backward compatibility with 32-bit programs. This is not the copy of Explorer that is handling your taskbar or desktop or File Explorer windows. So if the 32-bit Explorer is running on a 64-bit system, it’s because some other program is using it to do some dirty work.
But out of curiosity, I went to look at why this particular version of the buggy uninstaller was crashing.
This particular uninstaller’s injected code had a loop where it tried to do some file operations, and if they failed, it paused for a little bit and then tried again. However, the author of the code failed to specify the correct calling convention on the functions, so instead of calling them with the __stdcall calling convention, it called them with the __cdecl calling convention. In the __stdcall calling convention, the callee pops the parameters from the stack, but in the __cdecl calling convention, the caller pops them.
This calling convention mismatch means that each time the code calls a Windows function, the code pushes parameters onto the stack, the Windows function pops them, and then the calling code pops them again. Therefore, each time through the loop, the code eats away at its own stack.
Apparently, this loop iterated a lot of times, because it had eaten up its entire stack, and the stack pointer had incremented all the way into its injected code. Each time through the loop, a little bit more of the injected code was being encroached by the stack, until the stack pointer found itself inside the code being executed.
The code then crashed on an invalid instruction because the code no longer existed. It had been overwritten by stack data.
This left an ugly corpse behind, and so many of them that the Windows team thought that it was caused by a bug in Windows itself.
¹ The title is a reference to Clarke’s Third Law: Any sufficiently advanced technology is indistinguishable from magic.
I understand very much what's going on here. The real question is does Raymond want to keep complaining about it or does Raymond want to actually do something about it.
So far Raymond has published the following bad solutions:
1) .js script for self-deleting uninstaller
2) .vbs script for self-deleting uninstaller
In the same vein there is a batch script that works the same way.
The problem with all three of these is they are frequently blocked by GPO.
I have analyzed the copy-to-tempdir EXE trick, and it's a pending security disaster. The problem is the garbage that builds up in the temp...
I have analyzed the copy-to-tempdir EXE trick, and it’s a pending security disaster.
Raymond Chen has already published articles on this topic.
The TEMP directory is like a public hot tub whose water hasn’t been changed in over a year
How can I try to escape the disease-ridden hot-tubs known as the TEMP and Downloads directories?
Typically, people create a new uniquely named folder in the temp directory, which avoids such issues.
If I were to write an uninstaller, I would copy it to the temp folder and relaunch it from there. Programs that abuse debugger APIs like this are terrifying; they are always a source of system instability.
Yeah, the delete-on-close and relaunch dance is a very well-known trick at this point, I guess the worse solutions get better search ranking though…
Did some Googling and found the original code, it starts out by launching a copy of explorer.exe with the CREATE_SUSPENDED flag. I wonder if it would be worth having Windows track whether a process was launched with CREATE_SUSPENDED and including that information in the crash dumps? Might help you triage these cases more easily.
> The code then crashed on an invalid instruction because the code no longer existed.
Man, can’t these developers have the courtesy to at least mark their code as read-only? The uninstaller would still have crashed Explorer, but at least an attempted code overwrite would be easier to debug if that’s the operation that triggers the crash.
We are talking about people who get the calling convention wrong. I don’t think they have the skill level required to deal with read only code.
Hi, Raymond
I don’t often have enough background to understand your posts, but I enjoy them nevertheless. Especially the special vocabulary ones, microspeak.
I’m not knowledgeable about Windows interior functions. But knowing a very little bit about memory management, why was the code or injected code in the same memory space as the stack? Maybe it’s because of the obsolete 32 bit Windows kernel wasn’t separating stack memory from code memory?
Thank you!
No, this is different. The injected code didn't do something as nice load a library through the Windows library loader or anything like that. The uninstaller developers most likely just worked out the binary for their code that was either position independent or targetting a specific base address, allocated some memory in Explorer, wrote the code to that block of memory and then executed it. However, the developers didn't protect the memory. This makes it possible that the block of memory allocated just happened to be very close to the stack. Since Windows wasn't really aware of the importance of...
I found a description of the original code online and they are quite deliberately injecting the shellcode onto the stack. I’m not sure why they didn’t allocate memory for it; perhaps the stack was just the first thing they thought of.
This particular code-injection technique deliberately puts the injected code on the stack. It isn’t anything Windows is doing, whoever wrote the original code was abusing technology meant for use by debuggers,
and the particular state the target process is in when the code is injected made the stack the only available place for them to put it.Edit: I think I was wrong about that; they probably could have chosen to allocate separate memory for the shellcode, but they didn’t.