gdb disas main

Flux RSS


Derniers billets blog


























Version 3.0beta4-tuxfamily

Mais en fait, non ! - Le blog de Sylvain Sarméjeanne

News [malware] TLS strikes back

S'il vous arrive d'analyser du malware de temps à autres, il vous est peut-être déjà arrivé de faire ceci :

  • 1. On recupère le binaire :
wget_ie http://site/vx.exe
  • 2. On fait un file pour voir vite fait de quoi il s'agit :
file vx.exe
vx.exe: MS-DOS executable PE  for MS Windows (console) Intel 80386 32-bit, UPX compressed
  • 3. "Tiens, il est compressé en UPX, on tente de le décompresser direct, on sait jamais :"
upx -d vx.exe
[...]
Unpacked 1 file.
  • 4. "Youhou, comment dépacker un malware en 2 secondes ! Le gars qui a fait ça est vraiment naze. Je vais maintenant travailler directement sur la version unpackée."

Oui, mais en fait non. Le raisonnement ci-dessus part du principe suivant : si upx -d marche, alors il s'agissait bien d'un binaire compressé avec UPX et le binaire unpacké aura le même comportement que le binaire packé. Sauf que qu'est-ce qui vous dit que le binaire UPX n'a pas été bidouillé ? Par exemple en ajoutant du code qui s'exécute avant le point d'entrée pour modifier des octets en mémoire juste avant que l'algorithme de décompression ne passe par là ? Par exemple en ajoutant un callback TLS ?

L'idée de ce billet blog m'est venue lorsque j'ai travaillé sur le level 2 du Khallenge 2008 et que je me suis rendu compte que le callback TLS du binaire original (celui qui pose les bases d'une détection de debugger réalisée un peu plus bas dans le code) n'était pas conservé par la décompression UPX. Plutôt marrant.

Bon, je ne vais pas m'étendre outre mesure sur le mécanisme de Thread Local Storage sous Windows. Il s'agit d'une fonctionnalité du loader permettant d'exécuter du code avant le point d'entrée défini dans l'en-tête PE. C'est par exemple utilisé par les malware pour déchiffrer le code situé à l'entry point (cf analyse de PE GRUM par N. Brulez dans MISC 31). Les structures sont les suivantes :

  • dans l'en-tête optionnelle PE, structure IMAGE_DIRECTORY_ENTRY_TLS non vide
  • son champ VirtualAddress contient la VA d'une structure IMAGE_TLS_DIRECTORY
  • elle-même contient un champ AddressOfCallBacks contenant la VA vers un tableau de pointeurs de fonction

Un bon schéma valant mieux qu'on long discours :

IMAGE_DIRECTORY_ENTRY_TLS, IMAGE_TLS_DIRECTORY

On peut ajouter un callback TLS à un binaire UPX pour par exemple :

  • exécuter du code malicieux dans la version compressée mais qui ne sera pas exécuté dans la version décompressée (classique)
  • exécuter du code pour modifier certains octets en mémoire afin de changer le comportement entre les 2 versions (plus rigolo, c'est ce qu'on va faire ici).

On part d'un programme très simple qui affiche une message box :

#include <windows.h>

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MessageBox(NULL, "good", "tls", MB_OK);
    return 0;
}

On compile et on compresse en UPX. Toute la suite se fait dans un éditeur hexa. Première étape : trouver un endroit où stocker nos différentes structures. En parcourant vite fait le binaire, on trouve de la place à l'offset cf0 :

TLS callbacks

On y place notre structure IMAGE_TLS_DIRECTORY :

  • cf0 : StartAddressOfRawData = cf0 (offset) -> 4080f0 (VA)
  • cf4 : EndAddressOfRawData = cf0 (offset) -> 4080f0 (VA)
  • cf8 : AddressOfIndex pointe vers NULL à d00 (offset) -> 408100 (VA)
  • cfc : AddressOfCallBacks = d08 (offset) -> 408108 (VA)
  • d00 : SizeOfZeroFill = 0
  • d04 : Characteristics = 0

Le champ AddressOfCallBacks pointe vers l'offset d08 où on place maintenant notre tableau de pointeurs vers callbacks. On n'en a besoin que d'un seul :

  • d08 : pointeur vers notre callback = d10 (offset) -> 408110 (VA)

Que voulons-nous faire dans ce callback ? On va prendre l'exemple très simple de la modification de la chaine "good" qui se situe à l'offset 7ae, soit à l'adresse virtuelle 4075AE. Un simple MOV sur le contenu présent à cette adresse suffit (oui, je sais c'est particulièrement peu élégant comme méthode, mais c'est juste pour l'exemple...) :

55                         PUSH EBP
89 E5                      MOV EBP, ESP
36 C705 AE754000 6576696C  MOV DWORD PTR SS:[004075AE], 6C697665
b8 0000 0000               MOV EAX, 0
C9                         LEAVE
C3                         RET

En résumé, on obtient ceci :

TLS callbacks

On peut ainsi modifier tout octet dans la version UPXée du binaire (attention, pour patcher certaines zones, il faut aussi patcher le checksum UPX), de façon à obtenir un binaire décompressé en mémoire différent de celui décompressé en statique avec upx -d.

Tout est en place, il ne reste plus qu'à modifier l'en-tête optionnelle PE pour rendre la structure IMAGE_DIRECTORY_ENTRY_TLS non nulle et la faire pointer sur la structure définie plus haut :

  • 140 : VirtualAddress = cf0 (offset) -> 4080F0 (VA)
  • 144 : Size = 18
IMAGE_DIRECTORY_TLS

On obtient ainsi le binaire à télécharger en annexe. Si on va un peu vite à l'analyse et qu'on exécute la version décompressée avec upx -d, il affiche le message défini dans le source, à savoir "good" :

En revanche, quand on exécute le binaire UPX lui-même sous XP, surprise :

Archive : sylv1_tls.zip

Posté par sylv1 le 04/10/2008 à 17:52:07
3 commentaires

Ce billet est publié sous la licence CC-BY-SA.

[ Site créé par Sylvain Sarméjeanne ]
Cette page a été générée par mes scripts en 0.052 secondes :)
[Valid XHTML 1.1!] [Valid CSS!] [[Valid RSS]]