首页
社区
课程
招聘
[转帖]Obsidium 1.2.5.0 - unpacking - tut
发表于: 2006-6-16 14:40 4318

[转帖]Obsidium 1.2.5.0 - unpacking - tut

2006-6-16 14:40
4318
Obsidium 1.2.5.0 - unpacking   

Sunday, June 11 2006 @ 02:27 PM CEST
Contributed by: haggar
Views: 117
Level : intermediate

------------------------------------------
Obsidium 1.2.5.0 - unpacking
------------------------------------------

I didn't unpack anything for a while due to my coledge obligations, but today I gave on examne and I'm in good mood. Obsidium is pretty good protector that comes from chinese author. I didn't notice any apps packed with it and I don't know why, since it has some pretty cool options. In this tutorial I will try 1.2.5.0 version which is older one, but it's good as starting point. I think that this unpackme doesn't have all protection options enabled since I know that obsidium has option to relocate whole image. That is probably only possible with Borland applications.

Stuff (target, scripts,etc...) here http://www.reversing.be/binaries/articles/20060611142302945.rar

1. Intro

Tools that we need are usual:

Windows XP; OllyDbg; ImpREC; LordPE; some hex editor; PEiD is not important but if you have good external database, at least it can detect correct version of packer.

Obsidium has some cool anti-debug trickcs:

- CheckRemoteDebuggerPresent; this API detects debugger on XP machines,
- UnhandledExceptionFilter; trick to crush application which runs under debugger,
- FindWindowA; it finds olly window class "OLLYDBG",
- IsDebuggerPresent; detects debugger,
- threads; obsidium runs one or two threads that uses previous tricks,
- stolen OEP code; it steal couple bytes from OEP,
- cool import protection.

Also, obsidum code is full of junk opcodes like short jumps that makes tracing very annoying. That is actually one thing that I realy hate. Obsidium doesn't use GetProcAddress to find imports, instead it have custom procedure that find imports. It also checks for breakpoints at API's and emulate them partialy, so we need to place bp's on end of API's.

Anti-debug tricks, CheckRemoteDebuggerPresent, FindWindowA and IsDebuggerPresent, can be bypassed simply by placing bp's at the end of those API's and then seting return value EAX=0.

UnhandledExceptionFilter is used to handle some exceptions that protector generate on purpose. Problem is that if we are in debugger, application expects that debugger will take control instead system debugger (Dr. Watson). We can force system to think that application is not running within debugger. This is UnhandledExceptionFilter function and below shows how to trick it:

PUSH 248 <------------------------- UnhandledExceptionFilter function starts here.
PUSH kernel32.7C8635E0
CALL kernel32.7C8024CB
MOV EAX,DWORD PTR DS:[7C8836CC]
MOV DWORD PTR SS:[EBP-1C],EAX
MOV EBX,DWORD PTR SS:[EBP+8]
MOV DWORD PTR SS:[EBP-178],EBX
MOV DWORD PTR SS:[EBP-148],4
XOR EDI,EDI
MOV DWORD PTR SS:[EBP-13C],EDI
MOV DWORD PTR SS:[EBP-16C],EDI
MOV EAX,DWORD PTR DS:[EBX]
TEST BYTE PTR DS:[EAX+4],10
JE SHORT kernel32.7C862BD4
PUSH DWORD PTR DS:[EAX]
PUSH -1
CALL DWORD PTR DS:[<&ntdll.NtTerminatePr>; ntdll.ZwTerminateProcess
MOV EAX,DWORD PTR DS:[EBX]
MOV ESI,C0000005
CMP DWORD PTR DS:[EAX],ESI
JNZ SHORT kernel32.7C862BF9
CMP DWORD PTR DS:[EAX+14],1
JNZ SHORT kernel32.7C862BF9
PUSH DWORD PTR DS:[EAX+18]
CALL kernel32.7C862874
CMP EAX,-1
JNZ SHORT kernel32.7C862BF9
OR EAX,EAX
JMP kernel32.7C863458
MOV DWORD PTR SS:[EBP-124],EDI
PUSH EDI
PUSH 4
LEA EAX,DWORD PTR SS:[EBP-124]
PUSH EAX
PUSH 7
CALL kernel32.GetCurrentProcess <------------- Here is trick! This API returns FFFFFFFF. Change EAX to 0.
PUSH EAX
CALL DWORD PTR DS:[<&ntdll.NtQueryInform>; ntdll.ZwQueryInformationProcess
TEST EAX,EAX
...
...

2. OEP and stolen code

While writing this text, I didn't find some genereic way to find OEP that would be suitable for writing script. Instead that, I traced and experimented with code, exceptions , breakpoints. I found where import jumps are allocated, conclusion is that when crackme is unpacked, it will try to use some import so I just placed memory bp on whole import block. Crackme is small so it is not problem to find starting point.

But let's go in other direction. I placed breakpoints on all checks to kill anti tricks. When new thread started, I took a look in log window. Last occured exception is interesting:

Log data
Address Message
00415022 Access violation when reading [00000000]
0041EFA5 Access violation when reading [FFFFFFFF]

77DD0000 Module C:WINDOWSsystem32advapi32.dll
00390295 Access violation when reading [FFFFFFFF]
7C85994E Breakpoint at kernel32.7C85994E
7C901230 INT3 command at ntdll.DbgBreakPoint
Access violation when executing [00000000]
7C862C10 Breakpoint at kernel32.7C862C10
003907E8 Illegal instruction
00390854 Integer division by zero
77D6F3DC Breakpoint at USER32.77D6F3DC
Access violation when executing [00000000]
7C862C10 Breakpoint at kernel32.7C862C10
00395F82 Integer division by zero
00395F84 Access violation when reading [00000000]
0039655D Access violation when reading [8003F41E]
0039654A Integer division by zero
0039654C Access violation when reading [00000000]
0039654F Access violation when reading [FFFFFFFF]
0039654D Access violation when reading [FFFFFFFF]
0039655D Access violation when reading [8003F418]
00396550 Illegal instruction
003965BC Integer division by zero
7C812E10 Breakpoint at kernel32.7C812E10
00396555 INT3 command at 00396555
00395041 Integer division by zero
00395041 Integer division by zero
0039520A Integer division by zero
00395041 Integer division by zero
0039520A Integer division by zero
0039520A Integer division by zero
00415DA1 Illegal instruction
00415F9B Integer division by zero <---------------- This one.
7C810856 New thread with ID 00000304 created
7C85994E Breakpoint at kernel32.7C85994E

Last exception is in main image (last section, protectors one) and it is "Integer division by zero". So I done it all again, only this time I unchecked "Intiger divison by 0" in olly options, so I could break on that exception. After couple ones, here is last one:

00415F9B F7F0 DIV EAX <------------------------- Exception.
00415F9D 55 PUSH EBP
00415F9E 8BEC MOV EBP,ESP
00415FA0 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
00415FA3 6A 00 PUSH 0
00415FA5 68 CC971025 PUSH 251097CC
00415FAA 6A 00 PUSH 0
00415FAC FF50 18 CALL DWORD PTR DS:[EAX+18]
00415FAF 5D POP EBP
00415FB0 C2 0800 RETN 8
00415FB3 55 PUSH EBP
00415FB4 8BEC MOV EBP,ESP
00415FB6 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
00415FB9 8B00 MOV EAX,DWORD PTR DS:[EAX]
00415FBB 3D 1D0000C0 CMP EAX,C000001D
00415FC0 74 0E JE SHORT UnPackMe.00415FD0
00415FC2 3D 940000C0 CMP EAX,C0000094
00415FC7 75 41 JNZ SHORT UnPackMe.0041600A
00415FC9 B9 AB92AD01 MOV ECX,1AD92AB
00415FCE EB 05 JMP SHORT UnPackMe.00415FD5
00415FD0 B9 4E90AD01 MOV ECX,1AD904E
00415FD5 E8 00000000 CALL UnPackMe.00415FDA
00415FDA 5A POP EDX
00415FDB 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
00415FDE 81EA 893CBA00 SUB EDX,0BA3C89
00415FE4 8D940A EFAB0CFF LEA EDX,DWORD PTR DS:[EDX+ECX+FF0CABEF]
00415FEB 8990 B8000000 MOV DWORD PTR DS:[EAX+B8],EDX
00415FF1 33C9 XOR ECX,ECX
00415FF3 8948 04 MOV DWORD PTR DS:[EAX+4],ECX
00415FF6 8948 08 MOV DWORD PTR DS:[EAX+8],ECX
00415FF9 8948 0C MOV DWORD PTR DS:[EAX+C],ECX
00415FFC 8948 10 MOV DWORD PTR DS:[EAX+10],ECX
00415FFF C740 18 55003333 MOV DWORD PTR DS:[EAX+18],33330055
00416006 33C0 XOR EAX,EAX
00416008 5D POP EBP
00416009 C3 RETN
0041600A 33C0 XOR EAX,EAX
0041600C 40 INC EAX
0041600D 5D POP EBP
0041600E C3 RETN
0041600F 33C0 XOR EAX,EAX
00416011 8D44C0 03 LEA EAX,DWORD PTR DS:[EAX+EAX*8+3]
00416015 010424 ADD DWORD PTR SS:[ESP],EAX
00416018 C3 RETN

By this time, crackme code section is already unpacked. I could place memory breakpoint on it and I would land on "false oep". First, this crackme has code section at 404000. Second, Obsidium ALWAYS JUMPS ON FALSE OEP. Even if it doesn't steal bytes, it jumps couple opcodes further (I've been little examning obsidium work).

If we check stack, we can see where is SEH handler that will handle exception:

0012FF94 0012FFE0 Pointer to next SEH record
0012FF98 00415FB3 SE handler

That is code below our exception, so we can place bp there 00415FB3. We came to RETN that throw us to ntdll.dll:

00416009 C3 RETN

That is Ok, let's enter in and then place memory breakpoint on last section to return to obsidium code.

004161EB E8 67000000 CALL UnPackMe.00416257 <------------- We returning here.

Tracing in we find some decryptor loop which ends here:

004162BF ^0F85 D4FFFFFF JNZ UnPackMe.00416299
004162C5 EB 05 JMP SHORT UnPackMe.004162CC
004162C7 BD 7C3BF3D8 MOV EBP,D8F33B7C
004162CC 61 POPAD
004162CD C3 RETN

Then we go here:

004161EB FF96 8C010000 CALL DWORD PTR DS:[ESI+18C]
004161F1 8B7E 04 MOV EDI,DWORD PTR DS:[ESI+4]
004161F4 FFB6 44010000 PUSH DWORD PTR DS:[ESI+144]
004161FA FF96 80000000 CALL DWORD PTR DS:[ESI+80]
00416200 25 FF030000 AND EAX,3FF
00416205 8DBC07 00010000 LEA EDI,DWORD PTR DS:[EDI+EAX+100]
0041620C 56 PUSH ESI
0041620D 57 PUSH EDI
0041620E 8DB5 C83CBA00 LEA ESI,DWORD PTR SS:[EBP+BA3CC8]
00416214 B9 D2010000 MOV ECX,1D2
00416219 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
0041621B 5F POP EDI
0041621C 5E POP ESI
0041621D 8D85 F63EBA00 LEA EAX,DWORD PTR SS:[EBP+BA3EF6]
00416223 68 80000000 PUSH 80
00416228 50 PUSH EAX
00416229 FF76 04 PUSH DWORD PTR DS:[ESI+4]
0041622C FF76 20 PUSH DWORD PTR DS:[ESI+20]
0041622F FF96 80000000 CALL DWORD PTR DS:[ESI+80]
00416235 6A 3A PUSH 3A
00416237 57 PUSH EDI
00416238 50 PUSH EAX
00416239 FF76 04 PUSH DWORD PTR DS:[ESI+4]
0041623C FF76 28 PUSH DWORD PTR DS:[ESI+28]
0041623F FF96 80000000 CALL DWORD PTR DS:[ESI+80]
00416245 -FFE7 JMP EDI <--------------------- Skip all and go here!

Skipp all calls before JMP EDI (some decrypting is performed there) and to it (be aware of possible breakpoin checks if you place bp on JMP EDI). That JMP EDI leads to part which throws you at false oep. Trace through that code and it will transform to:

00398305 E8 00000000 CALL 0039830A
0039830A 5D POP EBP
0039830B 81ED CD3CBA00 SUB EBP,0BA3CCD
00398311 64:67:8F06 0000 POP DWORD PTR FS:[0]
00398317 83C4 04 ADD ESP,4
0039831A C685 FD3CBA00 E9 MOV BYTE PTR SS:[EBP+BA3CFD],0E9
00398321 8B95 063DBA00 MOV EDX,DWORD PTR SS:[EBP+BA3D06]
00398327 0356 10 ADD EDX,DWORD PTR DS:[ESI+10]
0039832A 8D8D 023DBA00 LEA ECX,DWORD PTR SS:[EBP+BA3D02]
00398330 2BD1 SUB EDX,ECX
00398332 8995 FE3CBA00 MOV DWORD PTR SS:[EBP+BA3CFE],EDX
00398338 61 POPAD
00398339 9D POPFD
0039833A -E9 D0BC0600 JMP UnPackMe.0040400F <----------------------- Jump to false OEP.

And here it is:

0040400F E8 01040000 CALL UnPackMe.00404415
00404014 68 00000000 PUSH 0
00404019 FF15 F4114000 CALL DWORD PTR DS:[4011F4]
0040401F A3 07F04000 MOV DWORD PTR DS:[40F007],EAX
00404024 60 PUSHAD
00404025 8925 0BF04000 MOV DWORD PTR DS:[40F00B],ESP
0040402B E9 30000000 JMP UnPackMe.00404060
00404030 8B25 0BF04000 MOV ESP,DWORD PTR DS:[40F00B]
00404036 61 POPAD
00404037 E8 19090000 CALL UnPackMe.00404955
0040403C E8 6D040000 CALL UnPackMe.004044AE
00404041 89EC MOV ESP,EBP
00404043 5D POP EBP
00404044 FF35 D4F14000 PUSH DWORD PTR DS:[40F1D4]
0040404A FF15 EC114000 CALL DWORD PTR DS:[4011EC]
00404050 9B WAIT
00404051 DBE2 FCLEX
00404053 D92D 00604000 FLDCW WORD PTR DS:[406000]
00404059 C3 RETN
0040405A 0000 ADD BYTE PTR DS:[EAX],AL
0040405C 0000 ADD BYTE PTR DS:[EAX],AL
0040405E 0000 ADD BYTE PTR DS:[EAX],AL
00404060 68 00000000 PUSH 0
00404065 B8 7C604000 MOV EAX,UnPackMe.0040607C ; ASCII "Coded by Teddy Rogers / SnD Team 2005"
0040406A 89C2 MOV EDX,EAX
0040406C 52 PUSH EDX
0040406D B8 54604000 MOV EAX,UnPackMe.00406054 ; ASCII "If you unpack it write a tutorial... :)"
00404072 89C6 MOV ESI,EAX
00404074 56 PUSH ESI
00404075 E8 8B0F0000 CALL UnPackMe.00405005
0040407A 89C7 MOV EDI,EAX
0040407C 57 PUSH EDI
0040407D B8 0D000000 MOV EAX,0D
00404082 50 PUSH EAX
00404083 E8 4D030000 CALL UnPackMe.004043D5
00404088 89C6 MOV ESI,EAX
0040408A 56 PUSH ESI
0040408B E8 750F0000 CALL UnPackMe.00405005

Problem is that OEP should be at 404000. That couple instructions are junk now, and they where probably obfuscated trough obsidium layer. But in this crackme, those few bytes are not important and file will work without it, so just leave it like that. So, make dump now.

3. Imports

Ah, imports are realy pain in the ass. If you check where calls lead, you will see this:

00B00000 60 PUSHAD
00B00001 66:BE 22A2 MOV SI,0A222
00B00005 B7 66 MOV BH,66
00B00007 -E9 1D5489FF JMP 00395429
00B0000C 60 PUSHAD
00B0000D 66:BE 23A2 MOV SI,0A223
00B00011 B7 66 MOV BH,66
00B00013 -E9 115489FF JMP 00395429
00B00018 60 PUSHAD
00B00019 66:BE 20A2 MOV SI,0A220
00B0001D B7 66 MOV BH,66
00B0001F -E9 055489FF JMP 00395429
...
...

Import call goes in some obsidium procedure. There, obsidim doesn't use GetProcAddress. Intead, import is saved as

- dll image base (handle),
- some flag (FFFFFFFF is good one),
- import code (which is 2,4,1,80, or 40),
- first character of import,
- CRC32 hash.

Obsidium will get info which dll program needs, then it will do some internal checks (is this import already used, is it emulated whole or partially, etc...), then it will search in that DLL export (which is our import) which first character corespond with one in "obsidium import info" table, then it will search for such import which CRC32 hash is equal to one in table. When it finds it, then it will do some emulation and finaly jump to it.

Let's see on one example, our import call:

00404453 |. FF15 08124000 CALL DWORD PTR DS:[401208]

leads to

00B000B4 60 PUSHAD
00B000B5 66:BE 25A2 MOV SI,0A225 <------ These two values gives info about host DLL.
00B000B9 B7 67 MOV BH,67 <--------/
00B000BB -E9 695389FF JMP 00395429

then

MOVZX EDX,BH
MOVZX EAX,SI
CALL 00395434
POP EBX
MOV EBP,EBX
MOV EBX,DWORD PTR DS:[EBX-74]
SUB EBP,0B8A0CB
CALL 00395445
ADD DWORD PTR SS:[ESP],0D
MOV ECX,DWORD PTR SS:[EBP+B8A0BC]
CALL ECX
RETN
MOV ESI,DWORD PTR DS:[EBX+44]
XOR DL,BYTE PTR SS:[EBP+B8A18C]
XOR AX,WORD PTR SS:[EBP+B8A18D]
SHL EDX,3
MOV ECX,EDX
SHL EDX,1
ADD ECX,EDX
LEA EDI,DWORD PTR DS:[ESI+ECX+4]
CMP DWORD PTR DS:[EDI+4],0 <---------- [1] Check, probably is DLL loaded. Must be FFFFFFFF.
JE 0039568C
ADD ESI,DWORD PTR DS:[EDI+14]
LEA ESI,DWORD PTR DS:[ESI+EAX*8]
MOVZX EAX,WORD PTR DS:[ESI] <--------- [2] It takes from table "import code" (2,4,1,80, or 40).
CMP EAX,4
JE 00395522
CMP EAX,1
JE SHORT 003954F8
CMP EAX,80
JE 00395754
CMP EAX,40
JE 0039554E
MOVZX EAX,WORD PTR DS:[ESI+2]
PUSH 1
PUSH EAX
PUSH 0
PUSH DWORD PTR DS:[ESI+4]
PUSH DWORD PTR DS:[EDI]
CALL DWORD PTR DS:[EBX+50] <--------- If "import code" is 2, it finds that import here.
TEST EAX,EAX <----------------------- [3] EAX will be API value.
JNZ SHORT 0039550A

Most of imports in some file are "code 2" type. It is just what type of obfuscation obsidium will use.

I repaired imports like this using OllyScript. First, this is import table in our unpackme:

0040119C 00 00 B0 00 0C 00 B0 00 18 00 B0 00 24 00 B0 00 ............$...
004011AC 30 00 B0 00 3C 00 B0 00 48 00 B0 00 54 00 B0 00 0...<...H...T...
004011BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004011CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004011DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004011EC 60 00 B0 00 6C 00 B0 00 78 00 B0 00 84 00 B0 00 `...l...x.......
004011FC 90 00 B0 00 9C 00 B0 00 A8 00 B0 00 B4 00 B0 00 ................
0040120C C0 00 B0 00 CC 00 B0 00 D8 00 B0 00 E4 00 B0 00 ................

It is very small. I placed breakpoints on [1], [2] and [3] lines in obsidium code. Then I wrote small script that will find one import call, place eip=that_line, run, check if DLL is OK [1] , check if "import code" is 2 [2], and in that case it will break on [3]. There eax holds correct import, now my script will take that API, place it in IAT, restore stack to default value and go searching for next one.

Basicly, I'm just using obsidium import algorithm against itself. Better would be, ofcourse, to code some app or plugin that could just do this by itself. After using script, IAT is:

0040119C 6E ED D4 77 BB D7 D4 77 7C B5 D4 77 0B 05 D8 77 n..w...w|..w...w
004011AC B2 02 D7 77 54 05 D5 77 9F F2 D6 77 54 00 B0 00 ...wT..w...wT...
004011BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004011CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004011DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004011EC 60 00 B0 00 37 97 80 7C 29 B5 80 7C 2D FF 80 7C `...7..|)..|-..|
004011FC 2F FE 80 7C D4 05 91 7C AE 30 82 7C 29 29 81 7C /..|...|®0.|)).|
0040120C 10 11 81 7C 3D 04 91 7C B9 8F 83 7C E4 00 B0 00 ...|=..|...|....

Three imports are not repaired because they are not "code 2" type. I could write new script for those types, or rewrite this one, but that would first need examning obsidium code again (I'm little tired now) , so I will just try to fix it manually.

If I check "all intermodular calls", I see that only one call is bad:

0040404A CALL DWORD PTR DS:[4011EC] DS:[004011EC]=00B00060
00404EF2 CALL DWORD PTR DS:[40119C] USER32.CallNextHookEx
00404F89 CALL DWORD PTR DS:[4011F0] kernel32.GetCurrentThreadId
00404C66 CALL DWORD PTR DS:[4011A0] USER32.GetDesktopWindow
00404019 CALL DWORD PTR DS:[4011F4] kernel32.GetModuleHandleA
00404C7F CALL DWORD PTR DS:[4011A4] USER32.GetWindowRect
004057CE CALL DWORD PTR DS:[4011F8] kernel32.GlobalAlloc
00405614 CALL DWORD PTR DS:[4011FC] kernel32.GlobalFree
00404622 CALL DWORD PTR DS:[401204] kernel32.HeapCompact
00404453 CALL DWORD PTR DS:[401208] kernel32.HeapCreate
00404584 CALL DWORD PTR DS:[40120C] kernel32.HeapDestroy
00404326 CALL DWORD PTR DS:[401214] kernel32.lstrcatA
00404FC9 CALL DWORD PTR DS:[4011A8] USER32.MessageBoxA
004045EF CALL DWORD PTR DS:[401200] ntdll.RtlAllocateHeap
0040463E CALL DWORD PTR DS:[401200] ntdll.RtlAllocateHeap
004046DD CALL DWORD PTR DS:[401210] ntdll.RtlFreeHeap
00404FA4 CALL DWORD PTR DS:[4011AC] USER32.SetWindowsHookExA
00404C17 CALL DWORD PTR DS:[4011B0] USER32.SystemParametersInfoA
00404FDB CALL DWORD PTR DS:[4011B4] USER32.UnhookWindowsHookEx

I traced that call, it is "code 40" type which then lead to ExitProcess:

0039562E FFD0 CALL EAX ; kernel32.ExitProcess

When I found that import, I repaired dump and it worked.

4. Final words

Altough target works, job is not completed. Stolen OEP code can be easily restored if target is not compiled in ASM since obsidium steals only few opcodes. Imports that are not used in target need to be examned.

Obsidium has some more tricks like relocating image and little different import protection, but I will maybe onother tutorial on obsidium. It jus needs little more researching.

I hope that this tutorial will be of some use to somebody. Scripts that I used are included in archive, OEP one should work one evry Obsidium 1.2.5.0 target, while IAT one needs to be used as described in this tutorial. I'm not sure will my dump work on other machines, this was simply first touch on this good protector.

Untill next tutorial, I send regards to all great people on BIW, ARTEAM, SND, CRACKMES.DE, etc...

See you ;)

[课程]Linux pwn 探索篇!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
楼主能不能帮我个忙啊
2006-6-16 14:40
0
游客
登录 | 注册 方可回帖
返回
//