################################################################################
# Disrespect Copyrights in Practice #
# by Nomenumbra/[0x00SEC] #
################################################################################
(code and other files associated with this article (and my other articles featured in hackthiszine #4 are located at
http://www.hackbloc.org/zine/vivalarevolution.rar - pass is 'anarchism')
Disclaimer:
Some official shit that's needed:
This document is to be used for legal and educational purposes only. The author,
nor anyone publishing this article can and/or will/might/shall not be held
responsible in any way for any damage (potentially) done by anything described
in this article. If this informotion makes you want to rape, murder, lie, cheat,
pillage, extort, be hyporitical and capatalisitic I strongly advise you to cut
your veins and die ...
Foreword:
In this globalist world there are only two values left, how much one can consume
for the hightest possible price and how much one can produce for the least
possible pay, all to serve the great green god, commonly referred to as 'the
dollar', and it's imperialistic hegemonistic pions, commonly referred to as
'CEOs'. Their ways of extortion of third world countries and the social
'lowerclass' and abduction of free speech and thought in the first world have
taken gross forms in today's society.. And like this isn't enough, they have
been joined by whitehats to help 'secure' their software from people who break
their unrighteous copyrights. This article will give the reader a standard
overview of techniques used to protect applications and ways to bypass them..
The target applications (called "Acts" (Act I,Act II,etc)) come with this zine
(if everything goes ok :p )
Have phun!
Introduction:
Well people, reversing applications can range in difficulity level from
extremely easy to mindcrushing. Since this article is an introduction, I won't
discuss extremely advanced schemes but I will show you some nice reversing
tricks. Required knowledge to understand this article:
-)Basic understanding of 32-bit windows ASM
-)Basic understanding of the usage of Debuggers/Disassemblers
-)A brain
You can either try to crack each app first and read my tutorial afterwards or
just follow along, you choice. Each Act is given an "objective" so you know what
to look for and what you can learn there (all passwords are normal words,eg. no
Ae534RKLjl passwords but SOMEPASSWORD).
Act I:
Difficulity: [....]
Tools: OllyDbg
Objective: Find the password
Ok, imagine you just downloaded a nice game ("LameGame V 1.0") and you're ready
to enjoy playing it. You launch the bitch and THIS jumps up:
LameGame V1.0
(c) MegaCorp 2006-2099
Usage:
cp1 <password>
Ok, THAT sucks ass, now we'll have to supply a password as a command-line
argument... Well, it shouldn't be THAT difficult to crack...
Let's fire up OllyDbg and load our app ....
One of the first things I always do when reversing an app is checking what
strings are inside the body. Now, if we scroll down a bit we'll see the text
"LameGame V1.0" displayed. Now we take a look at the assembler in that area we
see a call to <JMP.&msvcrt.strcmp> where the result of a call to 0042A040 (this
result is argv[1]) gets compared to the "BULKMONEY". That was foolish, leaving
the password in plaintext in the executable....
Act II:
Difficulity: My granny could do this
Tools: OllyDbg
Objective: Find the password
MegaCorp recently released a new version of "LameGame" since V1.0 was could be
cracked by any no-brains monkey. The new version claims to be more secure than
the first, but is this true? We fire up OllyDbg again and we see that the string
"HMPCBMJTU" gets copied to the address 00443010.
Now we search for the "LameGame V1.1" string. This time argv[1] gets compared to
00443010, so argv[1] is compared to "HMPCBMJTU" or is it? Take a closer look and
you'll see that the result of strlen("HMPCBMJTU") gets stored at EAX, and
compared to DWORD PTR SS:[EBP-4] (which is obviously a counter), if it isn't
below (so we've reached the end of the string "HMPCBMJTU") we leave this
subroutine. Now notice the following:
DWORD PTR SS:[EBP-4] gets stored at EAX, then the offset of "HMPCBMJTU" is added
(we now have the address of the current character in EAX), the next interesting
thing is the decrease of that character's value (MOVZX EAX,BYTE PTR DS:[EAX]
then DEC AL). Then we load the counter in EAX and increase it and continue the
loop. So what happens is that every character gets decreased with 1, so the
password should be "GLOBALIST".... Pathetic company, they really don't know
their shit, now do they?.....
Act III:
Difficulity: Easy as pie....
Tools: OllyDbg
Objective: Find the password
Well, MegaCorp anounced they recently hired a new programmer to ensure the
cracking of their game would be made impossible by implementing a far more
sophisticated encryption algorithm [that'd be time....]. Well, we fire up Olly
again and see not much has changed, the subroutine structures have remained the
same. But when we take a closer look we can see the cryptoscheme HAS been
improved (still pathetic and breakable within 13 seconds but hey....)
Well, we don't want to go trough all the hassle of thinking :D so we'll just let
the debugger do the job...
See the POP EBP at 004013F8? well, we'll put a breakpoint there to freeze
execution once we get there (so we can see how the cryptostring is
decrypted).Now press F9 and GO! Watch the dump an Voila, we got it
004013CF |. 81C1 10304400 |ADD ECX,Cp1.00443010 ; ASCII
"EXTORTION"
Act IV:
Difficulity: Medium
Tools: OllyDbg
Objective: Find the password or find hash-collision
Instead of reducing the absurdly high price of "LameGame" MegaCorp gave up it's
production because all they care about is profit and not their customers. But
they just brought out a new product, a new firewall named "Infernal Barricade".
In order to install "Infernal Barricade" we need to bypass their newest
copyright scheme. Let's take them on with OllyDbg once again...
Hmm... no strcmp anymore? That means they have though of something else than
using a password. Let's take a closer look.
It seems that the program makes the final desicion as to whether your key was
correct or not here:
00401491 |> 807D FF 00 CMP BYTE PTR SS:[EBP-1],0
00401495 |. 74 26 JE SHORT Cp1.004014BD
00401497 |. C74424 04 3400>MOV DWORD PTR SS:[ESP+4],Cp1.00440034 ; ASCII
"Installing 'Infernal Barricade'..."
And these call/cmp constructions are probably used to analyze your key too:
0040146B |. E8 308C0200 CALL Cp1.0042A0A0
00401470 |. 837D 08 01 CMP DWORD PTR SS:[EBP+8],1
00401474 |. 7E 1B JLE SHORT Cp1.00401491
00401476 |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
00401479 |. 83C0 04 ADD EAX,4
0040147C |. 8B00 MOV EAX,DWORD PTR DS:[EAX]
0040147E |. 890424 MOV DWORD PTR SS:[ESP],EAX
00401481 |. E8 0AFFFFFF CALL Cp1.00401390
00401486 |. 3D 10030000 CMP EAX,310
0040148B |. 75 04 JNZ SHORT Cp1.00401491
0040148D |. C645 FF 01 MOV BYTE PTR SS:[EBP-1],1
after analyzing each call it turns out this one:
00401481 |. E8 0AFFFFFF CALL Cp1.00401390
is the most interesting (looks like the decryption-constructions we've seen
before). The function returns a value in EAX that gets compared to the static
value 0x310. If we examine the function we can see the argument passed (argv[1]
in this case) is manipulated into a hash value, let's test this thesis.
To fake a command-line go to Debug->Arguments and supply your argument.
Ok, time to put a breakpoint before the end of the subroutine (located at
004013F9) and F9! Now take a look at the EAX register's value (seen in the right
part of the screen), I used "FUCKYOU" as an argument, resolving to 0x21C ....
That means we must supply a commandline argument that will be resolved to 0x310.
We could do this in two ways, by looking for a collision in the algorithm or by
bruteforce. Let's rip the algorithm first.
Ok, to make things clear:
DWORD PTR SS:[EBP-8] is the counter (i)
DWORD PTR SS:[EBP+8] is the beginning of argv[1]
DWORD PTR SS:[EBP-C] is input[i] (DWORD PTR SS:[EBP-8]+DWORD PTR SS:[EBP-8])
004013A4 |> 8B45 08 /MOV EAX,DWORD PTR SS:[EBP+8] ; |
004013A7 |. 890424 |MOV DWORD PTR SS:[ESP],EAX ; |
004013AA |. E8 C1F30000 |CALL <JMP.&msvcrt.strlen> ; \strlen
004013AF |. 3945 F8 |CMP DWORD PTR SS:[EBP-8],EAX
004013B2 |. 73 45 |JNB SHORT Cp1.004013F9
004013B4 |. 8B45 08 |MOV EAX,DWORD PTR SS:[EBP+8]
004013B7 |. 0345 F8 |ADD EAX,DWORD PTR SS:[EBP-8]
004013BA |. 0FBE00 |MOVSX EAX,BYTE PTR DS:[EAX]
004013BD |. 8945 F4 |MOV DWORD PTR SS:[EBP-C],EAX
004013C0 |. C745 F0 000000>|MOV DWORD PTR SS:[EBP-10],0
004013C7 |. 8B45 08 |MOV EAX,DWORD PTR SS:[EBP+8]
004013CA |. 0345 F8 |ADD EAX,DWORD PTR SS:[EBP-8]
004013CD |. 8038 00 |CMP BYTE PTR DS:[EAX],0
004013D0 |. 74 0D |JE SHORT Cp1.004013DF
004013D2 |. 837D F8 00 |CMP DWORD PTR SS:[EBP-8],0 ;if i is 0 result is 0
004013D6 |. 74 07 |JE SHORT Cp1.004013DF
004013D8 |. C745 F0 010000>|MOV DWORD PTR SS:[EBP-10],1
004013DF |> 8B45 F0 |MOV EAX,DWORD PTR SS:[EBP-10];-> (input[i] && i)
004013E2 |. 3345 F8 |XOR EAX,DWORD PTR SS:[EBP-8];-> EAX XoR i
004013E5 |. 0345 F8 |ADD EAX,DWORD PTR SS:[EBP-8];-> (EAX XoR i) + i
004013E8 |. 8B55 F4 |MOV EDX,DWORD PTR SS:[EBP-C]
004013EB |. 31C2 |XOR EDX,EAX ;-> ((EAX XoR i)+i) ^ input[i])
004013ED |. 8D45 FC |LEA EAX,DWORD PTR SS:[EBP-4]
004013F0 |. 0110 |ADD DWORD PTR DS:[EAX],EDX
004013F2 |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]
004013F5 |. FF00 |INC DWORD PTR DS:[EAX]
004013F7 |.^EB AB \JMP SHORT Cp1.004013A4
"Hash" algorithm:
(input[i] XoR (((input[i] && i) XoR i) + i))
Well, writing a bruteforcer for this is peanuts but there must be an easier
way....through algorithmic collision. Let's see, the input "TEST" generates 319
as a value, now let's try "UEST" ... 320, how predictable and let's try "TFST"
-> 322. Now we're getting somewhere :D.
Ok, let's try filling up the bitch with A's.
"AAAAAAAAAA" resolves to 721 while 1 A more gives us 805, so we need to sit
somewhere in between.
"AAAAAAAAAZ" resolves to 716 ,"AAAAAAAABZ" to 719 and "AAAAAAAACZ" to 718, let
me predict, "AAAAAAAAEZ" wil resolve to 720.... <.<
Ok, we need 784... after some trying we find out "AAAAAAA{{Z" resolves to 784..
Let's try >:).. YES! It works... Our collisive hash managed to trick the
program into installing, without having having to know the 'real' password
(which was MILITARISM btw)....
Act V:
Difficulity: Medium
Tools: OllyDbg, Hexeditor
Objective: Find the password, defeat anti-debugging
MegaCorp got fed up with being cracked over and over so they consulted some
whitehat corporate lapdog to strengthen their apps and sell our scene out at the
same time... Rumor has it he implemented an anti-debugging trick in the newest
version of "Infernal Barricade". Let's fire up OllyDbg YET AGAIN! Ok, lets see
what they have been trying to do this time...
0040144F |. C600 00 MOV BYTE PTR DS:[EAX],0 ; ||
00401452 |. E8 E9F50000 CALL <JMP.&KERNEL32.IsDebuggerPresent> ;
||[IsDebuggerPresent
00401457 |. 85C0 TEST EAX,EAX ; ||
00401459 |. 74 18 JE SHORT Cp1.00401473 ; ||
0040145B |. C70424 0C00440>MOV DWORD PTR SS:[ESP],Cp1.0044000C ; ||ASCII
"Your attempt to debug this application is considered a crime by the U.S
governement, legal action will be taken against you...
"
00401462 |. E8 69F30000 CALL <JMP.&msvcrt.printf> ; |\printf
00401467 |. C70424 FFFFFFF>MOV DWORD PTR SS:[ESP],-1 ; |
0040146E |. E8 4DF30000 CALL <JMP.&msvcrt.exit> ; \exit
LOL! They use a standard win32 API called IsDebuggerPresent to check if the
application is being debugged.... hmmm,
004013C4 |. C74424 04 0000>MOV DWORD PTR SS:[ESP+4],Cp1.00440000 ; |ASCII
"LOIACU]QH"
seems to be the encrypted password, we don't want to spend a lot of time to rip
the algorithm and decrypt it by hand so let's debug it! As expected the
application terminates when we debug it this way. Let's take a closer look at
the anti-debug technique:
00401452 |. E8 E9F50000 CALL <JMP.&KERNEL32.IsDebuggerPresent> ;
||[IsDebuggerPresent
00401457 |. 85C0 TEST EAX,EAX ; ||
00401459 |. 74 18 JE SHORT Cp1.00401473 ; ||
This piece is interesting, it calls IsDebuggerPresent and sees if true is
returned in EAX, if so, it ends, if not it continues... hmm interesting
conditional jump, what if we'd make it an uncomditional jump, always jumping to
continue the application (JMP is 0xEB, keep that in mind).....
Fire up a hexeditor (or just do it in OllyDBG, i just want to let you play with
HexEditors as well :D ) and open the app in it. Now look for the following
sequence of bytes:
00401457 |. 85C0 TEST EAX,EAX ; ||
00401459 74 18 JE SHORT Cp1.00401473 ; ||
find: 85C07418
and replace the 74 with EB...
That was easy, we already broke their anti-debugging technique (fuckers).
Now all we gotta do is put a breakpoint on
00401470 . C600 00 MOV BYTE PTR DS:[EAX],0
so we can watch ECX being "IGNORANCE"... yet another application broken, hehe
There are many commercial copyright-protection schemes which would make life
difficult if we'd reverse only in the ways described, but there are other ways
too, by taking advantage over the fact that the target program runs in YOUR
environment, you control the OS! That means you can manipulate it from all
sides. One way is process hijacking by DLL injection, which i'll describe here:
Process Hijacking
Process hijacking involves executing you code in another process' context (not
as in exploiting it to make it execute shellcode). This can be achieved in two
ways, either directly by executing a part of you executables code in the remote
process, or by DLL injection. With the advent of Windows DEP (Data Execution
Prevention) this leaves us the latter. Injecting your DLL into another process
goes as follows:
Fetch the target process' PID (Process ID)
Open a handle to the target process
Fetch the address of LoadLibraryA dynamically
Allocate enough memory for an argument to LoadLibraryA
Do a VirtualProtectEx to set the code pages to PAGE_EXECUTE_READWRITE
write the name of the DLL to load ,into the memory (we obviously can't use
a local address)
restore the old permissions
Here follows a sourcecode example in c++:
BOOL WriteToMemroy(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer,
SIZE_T nSize)
{
DWORD dwOldProtect;
BOOL boolReturn = FALSE;
if(hProcess == NULL) // own process?
{
VirtualProtect(lpBaseAddress, nSize, PAGE_EXECUTE_READWRITE,
&dwOldProtect); // now Ex needed, only a VirtualProtect
boolReturn = ((memcpy(lpBaseAddress, lpBuffer, nSize))? 1 : 0); //memcpy
instead of WriteProcessMemory
VirtualProtect(lpBaseAddress, nSize, dwOldProtect, &dwOldProtect); //
set back
}
else
{
VirtualProtectEx(hProcess, lpBaseAddress, nSize, PAGE_EXECUTE_READWRITE,
&dwOldProtect); // Virtualprotectex to be able to read and write code
boolReturn = WriteProcessMemory(hProcess, lpBaseAddress,
(LPVOID)lpBuffer, nSize, 0); // Write to memory
VirtualProtectEx(hProcess, lpBaseAddress, nSize, dwOldProtect,
&dwOldProtect); //set back
}
VirtualFreeEx(hProcess, lpBaseAddress, nSize, MEM_RELEASE); // free memory
return boolReturn;
}
BOOL InjectDLL(char* ProcessName, char* strHookDLL)
{
printf("Initiating injection of '%s' into '%s'\n",strHookDLL,ProcessName);
DWORD dwPID = GetProcessID(ProcessName);
if(dwPID == 0)
{
printf("Couldn't retreive valid ProcessID for process
'%s'!\n",ProcessName);
return FALSE;
}
HANDLE hProcess;
HMODULE hKernel;
LPVOID RemoteStr, LoadLibraryAddr;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID); // open the
process if(hProcess == INVALID_HANDLE_VALUE) //couldn't open?
{
printf("Couldn't open process '%s' with ID %d!\n",ProcessName,dwPID);
return FALSE;
}
hKernel = LoadLibrary("kernel32.dll"); //load kernel32.dll
if(hKernel == NULL)// couldn't load?
{
printf("Couldn't load Kernel32.dll!\n");
CloseHandle(hProcess);
return FALSE;
}
LoadLibraryAddr = (LPVOID)GetProcAddress(hKernel, "LoadLibraryA");// fetch
address of LoadLibraryA
RemoteStr = (LPVOID)VirtualAllocEx(hProcess, NULL, strlen(strHookDLL),
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // allocate memory size of argument
if(WriteProcessBytes(hProcess, (LPVOID)RemoteStr, strHookDLL,
strlen(strHookDLL)) == FALSE) // write it to memory
{
printf("Couldn't write to process '%s' memory!\n",ProcessName);//
failed?
CloseHandle(hProcess);
return FALSE;
}
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)LoadLibraryAddr, (LPVOID)RemoteStr, 0, NULL);// remotely
load our DLL
if(hRemoteThread == INVALID_HANDLE_VALUE)// failure?
{
printf("Couldn't create remote thread within process
'%s'!\n",ProcessName);
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return FALSE;
}
CloseHandle(hProcess);
printf("'%s' successfully injected into process '%s' with ID
%d!\n",strHookDLL,ProcessName,dwPID);
return TRUE;
}
Well that wasn't THAT difficult, now was it? The next question that arises is
"What to inject?". Well you can do a lot once your DLL is loaded, ranging from
process termination to full-blown input/output manipulation. The template of
your DLL should look like this:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DisableThreadLibraryCalls((HMODULE)hModule); //don't get re-called
// do what you want once attached
return true;
}break;
case DLL_PROCESS_DETACH:
{
// bring back to old state
}break;
}
return true;
}
Imagine the following application:
int main(int argc, char *argv[])
{
system("PAUSE");
if (argc-1)
{
if (strcmp(argv[1],"XPLT") == 0)
MessageBoxA(0,"Accepted","Accepted",0);
}
return 0;
}
Ok, this simple app can be fooled by hijacking the main function it relies on,
strcmp. Strcmp is a string comparing function located in the Dll ntdll.dll. The
pause is used to ensure we get the time to inject our DLL into the victim app.
Ok, we'll hijack the function by using a detours trampoline. Detours patching,
as described in:
http://research.microsoft.com/~galenh/Publications/HuntUsenixNt99.pdf
goes as follows:
Here follows a small example in C++:
DWORD InlineHook(const char *Library, const char *FuncName, void *Function,
unsigned char *backup)
{
DWORD addr = (DWORD)GetProcAddress(GetModuleHandle(Library), FuncName);
// Fetch function's address
BYTE jmp[6] = {
0xe9, //jmp
0x00, 0x00, 0x00, 0x00, //address
0xc3 // retn
};
ReadProcessMemory(GetCurrentProcess(), (void*)addr, backup, 6, 0);
// Read 6 bytes from address of hooked function from rooted process into backup
DWORD calc = ((DWORD)Function - addr - 5); //((to)-(from)-5)
memcpy(&jmp[1], &calc, 4); //build trampoline
WriteProcessMemory(GetCurrentProcess(), (void*)addr, jmp, 6, 0);
// write the 6 bytes long trampoline to address of hooked function to
current process
return addr;
}
This function resolves the address of the function to be hooked, and builds a
trampoline as follows:
JMP <4 empty bytes for addres to jump to>
RETN
the address to jump to (the hook) is resolved like this:
((To)-(From)-5) == ((HookAddress)-(TargetAddress)-5)
the old address is backed up, to be able to unhook the function later (by
overwriting the trampoline with the original address).
Ok, now let's hijack our little app to make any password work:
int WINAPI strcmphook(const char* str1,const char* str2); // prototype
DWORD Faddr=0; // address
BYTE Fbackup[6]; // backup
DWORD InlineHook(const char *Library, const char *FuncName, void *Function,
unsigned char *backup)
{
DWORD addr = (DWORD)GetProcAddress(GetModuleHandle(Library), FuncName);
// Fetch function's address
BYTE jmp[6] = {
0xe9, //jmp
0x00, 0x00, 0x00, 0x00, //address
0xc3 // retn
};
ReadProcessMemory(GetCurrentProcess(), (void*)addr, backup, 6, 0);
// Read 6 bytes from address of hooked function from rooted process into backup
DWORD calc = ((DWORD)Function - addr - 5); //((to)-(from)-5)
memcpy(&jmp[1], &calc, 4); //build trampoline
WriteProcessMemory(GetCurrentProcess(), (void*)addr, jmp, 6, 0);
// write the 6 bytes long trampoline to address of hooked function to
current process
return addr;
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DisableThreadLibraryCalls((HMODULE)hModule); //keeps it from being
re-called
Faddr = InlineHook("ntdll.dll","strcmp",strcmphook,Fbackup); // strcmp
in ntdll.dll
return true;
}break;
case DLL_THREAD_ATTACH: break;
case DLL_THREAD_DETACH: break;
case DLL_PROCESS_DETACH:
{
WriteProcessMemory(GetCurrentProcess(), (void*)Faddr, Fbackup, 6, 0); //
restore address
}break;
}
return true;
}
int WINAPI strcmphook(const char* str1,const char* str2)
{
return 0; // always return 0, no matter what password was.
};
Once we inject this DLL into our victim app like this:
InjectDLL("Victim.exe","hijack.dll"), you will notice that it doesn't matter
what password you supplied as a commandline argument, you will always get the
"Accepted" messagebox. As you can see process Hijacking can get you many things.
You could subvert an application to elevate your privileges, create an extra
account, download & execute an app with the privileges under which the app runs,
you could even backdoor the app itself by letting it execute code to run the DLL
injector @ startup, thus effectively taking over the app.
Act VI:
Difficulity: Hard
Tools: OllyDbg,PEiD,DeYoda (found here: http://xtaz3k.free.fr/decryptors/Dy.ace)
Objective: Get the MessageBox with the password to popup (the password IS
encrypted and is not to be found in plaintext in the app,
you can also decrypt the password by hand since the 'encryption' is pathetic,
but that way you'll miss some valuable knowledge)
Ok, there is this new IDE, called BulkIDE, you really wanna get your hands on,
it is said to be quite nice, but the price tag is a 'little' high, $3000,
outrageous for such a simple IDE, so let's crack the bitch. You managed to lay
your hands on the main installer executable, but you seem to be missing the
installation CD, but hey, we should get this working without that stupid license
.exe :) It is rumored though that the programmers behind this IDE are fans of
"security through obscurity" meaning we can expect a lot of opaque predicates (a
function that evaluates to true or false and of which the outcome is known to
the programmer on forehand, sometimes used as useless code that seems important
or anti-debugging).
First of all we load up PeiD and check the app, result:
yoda's cryptor 1.2
This is probably your first encounter with a packer/crypter. Many software these
days (especially commercial software and malware) is packed/crypted to make
reversing a tiny whiny bit harder and to reduce executable size.
Yoda's cryptor is quite a nice compressor/packer/crypter for PE files, but it
can be undone in a wink, just fire up DeYoda, load the app and GO! Fire up PEiD
again:
Nothing found *
Nice, that's what we wanna see.
Now fire up OllyDBG and load the unpacked executable.
We won't start looking at all strings, cause they are too obvious to be real
passwords, they're just bogus shit to confuse the cracker.
The first thing we see is:
00401000 >/$ 68 0A204000 PUSH unpacked.0040200A; /FileName = "user32.dll"
00401005 |. E8 B5020000 CALL <JMP.&KERNEL32.LoadLibraryA>; LoadLibraryA
0040100A |. 68 15204000 PUSH unpacked.00402015;ProcNameOrOrdinal =
"BlockInput"
0040100F |. 50 PUSH EAX; |hModule
00401010 |. E8 92020000 CALL <JMP.&KERNEL32.GetProcAddress>;GetProcAddress
00401015 |. A3 24204000 MOV DWORD PTR DS:[402024],EAX
0040101A |. 6A 01 PUSH 1
0040101C |. FF15 24204000 CALL DWORD PTR DS:[402024]
Well, the following happens:
GetProcAddress(LoadLibrary("user32.dll"),"BlockInput") gets stored in DWORD PTR
DS:[402024]. BlockInput is a function to halt all keyboard and mouse input if
it's argument is true, and resume it if it is false. If we look a bit further,
at ox0040101A we see a call to BlockInput with a true parameter and at
0x00401048 we see it with a false parameter. So obviously the program attempts
to block any input during program execution to prevent debugging and reversing.
Well to get rid of this nuisance, we'll just nop those PUSH <true||false>
CALLDWORD PTR DS:[402024] structures out with right click -> binary -> fill with
NOP's. Then we have another IsDebuggerPresent call, just breakpoint the test
eax,eax after the call, set EAX to 0 and continue.
00401030 |> 50 PUSH EAX
00401031 |. BE EC114000 MOV ESI,unpacked.004011EC ; Entry
address
00401036 |. B9 08000000 MOV ECX,8
0040103B |. E8 1C010000 CALL unpacked.0040115C
Hmmm, what's this? Let's first take a look at unpacked.0040115C:
0040115C /$ 33D2 XOR EDX,EDX
0040115E |> 51 /PUSH ECX
0040115F |. AD |LODS DWORD PTR DS:[ESI]
00401160 |. E8 17000000 |CALL unpacked.0040117C
00401165 |. 03D0 |ADD EDX,EAX
00401167 |. 59 |POP ECX
00401168 |.^E2 F4 \LOOPD SHORT unpacked.0040115E
0040116A \. C3 RETN
Ok, let's put it all in an ordered way:
-)EDX is set to 0
-)ECX is saved
-)EAX is loaded from ESI
-)unpacked.0040117C is called
-)EAX (probably the result of unpacked.0040117C) is added to EDX
-)ECX is restored
-)This is looped
So this is an additive repeation of unpacked.0040117C. Let's check
unpacked.0040117C out:
0040117C /$ B9 20000000 MOV ECX,20
00401181 |> D1E8 /SHR EAX,1
00401183 |. 73 05 |JNB SHORT unpacked.0040118A
00401185 |. 35 2083B8ED |XOR EAX,EDB88320
0040118A |>^E2 F5 \LOOPD SHORT unpacked.00401181
0040118C \. C3 RETN
Some people (Vxers, reversers and comp. Sci. Students) will recognize this as a
Cyclic Redudancy Check and that's what it is. A Cyclic Redudancy Check is a type
of hash function used to produce a checksum, in order to detect errors in
transmission or storage. Hmm so it seems unpacked.0040115C does an additive CRC
over ECX bytes, to calculate the CRC checksum of the code area unpacked.004011EC
and the next 8 bytes. This is obviously to check if the cracker made any
modifications (breakpoints, nops,etc) to this code area. Now let's check what
this area is all about:
004011EC /$ 6A 00 PUSH 0
004011EE |. 68 0D124000 PUSH unpacked.0040120D ; ASCII "DAEMON"
004011F3 |. 64:67:A1 3000 MOV EAX,DWORD PTR FS:[30]
004011F8 |. 0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX+2]
004011FC |. 0AC0 OR AL,AL
004011FE |. 74 02 JE SHORT unpacked.00401202
00401200 |. EB 04 JMP SHORT unpacked.00401206
00401202 |> 33C0 XOR EAX,EAX
00401204 |. C9 LEAVE
00401205 |. C3 RETN
00401206 |> B8 01000000 MOV EAX,1
0040120B |. C9 LEAVE
0040120C \. C3 RETN
Hmm, more experienced crackers will recognize this as a common trick to detect
OllyDBG. To circumvent this we don't need to modify this section at all, we just
need the Olly-Invisible plugin. Now, back to where we were, 0x0040103B. It seems
the result of this check, along with the result of a call to 0x004011EC (the
ollyDBG detection function) is stored in EDX and then 0x00401057 is called. Now
we need to watch out since we are gonna be stuffed with Opaque predicates. All
shit is bogus until this piece of code:
00401076 |. 68 06204000 PUSH unpacked.00402006 ; /RootPathName = "E:\"
0040107B |. E8 2D020000 CALL <JMP.&KERNEL32.GetDriveTypeA>; \GetDriveTypeA
00401080 |. 83F8 05 CMP EAX,5
Here the DriveType of E:\ is determined (since this is a test program not all
drives are enumerated but E:\ is assumed as the CD-ROM drive, whatever since we
don't have the installation CD it doesn't matter :D) and then it is checked if
E:\ is a CD-ROM drive (5 being DRIVE_CDROM). The next important call is a call
to GetVolumeInformationA, that will retrieve the CD-Serial in unpacked.00402020.
As we can see here:
004010A6 |. 813D 20204000 >CMP DWORD PTR DS:[402020],DEADBEEF
the serial is expected to be 0xDEADBEEF. Since we don't have the CD, we'll nop
out the conditional jump right after the CMP (it's a JNZ jump, meaning the
serial was invalid and only nasty stuff can happen afterwards so...). Now
0xDEADBEEF is stored in EDX (or at least, we store it there >:p) and a call to
unpacked.004011A1 is made, which seems to be a decryption function based on this
piece of code:
004011C6 |. B9 03000000 MOV ECX,3
004011CB |. BE 92124000 MOV ESI,unpacked.00401292 ; ASCII
"es`"
004011D0 |. 8BFE MOV EDI,ESI
004011D2 |> AC /LODS BYTE PTR DS:[ESI]
004011D3 |. 34 32 |XOR AL,32
004011D5 |. AA |STOS BYTE PTR ES:[EDI]
004011D6 |.^E2 FA \LOOPD SHORT unpacked.004011D2
What we see here is interesting too:
004011A1 /$ E8 1F010000 CALL <JMP.&KERNEL32.GetTickCount> ;
[GetTickCount
004011A6 |. 8BD8 MOV EBX,EAX
004011A8 |. CC INT3
004011A9 |. E8 17010000 CALL <JMP.&KERNEL32.GetTickCount> ;
[GetTickCount
004011AE |. 2BC3 SUB EAX,EBX
004011B0 |. 3D 58270000 CMP EAX,2758
A call to GetTickCount (Function that retrieves the number of milliseconds that
have elapsed since the system was started) is made, then INT3 is called and
another call to GetTickCount is made, the results being substracted (EAX thus
holding the difference). The interesting thing is INT3, INT3 is a breakpoint,
thus halting the debugger and pausing the run of the app. You already feel it
coming eh? Because a normal run of the app with a correct CD in the CD-drive
would go fine (without CD the app would get lost in invalid,buggy and useless
Opaque predicates) and smooth (INT3 doesn't break the app when not being
debugged) the difference between the first and second GetTickCount would be
nihil, but when debugging you either need to react very very fast (I gave you
more time with 2758 milliseconds than most apps that use this trick) or just nop
the shit out (providing you don't spot any nasty CRC tricks on that code ). For
those that think "TO HELL, NOP THOSE CRCs OUT TOO! FUCK YEAH!", those CRCs could
actually be used as an arithmetic parameter to a string decryption function.
Well, to counter this, we would just fire up the debugger, run it check the CRC
of the non-modified piece of code, note it restart all shit, modify the code and
feed the good CRC to the decryption function, but that is another story. Then
this function is called:
0040118D /$ AC LODS BYTE PTR DS:[ESI]
0040118E |. 3D CC000000 CMP EAX,0CC
00401193 |. 75 06 JNZ SHORT unpacked.0040119B
00401195 |. B8 01000000 MOV EAX,1
0040119A |. C3 RETN
0040119B |> B8 00000000 MOV EAX,0
004011A0 \. C3 RETN
apperently a check if the breakpoint is left intact <.< A pathetic attempt,
since we'll just manipulate the register holding the result (EAX). Now we
continue and voila! We get the popup with the password: WAR.
Afterword:
Well, this was just the top of the iceberg, letting you taste the 'forbidden
fruit' of reverse engineering, a most enjoyable and profitible practice, usefull
for crackers,vxers and exploit developpers alike. There are many,many more ways
for a programmer to protect his program from being cracked. The programmer could
also make his program decrypt @ runtime (much like a virus) when the correct key
is provided, but a reverse-engineer could whipe out the key-checking procedure
with nop's (0x90) or turn the conditional jump after the key-checker into an
unconditional one. He could make the app run in ring-0 but then we could use
soft-ice to debug the app. The programmer could use rootkit techniques to hide
his app from userland and kernelland, but then we could use the same techniques
as rootkitdetectors.
As you can see, there are endless amounts of ways to protect a program ... but
even more to break it :D. I hope you enjoyed reading this article, I certainly
enjoyed writing it and remember kids, don't let copyrights on shit products stop
you, but give credit where credit is due!
Outro:
Greets and shouts go to HTS (zine staff) members, NullSec, ASO/PTP members, VX.netlux
members, .aware crew,RRLF, the guys over at Smash The Stack, reversing.be (hagger in special for being such a
fucking good reverser) and IRC dudes.
# milw0rm.com [2006-09-27]