The fact that the code was encrypted meant the debugger couldn't disassemble it in any meaningful way, and also made it impossible to set a breakpoint (since the breakpoint would just end up being "decrypted" into some other opcode that would inevitably crash). The debugger also couldn't step through the code, because taking over the single step interrupt would prevent the decrypter from running, so you'd just be stepping through garbage.
The way I worked around this was by writing a debugger that could hook the single step interrupt in such a way that it still forwarded the interrupt onto the previous hook. I still couldn't set breakpoints, but I could step through the code, watching it decode itself as it proceeded.
For example, this is how the trace exception on the M68k works - the program counter of the next instruction to be executed can be read off the stack by the exception handler. The 6502 doesn't have built-in software single-stepping but the same effect was sometimes achieved by tying a short timer to the NMI - and when the NMI is asserted, the interrupted program counter is pushed onto the stack.
Breakpoints are also easy to spot. Just look for 0xCC in the memory. But of course there are also multiple ways to work around those checks (such as using harder to detect hardware breakpoints instead of software breakpoints).
Another trick to detect breakpoints as an anti-debugging measure was to compute a key over the block of memory that was to be protected. Any int3s in there would give a different output. A countermeasure was to use "memory breakpoints" which work via the x86 debug registers: https://en.wikipedia.org/wiki/X86_debug_register
http://pferrie.host22.com/papers/antidebug.pdf
For a fresh compilation (which includes the previous paper) you can check this article:
http://antukh.com/blog/2015/01/19/malware-techniques-cheat-s...
IsDebuggerPresent() is the most straightforward way: https://msdn.microsoft.com/en-us/library/windows/desktop/ms6...
In the specific case of x86 and int3 bps, you can abuse variable-length instruction encoding to jump into the middle of another instruction. Then code execution can differ depending on the presence of 0xcc.
Fun times then and now :)
i got my start in programming by trying to break various copy protection schemes in MS-DOS games. i used a software debugger that would set int 3 breakpoints, as the article mentions.
BUT, for the debugger to work, it has to rewrite the interrupt vector table so that int 3 instructions will cause the debugger's own code to be executed. really wily code would use the entry for int 3 in the interrupt vector table as a scratch variable to perform a bunch of its own calculations, perhaps thousands of them so you can't find and modify them all, which would ultimately wind up with an address to jump to, to continue execution of the program. this means that, when the debugger rewrites the int 3 vector, the program's internal calculations would be thwarted, effectively stopping the program at that point.
this was really difficult to get past. there were other people doing this kind of thing who seemed to find ways around this technique, but i never did.
If this kind of thing interests you check out 4AM's Apple II cracks. He's removing the copy protection on old software so the techniques aren't directly applicable to current machines, but there's a font of creativity in the code he's reverse engineering. https://twitter.com/a2_4am