You should not confuse "code protection" and "code obfuscation". "Code protection" techniques target in recognizing code modifications (you mention a checksum) and take suitable means like crashing or delivering wrong results when tampering with the code has been recognized. Anti-Debug measures also belong into this category.
"Code obfuscation" in a binary targets in blinding the investigator (and the disassembler) with awkward and often useless code, ranging from simple jmp chains to more complicated constructs. Most code obfuscation techniques result in some code bloat, and the tradeoff between the obfuscation and the performance must be considered. Here are two obfuscation examples being found "in the wild".
- Dummy code: There are many means to insert nops in binaries. Examples range from simple statemens like shr eax, 0 or (in the 32-bit world) shl bx, 20h to more complicated constructions like the following example. The combination of jz and jnz followed by garbage statements (cpuid, ret), avoiding a jmp often confuses disassemblers in their ability to display logical assembly blocks.
mov si, si
mov esp, ebp
xchg edi, esi
mov cl, cl
xchg esi, edi
mov di, di
jz loc_dcba ; followed by a jmp to loc_abcd
In the example, the only "real" statement is the mov esp, ebp.
- Complicate simple assembly constructs. You may write a simple jmp as a combination of a push and a ret. Or, if you don't like the "ret" statement, you may replace it by (in 64-bit code):
lea rsp, [rsp+8]
jmp qword ptr[rsp-8]
Many more, and much more complicated examples can be found. If you want to dive into this matter, you need practice, as in all SW reversing. Get a protected and/or obfuscated binary like a game or a dongle protector and train. In the literature (e.g. the excellent IdaPro book by Chris Eagle) you may find some obfuscation constructs as well.