Splashscreens can be important for demos and freeware applications, since advertisement is often the only reward the developer gets for them. But splashscreens are very easy targets for crackers. They often don't care about the reason behind 'nag'-screens...
But since we are the developers, we should protect ourselves from them. While it is impossible to create uncrackable code, there are some nice anti-crack tricks. You just have to anticipate on what the cracker will do. The primary tool a cracker uses is a debugger, or similar program which allows to see the application's assembly code in a convenient format. A debugger also automatically translates binary addresses into readable strings, so-called symbols. The trick in this code spotlight attacks exactly those symbols.
Many splashscreens use a Windows dialog. It's easy, just create a dialog resource, copy your picture into it, add some copyright notice, call CreateDialog, and you're done. Crackers know this. They especially know that you're going to call CreateDialog. So even a newbie cracker can place a breakpoint on this function, and wait till it gets called by your application. He doesn't have to understand a lot of assembly code to find exactly where you call CreateDialog. He removes that call, maybe remove a simple timer, and he's done!
Their strength, namely being able to place a breakpoint at CreateDialog, can be turned into a weakness. We can't change the fact that they'll place a breakpoint at the address of CreateDialog, but we do can prevent that breakpoint from being hit! Here's the trick: skip the first instruction(s). Sounds crazy? It isn't. All we have to do is compensate for the skipped instruction. Luckily, for CreateDialog, I'll show you this is easy...
First you have to know that CreateDialog actually is a pointer to another function, called CreateDialogParamA. You can easily see this by (ironically) placing a breakpoint at CreateDialog, and tracing through the assembly code. It doesn't take a lot of assembly knowledge to see that it actually translates into a call to CreateDialogParamA. This is also the function the cracker will really place a breakpoint on. Now, if you trace into this function, you'll find that the first instruction is "mov edi, edi". It should be clear that this instruction does... nothing! And if you look at the binary code, you'll see this instruction takes two bytes. So we can just skip these two bytes and execute our function from there. The newbie cracker will never notice this, since even in our code it won't find the CreateDialogParamA symbol any more.
But how do we do that? Don't panic, there's no self-written assembly code involved here, just some advanced C++. To summarize, what we really want to do is call CreateDialogParamA plus two bytes. Unfortunately you can't just write it in C++ like this:
HWND splash = (CreateDialogParamA + 2)(appInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, (DLGPROC)DefWindowProc, 0);
The C++ compiler doesn't allow you to add offsets to functions, which is of course a good protection. But we can easily fool it, by casting CreateDialogParamA to an integer, adding two, and then converting back to a function pointer of the same type as CreateDialogParamA:
HWND splash = ((HWND (__stdcall *)(HINSTANCE,LPCSTR,HWND,DLGPROC,LPARAM))((int)CreateDialogParamA + 2))
(dllInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, (DLGPROC)DefWindowProc, 0);
This looks quite complex because I splitted it over two lines, but it's really not that hard. The first line casts to integer, adds two and casts back to the function type. The second line is simply the argument list.
Now keep quite so the crackers don't learn about this trick...
Well, don't get too excited. This trick will only stop the newbie cracker. An experienced cracker will sooner or later figure out what happens. Either way, this simple trick can become a powerful weapon. Every method that fools the tools they use, make it much harder for them to do their 'job'. Assembly code without symbols is almost just as useless as looking at a sequence of zeros and ones.
With many functions you won't be so lucky that it starts with an instruction that does nothing. Most start with "push ebp", one byte long. It modifies the stack so you can't simply call one byte further. But it's not impossible. A "call" instruction only places the return address on the stack, and then performs a "jmp". So let's just do the same thing, using inline assembly:
This is a bit more advanced, but applicable to most standard functions. It can also be used to skip bigger parts of functions. And the debugger's call stack will be messed up because it won't detect that the jmp actually replaces a call.
Nicolas "Nick" Capens