Now let's take a more in-depth look at ways to protect against crackers' attempts at using breakpoints, tracers, and debuggers against your programs.
Detecting Tracers Using the Trap Flag
This is one of my favorite tricks. It allows you to detect any tracer including SoftICE, TRW, FrogsICE, and ProcDump.
My friend EliCZ discovered this trick based on elegant idea. If the Trap flag is set in the Eflags, EXCEPTION SINGLE STEP will be launched. In short, this means that the SEH service will be called before the instruction is performed. We can use the fact that if a program is traced, the SEH service will not be called, and we can set a mark there to show whether it was called or not. If not, a tracer is active.
.386
.MODEL FLAT,STDCALL locals jumps UNICODE=0 include w32.inc
;While you do not need any API calls for detection, you will use them for printing out the result ;and for ending the program
.DATA
message1 db "Tracer detection using the Trap flag",0 message3 db "Tracer found",0 message2 db "Tracer not found",0
DelayESP dd 0 ;the ESP register saves here
Previous dd 0 ;the ESP register saves the address of the
;previous SEH service here
mark db 0 ;sets a mark showing whether it went into the
;xhandler
.CODE Start:
call real_start ;jumps to the real start of the program
;A new SEH service in case of an error (xhandler)
inc mark ;increases the eax by one to show that it isn't
;being traced
sub eax,eax ;nulls the eax because of the future setting of
;the original SEH service
ret ;returns to the program
real_start:
xor eax,eax
push dword ptr fs:[eax]
mov fs:[eax], esp
pushfd
or byte ptr [esp+1], 1
popfd
pop dword ptr fs: [eax]
pop ebx
;nulls the eax register
;saves the original SEH service
;sets your new SEH service. Because you jumped ;by means of CALL, the following return address ;is saved into the stack. This will be your new ;SEH service. ;saves the flag registers
;sets the Trap flag inEflags, which means that
;the program will launch EXCEPTION SINGLE STEP,
;and your new SEH service will be called ;restores the flag registers with the previously ;set Trap flag. The whole process runs nop ;and xhandler is called
;sets the previous SEH service and clears the ;stack
;decreases the mark by 1. If it is -1
js jump
;(OFFFFFFFFh) after the decrease, thena tracer ;is active in memory because the xhandler hasn't ;been called ;and the program jumps
continue:
call MessageBoxA,O, offset message2,\ offset message1,O call ExitProcess, -1
jump:
call MessageBoxA,O, offset message3,\ offset message1,O call ExitProcess, -1
ends
end Start
This is a great detection method that is almost impossible for crackers to discover (when it is well hidden), especially because tracers seem to have a problem emulating it correctly.
To make it more difficult for the cracker to discover this trick, be sure that the protected application doesn't print out messages or warnings. The best response is to simply end the application incorrectly and punish the cracker that way, though you might use your imagination to come up with other responses.
This trick works in all Windows versions.
Detecting Breakpoints by Searching for Int 3h
To make debugging more difficult, it is very important to test for the presence of breakpoints. If a breakpoint is set in a debugger, there will be an Int 3h instruction at a particular place in memory.
The following example program first searches its code in memory for the value 0CCh, which is the prefix of the Int 3h instruction. There is one problem, though, which the program doesn't take into account: It is likely, in most applications, that the value 0CCh will exist somewhere in the code. In such a case, the testing routine should save these locations somewhere. Then, if it finds the value 0CCh, it will first test to see if it is where it was found before.
Once the program finishes this test, it tests to see if a breakpoint was set on an API call used by the program. It searches addresses for imported functions one by one in the IAT table, found after the program code. These are pointers to the beginnings of the API functions, and the 0CCh value would be found there if a breakpoint was set to this API function.
Another way to find the addresses of the API function routines would be to find them in the file's PE header, where this information is saved.
.386
.MODEL FLAT,STDCALL
locals
jumps
UNICODE=O
include w32.inc
Extrn SetUnhandledExceptionFilter : PROC
.DATA
message1 message3 message2 delayESP previous
db "Breakpoint detection by searching for Int 3h",O
db "Breakpoint found",O
db "Breakpoint not found",O
dd O ;the ESP register saves here
dd O ;the ESP register will save the address of the
;previous SEH service here
current_import dd O ;the address of the imported function currently
;being tested is saved here
.code
Start:
;Sets SEH in case of an error
mov [delayESP], esp push offset error
call SetUnhandledExceptionFilter mov [previous], eax
lea
mov
edi, Start
ecx, End-Start+1
;the starting address of the program will be put ;into the edi
;the length of the program, that is, the number ;of tested bytes, will be put into the ecx
mov
add
eax,OBCh eax, 1Oh
;puts OCCh into the eax (this is the prefix of
;the INT 3h instruction). Note! Do not use
;mov eax,OCCh or you will find this value in
;the program and think that it is a breakpoint.
repnz scasb ;searches for a breakpoint in the program
test ecx,ecx ;tests to see if ecx=O; if not, it has found a
;breakpoint
jne found ;and the program jumps
lea eax, Imports+2 ;puts the IAT (first function) into the eax
;register
mov dword ptr [current import], eax
start_searching:
mov eax,
dword ptr [current_import] cmp dword ptr [eax], O
je notfound mov ecx, [eax]
mov ecx, [ecx]
mov eax, OBCh add eax, 1Oh
cmp byte ptr [ecx], al
je found
add [current_import], 6 jmp start_searching found:
mov eax, 1 jmp farther
notfound:
;puts the imported function to be ;tested into the eax
;tests to see if it is O; if so, thenthis is ;the end of the Import table (the table of the ;imported functions)
;the program jumps if it hasn't found any ;breakpoints
;this reads the address where the imported ;function is located; that is the address of the ;beginning of its routine
;reads the address of the beginning of the ;routine of the imported function
;puts OCCh into the eax (the prefix of the INT ;3h instruction). Note! Do not use mov eax, ;OCCh or you will find this value in the program ;and think that it is a breakpoint. ;tests for the breakpoint's presence in the ;tested imported function
;the program will search for OCCh (actually Int ;3h) at the beginning of its routine ;and if a breakpoint is found it will end ;sets to another imported function in the IAT
;jumps to search for a breakpoint in the next ;imported function
;sets eax to 1 to show that the program has ;found a breakpoint
xor eax, eax ;sets eax to O to show that it hasn't found a
;breakpoint
farther:
push eax ;saves the return value
;Sets the previous SEH service
push dword ptr [previous]
call SetUnhandledExceptionFilter
pop eax ;restores the return value
test eax, eax jnz jump
continue:
call MessageBoxA,0, offset message2,\ offset message1,0 call ExitProcess, -1
jump:
call MessageBoxA,0, offset message3,\ offset message1,0 call ExitProcess, -1
error: ;sets a new SEH service in case of an error
mov esp, [delayESP] push offset continue ret
End:
Imports: ;this label must be at the very end of the
;program, since the IAT begins there
ends
end Start
This trick is limited by the fact that it cannot find debug breakpoints that do not change the program code. Therefore, it is good to combine this trick with a way to detect debug breakpoints.
This trick works in all Windows versions.
Detecting Breakpoints by CRC
This example shows a different method of breakpoint detection that I think is even better than the previous one. It's based on the CRC calculation for the particular program and the checks that run during the course of the program. A program's current CRC is found at the CRC label. The CRC label changes with alterations in the program, and is therefore adjusted after each alteration. This method is even effective against changes performed in memory.
This trick is simple: First, you run the debugger, and once it returns from the CRC32 routine, the EAX register will contain the current CRC of the program code in the memory. Next, you put this value into the CRC label, and when a protected application is launched, the program calculates the CRC of the code in the memory. If the CRC isn't the same as the value at the CRC label, the program has been changed, which tells you that either there were changes performed in the program code or there is a breakpoint set in the code.
.386
.MODEL FLAT,STDCALL
locals jumps
UNICODE=0
include w32.inc
Extrn SetUnhandledExceptionFilter : PROC
.data
message1 db "Breakpoint detection by CRC",0
message3 db "Breakpoint or a change in the program found",0 message2 db "Breakpoint not found",0
delayESP dd 0 ;the ESP register saves here previous dd 0
;the ESP register will save the address of the ;previous SEH service here
;Following is a table of the values necessary for the program's CRC calculation.
CRCTable dd 00000000h, 77073 096h, 0EE0E612Ch, 9 90 951BAh
dd 076DC419h, 706AF4 8Fh, 0E963A53 5h, 9E64 95A3h
dd 0EDB8832h, 79DCB8A4h, 0E0D5E91Eh, 97D2D988h
Start:
;Sets SEH in case of an error
mov [delayESP],esp push offset error
call SetUnhandledExceptionFilter mov [previous], eax
lea esi, CRCTable ;address of the CRC table needed for the
;calculation
lea edi, Start ;beginning of data for which the CRC will be
;calculated (beginning of your program)
mov ecx, End-Start+1 ;length of the data in bytes (length of your
;program)
call CRC32 ;jump to calculate the CRC
push eax ;saves the calculated CRC
;Sets the previous SEH service
push dword ptr [previous] call SetUnhandledExceptionFilter
pop eax ;restores the calculated CRC
cmp dword ptr, CRC, eax ;compares the calculated CRC with the saved one
jnz jump ;if the CRCs do not match, the program was
;either changed or a breakpoint was set
continue:
call MessageBoxA,O, offset message2, offset message1,O call ExitProcess, -1
jump:
call MessageBoxA,O, offset message3, offset message1,O call ExitProcess, -1
error: ;sets a new SEH service in case of an error
mov esp, [delayESP] push offset continue ret
;The routine for the CRC calculation ;input:
;ESI = CRCTable address
;EDI = beginning of the data address
;ECX = number of bytes for the calculation;
;output
;EAX = 32-bit CRC result
CRC32:
mov eax, OFFFFFFFFh mov edx,eax CRC32_1: push ecx
xor ebx,ebx
mov bl,byte ptr [edi]
inc edi
xor bl,al
shl bx,1 shl bx,1
add ebx,esi
mov cx,word ptr [ebx+2]
mov bx,word ptr [ebx]
mov al,ah
mov ah,dl
mov dl,dh
xor dh,dh
xor ax,bx
xor dx,cx
pop ecx
loop CRC32_1
not ax not dx push dx push ax pop eax
ret
End:
;initializes the eax and the edx
;saves the ecx into OFFFFFFFFh
;the number of bytes for the calculation
;reads 1 byte from the data ;and moves to the next
;sets according to the read byte in the CRC
;table
;reads a 32-bit
;the value from the CRC table
;moves DX:AX to the right by 8 bytes
;XOR CX:BX into DX:AX
;restores the number of the non-calculated data ;inbytes
;jumps if not all has been calculated yet, and ;decreases the number of bytes in the ecx by 1 ;the final calculation of the CRC
;saves the 16-bit values ;into the stack
;and after it has been read back, the complete ;32-bit CRC will be inthe eax register ;return
ends
end Start
Checking changes in a program through the CRC is one of the best ways to protect software against changes in the code. It isn't a bad idea to use the trick in more places too, to prevent the simple removal of protection. The program will also be well protected against viruses and breakpoints.
Note I will not discuss the mathematics of the CRC calculation here because it is easy to find detailed documentation about it. You can find several examples on the CD with this book, including some for the C language.
This trick cannot be used to control breakpoints at the API calls, or at least not in such a way that the CRC results for the API functions would be saved somewhere in the program. (Their code is different for various Windows versions.) The only way to do this is to calculate the CRC of the API functions that the program uses right at the start of the program, and to check this CRC when the functions are called. (Doing so may slow the program significantly, though.)
The previous example tests the CRC for the code section of the program, but it doesn't check the data. It's a good idea to have an application check even its own data.
This trick works in all Windows versions.
Detecting Debug Breakpoints
In Chapter 7, in the "Finding an Active Debugger Through the DR7 Debug Register" section, I demonstrated how to use the DR7 debug register to determine whether a debugger is active in memory. X86 processors have other registers besides DR7, though, so we'll take a look at the DR0 through DR3 registers here, which contain debug breakpoints.
Unfortunately, it is only possible to work with these registers in ring0. As such, we can use the same trick for switching over into ring0 that I used in previous examples in Chapter 7.
.386
.MODEL FLAT,STDCALL locals
jumps
UNICODE=O
include w32.inc
Extrn SetUnhandledExceptionFilter : PROC
Interrupt equ 5 ;the interrupt numbers 1 or 3 will make
;debugging more difficult
.DATA
message1 db "Debug breakpoint detection",O message2 db "Debug breakpoint not found",O message3 db "Debug breakpoint found",O
delayESP dd O ;the ESP register saves here
previous dd O ;the ESP register will save the address of the
;previous SEH service here
.CODE Start:
;Sets SEH in case of an error
mov [delayESP], esp push offset error
call SetUnhandledExceptionFilter mov [previous], eax
push edx
sidt [delayesp-2] ;reads IDT into the stack
pop edx
add edx, (Interrupt*8)+4 ;reads the vector of the required interrupt
mov ebx,[edx]
mov bx,word ptr [edx-4] ;reads the address of the old service of the
;required interrupt
lea edi,InterruptHandler
mov [edx-4],di
ror edi,16
mov [edx+2],di
push ds
push es
int Interrupt
pop es pop ds
mov [edx-4],bx ror ebx,16 mov [edx+2],bx push eax
;sets the new interrupt service
;saves registers for security
;jumps into Ring0 (a newly defined INT 5h ;service)
;restores the registers
;sets the original INT 5h interrupt service ;saves the return value
;Sets the previous SEH service
push dword ptr [previous]
call SetUnhandledExceptionFilter
pop eax test eax,eax jnz jump
;restores the return value ;tests to see if eax=0
;if not, the program has found a debug ;breakpoint and it ends
continue:
call MessageBoxA,0, offset message2,\ offset message1,0 call ExitProcess, -1
jump:
call MessageBoxA,0, offset message3,\ offset message1,0 call ExitProcess, -1
error: ;sets a new SEH service if there is an error
mov esp, [delayESP] push offset continue ret
;Your new service INT 5h (runs in Ring0)
InterruptHandler:
mov eax, dr0
test ax,ax
jnz Debug_Breakpoint
mov eax,dr1
test ax,ax
jnz Debug_Breakpoint
mov eax,dr2
test ax,ax
jnz Debug_Breakpoint
mov eax,dr3
test ax,ax
jnz Debug_Breakpoint
iretd
;reads a value from the DR0 debug register ;tests to see if a breakpoint was set ;if so, the program jumps
;reads a value from the DR1 debug register ;tests to see if a breakpoint was set ;if so, the program jumps
;reads a value from the DR2 debug register ;tests to see if a breakpoint was set ;if so, the program jumps
;reads a value from the DR3 debug register ;tests to see if a breakpoint was set ;if so, the program jumps
;if a breakpoint was not set the program will ;return 0 into the eax register
Debug_Breakpoint:
mov eax,1
iretd
;sets the value 1 into the eax register to show ;that breakpoints are active ;jump back into Ring3
ends
end Start
This technique is one of the few ways to discover debug breakpoints, and it makes it possible to delete them without stopping the application in the debugger. However, rather than delete them, I recommend going to an incorrect end of the application.
Unfortunately, the trick works only in Windows 9x because of the need to switch over into ring0. One way to use it in Windows NT, Windows 2000, and Windows XP is to place it into a Sys driver running in ring0. The same is true for all tricks that exploit ring0.
Detecting User Debuggers
The following trick will only discover a user debugger—mostly older types of debuggers that work on a different principle than SoftICE and TRW. It will, for example, find a debugger that is located in WinDasm.
.386
.MODEL FLAT,STDCALL
locals jumps
UNICODE=O
include w32.inc
Extrn SetUnhandledExceptionFilter : PROC
.data
message1
message3
message2
delayESP
previous
.code
Start:
db "User debugger detection",O
db "Debugger found",O
db "Debugger not found",O
dd O dd O
;the ESP register saves here
;the ESP register will save the address of the ;previous SEH service here
;Sets SEH in case of an error
mov [delayESP], esp push offset error
call SetUnhandledExceptionFilter mov [previous], eax
xor eax,eax ;nulls the eax register
mov al, fs:[2Oh] ;reads a value. If the value is other thanO, a
;debugger is active
push eax ;saves the value
;Sets the previous SEH service
push dword ptr [previous]
call SetUnhandledExceptionFilter
pop eax ;restores the value
test eax,eax ;tests to see if the read value was O
jnz jump ;if not, then a debugger is active in memory and
;the program jumps
continue:
call MessageBoxA,O, offset message2,\ offset message1,O call ExitProcess, -1
jump:
call MessageBoxA,O, offset message3,\ offset message1,O call ExitProcess, -1
error:
;sets a new SEH service in case of an error
mov esp, [delayESP] push offset continue ret
ends
end Start
While this trick will not find either TRW or SoftICE, it works reliably with older debuggers.
Detecting User Debuggers Using the API Function IsDebuggerPresent
You can use the API function IsDebuggerPresent to find a debugger that is active in memory. You needn't enter any parameters for calling this function. If a debugger was found, the EAX register will contain something other than 0.
.386
.MODEL FLAT,STDCALL
locals jumps
UNICODE=0
include w32.inc
Extrn SetUnhandledExceptionFilter : PROC
Extrn IsDebuggerPresent : PROC
.data
message1
message3
message2
delayESP
previous
.code
Start:
db "User debugger detection with the API IsDebuggerPresent function",0
db "Debugger found",0
db "Debugger not found",0
dd 0 ;the ESP register saves here
dd 0 ;the ESP register saves the address of the
;previous SEH service here
;Sets SEH in case of an error
mov [delayESP], esp push offset error
call SetUnhandledExceptionFilter mov [previous], eax
call IsDebuggerPresent
push eax ;saves the return value
;Sets the previous SEH service
push dword ptr [previous]
call SetUnhandledExceptionFilter
pop eax ;restores the return value
test eax,eax ;tests to see if the return value was 0
jnz jump ;if not, a debugger is active in memory and the
;program jumps
continue:
call MessageBoxA,0, offset message2,\ offset message1,0 call ExitProcess, -1
jump:
call MessageBoxA,0, offset message3,\ offset message1,0 call ExitProcess, -1
error: ;sets a new SEH service in case of an error
mov esp, [delayESP] push offset continue ret
ends
end Start
This trick will not find either SoftICE or TRW, but it will certainly find user debuggers such as the one in WinDasm.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment