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.
> 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.
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!