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.