I recently had the enjoyably challenging opportunity to patch a very, very old bit of software to defeat some very basic copy protection. (DISCLAIMER: this post doesn’t describe how to circumvent any kind of new application or game protection, which is typically orders of magnitude more complicated than this old stuff.)
[UPDATE 6/17/2010: see links at end of post for assembly code references]
Twice now, I’ve found myself with an ancient, unsupported program that has a pointless copy protection mechanism in place. This particular one required two codes to be entered into a separate activation program, which would then create a separate hidden file that the main program would read to detect proper activation. Fortunately, I learned just enough in my Assembly class from Fullerton College that looking at a disassembled DOS executable doesn’t completely intimidate me. I don’t know most of it, but I do understand basic instructions like mov
, jmp
, jz
, and the use of registers and flags.
What’s nice about old software is that the activation code can usually be traced down to a single logical test, which is just a logical operation followed by a conditional jump. The trick is to find this code, and then replace the conditional jump with either a no-op or an unconditional jump, depending on whether the original code jumps to a failure or success location. I have used this technique to write a no-CD patch, and now also an activation bypass patch. You need a couple of tools like IDA Pro (not free) or PVDasm (free) for disassembly, and then another one like HxD (free) for straightforward hex editing.
Here’s some clipped disassembled code from this last project:
seg008:12D3 call sub_1967F
seg008:12D6 or al, al
seg008:12D8 jz short loc_1A7FB
seg008:1309 jmp short loc_1A808
. . .
seg008:130B ; ----------------------------------------------------------------
seg008:130B
seg008:130B loc_1A7FB: ; CODE XREF: sub_1A75B+6D j
seg008:130B mov al, 0
seg008:130D push ax
seg008:130E mov di, offset aActiveError ; "Activation Error."...
seg008:1311 push cs
seg008:1312 push di
seg008:1313 call sub_2C89A
seg008:1318
seg008:1318 loc_1A808: ; CODE XREF: sub_1A75B+9E j
seg008:1318 mov sp, bp
seg008:131A pop bp
seg008:131B retf
seg008:131B sub_1A75B endp
The important bit is the bold red at “seg008:12D6” and “seg008:12D8”. The “or al, al
” is the logical instruction which sets the “zero” flag if the “al
” register is equal to zero. Then the “jz
” instruction right after it is a conditional jump to that “loc_1A7FB” label shown below. That’s the part that displays the activation failure message, so I figured that conditional jump was the code to change. In machine code, it’s just two bytes: 0x74 (the jz
opcode) and a 1-byte memory offset that points to the new location. I just replaced those two bytes in the .exe file with 0x90 (no-op), and voila! Now it never, ever jumps to the error message, and instead always just continues.
Chalk another one up to machine language succeeding where high-level languages are unusable.
UPDATE 6/17/2010: I’ve found a couple of excellent reference guides that help when you need to make direct edits to machine code:
X86 Opcode and Instruction Reference Home (byte-to-instruction reference)
http://ref.x86asm.net/coder32.html
DOS INT 21h – DOS Function Codes (disk access and some I/O reference)
http://spike.scu.edu.au/~barry/interrupts.html