There are many forms of hacking/modding. The first, the easiest one, is usually by editing data files. This is easy to block - doing CRC check on files, but protecting your CRC check code itself is much more difficult.
The next way is through modifying the executable, either while it's running (in memory) or on the hard drive. Protecting data this way can be achieved by using clever duplicates or sums. But the problems occur when the executable code is modified. Of course, you can try to CRC this again, but how can you guarantee someone doesn't block that either?
There are easy ways to protect your code, and more difficult ones. Easy ways are to make different parts of code depend on eachother by setting different variables. Maybe even use function pointers which only get a value in the middle of a CRC check, or check the CRC result in many places, make the code a little self-modifying etc. There are many ways. You may wonder why I said this is the easy way. That's because...
... the hard way is to start modifying other processes or use drivers. I wouldn't suggest anyone to use these methods, but I'll explain them anyway. You can inject a piece of code into other processes which reroutes or modifies the OpenProcess and CreateProcess functions. On CreateProcess call you make sure the new process gets injected, and on OpenProcess call you block the calls trying to open your process. If something is messed in this stage, you can cause a system crash. The other way is to make a driver (all games installing drivers usually get a bad reputation though). Driver hooks calls to OpenProcess and/or WriteProcessMemory. If you mess something up here, you're doomed (BSOD).
There are plenty of ways to get some code injected into a process. The most popular ones except modifying the executable are modifying the game's DLLs or copying a modified system DLL into the game directory. These are not easy to detect.
Trainers usually modify the data/code in memory by finding the process with FindWindow or EnumProcesses, opening the game with OpenProcess, and then using WriteProcessMemory and ReadProcessMemory. Sometimes applications use their own API functions instead of these functions (usually a straight copy of the API function) to avoid game protection systems. This helps against injecting protection, but not against drivers.
When a memory scanner or a trainer has accessed the application, they can inject their code into the executable or inject a DLL. One simple example of DLL injection is ArtMoney (memory scanner & editor).
Nowadays there are quite few protection systems that have stayed unhacked or at least a workaround hasn't been published for them. These include the huge protection systems like StarForce. Personally I hate these systems, but if a multiplayer game needs protection, there aren't many other choices.
"The Internet treats censorship as damage and routes around it." - John Gilmore
It's quite possible to hook API calls such as OpenProcess, ReadProcessMemory/WriteProcessMemory at user level simply by overwriting the import table in memory and injecting a DLL via CreateRemoteThread.
Many good firewalls also protect from this kind of behavior, as well, by asking.
Like I mentioned, from user mode it means injecting some code into every process. Bypassing import address table hooks is very simple though (pseudocode):
So I think the more reliable way is to hook the function itself, not only the IAT.Code:fnWriteProcessMemory=GetProcAddress(GetModuleHandle("kernel32"),"WriteProcessMemory"); //this call bypasses the hook fnWriteProcessMemory(x, x, x, x, x);
"The Internet treats censorship as damage and routes around it." - John Gilmore