Self Modifying Code

All tutorials we have thought to write or that have been compiled that do not explicitly belong in another category.
Post Reply
User avatar
weazy
Ex-Admin
Posts: 1688
Joined: Sun Jul 07, 2002 10:02 am
Location: any given
Contact:

Self Modifying Code

Post by weazy » Fri May 30, 2003 7:11 pm

Basics of SMC
by Ralph (fu@ckz.org)
-AWC (http://awc.rejects.net)
Version:
Date:


1. Introduction
-About this thing
-Who should read this?
-What is SMC?

2. Opcode Modification

3. Encryption

4. Sample Code

5. Conclusion



1. Introduction
================


About this thing
----------------
This file makes an attempt to explains the very basics of Self Modifying Code.
All code in here is presented in assembly, so I assume that you have at least an
understanding of the concepts behind it. In addition you would have to be familiar
with some hardware, specificly memory and CPU, details.


Who should read this?
---------------------
If you are new to this concept and are interested in the areas of commercial programming
or virus programming, or something similar you might find the information provided
useful. The reasons for developing SMC code vary greatly. Two of the more common
reasons are:
1. Stealth
-Viruses need to hide themselfs from detection. Many of todays viruses implement
some form of SMC
2. Anti-debugging
-Commercial programs that try to keep the source code a secret will benefit from SMC
as some t3kn33kz can successfully stop inexperienced crackers from finding out what
the code actually does.



2. Opcode Modification
=======================
Opcode modification is the process of changing the value of an instruction before it
is actually executed. The basic form is:
MOV REGISTER,CODE
MOV [ADDRESS],REGISTER
Where REGISTER is any temporary register, CODE is the new code to be written, and
[ADDRESS] is the address where the new code should be placed. This method can however
be a bit tricky because you basicly have to work at the binary level at times. A
variation of this approach is to place the code to be written into a memory location
which will never get executed. Here is a simply sample program to demostrate this:

MAIN SEGMENT
ASSUME CS:MAIN,DS:MAIN

ORG 100h ;define entry point

START:
CALL MODIFY ;call modification routine

BLAH:
MOV AH,09h ;this code will get overwritten
MOV DX,OFFSET HELLO ;useless code
INT 21h ;call interrupt 21h

MODIFY: ;routine to change word, try hidding it somewhere
MOV DI,OFFSET NEWBYTES ;put location of the new instruction into DI
MOV WORD PTR [BLAH],DI ;move DI to the memory area to be changed
RET ;return to caller

NEWBYTES: ;new instruction to be executed
MOV AH,4Ch ;DOS' terminated program interrupt

HELLO DB "Hello World$" ;useless data

MAIN ENDS
END START

If someone where to disassemble this code they would get output similar to this:

1: 11AA:0100 E80700 CALL 010A
2: 11AA:0103 B409 MOV AH,09
3: 11AA:0105 BA1401 MOV DX,0114
4: 11AA:0108 CD21 INT 21
5: 11AA:010A BF1201 MOV DI,0112
6: 11AA:010D 893E0301 MOV [0103],DI
7: 11AA:0111 C3 RET
8: 11AA:0112 B44C MOV AH,4C

Most people would simply assume that the lines 2-4 will just print the string located
at offset 0114, when infact line 2 gets replaced with line 8. The result is that after
the the routine returns control to the caller the code will look like this:

1: 11AA:0100 E80700 CALL 010A
2: 11AA:0103 B409 MOV AH,4C
3: 11AA:0105 BA1401 MOV DX,0114
4: 11AA:0108 CD21 INT 21
5: 11AA:010A BF1201 MOV DI,0112
6: 11AA:010D 893E0301 MOV [0103],DI
7: 11AA:0111 C3 RET
8: 11AA:0112 B44C MOV AH,4C

So the actual code that gets executed will simply terminate the program.
By the way, one way to maybe optimize this code is to replace the MODIFY routine
with this one:

MODIFY:
PUSH WORD PTR [NEWBYTES]
POP WORD PTR [BLAH]
RET

Stack operations are quite fast, but I am not sure of they are actually faster than
those two MOV instructions. Try running it through a timer.
Obviously this was just a simple example, but you should be able to understand the
general idea behind SMC.



3. Encryption
==============
Encryption is when the actual bits in your program are modifyed. The most common
and easiest method is to perform a XOR operation on each bit of your code. I am not
sure what the most efficient method of doing so is as I have not devoted too much time
to SMC, but I came up with a routine that seems to do the job just fine.
The program will actually have two runtime versions. One to do the initial XOR for
the first time run, this version will also create the second version. The second
version contains the body of the program in a XORed format, and contains another XOR
procedure at the beginning.

First Run:
---------------
| XOR Program |
---------------
| Write File |
---------------
| XOR Program |
---------------
| |
| Program |
| |
---------------

Every Other Run:
---------------
| XOR Program |
---------------
| |
|XORed Program|
| |
---------------

Sorry about the lame ASCII, but hopefully you can better visualize the proccess this
way. Anyway, here is the code for the program:

MAIN SEGMENT
ASSUME CS:MAIN,DS:MAIN

ORG 100h

START:
MOV CX,DONE-BODY ;put size to xor into CX
MOV BX,OFFSET BODY ;and xor location into BX

XOR_1:
XOR WORD PTR [BX],0FFFFh ;xor word located at BX with our xor mask (0FFFFh)
INC BX ;increment index
INC BX ;and again since its 16 bits, and inc only adds 8

DEC CX ;decrement counter
DEC CX ;again for same reason as above
CMP CX,00h ;check if done encrypting
JNE XOR_1 ;return to xor routine if not

CALL WRITE ;call procedure to write out new file
INT 20h ;and exit, new file was created

REAL_START:
MOV CX,DONE-BODY ;put size to xor into CX
MOV BX,OFFSET BODY-REAL_START+100h ;and xor location into BX

XOR_2:
XOR WORD PTR [BX],0FFFFh ;xor word located at BX with our xor mask (0FFFFh)
INC BX ;increment index
INC BX ;and again since its 16 bits, and inc only adds 8

DEC CX ;decrement counter
DEC CX ;again for same reason as above
CMP CX,00h ;check if done encrypting
JNE XOR_2 ;return to xor routine if not

BODY: ;some instructions to test if this thing worked
INT 20h
INT 20h
INT 20h

DONE:

WRITE PROC ;Procedure to write new, xored, file
@@OPEN_FILE:
MOV AX,3D02h ;Open File function, returns file handle in AX
MOV DX,OFFSET FILENAME
INT 21h

@@WRITE_FILE: ;Write to Device function
MOV BX,AX ;BX contains device handle
MOV AH,40h
MOV CX,OFFSET DONE - OFFSET REAL_START
MOV DX,OFFSET REAL_START ;location of buffer
INT 21h ;do it

@@CLOSE_FILE: ;Close File function
MOV AH,3Eh
INT 21h

RET ;return to caller
WRITE ENDP

FILENAME DB "XOR1.COM",0 ;new file

MAIN ENDS
END START

Might be a bit hard to understand at first glance, but there is nothing to it. CX gets
loaded with the size of bytes to be XORed for use in the loop later on, BX gets loaded
with the location of the bytes to be XORed. BX is the only general purpose register
that can be used for indexing. The line
XOR WORD PTR [BX],0FFFFh
performs the actual XOR. 0FFFFh is a bit mask. Any number will do for this as long as
you use the same for encrypting and decrypting. Now, what does this code look like to
somone trying to disassemble it? Well the program created after the first run
(XOR1.COM) looks like this before execution:

1. 11B0:0100 B90600 MOV CX,0006
2. 11B0:0103 BB1201 MOV BX,0112
3. 11B0:0106 8337FF XOR WORD PTR [BX],-01
4. 11B0:0109 43 INC BX
5. 11B0:010A 43 INC BX
6. 11B0:010B 49 DEC CX
7. 11B0:010C 49 DEC CX
8. 11B0:010D 83F900 CMP CX,+00
9. 11B0:0110 75F4 JNZ 0106
10. 11B0:0112 32DF XOR BL,BH
11. 11B0:0114 32DF XOR BL,BH
12. 11B0:0116 32DF XOR BL,BH

Notice how the actual body of the program (lines 10-12) are not what they are supposed
to be. During execution the program looks like this:

1. 11B0:0100 B90600 MOV CX,0006
2. 11B0:0103 BB1201 MOV BX,0112
3. 11B0:0106 8337FF XOR WORD PTR [BX],-01
4. 11B0:0109 43 INC BX
5. 11B0:010A 43 INC BX
6. 11B0:010B 49 DEC CX
7. 11B0:010C 49 DEC CX
8. 11B0:010D 83F900 CMP CX,+00
9. 11B0:0110 75F4 JNZ 0106
10. 11B0:0112 CD20 INT 20
11. 11B0:0114 CD20 INT 20
12. 11B0:0116 CD20 INT 20

Now we have our original program back.
Again, this program was very simplistic. Some major areas you could "upgrade" on are
error checking and speed. As you can see I am basicly wasting 10 bytes of code on
a duplicated procedure. Anyway, it was just an idea I had and it seems to work fine
for my purposes.



4. Sample Code
===============
Here is some sample source that combines the two techniques. The password is stored
in XORed format and the messages are switched around.

MAIN SEGMENT
ASSUME CS:MAIN,DS:MAIN

ORG 100h

START:
CALL MODIFY

MOV AH,09h
MOV DX,OFFSET ENTER_PASS ;"Enter password:"
INT 21h

MOV SI,OFFSET STRING ;Load up our string register
XOR CX,CX ;and a counter to keep track of the string length

READ_STRING:
MOV AH,00h ;BIOS interupt for Get Key Board Buffer
INT 16h

CMP AL,0Dh ;INT 16h returns the key pressed in AL
JE CHECK_STRING ;so we check if it was Enter and quite if so

MOV AH,02h ;we gotta echo back the key
MOV DL,AL
INT 21h

MOV [SI],AL ;move key into first item of array (STRING)
INC SI ;and increment the string pointer
INC CX ;keep track of how many keys have been pressed

CMP CX,09h ;if more than 8, display error
JGE TOO_LONG

JMP READ_STRING ;otherwise return and read next key

CHECK_STRING:
MOV CX,08h ;put size to xor into CX
MOV BX,OFFSET PASS ;and xor location into BX

XOR_:
XOR WORD PTR [BX],0FFFFh ;xor word located at BX with our xor mask (0FFFFh)
INC BX ;increment index
INC BX ;and again since its 16 bits, and inc only adds 8

DEC CX ;decrement counter
DEC CX ;again for same reason as above
CMP CX,00h ;check if done encrypting
JNE XOR_ ;return to xor routine if not

CMP_PASS:
MOV CX,08h ;size of string to compare
MOV SI,OFFSET STRING ;load SI and DI with the two strings to compare
MOV DI,OFFSET PASS
REP CMPSB ;do the comparison
JNE INVALID_PASS ;and branch of

CORRECT_PASS: ;this will display "Invalid Password"
MOV AH,09h
CHANGE_1:
MOV DX,OFFSET INVALID_MSG
INT 21h
INT 20h

INVALID_PASS: ;this will display "Correct Password"
MOV AH,09h
CHANGE_2:
MOV DX,OFFSET CORRECT_MSG
INT 21h
INT 20h

TOO_LONG:
MOV AH,09h
MOV DX,OFFSET T00_LONG_ ;"Password too long"
INT 21h
INT 20h

CHANGE1:
MOV DX,OFFSET CORRECT_MSG ;new string to replace CHANGE_1

CHANGE2:
MOV DX,OFFSET INVALID_MSG ;new string to replace CHANGE_2

MODIFY: ;routine to change the two
PUSH WORD PTR [CHANGE1 + 1]
POP WORD PTR [CHANGE_1 + 1]

PUSH WORD PTR [CHANGE2 + 1]
POP WORD PTR [CHANGE_2 + 1]

RET


ENTER_PASS DB "Enter password: $"
CORRECT_MSG DB 0Ah,0Dh,"Correct Password",0Ah,0Dh,"$"
INVALID_MSG DB 0Ah,0Dh,"Invalid Password",0Ah,0Dh,"$"
T00_LONG_ DB 0Ah,0Dh,"Password too long",0Ah,0Dh,"$"
PASS DB 097h,0CBh,087h,0CFh,08Dh,0CCh,09Bh,0DEh ;XORed password
STRING DB 08h DUP (?) ;buffer to hold password
MAIN ENDS
END START

The password is "h4x0r3d!". Assemble as a .COM file and see what the result is.


5. Conclusion
==============
The two techniques discussed above are both best suited for two different kinds of
programs. The opcode modification is meant to trick humans into thinking your code
does something it does not, thus it is best for anti-dedugging. The encryptor
technique however does not produce output ment for humans. Most of the time it does
not even produce valid opcodes, thus its main purpose is to fool machines. A virus
might benefit from this one.
One important thing to keep in mind is that you should always get your program
completly working before you even think about making self modifying. Otherwise you
are just asking for trouble.
And another thing, some CPUs require you to modify the instructions a certain amount
of ticks before their execution due to read ahead and shit like that. Check your CPU
documentation for exacact numbers. Sadly most of those are not available these days,
and I have yet to figure out a way to get them on a CPU with a dynamic pre-fetch que.
If your program is supposed to run on a 486 or lower, you can give the following
program a try. I'm not sure if it actually works as I only have one computer to test
it on, but it seems to do the job. It should return the buffer size of the pre-fetch
units que.

MAIN SEGMENT
ASSUME CS:MAIN,DS:MAIN

ORG 100h

START:
MOV AX,CS
MOV ES,AX
MOV CX,80h
MOV DI,OFFSET QUE
MOV AL,90h
CLD
REP STOSB

XOR BX,BX
MOV CX,80h
MOV DI,OFFSET QUE+80h-1
STD
MOV AL,80h
OUT 70h,AL
CLI
MOV AX,43h
REP STOSB
MOV DL,1

LOAD_PF:
DIV DL
DIV DL
DIV DL
DIV DL
DIV DL
DIV DL
DIV DL
DIV DL

QUE DB 80h DUP (90h)

XOR AL,AL
OUT 70h,AL
STI
CLD
MOV AX,80h
SUB AX,BX

;this part translates the result into ASCII, the _ after the lables is to
;keep tasm from complaining

MOV BX,OFFSET TABLE_
MOV CX,04h

LOOP_:
PUSH AX
MOV AL,Ah
SHR AL,04h
XLAT

MOV DL,AL
MOV AH,02h
INT 21h

POP AX
SHL AX,04h
DEC CX
CMP CX,00h
JNE LOOP_

MOV AH,02h
MOV DL,'h'
INT 21h
INT 20h

TABLE_ DB "0123456789ABCDEF"

MAIN ENDS
END START
--The Devil is in the Details--

Post Reply