03/09/2011

Unsafedisc

Introduction

I'm actually doing an internship, so it's difficult to work on my personal project.
But today i found some times to finish the first release of my unsafedisc, actually it will work only with version 1.11.0 because i haven't got enough game with safedisc protection.
But when i will touch my pay, i will have the possibility to buy some old school games :]

Tiny Encryption Algorithm

In my last post about safedisc, i said dplayerx.dll was here for decrypting some sections of the icd file, like .data and .text. I reversed all the stuff from this dll, there is a lot obfuscation inside by using stc, jb, jmp instruction, the only solution i found was to trace the code and reconstruct with my own hand. If you want to check the decrypt routine look at the begining of segment text2 inside dplayerx.dll.
The interesting thing to know is the key that is present in this form :
ABCD - ABCD - ABCD - ABCD
It uses a 128-bit key with the same 32-bit pattern.
So we are able to bruteforce it, but how to know it's the good key ?
At the begining i was thinking about decrypting the 64-bit blocks where oep is, and check if the value equal to push ebp, mov ebp, esp (0x55 0x89 0xe5), but it's not enough, it was a really bad idea.
After switching on my brain, i looked at .data section :

unsafe_winhex

We can easily see the same pattern : 0xA7E6ED0A 0x01E06592, TEA use electronic codebook mode (ECB), if we got in our example two 64-bit block of "0" they will have the same cipher.
With the end of section data, we are able to bruteforce easily the key :].

Monte Carlo algorithm

The last part for breaking safedisc, is reconstruct all the iat. By reading information from woodmann, and my reversed stuff from dplayerx.dll especially this routine :

00D42420    51              PUSH ECX
00D42421    8B4424 10       MOV EAX,DWORD PTR SS:[ESP+10]		; tea key
00D42425    53              PUSH EBX
00D42426    8B5C24 0C       MOV EBX,DWORD PTR SS:[ESP+C]		; Allocated memory
00D4242A    55              PUSH EBP
00D4242B    56              PUSH ESI
00D4242C    8B7424 18       MOV ESI,DWORD PTR SS:[ESP+18]		; Nb max api 0x99 or 0x5c
00D42430    33ED            XOR EBP,EBP
00D42432    33C9            XOR ECX,ECX
00D42434    57              PUSH EDI
00D42435    8B38            MOV EDI,DWORD PTR DS:[EAX]
00D42437    3BF5            CMP ESI,EBP
00D42439    896C24 10       MOV DWORD PTR SS:[ESP+10],EBP
00D4243D    76 11           JBE SHORT dplayerx.00D42450
00D4243F    33C0            XOR EAX,EAX
00D42441    41              INC ECX
00D42442    890483          MOV DWORD PTR DS:[EBX+EAX*4],EAX
00D42445    8BC1            MOV EAX,ECX
00D42447    25 FFFF0000     AND EAX,0FFFF
00D4244C    3BC6            CMP EAX,ESI
00D4244E  ^ 72 F1           JB SHORT dplayerx.00D42441
00D42450    3BF5            CMP ESI,EBP                          
00D42452    76 5E           JBE SHORT dplayerx.00D424B2
00D42454    69FF 6D5AE835   IMUL EDI,EDI,35E85A6D
00D4245A    33D2            XOR EDX,EDX
00D4245C    81C7 E9621936   ADD EDI,361962E9
00D42462    85F6            TEST ESI,ESI
00D42464    8BC6            MOV EAX,ESI
00D42466    74 05           JE SHORT dplayerx.00D4246D
00D42468    42              INC EDX
00D42469    D1E8            SHR EAX,1
00D4246B  ^ 75 FB           JNZ SHORT dplayerx.00D42468
00D4246D    81E2 FFFF0000   AND EDX,0FFFF
00D42473    8BC7            MOV EAX,EDI
00D42475    8BCA            MOV ECX,EDX
00D42477    D3E8            SHR EAX,CL
00D42479    B9 20000000     MOV ECX,20
00D4247E    2BCA            SUB ECX,EDX
00D42480    0FAFC6          IMUL EAX,ESI
00D42483    D3E8            SHR EAX,CL
00D42485    8BCD            MOV ECX,EBP
00D42487    81E1 FFFF0000   AND ECX,0FFFF
00D4248D    3BC1            CMP EAX,ECX
00D4248F    74 14           JE SHORT dplayerx.00D424A5
00D42491    8B148B          MOV EDX,DWORD PTR DS:[EBX+ECX*4]
00D42494    895424 20       MOV DWORD PTR SS:[ESP+20],EDX
00D42498    8B1483          MOV EDX,DWORD PTR DS:[EBX+EAX*4]
00D4249B    89148B          MOV DWORD PTR DS:[EBX+ECX*4],EDX
00D4249E    8B4C24 20       MOV ECX,DWORD PTR SS:[ESP+20]
00D424A2    890C83          MOV DWORD PTR DS:[EBX+EAX*4],ECX
00D424A5    45              INC EBP
00D424A6    8BD5            MOV EDX,EBP
00D424A8    81E2 FFFF0000   AND EDX,0FFFF
00D424AE    3BD6            CMP EDX,ESI
00D424B0  ^ 72 A2           JB SHORT dplayerx.00D42454
00D424B2    5F              POP EDI
00D424B3    5E              POP ESI
00D424B4    5D              POP EBP
00D424B5    5B              POP EBX
00D424B6    59              POP ECX
00D424B7    C3              RET

This routine will just build a table of ascending dwords, with a size of nb api, and sort the table into the correct order, using two consts, and morphing the decrypt key with them.

unsafe_ordinal

As you can see all the ordinal value of each IMAGE_THUNK_DATA differ, we have to reverse their algorithm for reconstruct well all IMAGE_THUNK_DATA.

Conclusion

That's all for understanding all the suff i coded in masm : my_unsafedisc.rar.

Screenshot

unsafe_screen

25/08/2011

NOD or GDI

Introduction

It's my first english article so please be cool.
If you haven't noticed yet, I (only) like old video games (see SNES), today i'm going to play with Red Alert : Tiberian Sun.
This game is protected by an old commercial protection : Safedisc aka C-dilla.

Detection

Safedisc version 1 can be recognized by several files on the CD :

And also the existence of two executables, Game.EXE and Game.ICD.
We can also recognize this protection by her signature :

tibsun_winhex

The signature is "BoG_ *90.0&!! Yy>" followed by 3 unsigned integers : the version, subversion an revision number.

Or simply by using Protection ID :

tibsun_protecid

So in this article we will talk about how to defeat Safedisc version 1.11.0000 using a ring3 debugger.
Yes because all over the internet, i just found article / tutorial using Softice.

Anti Debug

So let's open Game.exe into OllyDbg.
We can found two anti debug tricks related to a ring 3 debugger.
The first anti debug trick is several call to IsDebuggerPresent(), and they also check this manually :

004212C6   .  64:A1 1800000>MOV EAX,DWORD PTR FS:[18]
004212CC   .  8B48 30       MOV ECX,DWORD PTR DS:[EAX+30]
004212CF   .  0FB641 02     MOVZX EAX,BYTE PTR DS:[ECX+2]
004212D3   .  85C0          TEST EAX,EAX

So by putting this code where you want, and change the origin ("New origin here" => Ctrl + Gray *), we can defeat this trick.

004197C5      64:A1 1800000>MOV EAX,DWORD PTR FS:[18]
004197CB      8B40 30       MOV EAX,DWORD PTR DS:[EAX+30]
004197CE      83C0 02       ADD EAX,2
004197D1      C600 00       MOV BYTE PTR DS:[EAX],0
00419E35    ^\E9 5675FFFF   JMP GAME.<ModuleEntryPoint>

The second trick is several call to ZwQueryInformationProcess(), with ProcessInformationClass argument set to ProcessDebugPort for checking if the process is being run under the control of a ring 3 debugger.
I know, it can be simply bypass by using the Phant0m plugin, but i wanted to write a script to defeat it :

var is_ProcessDebugPort
var buffer
var ZwQueryInformationProcess

gpa "ZwQueryInformationProcess", "ntdll.dll"
bphws $RESULT, "x"
mov ZwQueryInformationProcess, $RESULT
add $RESULT, C
bphws $RESULT, "x"
mov is_ProcessDebugPort, 0

cnt:
eob break
run

break:
cmp eip, ZwQueryInformationProcess
je begin_zwqueryinformationprocess

end_zwqueryinformationprocess:
cmp is_ProcessDebugPort, 1
jne cnt
mov is_ProcessDebugPort, 0
mov [buffer], 0
jmp cnt

begin_zwqueryinformationprocess:
cmp [esp + 8], 7
jne cnt
mov buffer, [esp + C]
log buffer
mov is_ProcessDebugPort, 1
jmp cnt

Explanation :

No we can work with our executables without problems.
There are another anti debug tricks, like calls to CreateFileA() with argument : "\.SICE" and "\.NTICE", for checking is softice is running.
Also in this version, they load a driver (secdrv.sys) but i have noticed nothing about anti debug techniques.
I don't know what the purpose of this driver in this version of safedisc (1.11.0000), i tried to do some shit with DeviceIoControl() api but without result.
Apparently, the driver is safe in this version while others are : CVE-2007-5587.

*.ICD

At the beginning of the post, I mentionned the existence of two files Game.EXE and Game.ICD.
Game.EXE executable is only a loader which decrypts and loads the protected game executable in the encrypted ICD file :

tibsun_procexp

tibsun_createproc

Safedisc uses a debug locker technique with buffer overflow to the start the real executable (ICD file).
So let's see the buffer used by WriteProcessMemory() :

tibsun_writeprocess

You can notice the different call eax, LoadLibrary("dplayerx.dll"), GetProcAdress("Ox77F052CC"), then it call this function, for decrypting all sections of the icd file (it uses TEA ).
So if we want to attach a debugger to the icd process, remember my last post about EBFE or CC tricks, and notice the PUSH EBP RET (0x53C3), we can replace these opcode by EBFE, then attach olly to the process.
We can see that if DEP is on, the game will crash (failed?).
So let's replace (0x53C3 by 0xEBFE), continue the program (ResumeThread() will be called) and open a new ollydbg and attach to this process.
Then pause the program ( F12 ), and replace JMP SHORT 0012FFB2 by PUSH EBX RET.
Trace the code you will arrive into kernel32, continue tracing, then at the entry point of the icd file.

tibsun_oep

So we are here and the fun part can begin :] :

006854E3 >  55              PUSH EBP
006854E4    8BEC            MOV EBP,ESP

Call redirection

In Safedisc, all the calls to Kernel32 or User32 api are done through dplayerx.dll.
Let's take an example :

00685509    FF15 54826900   CALL DWORD PTR DS:[<&KERNEL32.CreateDirectoryA>] ; DS:[00698254]=01213423

...

01213423    60              PUSHAD
01213424    68 67000000     PUSH 67
01213429    68 00000000     PUSH 0
0121342E    FF15 44342101   CALL DWORD PTR DS:[1213444]                      ; dplayerx.00D4E9D0
01213434    83C4 08         ADD ESP,8
01213437    61              POPAD
01213438    FF25 3E342101   JMP DWORD PTR DS:[121343E]                       ; Jump to api

You can notice a call to dplayerx.00D4E9D0, with a stack where we pushed 0x67 and 0x0.
What does it mean ?
It simple, 0x0 is for telling the routine that it's a kernel32 api, and 0x67 is the number of the api to call.
This routine will GetProcAddress() for the specified api, then jump to it.
And what about User32 api ?

00D750B7    60              PUSHAD
00D750B8    68 43000000     PUSH 43
00D750BD    68 01000000     PUSH 1
00D750C2    FF15 D850D700   CALL DWORD PTR DS:[D750D8]                                           ; dplayerx.00D4E9D0
00D750C8    83C4 08         ADD ESP,8
00D750CB    61              POPAD
00D750CC  - FF25 D250D700   JMP DWORD PTR DS:[D750D2]

Same thing but we pushed 0x1 instead of 0x0, and jump to the resolved address.

Fix this

The problem is if we dump the executable and try to reconstruct the iat, it will fail, because all calls to kernel32 or user32 api must be solved by dplayerx.dll :

tibsun_import

So why not using their own routine (dplayerx.00D4E9D0) to fix our iat :] ?
Let's take a look at rdata section for all kernel32 redirection :

tibsun_kernel32

It starts at 0x6980b8 and ends at 0x69831b

>>> hex(((0x69831b - 0x6980b8) / 4) + 1)
'0x99'

So we got 0x99 call to fix, like ImportRec said us : NbFunc:99(decimal:153) Valid:NO.
Be careful because the rdata section is not Writable, go fix it :

tibsun_rdata

No choose any memory region wich is executable and code this :

01213DEB    33DB               XOR EBX,EBX				; we start with api 0
01213DED    BA B8806900        MOV EDX,006980b8			; Start of the rdata kernel32 redirection
01213DF2    60                 PUSHAD
01213DF3    53                 PUSH EBX
01213DF4    6A 00              PUSH 0				; Kernel32 api
01213DF6    FF15 61252101      CALL DWORD PTR DS:[1212561]          ; dplayerx.00D4E9D0
01213DFC    83C4 08            ADD ESP,8
01213DFF    890D 00009500      MOV DWORD PTR DS:[950000],ECX	; save the api address
01213E05    61                 POPAD
01213E06    8B0D 00009500      MOV ECX,DWORD PTR DS:[950000]        
01213E0C    890A               MOV DWORD PTR DS:[EDX],ECX		; fix the api address in rdata
01213E0E    43                 INC EBX   	 			; next api
01213E0F    81FB 99000000      CMP EBX,99				; no more api to fix ?
01213E15    74 05              JE SHORT 01213E1C
01213E17    83C2 04            ADD EDX,4				; next api address to solve
01213E1A  ^ EB D6              JMP SHORT 01213DF2
01213E1C    CC                 INT3					; please stop

When we got out of the func dplayerx.00D4E9D0, ecx equals the address of api wich will be called, so we save this in a memory region writable.
And then we fix this in the rdata section for each api for kernel32.
Set a new origin at the begining of your code and run it.
Olly break, and our rdata look pretty nice :

tibsun_fix

We need to do the same thing for user32.

tibsun_user32

>>> hex(((0x6984C4 - 0x698358) / 4) + 1)
'0x5b'

We will use the same code as above but change the start value of edx to 0x698358, PUSH 0 to PUSH 1 and cmp EBX, 99 to cmp EBX, 5C.
Run it and now come back into import rec :

tibsun_ok

Everything is ok, we can dump and fix the iat without problems. Safedisc has been removed correctly :].
BUT ! there is an another problem when we launch the game it ask us for the a cd ....

tibsun_cd

This is not the purpose of the article but you should copy all *.mix files form the cd into the tiberian sun directory and look around 0x004DCBAE ;)

Conclusion

I know over the internet there are several unwrappers for safedisc but with closed source, so i'm actually studying differents versions of safedisc, for writting a similar tools but with source included.

Useful links

02/08/2011

Diablo II part 2

Introduction

Après mon post sur la fabrication de serial pour diablo II, je ne pouvais pas en rester là.
Il fallait que je joue avec Securom afin de me fabriquer un no-cd avec mes petits doigts.
Bab00n a écrit un excellent article sur la création d'un no-cd pour Diablo II Lord Of Destruction (l'extension du jeu).

Securom v4

Ici pour diablo II original (Version 1.03), nous avons affaire à du securom v4, il faut savoir que nous sommes en présence de 2 éxécutables à la base.

   > hexdump -C Game.exe | grep -i "AddD"
   0003b940  00 00 00 00 41 64 64 44  00 00 00 00 35 39 4a 50  |....AddD....59JP|
   0003b960  00 00 00 00 46 57 53 00  41 64 64 44 00 00 00 00  |....FWS.AddD....|
   0003b970  41 64 64 44 00 00 00 00  5c 00 00 00 1a 27 1a 12  |AddD....\....'..|
   00048000  41 64 64 44 03 00 00 00  34 2e 30 37 2e 30 30 00  |AddD....4.07.00.|

Ici nous avons affaire à du securom version 4.07.00, protection ID aurait pu très bien faire l'affaire :

diablo2_protid

Find OEP

La première étape est de trouver l'oep, 2 solutions s'offrent à nous :


    004169A6  |> |58                    /POP EAX
    004169A7  |. |FFE0                  |JMP EAX

Nous arrivons enfin sur l'enty point :

00402120  /.  55            PUSH EBP

Reconstruction des calls redirigés

Securom met en place un système pour rédiriger les calls vers les différentes API dont il a besoin.
C'est en recherchant des renseignements sur securom, que j'ai commencé à me demander si j'allais etre en présence de call [Securom] / call Securom.
Securom étant la section dans notre cas "cms_t".
Donc je décide d'écrire un script Olly :

//log "Loggin all call [Securom]"
//next:
//findop eip, #FF15#
//mov eip, $RESULT
//log eip
//add eip, 6
//cmp $RESULT, 0
//jne next
//pause

var dest
log "Loggin all call Securom"
next:
findop eip, #E8????????#
mov eip, $RESULT
mov dest, [eip + 1]
add dest, eip
cmp dest, 40F000
jbe not_log
log "-- Maybe new ?"
log dest
log eip
not_log:
add eip, 5
cmp $RESULT, 0
jne next
pause

La première parti commenté du script sert à loguer les call [Securom], alors que l'autre sert à loguer les call Securom.
Résultat nous sommes en présence de seulement de call [Securom], dont voici un exemple :

00402146    FF15 F0024200   CALL DWORD PTR DS:[4202F0]               ; Game.0040FBD0

La routine en 0x0040FBD0, va calculer l'api à apeler puis mettre le résultat dans eax, et ansi jmp eax, afin de l'apeler.
N'étant pas sur de moi, j'ai tout fait à la mano ('Non je ne suis pas fou, je voulais pas faire de la race c'est tout').
Donc j'ai écrit un script Olly pour loguer tout ca et remplacer correctement les call [Securom] par call [API].

Voici un bout de la routine intéressant :

0041035E    8B06            MOV EAX,DWORD PTR DS:[ESI]
00410360    5F              POP EDI
00410361    5E              POP ESI
00410362    5B              POP EBX
00410363    8BE5            MOV ESP,EBP
00410365    5D              POP EBP
00410366    FFE0            JMP EAX

Le but va etre de bp en 0x0041035E, loguer esi, et loguer d'ou nous avons été apelé.

var rez
bp 0041035E
cnt:
eob Break
eoe handler
run

handler:
ret

Break:
log esi
mov rez, [esp - 4]
sub rez, 6
log rez
log [esi]
jmp cnt

Arrivé à l'entry point, j'exec le script et la failed :

diablo2_error

Pourtant le script partait bien :

diablo2_log

En fait bêtement dans cette routine, ils font appel à timeGetTime :

00410264    FF15 F8164400   CALL DWORD PTR DS:[4416F8]               ; WINMM.timeGetTime

Je m'emmerde pas, je nop un saut conditionnel en 0x004100F0.
Et la le jeu se lance, nous avons été en mesure de tout loguer.
Il suffit de remplacer à la mano les call [Securom], par call [API], ou alors il suffit juste de modifier le script :].
Vous n'aurez plus qu'a dump le processus, un coup d'importRec et le tour est joué !

Hmmm par contre il reste un CD-Check dans storm.dll (0x00357CA4) ;)

Have Fun :]
On se revoit pour une version 5 de securom ou du safedisc.

26/07/2011

I need the key

Introduction

Tout droit sorti du grenier je ressors mon boitier de diablo II, oui oui ce jeu magique cube horadrim tout ca tout ca :]
Mais ce post n'est pas destiné à faire l'éloge de ce jeu mais plutôt arriver à y jouer.

Serial

Malheuresement au moment de l'installation on nous demande un Nom et un Serial, comme par hasard j'ai perdu le manuel avec ma clef CD...
"Comment ca je peux pas joueur ?!"

diablo2_serial.png

La routine de vérification ne tient pas compte du nom que vous avez entré mais uniquement du serial, j'ai du coup coder un générateur de clef (bruteforce ?).
Oui je génére toutes les clefs possibles et inimaginables et en ayant reverse toute leur routine de vérificaion, je regarde si elle est valide ou non.
Afin que ca prenne pas la vie, il faut définir la variable "dic" avec un minimum de caractères qui sont présents au début du code (commentaire 0x43f750...).
Trève de bavardage voici le code :

#! /usr/local/bin/python2.6

import sys
import binascii

###################################
# 0x43f750 .....
#
#             #0 #1 #2 #3 #4 #5
#       FF FF FF FF 00 FF 01 FF
#
#       #6 #7 #8 #9 #: #; #< #=
#       02 03 04 05 FF FF FF FF
#
#       #> #? #@ #A #B #C #D #E
#       FF FF FF FF 06 07 08 09
#
#       #F #G #H #I #J #K #L #M
#       0A 0B 0C FF 0D 0E FF 0F
#
#       #N #O #P #Q #R #S #T #U
#       10 FF 11 FF 12 FF 13 FF
#
#       #V #W #X #Y #Z #[ #\ #]
#       14 15 16 FF 17 FF FF FF
#
#       #^ #_ #` #a #b #c #d #e
#       FF FF FF FF 06 07 08 09
#
#       0A 0B 0C FF 0D 0E FF 0F
#
#       10 FF 11 FF 12 FF 13 FF
#
#       14 15 16 FF 17 FF FF FF
#
# Caracter can only be '246789BCDEFGHJKMNPRTVWXZ'
###################################

s_ebx = 1
spe = 0

def combine(alphabet, nb, base = ''):
    for c in alphabet:
        if nb == 1:
            yield base + c
        else:
            for c1 in combine(alphabet, nb - 1, base + c):
                yield c1

rep = 4
#dic = '246789BCDEFGHJKMNPRTVWXZ'
dic = '7NJDTF'
tab_43f750 = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x02, 0x03,
              0x04, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
              0x0C, 0xFF, 0x0D, 0x0E, 0xFF, 0x0F, 0x10, 0xFF,
              0x11, 0xFF, 0x12, 0xFF, 0x13, 0xFF, 0x14, 0x15,
              0x16, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
              0x0C, 0xFF, 0x0D, 0x0E, 0xFF, 0x0F, 0x10, 0xFF,
              0x11, 0xFF, 0x12, 0xFF, 0x13, 0xFF, 0x14, 0x15,
              0x16, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]

def routine_414770(res):
    if res < 0x0a:
        res += 0x30
    else:
        res += 0x37
    return res

def routine_41457e(a, b):
    global s_ebx
    global spe
    A = tab_43f750[ord(a)]
    B = tab_43f750[ord(b)]
    if A == 0xff:
        A = 0xffffffff
    if B == 0xff:
        B = 0xffffffff
    RES = A + A * 2 & 0xffffffff
    RES = RES * 8 + B & 0xffffffff
    if RES > 0x100:
        spe = spe + s_ebx
        RES = RES - 0x100
    a = RES
    a = a & 0x0f
    RES = RES >> 4
    RES = RES & 0xf
    RES = routine_414770(RES)
    a = routine_414770(a)
    s_ebx = s_ebx << 1
    return [RES, a]

def routine_3(combi):
    res = routine_41457e(combi[0], combi[1])
    res2 = routine_41457e(combi[2], combi[3])
    return res + res2

def routine(one, two, three, four):
    global s_ebx
    global spe
    s_ebx = 1
    spe = 0
    one = routine_3(one)
    two = routine_3(two)
    three = routine_3(three)
    four = routine_3(four)
    if routine_add(one, two, three, four) == True and test(one, two, three, four) == True:
        return True
    return False

def test(one, two, three, four):
    edi = 0x15
    eax = 15
    ecx = 0x16
    serial = one + two + three + four
    while eax >= 0:
        dl = serial[eax]
        ecx = ecx & 0xf
        bl = serial[ecx]
        serial[eax] = bl
        serial[ecx] = dl
        eax = eax - 1
        ecx = ecx - 1
    eax = 15
    esp_38 = 0x13AC9741
    while eax >= 0:
        ecx = serial[eax]
        if ecx <= 0x37:
            ecx = esp_38
            dl = esp_38 & 0xff
            dl = dl & 0x7
            serial[eax] = dl ^ serial[eax]
            esp_38 = esp_38 >> 3
        if ecx <= 0x41:
            cl = eax
            cl = cl & 0x1
            serial[eax] = cl ^ ecx
        eax = eax - 1
    if serial[0] == 0x30 and (serial[1] == 0x37 or serial[1] == 0x36):
        return True

def routine_41458D(a):
    if a >= 0x41 and a <= 0x5a:
        a = a + 0x20
    if a < 0x61:
        a = a - 0x30
    else:
        a = a - 0x57
    return a
    
def routine_add(one, two, three, four):
    global spe
    edi = 0x3
    for i in one:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    for i in two:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    for i in three:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    for i in four:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    edi = edi & 0xff
    if edi == spe:
        return True
    return False

if __name__ == "__main__":
    for one in  combine(dic, rep):
        for two in combine(dic, rep):
            for three in combine(dic, rep):
                for four in combine(dic, rep):
                    if routine(one, two, three, four) == True:
                        print "[+] Key found try : " + one + "-" + two + "-" + three + "-" + four

Je tiens à m'excuser si le code n'est pas très explicite, mais j'ai juste supra la flemme de tout clean :].

26/07/2011

cpuid is your friend

Introduction

A un moment donné faut se tenir à jour, j'entends par là utiliser des OS peut être pas de qualité, mais qui sont au goût du jour (Seven 64bits), histoire de pouvoir jouer avec des rookits par exemple, seul hic :
"Merde mon proc il supporte le 64 bits ?"
C'est aprè avoir vu un joli failed (à se demander si les mecs qui codent des FakeAV allument leur cerveau quand ils codent ), que cpuid est la réponse à ma question.

Code

Je poste un bout de code qui pourra peut être servir à d'autre qu'à moi, même si des outils le font très bien.
Mais on aime souvent réinventer la roue :

#include <stdio.h>

int main(void)
{
  unsigned int code = 0x80000001;
  unsigned int a, b, c, d;

  __asm__ volatile ("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(code));
  if (d & (1 << 29))
    printf("64 bit available\n");
  else
    printf("64 bit not available\n");
  return (0);
}

Code simpliste juste pour checker si le long mode est disponible ou non sur le processeur.

25/07/2011

EBFE vs CC

Introduction

Tout droit revenu d'un entretien pour un stage, et oui je ne suis encore qu'un étudiant, je m'empresse de revenir à mes activités de reverse de vx.
Je fais un billet sur ce que j'ai reverse car j'ai utilisé une technique (EBFE tricks )tout droit sorti du grenier.
Technique utilisé pour vaincre safedisc v1 et 2, si je dis pas de bêtises. Je l'avais utilisé pour Sim City 3000 ;).

La cible

> md5sum porno-rolik12.avi.exe
726537d1ea6b871a2db9b0a3e2098ce7  porno-rolik12.avi.exe

> sha1sum porno-rolik12.avi.exe
79458bdc8bc60faf5198b9b60f2c2657b4f5c3a8  porno-rolik12.avi.exe

> sha256sum porno-rolik12.avi.exe
6f91c648ee422571225a1839395118c2ac706ac83c7d0bc64e491e75e8c08bf1  porno-rolik12.avi.exe

Un winlocker plus ou moins original où j'ai dû déjouer certaines choses :

rolik_winlock.png

Il m'informe que si je veux supprimer leur widget je dois envoyer au compte beeline 9099416525 un montant de 500 USD.

First stage, First failed

On ouvre la bête avec Olly et là c'est le drame, on tombe sur un crypter en VB, sérieusement le vb à reverse c'est juste chiant ( voir même de la merde ).
La personne qui a inventé ce langage j'ai tout simplement envie de la tuer, car sur le marché un paquet de crypter sont écrits en vb et c'est à la porté de tout le monde de le faire.

Les mecs qui ont codés ce WinLocker doivent être juste idiot :

HKU\S-1-5-21-1004336348-1580436667-1708537768-1003\Software\Microsoft\Windows\CurrentVersion\Run\711527436: "C:\Documents and Settings\analys\711527436.exe"

C:\Documents and Settings\analys\711527436.exe

Après avoir copié le réel Winlocker, et changer la clef de registre pour se start au démarrage, ils font un appel à ShellExecuteW pour lancer "shutdown /r /f /t 3" :

rolik_shutdown.png

Nous avons exactement 3 secondes pour ouvrir une invite de commande et taper "shutdown -a" pour annuler la mise hors tension.
Nous sommes donc maitenant en mesure de travailler avec le réel WinLocker.

rolik_exec.png

Dans un précédent billet, je parlais d'injection dans des processus, ici c'est la même, sauf qu'au lieu de passer par kernel32, il passe direct pas ntdll, du coup il use ZwWriteVirtualMemory, ZwSetContextThread ... :

rolik_proc.png

Une fois le thread du processus resume, ces saguouins font appel à SystemParametersInfo, afin de bloquer la souris au sein de la fenetre du ransomware, faire semblant qu'on est dans un screensaver, du coup pas de Alt-Tab, ni Ctrl-Alt-Suppr possible.

p0wn stage 2

Il nous faut trouver un moyen pour essayer de prendre la main sur le processus dans lequel le processus actuel injecte du code, mais qui dit code injecté dit code qu'on peut modifier.
On va donc modifier le buffer et écrire notre propre code en breakant sur ZwWriteProcessMemory.
C'est là qu'arrive 2 instructions qui vont nous être bien utiles :

> rasm2 -d EBFE
jmp 0x8048000
> rasm2 -d CC
int3

Il faut trouver l'endroit (EIP), où le thread va reprendre son éxécution, un bp sur ZwSetContextThread, et regarder ce qu'il y a dans la structure CONTEXT :

rolik_context.png

Ok, notre thread reprendra en 0x0040f160, revenons en arrière afin maitenant de breaker sur ZwWriteVirtualMemory, et modifier le buffer d'écriture.
Nous devons calculer où se situe le future eip du thread dans le buffer :

> hex(0x40F160 - 0x40A000) # new eip - adresse ou il start a ecrire
'0x5160'
> hex(0x182408 + 0x5160)   # buffer + offset new eip
'0x187568'

rolik_write.png

Si vous regardez bien la stack :

On continue l'éxécution ( après avoir bien configuré Olly : Just-in-time debugging ) :

rolik_kdo.png

On nous invite gentiment à debugger le programme (int3 ftw), à partir de là c'est gagné, on tombe sur du upx, la routine habituel, dump processus, reconstruction de l'IAT. Et on peut travailler tranquilement avec le locker et le p0wn.
Car malheuresement ils usent l'API StrCmpW, pour checker le code, un bp dessus est :

rolik_serial.png

Le code est : "SEXXX".

23/07/2011

MBRLocker

Introduction

En ce moment je m'amuse à reverse quelques vx. Mais aujourd'hui c'est jour de fête, je suis tombé sur mon premier MBRLocker :

mbr_code

Reverse de la bête

Le problème que j'avais c'était comment dump mon mbr alors que j'étais infécté, revenir au snapshot précédent, attendre que le malware écrive le nouveau mbr et en 1 ligne de python dump le nouveau mbr :

file("vuln_mbr", "wb").write(file(r"\\.\PhysicalDrive0","rb").read(0x200))

Mais j'ai préféré opté pour une autre solution, convertir le disque de ma vm et allé récupérer le mbr à la main :

> vboxmanage clonehd --variant static analyse.vdi temp.vdi
> head -c 348 temp.vdi | hexdump -C
00000000  3c 3c 3c 20 4f 72 61 63  6c 65 20 56 4d 20 56 69  |<<< Oracle VM Vi|
00000010  72 74 75 61 6c 42 6f 78  20 44 69 73 6b 20 49 6d  |rtualBox Disk Im|
00000020  61 67 65 20 3e 3e 3e 0a  00 00 00 00 00 00 00 00  |age >>>.........|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  7f 10 da be 01 00 01 00  90 01 00 00 02 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000150  00 00 00 00 00 10 00 00  00 b0 00 00              |............|
0000015c

On récupère l'offset de notre disque dans le fichier : 0x0000b000.
On go chercher le mbr :

> echo $((0x0000b000))
45056
dd if=./temp.vdi of=./vuln_mbr bs=512 count=1 skip=$((45056/512))

On regarde que c'est bien ca :

> file vuln_mbr
mbr_vuln.bin: x86 boot sector; partition 1: ID=0x7, active, starthead 1, startsector 63, 20948697 sectors, code offset 0x80

Bon bah reste plus qu'à reverse le truc, moins de 512 octets de code ca devrait allé vite :

> objdump -D -b binary -mi386 -Maddr16,data16 mbr_vuln

Je vais pas tout vous balancer tout le disas mais juste des lignes inéressantes :

88:	b4 00                  mov    ah,0x0
8a:	57                     push   di
8b:	cd 16                  int    0x16
8d:	5f                     pop    di
8e:	eb 37                  jmp    0xc7					     

...

c7:	3c 0d                  cmp    al,0xd
c9:	75 55                  jne    0x120
cb: be 8c 7d               mov    si,0x7d8c
ce: bf 00 6c               mov    di,0x6c00
d1: b5 00                  mov    ch,0x0
d3:	ea 72 7d 00 00         jmp    0x0:0x7d72

Ici on fait appel à l'interrupt 0x16 avec ah = 0 afin de lire un caractère, si ce caractère est égale à 0xd (Carriage Return) alors on part en 0x172 ( 0x7d72 - 0x7C00 ), le bootloader est chargé en 0x7C00 par le BIOS.
Surement la routine de vérifiction de notre code :

172: 8a 0e 8b 7d          	  mov    cl,BYTE PTR ds:0x7d8b
176: ac                   	  lods   al,BYTE PTR ds:[si]
177: 8a 15                	  mov    dl,BYTE PTR [di]
179: 80 f2 ed             	  xor    dl,0xed
17c: 47                   	  inc    di
17d: 3a c2                	  cmp    al,dl
17f: 0f 85 58 ff          	  jne    0xdb
183: 49                   	  dec    cx
184: 75 f0                	  jne    0x176
186: ea 36 7d 00 00       	  jmp    0x0:0x7d36

On peut voir clairement que dans %cl, il y aura la taille du code attendu, %si lui pointe vers le serial auquel on va être comparé, %di pointe sur ce qu'on a rentré, donc tous nos carac vont être xored avec 0xED.
Il suffit d'aller voir ce qui traine en 0x18B ( 0x7d8B - 0x7C00 ) :

> hexdump -s 0x18B mbr_vuln
0000018c  07 a0 ad dc d9 d8 bf b9  00 00 00 00 00 00 00 00

>>> chr(0xa0 ^ 0xED)
'M'
>>> chr(0xad ^ 0xED)
'@'
>>> chr(0xdc ^ 0xED)
'1'
>>> chr(0xd9 ^ 0xED)
'4'
>>> chr(0xd8 ^ 0xED)
'5'
>>> chr(0xbf ^ 0xED)
'R'
>>> chr(0xb9 ^ 0xED)
'T'

Le code serait donc : "M@145RT", on teste et ca boot :]

Bonus

Je trouvais leur message, un peu triste, alors j'ai voulu pour le fun coder un petit bootloader qui déssine une bière, mais qui ne fait rien au final, car 512 octets pour pouvoir caller un dession c'est hard, du coup j'ai 2 stage, le stage 1 qui init la gdt, passer en mode protected, on load le printer de la biere en memoire (0x1000), et on finira par l'exec, voici le résultat :

mbr_beer

Le code :

21/07/2011

Faire du re et apprendre des choses

Introduction

L'autre jour sur IRC, on me balance un link vers un VX qui aboutit sur le download d'un FakeAV.
Le truc fun quand j'ai reverse la bête, j'ai appris une technique d'injection dans un processus qui est oldschool mais n'étant pas expert dans la matière, j'ai voulu la recoder.

Injection

Voici un extrait du disas commenté :

00403ACE  |.  FF90 70080000 CALL DWORD PTR DS:[EAX+870]              ;  kernel32.CreateProcessA
00403AD4  |.  85C0          TEST EAX,EAX
00403AD6  |.  0F84 9C000000 JE usps.00403B78
00403ADC  |.  A1 10C54000   MOV EAX,DWORD PTR DS:[40C510]
00403AE1  |.  53            PUSH EBX
00403AE2  |.  57            PUSH EDI
00403AE3  |.  FF90 74080000 CALL DWORD PTR DS:[EAX+874]              ;  kernel32.GetModuleHandleA
00403AE9  |.  8BF0          MOV ESI,EAX
00403AEB  |.  8B46 3C       MOV EAX,DWORD PTR DS:[ESI+3C]
00403AEE  |.  8B5C30 50     MOV EBX,DWORD PTR DS:[EAX+ESI+50]
00403AF2  |.  A1 10C54000   MOV EAX,DWORD PTR DS:[40C510]
00403AF7  |.  6A 40         PUSH 40                                  ;  flProtect = PAGE_EXECUTE_READWRITE
00403AF9  |.  68 00300000   PUSH 3000                                ;  flAllocationType = MEM_COMMIT | MEM_RESERVE
00403AFE  |.  53            PUSH EBX                                 ;  dwSize = 0xE000
00403AFF  |.  56            PUSH ESI                                 ;  lpAddress = 0x00400000
00403B00  |.  FF75 EC       PUSH DWORD PTR SS:[EBP-14]               ;  hProcess = PROCESS_INFORMATION->hProcess
00403B03  |.  FF90 78080000 CALL DWORD PTR DS:[EAX+878]              ;  kernel32.VirtualAllocEx
00403B09  |.  3BC7          CMP EAX,EDI
00403B0B  |.  74 60         JE SHORT usps.00403B6D
00403B0D  |.  8D4D E8       LEA ECX,DWORD PTR SS:[EBP-18]
00403B10  |.  51            PUSH ECX                                 ;  *lpNumberOfBytesWritten = 0x00B8FC6C
00403B11  |.  53            PUSH EBX                                 ;  nSize = 0xE000
00403B12  |.  56            PUSH ESI                                 ;  lpBuffer = 0x00400000
00403B13  |.  50            PUSH EAX                                 ;  lpBaseAddress = 0x00400000
00403B14  |.  FF75 EC       PUSH DWORD PTR SS:[EBP-14]               ;  hProcess = PROCESS_INFORMATION->hProcess
00403B17  |.  A1 10C54000   MOV EAX,DWORD PTR DS:[40C510]
00403B1C  |.  FF90 88080000 CALL DWORD PTR DS:[EAX+888]              ;  kernell32.WriteProcessMemory
00403B22  |.  8D85 D4FBFFFF LEA EAX,DWORD PTR SS:[EBP-42C]
00403B28  |.  50            PUSH EAX                                 ;  lpContext = 0x00B8F858
00403B29  |.  FF75 F0       PUSH DWORD PTR SS:[EBP-10]               ;  hThread = PROCESS_INFORMATION->hThread
00403B2C  |.  A1 10C54000   MOV EAX,DWORD PTR DS:[40C510]
00403B31  |.  FF90 84080000 CALL DWORD PTR DS:[EAX+884]              ;  kernel32.GetThreadContext
00403B37  |.  8D85 D4FBFFFF LEA EAX,DWORD PTR SS:[EBP-42C]
00403B3D  |.  50            PUSH EAX                                 ;  lpContext = 0x00B8F858
00403B3E  |.  FF75 F0       PUSH DWORD PTR SS:[EBP-10]               ;  hThread = 0xC0
00403B41  |.  A1 10C54000   MOV EAX,DWORD PTR DS:[40C510]
00403B46  |.  C785 8CFCFFFF>MOV DWORD PTR SS:[EBP-374],usps.00402EF5 ;  lpContext->Eip = 0x00402EF5 ( OEP )
00403B50  |.  FF90 80080000 CALL DWORD PTR DS:[EAX+880]              ;  kernel32.SetThreadContext
00403B56  |.  FF75 F0       PUSH DWORD PTR SS:[EBP-10]               ;  hThread = PROCESS_INFORMATION->hThread
00403B59  |.  A1 10C54000   MOV EAX,DWORD PTR DS:[40C510]
00403B5E  |.  FF90 7C080000 CALL DWORD PTR DS:[EAX+87C]              ;  kernel32.ResumeThread 

La méthode est toute simple :

A partir de là, j'étais en mesure de pouvoir réecrire la chose, seul soucis lorsque je vais injecter du code dans le processus cible, il va falloir que je resolv à la main les adresses des API dont j'ai besoin. (reconstruction d'IAT à l'éxécution).

Éxecutable injecté

Le code injecté est assez fun, je vais résoudre l'adresse de kernel32.dll, chercher l'adresse de la table de fonction, puis me balader sur la table de noms de fonctions, et calculer un hash pour chaque nom, que je comparerais avec un hash précalculé pour l'API que je désire appeler.
Dans l'exemple ici, j'affiche juste un message "It Works", donc je load User32.dll et fait un appel à MessageBoxA.
Bref le code parlera mieux de lui même :

.386
.model flat,stdcall
option casemap:none 
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib


assume fs:nothing

.code
start:

	 cld
	 xor	edx, edx
	 ;; Get address of kernel32.dll
	 mov    edx, fs:[edx + 30h]  
	 mov    edx, [edx + 0Ch]

	 mov    edx, [edx + 14h]
next_mod:

	 mov	esi, [edx + 028h]
	 mov 	ecx, 24

	 xor 	edi, edi
loop_modname:
	 xor	eax, eax
         lodsb
	 cmp	al, 'a' 
         jl 	not_lowercase
	 sub 	al, 020h

not_lowercase:
	 ror	edi, 13
         add 	edi, eax
	 loop 	loop_modname
	 cmp 	edi, 6A4ABC5Bh
         mov 	ebx, [edx + 10h]
	 mov 	edx, [edx]
	 jne 	next_mod

  
	 mov	eax, ebx
	 push 	ebx
	 push 	offset Addrkernel32
	 push 	offset Afonction
	 push 	offset Namefunc
	 push 	offset Aordinal
	 call 	filladdr 
  	   
	 lea		edi, Nuser
	 call		load_dll
	   
	 push	eax
	 push 	offset Addruser32
	 push 	offset Afunuser32
	 push 	offset Nfunuser32
	 push 	offset Aorduser32
	 call 	filladdr 

	 push 	30h
	 push	offset Message
	 push	offset Message
	 push 	0
	 push 	4
	 push 	[Addruser32]
	 push 	[Nfunuser32]
	 push 	[Aorduser32]
	 push 	[Afunuser32]
	 push 	[HMessageBox]
	 call 	callapi
	   
	 jmp	exit
	   
;; -------------------------
;; Function : load_dll
;; Description : Load the desired dll
;; Parameters : EDI = Crypted name of the dll
;; Output : Eax = Adress of the loaded dll
;; -------------------------
load_dll:
	 push	      ebp
	 mov	      ebp, esp

	 push	      edi
	 push 	      1
	 push 	      [Addrkernel32]
	 push 	      [Namefunc]
	 push 	      [Aordinal]
	 push 	      [Afonction]
	 push 	      [HLoadLibrary]
	 call 	      callapi
	 leave
	 ret	

;; -------------------------
;; Function : strlen
;; Description : Compute the length of a string
;; -------------------------------  
strlen:
	 push	      edi
	 mov	      edi, dword ptr[esp + 08]

	 push 	      edi
	 xor 	      eax, eax
symbol:
	 scas	byte ptr[edi]
	 jne 	symbol
	 xchg 	eax, edi
	 dec 	eax
	 pop 	edi
	 sub 	eax, edi
	 pop 	edi
	 ret
	 

;; -------------------------------
;; Function : xCRC32
;; Description : Calcul CRC32 for the desired string	
;; Parameters :
;; 	       - String
;; 	       - Length of the string
;; --------------------------------	
xCRC32:
	 pushad
	 mov     ebp,esp
	 xor     eax,eax
	 mov     edx,dword ptr [ebp+24h]
	 mov     ecx,dword ptr [ebp+28h]
	 jecxz   @4
	 dec     eax
@1:
	 xor     al, byte ptr [edx]
       	 inc     edx
	 push    08
	 pop     ebx
@2:
       	 shr     eax,1
	 jnc     @3
	 push    edx
	 push    ecx
	 mov     ecx, 0EDB8h
	 mov     edx, eax
	 shr     eax, 16
	 xor     eax, ecx
	 mov     ecx, 08320h
	 xor     edx, ecx
	 and     edx, 0ffffh
	 shl     eax, 16
	 or	 eax, edx
	 pop     ecx
	 pop     edx
@3:
         dec     ebx
	 jnz     @2
	 loop    @1
	 not     eax
@4:
         mov     dword ptr [ebp + 1Ch], eax
         popad
         ret	
       
;; -------------------------------
;; Function   : callapi
;; Description : Function for calling the desired api with all her arguments 
;; Parameters :
;; - Push all the arguments for the further called api
;; - Number of arguments for the further called api
;;   - Adress of the desired dll
;;   - Adress of name table of the desired dll
;;   - Adress of ordinal table of the desired dll
;;   - Adress of function table of the desired dll
;;   - Hash (CRC32) of the desired api to call
;;
;; -------------------------------
callapi:
	 push	ebp
	 mov   ebp,esp
	 mov   ecx,dword ptr [ebp + 01Ch]  ; Number of args
	 add   ecx, 7
load:
	 mov   edx,dword ptr [ebp + 4 * ecx]
	 push  edx
	 dec   ecx
	 cmp   ecx, 7
	 jne   load
	 mov   edx, dword ptr [ebp + 018h] ; Addr dll
	 mov   esi, dword ptr [ebp + 014h] 	   ; Addr Name func
	 xor   ecx, ecx
gob:
	 lodsd
	 add   eax, edx
	 mov   edi, eax
comb:
	 push  edx
	 lea   edi, [edi]
	 push  edi
	 call  strlen
	 pop   edi
	 mov   edx, eax
	 push  edx
	 push  edi
	 call  xCRC32
	 pop   edi
	 pop   edx
	 cmp   eax, dword ptr [ebp + 08h]  ; HashApi

	 pop   edx
	 je	   goob
	 jmp   nexte
nexte:
	 inc   ecx
	 jmp   gob
goob:
	 shl   ecx, 1
	 add   ecx, dword ptr [ebp + 010h]  ; Addrordinal
	 xor   eax, eax
	 mov   ax, word ptr [ecx]
	 shl   eax, 2
	 add   eax, dword ptr [ebp + 0Ch]  ; Addrfonction
	 mov   eax, [eax]
	 add   eax, edx
	 call  eax
	 pop   ebp
	 ret	
	 
;; -------------------------------
;; Function : filladdr
;; Description : usefull function for filling all information about a dll 
;; Parameters :
;; 	       - Addr of "ms-dos" header of the desired dll
;; 	       - Ptr to addr of name table
;; 	       - Ptr to addr of ordinal table
;; 	       - Ptr to addr of function table
;; --------------------------------
filladdr:
	 push	ebp
	 mov     ebp,esp
	 mov     ecx, dword ptr [ebp + 018h]
	 mov     ecx, [ecx + 03Ch]
	 add     ecx, dword ptr [ebp + 018h]
	 cmp     word ptr [ecx], 'EP'
	 jnz     exit
	 ;; Save Header addr
	 mov	edi, dword ptr[ebp + 014h]
	 mov 	[edi], eax
	 mov	edx, [ecx + 78h]
	 add 	edx, eax
	 mov 	ebx, [edx + 1ch]
	 add 	ebx, eax
	 ;; Save fun addr
	 mov	edi, dword ptr[ebp + 010h]
	 mov 	[edi], ebx
	 mov 	ebx, [edx + 20h]
	 add 	ebx, eax
	 ;; Save Name fun addr
	 mov   edi, dword ptr[ebp + 0Ch]
	 mov   [edi], ebx
	 mov   ebx, [edx + 24h]
	 add   ebx, eax
	 ;; Save ordinal addr
	 mov  edi, dword ptr[ebp + 08h]
	 mov  [edi], ebx
	 pop  ebp
	 ret

exit:
;; Exit(0)
	 push	0
	 push 	1
	 push 	[Addrkernel32]

	 push 	[Namefunc]
	 push 	[Aordinal]
	 push 	[Afonction]

	 push 	[HExitProcess]
	 call 	callapi						



;; Address of kernel32.dll	
Addrkernel32   		dd	0
;; Adress of function table in kernel32.dll				
Afonction 		dd 	0
;; Adress of name function table in kernel32.dll			
Namefunc  	        dd 	0
;; Adress of ordinal's table in kernel32.dll				
Aordinal  	        dd 	0

;; Address of user32.dll
Addruser32		dd	0
;; Adress of function table in user32.dll
Afunuser32	       	dd	0

;; Adress of name function table in user32.dll
Nfunuser32	   	dd	0
;; Adress of ordinal's table in user32.dll
Aorduser32		dd	0 
	      

Nuser			db	"user32.dll", 0 

;; Hash for kernel32.dll
HLoadLibrary    dd	03FC1BD8Dh
HExitProcess   	dd	0251097CCh

;; Hash for user32.dll
HMessageBox	dd	0572D5D8Eh
				 
Message		db	"It Works !", 0 

end start

Le makefile qui va avec :

@echo off

if exist "creepy.obj" del "creepy.obj"
if exist "creepy.exe" del "creepy.exe"

\masm32\bin\ml /c /coff "creepy.asm"
if errorlevel 1 goto end

\masm32\bin\Link /SUBSYSTEM:WINDOWS /SECTION:.text,erw /BASE:0x400000 "creepy.obj"
 if errorlevel 1 goto end

:end
pause

Nous avons donc notre programme qui va être injecté, il nous faut le programme pour l'injecter mais avant j'ai écrit un script python pour transformer le binaire injecté en variable pour masm, afin de pouvoir l'inclure dans mon programme :

out = open("creepy_exe.asm", "w")
i = 0

count = 0
f = open ("creepy.exe", "rb" )

out.write("creepy db ")
while (1):
    tampon = f.read(1)

    if tampon == "":
        break
    i = i + 1 
    if i % 8 == 0:    
        s = "0%02Xh\n" % ord(tampon)

    else:
        s = "0%02Xh, " % ord(tampon)

    out.write(s)
    count = count + 1

    if i % 8 == 0:
        out.write("\tdb\t")

        i = 0
out.write("000h")  
print "COUNT =", count        

Voilà il ne reste plus qu'à coder l'injecteur :

.386
.model flat, stdcall
assume fs:nothing
option casemap :none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib


.data
include	creepy_exe.asm
scr      					db	"\svchost.exe", 0
len_code					dd	1536
oep_foffset					dd	200h
oep						dd 00401000h

.data?
SystemDir   db 256 dup(?)
Bwritten    dd ? 
Ctx         CONTEXT <?>
SuInfo      STARTUPINFO <?>
PrcInfo     PROCESS_INFORMATION <?>

.code

start:
invoke		GetSystemDirectory, addr SystemDir, sizeof SystemDir
invoke 		lstrcat, addr SystemDir, addr scr
invoke 		CreateProcess, NULL, addr SystemDir, NULL, NULL, NULL, CREATE_SUSPENDED, NULL, NULL, addr SuInfo,addr PrcInfo
invoke          VirtualAllocEx, PrcInfo.hProcess, oep, len_code, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE
mov		eax, len_code
sub		eax, oep_foffset
mov		ebx, offset creepy
add		ebx, oep_foffset
invoke          WriteProcessMemory, PrcInfo.hProcess, oep, ebx, eax, Bwritten
mov 		Ctx.ContextFlags, CONTEXT_FULL
invoke 	 	GetThreadContext, PrcInfo.hThread, addr Ctx
push		oep
pop         	Ctx.regEax
invoke 	 	SetThreadContext, PrcInfo.hThread, addr Ctx
invoke		ResumeThread, PrcInfo.hThread
invoke 		ExitProcess, 0 

end start

Voilà, la bêtise que j'ai ecrite récemment et qui m'a bien amusé.

Bonus

Just for the lulz, en reversant le malware il communiquait avec un serveur distant à l'aide du format XML. J'ai écrit un petit script python pour voir les communications :

import base64

def decrypt(s):
    res = ""

    for c in s:
        res += chr(ord(c) ^ 0x53)

    print res

ch1 = "HBhvITw8J21jbycgKj0wczo3bnRiYGJiYmFjY2RqdHxtb3whPDwnbQ=="
ch2 = """byE8PCdtbzE6PTU8czo3bnRgYGVqZGFgYGpidHM9J250Y3RzMSVudGd
         9ZXRzPydudB8SHXRzPCBudAQ6PTc8JCBzCwNzAyE8NTYgIDo8PTI/c3
         Rtc298MTo9NTxtb3whPDwnbQ=="""

ch3 = "byE8PCdtbyM6PTRzOjdudGBgZWpkYWBgamJ0fG1vfCE8PCdt"

f = open('527069638.dat', 'r')

s = f.read()

decrypt(s)
decrypt(base64.b64decode(ch1))

decrypt(base64.b64decode(ch2))
decrypt(base64.b64decode(ch3))

Output :

<?xml version="1.0"?>

<root/>


OK<root>0<tsync id='3369723391'/></root>
<root><binfo id='3369723391' nt='0' bv='4.6' lt='LAN' os='Windows XP Professional '> </binfo></root>
<root><ping id='3369723391'/></root>

18/07/2011

Super nain tant dos

Introduction

Après le reverse de Aladdin dans le dernier post, j'ai voulu jouer avec un autre jeux, Zombies :

zombie_jaq.png

Je crois que ce jeu quand j'étais petit je n'ai jamais réussi à le finir en entier, il est super difficile, mais super fun.

Passwords

Comme dans le jeu Aladdin, tous les 5 levels, on nous file un password pour pouvoir continuer là où on s'est arrêté :

zombie_menu.png

Afin de trouver facilement la routine de vérification des passwords, il m'en fallait un valide, comme ca en tracant le code, je la trouverais facilement, je suis donc allé jusqu'au level 5 :

zombie_passwd.png

Sayez je suis en mesure de pouvoir loguer et meme voir les password que je rentre au sein du debugger :

zombie_w4kf.png

Voici la routine commenté :

$82/B018 AD A0 1E    LDA $1EA0  [$82:1EA0]
$82/B01B C9 42 43    CMP #$4342              ====\
$82/B01E D0 0D       BNE $0D    [$B02D]          |
$82/B020 AD A2 1E    LDA $1EA2  [$82:1EA2]       |
$82/B023 C9 44 46    CMP #$4644                  |  SECRET LEVEL == "BCDF"
$82/B026 D0 05       BNE $05    [$B02D]     =====|
$82/B028 9C 7C 1E    STZ $1E7C  [$82:1E7C]       |
$82/B02B 18          CLC                         |
$82/B02C 6B          RTL                /*RET*/  |
$82/B02D E2 30       SEP #$30               <====/
$82/B02F AE A0 1E    LDX $1EA0  [$82:1EA0]   X = *(0x1EA0)
$82/B032 AD A2 1E    LDA $1EA2  [$82:1EA2]   A = *(0x1EA2)
$82/B035 8D A0 1E    STA $1EA0  [$82:1EA0]   *(0x1EA0) = A
$82/B038 8E A2 1E    STX $1EA2  [$82:1EA2]   *(0x1EA2) = X
$82/B03B C2 30       REP #$30                
$82/B03D A2 00 00    LDX #$0000              X = 0
$82/B040 A9 05 00    LDA #$0005              A = 5
$82/B043 8D 7C 1E    STA $1E7C  [$82:1E7C]   *(0x1E7C) = A 
$82/B046 E2 30       SEP #$30                <======================================\
$82/B048 BC 4B B1    LDY $B14B,x[$82:CC15]   Y = *(0xB14B + x)                       |
$82/B04B B9 78 B1    LDA $B178,y[$82:B17E]   A = *(0xB178 + y)                       |
$82/B04E EB          XBA                     Exchange byte (ex : 4243 -> 43 42)      |
$82/B04F BC 4A B1    LDY $B14A,x[$82:CC14]   Y = *(0xB14A + x)                       |
$82/B052 B9 78 B1    LDA $B178,y[$82:B17E]   A = *(0xB178 + y)                       |
$82/B055 C2 30       REP #$30                                                        |
$82/B057 CD A0 1E    CMP $1EA0  [$82:1EA0]                                           |
$82/B05A F0 1B       BEQ $1B    [$B077]      === First stage Ok =====\               |
$82/B05C EE 7C 1E    INC $1E7C  [$82:1E7C]   \                       |               |
$82/B05F EE 7C 1E    INC $1E7C  [$82:1E7C]   |                       |               |
$82/B062 EE 7C 1E    INC $1E7C  [$82:1E7C]   |--> 0x1E7C += 4        |               |
$82/B065 EE 7C 1E    INC $1E7C  [$82:1E7C]   /                       |               |
$82/B068 E8          INX                     X++;                    |               |
$82/B069 E8          INX                     X++;                    |               | 
$82/B06A E0 1A 00    CPX #$001A              if (A == 0x1A)          |               |
$82/B06D D0 D7       BNE $D7    [$B046]      ========================|===============/
$82/B06F A9 01 00    LDA #$0001                                      |
$82/B072 8D 7C 1E    STA $1E7C  [$82:1E7C]                           |
$82/B075 38          SEC                                             |
$82/B076 6B          RTL                     /* RET == Failed */     |
$82/B077 8E 38 00    STX $0038  [$82:0038]   <=======================|
$82/B07A A2 00 00    LDX #$0000              X = 0
$82/B07D A9 01 00    LDA #$0001              A = 1
$82/B080 8D 3A 00    STA $003A  [$82:003A]   *(0x3A) = A
$82/B083 E2 30       SEP #$30                <======================================\
$82/B085 AC 38 00    LDY $0038  [$82:0038]   Y = *(0x38)                             |
$82/B088 BD 65 B1    LDA $B165,x[$82:CC2F]   A = *(0xB165 + x)                       |
$82/B08B 18          CLC                                                             |
$82/B08C 79 8E B1    ADC $B18E,y[$82:B194]   A += *(0xB18E + y) & 0xff               |
$82/B08F A8          TAY                     Y = A                                   |
$82/B090 B9 78 B1    LDA $B178,y[$82:B17E]   A = *(0xB178 + y)                       |
$82/B093 EB          XBA                     Exchange byte (ex : 4243 -> 43 42)      |
$82/B094 AC 38 00    LDY $0038  [$82:0038]   Y = *(0x38)                             |
$82/B097 BD 64 B1    LDA $B164,x[$82:CC2E]   A = *(0xB164 + x)                       |
$82/B09A 18          CLC                                                             |
$82/B09B 79 8D B1    ADC $B18D,y[$82:B193]   A += *(0xB18D + y) & 0xff               |
$82/B09E A8          TAY                     Y = A                                   |
$82/B09F B9 78 B1    LDA $B178,y[$82:B17E]   A = *(0xB17E + y)                       |
$82/B0A2 C2 30       REP #$30                                                        |
$82/B0A4 CD A2 1E    CMP $1EA2  [$82:1EA2]                                           |
$82/B0A7 F0 12       BEQ $12    [$B0BB]      === JUMP GOOD BOY ===                   |
$82/B0A9 EE 3A 00    INC $003A  [$80:003A]                                           |
$82/B0AC E8          INX                     x++                                     |
$82/B0AD E8          INX                     x++                                     |
$82/B0AE E0 14 00    CPX #$0014              if (x != 0x14)                          |
$82/B0B1 D0 D0       BNE $D0    [$B083]      =======================================/
$82/B0B3 A9 01 00    LDA #$0001              
$82/B0B6 8D 7C 1E    STA $1E7C  [$80:1E7C]
$82/B0B9 38          SEC                  
$82/B0BA 6B          RTL                  /* RET = Failed Password */

Elle peut paraitre un peu longue mais en fait il y a une subtilité marrante, pour chaque password, il y en a 10 de possible, chacun d'eux donnera un nombre de victimes différents lorsque vous partirez du niveau désiré.
J'ai ecrit un petit code en python pour les générer:

stage = "HNCVWBXKGZTDLFPSRYJMQ"
B14A_offset =    [0x10, 0x00, 0x08, 0x05, 0x02,
                  0x0D, 0x11, 0x07, 0x05, 0x06,
                  0x0C, 0x11, 0x09, 0x0C, 0x14,
                  0x12, 0x03, 0x09, 0x0E, 0x10,
                  0x00, 0x0C, 0x12, 0x04, 0x0F,
                  0x0B, 0x04, 0x07]

B18D_offset = [0x00, 0xFE, 0xFE, 0xFF, 0x02,
                  0x00, 0x01, 0x02, 0xFF, 0xFE,
                  0x02, 0x02, 0x02, 0x01, 0x00,
                  0x00, 0x01, 0x01, 0x01, 0x00,
                  0xFF, 0xFF, 0x00, 0xFF, 0x00,
                  0x01, 0x18, 0x69, 0x8F]

B164_offset =    [0x04, 0x07, 0x0F, 0x0A, 0x0C,
                  0x12, 0x10, 0x09, 0x0E, 0x10,
                  0x03, 0x05, 0x0D, 0x08, 0x08,
                  0x0C, 0x0A, 0x0E, 0x06, 0x11]

x = 0
y = 0
print "Nb victims \t 1\t 2\t 3\t 4\t 5\t 6\t 7\t 8\t 9\t 10"
while (x < 0x1a):
    second = stage[B14A_offset[x + 1]]
    third = stage[B14A_offset[x]]
    y = 0
    lvl = "Lvl X\t\t"
    while (y < 0x14):
        a = B164_offset[y + 1]
        a += B18D_offset[x + 1]
        a &= 0xff
        fourth = stage[a]
        a = B164_offset[y]
        a += B18D_offset[x]
        a &= 0xff
        first = stage[a]
        lvl += first + second + third + fourth + "\t"
        y += 2
    print lvl
    x += 2
print "Secret Level : BCDF"

Voici l'output :

Nb victims 1	  2	  3	  4	  5	  6	  7	  8	  9	 10
Lvl X	WHRB	 SHRG	 LHRR	 RHRK	 PHRP	 VHRV	 FHRX	 GHRT	 THRL	XHRS	
Lvl X	CBGX	 FBGZ	 TBGY	 PBGG	 LBGS	 NBGW	 DBGK	 XBGD	 GBGF	WBGR	
Lvl X	XFCK	 YFCT	 PFCJ	 JFCZ	 RFCR	 BFCB	 SFCG	 TFCL	 LFCP	GFCY	
Lvl X	BKYZ	 RKYL	 FKYQ	 YKYD	 SKYJ	 WKYK	 PKYT	 ZKYP	 DKYR	KKYM 
Lvl X	VXBB	 PXBG	 DXBR	 SXBK	 FXBP	 CXBV	 LXBX	 KXBT	 ZXBL   BXBS 
Lvl X	XYLZ	 YYLL	 PYLQ	 JYLD	 RYLJ	 BYLK	 SYLT	 TYLP    LYLR   GYLM 
Lvl X	XLZG	 YLZD	 PLZM	 JLZT	 RLZY	 BLZX	 SLZZ    TLZF    LLZS   GLZJ 
Lvl X	WJQK	 SJQT	 LJQJ	 RJQZ	 PJQR	 VJQB    FJQG    GJQL    TJQP   XJQY 
Lvl X	BZVG	 RZVD	 FZVM	 YZVT	 SZVY    WZVX    PZVZ    ZZVF    DZVS   KZVJ 
Lvl X	BRPK	 RRPT	 FRPJ	 YRPZ    SRPR    WRPB    PRPG    ZRPL    DRPP   KRPY 
Lvl X	VLHX	 PLHZ	 DLHY    SLHG    FLHS    CLHW    LLHK    KLHD    ZLHF   BLHR 
Lvl X	WWJX	 SWJZ    LWJY    RWJG    PWJS    VWJW    FWJK    GWJD    TWJF	XWJR 
Lvl X	WDSG     SDSD    LDSM    RDST    PDSY    VDSX    FDSZ    GDSF	 TDSS   XDSJ 
Secret Level : BCDF

On teste le secret level and it works :

zombie_bonus.png

Tous les autres passwords sont aussi fonctionnels, Have fun :]

18/07/2011

Retour en enfance

Introduction

C'est en voyant des vidéos du site le joueur du grenier où le mec délire à finir un paquet de jeux old-school, ou encore voir des speed run sur speeddemosarchive, qu'une envie de ressortir les vieilles consoles prend le dessus.
C'est pour ça que je décide de m'amuser avec une vieille rom d'un jeu que j'ai pas mal apprécié, Aladdin :

aladdin_front.jpg

C'est l'occasion aussi de tester ce que vaut un tracer/debugger pour SNES.

Passwords

Dans le jeu Aladdin, tous les 2 - 3 levels, on nous donne un password à rentrer pour pouvoir revenir là où on en était la dernièere fois :

aladdin_password

Chaque password est une combinaison de 6 personnages du jeu :

J'étaits en mesure de pouvoir tracer le programme avec cette outil :

aladdin_snes

A partir de là le logiciel me crée des fichiers de logs : Aladdin0000.log, et en sortant les couteaux suisses : sort, uniq, cut en loguant quand le password est valide ou non, je fus en mesure de trouver la routine de vérification des password :

$84/ADE4 A0 00       LDY #$00
$84/ADE6 B6 A6       LDX $A6,y  [$00:00A6]
$84/ADE8 8A          TXA
$84/ADE9 D1 AA       CMP ($AA),y[$00:5555]
$84/ADEB F0 18       BEQ $18    [$AE05]
$84/ADED C2 21       REP #$21
$84/ADEF A5 AA       LDA $AA    [$00:00AA]
$84/ADF1 69 04 00    ADC #$0004
$84/ADF4 85 AA       STA $AA    [$00:00AA]
$84/ADF6 E2 20       SEP #$20
$84/ADF8 EE 0C 00    INC $000C  [$00:000C]
$84/ADFB AD 0C 00    LDA $000C  [$00:000C]
$84/ADFE C9 08       CMP #$08
$84/AE00 D0 E2       BNE $E2    [$ADE4]
$84/AE02 4C 7B AE    JMP $AE7B  [$00:AE7B]
$84/AE05 C8          INY
$84/AE06 C0 04       CPY #$04
$84/AE08 D0 DC       BNE $DC    [$ADE6]
$84/AE0A AD 0C 00    LDA $000C  [$00:000C]
$84/AE0D AA          TAX
$84/AE0E BD D6 FD    LDA $FDD6,x[$00:FDD6]
$84/AE11 8D 05 00    STA $0005  [$00:0005]
$84/AE14 8D 5C 03    STA $035C  [$00:035C]
$84/AE17 9C 5D 03    STZ $035D  [$00:035D]
$84/AE1A 22 0A 81 80 JSL $80810A[$80:810A]
$84/AE1E 4C 71 AE    JMP $AE71  [$00:AE71]
$84/AE21 89 08       BIT #$08
$84/AE23 F0 0E       BEQ $0E    [$AE33]
$84/AE25 A6 A5       LDX $A5    [$00:00A5]

Bon j'ai du apprendre vite fait l'assembleur du cpu de la SNES : 65c816, afin de comprendre comment retrouver tous les passwords sans trop de difficulté.

aladdin_val_pass

C'est à l'adresse 0xA6 que nos choix de password sont stockés, on voit meme à quelle valeur correspond tel personnage :

Revenons sur le code précédent, je vais le réecrire en pseudo-code pour ce qui ont du mal :

int	*pass_valid = 0xFD75;
cpt = 0;
begin:
y = 0;
test:
a = input[y]; /* (0xA6) */
if (a == *(pass_valid + y))
	y++;
	if (y != 4)
		goto test;
	else
		goto pass_valid;
pass_valid += 4;
cpt += 1;
if (cpt != 8)
	goto begin;
else
	/* Password failed : Restart du jeu */

En fait il y a 7 passwords possible qu'on va gentiment dumper :

aladdin_dump

Attention ils se lisent à l'envers ;) :

Voilà on a tous les passwords, bon on s'est quand meme bien fait chier car suffisait de google : "Password Aladdin SNES".
Mais bon le but c'était de s'amuser :]

aladdin_completed

Pages : 1 2 3 4 5