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 :
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.
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
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 :
- 00000001.TMP
- CLOKSPL.EXE
- DPLAYERX.DLL
- SECDRV.SYS
And also the existence of two executables, Game.EXE and Game.ICD.
We can also recognize this protection by her signature :
The signature is "BoG_ *90.0&!! Yy>" followed by 3 unsigned integers : the version, subversion an revision number.
Or simply by using Protection ID :
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 :
- Put an hardware breakpoint at the entry point of the function, and at the end.
- Check if the function was called with ProcessInformationClass argument = ProcessDebugPort ( 0x7 ).
- If it was called with the fact mentionned before, we set the buffer to 0 (The process is not being run under ring 3 debugger).
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 :
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() :
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.
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 :
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 :
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 :
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 :
We need to do the same thing for 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 :
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 ....
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
- http://www.woodmann.com/fravia/artha_safedisc.htm
- http://www.winehq.org/pipermail/wine-users/2002-April/007910.html
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.
- Diablo II.exe : Qui fait un CreateProcess('Game.exe' ... ).
- Game.exe : Notre éxécutable protégé par securom.
> 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 :
Find OEP
La première étape est de trouver l'oep, 2 solutions s'offrent à nous :
- Bp sur WriteProcessMemory, compter le nombre de break avant que le jeu se lance, une fois ce nombre calculé, s'arreter sur le dernier appel, retourner dans le user code, et chercher un jmp EAX.
- Bp sur GetDriveTypeA, retourner en code user, et scroller jusqu'à trouver le jmp EAX.
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 :
Pourtant le script partait bien :
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.
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 ?!"
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 :].
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.
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 :
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" :
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.
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 ... :
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
- 0xEBFE : C'est un simple jmp short 0, une boucle infinie
- 0xCC : Interruption 3, trap to debugger
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 :
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'
Si vous regardez bien la stack :
- 0x60 : Handle du processus
- 0x0040A000 : Adresse de départ où on va écrire
- 0x00182408 : Buffer à écrire (placement d'un int3 0xCC)
- 0x5E00 : Taille à écrire
On continue l'éxécution ( après avoir bien configuré Olly : Just-in-time debugging ) :
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 :
Le code est : "SEXXX".
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 :
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 :
Le code :
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 :
- Création d'un processus (ici en l'occurence svchost.exe)
- Appel à VirtualAllocEx, pour allouer de la mémoire dans un processus cible
- Appel à WriteProcessMemory, on écrit le code qui va étre éxecuté
- Appel à GetThreadContext, on récupère le context du thread du processus cible.
- On set eip sur le code que l'on vient d'écrire
- Appel à SetThreadContext, on met à jour le context du thread du processus cible.
- Appel à ResumeThread, on relance l'éxécution du thread du processus cible.
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>
Super nain tant dos
Introduction
Après le reverse de Aladdin dans le dernier post, j'ai voulu jouer avec un autre jeux, Zombies :
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é :
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 :
Sayez je suis en mesure de pouvoir loguer et meme voir les password que je rentre au sein du debugger :
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 :
Tous les autres passwords sont aussi fonctionnels, Have fun :]
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 :
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 :
Chaque password est une combinaison de 6 personnages du jeu :
- Aladdin
- Jafar
- Sultan
- Apu (singe)
- Génie
- Jasmine
J'étaits en mesure de pouvoir tracer le programme avec cette outil :
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é.
C'est à l'adresse 0xA6 que nos choix de password sont stockés, on voit meme à quelle valeur correspond tel personnage :
- Jafar : 5
- Sultant : 4
- Apu : 3
- Génie : 2
- Jasmine : 1
- Aladdin : 0
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 :
Attention ils se lisent à l'envers ;) :
- Génie, Apu, Aladdin, Sultan
- Jafar, Apu, Jasmine, Génie
- Génie, Jafar, Aladdin, Apu
- Apu, Aladdin, Génie, Jasmine
- Jafar, Jasmine, Aladdin, Jafar
- Jasmine, Jafar, Sultan, Jasmine
- Aladdin, Jasmine, Apu, Sultan
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 :]