Tuesday, December 12 2006 @ 09:51 PM CET Contributed by: ColdT Views: 8189
*** Armadillo – MANUALLY skinning
the mutant ***
***** ***** by crUsAdEr ***** ****
This tutorial aims to discuss about Armadillo 2.61 protection and how to
MANUALLY remove the armadillo protection layer! Hopefully this will demonstrate
some manual unpacking techniques that have been forgotten as crackers get more
and more dependent on tools.
TOOLS used :
Soft Ice on Win2kSP3
Targets : Get Right 5.0 beta 1
Armadillo 2.61 just released with a few new features that make it slightly
more interesting to reverse. The fact that Armadillo debugs its own protected
program make it harder for us, crackers to debug the target but the good side
is that Armadillo code is not obfuscated or encrypted in anyway so we can
disassemble the protection layer and study it in IDA.
i)All code snippets in this tutorial are taken from IDA
disassembly, beside the IAT redirection part, code snippets in other parts of
this tutorial can be found at the same address in IDA if you can obtain the
same version of “getright.exe” file.
ii)Throughout this essay, I used variable names like “d
ebp+someName” to make it easier for readers to follow, when you are in sice,
you have to type out the actual value, for example “d enp+FFFFFAE0”.
iii)Armadillo protected programs starts 2 process, the
protecting layer debugs the protected target so I shall refer to the debugger
as “server” and the debugee as “client”.
iv)Also note that IAT redirection on WIN9x is different
from winNT/2k/XP so this essay only discuss IAT redirection on win2k though you
can find the redirection routine on win9x in a similar way!
(Finally, please READ those threads in Fravia board
about Armadillo protection and also make SURE you have a solid understanding of
PE format as it is essential to rebuild a working PE image!)
1. Defeating copy-mem II
OK, I hope you have already known what copy-mem II is and what it does.
Standard routine, to find OEP do "bpx
Once sice break, check it that sice is in Get Right context, if not then press
F5 till sice breaks in Get Right context. Then press F12 and trace with F10 for
a few instructions till you see a "call edi", stop there and note
down the value of edi. That is OEP. Remove all breakpoint and "bpx
WriteProcessMemory". Press F5, once sice breaks press F12 twice you will
be at the usual decrypting routine of Armadillo that you have seen in previous
version of Armadillo.
005E03FF 6A 00push 0; flag to indicate Decrypt if 0
; if flag is 1 then encrypt
005E0401 8B 4D 0Cmov ecx, [ebp+key_address]
005E0404 51push ecx
005E0405 8B 55 08mov edx, [ebp+block_no]
005E0408 52push edx
005E0409 E8 76 01 00 00call Decrypt_Encrypt
005E040E 83 C4 0Cadd esp, 0Ch; YOU SHOULD LAND HERE!!!!
looping around these instructions to force it to decrypt all code section and
you will get garbage because now each memory block of 1000h bytes has a diff
decrypt key. So we will have to find the decrypt key generation routine.
Trace a bit more down you will see this :
0044E429 A3 88 9A 45 00mov ds:block_count, eax; in process counter
0044E470mov edx, ds:block_count; compare number if decrypted blocks
0044E476 3B 15 70 66 45 00cmp edx, ds:max_number_of_decrypted_block ; with maximum no. allowed
0044E47C 0F 8E FA 00 0000jle ok
you can guess, Armadillo store number of memory block already decrypted to make
sure that not all blocks are decrypted in memory at anytime. If you do a “bpm
max_number_of_decrypted_block” you will see that Armadillo calculates this
value from text code section size divide by 1000h (number of memory blocks)
then divide by 2. Here we now have 2 ways, patch the jump to make sure it
always jump or we can change the value at ds:max_number_of_decrypted_block to
1000h, something big!!!!
Once done, press F12 we will be in the middle of Debug loop of the Armadillo
0044D697LOOP:; Start looping here to force
0044D697 8B 8D 2C FA FF FFmov ecx, [ebp+mem_block]; decrypt the whole code section
0044D75A 83 C4 0Cadd esp, 0Ch;YOU SHOULD LAND HERE!!!!
0044D75D 25 FF 00 0000and eax, 0FFh
0044D762 85 C0test eax, eax; here patch to increment the mem block
0044D762; value to force decrypting the next block
0044D764 74 0Ajzshort bad_jump; then jump to LOOP0067B0E8
we can see how Armadillo generates keys for each memory block and passed it to
the Decrypt_codes routine. Also, we can understand why Armadillo exit silently
if we single step on the "server-debugger" code. Here, we shall make
the big loop containing the key generation routine to force armadillo decrypt
the whole code section. At address 44D75D do
inc dword ptr [ebp+mem_block]
we edit the value at "ebp+mem_block" to 0 (tell Armadillo to start
decrypting from first memory block), and set the end point at 44D6A3 by
changing the instruction there to "jge 46D6A3". Here Armadillo is
checking if the mem block is above the code section, we simply put an infinite
loop there. Let the program runs on, wait a while and then fire up LordPE to
make a full image dump. If LordPE reports "Can't grab memory" then
you probably did something wrong and Armadillo hasn’t fully decrypted the code
section. Once this is done, we can proceed to rebuild IAT.
Rebuild Import data
the packed file with LordPE and study the section header, you will notice import
data should be in section .idata! Why? Because the name already say it... and
you can also double check by open the file in Hex Workshop and go to that
section offset, you will see scattering words like "kernel32.dll" and
"user32.dll" etc... Import data should be there!
task is to find the API redirection routine, study it and based on this routine
to rebuild our Import.
find the IAT redirection routine, use "addr" command of sice to get
into the context of the protected (client) program then
bpm IAT_address W(Once break,
disable this breakpoint)
break, disable this breakpoint)
F12 twice and we should be here now
10006622 39 3D E8 C7 01 10cmp dll_handle_table, edi ; check if pointer is 0 because edi = 0 here
10006628 B8 E8 C7 01 10mov eax, offset dll_handle_table; here is the Table listing
1000662D 74 0Cjzshort no_special_IAT; special DLL and their handles
1000662F; the APIs in these DLL will be redirected
1000662F 3B 48 08cmp ecx, [eax+8]; is dll handle same as handle listed
10006632 74 1Bjzshort special_dll
10006634 83 C0 0Cadd eax, 0Ch; next dll on the table
10006637 39 38cmp [eax], edi
10006639 75 F4jnz short loop
1000663Bno_special_IAT:; Get API address normally, no redirection!!!
1001C4C0 CA 6C 00 10dd offset Fake_TerminateProcess
1001C4C4 00 000000dd TerminateProcess_Address
1001C4C8 FC CA 01 10dd offset strExitthread; "ExitThread"
1001C4CC 00 000000dd 0
1001C4D0 00 6D 00 10dd offset Fake_ExitThread
1001C4D4 00 000000dd ExitThread_Address
User32_API_table looks similar of course!
this point, if you are lazy, you can simply patch the program to jump over the
dll_handle_table scanning part, then our IAT will never be redirected and we
can simply run Imprec or Revirgin on the running process to re-build IAT... BUT
we are doing everything manually aren’t we :)? So let's trace on. If you press
F12 one more time, you will be in the middle Import loading loop,
10010DED FF B5 70 FD FF FFpush [ebp+API_name]
10010DF3 FF B5 94 FD FF FFpush [ebp+dll_handle]
10010DF9 E8 FA 57 FF FFcall Get_API_address
10010DFE 89 85 78 FD FF FFmov [ebp+API_add], eax WE SHOULD LAND HERE!!!!
10010E04 83 BD 78 FD FF FF 00cmp [ebp+API_add], 0
10010E0B 75 38jnz short API_add_found
10010E45 8B 85 80 FD FF FFmov eax, [ebp+IAT_add]
10010E4B 8B 8D 78 FD FF FFmov ecx, [ebp+API_add]
10010E51 89 08mov [eax], ecxHERE is where first thunk is updated!!!
10010E53 8B 85 80 FD FF FFmov eax, [ebp+IAT_add]
10010E59 83 C0 04add eax, 4
10010E5C 89 85 80 FD FF FFmov [ebp+IAT_add], eax
comments already say it all, put a breakpoint on the Call Get_API_Address then
trace around the loop to get the hang of it. Then you will see [ebp+API_name]
store the pointer to Import ASCIIs and follow this
pointer, you will see a long list of API and DLL names… WOW is this our Import
ASCII :)? Make a dump of the whole Table and you will see the dump looking like
this table we'll see that it start with a DLL name, then the RVA of first thunk
corresponding with that DLL, then the number of Import from that DLL andlist of all Imports ASCII from that DLL. For
example, WIMM.dll first thunk start at 001D519C, (remember reversed order of
bytes) and there is one import form this DLL which is PlaySoundA.
Kernel32.dll first thunk starts at 1D4B74 and there are 9Fh Imports from
if you take a look at the first thunk, by "d eax" when you are at
instruction 10010E51 you will notice that the First Thunk is untouched by
Armadillo! Which means that we know the original location of ASCII entries of
API, however take a look at those location and we see only zeros :(...so Import
ASCII are deleted but hey at least we know where each entry of Import ASCII
should be stored.
now what do we have to do? We need to rebuild Import Table, in other words copy
Import ASCII to location where they should be. Time for some patching on the
fly! Set a break point "bpm 10010DF3 x" then restart the program.
Once it break,
mov ecx, [ebp+IAT_add]; get the RVA
of Import ASCII
add ecx, 400002h; calculate VA of
Import ASCII by adding image base and 2 is size of HINT
I hope you understand what the little inline patch codes do. The next task is
stopping Armadillo from destroying our First Thunk, simply NOP the instruction
at 10010E51. Finally, press F12 and let Armadillo rebuilt IAT for you :>...
once sice break again dump the whole .idata segment.
course you will notice that the Import Directory is missing so it is your task
to rebuild this directory. I shall leave as an exercise for readers. If you are
wondering about Import by ordinal because we can’t copy the Import ASCII like
import by name case, don’t worry! Import by ordinal is handled separately by
Armadillo (if you scroll up code window in sice for one or two pages, you will
see that “Call Get_API_address” is called there as
well to handle ordinal cases. However, as I have said, our First Thunk is
intact so Ordinals are already stored in First Thunk and as long as we NOP the
instruction at 10010E51, our Import by ordinals are preserved!
: Another more generic approach would be coding a small utility that reads the
Import ASCII dump and insert Import ASCII into our exe dump file and rebuild
the Import Directory. This would be usable on all current Armadillo target!!!
New int3 tricks
the exe now and BOOM, you will get a big message from Windows saying int3
occurs and is not handled. that is the newest trick of Armadillo to prevent
dumping. Time to get into sice and IDA to trace again, just look through the
Debug loop of the "server" and you will see the int3 handling
0044DAAC 8B 85 38 FA FF FFmov eax, [ebp+lpDebugEvent]
0044DAC1 8A 0D 6E 9A 45 00mov cl, ds:int3_feature_installed
begins our quest to kill int3. Start tracing from this point, you will see that
the "server" reads a block of data from the "client" memory
space and divide this into 4 tables and then decrypt this 4 tables into 4
separate virtually allocated memory space. Then the "server" use
GetThreadContext to check for location of eip of the "client". This
eip is one byte after the location int3 exception occurs! Trace on and we'll
see that armadillo performs binary search on Table_1 to find the position of
eip. Once eip location in Table_1 found, we are here :
005DFDD5 8D 95 28 F7 FF FFlea edx, [ebp+Context]
005DFDDB 52push edx
005DFDDC A1 58 BA 5E 00mov eax, ds:table_2
005DFDE1 03 85 20 F7 FF FFadd eax, [ebp+relative_location] ; location in table_1 where eip is found
005DFDE7 8A 08mov cl, [eax]
005DFDE9 51push ecx
005DFDEA E8 24 12 00 00call Get_Jump_Type; determine which kind of Jump it is
005DFDEF 83 C4 08add esp, 8
005DFDF2 25 FF 00 0000and eax, 0FFh
005DFDF7 85 C0test eax, eax
005DFDF9 74 1Cjzshort it_is_NOT_a_jump;if al = 1 then it is a jump
005DFDFB 8B 95 20 F7 FF FFmov edx, [ebp+relative_location]; if al = 0 then it is not a jump
005DFE01 A1 48 BA 5E 00mov eax, ds:Table_4
005DFE06 8B 8D E0 F7 FF FFmov ecx, [ebp+Context.Eip]; add eip with corresponding value in Table_3
005DFE43 FF 15 80 80 5E 00call ds:SetThreadContext; SetThreadContext:
Armadillo has replaced some of the jumps (conditional & unconditional) in
the original exe with int3 and generate these 4 tables storing information about
position, distances of all these jumps. All done during packing time. As you
can see, when and int3 occurs in the "client" process, the
"server" will check for eip where int3 occurs, look it up in Table_1,
once found, the "server" will check corresponding value in table_2
which is what kind of Jump it is at this eip. Then "the server" will
check eflags register on “client” context
(inside the call Get_Jump_type" routine) to see if the "client"
should jump or not. If jump then the "client" eip will be moved by a
corresponding value in Table_4 (jump distance), if not then the
"client" eip will be moved by a corresponding value in table_3
(length of jump instruction). Take a look into the Get_Jump_Type routine.
005E1046jump type 9:
005E1046 B0 01mov al, 1;al always 1 => always jump no matter what
005E10B2 42inc edx; edx = 0 if O flag set, else edx = 1
005E10B3 33 C9xor ecx, ecx
005E10B5 3B C2cmp eax, edx; compare eax with edx
005E10B7 0F 95 C1setnzcl; al = 1 if not equal
005E10BA 8A C1mov al, cl
005E10BC E9 3B 01 00 00jmp end; jump if SF != OF==>> JL
all together there are 11h type of jump. Finally, it is out task to fix those
int3 so I coded a small utility to scan through Table_1, check each virtual
address entry in table_1 with our dump, if the opcode is "CC" then it
is out int3, check the corresponding value in Table_2 to determine what kind of
jump it is (JNZ or JZ or JMP etc). Then it check table_3 to see how long the
jump instruction should be and assign write the correct jump opcodes into our
exe dump. Finally, it check the corresponding value in Table_4 which is the
distance how far the jump is and update the value in the exe again. Thus, run
the protected program, dump the for tables into 4 separate files, (make sure
that Table_1 size is same as table_4 size and is 4 times the table_2 and 3
size. Why?). I have attached the masm source code of this little program for
The Final Touch
After fixing int3, the dump still refused to
run, exiting with some stupid beeping noise. This got me baffled for a while...
tracing doesn’t seem to get me anywhere, double check my int3 patching routine,
double check IAT... everything seems fine. I was at my wits end when I decide,
hey the Beep sound doesn’t come out when I run the original exe, voila, bpx
MessageBeep then press F12 we land here...
SO our task is to patch the conditional jump
at 438E3C to JMP... play around with the program, like try resuming a download
you will received a another beep :
Here is summary of all the patches
Now Get Right runs like a baby :>...
though I find the downloading really slow. Personally I don’t recommend you to
use this download manager :>, so once you crack it uninstall it immediately!
That is it, phew. What a long tutorial! I
hope you find it useful. I tried my best to explain things but I realise I
can’t explain everything in details or else the tutorial will be too long for
anyone to bother read it. So if there is any part that you find unclear, drop
me a message on fravia board and I will make the appropriate changes.
the enthusiasts: If you notice that the IAT redirection routine is in some
unknown memory space, you can try scrolling up to the beginning of this memory
section and you will find a PE header! Check out the image size in the header,
put a bpm at the end of the image (to make sure the whole image is loaded in
memory) then re-run Get Right, once sice breaks, check that image is fully
loaded in memory… dump it and rename it as “arma.dll” you will get a virgin dll
that export functions like "LoadProgramInfo",
"ArmAccess" and "RunUserProgram".
Promising enough :>???