How to code a COM - crypter
12/97 by UniquE
Hello all!
~^~~~~~~^~
This is my very first try to write an article for PAiN,
so check this out!
Maybe you realised that there are already much powerful protectors available
HackStop, Protect!, BinLock, ...).
So why shouldn't you use one of these for your productions? For sure, you
can do this, but there is a large number of unpackers for nearly any
well-known protector around in the net and on boards. With these tools,
any totally incapable pseudo "computer freaks" can "hack" your programs.
Maybe you realised that guys like Dark Destroyer, Beast, Moixa and
others protect their releases with some non-public-available protectors.
I 'll give you the basics for writing your very own COM crypter, featured
with:
- tiny size (+- 140 bytes)
- new, UniquE techniques against debugging, tracing,
analysing, disassembling, heuristic analysing, unpacking
- encryption (a shitty 16-bit XOR in 65'535 variations)
o The basic concept
First, we have to decide how we want to construct our little
protector. Let's say that we want to prevent automatic unpacking with
powerful tools like CUP386, common programs like
the heuristic cleaner TBCLEAN from TBAV and many others. Further,
we want to encrypt the original program for prevent simple
changes of copyright notes and other strings. We won't use
multiple encryption layers (like in my USCC) but we don't
let the whole decrypter code unencrypted. So we encrypt
a part of our decrypter who's executed _after_ the main-
decryption of the original COM file. Anything clear? No.
Good, just read on!
o The COM file structure
We have to know what we are going to do, so let's have a look
at the structure of a DOS COM program. The main difference between
a COM and an EXE program is the fact that a COM can only use ONE
segment for both data and code. Furthermore the stack is
downgrowing from the top of the same segment:
Offset What?
0000h..00FFh Program Segment Prefix (PSP), created by DOS. (P)
0100h.. Code and data area. (C)
.. Free space among the heap and the stack. (x)
..FFFEh The program stack, downgrowing to the code. (S)
Offset 0000F.....0100h...........................FFFFh
What? PPPPPPPPPPCCCCCCCCCCCCCCCCCCxxxxxxxxxxxSSSSSSS
If a COM files becomes executed, DOS reserves a whole segment of
memory (65'535 dec. / FFFFh). Then it creates a PSP between
offset 0000h and 00FFh. Next, it loads the data from the
COM - file 1:1 at offset 0100h in the reserved segment
(our codesegment CS is our datasegment DS and stacksegement SS,
so CS = DS = SS and also = ES). DOS puts the instruction pointer
IP at 0100h, because the code has been loaded at CS:0100h.
Before DOS gives control to the COM, it
sets the stack at CS:FFFEh for leaving a maximum of distance
between the code and the stack. Furthermore, all registers
(except SI, don't ask why) will be set to zero (0000h).
We have to ensure that all these conditions are still true
after our decryptioncode has been processed. If not, some programs
won't longer run. If you want to try your protector under
"hard" conditions, then process a program of the 1st coding competition
of PAiN. These programs take full advantage of the conditions
discussed right now.
o The encryption / protection
1st Read the file into memory.
2nd Save the first three bytes of the COM in the decrypter code.
3rd Recalculation of some offsets in the decrypter code.
4th We get a random encryption key and store it in the
decrypter, too (for decryption while running).
5th Append the decrypter to the COM who was loaded at "1st".
6th Finally, we encrypt the COM with a part of our decrypter.
7th We write the funny thing down to disk.
After the program execution, the segment looks like this:
Offset 0000F.....0100h...........................FFFFh
What? PPPPPPPPPPJJJXXXXXXXXXXXXXXXWWWVVVxxxxxSSSSSSS
P PSP
J JMP to "New_Entry_Point:"
X encrypted COM file
W encrypted part of the decryptor
V decryption code
x free space
S Stack
o The calculation and the encryption of the COM file
We said that we want to call our code by a JMP instruction
at the beginning of the file. So we have to save the bytes
who'll become overwritten by the JMP:
; save the first three bytes
mov ax, Word Ptr [Offset COM_Start]
mov bl, Byte Ptr [Offset COM_Start + 2]
mov Saved_Byte_1_2, ax
mov Saved_Byte_3, bl
Now, we put the JMP - opcode E9h at the first position of the
COM and calculate the location of the start of the decryption
code where he has to jump:
; write a JMP to our code at the begin of the file
mov Byte Ptr [Offset COM_Start], 0E9h ; JMP ....
mov ax, COM_Size ; we start at the end of the old COM
add ax, (Offset New_Entry_Point - Offset Decrypt_Start) -3
mov Word Ptr [Offset COM_Start + 1], ax
Using the technique of adding the size of the original COM file
to all operations that access an absolute memory address, we can ensure
that we won't get wrong positions. After that, a simple routine
copies the decrypter at the end of the original COM for that
it can be encrypted with a random number calculated from the
system time. The encryption loop can be done like this:
; encrypt the COM with a poor XOR - loop
mov cx, Crypt_Length ; get the length for the encryption
mov bp, Offset COM_Start + 3 ; begin _after_ the JMP!
mov bx, Decrypt_Key ; get the encryption - key
Encrypt_Loop:
xor Word Ptr[bp], bx ; encrypt NOW!
add bp, 02h ; to the next word...
loop Encrypt_Loop ; encrypt all!
o A closer look at some parts of the decryption code
We have to save all registers for the original program. This is
also simple and important, done in two lines:
New_Entry_Point:
pusha ; save all registers
push es
After that, we want to fuck up some poor techniques of code
analysing and debugging and not very versed people with some
code who was never seen before (I think):
We switch the keyboard off:
cli ; interrupts OFF
in al, 64h ; keyboard OFF
or al, 040h
out 64h, al
We amaze the world by writing _code_in_the_keyboard_buffer
who must be executed for ensuring the proper decryption of
the program. If you disassemble the keyboardbuffer, you'll
find these instructions:
pop bx ; We take the IP in BX...
inc bx ; ... and adjust the return-point.
inc bx
push bx ; Back to stack, for IRET
sub bx, COM_Size ; execute an important operation
iret ; Leave the keyboard buffer
For bringing this code at this place, we use these lines:
mov ax, 0040h ; BIOS segment address
mov es, ax ; ES = 0040h
mov Word Ptr es:[0020h], 0435Bh ; We write CODE in
mov Word Ptr es:[0022h], 05343h ; the keyboard buffer!
mov Word Ptr es:[0024h], 0EB81h
mov_es_24 db 26h, 0C7h, 06h, 26h, 00h ; (MOV WORD PTR ES:[0024], .)
Length_of_COM dw (Offset Division_Position - Offset Decrypt_Start) + 2
mov Byte Ptr es:[0028h], 0CFh
Then we change the interrupt handler of INT 00h (division-by-zero
interrupt) to our code in the keyboardbuffer:
xor ax, ax ; change the INT-table at 0000:0000
mov es, ax
mov cx, es:[0000h] ; save the INT 00h handler
push cx
mov cx, es:[0002h]
push cx
mov Word Ptr es:[0002h], 0040h ; Set segment...
mov Word Ptr es:[0000h], 0020h ; and offset of the new
; INT handler.
This is an old but very useful technique for nuking nearly any
analysing tool. What's the result of a division by zero? Can
it fit in a register, if possible? Well, who knows :)...
_You_ determinated the rules by creating a new interrupt handler.
This line forces an INT 00h - call. You remember, we wrote
a handler for this interrupt who executes an "important
instruction". In our case, we calculate the position of the
"New_Entry_Point:" (=> see complete source code).
Division_Position:
div ax ; AX = 0000h => division by ZERO!
; ==> INT 00h call!!
So, now there aren't many analysing-tools "alive", we can restore the
old interrupt and reenable the keyboard. By the way, what would happen
if you hadn't deactivate the keyboard? Right, if the user (or
the hacker) had pressed a key before the code in the Keyboardbuffer
has been executed, the would destroy the "important instructions"
in the buffer and the program would probably crash. So you have
to debug without your keyboard (if you aren't using an advanced
debugger like CUP386).
pop cx ; restore the old INT 00h handler
mov es:[0002h], cx
pop cx
mov es:[0000h], cx
in al, 64h ; keyboard ON
and al, 0BFh
out 64h, al
sti ; interrupts ON
Now we can begin with the decryption of the program with
the following code. You can see that we use self-modifying
code for keeping the codesize as tiny as possible:
mov_cx db 0B9h ; move to CX (MOV CX, ....)
; the length of the crypted code
Crypt_Length dw (Offset New_Entry_Point - Offset Decrypt_Start) / 2
mov bp, 101h ; decryption begins at CS:0101h
push bx ; save the IP for the next RET
add bx, (Offset New_Entry_Point - Offset Decrypt_Start)
Decrypt_Loop:
xor_word_ptr_bp db 081h, 076h, 00h ; decrypt (XOR WORD PTR [BP], ...)
Decrypt_Key dw 0DEADh ; ... with the decryption key
inc bp ; go to the next position
inc bp
cmp bp, bx ; everything decrypted?
jna Decrypt_Loop ; no. => continue!
ret ; jump to "Decrypt_Start:"
Decrypt_End:
Cool, we have successfully decrypted the original program. Let's
restore the first three bytes (remember, they have been replaced
by a "JMP New_Entry_Point" instruction) and the saved registers:
Decrypt_Start:
; Restore the original COM file
mov_W_Ptr_100 db 0C7h, 06h, 00h, 01h ; (MOV WORD PTR [100], ....)
Saved_Byte_1_2 dw ? ; the first two original bytes
mov_B_Ptr_102 db 0C6h, 06h, 02h, 01h ; (MOV BYTE PTR [102], ..)
Saved_Byte_3 db ? ; the third original byte
pop es ; restore all registers
popa
push 0100h ; push the original CS:IP to stack
ret ; return to the original COM now!
So, that's it. I hope you understood the essence of the encryption of
DOS COM program files and some weak techniques of anti debugging. If
you want to learn more about software protection, then download any
version of HackStop or check out some releases done by Dark Destroyer.
But remember: everything you protect by software can be deprotected by
software! It is impossible to code the unhackable protector (or if you
have one just pass it to some elite crackers and you 'll see...).
Regards
UniquE
I want to give my respects to Dark Destroyer and Beast.
You can download this text with the complete source code (Borland Turbo Assembler).
>Back to the project page
Last updated by UniquE on 16.10.1998