Date de publication : 8 mai 2007 15h15
Auteur : BeatriX
Cette section va aborder l'utilisation des fonctions waveOut qui sont chargées de lire des samples au format WAV. Il me semble qu'il est intéressant de connaître leur usage avant d'attaquer l'analyse pure et dure de ce binaire.
Voici les fonctions utilisées dans meloquynthe (issues de winmm.dll) :
J'ai recodé et compilé à l'aide de GoAsm, compilateur de Jeremy Gordon, un petit projet qui reprend de façon très simple le fonctionnement de la gestion de la musique par meloquynthe en exploitant l'un des samples du keygenme. Voici le projet complet téléchargeable ICI.
En résumé, le programme commence par ouvrir le device à l'aide de la fonction waveOutOpen puis crée un thread qui sera chargé de contrôler la musique, c'est-à-dire d'initialiser le buffer qui contient le sample, d'alimenter le device avec waveOutWrite à chaque fois que cela est nécessaire et de conclure quand cela est demandé.Voici le code commenté :
WAVEHDR STRUCT
lpData DD 0
dwBufferLength DD 0
dwBytesRecorded DD 0
dwUser DD 0
dwFlags DD 0
dwLoops DD 0
lpNext DD 0
Reserved DD 0
WAVEHDR ENDS
WAVEFORMATEX STRUCT
wFormatTag DW 0
nChannels DW 0
nSamplesperSec DD 0
nAvgBytesPerSec DD 0
nBlockAlign DW 0
wBitsPerSample DW 0
cbSize DW 0
WAVEFORMATEX ENDS
MSG STRUCT
hwnd DD 0
message DD 0
wParam DD 0
lParam DD 0
time DD 0
pt DD 0
MSG ENDS
.const
WAVE_MAPPER EQU -1
CALLBACK_THREAD EQU 20000h
MM_WOM_OPEN EQU 3BBh
MM_WOM_CLOSE EQU 3BCh
MM_WOM_DONE EQU 3BDh
PAGE_READWRITE EQU 4h
MEM_COMMIT EQU 1000h
MEM_DECOMMIT EQU 4000h
INFINITE EQU -1
.data
memptr DD 0
ThreadID DD 0
hDeviceAudio DD 0
hEvent DD 0
hEvent2 DD 0
titre DB "FRET",0
texte DB "waveOut example from meloquynthe",0
WaveFormat WAVEFORMATEX <1,1,0x5622,0xAC44,2,0x10,0>
WaveHeader1 WAVEHDR <>
MyMessage MSG <>
MySample1 INCBIN MySample1.bin ; <--- Sample rippé
StopMusic DB 0
.code
start:
; **************************** création du Thread de gestion de la musique
push offset ThreadID
push 0
push 0
push offset ThreadWave
push 0
push 0
call CreateThread
; **************************** Créer un event pour la synchronisation avec le thread
push 0
push 0
push 0
push 0
call CreateEventA
mov [hEvent], eax
; **************************** Créer un event pour la synchronisation avec le thread
push 0
push 0
push 0
push 0
call CreateEventA
mov [hEvent2], eax
; **************************** Ouvrir le device --> Le thread va recevoir MM_WOM_OPEN
push CALLBACK_THREAD ; fdwOpen
push 0 ; dwCallbackInstance
push [ThreadID] ; dwCallback
push offset WaveFormat ; pwfx
push WAVE_MAPPER ; uDeviceID
push offset hDeviceAudio ; phwo
call waveOutOpen
; **************************** Attendre que le thread ait fini son initialisation
push INFINITE
push [hEvent]
call WaitForSingleObject
; **************************** Préparer le header
push SIZEOF WAVEHDR
push offset WaveHeader1
push [hDeviceAudio]
call waveOutPrepareHeader
; **************************** jouer le sample
push SIZEOF WAVEHDR
push offset WaveHeader1
push [hDeviceAudio]
call waveOutWrite
; **************************** Petit message :)
push 0
push offset titre
push offset texte
push 0
call MessageBoxA
; **************************** Arrêter la musique
mov b [StopMusic], 1
; **************************** Attendre que le thread ait fini sa fermeture
push INFINITE
push [hEvent2]
call WaitForSingleObject
; **************************** libérer le buffer
FreeBuffer:
push MEM_DECOMMIT
push 1CB60h
push [memptr]
call VirtualFree
; **************************** Libérer les handles des 2 events
push [hEvent]
call CloseHandle
push [hEvent2]
call CloseHandle
push 0
call ExitProcess
; ******************************************
;
; Thread de gestion de la musique
;
; ******************************************
ThreadWave:
pushad
WaitForMessage:
xor esi, esi
push esi
push esi
push esi
push offset MyMessage
call GetMessageA
mov edi, [MyMessage.message]
mov ebp, [MyMessage.lParam]
cmp edi, MM_WOM_OPEN ; <------------------- Message envoyé par waveOutOpen
je >InitBuffer
cmp edi, MM_WOM_CLOSE ; <------------------- Message envoyé par waveOutClose
je >CloseThread
cmp edi, MM_WOM_DONE ; <------------------- Message envoyé par le device
je >SendSample
jmp WaitForMessage
ret
; *****************************
;
; Fixer le WaveHeader
;
; *****************************
InitWaveHeader:
pushad
xor ecx, ecx
mov ebx, d [esp+28h]
mov eax, d [esp+24h]
mov d [eax], ebx
mov d [eax+4h], 1CB60h
mov d [eax+8h], ecx
mov d [eax+0Ch], ecx
mov d [eax+10h], ecx
mov d [eax+14h], 1h
mov d [eax+18h], ecx
mov d [eax+1Ch], ecx
popad
retn 8h
; *************************************************
;
; Récupérer le sample dans une VirtualAlloc
;
; *************************************************
InitBuffer:
; ************************ Allouer un buffer pour les samples
push PAGE_READWRITE
push MEM_COMMIT
push 1CB60h
push 0
call VirtualAlloc
mov [memptr], eax
mov ecx, 1CB60h
mov esi, offset MySample1
mov edi, [memptr]
rep movsb
; ************************ Initialiser le WaveHeader
push [memptr]
push offset WaveHeader1
call InitWaveHeader
; ************************ Rendre la main
push [hEvent]
call SetEvent
jmp <WaitForMessage
; *****************************
;
; Fermeture du thread
;
; *****************************
CloseThread:
push [hEvent2]
call SetEvent
push 0
call ExitThread
; *****************************
;
; Alimenter le device
;
; *****************************
SendSample:
cmp b [StopMusic], 1
je >StopwaveOut
push SIZEOF WAVEHDR
push offset WaveHeader1
push [hDeviceAudio]
call waveOutWrite
jmp <WaitForMessage
; *****************************
;
; Stopper le device
;
; *****************************
StopwaveOut:
; **************************** Stopper la musique
push [hDeviceAudio]
call waveOutReset
; **************************** libérer le WaveHeader
push SIZEOF WAVEHDR
push offset WaveHeader1
push [hDeviceAudio]
call waveOutUnprepareHeader
; **************************** Fermer le device --> Le thread va recevoir MM_WOM_CLOSE
push [hDeviceAudio]
call waveOutClose
jmp <WaitForMessage
Meloquynthe utilise 28 "samples" accessibles via une table située en 0x5BFF0B. Voici cette table complète que j'ai dumpé à l'aide du plugin Olly "Table exporter 1.0" de DarkSide :
0x5BFF0B
const
unsigned long Values[28] = {
0x00408997, samples de base pour buffer 1
0x004D5E2D,
0x005BFFEE,
0x0047EA41,
0x006AA1DA, samples de base pour buffer 2
0x005128A4,
0x00444FF7,
0x004260ED,
0x0049C335, samples à ajouter pour buffer 1
0x007DFF85,
0x006177BE,
0x0053036A,
0x00784F65, samples à ajouter pour buffer 2
0x007C2B31,
0x007FF49E,
0x004B8F6C,
0x0075E1A3, samples à ajouter pour buffer 1 (une fois sur 2)
0x006C7E22,
0x0081C229, samples à ajouter pour buffer 1 (une fois sur 2)
0x007A406C,
0x0083C0F1, "crackme" -- paroles à ajouter au buffer en cours (de façon aléatoire)
0x005DCB78, "tendinite"
0x00461DD4, "F7"
0x004F559B, "Yo"
0x005F9746, "meat"
0x0054CEF9,
0x006E8B1C,
0x0063440A
};
Ces morceaux sont en fait les parties "data" de fichiers au format WAV. Les entêtes sont donc manquantes pour obtenir des fichiers *.wav valides. On peut néanmoins coder un petit outil qui permet d'extraire ces morceaux et d'ajouter des entêtes pour reconstruire les 28 fichiers WAV utiles au meloquynthe. Vous pouvez récupérer ces 28 morceaux que j'ai reconstitués ICI. Le header de 44 bytes ajouté a cette forme là :

Le meloquynthe réalise en réalité une véritable composition et fait ses arrangements en choisissant un morceau de base et en ajoutant d'autres morceaux choisis. Il ajoute de temps en temps une parole choisie parmi "crackme", "tendinite", "F7", "Yo" et "meat". Voici la routine commentée qui réalise ce petit travail :
*********************** * * 7A3F74 * *********************** ebp = pointeur vers la structure du buffer joué pushad xor eax, eax cmp byte ptr [783BDDh], al EndingKeygen == 0 ? jne 7C2AEEh call 7A3FF7h ======================= = choix du morceau ======================= mov ebx, 5BFF0Bh cmp ebp, 7FE173h je @F mov ebx, 5BFF1Bh @@: push 4h call 6E7120h ------ Choix aléatoire entre 0 - 4 - 8 - 12 shl eax, 2h add eax, ebx push 1CB60h push dword ptr [eax] push ebp call 442E8Ch ------- copier le morceau choisi dans le buffer à jouer ======================================= = Ajouter un sample au morceau ======================================= mov ebx, 5BFF2Bh cmp ebp, 7FE173h je @F mov ebx, 5BFF3Bh @@: push 4h call 6E7120h ------ Choix aléatoire entre 0 - 4 - 8 - 12 shl eax, 2h add eax, ebx push 0E5B0h push dword ptr [eax] push ebp call 7A400Bh -------- Ajouter le morceau test dword ptr [77ADB5h], 2h jne label_47E961h ======================================= = Ajouter un sample au morceau ======================================= mov ebx, 5BFF4Bh cmp ebp, 7FE173h je @F mov ebx, 5BFF53h @@: push 2h call 6E7120h ---------- Choix aléatoire entre 0 - 4 shl eax, 2h add eax, ebx push 0E5B0h push dword ptr [eax] push ebp call 7A400Bh -------- Ajouter le morceau label_47E961h: xor eax, eax mov dword ptr [ebp+20h], eax push 2h call 6E7120h ---------- Choix aléatoire entre 0 - 4 or eax, eax je @F ======================================= = Ajouter une parole au morceau ======================================= push 5h call 6E7120h ---------- Choix aléatoire entre 0 - 4 - 8 - 12 - 16 shl eax, 2h mov eax, dword ptr [eax+5BFF5Bh] mov dword ptr [ebp+20h], eax ---------- sauver la parole jouée (pour vérification ultérieure) push 0E5B0h push eax push ebp call 7A400Bh -------- Ajouter le morceau @@: push ebp call 7C2980h ---------- waveOutWrite et [77ADB5]++ popad ret
Meloquynthe utilise deux buffers pour assurer une continuité dans la musique, c'est-à-dire pour que le device audio ne manque jamais de samples à jouer. Ceci permet également de varier la mélodie en choisissant des morceaux de base différents pour chaque buffer. Chaque buffer est géré par deux structures WAVEHDR dont les adresses sont 0x7FE173 et 0x5302E8. Ceci explique les lignes "cmp ebp, 7FE173h" dans le code précédent.
Copyright (C)- FRET (2007)