Monday, August 17, 2009

Hackers Disassembling 1.1.4.1 (Method 0: Cracking the Original Password )

Method 0: Cracking the Original Password


Using the wldr utility delivered with SoftIce, load the file to be cracked by specifying its name on the command line, for example, as follows:

> wldr simple.exe

Yes, wldr is a 16-bit loader, and NuMega recommends that you use its 32-bit version, loader32, developed for Windows NT/9x. They have a point, but loader32 often malfunctions. (In particular, it does not always stop at the first line of the program.) However, wldr works with 32-bit applications, and the only disadvantage is that it doesn't support long file names.

If the debugger is configured correctly, a black textbox appears — a surprise to beginners. Command.com in the era of graphical interfaces! Why not? It's faster to type a command than to search for it in a long chain of nested submenus, trying to recollect where you saw it last. Besides, language is the natural means to express thoughts; a menu is best suited for listing dishes at a cafe. As an example, try to print the list of files in a directory using Windows Explorer. Have you succeeded? In MS-DOS, it was simple: dir > PRN.

If you only see INVALID in the text box (this will probably be the case), don't get confused: Windows simply hasn't yet allocated the executable file in memory. You just need to press the key (an analog of the P command that traces without entering, or stepping over, the function) or the key (an analog of the T command that traces and enters, or steps into, the function). Everything will fall into place.

001B:00401277 INVALID
001B:00401279 INVALID
001B:0040127B INVALID
001B:0040127D INVALID
:P

001b:00401285 push ebx
001b:00401286 push esi
001b:00401287 push edi
001b:00401288 mov [ebp-18], esp
001B:0040128B call [KERNEL32!GetVersion]
001b:00401291 xor edx, edx
001b:00401293 mov dl, ah
001b:00401295 mov [0040692c], edx

Pay attention: Unlike the DUMPBIN disassembler, SoftIce recognizes system function names, thus significantly simplifying analysis. However, there's no need to analyze the entire program. Let's quickly try to find the protection mechanism and, without going into detail, chop it off altogether. This is easy to say—and even easier to do! Just recall where the reference password is located in memory. Umm… Is your memory failing? Can you remember the exact address? We'll have to find it!

We'll ask the map32 command for help. It displays the memory map of a selected module. (Our module has the name "simple," the name of the executable file without its extension.)

:map32 simple
Owner Obj Name Obj# Address Size Type
simple .text 0001 001B:00401000 00003F66 CODE RO
simple .rdata 0002 0023:00405000 0000081E IDATA RO
simple .data 0003 0023:00406000 00001E44 IDATA RW

Here is the address of the beginning of the .data section. (Hopefully you remember that the password is in the .data section.) Now, create the data window using the wc command. Then, issue the d 23:406000 command, and press the + key combination to get to the desired window. Scroll using the <↓> key, or put a brick on the key. We won't need to search long.

0023:00406040 6D 79 47 4F 4F 44 70 61-73 73 77 6F 72 64 0A 00 myGOODpassword...
0023:00406050 57 72 6F 6E 67 20 70 61-73 73 77 6F 72 64 0A 00 Wrong password..
0023:00406060 50 61 73 73 77 6F 72 64-20 4F 4B 0A 00 00 00 00 Password OK.....
0023:00406070 47 6E 40 00 00 00 00 00-40 6E 40 00 01 01 00 00 Gn@.....@n@.....
0023:00406080 00 00 00 00 00 00 00 00-00 10 00 00 00 00 00 00 ................
0023:00406090 00 00 00 00 00 00 00 00-00 00 00 00 02 00 00 00 ................
0023:004060A0 01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0023:004060B0 00 00 00 00 00 00 00 00-00 00 00 00 02 00 00 00 ................

We've got it! Remember that to be checked, the user-entered password needs to be compared to the model value. By setting a breakpoint at the instruction for reading address 0x406040, we will catch the comparison "by its tail." No sooner said than done.

:bpm 406040

Now, press the + key combination (or issue the x command) to exit the debugger. Enter any password that comes to mind — KPNC++, for example. The debugger pops up immediately:

001B:004010B0 mov eax, [edx]
001B:004010B2 cmp al, [ecx]
001B:004010B4 jnz 004010E4 (JUMP ↑)
001B:004010B6 or al, al
001B:004010B8 jz 004010E0
001B:004010BA cmp ah, [ECX+01]
001B:004010BD jnz 004010E4
001B:004010BF or ah, ah
Break due to BPMB #0023:00406040 RW DR3 (ET=752.27 milliseconds)
MSR LastBranchFromIp=0040104E
MSR LastBranchToIp=004010A0

Because of certain architectural features of Intel processors, the break is activated after the instruction has been executed (i.e., CS:EIP points to the following executable instruction — to JNZ 004010E4, in our case). Therefore, the memory location with our breakpoint was addressed by the CMP AL, [ECX] instruction. What is in AL? Let's look at the line above: MOV EAX, [EDX]. We can assume that ECX contains a pointer to the string with the reference password (because it caused the break in execution). This means EDX must be a pointer to the password entered by the user. Let's verify our assumption.

:d edx
0023:00406040 6D 79 47 4F 4F 44 70 61-73 73 77 6F 72 64 0A 00 myGOODpassword..
:d edx
0023:0012FF18 4B 50 4E 43 2B 2B 0A 00-00 00 00 00 00 00 00 00 KPNC++..........

We were right. Now, the only question is how to crack this. We might replace JNZ with JZ, or more elegantly replace EDX with ECX — then the reference password will be compared to itself! Wait a minute… We shouldn't hurry. What if we aren't in the protection routine, but in the library function (actually, in strcmp)? Changing it will result in the program perceiving any strings as identical, not just the reference and entered passwords. It won't hurt our example, in which strcmp was only called once, but it would cause normal, fully functional applications to fail. What can be done?

Let's exit strcmp and change the IF that determines whether or not the password is right. For this purpose, P RET is used (to trace until the RET instruction occurs — returning from the function).

:P RET
001B:0040104E call 004010A0
001B:00401053 add esp, 08
001B:00401056 test eax, eax
001B:00401058 jz 00401069
001B:0040105A push 00406050
001B:0040105F call 00401234
001B:00401064 add esp, 04
001B:00401067 jmp 0040106B

This is familiar. We were previously here with the disassembler. We can take the same steps now: Replace the TEST instruction with XOR, or write the sequence of bytes that identifies… Just a moment. Where are our bytes, the hexadecimal instructions? SoftIce doesn't display them by default, but the CODE ON command forces it to do so.

code on
001B:0040104E E84D000000 call 004010A0
001B:00401053 83C408 add esp, 08
001B:00401056 85C0 test eax, eax
001B:00401058 740F jz 00401069
001B:0040105A 6850604000 push 00406050
001B:0040105F E8D0010000 call 00401234
001B:00401064 83C404 add esp, 04
001B:00401067 EB02 jmp 0040106B

That's better. But how can we be sure that these bytes will be in the executable file at the same addresses? The question isn't as silly as it may seem. Try to crack the example crackme0x03 using the method just given. At first, it seems similar to simple.exe—even the reference password is located at the same address. Let's set a breakpoint on it, wait for the debugger to pop up, exit the comparing procedure, and look at the code identical to the one we previously came across.

001B:0042104E E87D000000 call 004210D0
001B:00421053 83C408 add esp, 08
001B:00421056 85C0 test eax, eax
001B:00421058 740F jz 00421069

Start HIEW, jump to address 0x421053, and… Oops; HIEW is upset with us. It says there's no such address in the file! The last byte ends at 0x407FFF. How can we be at 0x421053 in the debugger but not in the file? Perhaps we're in the body of a Windows system function. But Windows system functions are located much higher — beginning at 0x80000000.

The PE file could be loaded at a different address than the one for which it was created. (This property is called relocatability.) The system automatically corrects references to absolute addresses, replacing them with new values. As a result, the file image in memory doesn't correspond to the one written on disk. How can we find the place that needs to be corrected now?

This task is partly facilitated by the system loader, which only can relocate DLLs and always tries to load executable files at their "native" addresses. If this is impossible, loading is interrupted and an error message is sent. Likely, we are dealing with a DLL loaded by the protection we are investigating. Why are DLLs here, and where did they come from?

We'll have to study Listing 2 to find out.

Listing 4.2: The Source Code of crackme0x03


#include
#include


__declspec(dllexport) void Demo()
{
#define PASSWORD_SIZE 100
#define PASSWORD "myGOODpassword\n"

int count=0;
char buff [PASSWORD_SIZE]="";

for(;;)
{
printf("Enter password:");
fgets(&buff[0], PASSWORD_SIZE-1, stdin);

if (strcmp(&buff[0], PASSWORD))
printf("Wrong password\n");
else break;

if (++count>2) return -1;
}
printf("Password OK\n");
}

main()
{
HMODULE hmod;
void (*zzz) ();

if ((hmod=LoadLibrary("crack0~1.exe"))
&& (zzz=(void (*) ())GetProcAddress (h, "Demo")))
zzz();

}




What a way to call a function! This technique exports it directly from the executable file and loads the same file as a DLL. (Yes, the same file can be both the executable application and the DLL.)

"It doesn't make a difference", a naive programmer might object. "Everyone knows that Windows isn't so silly as to load the same file twice. LoadLibrary will return the base address of the crackme0x03 module, but won't allocate memory for it." Nothing of the sort! An artful protection scheme accesses the file by its alternate short name, leaving the system loader in a deep delusion.

The system allocates memory and returns the base address of the loaded module to the hmod variable. The code and data of this module are displaced by the hmod value — the base address of the module with which HIEW and the disassembler work. We can easily figure out the base address: Just call DUMPBIN with the /HEADERS key. (Only a fragment of its response is given.)


>dumpbin /HEADERS crack0x03
OPTIONAL HEADER VALUES
...
400000 image base
...

Hence, the base address is 0x400000 (in bytes). We can determine the load address using the mod -u command in the debugger. (The -u key allows us to display only application modules, not system ones.)

:mod -u
hMod Base PEHeader Module Name File Name
00400000 004000D8 crack0x0 \.PHCK\src\crack0x03.exe
00420000 004200D8 crack0x0 \.PHCK\src\crack0x03.exe
77E80000 77E800D0 kernel32 \WINNT\system32\kernel32.dll
77F80000 77F800C0 ntdll \WINNT\system32\ntdll.dll

Two copies of crack0x03 are loaded at once, and the last one is located at 0x420000 — just what we need! Now, it's easy to calculate that the address 0x421056 (the one we tried to find in the cracked file) "on disk" corresponds to the address 0x421056 - (0x42000 - 0x400000) = 0x421056 - 0x20000 = 0x401056. Let's take a look at that location:

00401056: 85C0 test eax, eax
00401058: 740F je .000401069 --------(1)

Everything is as expected — see how well it matches the dump produced by the debugger:

001B:00421056 85C0 test eax, eax
001B:00421058 740F jz 00421069

This calculation technique is applicable to any DLL, not just to those representing executable files.

If, instead of tracing the addresses, we used the debugger on the program being cracked to look for the sequence of bytes taken from the debugger, including the one in CALL 00422040, would we find the sequence?

001B:0042104E E87D000000 call 004210D0
001B:00421053 83C408 add esp, 08
001B:00421056 85C0 test eax, eax
001B:00421058 740F jz 00421069
:File image in memory
.0040104E: E87D000000 call .0004010D0 --------(1)
.00401053: 83C408 add esp, 008 ; "▪"
.00401056: 85C0 test eax, eax
.00401058: 740F je .000401069 --------(2)
:File image on disk

The same machine code — E8 7D 00 00 00 — corresponds to the CALL 0x4210D0 and CALL 0x4010D0 instructions. How can this be? Here's how: The operand of the 0xE8 processor instruction does not represent the offset of a subroutine; it represents the difference between the offsets of the subroutine and the instruction next to the CALL instruction. Therefore, in the first case, 0x421053 (the offset of the instruction next to CALL) + 0x0000007D (don't forget about the reverse byte order in double words) = 0x4210D0 — the required address. Thus, when the load address is changed, we don't need to correct the CALL instruction.

In the crack0x03 example, the following line is also in another location (which can be found using HIEW):

004012C5: 89154C694000 mov [00040694C], edx

The MOV instruction uses absolute addressing, rather than indirect. What will happen if you change the load address of the module? Will the file image on disk and that in memory be identical in this case?

Looking at the address 0x4212C5 (0x4012C5 + 0x2000) using the debugger, we see that the call does not go to 0x42694C, but to 0x40694C! Our module intrudes in another's domain, modifying it as it likes. This can quickly lead to a system crash! In this case, it doesn't crash, but only because the line being accessed is located in the Startup procedure (in start code), has already been executed (when the application started), and isn't called from the loaded module. It would be another matter altogether if the Demo () function accessed a static variable; the compiler, having substituted its offset, would make the module unrelocatable! It's hard to imagine how DLLs, whose load address isn't known beforehand, manage to work. But there are at least two solutions.

The first is to use indirect addressing instead of direct (for example, [reg+offset_val], where reg is a register containing the base load address, and offset_val is the offset of the memory location from the beginning of the module). This will allow the module to be loaded at any address, but the loss of just one register will appreciably lower the program's performance.

The second is to instruct the loader to correct direct offsets according to a selected base load address. This will slightly slow loading, but it won't affect the speed of the program. This doesn't mean that load time can be neglected; this method simply is preferred by Microsoft.

The problem is distinguishing actual direct offsets from constants that have the same value. It'd be silly to decompile a DLL just to clear up which locations we need to tweak. It's much easier to list the addresses in a special table, bearing the name Relocation [Fix Up] table, directly in the loaded file. The linker is responsible for creating it. Each DLL contains such a table.

To get acquainted with the table, compile and study the following listing.

Listing 4.3: The Source Code of fixupdemo.c


::fixupdemo.c
_ _declspec(dllexport) void meme(int x)
{
static int a=0x666;
a = x;
}
> cl fixupdemo.c /LD




Compile the code, then decompile it right away using "DUMPBIN/DISASM fixupdemo.dll" and "DUMPBIN/SECTION:.data/RAWDATA".

10001000: 55 push ebp
10001001: 8B EC mov ebp, esp
10001003: 8B 45 08 mov eax, dword ptr [ebp+8]
10001006: A3 30 50 00 10 mov [10005030], eax
1000100B: 5D pop ebp
1000100C: C3 ret

RAW DATA #3
10005000: 00 00 00 00 00 00 00 00 00 00 00 00 33 24 00 10 ............3$..
10005010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10005020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10005030: 66 06 00 00 64 11 00 10 FF FF FF FF 00 00 00 00 f...d...........

Judging by the code, the contents of EAX are always written to 0x10005030. Nevertheless, don't jump to conclusions! Try "DUMPBIN/RELOCATIONS fixupdemo.dll".

BASE RELOCATIONS #4
1000 RVA, 154 SizeOfBlock
7 HIGHLOW
1C HIGHLOW
23 HIGHLOW
32 HIGHLOW
3A HIGHLOW

The relocation table isn't empty! Its first entry points to the location 0x100001007, obtained by adding the offset 0x7 with the RVA address 0x1000 and the base load address 0x10000000 (found using DUMPBIN). The location 0x100001007 belongs to the MOV [0x10005030], EAX instruction, and it points to the highest byte of the direct offset. This offset is corrected by the loader while linking the DLL (if required).

Want to check? Let's create two copies of one DLL (such as fixupdemo.dll and fixupdemo2.dll) and load them one by one using the following program:

Listing 4: The Source Code of fixupload.c


: :fixupload.c
#include

main ()
{
void (*demo) (int a) ;
HMODULE h;
if ( (h=LoadLibrary ("fixupdemo.dll") ) &&
(h=LoadLibrary ("fixupdemo2.dll") ) &&
(demo=(void (*) (int a) )GetProcAddress (h, "meme") ) )
demo (0x777);
}
> cl fixupload




Since we can't load two different DLLs at the same address (how will the system know it's the same DLL?), the loader has to relocate one. Let's load the compiled program in the debugger, and set a breakpoint at the LoadLibraryA function. This is necessary to skip the startup code and get into the main function body. (Program execution doesn't start from the main function; instead, it starts from the auxiliary code, in which you can easily "drown.") Where did the A character at the end of the function name come from? Its roots are closely related to the introduction of Unicode in Windows. (Unicode encodes each character with 2 bytes. Therefore, 216 = 65,536 symbols, enough to represent practically all of the alphabets of the world.) The LoadLibrary name may be written in any language or in many languages simultaneously — in Russian-French-Chinese, for example. This seems tempting, but doesn't it decrease performance? It certainly does, and substantially. There's a price to be paid for Unicode! ASCII encoding suffices in most cases. Why waste precious processor clock ticks? To save performance, size was disregarded, and separate functions were created for Unicode and ASCII characters. The former received the W suffix (Wide); the latter received A (ASCII). This subtlety is hidden from programmers: Which function to call — W or A — is decided by the compiler. However, when you work with the debugger, you should specify the function name — it cannot determine the suffix independently. The stumbling block is that certain functions, such as ShowWindows, have no suffixes; their library names are the same as the canonical one. How do we know?

The simplest way is to look up the import table of the file being analyzed, and find your function there. For example, in our case:

> DUMPBIN /IMPORTS fixupload.exe > filename
> type filename
19D HeapDestroy
1C2 LoadLibraryA
CA GetCommandLineA
174 GetVersion
7D ExitProcess
29E TerminateProcess
...

From this fragment, you can see that LoadLibrary has the A suffix. The Exit-Process and TerminateProcess functions have no because they don't work with strings.

The other way is to look in the SDK. You won't find library names in it, but the Quick Info subsections give brief information on Unicode support (if such support is implemented). If Unicode is supported, the W or A suffix is indicated; if not, there are no suffixes. Shall we check this?

Here's Quick Info on LoadLibrary:

QuickInfo
Windows NT: Requires version 3.1 or later.
Windows: Requires Windows 95 or later.
Windows CE: Requires version 1.0 or later.
Header: Declared in winbase.h.
Import Library: Use kernel32.lib.
Unicode: Implemented as Unicode and ANSI versions on Windows NT.

We now understand the situation for Windows NT, but what about the one for the more common Windows 95/98? A glance at the KERNEL32.DLL export table shows there is such a function. However, looking more closely, we see something surprising: Its entry point coincides with the entry points of ten other functions!

ordinal hint RVA name
556 1B3 00039031 LoadLibraryW

The third column in the DUMPBIN report is the RVA address — the virtual address of the beginning of the function minus the file-loading base address. A simple search shows that it occurs more than once. Using the srcln program-filter to obtain the list of functions, we get the following:

21: 118 1 00039031 AddAtomW
116: 217 60 00039031 DeleteFileW
119: 220 63 00039031 DisconnectNamedPipe
178: 279 9E 00039031 FindAtomW
204: 305 B8 00039031 FreeEnvironmentStringsW
260: 361 F0 00039031 GetDriveTypeW
297: 398 115 00039031 GetModuleHandleW
341: 442 141 00039031 GetStartupInfoW
377: 478 165 00039031 GetVersionExW
384: 485 16C 00039031 GlobalAddAtomW
389: 490 171 00039031 GlobalFindAtomW
413: 514 189 00039031 HeapLock
417: 518 18D 00039031 HeapUnlock
440: 541 1A4 00039031 IsProcessorFeaturePresent
455: 556 1B3 00039031 LoadLibraryW
508: 611 1E8 00039031 OutputDebugStringW
547: 648 20F 00039031 RemoveDirectoryW
590: 691 23A 00039031 SetComputerNameW
592: 693 23C 00039031 SetConsoleCP
597: 698 241 00039031 SetConsoleOutputCP
601: 702 245 00039031 SetConsoleTitleW
605: 706 249 00039031 SetCurrentDirectoryW
645: 746 271 00039031 SetThreadLocale
678: 779 292 00039031 TryEnterCriticalSection

What a surprise: All Unicode functions live under the same roof. Since it's hard to believe that LoadLibraryW and, say, DeleteFileW are identical, we have to assume that we are dealing with a "stub", which only returns an error. Therefore, the LoadLibraryW function isn't implemented in Windows 9x.

However, let's get back to the subject at hand. Let's open the debugger, set a breakpoint on LoadLibraryA, then quit the debugger and wait for it to pop up. Fortunately, we won't have to wait long.

KERNEL32!LoadLibraryA
001B:77E98023 push ebp
001B:77E98024 mov ebp, esp
001B:77E98026 push ebx
001B:77E98027 push esi
001B:77E98028 push edi
001B:77E98029 push 77E98054
001B:77E9802E push dword ptr [ebp+08]

Let's issue the P RET command to exit LoadLibraryA (we really don't need to analyze it), and return to the easily recognizable main function.

001B:0040100B call [KERNEL32!LoadLibraryA]
001B:00401011 mov [ebp-08], eax
001B:00401014 cmp dword ptr [ebp-08], 00
001B:00401018 jz 00401051
001B:0040101A push 00405040
001B:0040101F call [KERNEL32!LoadLibraryA]
001B:00401025 mov [ebp-08], eax
001B:00401028 cmp dword ptr [ebp-08], 00

Note the value of the EAX register — the function has returned the load address to it (on my computer, 0x10000000). Continuing to trace (using the key), wait for the second execution of LoadLibraryA. This time, the load address has changed. (On my computer, it now equals 0x0530000.)

We are getting closer to the demo function call. (In the debugger, it looks like PUSH 00000777\ CALL [EBP-04]. The EBP-04 tells us nothing, but the 0x777 argument definitely reminds us of something in Listing 4.) Don't forget to move your finger from the key to the key to enter the function.

001B:00531000 55 push ebp
001B:00531001 8BEC mov ebp, esp
001B:00531003 8B4508 mov eax, [ebp+08]
001B:00531006 A330505300 mov [00535030], eax
001B:0053100B 5D pop ebp
001B:0053100C C3 ret

That's it! The system loader corrected the address according to the base address of loading the DLL itself. This is how it should work. However, there's one problem — neither that location, nor the sequence A3 30 50 53 00, is in the original DLL, which we can easily see via a context search. How can we find this instruction in the original DLL? Perhaps we'd like to replace it with NOPs.

Let's look a little bit higher — at instructions that don't contain relocatable elements: PUSH EBP/MOV EBP, ESP/MOV EAX, [EBP+08]. Why not look for the sequence 55 8B EC xxx A3? In this case, it'll work but, if the relocatable elements were densely packed with "normal" ones, we wouldn't find it. The short sequence would produce many false hits.

A more reliable way to find the contents of relocatable elements is to subtract the difference between the actual and recommended load address from them: 0x535030 (the address modified by the loader) - (0x530000 (the base loading address) - 0x10000000 (the recommended loading address)) - 0x10005030. Taking into account the reverse sequence of bytes, the machine code of the MOV [10005030], EAX instruction should look like this: A3 30 50 00 10. If we search for it using HIEW, miracle of miracles, there it is!

0 comments:

Post a Comment