Rappelons nous le dernier post. On était arrivé à la conclusion que notre exécutable était packé (On a d’ailleur a cette occasion appris ce que c’était), mais on patinait dans la choucroute pour le dépacker de façons automatique. On va donc devoir sortir tout l’attirail pour tenter de le dépacker, J’ai nommé IDA pour désassembler et Ollydgb pour débugger et le vénérable LORD_PE pour dumper le processus depuis la ram. Pour une somme modique on peut tout faire avec IDA sur une vaste gamme de systèmes avec des plugins comme IDA_Dumper. Ha l’argent, c’est probablement la raison pour laquelle tout le monde se tourne vers OllyDbg (Enfin pour l’instant en 32Bits uniquement). Il nous faudra aussi d’autres petits tools. Nous y reviendrons plus tard.
Pour s’échauffer on va reprendre notre Hello World packé sous UPX de la partie 1 de cet article et on va se le dé-packer à la main.
Le cas simple “find the Jump”
L’idée est la suivante; Que va faire un packer simple, il va décompresser/déobfusquer le code réel, puis il vas sauter dedans pour l’exécuter. La première méthode dans le cas d’un packer pour newbies est de trouver oû est ce saut, d’y poser un breakpoint, de laisser faire le programme dépackant et quand on arrive à notre breakpoint on dumpe le programme et on crée un nouvel exécutable.
Ouvrons notre hello world upx dans IDA, dés-fois que cela serait simple. Et heureusement oui, UPX c’est bien pour l’apprentissage. Au premier abords ca fait peur. En y regardant plus près, il n’y a qu’une seule fonction. Et on trouve facilement le saut car IDA nous informe qu’au dela de ce saut, le il n’y a pas de programme exécutable (sp-analysis failed). Il n’y a pas encore de programme car le décompresseur n’a pas été lancé. Là c’est facile ida vois bien qu’il n’y a pas de code derrière le “JMP”, mais cela peut être moins évident avec un saut de type “call registre” ou un “ret”.
On se met sur la ligne de saut, on appuie sur “space” pour avoir le désassembler en vue liste et on note l’adresse du saut (Ici 0x004076EB).
Ensuite on ouvre OllyDbg, on charge l’exécutable. On met un point d’arrêt (F2) au même endroit.
Voila, le piège est en place, ne reste plus qu’a lancer. Quand Ollydbg rend la main on est devant notre saut, on step d’une instruction (F7) et on se retrouve devant notre code dépacké. On note la valeur d’Eip, ici 0x00401130, c’est là que le vrai programme commence.
Il ne reste qu’a dumper le programme, Pour cela on lance notre LORD_PE (Nota il y a plein de soft qui font la même chose, même des plugins OllyDbg le font (ex PE_Dumper) , choisis tes armes camarade).
On sélectionne notre exécutable et on le dump “Full”.
Là notre LORD_PE fait un truc étonnant dont il faut avoir conscience. Il “Rebuild Import Table”
Apparté, Rebuilding de l’IAT : Dans un executable PE, l’IAT de son nom complet Import Adress Table est un tableau de correspondances pour les fonctions importées (Un appel a une fonction de Kernel32.dll par exemple). Elle contient ce qui doit être importé ainsi que le nom des dites DLL (Pour les amis des ELF on peut considéré cela comme une genre de GOT). C’est indépendant de la version de windows, au démarrage la correspondance fonction vers l’adresse de la lib est remise en place. En plus de cela, notre Packer a nettoyé cette IAT avec les fonction que seul lui utilise, une fois dépacké, il faut donc remettre l’IAT en ordre. A la main, on oublie, mais avec le bon tools c’est simple.
Pour plus d’informations sur l’IAT je vous encourage à lire ce lien.
Mais ce n’est pas tout ! Il faut encore changer l’EP, l’Entry Point. C’est a dire l’emplacement en mémoire où va démarrer le programme. Il faut que l’on démarre là où on a finis le dépacking (rapellez vous l’addresse 0x00401130 que l’on a noté ).
On reprend LordPE, on prend l’option PE Editor, on édite l’exécutable que l’on vient de dumper. On change l’Entry Point. La base étant 0x0040000 il suffit de mettre 0x1130 dans la case.
Voila c’est finis, on a retrouvé notre executable “décompréssé”, Avec son code et ses strings en clair.
$ strings hello-upx-dumped.exe | grep hello hello World !
Malheureusement c’est rarement aussi facile qu’avec UPX.
La méthode ‘Regarde le Tas’
Si le packers commence par sauver la valeur de la pile ou fait un gros PUSHA (haaa ce formidable 32 Bits, il va me manquer). On peut aussi penser à mettre un break point hardware sur la position mémoire de la pile. En se disant, arrivé dans notre code packé, la pile sera revenue à la position initiale. Upx fait aussi comme cela, il est nickel comme exemple cet UPX. Pour faire ceci avec OllyDbg je lance mon programme, je passe le PUSHAD (f7) en step by step, je vais sur la valeur du registre ESP et je l’affiche dans le dump.
Je selection le word qui se trouve à l’emplacement de ma pile, et j’insère un breakpoint hardware en R/W.
Je lance le programme… Et hoo miracle il rend la main dans la fameuse boucle (que l’on connais maintenant) et qui va sauter sur le programme dépacké. il suffit de faire un peu pas à pas jusqu’au saut et on se retrouve là aussi en 0x00401130
A partir de là, Dump, IAT et EP..comme précédemment et c’est bon.
La technique de siY0ug
Mais dans la vraie vie c’est pas toujours aussi rose, même s’il n’y a pas d’anti debugger, on peut errer dans le code pendant des jours à chercher le saut. Par exemple notre facture.exe (Hash 17832c9a78b36c8a3133e2c2e24ebc3b9896763a chez Malware.lu). Il contient 131 fonctions, du code long comme le bras avec utilisation du co-processeur arithmétique, des champs complet de calls dans tous les sens, pas de pusha au début (par contre de la réservation de heap).. Bref… Glups.
Nous voici acculé. Pas de dépackeur tout fait, Trop alambiqué dans IDA. C’est à ce moment là que l’on pleure auprès d’un compagnon de café qui lâche la méthode suivante. Dixit le gaillard (Et c’est pas une tanche coté déssossage) on arrive à dépacker 3/4 des packeur de PE avec du malware dedans. L’idée est la suivante;
On va laisser le malware faire son un-package et on va breaker quand monsieur va faire un appel bien sentis à kernel32. Généralement quand il est en train d’injecter son code dans un autre process (c’est typique pour un malware voir ceci). Actuellement on a vu des packer qui se décompressent dans le même segment mémoire, mais certain se prennent du heap et y sortent carrément un autre executable.
Bon comme çà, ça parait simple, mais ne déconnez pas, faite ceci dans un environnement clos. Une bonne grosse sandbox; Cuckoo, Vmware, au pire un poste dédié.
Si vous ratez le breakpoint, vous êtes poncé, si le dépackeur contient aussi une saloperie, vous êtes poncé., Si le malware s’attache pas à un process comme vous le pensez, vous êtes poncé; Bref à chaque mauvaise manip, vous êtes poncé.
Reprenons Ollydbg. On ouvre l’exécutable, on demande la fenêtre “Executable Modules” (Alt+E) on sélectionne Kernel32, on fait “show names” (ctrl+N). Devant nous apparaît la fenêtre avec la liste de toutes les fonctions de Kernel32.dll
On cherche “WriteProcessMemory” (Il suffit de taper les premières lettres) et on met en place un breakpoint (F2).
Ensuite , confiant on lance le programme. Quand celui ci rend la main juste avant la fonction. Dans la stack on retrouve les paramètre d’appel à cette fonction, et si on fait “Follow in dump” de l’addresse du “Buffer” on se rend compte que dans ce buffer on est en présence d’un exécutable complet.
Là c’est différent, c’est un programme complet qu’il faudra extraire du dump mémoire. Soyez heureux, pas de IAT et d’EP à tripoter).
Si ça ne va pas avec cette fonction là … On peut essayer les fonctions suivantes
Maintenant extrayons notre exécutable de la mémoire. Sur le dump mémoire sous OllyDbg on fait Bouton droit de la souris => Backup => Create Backup. on se retrouve avec un dump mémoire complet de ce processus. Il suffit de trouver ou commence l’exécutable, et découper. et pour cela, on remercie Zbikowski !!! on cherche les MZ
$ grep -boa MZ backup_00140000.bin.bin 21792:MZ 27676:MZ 53930:MZ 114271:MZ 116204:MZ 197816:MZ 308269:MZ
Pas de bol il y en a un ou deux.. on les essaye plus précis, on a vu dans le dump mémoire que c’était MZ suivis de 0x90 0x00.
$ grep -boa MZ$'\x90\x00' backup_00140000.bin.bin 21792:MZ? 197816:MZ?
Ouf plus que 2 à essayer, Rappelons nous l’original packé était :
$ peentro.py facture.exe.malv Section Entropy Bytes Size MD5 Remark .code 5.65 6 8503 6d7c8f47c89843bafb8d8a0912730242 .text 6.49 7 9716 5e70fa9623805d69b6e548873c23d37f .rdata 1.75 2 188 96fdc428b50e1fa15bc8ce78d17a0525 .data 5.29 6 2796 cd00c1734804fcf8f776f8ce3431b129 .rsrc 2.94 3 4444 0b5ae9b8bfbc2ecfcb22c9578e800439
Extrayons le premier. (Note : la valeur pour le tail est +1 par rapport au grep.)
$ tail -c+21793 backup_00140000.bin.bin > facture1.unpacked $ peentro.py facture1.unpacked Section Entropy Bytes Size MD5 Remark .code 5.65 6 8503 6d7c8f47c89843bafb8d8a0912730242 .text 6.49 7 9716 5e70fa9623805d69b6e548873c23d37f .rdata 1.75 2 188 96fdc428b50e1fa15bc8ce78d17a0525 .data 5.29 6 2796 cd00c1734804fcf8f776f8ce3431b129 .rsrc 2.94 3 4444 0b5ae9b8bfbc2ecfcb22c9578e800439
Zut, on est retombé sur l’original.. Allons y pour le second candidat :
$ tail -c+197817 backup_00140000.bin.bin > facture2.unpacked $ peentro.py facture2.unpacked Section Entropy Bytes Size MD5 Remark .text 6.72 7 134836 97f0523f3fa408480c81e763935aedc1 .data 1.58 2 8276 841bea2427018da084fc8db606a058f8 .reloc 5.67 6 5778 d251d79e05658d968ee468f36f562f3e
Bingo un autre executable ! Allons y.. tentons un strings, somme nous victorieux ?? (Le grep étrange c’est pour n’attraper que les digram les plus utilisé en anglais, ca évite pas mal de strings random)
$ strings -10 facture2.unpacked | egrep 'th|he|in|er|an|re|nd|at' Content-Length Authorization Transfer-Encoding Accept-Encoding If-Modified-Since userenv.dll CreateEnvironmentBlock Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1) Content-Type: application/x-www-form-urlencoded ObtainUserAgentString cabinet.dll FCIFlushCabinet bepfLvtcklre v6!!2>6-7<=)+,.an"*&%/--gulh!y GdipCreateBitmapFromHBITMAP GdipGetImageEncodersSize GdipGetImageEncoders GdipSaveImageToStream CreateStreamOnHGlobal CreateCompatibleDC CreateCompatibleBitmap GetProcAddress NtCreateThread NtCreateUserProcess NtQueryInformationProcess RtlUserThreadStart LdrGetDllHandle GetModuleHandleW GetPrivateProfileStringW CreateFileW FlushFileBuffers GetPrivateProfileIntW GetProcAddress EnterCriticalSection CloseHandle WaitForSingleObject FileTimeToDosDateTime CreateMutexW FindFirstFileW SetEndOfFile FreeLibrary CreateProcessW SetFilePointerEx CreateDirectoryW GetCurrentThread VirtualFree VirtualQueryEx Thread32First VirtualFreeEx HeapCreate Thread32Next GetTimeZoneInformation GetTempPathW RemoveDirectoryW FindNextFileW CreateToolhelp32Snapshot GetVolumeNameForVolumeMountPointW GetFileInformationByHandle CreateThread ExpandEnvironmentStringsW SetThreadPriority GetCurrentThreadId CreateEventW GetThreadContext SetThreadContext CreateFileMappingW CreateRemoteThread GetUserDefaultUILanguage TerminateProcess GetCommandLineW GetComputerNameW GetVersionExW DuplicateHandle GetCurrentProcessId GetNativeSystemInfo DispatchMessageW SendMessageTimeoutW SetWindowLongW CharUpperW CharLowerA GetWindowLongW TranslateMessage WindowFromPoint GetTopWindow ExitWindowsEx CharLowerBuffA OpenWindowStationW GetUserObjectInformationW SetThreadDesktop GetProcessWindowStation CreateWindowStationW CloseWindowStation GetThreadDesktop SetProcessWindowStation CreateDesktopW RegisterClassA DefWindowProcW CallWindowProcW CallWindowProcA RegisterClassW ReleaseCapture DefWindowProcA GetClipboardData RegisterClassExW GetCapture GetUpdateRect BeginPaint SetCapture GetWindowDC RegisterClassExA GetUpdateRgn GetShellWindow GetWindowThreadProcessId SendMessageW PostThreadMessageW GetMenuState SystemParametersInfoW MenuItemFromPoint SetKeyboardState RegisterWindowMessageW GetKeyboardState MapWindowPoints SetWindowPos GetWindowInfo GetWindowRect PrintWindow IntersectRect CharLowerW GetSidSubAuthority CryptAcquireContextW OpenThreadToken GetSidSubAuthorityCount GetTokenInformation RegCreateKeyExW RegQueryValueExW CreateProcessAsUserW CryptCreateHash ConvertStringSecurityDescriptorToSecurityDescriptorW CryptHashData InitiateSystemShutdownExW GetLengthSid ConvertSidToStringSidW wvnsprintfW PathIsDirectoryW PathFindFileNameW PathRemoveFileSpecW PathAddBackslashW PathSkipRootW PathCombineW PathAddExtensionW PathUnquoteSpacesW PathRemoveBackslashW PathMatchSpecW wvnsprintfA PathIsURLW PathQuoteSpacesW PathRenameExtensionW SHGetFolderPathW ShellExecuteW CommandLineToArgvW GetUserNameExW CoCreateInstance CoUninitialize CLSIDFromString StringFromGUID2 CreateCompatibleBitmap CreateDIBSection CreateCompatibleDC freeaddrinfo getaddrinfo WSAAddressToStringW CryptUnprotectData PFXImportCertStore CertDeleteCertificateFromStore CertOpenSystemStoreW CertCloseStore CertEnumCertificatesInStore CertDuplicateCertificateContext PFXExportCertStoreEx HttpQueryInfoA InternetConnectA InternetQueryOptionW InternetCrackUrlA InternetReadFile InternetSetOptionA HttpSendRequestA InternetOpenA InternetCloseHandle InternetQueryOptionA HttpSendRequestExA HttpSendRequestExW InternetQueryDataAvailable InternetReadFileExA HttpSendRequestW GetUrlCacheEntryInfoW InternetSetStatusCallbackW HttpAddRequestHeadersW HttpAddRequestHeadersA NetUserGetInfo NetApiBufferFree NetUserEnum
Et en unicode, y a t’il des strings ?
$ strings -el -10 facture2.unpacked kernel32.dll S:(ML;;NRNWNX;;;LW) SeSecurityPrivilege S:(ML;CIOI;NRNWNX;;;LW) SeShutdownPrivilege SeTcbPrivilege SOFTWARE\Microsoft cGlobal\%08X%08X%08X SysListView32 CiceroUIWndFrame ConsoleWindowClass
Pas mal, quel sont maintenant les dll utilisées ?
$ objdump -x facture2.unpacked | grep "DLL Name" DLL Name: KERNEL32.dll DLL Name: USER32.dll DLL Name: ADVAPI32.dll DLL Name: SHLWAPI.dll DLL Name: SHELL32.dll DLL Name: Secur32.dll DLL Name: ole32.dll DLL Name: GDI32.dll DLL Name: WS2_32.dll DLL Name: CRYPT32.dll DLL Name: WININET.dll DLL Name: OLEAUT32.dll DLL Name: NETAPI32.dll
Ho le support du réseau est arrivé ;) et plein d’autres DLLs qui laissent a penser que le malware est bien juteux
Et bien voila, c’est une victoire il semblerai la bête soit dé-packée. Ne reste plus qu’a l’analyser… Une broutille ;)
Mais ca c’est pour une autre fois.