*** 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 Win2k SP3
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 00 push 0 ; flag to indicate Decrypt if 0
; if flag is 1 then encrypt
005E0401 8B 4D 0C mov ecx, [ebp+key_address]
005E0404 51 push ecx
005E0405 8B 55 08 mov edx, [ebp+block_no]
005E0408 52 push edx
005E0409 E8 76 01 00 00 call Decrypt_Encrypt
005E040E 83 C4 0C add 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 :
0044E421 A1 88 9A 45 00 mov eax, ds:block_count
0044E426 83 C0 01 add eax, 1 ; increment decrypted memory block
0044E429 A3 88 9A 45 00 mov ds:block_count, eax ; in process counter
0044E470 mov edx, ds:block_count ; compare number if decrypted blocks
0044E476 3B 15 70 66 45 00 cmp edx, ds:max_number_of_decrypted_block ; with maximum no. allowed
0044E47C 0F 8E FA 00 00 00 jle 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 "server".
0044D697 LOOP: ; Start looping here to force
0044D697 8B 8D 2C FA FF FF mov ecx, [ebp+mem_block] ; decrypt the whole code section
0044D69D 3B 0D 84 9A 45 00 cmp ecx, ds:text_section_size
0044D6A3 0F 8D C7 00 00 00 jge continue_1 ;change to "jmp eip", infinite loop when finished
0044D6A9 8B 95 9C FA FF FF mov edx, [ebp+trap_flag_not_set_flag]
0044D6AF 81 E2 FF 00 00 00 and edx, 0FFh ;armadillo set trap flag at the beginning of
0044D6B5 85 D2 test edx, edx ;its debug loop to check if it is being traced
0044D6B7 0F 84 A9 00 00 00 jz continue_ok ; and it is checking for the flag now!
0044D6BD 6A 00 push 0 ; instruct to decrypt code
0044D6BF 8B B5 2C FA FF FF mov esi, [ebp+mem_block]
0044D6C5 C1 E6 04 shl esi, 4 ;
0044D6C8 8B 85 2C FA FF FF mov eax, [ebp+mem_block]
; Decrypt key Generation
0044D73F 83 E7 0F and edi, 0Fh ; codes removed but u can refer to IDA
0044D742 03 F7 add esi, edi ; disassembly
0044D744 8B 15 74 9A 45 00 mov edx, ds:key_address_table
0044D74A 8D 04 B2 lea eax, [edx+esi*4]
0044D74D 50 push eax
0044D74E 8B 8D 2C FA FF FF mov ecx, [ebp+mem_block]
0044D754 51 push ecx
0044D755 E8 86 0B 00 00 call Decrypt_codes ; push crypt_flag
0044D755 ; push key_add
0044D755 ; push memaddress
0044D75A 83 C4 0C add esp, 0Ch ;YOU SHOULD LAND HERE!!!!
0044D75D 25 FF 00 00 00 and eax, 0FFh
0044D762 85 C0 test eax, eax ; here patch to increment the mem block
0044D762 ; value to force decrypting the next block
0044D764 74 0A jz short bad_jump ; then jump to LOOP0067B0E8
Now 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]
Then 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.
2. Rebuild Import data
Open 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!
Our 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)
bpx GetProcAddress (Once break, disable this breakpoint)
F12 twice and we should be here now
10006622 39 3D E8 C7 01 10 cmp dll_handle_table, edi ; check if pointer is 0 because edi = 0 here
10006628 B8 E8 C7 01 10 mov eax, offset dll_handle_table ; here is the Table listing
1000662D 74 0C jz short no_special_IAT ; special DLL and their handles
1000662F ; the APIs in these DLL will be redirected
1000662F 3B 48 08 cmp ecx, [eax+8] ; is dll handle same as handle listed
10006632 74 1B jz short special_dll
10006634 83 C0 0C add eax, 0Ch ; next dll on the table
10006637 39 38 cmp [eax], edi
10006639 75 F4 jnz short loop
1000663B no_special_IAT: ; Get API address normally, no redirection!!!
1000663B push dword ptr [ebp+0Ch] ; lpProcName
1000663E FF 75 08 push [ebp+dll_handle] ; int
10006641 E8 41 00 00 00 call GetProcAddr
10006646 59 pop ecx WE SHOULD LAND HERE!!!!
10006647 59 pop ecx
1000664F ; ---------------------------------------------------------------------------
1000664F 8B 40 04 mov eax, [eax+4] ; get Special IAT table
10006652 3B C7 cmp eax, edi ; if eax = 0
10006654 74 E5 jz short no_special_IAT
10006656 39 78 08 cmp [eax+8], edi
10006659 8B F0 mov esi, eax ; esi points to start of special API
1000665B 74 DE jz short no_special_IAT ; name table… these APIs will be redirected
1000665D 66 3B DF cmp bx, di
10006660 74 06 jz short loc_10006668
10006662 66 3B 5E 04 cmp bx, [esi+4]
10006666 EB 0E jmp short loc_10006676
10006668 FF 36 push dword ptr [esi] ; The API name in special API table
1000666A FF 75 0C push dword ptr [ebp+0Ch] ; our API name
1000666D E8 8E 2B 01 00 call StringCompare
10006672 59 pop ecx
10006673 59 pop ecx
10006674 85 C0 test eax, eax ; if API name is found on the table
10006676 ; jump to Redirect_API_now
10006676 74 0A jz short Redirect_API_now
10006678 83 C6 10 add esi, 10h ; if not, update pointer on table to next API
1000667B 39 7E 08 cmp [esi+8], edi ; Is there anymore API on the table?
1000667E 75 DD jnz short API_name_check_loop ; if Yes then loop back to check this API
10006680 EB B9 jmp short no_special_IAT ; else go ahead and find API address normally
10006682 ; ---------------------------------------------------------------------------
10006682 8B 46 08 mov eax, [esi+8] ; eax = redirected API address
10006685 EB C1 jmp short end_routine00A4A496 load_next_dl_import:
The dll_handle_Table looks like this :
1001C7E8 18 C8 01 10 dd offset strKernel32_dll ; "kernel32.dll"
1001C7EC A8 C4 01 10 dd offset kernel32_API_table
1001C7F0 00 00 00 00 kernel32_handle dd 0
1001C7F4 0C C8 01 10 dd offset strUser32_dll ; "user32.dll"
1001C7F8 C8 C7 01 10 dd offset User32_API_table
1001C7FC 00 00 00 00 User32_handle dd 0
The kernel32_API_table looks like this :
1001C4A8 1C CB 01 10 dd offset strExitprocess ; "ExitProcess"
1001C4AC 00 00 00 00 dd 0
1001C4B0 77 6C 00 10 dd offset Fake_ExitProcess ; if string compare is same, this
1001C4B4 00 00 00 00 dd ExitProcess_Address ; Fake_ExitProcess routine is used!!!
1001C4B8 08 CB 01 10 dd offset strTerminateprocess ; "TerminateProcess"
1001C4BC 00 00 00 00 dd 0
1001C4C0 CA 6C 00 10 dd offset Fake_TerminateProcess
1001C4C4 00 00 00 00 dd TerminateProcess_Address
1001C4C8 FC CA 01 10 dd offset strExitthread ; "ExitThread"
1001C4CC 00 00 00 00 dd 0
1001C4D0 00 6D 00 10 dd offset Fake_ExitThread
1001C4D4 00 00 00 00 dd ExitThread_Address
The User32_API_table looks similar of course!
At 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 FF push [ebp+API_name]
10010DF3 FF B5 94 FD FF FF push [ebp+dll_handle]
10010DF9 E8 FA 57 FF FF call Get_API_address
10010DFE 89 85 78 FD FF FF mov [ebp+API_add], eax WE SHOULD LAND HERE!!!!
10010E04 83 BD 78 FD FF FF 00 cmp [ebp+API_add], 0
10010E0B 75 38 jnz short API_add_found
10010E45 API_add_found :
10010E45 8B 85 80 FD FF FF mov eax, [ebp+IAT_add]
10010E4B 8B 8D 78 FD FF FF mov ecx, [ebp+API_add]
10010E51 89 08 mov [eax], ecx HERE is where first thunk is updated!!!
10010E53 8B 85 80 FD FF FF mov eax, [ebp+IAT_add]
10010E59 83 C0 04 add eax, 4
10010E5C 89 85 80 FD FF FF mov [ebp+IAT_add], eax
My 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 :
Study 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 and list 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 kernel32.dll...etc
Moreover 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.
So 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
Yep, 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.
Of 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!
Note : 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!!!
3. New int3 tricks
Run 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 routine.
0044DAAC 8B 85 38 FA FF FF mov eax, [ebp+lpDebugEvent]
0044DAB2 81 78 0C 03 00 00 80 cmp dword ptr [eax+0Ch], 80000003h ; ç INT 3 exception code
0044DAB9 0F 85 96 03 00 00 jnz loc_44DE55
0044DABF 33 C9 xor ecx, ecx
0044DAC1 8A 0D 6E 9A 45 00 mov cl, ds:int3_feature_installed
Here 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 FF lea edx, [ebp+Context]
005DFDDB 52 push edx
005DFDDC A1 58 BA 5E 00 mov eax, ds:table_2
005DFDE1 03 85 20 F7 FF FF add eax, [ebp+relative_location] ; location in table_1 where eip is found
005DFDE7 8A 08 mov cl, [eax]
005DFDE9 51 push ecx
005DFDEA E8 24 12 00 00 call Get_Jump_Type ; determine which kind of Jump it is
005DFDEF 83 C4 08 add esp, 8
005DFDF2 25 FF 00 00 00 and eax, 0FFh
005DFDF7 85 C0 test eax, eax
005DFDF9 74 1C jz short it_is_NOT_a_jump ;if al = 1 then it is a jump
005DFDFB 8B 95 20 F7 FF FF mov edx, [ebp+relative_location] ; if al = 0 then it is not a jump
005DFE01 A1 48 BA 5E 00 mov eax, ds:Table_4
005DFE06 8B 8D E0 F7 FF FF mov ecx, [ebp+Context.Eip]; add eip with corresponding value in Table_3
005DFE0C 03 0C 90 add ecx, [eax+edx*4] ; relative location = location of eip
005DFE0F 89 8D E0 F7 FF FF mov [ebp+Context.Eip], ecx ; in Table 1
005DFE15 EB 1E jmp short Update_eip
005DFE17 ; ---------------------------------------------------------------------------
005DFE17 8B 15 5C BA 5E 00 mov edx, ds:Table_3
005DFE1D 03 95 20 F7 FF FF add edx, [ebp+relative_location] ;set edx to corresponding value in Table_4
005DFE23 33 C0 xor eax, eax
005DFE25 8A 02 mov al, [edx] ; get distance eip to be moved by
005DFE27 8B 8D E0 F7 FF FF mov ecx, [ebp+Context.Eip]
005DFE2D 03 C8 add ecx, eax
005DFE2F 89 8D E0 F7 FF FF mov [ebp+Context.Eip], ecx ; update eip
005DFE35 8D 95 28 F7 FF FF lea edx, [ebp+Context]
005DFE3B 52 push edx ; lpContext
005DFE3C 8B 85 F4 F9 FF FF mov eax, [ebp+hThread]
005DFE42 50 push eax ; hThread
005DFE43 FF 15 80 80 5E 00 call ds:SetThreadContext ; SetThreadContext:
Basically, 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.
005E1046 jump type 9:
005E1046 B0 01 mov al, 1 ;al always 1 => always jump no matter what
005E1048 E9 AF 01 00 00 jmp end ;==>> JMP
005E104D jump type E:
005E104D 8B 55 0C mov edx, [ebp+context]
005E1050 8B 82 C0 00 00 00 mov eax, [edx+0C0h] ; get eflags register
005E1056 83 E0 41 and eax, 41h ; check Zero and Carry flag
005E1059 F7 D8 neg eax ; eax is negative if either flag set
005E105B 1B C0 sbb eax, eax ; eax = -1 if either flag is set
; eax = 0 if neither flag is set
005E105D 40 inc eax ; al = 1 when neither flag set
005E105E E9 99 01 00 00 jmp loc_5E11FC ; ==> jump when CF=0 and ZF=0 => JA
005E108C jump type 4: ; CODE XREF: Get_Jump_Type+2Cj
005E108C 8B 55 0C mov edx, [ebp+context]
005E108F 8B 82 C0 00 00 00 mov eax, [edx+0C0h] ; get eflags register
005E1095 25 80 00 00 00 and eax, 80h ; check Sign Flag
005E109A F7 D8 neg eax
005E109C 1B C0 sbb eax, eax
005E109E 40 inc eax ; eax = 0 is S flag set, else eax = 1
005E109F 8B 4D 0C mov ecx, [ebp+context]
005E10A2 8B 91 C0 00 00 00 mov edx, [ecx+0C0h] ; get eflags
005E10A8 81 E2 00 08 00 00 and edx, 800h ; check Overflow flag
005E10AE F7 DA neg edx
005E10B0 1B D2 sbb edx, edx
005E10B2 42 inc edx ; edx = 0 if O flag set, else edx = 1
005E10B3 33 C9 xor ecx, ecx
005E10B5 3B C2 cmp eax, edx ; compare eax with edx
005E10B7 0F 95 C1 setnz cl ; al = 1 if not equal
005E10BA 8A C1 mov al, cl
005E10BC E9 3B 01 00 00 jmp end ; jump if SF != OF ==>> JL
Yep, 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 your reference.
4. 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...
00438E03 68 C4 0E 5C 00 push offset strDellatsnisyad ; "DELLATSNISYAD"
00438E08 8D 4D E4 lea ecx, [ebp-1Ch]
00438E0B 89 46 6C mov [esi+6Ch], eax
00438E0E E8 F4 42 10 00 call ??4CString@@QAEABV0@PBD@Z ; CString::operator=(char const *)
00438E13 8D 4D E4 lea ecx, [ebp-1Ch]
00438E16 E8 91 46 10 00 call ?MakeReverse@CString@@QAEXXZ ; The name says it all :>
00438E1B 8B 7D E4 mov edi, [ebp-1Ch]
00438E1E B8 FF 00 00 00 mov eax, 0FFh
00438E23 50 push eax ; nSize
00438E24 50 push eax
00438E25 8D 4D E8 lea ecx, [ebp-18h]
00438E28 E8 84 45 10 00 call ?GetBuffer@CString@@QAEPADH@Z ; CString::GetBuffer(int)
00438E2D 50 push eax ; lpBuffer
00438E2E 57 push edi ; lpName
00438E2F FF 15 8C 4C 5D 00 call ds:GetEnvironmentVariableA ; GetEnvironmentVariableA:
00438E35 85 C0 test eax, eax ; check how many days installed :)?
00438E37 6A FF push 0FFFFFFFFh
00438E39 8D 4D E8 lea ecx, [ebp-18h]
00438E3C 75 36 jnz short GOOD_JUMP ; if variable not found, BEEP n exit
00438E3E E8 BD 45 10 00 call ?ReleaseBuffer@CString@@QAEXH@Z ; CString::ReleaseBuffer(int)
00438E43 6A 01 push 1 ; uType
00438E45 FF 15 64 50 5D 00 call ds:MessageBeep ; MessageBeep:
00438E4B 53 push ebx ;
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
438E3C 75 DAYSINSTALLED
4720EE 75 DAYSINSTALLED
4E6B7F 75 ALTUSERNAME
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!
5. The end
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.
For 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", "NukeNow", "CheckNetLicense", "ArmAccess" and "RunUserProgram". Promising enough :>???
This tutorial would not have been possible without the help of all people on Fravia Message Board. So greetz fly out to all my friends on Fravia board and on mirc.
Special greetz to Woody and March^drn (get well soon :)
Last Edited : 7 Nov 2002