What does one need to reverse engineer? What does one need to crack? I should begin by pointing out that cracking and re-engineering are for the most part one and the same; cracking is a dynamic form of re-engineering--more like a surgical strike than a full de-compilation--that exploits flaws inherent in the program (or, more commonly, in the API of the OS) to achieve a certain end, while reverse engineering is the attempt to recover the original source code from an existing binary file--usually to copy or change the functions in that binary file. The skills and tools used for cracking and re-engineering are the same; re-engineering is just a more involved process.
First off, to re-engineer one must know assembly language. It is only necessary to be able to read ASM, not to program in it--though that skill will prove useful as well. In addition, one must be familiar with the language in which the target file was written--if it was written in Java, learn Java; if in C++, learn C++; if in Visual Basic, then you must lower yourself to learn that (for lack of a better word) language as well (and it may be worth learning, for more and more applications are being written in the psuedocode that is VB5 now that MS has become dominant in the PC market). You must have an intimate knowledge of your PC, especially the CPU and the memory addressing scheme. And finally, you must have patience and a good problem-solving ability.
The "tools" end of the package is much easier to fill. It is essential to have a disassembler such as W32DASM, WCB, or Sourcer; it is also good to have a debugger in order to clarify and correct the code produced by your disassembler. Numega's unbelievable Soft-Ice is the prime choice, but with a good disassembler one can get by with CodeView, Turbo Debugger, Brand-X or even DEBUG. An API reference for the language and OS of the target program is absolutely required, as well as a text editor or word processor (hopefully one that colorizes source code for a number of languages, such as Multi-Edit) that can handle the large files created by the disassembler (5 to 10 times the size of the original binary).
Any additional tools are more or less mandated by style--resource editors for Windows programs, hex editors to patch files, compilers for re-generating executables, and system monitor utilities for information gathering.
Oh yes, you will also need a computer. And lots of time to play with it....
Documentation on the trade of reverse engineering can be found in abundance on the internet, usually on sites that cover application porting, source code recovery, cracking, or decompiler writing. In addition, knowledge of the target executable file format, the target operating system, and the hardware which the target is to run on is important as well. One of the best discussions of the behavior of executables running on DOS-based operating systems is Chapter 3 of Cristina Cifuentes' doctoral thesis.
The problem with taking up a pursuit such as Reverse Engineering is that there is no established method of acquiring the skills and knowledge one will need. Perhaps the best introduction can be found in Andrew Schulman's Undocumented Windows, which covers Windows 3.1 in practice but which can be applied to Win32. Matt Pietrek's Windows Internals book is also quite useful, while Barry Kauler's Windows Assembly Language and Systems Programming provides a good demonstration of mixing Windows and Assembly Language. In addition, there are numerous online pages and newsgroups devoted to reverse engineering, including Georgia Tech, the DCC homepage, and the New Jersey Machine Code Toolkit page...the problem being that most of these efforts focus on Unix and mainframe platforms. The alternative is to acquire the skills the hard way...by examining files and consulting reference material (such as the Win32 API and the PE file header format) for guidance. This "dive right in" approach may be the best way to go with Windows programs, as their secrets are given up easily enough to keep a novice interested.
Take for an example the file Mkcompat.exe, located in the C:\Windows\System directory. Doing a right-click/Properties on this file shows that it is a 33,792-byte "Windows 95 Make Compatible App Hacker" program, supplied with version 4.00.950 of the Windows OS. As this is a stand alone file and not an application, there are not .cfg, .ini, or .dat files through which to sift for clues. Instead, the executable itself must be examined, and this can be done on a standard Win95 install by right-clicking the Mkcompat icon and choosing "QuickView". This will give you an output split into five parts:
Note that other files may have additional sections displayed by QuickView, such as the Export Table. All of this information is contained in the PE header; study this structure, read VLAD #6 or Microsoft's Reference or Project 3 on this site...you will learn much about Win32 files and how to reverse them.
The Image File Header and the Image Optional Header sections reveal some basic information about the target OS, the date of writing, size of the file, linker version, and quite a bit of information about the structure of the binary file itself. The Section Table details out the different sections or directories of the file, the most notable being .text, .reloc, and .rsrc, which contain the program's execuable code, relocations (jump tables), and resources respectively. The Header Information is only really useful to the operating system and memory management programs.
All of the important information in this QuickView display is contained in the Import Table, which reads as follows:
Import Table KERNEL32.dll Ordinal Function Name 00bc GetCommandLineA 028e _lclose 0124 GetStartupInfoA 0291 _lopen 0071 ExitProcess 00f9 GetModuleHandleA 0119 GetProfileIntA 0287 WriteProfileStringA 0290 _llseek 0292 _lread 0172 InitializeCriticalSection 005e EnterCriticalSection 0261 VirtualAlloc 0262 VirtualFree 024f TlsGetValue 0250 TlsSetValue 00d0 GetCurrentThreadId 024b TlsAlloc 0126 GetStdHandle 00b5 GetCPInfo 00e7 GetFileType 00af GetACP 00f7 GetModuleFileNameA 0102 GetOEMCP 01e8 RtlUnwind 0146 GetVersion 0258 UnhandledExceptionFilter 0188 LeaveCriticalSection 00ef GetLastError 029f lstrcpyA 00da GetEnvironmentStrings USER32.dll Ordinal Function Name 00ff GetMessageA 020f TranslateMessage 0101 GetMessagePos 0173 MapWindowPoints 015e LoadIconA 0219 UpdateWindow 00cf GetClientRect 011e GetWindow 0081 DestroyWindow 013b InvalidateRect 01ec SetWindowPos 01ad SendDlgItemMessageA 01c6 SetDlgItemTextA 00dd GetDlgItem 022d wsprintfA 00f9 GetMenuItemInfoA 01af SendMessageA 00f3 GetMenu 017d ModifyMenuA 00a5 EnableMenuItem 00c2 GetAsyncKeyState 0050 CreateWindowExA 002f CheckMenuItem 0116 GetSystemMetrics 0086 DispatchMessageA 01f9 ShowWindow 0168 LoadStringA 0078 DefWindowProcA 0196 RegisterClassExA 015a LoadCursorA 0176 MessageBoxA GDI32.dll Ordinal Function Name 00bc GetStockObject COMCTL32.dll Ordinal Function Name 0024 ImageList_LoadImage SHELL32.dll Ordinal Function Name none listedNote that this list tells you all of the .DLL files that Mkcompat uses, as well as the exported functions (in this case, exported Windows API functions) that Mkcompat calls from each of those .DLLs. Note that most of the functions listed here are pretty standard (there is no third-party "timelock" .DLL with a GetRegistrationInfo function, for example), but you can tell right away that the program takes a command line parameter (Kernel.00BC), has some exception handling (KERNEL.0172, etc), and uses threads (Kernel.024B).
At this point you should look for routines that you do not recognize or that are out of the ordinary, for which two candidates seem to present themselves:
Neither of these shows up in a Win32 API reference; doing a Quickview on Comctl32.dll reveals a number of ImageList_ functions, with names such as AddIcon, Drag, and GetBkColor, so it can be assumed that this function has to do with graphics images and not file images. Similarly, Kernel32.dll contains a few RTL functions with names such as RTLMoveMemory and RTLZeroMemory, so it appears that RTLUnwind is some sort of (undocumented?) memory management function. Apparently there are no surprises in Mkcompat's imports, which is not really a shock; much of its .exe-modifying code must be internal.
Loading Mkcompat in a Resource Editor such as BRW reveals little of interest save for some strings that must be internal filenames: "TTIGNORERASTERDUDE", "STUPIDPALLETTEAPP", and "RANDOM3XUI". These strings can also be viewed in a hex editor such as HIEW (at offset 008F10) in "wide" format, or even in a simple text editor such as Notepad (you do have Notepad.exe in your SendTo folder, right?). Still, getting an idea of the internal function names helps in understanding the file (and the banality of the programmers).
At this stage, the easy stuff is all over; from here on you will have to deal with Assembly Language. This is a small file that can be loaded into a disassembler/code browser such as W32DASM (very good for novices and for browsing the code quickly) relatively quickly, so poring over the code will not be too tedious; however, be sure you have some knowledge of asm as well as a more-than-casual acquaintance with Windows programming before going any further. It is also good to limit your search to a single, simple aspect of the target (especially in a basic example like this) so that you do not end up reversing the entire program "just for kicks".
At this point nothing really stands out aside from a few odd names in the string table, no obvious clues as to how the program performs its magic upgrading. Time for some action: run MKCOMPAT.EXE and, once inside, open SYSEDIT.EXE (a 16-bit application). Now run Filemon to get a record of what is happening (the Filemon log file will indicate all file accesses, including system/user.dat (registry) acesses and DLL loads/unloads; it makes for a good survey of an application's live behavior), check a few of the option boxes in MKCOMPAT (in this case, the last 3 were set), and save the changes.
The Filemon log indicates no registry or unexpected DLL access, however there is a write to WIN.INI...evidently this
app is not so tricky after all, just a win.ini write! Open up WIN.INI (in sysedit, of course) and search for "sysedit".
There is one hit, under the [Compatibility] tag, the last key added under that tag: SYSEDIT=0x80000090
.
To test the assumption, uncheck the boxes in MKCOMPAT and save again: SYSEDIT=0x0
. That would be it, then....
So, on to the disassembler, the target naturally being the 0287 WriteProfileStringA
import shown in QuickView. The disassembler used in this case is IDA Pro, preferred both for its interactive
interface (allowing the reinterpretation and labelling of code and data on-the-fly) and its
disassembling capability. There are two calls WriteProfileString, at locations 4019A9 and 4019BA.
These are in the same subroutine, which can be renamed in IDA to WriteToWIN_INI; the subroutine is as follows:
0040196D ; S u b r o u t i n e
0040196D ; Attributes: bp-based frame
0040196D WriteToWIN_INI proc near ; CODE XREF: sub_401CDE+49j
0040196D 55 ; sub_401E31+21p
0040196D var_50= byte ptr -50h
0040196D push ebp
0040196E 8B 0D 14 40 40 00 mov ecx, dword_404014
00401974 8B EC mov ebp, esp
00401976 83 EC 50 sub esp, 50h
00401979 3B 0D 10 40 40 00 cmp ecx, dword_404010
0040197F 56 push esi
00401980 74 46 jz short loc_4019C8
00401982 51 push ecx
00401983 8D 45 B0 lea eax, [ebp+var_50]
00401986 68 2C 40 40 00 push offset a0xX
0040198B 50 push eax
0040198C FF 15 90 62 40 00 call ds:wsprintfA
00401992 83 C4 0C add esp, 0Ch
00401995 8D 4D B0 lea ecx, [ebp+var_50]
00401998 8B 35 D4 61 40 00 mov esi, ds:WriteProfileStringA ; WriteProfileStringA:
0040199E 51 push ecx
0040199F 68 A0 52 40 00 push offset byte_4052A0
004019A4 68 50 52 40 00 push offset unk_405250
004019A9 FF D6 call esi
004019AB 68 28 40 40 00 push offset unk_404028
004019B0 68 28 40 40 00 push offset unk_404028
004019B5 68 28 40 40 00 push offset unk_404028
004019BA FF D6 call esi
004019BC 8B 0D 14 40 40 00 mov ecx, dword_404014
004019C2 89 0D 10 40 40 00 mov dword_404010, ecx
004019C8 loc_4019C8: ; CODE XREF: WriteToWIN_INI+13
004019C8 5E pop esi
004019C9 8B E5 mov esp, ebp
004019CB 5D pop ebp
004019CC C3 retn
004019CC WriteToWIN_INI endp
The first thing to do is to start labelling the code locations passed to the API calls (WriteProfileStringA and wsprintfA).
According to the Win32 API reference, these two functions are as follows:
BOOL WriteProfileString(
LPCTSTR lpszSection, // address of section name
LPCTSTR lpszKey, // address of key name
LPCTSTR lpszString // address of string to write
);
int wsprintf(
LPTSTR lpOut, // address of buffer for output
LPCTSTR lpFmt // address of format-control string
);
WriteProfileString is a standard API function, so its parameters are pushed in reverse order, and the
parameters can be labelled as follows:
004019A9: push ecx ;contains pointer to string
push keyname
push Section
call WriteProfileString
004019BA: push weirdvar ;used because the three addresses are the same, which is...weird
push weirdvar
push weirdvar
call WriteProfileString
WsprintfA is a _cdecl function, so its parameters are pushed in order and can be labelled as follows:
0040198B: push eax ;buffer
push formatstring
call wsprintfA
The subroutine now has the following structure in C:
WriteToWinINI( )
{
WritePrivateProfileString( loc_405250, loc_4052A0, wsprintfA( lpszString, "0x%x"), loc_404014 );
WritePrivateProfileString( loc_404028, loc_404028, loc_404028_, loc_404014);
}
It is time for some work to be done to clear up the mysterious "loc" names and cause the whole code to make sense.At the start of the routine, it appears that dword_404014 is being compared with dword_404010 and then being pushed as a parameter to the WriteProfileString call. The next question is where did the dword_404014 and dword_404010 come from? Click on the first dword reference in IDA to find out:
dword_404014 dd 0 ; DATA XREF: sub_4015B5+58^r
; sub_4015B5+7D^r
; sub_4018C1+21^w
; WriteToWIN_INI+1^r
; WriteToWIN_INI+4F^r
; sub_401CDE+F^r
; sub_401DC6+48^r
; sub_401DC6+58^w
A quick summary of the unknown locations:
0040160D 23 15 14 40 40 00 and edx, dword_404014 ; It is already set here
00401632 A1 14 40 40 00 mov eax, dword_404014 ; And here
004018E2 A3 14 40 40 00 mov dword_404014, eax ; Paydirt! See below
00401CED 39 05 14 40 40 00 cmp dword_404014, eax ; not followed
00401E0E A1 14 40 40 00 mov eax, dword_404014 ; not followed
00401E1E A3 14 40 40 00 mov dword_404014, eax ; not followed
The "paydirt" line demonstrates what both dword_404014 and dword_404010 contain:
004018C1 ; S u b r o u t i n e
004018C1
004018C1 sub_4018C1 proc near ; CODE XREF: sub_4018E8+76vp
004018C1 A1 04 40 40 00 mov eax, dword_404004
004018C6 80 38 00 cmp byte ptr [eax], 0
004018C9 74 1C jz short locret_4018E7
004018CB 6A 00 push 0 ; Default
004018CD 68 A0 52 40 00 push offset keyname ; KeyName
004018D2 68 50 52 40 00 push offset SectionName ; Section
004018D7 FF 15 D0 61 40 00 call ds:GetProfileIntA ; GetProfileIntA:
004018DD A3 10 40 40 00 mov dword_404010, eax ; 404010 = string following keyname
004018E2 A3 14 40 40 00 mov dword_404014, eax ; 404014 = string following keyme
004018E7
004018E7 locret_4018E7: ; CODE XREF: sub_4018C1+8j
004018E7 C3 retn
004018E7 sub_4018C1 endp
GetProfileInt returns the string following the keyname in the section specified by the GetProfileInt call.
In an .INI file, the entries look like this:
[section]
key=string
so dword_404014 ("string") definitely refers to the compatibility value stored in WIN.INI ("section"
will of course be "Compatibility", and "key" will be the application name). The "string" value is set by responding to
dialog box messages (too convoluted to go into here, but check it out in the source code if you have the extra hour or three),
then copied into the "string" variable (now renamed to "new_stringval") and written to WIN.INI. The Windows 95 loader checks the
"Compatibility" section of WIN.INI for the app name and, if it finds it, uses the bitmask in the given string to alter the loading parameters.
Further investigation would require some analysis of the PE file format and the Windows 95 file loader, and is beyond the scope of this
simple demonstration.
The subroutine can now be "cleaned up" with more meaningful names and a few comments:
0040196D ; S u b r o u t i n e
0040196D ; Attributes: bp-based frame
0040196D
0040196D WriteToWIN_INI proc near ; CODE XREF: sub_401CDE+49j
0040196D 55 ; sub_401E31+21p
0040196D
0040196D CompatStringBuf= byte ptr -50h
0040196D
0040196D push ebp
0040196D_________________________WriteToWIN_INI( );
0040196E 8B 0D 14 40 40 00 mov ecx, new_stringval ; 404014 = EXEName
00401974 8B EC mov ebp, esp ; set up stack frame (ENTER)
00401976 83 EC 50 sub esp, 50h ; make room for variable
00401979 3B 0D 10 40 40 00 cmp ecx, old_stringval
0040197F 56 push esi ; save esi
00401980 74 46 jz short exit_WriteToWIN_INI_proc ; Get rid of stack frame (LEAVE)
00401982 51 push ecx ; ecx=new string val for win.ini
00401983 8D 45 B0 lea eax, [ebp+CompatStringBuf]
00401986 68 2C 40 40 00 push offset aFormatString0x_x ; Format String == 0x%X
0040198B 50 push eax ; Buffer of string == CompatStringBuf
0040198C FF 15 90 62 40 00 call ds:wsprintfA ; wsprintfA:
00401992 83 C4 0C add esp, 0Ch ; adjust stack --wsprintfA = c_decl
00401995 8D 4D B0 lea ecx, [ebp+CompatStringBuf]
00401998 8B 35 D4 61 40 00 mov esi, ds:WriteProfileStringA ; WriteProfileStringA:
0040199E 51 push ecx ; string to write
0040199F 68 A0 52 40 00 push offset keyname
004019A4 68 50 52 40 00 push offset SectionName
004019A9 FF D6 call esi
004019A9_________________________WritePrivateProfileString( keyname, SectionName, wsprintf( CompatStringBuf, aFormatString0x_x))
004019AB 68 28 40 40 00 push offset unk_404028 ; String to write
004019B0 68 28 40 40 00 push offset unk_404028 ; KeyName
004019B5 68 28 40 40 00 push offset unk_404028 ; SectionName
004019BA FF D6 call esi
004019BA_________________________WritePrivateProfileString( loc_404028, loc_404028, loc_404028)
004019BC 8B 0D 14 40 40 00 mov ecx, new_stringval ; 404014=EXEName
004019C2 89 0D 10 40 40 00 mov old_stringval, ecx ; Set 404010=EXEName
004019C8
004019C8 exit_WriteToWIN_INI_proc: ; CODE XREF: WriteToWIN_INI+13
004019C8 5E pop esi ; Get rid of stack frame (LEAVE
004019C9 8B E5 mov esp, ebp
004019CB 5D pop ebp
004019CC C3 retn
004019CC WriteToWIN_INI endp
Now, in C, this would be
//global data
char new_stringval[], old_stringval[], keyname[], SectionName[], strangeval[];
//....
WriteToWIN_INI() {
char CompatStringBuf[50];
if (new_stringval != old_stringval) {
WritePrivateProfileString( keyname, SectionName, wsprintf( CompatStringBUf, aFormatString0x_x) );
WritePrivateProfileString( strange_var, strange_var, strange_var);
}
ret;
}
Recovering Source Code
The primary function of reverse engineering is the recovery of lost or inaccessible source code. Often only a specific
section of the source code is needed, for example the routine which provides copy protection or
time limits, an area of code whose functionality the reverse engineer wishes to duplicate but whose workings he is
unclear on (case in point: Microsoft's reverse-engineering of Stacker to duplicate its functionality in DOS),
or a procedure which has an error that needs to be modified. With the migration of legacy code to modern machines
and operating systems, in particular relating to the Y2K "crisis", it is necessary to regain the entire source code of an
application in order to rewrite it, or to port it to another system.
The example used in this case is the ghf crackme (supplied as a curio by Blitz), a DOS application that
prompts for a password and then exits. Quick perusals of this .COM file with debug and sourcer prove it to be encrypted and
somewhat tricky: it makes a good case-in-point for recovering a program's source code not only to bypass its protection (in this
case, a simple password), but to learn some advanced assemlbly coding techniques from its author.
To start with, open the file in a disassembler (IDA is used throughout this example, and is highly recommended for this type of work
for reasons to be made clear) and mark the obvious code/data areas:
seg000:0100 90 public start
seg000:0100 start proc near
seg000:0100 nop ; No Operation
seg000:0101 90 nop ; No Operation
seg000:0102 90 nop ; No Operation
seg000:0103 B4 09 mov ah, 9
seg000:0105 BA 25 01 mov dx, 125h
seg000:0108 CD 21 int 21h ; DOS - PRINT STRING
seg000:0108 ; DS:DX -> string terminated by "$"
seg000:010A B4 0A mov ah, 0Ah
seg000:010C BA 1A 01 mov dx, 11Ah
seg000:010F CD 21 int 21h ; DOS - BUFFERED KEYBOARD INPUT
seg000:010F ; DS:DX -> buffer
seg000:0111 89 E5 mov bp, sp
seg000:0113 BC 78 02 mov sp, 278h
seg000:0116 FA cli ; Clear Interrupt Flag
seg000:0117 E9 B3 00 jmp loc_0_1CD
seg000:0117 ; ---------------------------------------------------------------------------
seg000:011A 0A 00 00 00 dd 0Ah
seg000:011E 00 00 00 00 dd 0
seg000:0122 00 00 dw 0
seg000:0124 00 db 0
seg000:0125 20 20 5B 67 48 46+aGhfCrackingTut db ' [gHF] Cracking tutorial ',0Dh,0Ah
seg000:0125 5D 20 43 72 61 63+db ' By Sun-Tzu` ',0Dh,0Ah
seg000:0125 6B 69 6E 67 20 74+db 'Crack to be a trial member',0Dh,0Ah
seg000:0125 75 74 6F 72 69 61+db 0Dh,0Ah
seg000:0125 6C 20 0D 0A 20 20+db 'Enter the Password: $'
seg000:0190 0D 0A 49 6E 63 6F+aIncorrect db 0Dh,0Ah
seg000:0190 72 72 65 63 74 21+db 'Incorrect!',0Dh,0Ah,'$'
seg000:019F 0D 0A 43 6F 6E 67+aCongratulation db 0Dh,0Ah
seg000:019F 72 61 74 75 6C 61+db 'Congratulations. Contact #[gHF] on DALNet',0Dh,0Ah,'$'
seg000:01CD ; ---------------------------------------------------------------------------
seg000:01CD
seg000:01CD loc_0_1CD: ; CODE XREF: start+17^j
seg000:01CD B9 79 02 mov cx, 279h
seg000:01D0 81 E9 FC 01 sub cx, 1FCh ; Integer Subtraction
seg000:01D4 BE FA 01 mov si, 1FAh
seg000:01D7 31 DB xor bx, bx ; Logical Exclusive OR
seg000:01D9 8E C3 mov es, bx
seg000:01DB assume es:nothing
seg000:01DB 26 A1 84 00 mov ax, es:84h
seg000:01DF 26 A3 0C 00 mov es:0Ch, ax
seg000:01E3 26 A1 86 00 mov ax, es:86h
seg000:01E7 26 A3 0E 00 mov es:0Eh, ax
seg000:01EB
seg000:01EB loc_0_1EB: ; CODE XREF: start+FA^j
seg000:01EB 58 pop ax
seg000:01EC 32 04 xor al, [si] ; Logical Exclusive OR
seg000:01EE 50 push ax
seg000:01EF 4C dec sp ; Decrement by 1
seg000:01F0 4E dec si ; Decrement by 1
seg000:01F1 81 FE EB 01 cmp si, 1EBh ; Compare Two Operands
seg000:01F5 73 03 jnb loc_0_1FA ; Jump if Not Below (CF=0)
seg000:01F7 BE FA 01 mov si, 1FAh
seg000:01FA
seg000:01FA loc_0_1FA: ; CODE XREF: start+F5^j
seg000:01FA E2 EF loop loc_0_1EB ; Loop while CX != 0
seg000:01FC E9 35 4C jmp near ptr 4E34h ; Jump
seg000:01FC start endp
seg000:01FC
seg000:01FF ; ---------------------------------------------------------------------------
seg000:01FF 00 17 add [bx], dl ; Add
seg000:0201 CC int 3 ; Trap to Debugger
seg000:0202 03 CC add cx, sp ; Add
seg000:0204 7B BC jnp near ptr aCongratulation+23h ; Jump if Not Parity (PF=0)
seg000:0206 70 04 jo loc_0_20C ; Jump if Overflow (OF=1)
seg000:0208 D2 80 06 FB rol byte ptr [bx+si-4FAh], cl ; Rotate Left
seg000:020C
seg000:020C loc_0_20C: ; CODE XREF: seg000:0206^j
seg000:020C 76 7E ; seg000:0220^j
seg000:020C jbe near ptr 28Ch ; Jump if Below or Equal (CF=1 | ZF=1)
seg000:020E 48 dec ax ; Decrement by 1
seg000:020F ED in ax, dx
seg000:0210 FA cli ; Clear Interrupt Flag
seg000:0211 CD 33 int 33h ; - MS MOUSE -
seg000:0213 75 6F jnz near ptr 284h ; Jump if Not Zero (ZF=0)
seg000:0215 BA 70 1C mov dx, 1C70h
seg000:0218 C4 6A 2C les bp, [bp+si+2Ch] ; Load Full Pointer to ES:xx
seg000:021B assume es:nothing
seg000:021B 08 50 6A or [bx+si+6Ah], dl ; Logical Inclusive OR
seg000:021E 7C 9F jl near ptr aCongratulation+20h ; Jump if Less (SF!=OF)
seg000:0220 7A EB jp near ptr loc_0_20C+1 ; Jump if Parity (PF=1)
seg000:0222 89 76 4C mov [bp+4Ch], si
seg000:0225 5C pop sp
seg000:0226 25 FC 69 and ax, 69FCh ; Logical AND
seg000:0226 ; ---------------------------------------------------------------------------
At line seg000:0113
this program is already doing something tricky. Browse over lines
seg000:01CD
to seg000:01FA
to get the full effect. The program is setting the
top of the stack to the last address of the program; then the address on the top of the stack is POPed into
AX, XORed with the address in SI, and pushed back onto the stack. The stack pointer (SP) is manually decremented so
that the next address to be decrypted ( the address preceding the one that was just decrypted) becomes the top of the
stack. The value in SI loops through the 16 bytes between seg000:01FA
and seg000:01EB
, with the overall
effect that the opcodes between seg000:01FC
and seg000:0278
are XORed with the opcodes
between seg000:01FA
and seg000:01EB
. The following IDc script emulates this decryption and
directly patches the bytes in IDA:
// ghf.idc : XOR decryption for ghf-crackme
//code 1998 per mammon_
#include
static main(){
auto start_xor, curr_xor, curr_byte;
start_xor = SegStart( FirstSeg() ) + 0xFA;
curr_xor = start_xor;
curr_byte = SegEnd( FirstSeg() ) - 1;
Message( "StartXor " + atoa(start_xor) + " Curr_Byte " + atoa(curr_byte) +"\n");
while ( atoa(curr_byte) != "seg000:01FB" ) {
PatchByte(curr_byte, Byte(curr_byte) ^ Byte(curr_xor) );
curr_xor = PrevAddr(curr_xor);
if ( curr_xor == SegStart( FirstSeg() ) + 0xEA) curr_xor = start_xor;
curr_byte = PrevAddr(curr_byte);
}
Message("Done!\n");
}
Running this script and changing programs names/comments to reflect what is know about the code
will provide the following information (not showing the first 0xCC lines of the program):
seg000:01CD PrepareForSMC: ; CODE XREF: start+17^j
seg000:01CD B9 79 02 mov cx, 279h ; Count = 1 more than stack
seg000:01D0 81 E9 FC 01 sub cx, 1FCh ; Count -Starting Address = 7D
seg000:01D4 BE FA 01 mov si, 1FAh ; XOR with this address
seg000:01D7 31 DB xor bx, bx ; set BX=0
seg000:01D9 8E C3 mov es, bx ; set ES=0
seg000:01DB assume es:nothing
seg000:01DB 26 A1 84 00 mov ax, es:84h ; byte 4 of command line
seg000:01DF 26 A3 0C 00 mov es:0Ch, ax ; offset of Int 22h Termination handler
seg000:01DF Command-Line byte 4 to offset (0) of Int22h Handler
seg000:01E3 26 A1 86 00 mov ax, es:86h ; byte 6 of command line
seg000:01E7 26 A3 0E 00 mov es:0Eh, ax ; segment of Int23h Ctrl-C handler
seg000:01EB SMC_XOR_loop: XOR addresses 278-1FC descending
seg000:01EB
seg000:01EB SMC_XOR_loop: ; CODE XREF: start+FA^j
seg000:01EB 58 pop ax ; Get byte to XOR
seg000:01EC 32 04 xor al, [si] ; XOR with contents of SI
seg000:01EE 50 push ax ; Write byte back to file
seg000:01EF 4C dec sp ; Decrement top of stack to nextencryptedbyte
seg000:01F0 4E dec si ; Decrement SI to nextXOR-with address
seg000:01F1 81 FE EB 01 cmp si, 1EBh ; if SI < 1EBh then SI= 1FAh
seg000:01F5 73 03 jnb NextXOR ; Jump if Not Below (CF=0)
seg000:01F7 BE FA 01 mov si, 1FAh
seg000:01FA
seg000:01FA NextXOR: ; CODE XREF: start+F5^j
seg000:01FA E2 EF loop SMC_XOR_loop ; Get byte to XOR
seg000:01FC B9 79 02 mov cx, 279h ; Set Count = 1 more than Last BYte in File
seg000:01FF 81 E9 27 02 sub cx, 227h ; Set Count = 52h
seg000:0203 BF 78 02 mov di, 278h ; First byte to modify= last byte in file
seg000:0206 SMC2 _XOR_loop: XOR addresses 278-226 descending
seg000:0206
seg000:0206 SMC2_XOR_loop: ; CODE XREF: seg000:0225^j
seg000:0206 8A 05 mov al, [di] ; Get byte to XOR
seg000:0208 30 D8 xor al, bl ; XOR with bl (starting bl=0)
seg000:020A 34 FF xor al, 0FFh ; XOR with FF
seg000:020C 26 32 06 6C 04 xor al, es:46Ch ; XOR with 0
seg000:0211 26 32 06 6C 04 xor al, es:46Ch ; XOR with 0
seg000:0216 8A 1D mov bl, [di] ; Get byte to XOR with
seg000:0218 26 32 1E 0C 00 xor bl, es:0Ch ; XOR with 66
seg000:021D 26 32 1E 84 00 xor bl, es:84h ; XOR with 66
seg000:0222 88 05 mov [di], al ; Write byte back to code
seg000:0224 4F dec di ; Next code byte (preceding address)
seg000:0225 E2 DF loop SMC2_XOR_loop ; Get byte to XOR
seg000:0225 ; ---------------------------------------------------------------------------
Here there is a second decryption routine, handled differently. The count (CX) for the loop is set to
52 (279-227), indicating that 52 lines of the program will be decrypted. The first address to decrypt is set
to seg000:0278
, the last line of the program, and is decremented during the loop so that all code
down to seg000:0226
will be decrypted. At the start of the loop BL is set to 0 (far, far up in the code
at seg000:01D7
); the opcode to be decrypted is XORed with BL and then with FFh and written back to
memory, and the original opcode (before decryption) is saved in BL. Note that lines seg000:020C
and
seg000:0211
cancel each other out, as do lines seg000:0218
and seg000:021D
(though
the last one tries to be tricky). Once again, an IDC script is prepared for the decryption:
// ghf2.idc : Second XOR decryption for ghf-crackme
//code 1998 per mammon_
#include
static main(){
auto bl_xor, count, curr_byte, temp_byte;
bl_xor = 0x0;
count = 0x52;
curr_byte = SegEnd( FirstSeg() ) - 1;
while ( count > "0" ) {
temp_byte = Byte(curr_byte);
Message( "XORing " + atoa(curr_byte) + " with " + ltoa(bl_xor, 16) + "\n");
temp_byte = temp_byte ^ bl_xor;
temp_byte = temp_byte ^ 0xFF;
bl_xor = Byte(curr_byte);
PatchByte(curr_byte, temp_byte );
curr_byte = PrevAddr(curr_byte);
count = count - 1;
}
Message("Done!\n");
}
This provides the following final version of the disassembled code which, once commented, makes everything clear:
seg000:0100 90 public start
seg000:0100 start proc near
seg000:0100 nop ; No Operation
seg000:0101 90 nop ; No Operation
seg000:0102 90 nop ; No Operation
seg000:0103 B4 09 mov ah, 9
seg000:0105 BA 25 01 mov dx, 125h
seg000:0108 CD 21 int 21h ; DOS - PRINT STRING
seg000:0108 ; DS:DX -> string terminated by "$"
seg000:010A B4 0A mov ah, 0Ah
seg000:010C BA 1A 01 mov dx, 11Ah
seg000:010F CD 21 int 21h ; DOS - BUFFERED KEYBOARD INPUT
seg000:010F ; DS:DX -> buffer
seg000:0111 89 E5 mov bp, sp ; save Stack Pointer
seg000:0113 BC 78 02 mov sp, 278h ; Set Top of Stack = last byte in file
seg000:0116 FA cli ; Clear Interrupt Flag
seg000:0117 E9 B3 00 jmp PrepareForSMC ; Count = 1 more than stack
seg000:0117 ; ----------------------------------------------------------------------
seg000:011A ------------- Keyboard Input Buffer -------------
seg000:011A 0A MaxBufferLength db 0Ah ; Max length of input = 0ah or 10 dec
seg000:011B 00 KeyboardInputLength db 0 ; DATA XREF: seg000:022F^r
seg000:011B ; seg000:0242^r
seg000:011B ; Number of characters entered
seg000:011C 00 00 00 00 KeyboardInputBuffer dd 0
seg000:0120 00 00 00 00 dd 0
seg000:0124 00 db 0
seg000:0124 ^------------ Keyboard Input Buffer ------------^
seg000:0125 20 20 5B 67 48 46+aGhfCrackingTut db ' [gHF] Cracking tutorial ',0Dh,0Ah
seg000:0125 5D 20 43 72 61 63+db ' By Sun-Tzu` ',0Dh,0Ah
seg000:0125 6B 69 6E 67 20 74+db 'Crack to be a trial member',0Dh,0Ah
seg000:0125 75 74 6F 72 69 61+db 0Dh,0Ah
seg000:0125 6C 20 0D 0A 20 20+db 'Enter the Password: $'
seg000:0190 0D 0A 49 6E 63 6F+aIncorrect db 0Dh,0Ah
seg000:0190 72 72 65 63 74 21+db 'Incorrect!',0Dh,0Ah,'$'
seg000:019F 0D 0A 43 6F 6E 67+aCongratulation db 0Dh,0Ah
seg000:019F 72 61 74 75 6C 61+db 'Congratulations. Contact #[gHF] on DALNet',0Dh,0Ah,'$'
seg000:01CD ; ----------------------------------------------------------------------
seg000:01CD
seg000:01CD PrepareForSMC: ; CODE XREF: start+17^j
seg000:01CD B9 79 02 mov cx, 279h ; Count = 1 more than stack
seg000:01D0 81 E9 FC 01 sub cx, 1FCh ; Count -Starting Address = 7D
seg000:01D4 BE FA 01 mov si, 1FAh ; XOR with this address
seg000:01D7 31 DB xor bx, bx ; set BX=0
seg000:01D9 8E C3 mov es, bx ; set ES=0
seg000:01DB assume es:nothing
seg000:01DB 26 A1 84 00 mov ax, es:84h ; byte 4 of command line (66 displayed in debug)
seg000:01DF 26 A3 0C 00 mov es:0Ch, ax ; offset of Int 22h Termination handler
seg000:01DF Command-Line byte 4 to offset (0) of Int22h Handler
seg000:01E3 26 A1 86 00 mov ax, es:86h ; byte 6 of command line (63 displayed by debug)
seg000:01E7 26 A3 0E 00 mov es:0Eh, ax ; segment of Int23h Ctrl-C handler
seg000:01EB SMC_XOR_loop: XOR addresses 278-1FC descending
seg000:01EB
seg000:01EB SMC_XOR_loop: ; CODE XREF: start+FA^j
seg000:01EB 58 pop ax ; Get byte to XOR
seg000:01EC 32 04 xor al, [si] ; XOR with contents of SI
seg000:01EE 50 push ax ; Write byte back to file
seg000:01EF 4C dec sp ; Decrement top of stack to nextencryptedbyte
seg000:01F0 4E dec si ; Decrement SI to nextXOR-with address
seg000:01F1 81 FE EB 01 cmp si, 1EBh ; if SI < 1EBh then SI= 1FAh
seg000:01F5 73 03 jnb NextXOR ; Jump if Not Below (CF=0)
seg000:01F7 BE FA 01 mov si, 1FAh
seg000:01FA
seg000:01FA NextXOR: ; CODE XREF: start+F5^j
seg000:01FA E2 EF loop SMC_XOR_loop ; Get byte to XOR
seg000:01FC B9 79 02 mov cx, 279h ; Set Count = 1 more than Last BYte in File
seg000:01FC start endp
seg000:01FC
seg000:01FF 81 E9 27 02 sub cx, 227h ; Set Count = 52h
seg000:0203 BF 78 02 mov di, 278h ; First byte to modify= last byte in file
seg000:0206 SMC2_XOR_loop: XOR addresses 278-226 descending
seg000:0206
seg000:0206 SMC2_XOR_loop: ; CODE XREF: seg000:0225^j
seg000:0206 8A 05 mov al, [di] ; Get byte to XOR
seg000:0208 30 D8 xor al, bl ; XOR with bl (starting bl=0)
seg000:020A 34 FF xor al, 0FFh ; XOR with FF
seg000:020C 26 32 06 6C 04 xor al, es:46Ch ; XOR with 0--do nothing
seg000:0211 26 32 06 6C 04 xor al, es:46Ch ; XOR with 0--undo last line
seg000:0216 8A 1D mov bl, [di] ; XOR next byte with original encrypted present byte
seg000:0218 26 32 1E 0C 00 xor bl, es:0Ch ; XOR with 66
seg000:021D 26 32 1E 84 00 xor bl, es:84h ; XOR with 66--Undo last line
seg000:0222 88 05 mov [di], al ; Write byte back to code
seg000:0224 4F dec di ; Next code byte (preceding address)
seg000:0225 E2 DF loop SMC2_XOR_loop ; Get byte to XOR
seg000:0227 String Compare Routine:
seg000:0227 89 EC mov sp, bp ; restore stack pointer to real stack
seg000:0229 8C C8 mov ax, cs ; set ax=Code Segment
seg000:022B 8E C0 mov es, ax ; Set ES = Code Segment
seg000:022D assume es:seg000
seg000:022D 30 ED xor ch, ch ; Set CH = 0
seg000:022F 2E 8A 0E 1B 01 mov cl, cs:KeyboardInputLength ; Set CL= length of user input
seg000:0234 BE 1C 01 mov si, 11Ch ; String 1 (User Input)
seg000:0237 BF 57 02 mov di, 257h ; String 2 (Stored Pwd)
seg000:023A
seg000:023A loc_0_23A: ; CODE XREF: seg000:0240^j
seg000:023A 80 35 FF xor byte ptr [di], 0FFh ; XOR pwd byte with FF
seg000:023D A6 cmpsb ; Compare Strings
seg000:023E 75 0F jnz Incorrect_Guess ; Incorrect
seg000:0240 E2 F8 loop loc_0_23A ; XOR pwd byte with FF
seg000:0242 2E 80 3E 1B 01 00 cmp cs:KeyboardInputLength, 0 ; Number of characters entered
seg000:0248 74 05 jz Incorrect_Guess ; Incorrect
seg000:024A BA 9F 01 mov dx, 19Fh ; Congrats
seg000:024D EB 03 jmp short loc_0_252
seg000:024F
seg000:024F Incorrect_Guess: ; CODE XREF: seg000:023E^j
seg000:024F BA 90 01 ; seg000:0248^j
seg000:024F mov dx, 190h ; Incorrect
seg000:0252
seg000:0252 loc_0_252: ; CODE XREF: seg000:024D^j
seg000:0252 B4 09 mov ah, 9
seg000:0254 CC int 3 ; Trap to Debugger--Prob supposed to be Int21h
seg000:0255 EB 09 jmp short Jmp_Over_Data ; Count = 26A
seg000:0257 Encrypted Password:
seg000:0257 AC AA B1 D2 dd 0D2B1AAACh ; 53 55 4E 2D
seg000:025B AB A5 AA C6 dd 0C6AAA5ABh ; 54 5A 55 39
seg000:025F CA db 0CAh ; 35
seg000:025F Unencypted Password: SUN-TZU95
seg000:0260
seg000:0260 Jmp_Over_Data: ; CODE XREF: seg000:0255^j
seg000:0260 B9 6A 02 mov cx, 26Ah ; Count = 26A
seg000:0263 81 E9 00 01 sub cx, 100h ; Count - 100h = 16A
seg000:0267 BF 00 01 mov di, 100h ; Set byte-to-encrypt to 100h (NOP)
seg000:026A
seg000:026A SMC3_XOR_loop: ; CODE XREF: seg000:0270^j
seg000:026A 8A 45 FF mov al, [di-1]
seg000:026D 30 05 xor [di], al ; Logical Exclusive OR
seg000:026F 47 inc di ; Increment by 1
seg000:0270 E2 F8 loop SMC3_XOR_loop ; Loop while CX != 0
seg000:0272 8C C8 mov ax, cs
seg000:0274 8E D8 mov ds, ax
seg000:0276 B4 4C mov ah, 4Ch
seg000:0278 CC int 3 ; Trap to Debugger
seg000:0278 seg000 ends
seg000:0278 end start
Note how the password is encrypted with a simple XOR FFh; the bytes are left encrypted in the file (no need
going through the trouble of an IDC script with this one) and stored in their unencrypted state in the
comments.
The last XOR routine (SMC3) is used to encrypt the entire file rather than to decrypt any more
of it; in this way, since once the program has terminated it is still present in memory and can be
viewed with a debugger, the code will be once again encrypted and any would-be cracker would not be able to
save it directly to disk. For the curious, the following IDC script can be used to simulate the SMC3
loop within IDA:
// gh3.idc : XOR decryption for ghf-crackme
//code 1998 per mammon_
#include
static main(){
auto curr_xor, curr_byte, count;
curr_byte = SegStart( FirstSeg() ) + 0x100;
curr_xor = curr_byte - 1;
count = 0x16A;
while ( count > 0 ) {
PatchByte(curr_byte, Byte(curr_byte) ^ Byte(curr_xor) );
curr_xor = curr_byte;
curr_byte = NextAddr(curr_byte);
count = count - 1;
}
Message("Done!\n");
}
At this point the basic source code is available for recovery. IDA can output an .asm file
which, with the addition of symbolic labels (rather than absolute addresses) and the
stripping of unneeded information, will look like the following:
; This file is generated by The Interactive Disassembler (IDA)
; File Name : D:\GHF.COM
; Format : MS DOS COM File
; Base Address: 1000h Range: 10100h - 10279h Loaded length: 0179h
seg000 segment byte public 'CODE'
assume cs:seg000
org 100h
assume es:nothing, ss:nothing, ds:seg000
start:
nop
nop
nop
mov ah, 9
mov dx, offset aGhfCrackingTut
int 21h ; DOS - PRINT STRING
mov ah, 0Ah
mov dx, offset MaxBufferLength ; start of buffer
int 21h ; DOS - BUFFERED KEYBOARD INPUT
mov bp, sp
mov sp, offset EOF
cli
jmp PrepareForSMC
;------------- Keyboard Input Buffer -------------
MaxBufferLength db 0Ah ;should be 09h [FIX]
KeyboardInputLength db 0
KeyboardInputBuffer dd 0
dd 0
db 0
;------------ Strings ----------------------------
aGhfCrackingTut db ' [gHF] Cracking tutorial ',0Dh,0Ah
db ' By Sun-Tzu` ',0Dh,0Ah
db 'Crack to be a trial member',0Dh,0Ah
db 0Dh,0Ah
db 'Enter the Password: $'
aIncorrect db 0Dh,0Ah
db 'Incorrect!',0Dh,0Ah,'$'
aCongratulation db 0Dh,0Ah
db 'Congratulations. Contact #[gHF] on DALNet',0Dh,0Ah,'$'
PrepareForSMC:
mov cx, offset EOF + 1 ; Count = 1 more than stack
sub cx, offset Encrypted_Section_1 ; Count -Starting Address = 7D
mov si, offset NextXOR ; XOR with this address
xor bx, bx
mov es, bx
mov ax, es:84h
mov es:0Ch, ax
mov ax, es:86h
mov es:0Eh, ax
SMC_XOR_loop:
pop ax ; Get byte to XOR
xor al, [si] ; XOR with contents of SI
push ax ; Write byte back to file
dec sp ; Decrement top of stack to next encrypted byte
dec si ; Decrement SI to next XOR-with address
cmp si, offset SMC_XOR_loop ; if SI < 1EBh then SI= 1FAh
jnb NextXOR
mov si, offset NextXOR
NextXOR:
loop SMC_XOR_loop
Encrypted_Section_1:
mov cx, offset EOF + 1 ; Set Count = 1 more than Last BYte in File
sub cx, offset String_Compare_Routine ; Set Count = 52h
mov di, offset EOF ; First byte to modify= last byte in file
SMC2_XOR_loop:
mov al, [di] ; Get byte to XOR
xor al, bl ; XOR with bl (starting bl=0)
xor al, 0FFh ; XOR with FF
xor al, es:46Ch ; XOR with 0--do nothing
xor al, es:46Ch ; XOR with 0--undo last line
mov bl, [di] ; XOR next byte with original encrypted present byte
xor bl, es:0Ch ; XOR with 66
xor bl, es:84h ; XOR with 66--Undo last line
mov [di], al ; Write byte back to code
dec di ; Next code byte (preceding address)
loop SMC2_XOR_loop
String_Compare_Routine:
mov sp, bp ; restore stack pointer to real stack
mov ax, cs
mov es, ax
xor ch, ch
mov cl, KeyboardInputLength
mov si, offset KeyboardInputBuffer
mov di, offset Password
Compare_Loop:
xor byte ptr [di], 0FFh ; XOR pwd byte with FF
cmpsb
jnz Incorrect_Guess
loop Compare_Loop
cmp KeyboardInputLength, 0
jz Incorrect_Guess
mov dx, offset aCongratulation
jmp short OutputString
Incorrect_Guess:
mov dx, offset aIncorrect
Output_String:
mov ah, 9
int 3 ;Should be Int21h [FIX]
jmp short Jmp_Over_Data
Password dd 0D2B1AAACh ; 53 55 4E 2D SUN-
dd 0C6AAA5ABh ; 54 5A 55 39 TZU9
db 0CAh ; 35 5
Jmp_Over_Data:
mov cx, offset SMC3_XOR_loop ; Count = 26A
sub cx, offset start ; Count - 100h = 16A
mov di, offset start ; Set byte-to-encrypt to 100h (NOP)
SMC3_XOR_loop:
mov al, [di-1]
xor [di], al
inc di
loop SMC3_XOR_loop ; This will encrypt file down to 26A
mov ax, cs
mov ds, ax
mov ah, 4Ch
int 3 ;Should be Int21h [FIX]
EOF:
seg000 ends
end start
Note that there are a few small problems with the code (for example, why the use of the correct
password causes a loop in the program instead of displaying the desired string); the fixes for these
are given in the code comments. The file should compile with tasm, though it will need to be
encrypted twice after compilation.
Home * Tools * 95/NT Tech Info * Links