PDA

View Full Version : ASProtect frustration


TBone
June 13th, 2008, 17:23
So I'm trying to get back into the swing of this whole "reversing" business. I've been messing around for while just downloading dozens of trial versions of random software packages from your friendly local megalithic shareware site(s) and trying to see how many of them I can..."liberate".

I'm having a heck of a hard time with this one, though. PEID (using Neil's UserDB) claims that it's packed with:

ASProtect 1.2x - 1.3x [Registered] -> Alexey Solodovnikov [Overlay]

RDG says that it's ASProtect 2.xx. I'm no expert, but to me it looks like 2.xx. Then again, I'm not really sure how to tell. Here's my problem:

First of all, the usual ASProtect approach (count the number of times it breaks on an exception before running) doesn't work at all. This program runs immediately without throwing any exceptions. And yes, I have ollydbg configured not to ignore any exceptions - not even memory access violations in kernel32. It literally does not throw any exceptions before displaying the programs main window. The "MO" is definitely ASProtect, though.

I've back-traced through the code to what I believe is the OEP. It doesn't look like there are any stolen bytes. That is, it looks like fairly standard prologue:

Code:
0065ABD4 0C6D6200 dd <censored>.00626D0C
0065ABD8 20B06200 dd <censored>.0062B020
0065ABDC F0AF6200 dd <censored>.0062AFF0
0065ABE0 00 db 00
0065ABE1 00 db 00
0065ABE2 00 db 00
0065ABE3 00 db 00
0065ABE4 88A06500 dd <censored>.0065A088
0065ABE8 . 55 push ebp ; <--- OEP?
0065ABE9 . 8BEC mov ebp, esp
0065ABEB . B9 05000000 mov ecx, 5
0065ABF0 > 6A 00 push 0
0065ABF2 . 6A 00 push 0
0065ABF4 . 49 dec ecx
0065ABF5 .^ 75 F9 jnz short <censored>.0065ABF0


At this point, the call stack is empty and the main thread's stack frame looks like it would return to a section of dynamically allocated memory created by the packer code. This is the stack from the ground up (er...top down?):

Code:
0012FF68 00B3CC60 ; <--- ESP
0012FF6C 00400000 ASCII "MZP"
0012FF70 30E992D6
0012FF74 027FCF3C
0012FF78 0012FF98
0012FF7C 00F3DE99 RETURN to 00F3DE99 from 00F3DCDC ; <--- EBP
0012FF80 0012FFE0
0012FF84 00F3E41E
0012FF88 0012FF98
0012FF8C 00F20000
0012FF90 00EE0000
0012FF94 00F3E3BC
0012FF98 0078D419 RETURN to <censored>.0078D419 from <censored>.0078D419 ; imports and reloc. section
0012FF9C 00000000
0012FFA0 0078D82D <censored>.0078D82D ; ditto
0012FFA4 00000000
0012FFA8 00000000
0012FFAC 0012FFF0
0012FFB0 0012FFC4
0012FFB4 7FFDE000
0012FFB8 7C8285EC ntdll.KiFastSystemCallRet
0012FFBC 0012FFB0
0012FFC0 00000000
0012FFC4 77E6F23B RETURN to kernel32.77E6F23B
0012FFC8 00000000
0012FFCC 00000000
0012FFD0 7FFDE000
0012FFD4 00000000
0012FFD8 0012FFC8
0012FFDC A6C24CE4
0012FFE0 FFFFFFFF End of SEH chain
0012FFE4 77E61A60 SE handler
0012FFE8 77E6F248 kernel32.77E6F248
0012FFEC 00000000
0012FFF0 00000000
0012FFF4 00000000
0012FFF8 00401000 <censored>.<ModuleEntryPoint>
0012FFFC 00000000


If there are stolen bytes, I can't think of any way to find them short of manually tracing through the entire packer code, an exercise that would surely cost me my precious remaining sanity

When I look at the IAT, it's clear that large portions of it have been deliberately smashed. For example:

Code:
00666730 BC 69 38 77 79 0D 39 77
00666738 05 A0 6C B0 B8 27 39 77
00666740 9A 45 39 77 02 FF 39 77

The address at 00666738 isn't a redirection into packer code. It's just a bad pointer entirely; that memory isn't even mapped to anything. The addresses surrounding it all are function entry points in user32.dll. If you look at the thunk table, there's a corresponding "hole" where ASProtect has ripped code:

Code:
00408230 $- FF25 3C676600 jmp near [dword ds:66673C] ; user32.RedrawWindow
00408236 8BC0 mov eax, eax
00408238 $ E8 C37DDB00 call 011C0000 ; Should be 0666738...
0040823D B8 db B8 ; Maybe the tail end of the original address?
0040823E 8BC0 mov eax, eax
00408240 $- FF25 34676600 jmp near [dword ds:666734] ; user32.RegisterWindowMessageA
00408246 . 8BC0 mov eax, eax
00408248 .- FF25 30676600 jmp near [dword ds:666730] ; user32.RegisterHotKey


I can't figure out how to straighten these out. I tried setting memory breakpoints on the addresses in question so that I could watch them every time the packer modified them. This doesn't appear to be a case of the packer smashing the imports "after the fact", but rather the original imports are never constructed in the first place.

I tried to follow one of these calls into the API dlls, and I thought that I had identified one of them as ultimately being a call to kernel32.GetCurrentProcessId. I patched the IAT and thunk table so that it called GetCurrentProcessId directly instead of calling into the ASProtect code. The call was successful, but the program crashed before returning. Apparently I didn't identify the call correctly.

I've read several ASProtect 2.xx tutorials (mostly the stuff from ARTeam) but I've gotta tell you - all this business about reversing and analyzing virtual.dll is going way over my head. At this point, I just really don't know what to do. Maybe I should work on something simpler, but frankly I don't seem to be getting any better at this. The easier stuff is all too easy, but all of the "real" protections seem to be way too hard. There seems to be very little middle ground out there, and I just don't know how to proceed.

I've been on a sort of hiatus from this hobby for a while because I was getting frustrated with the entire thing. I thought if I took some time off and came back with a fresh perspective that I might be able to make some progress again. But it doesn't seem like anything's changed. I'm still just banging my head against the same old walls. And I think it's really bothering me because -- at the risk of sounding arrogant -- I've honestly never had trouble learning anything before. Sure, some things have been harder to learn than others, and I've felt the mental strain of grappling with complex subjects before. But I've never said to myself "I'm not sure I can learn this". Until now .

deroko
June 13th, 2008, 21:05
It seems like oep of an Delphi app if I'm not mistaken.

The other thing is AIP, luckily when jmp dword ptr[] is emulated, like in this Delphi, app then there is no much work to be done to fix it, set break on LoadLibraryA and GetProcAddress and run that call, soon you will have name of the API. Pain begins with msvc code or anything that uses call dword ptr[] where ASPR tries to emulate opcodes afte call dword ptr[] which forces you to decompile vm for oep(if used) and vm for AIP, which are similar but not the same

GetCurrentProcessId is used as anti-dump in AIP and oep vm protection to make memory block process specific

TBone
June 17th, 2008, 12:33
Thanks for the tip. I set a breakpoint on one of the redirected calls and then set one on LoadLibraryA and GetProcAddress once it broke there. The call eventually winds up at LoadLibraryA, but it never calls GetProcAddress. I'm guessing that ASProtect emulates GetProcAddress to make things harder?

Forutnately, when it breaks on LoadLibraryA, EDI just happens to contain a pointer to the desired import function name (GetModuleHandleA, in this case). I restarted the program with the thunk and IAT patched so that it would call GetModuleHandleA instead of the ASProtect code. The program seemed to run normally.

Although this works, fixing up every busted API call like this doesn't seem possible for anything more complicated that a crackme. Even if I can find all of the API calls, it would be very difficult to make the program execute each and every one of them so that I can fix them this way. There must be some programmatic way that I can fix them while the IAT is being built.

I went back to the unpacking routine and found where it was building the IAT. By comparing run traces from when it built a good IAT entry vs. when it redirects the API (which leaves gibberish for the IAT entry), I believe I've found the branch point where ASProtect decides whether to gank the API or not:

Code:
00F491EC 75 5E jnz short 00F4924C ; jumps if this IAT entry is going to be redirected
00F491EE EB 01 jmp short 00F491F1
00F491F0 C7 ??? ; Unknown command
00F491F1 66:8B02 mov ax, [word ds:edx]
00F491F4 66:8945 FA mov [word ss:ebp-6], ax
00F491F8 83C2 02 add edx, 2
00F491FB 8955 FC mov [dword ss:ebp-4], edx
00F491FE 66:817D FA FF00 cmp [word ss:ebp-6], 0FF
00F49204 76 0B jbe short 00F49211
00F49206 0FB745 FA movzx eax, [word ss:ebp-6]
00F4920A E8 3593FDFF call 00F22544
00F4920F 8BF8 mov edi, eax
00F49211 0FB775 FA movzx esi, [word ss:ebp-6]
00F49215 8BCE mov ecx, esi
00F49217 8B55 FC mov edx, [dword ss:ebp-4]
00F4921A 8BC7 mov eax, edi
00F4921C E8 6FC8FDFF call 00F25A90
00F49221 6A 0A push 0A
00F49223 8D4B 12 lea ecx, [dword ds:ebx+12]
00F49226 8BD6 mov edx, esi
00F49228 8BC7 mov eax, edi
00F4922A E8 558BFEFF call 00F31D84 ; EDI should point to function name after this call
00F4922F 897D FC mov [dword ss:ebp-4], edi
00F49232 8B45 FC mov eax, [dword ss:ebp-4]
00F49235 50 push eax
00F49236 8B45 10 mov eax, [dword ss:ebp+10]
00F49239 50 push eax
00F4923A 53 push ebx
00F4923B E8 7CFDFFFF call 00F48FBC ; GetProcAddress happens in this func
00F49240 8B55 0C mov edx, [dword ss:ebp+C]
00F49243 8B12 mov edx, [dword ds:edx]
00F49245 8902 mov [dword ds:edx], eax ; records addr in IAT
00F49247 E9 74010000 jmp 00F493C0


From previous experience with earlier ASProtect versions, I thought this might be a "magic jump" that I could patch to make it build a clean IAT. Unfortunately, NOPing this jump just makes the program crash with "The procedure entry point <random gibberish> could not be located in the dynamic link library <dll name>" when it encounters the first redirected entry. EDI points to a buffer on the stack. After the call at 00F4922A, this buffer should contain the import function name. This is used to supply the argument for a call to GetProcAddress that occurs in the function called at 00F4923B. If I NOP the jump on a "bad run" (i.e. when it would normally have taken the jump because of API redirection), the buffer just contains seemingly random bits after the call at 00F4922A.

I think it might decrypting the function names from an encrypted copy of the original IDT, but it's hard to tell. The code is very obfuscated, and I can't figure out how to extract the function name out of the code whenever ASProtect decides to redirect an API. I'm sure I could find a cave somewhere to stick some fix-up code if I could ever figure out what's going wrong. Does any of this look familiar to anyone?

deroko
June 17th, 2008, 13:52
I've attached my notes when I was wriitng aspr 2.3 unpacker, here are some informations, could be usefull. Basically, when you break at code which is responsible for handling import in EBX you will have all redirections. I don't say that it's easy to extract them, as it requires a lots of writing, but here is part of my code which does that (enumerats all of them and calls them):

Code:

void __declspec(naked) __stdcall DecryptAipOpcode_internal(DWORD *bla0){
__asm{
push ebp
mov ebp, esp
and ecx, ecx
jz short loc_9B4A29
push esi
push edi
mov esi, eax
mov edi, [ebp+8]
test ecx, 1
jz short loc_9B4A00
dec ecx
mov al, [esi+ecx]
xor al, [edx+ecx]
mov [edi+ecx], al
and ecx, ecx
jz short loc_9B4A27

loc_9B4A00:
shr ecx, 1
test ecx, 1
jz short loc_9B4A17
dec ecx
mov ax, [esi+ecx*2]
xor ax, [edx+ecx*2]
mov [edi+ecx*2], ax

loc_9B4A17:
shr ecx, 1

loc_9B4A19:
dec ecx
jl short loc_9B4A27
mov eax, [esi+ecx*4]
xor eax, [edx+ecx*4]
mov [edi+ecx*4], eax
jmp short loc_9B4A19

loc_9B4A27:
pop edi
pop esi

loc_9B4A29:
pop ebp
retn 4
}
}
/*
in ecx address of constant buffer used for decryption
in edx size of opcode
in eax address of opcode
push 10h
*/

void __declspec(naked) __stdcall DecryptAIPOpcode(DWORD *bla){
__asm{
push ebp
mov ebp, esp
push ecx
push ebx
push esi
push edi
mov [ebp-4], ecx
mov edi, edx
mov esi, eax
mov ebx, [ebp+8]
cmp ebx, edi
jnb short loc_9B4B86

loc_9B4B71:
sub edi, ebx
push esi
mov ecx, ebx
mov edx, [ebp-4]
mov eax, esi
call DecryptAipOpcode_internal
add esi, ebx
cmp ebx, edi
jb short loc_9B4B71

loc_9B4B86:
push esi
mov ecx, edi
mov edx, [ebp-4]
mov eax, esi
call DecryptAipOpcode_internal
pop edi
pop esi
pop ebx
pop ecx
pop ebp
retn 4
}
}

void DecryptEmuOpcode(){
__asm{
push 10h
mov ecx, offset ConstantBuffer
mov edx, AipSizeOfOpcode
mov eax, offset EmuOpcode
call DecryptAIPOpcode
}
}

enum CallAipProcIndex{
CallAipProc_GetRedirectionVA = 0,
CallAipProc_JmpOrCall = 1,
CallAipProc_IsEmulationNeeded = 2,
CallAipProc_IsAip = 9
};

enum CallEmuProcIndex{
CallEmuProc_GetOpcodeType = 1,
CallEmuProc_GetDataType2 = 2,
CallEmuProc_GetDataType3 = 3,
CallEmuProc_GetJccType = 4,
CallEmuProc_GetDstRegister = 5,
CallEmuProc_GetSrcRegister = 6,
CallEmuProc_GetDstData = 7,
CallEmuProc_GetSrcData = 8,
CallEmuProc_GetCmpType = 9
};


DWORD CallAipProc(DWORD ProcIndex, BYTE *AipOpcode){
DWORD return_data;
__asm{
mov eax, Aip_DataMemory
lea eax, [eax+40h] ;go to index position
mov ecx, ProcIndex
movzx eax, [eax+ecx] ;grab index from Aip_DataMemory
lea eax, [eax+eax*2] ;grab index into procedures at [ebx+68h]
mov ecx, Aip_DataMemory
mov ecx, [eax*4+ecx+68h] ;grab procedure that we have to call
sub ecx, Aip_AsprBase
add ecx, Aip_Base
mov eax, AipOpcode
call ecx
mov return_data, eax
}

return return_data;
}


This all inline asm is used to decrypt Aip opcodes and to call certain internal procedures to extract data from them, as AIP opcodes are changing constantly, so those offsets are required to extract needed data.

Now comes part responsible for decoding AIP opcode:

Code:

Aip_DataMemory = (BYTE *)GlobalAlloc(GPTR, 0x1000);
ReadProcessMemory(phandle, (PVOID)ebx, Aip_DataMemory, 0x1000, 0);

AipTotalNumberOfRedirections = *(DWORD *)(Aip_DataMemory + 0x18);
AipSizeOfOpcode = *(DWORD *)(Aip_DataMemory + 0xE4);
AipData24 = *(DWORD *)(Aip_DataMemory + 0x24);
AipDataE0 = *(DWORD *)(Aip_DataMemory + 0xE0);
AipAsprOpcodes = *(DWORD *)(Aip_DataMemory + 0x54);
AipData4A = Aip_DataMemory[0x4A];
AipData4B = Aip_DataMemory[0x4B];
EmuOpcodeBase = *(DWORD *)(Aip_DataMemory + 0x58);

EmuData4A = Aip_DataMemory[0x4A];
EmuData4B = Aip_DataMemory[0x4B];
EmuData4C = Aip_DataMemory[0x4C];
EmuData4D = Aip_DataMemory[0x4D];
EmuData4F = Aip_DataMemory[0x4F];
EmuData50 = Aip_DataMemory[0x50];
EmuData51 = Aip_DataMemory[0x51];
EmuData52 = Aip_DataMemory[0x52];

..........

BYTE IsNormal = CallAipProc(CallAipProc_IsAip, AipOpcodes);
DWORD ref = CallAipProc(CallAipProc_GetRedirectionVA, AipOpcodes);
DWORD emu = CallAipProc(CallAipProc_IsEmulationNeeded, AipOpcodes);
BYTE j = CallAipProc(CallAipProc_JmpOrCall, AipOpcodes);
ref += AipData24;
ref += AipDataE0;
emu += AipDataE0;

....

AipOpcodes+=AipSizeOfOpcode;


Code is reall mess AipOpcodes are actually AipOpcodes from aspr copied to my dumper

I doubt this code will help, as it's a mess for me now, didn't touch it for more then a year and half, but maybe it can help you with this attached .txt.

TBone
June 17th, 2008, 15:20
Yikes. I think I'm in way over my head .

I did some more reading. Apparently this flavor of ASProtect hashes the calling address to map it to the API that should be called. If I had looked more closely, I would have seen that all of the munged thunks were converted to the same thing: "call 011C0000". But the code at 011C0000 is just...all kinds of insane. Polymorphic whatzits and obfuscated spaghetti code without end. With a LOT of effort, I can trace my way through it on a case-by-case basis and figure out what the original API call was, but I don't understand what the code is actually doing at all.

Maybe I'm just missing it, but there doesn't seem to much in the way of tutorials, etc. that give any insight on this particular aspect. I know there are scripts out there that can analyze the calls and figure out what API function is being called, but running someone else's script doesn't teach me anything. I can't find any information on how the scripts were created in the first place, and looking at the code in the scripts makes as little sense to me as looking at the ASPRotect code itself.

How the heck do you people ever figure this stuff out? The size and complexity of these things is just completely beyond me. I'm starting to feel like you have be Alan frakin' Turing to be able to decipher this stuff. I'm smart, but I'm not that caliber of smart.

deroko
June 17th, 2008, 21:11
http://arteam.accessroot.com/tutorials.html?fid=206 here is detailed tutorial on how I did asprotect vm analyze, it also included part how import protection is done. Could be more useful then previously posted code and notes.

I've never used scripts for unpacking, so I can't tell how those really work I don't even understand them when reading simple script to get to the oep of upx. sti, sto etc... sti remind me of set I flag

Well Asprotect analyze is not that hard imho, it took me 1 day to analyze how poly oep works, then 1 day to figure AIP and after that 2-3 days to write unpacker, because restoring opcodes, restoring AIP by hand... uhhh... I would go crazy I used softice to fastly break at critical points to observe data, and IDA to write detailed comments what is what. Of course, I dumped virtual.dll with same imagebase as it would be in my target so I could fastly setup hardware breaks and observe data in a few seconds.