0. Sommaire
1. Pré-requis
2. Qu'est qu'une TLS
2.1 Comment ça marche ?
3. L'intérêt
4. Exemple !
4.1 Mise en Oeuvre
5. Connaître son ennemi
6. Quelques Liens
1. Pré-Requis
Pour comprendre ce papier vous aurez besoin des éléments suivants :
- Une bonne connaissance de programmation générale (structure, pointeur, etc)
- Un langage de programmation
- Comprendre un peu l'ASM pour le poc mais ça reste vraiment basique
- Une compétence approfondie sur les systèmes d'exploitation Microsoft
- Une base sur le format d'exécutable Microsoft (PE / COFF)
- Savoir se servir un minimum des outils d'éditions des headers PE (LordPE, StudPE, etc)
2. Qu'est qu'une TLS
TLS est une abréviation de Thread Local Storage. C'est une fonctionnalité système (quelque soit l'OS) permettant de
- Stocker des données uniques à chaque thread d'un programme. C'est le gros avantage de la technologie, permettant ainsi de s'affranchir des éléments de synchronisation lors de cross-threading sans utilisation intempestive d'allocation sur la pile, notamment du point de vue du traitement de variable statique dont l'espace mémoire est partagé par l'ensemble d'un processus donné.
- Exécuter des routines d'initialisation / nettoyage pour chaque thread d'un programme.
2.1 Comment ça marche ?- Ce mécanisme peut être utilisé volontairement dans le developpement d'un programme via un ensemble d'API Windows documentée, le TLS est à ce moment la appelé à volonté par les codeurs via les fonctions TlsGetValue, TlsSetValue, etc, un lien est présent en fin de doc pour les détails
. - Les compilateurs Borland (sauf erreur), quant à eux, vont systématiquement créer une section de TLS pour le fonctionnement des binaires qu'ils compilent en mettant les headers PE à contribution. Ce mode de fonctionnement représente une alternative à la première solution, gérée par le système d'exploitation "itself" via NTDLL.DLL lors du chargement des threads.
Cette dernière option, c'est bien celle qui nous intéresse (vous verrez pourquoi un peu plus bas), nous détournerons bien sur son usage initial et vous comprendrez rapidement que c'est une arme redoutable, notamment sur le sujet du developpement de code malveillant. Le TLS est essentiellement composé de deux éléments :
- Une structure IMAGE_TLS_DIRECTORY généralement isolé dans une section dédiée (lorsqu'elle est gérée par le compilateur)
- Une entrée dans les IMAGE_DATA_DIRECTORY des headers PE, à l'index 9, appelée TLSTable où seront spécifiées l'adresse et la taille de l'IMAGE_TLS_DIRECTORY
Voyons la composition de la structure IMAGE_TLS_DIRECTORY, ainsi qu'une brève explication sur l'utilité de chacune des entrées :
struct IMAGE_TLS_DIRECTORY
{
DWORD StartAddressOfRawData ;adresse de départ pour le stockage des données TLS pour chaque thread
DWORD EndAddressOfRawData ;adresse de fin pour le stockage des données TLS pour chaque thread
DWORD AddressOfIndex ;Pointeur vers le handle du TLS
DWORD AddressOfCallBacks ;Le plus interessant, pointeur vers un tableau d'addresse de callback (PIMAGE_TLS_CALLBACK), ces fonctions seront appelées à la création/destruction de chaque thread
DWORD SizeOfZeroFill ;pas utilisé (spec PE/COFF)
DWORD Characteristics ;pas utilisé (spec PE/COFF)
}
Notre objectif, vous l'avez bien compris, est de créer un TLS artisanal, une IMAGE_TLS_DIRECTORY dont l'entrée AddressOfCallBacks pointera sur une (des) fonction(s) destinée(s) à servir notre sombre dessein. En modifiant les headers pour sa prise en charge, nous allons donc pouvoir exécuter du code d'initialisation de thread. Une question subsiste, pourquoi ?
3. L'intérêt
Un fait, et une entrée de la structure IMAGE_TLS_DIRECTORY; A savoir que l'ensemble des fonctions pointées par l'entrée AddressOfCallBacks seront exécutées pour initialisation de CHAQUE thread, et qui dit chaque thread dit AUSSI la thread principale du programme. Qu'est ce que cela signifie ? Et bien que le code contenu sera exécuté AVANT que le programme en lui même ne soit lancé.
Pour donner un exemple concret, imaginez un virus dont la charge de reproduction serait démarrée via une TLS ? Quelle serait les conséquences ? Un antivirus qui tenterait d'émuler le code commencerait par gerer le tout à partir de l'EntryPoint ... Et la c'est dramatique. L'entryPoint n'est pas modifé, le corps du virus s'est éxécuté avant, l'émulateur ne voit RIEN.
Une autre conséquence, c'est le reverse engineering, et la vous y reflechirez deux fois avant d'ouvrir un crackme :-) le code sera exécuté AVANT le chargement du debugger. Vous verrez dans l'exemple que nous exécuterons du code avant que Ollydbg n'ai desassemblé le contenu. Si c'est un virus, vous êtes DEJA infecté. J'ajoute également une intéressante possibilité du point de vue anti-debug. Finalement rien ne nous empecherait de corrompre le code si on detecte un debugger (et pour ça il y a vraiment une multitude de techniques).
J'insiste bien sur ce point, cette méthode est térrifiante puisque tout code présent dans une TLS et pointé par PIMAGE_TLS_CALLBACK sera exécuté AVANT même la toute première instruction du code du processus cible.
4. Exemple !
Notre POC est codé en ASM (FASM). C'est un simple Hello World dont le corps à été modifié par un vilain hacker via une TLS. J'ai sévèrement commenté le code pour que tout le monde puisse être à même de comprendre l'exemple.
format PE GUI 4.0
entry start
include 'win32a.inc'
;section code
section '.text' code readable executable writeable
;affiche un simple MessageBox et on quitte le programme
start:
push 0
push szCaption
push szOriginalMessage ;on voit bien ici que le message est Hello World !
push 0
call [MessageBox]
ret
;ici c'est le code que nous allons exécuter en TLS
TLS_Function:
pushad ;sauvegarde du contexte initial, on sait jamais
mov edi, start + 8 ;on pointe sur l'addresse de l'argument message dans l'appel de MessageBox
mov esi, szBetterMessage ;on pointe sur notre mod
mov dword[edi], esi ;on modifie !
popad ;on a sauvegarder tout le contexte d'exécution initial, on rétablit
ret ;on quitte proprement
IMAGE_TLS_DIRECTORY:
StartAddressOfRawData dd TLS_RawData ;adresse de départ pour le stockage des données TLS pour chaque thread
EndAddressOfRawData dd TLS_RawData ;adresse de fin pour le stockage des données TLS pour chaque thread
;on en a pas besoin dans notre POC
AddressOfIndex dd TLS_Handle ;Pointeur vers le handle du TLS
AddressOfCallBacks dd p1 ;Le plus interessant, pointeur vers un tableau d'addresse de callback,
;ces fonctions seront appelées à la création de chaque thread
SizeOfZeroFill dd 0 ;pas utilisé (spec PE/COFF)
Characteristics dd 0 ;pas utilisé (spec PE/COFF)
PIMAGE_TLS_CALLBACK:
p1 dd TLS_Function ;pointeur sur l'adresse de la fonction à utiliser
p2 dd ? ;on fini le tableau par une entrée vide
TLS_RawData dd ? ;voir plus haut
TLS_Handle dd ? ;voir plus haut
;section data
section '.data' data readable writeable
szCaption db "Hello !",0
szOriginalMessage db "Hello World",0
szBetterMessage db "Hello Newbie Contest World",0
;imports
section '.idata' import data readable writeable
library user32, "user32.dll"
import user32, MessageBox, 'MessageBoxA'
4.1 Mise en oeuvreAlors la c'est un peu spécial, en effet modifier les headers à la volée m'aurait obligé à coder un POC plus conséquent ou à carrément coder le PE dans le source, ça aurait été plus complexe à comprendre et beaucoup plus long à developper. L'impact ? vous allez mettre un peu les main dans le camboui. Pour cela vous devrez modifider les headers de l'exécutable pour spécifier la TLS dans le IMAGE_TLS_DIRECTORY. Pour la réalisation avec LordPE:
a. Click sur "
PE Editor" => aller chercher le code compilé)
b. Click sur le bouton "
Directory"
c. Dans la fenêtre présentée, chercher l'entrée "
TlsTable".
d. Dans le champs "
RVA" entrez : 00001024 (vous pouvez determinez vous même la RVA avec Ollydbg, c'est après le RET qui suit le POPAD

- N' oubliez pas de soustraire l'image base.)
e. Dans le chaps "
Size" 00000018, c'est la taille de IMAGE_TLS_DIRECTORY
Note : vous pouvez tester l'exécutable avant et après la modification pour bien vous rendre compte de la nature perverse de cette technologie !Note, le retour : Propriété intéressante, vous constaterez si vous en avez la possibilité technique que le lancement d'un programme implémentant la technologie présentée n'exécutera PAS le code présent dans la TLS s'il est lancé à partir du réseau (d'un partage donc). Finalement vous vous retrouvez avec un exécutable dont le traitement est modifié selon le contexte (en tout cas sous Windows 7).L'exemple fourni est très simple, c'est vrai. Mais d'autres implémentation permette entre autre de carrément modifier les AddressOfCallBacks à la volée pour traiter des conditions ! Vous pouvez donc "choisir" ce que vous exécuterez, tout ça ... avant que le programme ne démarre lui même. C'est très violent.
5. Connaître son ennemi
De nombreux worms et autres codes malveillants utilisent cette technologie (le début des implémentations massives démarre en 2004, elle est connue depuis les années 2000...). Ce qui donne, en 2011, une chance pour un antivirus de qualité de facilement detecter ce trick.
Pour ce qui est du repérage manuel (en dehors de la "fécondation in-compilo"), il faut configurer son debugger. Les plus anciens ne feront pas l'affaire. Toutefois, pour le plus connu d'entre eux, Ollydbg il reste une oasis : il faut regarder du coté des options de debugging. Par défaut l'outil est configuré pour "breaker" sur l'entrypoint du programme, matérialisé par l'option "
WinMain (if location is known)". Comme dit précédemment, c'est bien le loader de Windows (au travers d'NTDLL) qui gère l'exploitation de la TLS pour l'implémentation au travers des headers, ce n'est donc pas l'exécutable en lui même, mais votre système. Remplacer cette option par "
System breakpoint" suffira donc faire en sorte de s'affranchir du risque tout de mêe conséquent de s'infecter en debuggant un programme, sans même s'en rendre compte !
6. Quelques liens