Archive for juillet, 2007
Toujours, à suivre les dernières recherches dans le domaine, je m’aperçois souvent que les chinois nous mettent une grosse raclée dans la gueule. Dernièrement je suis tombé sur post décrivant une nouvelle technique permettant de caché une clé dans le registre. Après avoir passé quelques heures à reverser et étudier le POC, je vais vous décrire (pour votre plus grand plaisir) le fonctionnement de cette nouvelle technique.
D’abord je tiens à préciser que j’utiliserais les termes anglais pour désigner les divers composants du registre, tout simplement pour qu’il soit plus simple de reconnaître le rôle des API (et puis fu le français).
Il faut savoir que le registre est composé d’un ensemble de hives, ceux ci sont définit dans la clé HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist :
C:\\Drivers\\HideKey>reg query HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\hivelist
! REG.EXE VERSION 3.0
HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\hivelist
\\REGISTRY\\MACHINE\\HARDWARE REG_SZ
\\REGISTRY\\MACHINE\\SECURITY REG_SZ \\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\SECURITY
\\REGISTRY\\MACHINE\\SOFTWARE REG_SZ \\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\software
\\REGISTRY\\MACHINE\\SYSTEM REG_SZ \\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\system
\\REGISTRY\\USER\\.DEFAULT REG_SZ \\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\default
\\REGISTRY\\MACHINE\\SAM REG_SZ \\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\SAM
\\REGISTRY\\USER\\S-1-5-20 REG_SZ \\Device\\HarddiskVolume1\\Documents and Settings\\NetworkService\\NTUSER.DAT
\\REGISTRY\\USER\\S-1-5-20_Classes REG_SZ \\Device\\HarddiskVolume1\\Documents and Settings\\NetworkService\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat
\\REGISTRY\\USER\\S-1-5-21-448539723-2025429265-839522115-1003 REG_SZ \\Device\\HarddiskVolume1\\Documents and Settings\\Moi\\NTUSER.DAT
\\REGISTRY\\USER\\S-1-5-21-448539723-2025429265-839522115-1003_Classes REG_SZ \\Device\\HarddiskVolume1\\Documents and Settings\\Moi\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat
Ces hives sont situées sur le disque dans le dossier C:\WINDOWS\system32\config\ comme l’indique le dump au dessus. En fait le registre sous Windows n’est en fait qu’un ensemble de fichiers qui contiennent des structures définies de manière non documentées et dont le noyau manipule les valeurs en mappant ces derniers en mémoire. Une hive est composée de Keys de Subkeys puis de Data qui contiennent les Values.
Bon, je vais aller vite pour la suite. On veut étudier le fonctionnement de ZwEnumerateKey, cette fonction permet d’énumérer toutes les Subkeys d’une Key, son prototype est le suivant :
NTSTATUS
ZwEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);
Le KeyHandle peut être obtenu avec un ZwCreateKey ou ZwOpenKey, en incrémentant le paramètre Index on énumère les différentes SubKeys jusqu’à que la fonction nous renvoie un STATUS_NO_MORE_ENTRIES. Pour la suite je me baserais sur la call stack suivante :
kd> kv
ChildEBP RetAddr Args to Child
f944cc70 8056f016 e1338008 007f5a50 e1091fb8 nt!HvpGetCellMapped (FPO: [Non-Fpo])
f944ccb4 8056f17d e159c1e8 00000001 00000000 nt!CmEnumerateKey+0x65 (FPO: [Non-Fpo])
f944cd44 804de7ec 00000070 00000001 00000000 nt!NtEnumerateKey+0x1ea (FPO: [Non-Fpo])
f944cd44 7c91eb94 00000070 00000001 00000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f944cd64)
0007f454 7c91d958 77dada0c 00000070 00000001 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0007f458 77dada0c 00000070 00000001 00000000 ntdll!NtEnumerateKey+0xc (FPO: [6,0,0])
WARNING: Stack unwind information not available. Following frames may be wrong.
0007f5c0 77dad6c1 00000070 00000001 0007f5ec ADVAPI32!RegNotifyChangeKeyValue+0xa6
0007f5f8 010088ce 00000070 00000001 0007f628 ADVAPI32!RegEnumKeyW+0x78
0007f82c 0100896b 0007011e 000add90 0000004e regedit!KeyTree_ExpandBranch+0xe3 (FPO: [Non-Fpo])
0007fa78 01005221 00070106 0007fc48 0007faa0 regedit!RegEdit_OnKeyTreeItemExpanding+0x62 (FPO: [Non-Fpo])
0007fa88 0100674c 00070106 00000001 0007fc48 regedit!RegEdit_OnNotify+0xa7 (FPO: [Non-Fpo])
0007faa0 7e398734 00070106 0000004e 00000001 regedit!RegEditWndProc+0x9c (FPO: [Non-Fpo])
0007facc 7e398816 010066b0 00070106 0000004e USER32!InternalCallWinProc+0x28
0007fb34 7e39b89b 00000000 010066b0 00070106 USER32!UserCallWinProcCheckWow+0x150 (FPO: [Non-Fpo])
0007fb70 7e39b903 0054bf18 0054be20 00000001 USER32!SendMessageWorker+0x4a5 (FPO: [Non-Fpo])
0007fb90 773aaff1 00070106 0000004e 00000001 USER32!SendMessageW+0x7f (FPO: [Non-Fpo])
0007fc28 773c87e9 000a8c70 fffffe3a 0007fc48 COMCTL32!CCSendNotify+0xc20 (FPO: [Non-Fpo])
0007fcb8 773c9833 000a8c70 fffffe3a 000add90 COMCTL32!TV_SendItemExpand+0x83 (FPO: [Non-Fpo])
0007fd0c 773ca527 000a8c70 00000002 00000002 COMCTL32!TV_Expand+0x90 (FPO: [Non-Fpo])
0007fd80 773cab7f 000a8c70 00000201 00000001 COMCTL32!TV_ButtonDown+0x240 (FPO: [Non-Fpo])
Commençons par regarder la valeur du Handle passé à NtEnumerateKey :
kd> !handle 70
processor number 0, process 80d6d850
PROCESS 80d6d850 SessionId: 0 Cid: 00e0 Peb: 7ffdf000 ParentCid: 0490
DirBase: 01492000 ObjectTable: e16508e0 HandleCount: 33.
Image: regedit.exe
Handle table at e105f000 with 33 Entries in use
0070: Object: e1091fb8 GrantedAccess: 00000008 Entry: e105f0e0
Object: e1091fb8 Type: (80e96040) Key
ObjectHeader: e1091fa0 (old version)
HandleCount: 1 PointerCount: 2
Directory Object: 00000000 Name:
\\REGISTRY\\MACHINE\\SOFTWARE\\MICROSOFT\\WINDOWS
La Key reférencé ici est donc HKEY_LOCAL_MACHINE\SYSTEM\SOFTWARE\MICROSOFT\WINDOWS, on souhaite obtenir des infos sur la SubKey à l’indice 1. Pour cela NtEnumerateKey va faire appel aux API du Configuration Manager (CM), plus précisément à CmEnumerateKey dont le prototype est le suivant :
NTSTATUS
CmEnumerateKey(
IN PCM_KEY_CONTROL_BLOCK KeyControlBlock,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
IN PVOID KeyInformation,
IN ULONG Length,
IN PULONG ResultLength
)
L’argument intéressant est le KeyControlBlock :
kd> dt nt!_CM_KEY_CONTROL_BLOCK e159c1e8
+0x000 RefCount : 4
+0x004 ExtFlags : 0y00000000 (0)
+0x004 PrivateAlloc : 0y1
+0x004 Delete : 0y0
+0x004 DelayedCloseIndex : 0y100000000000 (0x800)
+0x004 TotalLevels : 0y0000000101 (0x5)
+0x008 KeyHash : _CM_KEY_HASH
+0x008 ConvKey : 0x63e9570a
+0x00c NextHash : (null)
+0x010 KeyHive : 0xe1338008 _HHIVE
+0x014 KeyCell : 0x7590f8
+0x018 ParentKcb : 0xe101a648 _CM_KEY_CONTROL_BLOCK
+0x01c NameBlock : 0xe1680aa0 _CM_NAME_CONTROL_BLOCK
+0x020 CachedSecurity : 0xe13583e8 _CM_KEY_SECURITY_CACHE
+0x024 ValueCache : _CACHED_CHILD_LIST
+0x02c IndexHint : 0x00000003 _CM_INDEX_HINT_BLOCK
+0x02c HashKey : 3
+0x02c SubKeyCount : 3
+0x030 KeyBodyListHead : _LIST_ENTRY [ 0xe1091ff4 - 0xe1091ff4 ]
+0x030 FreeListEntry : _LIST_ENTRY [ 0xe1091ff4 - 0xe1091ff4 ]
+0x038 KcbLastWriteTime : _LARGE_INTEGER 0x1c7674a`5d986830
+0x040 KcbMaxNameLen : 0x1c
+0x042 KcbMaxValueNameLen : 0
+0x044 KcbMaxValueDataLen : 0
+0x048 KcbUserFlags : 0y0000
+0x048 KcbVirtControlFlags : 0y0000
+0x048 KcbDebug : 0y00000000 (0)
+0x048 Flags : 0y0000000000100000 (0x20)
Allay hop, sous l’influence de chocapics on fait un peu n’importe quoi, alors dumpons quelques structures :
kd> dt nt!_CM_NAME_CONTROL_BLOCK 0xe1680aa0
+0x000 Compressed : 0x1 ''
+0x002 RefCount : 5
+0x004 NameHash : _CM_NAME_HASH
+0x004 ConvKey : 0x2f7de68b
+0x008 NextHash : (null)
+0x00c NameLength : 7
+0x00e Name : [1] 0x4957
kd> db 0xe1680aa0+E l 7
e1680aae 57 49 4e 44 4f 57 53 WINDOWS
Juste par curiosité, faisons pareil avec le champ ParentKcb :
kd> dt nt!_CM_KEY_CONTROL_BLOCK 0xe101a648
+0x000 RefCount : 0x60
+0x004 ExtFlags : 0y00000000 (0)
+0x004 PrivateAlloc : 0y1
+0x004 Delete : 0y0
+0x004 DelayedCloseIndex : 0y100000000000 (0x800)
+0x004 TotalLevels : 0y0000000100 (0x4)
+0x008 KeyHash : _CM_KEY_HASH
+0x008 ConvKey : 0xdf3378bb
+0x00c NextHash : (null)
+0x010 KeyHive : 0xe1338008 _HHIVE
+0x014 KeyCell : 0x6771c0
+0x018 ParentKcb : 0xe101aa08 _CM_KEY_CONTROL_BLOCK
+0x01c NameBlock : 0xe147c988 _CM_NAME_CONTROL_BLOCK
+0x020 CachedSecurity : 0xe13583e8 _CM_KEY_SECURITY_CACHE
+0x024 ValueCache : _CACHED_CHILD_LIST
+0x02c IndexHint : 0x0000005f _CM_INDEX_HINT_BLOCK
+0x02c HashKey : 0x5f
+0x02c SubKeyCount : 0x5f
+0x030 KeyBodyListHead : _LIST_ENTRY [ 0xe101a678 - 0xe101a678 ]
+0x030 FreeListEntry : _LIST_ENTRY [ 0xe101a678 - 0xe101a678 ]
+0x038 KcbLastWriteTime : _LARGE_INTEGER 0x1c7cd6d`b11231e0
+0x040 KcbMaxNameLen : 0x38
+0x042 KcbMaxValueNameLen : 0
+0x044 KcbMaxValueDataLen : 0
+0x048 KcbUserFlags : 0y0000
+0x048 KcbVirtControlFlags : 0y0000
+0x048 KcbDebug : 0y00000000 (0)
+0x048 Flags : 0y0000000000100000 (0x20)
kd> dt nt!_CM_NAME_CONTROL_BLOCK 0xe147c988
+0x000 Compressed : 0x1 ''
+0x002 RefCount : 6
+0x004 NameHash : _CM_NAME_HASH
+0x004 ConvKey : 0x7f00cd26
+0x008 NextHash : (null)
+0x00c NameLength : 9
+0x00e Name : [1] 0x494d
kd> db 0xe147c988+e l 9
e147c996 4d 49 43 52 4f 53 4f 46-54 MICROSOFT
Par la suite CmEnumerateKey appelle HvpGetCellMapped, son prototype est :
struct _CELL_DATA *
HvpGetCellMapped(
PHHIVE Hive,
HCELL_INDEX Cell
)
Le premier paramètre est donné par CmEnumerateKey en lisant le membre KeyHive à l’offset 0×10 de la structure CM_KEY_CONTROL_BLOCK. Le second paramètre est passé par CmEnumerateKey en prennant le champ KeyCell.
kd> dt nt!_HHIVE 0xe1338008
+0x000 Signature : 0xbee0bee0
+0x004 GetCellRoutine : 0x8056e48b _CELL_DATA* nt!HvpGetCellMapped+0
+0x008 ReleaseCellRoutine : 0x8056e5c3 void nt!HvpReleaseCellMapped+0
+0x00c Allocate : 0x8058c2f0 void* nt!CmpAllocate+0
+0x010 Free : 0x8058c337 void nt!CmpFree+0
+0x014 FileSetSize : 0x8058ce45 unsigned char nt!CmpFileSetSize+0
+0x018 FileWrite : 0x80594fac unsigned char nt!CmpFileWrite+0
+0x01c FileRead : 0x805acd92 unsigned char nt!CmpFileRead+0
+0x020 FileFlush : 0x80594e9e unsigned char nt!CmpFileFlush+0
+0x024 BaseBlock : 0xe1339000 _HBASE_BLOCK
+0x028 DirtyVector : _RTL_BITMAP
+0x030 DirtyCount : 0
+0x034 DirtyAlloc : 0x94c
+0x038 RealWrites : 0x1 ''
+0x03c Cluster : 1
+0x040 Flat : 0 ''
+0x041 ReadOnly : 0 ''
+0x042 Log : 0x1 ''
+0x044 HiveFlags : 0
+0x048 LogSize : 0x400
+0x04c RefreshCount : 0
+0x050 StorageTypeCount : 2
+0x054 Version : 5
+0x058 Storage : [2] _DUAL
La fonction HvpGetCellMapped renvoie une des structures faisant partie de l’union suivante :
typedef struct _CELL_DATA {
union _u {
CM_KEY_NODE KeyNode;
CM_KEY_VALUE KeyValue;
CM_KEY_SECURITY KeySecurity; // Variable security descriptor length
CM_KEY_INDEX KeyIndex; // Variable sized structure
CM_BIG_DATA ValueData; // This is only for big cells; a list of cells
// all of the length CM_KEY_VALUE_BIG
HCELL_INDEX KeyList[1]; // Variable sized array
WCHAR KeyString[1]; // Variable sized array
} u;
} CELL_DATA, *PCELL_DATA;
Dans mon cas, HvpGetCellMapped me renvoie une structure CM_KEY_NODE, indiquant que nous avons une SubKey :
kd> gu
nt!CmEnumerateKey+0x65:
8056f016 85c0 test eax,eax
kd> dt nt!_CM_KEY_NODE @eax
+0x000 Signature : 0x6b6e
+0x002 Flags : 0x20
+0x004 LastWriteTime : _LARGE_INTEGER 0x1c7674e`b60b8700
+0x00c Spare : 0
+0x010 Parent : 0x6771c0
+0x014 SubKeyCounts : [2] 1
+0x01c SubKeyLists : [2] 0x6aba18
+0x024 ValueList : _CHILD_LIST
+0x01c ChildHiveReference : _CM_KEY_REFERENCE
+0x02c Security : 0x210
+0x030 Class : 0xffffffff
+0x034 MaxNameLen : 0y0000000000000110 (0x6)
+0x034 UserFlags : 0y0000
+0x034 VirtControlFlags : 0y0000
+0x034 Debug : 0y00000000 (0)
+0x038 MaxClassLen : 0
+0x03c MaxValueNameLen : 0
+0x040 MaxValueDataLen : 0
+0x044 WorkVar : 0
+0x048 NameLength : 8
+0x04a ClassLength : 0
+0x04c Name : [1] 0x5448
kd> db @eax+4C l 8
c5e6cd38 48 54 4d 4c 48 65 6c 70 HTMLHelp
Si on regarde le disass de CmEnumerateKey on s’aperçoit quelle appel HvpGetCellMapped en lisant le champ GetCellRoutine de la structure HHIVE passée en argument. Alors pensons un peu, connaissant le prototype de HvpGetCellMapped, si on trouve une méthode nous permettant de récupèrer une structure HHIVE et modifier le pointeur de fonction GetCellRoutine par une fonction filtrant le retour de HvpGetCellMapped, il devrait être possible de renvoyé une valeur disant qu’une SubKey précise n’a pas été trouvé.
Alors, le hook va se dérouler de cette manière :
On ouvre un handle avec ZwOpenKey sur la Key qu’on veut cachée, puis un ObRefenceObjectByHandle pour retrouver l’objet référé par le handle qui est de type CM_KEY_BODY.
kd> dt nt!_CM_KEY_BODY
+0x000 Type : Uint4B
+0x004 KeyControlBlock : Ptr32 _CM_KEY_CONTROL_BLOCK
+0x008 NotifyBlock : Ptr32 _CM_NOTIFY_BLOCK
+0x00c ProcessID : Ptr32 Void
+0x010 Callers : Uint4B
+0x014 CallerAddress : [10] Ptr32 Void
+0x03c KeyBodyList : _LIST_ENTRY
A partir de la structure CM_KEY_BODY on peut retrouver le CM_KEY_CONTROL_BLOCK qui contient un pointeur sur la structure _HHIVE. De là on sauvegarde l’adresse et la valeur de la fonction HvpGetCellMapped et on l’a remplace par la notre qui va toujours faire appel à HvpGetCellMapped mais dont on pourra modifier le retour.
Ensuite sachant que l’on veut caché une Key, la valeur renvoyé par HvpGetCellMapped sera un pointeur sur une structure CM_KEY_NODE, ce pointeur aura été obtenu précédemment par un appel avec HvpReleaseCellMapped sur le HHIVE et le KeyCell qu’on vise. Lorsque notre fonction qui Hook HvpGetCellMapped, on va donc comparer le pointeur retourné à celui de notre Key, si ils ont égaux alors on va chialer
En effet, si jamais notre fonction qui hook HvpGetCellMapped renvoie NULL, CmEnumerateKey considère que la liste des SubKey est finie, l’idée consiste à renvoyé à la place de notre Subkey la dernière SubKey de la liste, le truc cool c’est qu’au départ je croyais que celait allait briser l’affichage par ordre alphabétique des Subkey mais il n’en n’est rien !
Pour récupérer la dernière SubKey, on va lire le champ Parent de notre KeyNode actuel :
kd> dt nt!_CM_KEY_NODE 0xca7c86d4
+0x000 Signature : 0x6b6e
+0x002 Flags : 0x20
+0x004 LastWriteTime : _LARGE_INTEGER 0x1c7cef7`f0e79ff0
+0x00c Spare : 0
+0x010 Parent : 0x1b8
+0x014 SubKeyCounts : [2] 0x112
+0x01c SubKeyLists : [2] 0xdc020
+0x024 ValueList : _CHILD_LIST
+0x01c ChildHiveReference : _CM_KEY_REFERENCE
+0x02c Security : 0x218
+0x030 Class : 0xffffffff
+0x034 MaxNameLen : 0y0000000001001100 (0x4c)
+0x034 UserFlags : 0y0000
+0x034 VirtControlFlags : 0y0000
+0x034 Debug : 0y00000000 (0)
+0x038 MaxClassLen : 0xa
+0x03c MaxValueNameLen : 0
+0x040 MaxValueDataLen : 0
+0x044 WorkVar : 3
+0x048 NameLength : 8
+0x04a ClassLength : 0
+0x04c Name : [1] 0x6553
Ici le nom du KeyNode est celui de : HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services
kd> db 0xca7c86d4+4C l 8
ca7c8720 53 65 72 76 69 63 65 73 Services
Le parent KeyNode contient un HCELL_INDEX indiquant une structure appelé CM_KEY_INDEX. Cette structure est définie par :
typedef struct _CM_KEY_INDEX {
USHORT Signature; // also type selector
USHORT Count;
HCELL_INDEX List[1]; // Variable sized array
} CM_KEY_INDEX, *PCM_KEY_INDEX;
Elle contient une liste de HCELL_INDEX de taille Count, qui est tout simplement un tableau permettant de retrouver les SubKeys.
kd> !reg cellindex 0xe101b008 0xdc020
Map = e101e000 Type = 0 Table = 0 Block = dc Offset = 20
MapTable = e101f000
BlockAddress = ca81d000
pcell: ca81d024
kd> dt nt!_CM_KEY_INDEX ca81d024
+0x000 Signature : 0x686c ;=CM_KEY_FAST_LEAF
+0x002 Count : 0x112
+0x004 List : [1] 0x20d560
kd> dd ca81d024+4 l 20
ca81d028 0020d560 1c17bbb0 0019a3e0 5aca60b7
ca81d038 00087728 56e32bd0 00087858 70b87c26
ca81d048 00087a98 0033af41 00087ce0 14643cd5
ca81d058 00087e08 a63ea796 00088080 000165fb
ca81d068 00088468 056776b8 000886e0 09c77b47
ca81d078 00088948 09c77bdc 00088b80 167c6f83
ca81d088 00089378 000166dc 00089810 15603420
ca81d098 00088828 15848934 00089bb8 283b4b86
Si je dump, la première entrée :
kd> !reg cellindex 0xe101b008 0x20d560
Map = e101e000 Type = 0 Table = 1 Block = d Offset = 560
MapTable = e1021000
BlockAddress = ca94e000
pcell: ca94e564
kd> dt nt!_CM_KEY_NODE ca94e564
+0x000 Signature : 0x6b6e
+0x002 Flags : 0x20
+0x004 LastWriteTime : _LARGE_INTEGER 0x1c7cef5`53bac860
+0x00c Spare : 0
+0x010 Parent : 0x876d0
+0x014 SubKeyCounts : [2] 1
+0x01c SubKeyLists : [2] 0x20d5c0
+0x024 ValueList : _CHILD_LIST
+0x01c ChildHiveReference : _CM_KEY_REFERENCE
+0x02c Security : 0x218
+0x030 Class : 0xffffffff
+0x034 MaxNameLen : 0y0000000000010000 (0x10)
+0x034 UserFlags : 0y0000
+0x034 VirtControlFlags : 0y0000
+0x034 Debug : 0y00000000 (0)
+0x038 MaxClassLen : 0
+0x03c MaxValueNameLen : 0x18
+0x040 MaxValueDataLen : 0x54
+0x044 WorkVar : 0
+0x048 NameLength : 0xf
+0x04a ClassLength : 0
+0x04c Name : [1] 0x2d31
kd> db ca94e564+4C l f
ca94e5b0 31 2d 64 72 69 76 65 72-2d 76 6d 73 72 76 63 1-driver-vmsrvc
Je tombe bien sur la première SubKey de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. Il faut savoir qu’on peut tomber sur 2 types de listes d’index. Soit sur un tableau de CM_INDEX (le CM_KEY_INDEX est alors un structure CM_KEY_FAST_INDEX) dont chaque élément fait 8 bytes, ou un tableau de HCELL_INDEX de 4 bytes chacun (CM_KEY_INDEX est une structure « normale »). Pour être plus clair :
typedef ULONG HCELL_INDEX;
typedef HCELL_INDEX *PHCELL_INDEX;
typedef struct _CM_INDEX {
HCELL_INDEX Cell;
union {
UCHAR NameHint[4]; // upcased first four chars of name
ULONG HashKey; // hash key of name
};
} CM_INDEX, *PCM_INDEX;
typedef struct _CM_KEY_FAST_INDEX {
USHORT Signature; // also type selector
USHORT Count;
CM_INDEX List[1]; // Variable sized array
} CM_KEY_FAST_INDEX, *PCM_KEY_FAST_INDEX;
typedef struct _CM_KEY_INDEX {
USHORT Signature; // also type selector
USHORT Count;
HCELL_INDEX List[1]; // Variable sized array
} CM_KEY_INDEX, *PCM_KEY_INDEX;
Dans le dump de la CM_KEY_INDEX je suis tombé sur une structure CM_KEY_FAST_INDEX, donc ma liste contient un HCELL_INDEX suivit d’un Hash. Pour déterminer le type de liste qu’on trouve, il suffit de regarder le champ signature qui peut prendre les valeurs :
Listes de 4 bytes
#define CM_KEY_INDEX_ROOT 0x6972 // ir
#define CM_KEY_INDEX_LEAF 0x696c // il
Listes de 8 bytes
#define CM_KEY_FAST_LEAF 0x666c // fl
#define CM_KEY_HASH_LEAF 0x686c // hl
Bref, quand HvpGetCellMapped va renvoyé le pointeur sur la CM_KEY_NODE qu’on veut hider, on va aller lire le ParentNode pour obtenir la dernière SubKey de la liste et la renvoyé à la place. Plus tard lorsque la fonction HvpGetCellMapped nous renvoie le LastKeyNode alors on renvoie NULL à la place (pour retomber sur nos pattes).
Finalement ce qu’il faut retenir c’est qu’il est possible de modifier la fonction énumérant les SubKeys d’un Key. L’avantage c’est qu’on n’effectue aucun hookind d’image de binaire mappé mais du KOH (Kernel Object Hooking) sur la structure HHIVE. Grâce à cela, on a une technique permettant de cacher une Key plus difficile à detecter car celles d’avant consistaient à hooker les différentes API natives.
Voici un POC qui cache la clé HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NDIS
http://ivanlef0u.fr/repo/HideKey.rar
FEARZ DAZ CH1NEZZE L33STZ SCKIL2Z
Ref:
http://en.wikipedia.org/wiki/Windows_Registry
http://book.itzero.com/read/microsoft/0507/Microsoft.Press.Microsoft.Windows.Internals.Fourth.Edition.Dec.2004.internal.eBook-DDU_html/0735619174/ch04lev1sec1.html
juillet 25th, 2007
Il pleut, nous sommes le 23 juillet et il pleut, un temps qui permet d’avoir une bonne escuse pour rester devant le PC, même si en ce moment je post moins souvent cela ne signifie pas que je ne produit plus rien, c’est juste que je prefère me taire plutôt que de dire de la merde comme certains. Puisque c’est les vacances et que personnes n’a envie de se prendre la tête, je vais essayé de vous expliquer de façon claire et simple une des technique de base pour faire du kernel hooking, le hook SSDT (System Service Descriptor Table). Au moins cette fois ci, je vais tenter de me faire comprendre du commum des mortels, ceux qui ne comprendront rien pourront se consoler en mangeant des chocapics halucinèges.
Sous Windows, les appels système ce font avec les fonctions en Zw*, genéralement situées dans ntdll, elles permettent au thread de passer du ring3 (userland) au ring0 (kernelland), ces fontions ont la forme suivante :
ntdll!Zw*
MOV EAX, ; n0 du syscall
MOV EDX,7FFE0300 ;SharedUserData!SystemCallStub
CALL NEAR DWORD PTR DS:[EDX] ;ntdll!KiFastSystemCall
RET 2C
ntdll!KiFastCallEntry
MOV EDX,ESP
SYSENTER
NOP
NOP
NOP
NOP
NOP
RET
L’appel à sysenter aboutit à la fonction KiFastCallEntry du noyau, qui après diverses opérations va arriver dans KiSystemService, c’est cette dernière qui utilise le numéro du syscall pour retrouver la fonction à appeler. La SSDT est une structure de type KSERVICE_TABLE_DESCRIPTOR :
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
PULONG_PTR Base;
PULONG Count;
ULONG Limit;
PUCHAR Number;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
Le champ Base contient un tableau de pointeurs sur les fonctions en Nt*, qui contiennent le code. Limit est le nombre de ces fonctions (0x11C=284 sous xp)). Number est un tableau de UCHAR contenant la taille des arguments prit par chaque fonction.
kd> dd nt!KeServiceDescriptorTable l 4
80559880 804e26a8 00000000 0000011c 80512ef8
kd> dds 804e26a8
804e26a8 80580115 nt!NtAcceptConnectPort
804e26ac 805702d4 nt!NtAccessCheck
804e26b0 8058b6e6 nt!NtAccessCheckAndAuditAlarm
804e26b4 80589c5b nt!NtAccessCheckByType
804e26b8 80590cfb nt!NtAccessCheckByTypeAndAuditAlarm
[...]
kd> db 80512ef8 l 5
80512ef8 18 20 2c 2c 40
On peut donc voir (si on est pas trop déchiré) que la taille des arguments passé sur la pile est de 0×18 bytes pour NtAcceptConnectPort, 0×20 pour NtAccessCheck, etc …
Maitenant PENSONS (ou pas), sachant que la variable globale KeServiceDescriptorTable est définit dans la section .data de ntoskrnl et que les propriétés de la section sont :
5. item:
Name: .data
VirtualSize: 0x00016CA0
VirtualAddress: 0x00074F80
SizeOfRawData: 0x00016D00
PointerToRawData: 0x00074F80
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0xC8000040
(INITIALIZED_DATA, NOT_PAGED, READ, WRITE)
On remarque que cette section est non pagée mais surtout quelle est en Read/Write, ce qui veut dire qu’il est autorisé de modifier le champ Base de la structure KeServiceDescriptorTable. Il est donc possible de crée notre propre table de fonctions et d’y rediriger les appels vers celles-ci sans problème. Pour vérifier qu’on a bien le droit d’écrire dans cette zone mémoire il suffit de regarder le PTE associé à notre VA.
kd> !pte 80559880
VA 80559880
PDE at C0300804 PTE at C0201564
contains 004001E3 contains 00000000
pfn 400 -GLDA--KWEV LARGE PAGE 559
GRUZTZ on tombe sur une large page, dans ce cas, le PTE et le PDE sont confondu. Dans le cas présent, le flag W (GLDA–KWEV) signigie que la page est dispo en écriture, c’est plutôt cool pour nous. Pendant que j’y suis, il existe 2 fonctions exportées par le kernel, KeAddSystemServiceTable et KeRemoveSystemServiceTable, je vous laisse lire :
BOOLEAN
KeAddSystemServiceTable (
IN PULONG_PTR Base,
IN PULONG Count OPTIONAL,
IN ULONG Limit,
IN PUCHAR Number,
IN ULONG Index
)
/*++
Routine Description:
This function adds the specified system service table to the system.
Arguments:
Base - Supplies the address of the system service table dispatch table.
Count - Supplies an optional pointer to a table of per system service
counters.
Limit - Supplies the limit of the service table. Services greater
than or equal to this limit will fail.
Arguments - Supplies the address of the argument count table.
Index - Supplies index of the service table.
Return Value:
TRUE - The operation was successful.
FALSE - the operation failed. A service table is already bound to
the specified location, or the specified index is larger than
the maximum allowed index.
--*/
BOOLEAN
KeRemoveSystemServiceTable (
IN ULONG Index
)
/*++
Routine Description:
This function removes a system service table from the system.
Arguments:
Index - Supplies index of the service table.
Return Value:
TRUE - The operation was successful.
FALSE - the operation failed. A service table is is not bound or is illegal to remove
--*/
Bon même si l’idée de se recrée une nouvelle table de pointeurs de fonctions est plutôt sympa, dans le cadre d’un simple hook, il est tout de même plus pratique de modifier uniquement l’entré qui nous intéresse. On va donc écrire dans la table KiServiceTable qui contient les pointeurs de fonctions pour rediger l’appel système vers notre code. Le petit problème c’est que depuis windows XP cette table à des droits modifiés, si on regarde le moment de l’initialisation au moment de l’appel à KiInitSystem, on peut voir quelle fait partie de la section .text :
KiInitSystem()
mov ds:_KeServiceDescriptorTable, offset _KiServiceTable
mov ds:dword_48A504, esi
mov ds:dword_48A50C, offset _KiArgumentTable
.text:0040D8B0 60 7D 4B 00 _KiServiceTable
Or les droits de la section .text sont :
->Section Header Table
1. item:
Name: .text
VirtualSize: 0x00074DB5
VirtualAddress: 0x00001000
SizeOfRawData: 0x00074E00
PointerToRawData: 0x00000600
PointerToRelocations: 0x00000000
PointerToLinenumbers: 0x00000000
NumberOfRelocations: 0x0000
NumberOfLinenumbers: 0x0000
Characteristics: 0x68000020
(CODE, NOT_PAGED, EXECUTE, READ)
EWwWZ la section est lecture seule, pour être sur il suffit de check le PTE associé à la VA de la KiServiceTable :
kd> !pte nt!KiServiceTable
VA 804e26a8
PDE at C0300804 PTE at C0201388
contains 0003D163 contains 004E2121
pfn 3d -G-DA--KWEV pfn 4e2 -G--A--KREV
Dans les droits du PTE (-G–A–KREV) le R signifie read only et le K que c’est une kernel page, si on tente d’écrire dedans on se mangera un joli BSOD. Pour désactiver cette protection, il faut jouer avec le 16 ème bit du registre de control cr0 :
5.6.4 Write Protect The ability to write to read-only pages is governed by the
(CR0.WP) Bit processor mode and whether write protection is enabled. If
write protection is not enabled, a processor running at CPL 0, 1,
or 2 can write to any physical page, even if it is marked as read-
only. Enabling write protection prevents supervisor code from
writing into read-only pages, including read-only user-level
pages.
A page-fault exception (#PF) occurs if software attempts to
write (at any privilege level) into a read-only page while write
protection is enabled.
Normalement le bit CR0.WP est mit un 1 :
kd> r cr0
cr0=8001003b=10000000000000010000000000111011
Ce qui fait que si un code ring0 tente d’écrire dans un page kernel en read only, un exception sera levé. Par contre si on désactive ce bit « a processor running at CPL 0, 1,or 2 can write to any physical page, even if it is marked as read-
only », on peut écrire dans n’importe quelle page, w00t
Un code mettant en pratique cette méthode pourrait être :
__asm
{
push eax
mov eax, CR0
and eax, 0FFFEFFFFh
mov CR0, eax
pop eax
}
// do something
HOOK_SYSCALL(ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );
// RE-protect memory
__asm
{
push eax
mov eax, CR0
or eax, NOT 0FFFEFFFFh
mov CR0, eax
pop eax
}
Cependant même si cette méthode est super simple à mettre en place, elle possède un certain inconvénient, imho, dans le cas ou l’on modifit le cr0, rien ne nous garantit que notre routine ne se fasse switcher juste après et qu’un code tente d’écrire dans un page en read-only évitant ainsi l’exception, alors quelle auraît du intervenir. Evidemment, c’est un peu tiré par les cheveux mais il existe un autre moyen pour bypass la protection de la page.
Mais avant, je vais définir le rôle de quelque macros. Lorsque qu’on veut hooker la SSDT il nous faut modifier un des pointeurs de la KiServiceTable. Le problème, c’est que même si les fonctions Zw* sont exportées par le kernel, on n’arrive pas directement sur leur code, par exemple avec la fonction ZwQuerySystemInformation :
kd> u ZwQuerySystemInformation
nt!ZwQuerySystemInformation:
804ddbc0 b8ad000000 mov eax,0ADh ; 0xAD syscall nbr
804ddbc5 8d542404 lea edx,[esp+4]
804ddbc9 9c pushfd
804ddbca 6a08 push 8
804ddbcc e8d5120000 call nt!KiSystemService (804deea6)
804ddbd1 c21000 ret 10h
HUZ? ce merdier, va faire comme la fonction NtQueryInformationSystem du userland, appeler KiSystemService, pourquoi ? Pour que plus tard, le code de la fonction puisse savoir si il a été appelé du userland ou du kerneland. Bref nous on connaître à quel indice est notre fonction dans la KiServiceTable. Il suffit juste de lire le 2 byte pointé par la fontion en Zw* pour le retrouver:
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
Maitenant qu’on a l’indice, on peut lire l’adresse effective de la fonction en Nt* avec un :
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
Enfin, pour hooker, il nous faut l’adresse de la fonction en Zw* pour retrouver l’indice, l’adresse de notre fonction qui va remplacer le pointeur et une variable pour stocker l’adresse orignalle, tout cela est réalisé par les macro suivantes :
#define HOOK_SYSCALL(_Function, _Hook, _Orig )
_Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig )
InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
Alors, l’astuce permettant de bypass la protection est d’utilser un MDL (Memory Descriptor List) en remappant la SSDT. Le code est le suivant :
// Map the memory into our domain so we can change the permissions on the MDL
g_pmdlSystemCall=IoAllocateMdl(KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4, 0, 0, NULL);
if(!g_pmdlSystemCall)
return STATUS_UNSUCCESSFUL;
//The MmBuildMdlForNonPagedPool routine receives an MDL that specifies a virtual memory buffer in nonpaged pool,
//and updates it to describe the underlying physical pages.
MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
//The MmMapLockedPages routine maps the physical pages that are described by a given MDL.
MappedSystemCallTable=MmMapLockedPages(g_pmdlSystemCall, KernelMode);
//hook system calls
OldZwQuerySystemInformation=HOOK_SYSCALL(ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );
En fait, une chose BIZARRE, apparaît au moment on l’on fait appel à la la fonction MmMapLockedPages :
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
#define MDL_MAPPED_TO_SYSTEM_VA 0x0001
#define MDL_PAGES_LOCKED 0x0002
#define MDL_SOURCE_IS_NONPAGED_POOL 0x0004
#define MDL_ALLOCATED_FIXED_SIZE 0x0008
#define MDL_PARTIAL 0x0010
#define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020
#define MDL_IO_PAGE_READ 0x0040
#define MDL_WRITE_OPERATION 0x0080
#define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100
#define MDL_FREE_EXTRA_PTES 0x0200
#define MDL_DESCRIBES_AWE 0x0400
#define MDL_IO_SPACE 0x0800
#define MDL_NETWORK_HEADER 0x1000
#define MDL_MAPPING_CAN_FAIL 0x2000
#define MDL_ALLOCATED_MUST_SUCCEED 0x4000
avant le lock
kd> dt nt!_MDL 0xffbd8670
+0x000 Next : (null)
+0x004 Size : 32
+0x006 MdlFlags : 12 ; MDL_ALLOCATED_FIXED_SIZE|MDL_SOURCE_IS_NONPAGED_POOL
+0x008 Process : (null)
+0x00c MappedSystemVa : 0x804e26a8
+0x010 StartVa : 0x804e2000
+0x014 ByteCount : 0x470
+0x018 ByteOffset : 0x6a8
kd> !pte 0x804e26a8
VA 804e26a8
PDE at C0300804 PTE at C0201388
contains 0003D163 contains 004E2121
pfn 3d -G-DA--KWEV pfn 4e2 -G--A--KREV
Avant l’appel, le champ MappedSystemVa du MDL contient la VA « normale » qui représente les frames physiques contenant la KiServiceTable.
Après le lock
kd> dt nt!_MDL 0xffbd8670
+0x000 Next : (null)
+0x004 Size : 32
+0x006 MdlFlags : 13 ; MDL_ALLOCATED_FIXED_SIZE|MDL_SOURCE_IS_NONPAGED_POOL|MDL_MAPPED_TO_SYSTEM_VA
+0x008 Process : (null)
+0x00c MappedSystemVa : 0xfd3f76a8 ; !!!!
+0x010 StartVa : 0x804e2000
+0x014 ByteCount : 0x470
+0x018 ByteOffset : 0x6a8
kd> !pte 0xfd3f76a8
VA fd3f76a8
PDE at C0300FD0 PTE at C03F4FDC
contains 01031163 contains 004E2163
pfn 1031 -G-DA--KWEV pfn 4e2 -G-DA--KWEV
Après l’appel à MmMapLockedPages, le MappedSystemVa a changé et le pte associé possède un magnique W dans ses flags, indiquant qu’on a le droit d’écriture sur la page ! On peut vérifier que cette VA 0xfd3f76a8 est bien associdé à la même frame que la VA 0x804e26a8 en dumpant le contenu de la physical memory :
kd> !dd 4e2*1000+6a8
# 4e26a8 80580115 805702d4 8058b6e6 80589c5b
# 4e26b8 80590cfb 80636c94 80638e25 80638e6e
# 4e26c8 8057833f 806476ab 80636453 8057ae00
# 4e26d8 8062e598 80578d91 8058cb5e 8062569d
# 4e26e8 805db60c 8056819d 805d81ad 805a1290
# 4e26f8 804e2cb4 80647697 805c88e8 804ecfac
# 4e2708 80568849 80566f49 805906f0 8064d6bb
# 4e2718 8058f858 8057f4a1 8064d929 8058b738
Bon, alors bug ou pas bug ? Je n’en sait trop rien, normalement le fait de remapper une page ne devrait pas permettre de pouvoir y accéder de façon différente, d’après ce que j’ai vu c’est la fonction MiReserveSystemPtes qui va allouer le PTE durant l’appel à MmMapLockedPages.
Enfin le code d’exemple. Il montre un exemple d’un hook SSDT sur la fonction NtQuerySystemInformation, cachant tout les process commençant par _root_. Je vous laisse jouer avec :
http://ivanlef0u.fr/repo/HookSsdtMdl.rar
SPLITZ D4 MEG4HZURTZ
http://book.itzero.com/read/microsoft/0507/Microsoft.Press.Microsoft.Windows.Internals.Fourth.Edition.Dec.2004.internal.eBook-DDU_html/0735619174/ch07lev1sec5.html
http://ivanlef0u.fr/repo/windoz/Nt_vs_Zw.txt
http://www.amd.com/us-en/assets/content_type/DownloadableAssets/dwamd_24593.pdf
juillet 23rd, 2007
Je suis de retour, plus mad que jamais, et cette fois-ci, vous allez souffrir ! En effet, vous allez découvrir le voyage effectué par un packet TCP au sein du windows. À partir de l’appel à la fonction connect, jusqu’a la dernière instruction assembleur faisant appel à la carte réseau. Évidemment si j’ai fait cela, c’était surtout pour mieux comprendre comment sont mises en places les différences couches réseau du Windows. Préparez-vous à une aventure dont vous ne reviendrez pas indemne !
L’API Winsock respecte les sockets BDS et est dans implémenté dans libraire ws2_32.dll. Cette dll se base sur différents SPI (Service Provider Interface), on a par exemple le NameSpace SPI (NSP), qui permet d’effectuer des requêtes DNS avec des fonctions comme gethostbyname et surtout le Winsock Provider (WSP) qui va send les demandes aux couches inférieures. Ces 2 providers sont fourni par mswsock.dll qui est vraiment le coeur de l’API Winsock servant à envoyer les requêtes au noyau à travers un ZwDeviceIoControlFile comme on peut le voir avec la call stack suivante :
ntdll.dll!KiFastSystemCall
ntdll.dll!ZwDeviceIoControlFile+0xc
mswsock.dll!QueryNbtWins+0x50
mswsock.dll!TryNbt+0x22
mswsock.dll!NSPLookupServiceNext+0x57e
WS2_32.dll!NSPROVIDER::NSPLookupServiceNext+0x17
WS2_32.dll!NSPROVIDERSTATE::LookupServiceNext+0x1c
WS2_32.dll!NSQUERY::LookupServiceNext+0xae
WS2_32.dll!WSALookupServiceNextW+0x78
WS2_32.dll!WSALookupServiceNextA+0x63
WS2_32.dll!getxyDataEnt+0xa1
WS2_32.dll!gethostbyname+0xb4
rezo.exe+0x2e6
kernel32.dll!BaseProcessStart+0x23
Depuis que j’ai ouvert ce blog, je fearais l’exploration de l’implémentation réseau sous Windows, après avoir passé pas mal de temps dessus, je crois qu’au final j’avais raison de pas m’y aventurer. Je voulais en fait, suivre le parcourt d’un packet, à partir du moment ou l’on appel la fonction send(), jusqu’à l’arrivé sur la carte réseau. Pour cela j’ai employé une méthode appelée le brutal tracing, l’avantage de cette technique c’est quelle est super simple à mettre en oeuvre, à l’aide de IDA et de WinDbg on regarde les différentes fonctions des lib/drv et puis on trace pour vérifier qu’on ne s’est pas trompé.
Alors première étape, se coder un petit programme de base. Un simple client qui send un « GET / HTTP/1.1″ suffira. Le but, suivre le parcourt de nos datas après l’appel avec la fonction send(). Voici le code (très simple) du client :
#include <Winsock2.h>
#include <stdio.h>
//compil with vc6
//fuck d4 CRT sh1t
#pragma comment (linker, "/nodefaultlib:libc.lib")
#pragma comment (linker, "/entry:\"main\"")
#pragma comment (lib, "msvcrt.lib")
#pragma comment (lib, "ws2_32.lib")
ULONG main()
{
WORD Ver;
WSADATA Wsa;
SOCKET Socket;
SOCKADDR_IN Sin;
char SendBuff[]=
"GET / HTTP/1.1rn"
"rn";
Ver=MAKEWORD(2, 2);
if(WSAStartup(Ver, &Wsa))
{
printf("Error with WSAStartupn");
return 0;
}
Socket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(Socket==INVALID_SOCKET)
{
printf("Error with socket : %dn", WSAGetLastError());
WSACleanup();
return 0;
}
RtlZeroMemory(&Sin, sizeof(SOCKADDR_IN));
// Create a sockaddr_in object and set its values.
Sin.sin_family=AF_INET;
Sin.sin_addr.s_addr=inet_addr("209.85.135.103"); //IP google
Sin.sin_port=htons(80);
if(connect(Socket, (SOCKADDR*)&Sin, sizeof(SOCKADDR_IN))==SOCKET_ERROR)
{
printf("Error with connect failed : %dn", WSAGetLastError());
closesocket(Socket);
return 0;
}
if(send(Socket, SendBuff, sizeof(SendBuff), 0)!=0)
{
printf("Error with send : %dn", WSAGetLastError());
closesocket(Socket);
return 0;
}
//recv(Socket, Buff, sizeof(Buff), 0);
//printf("%s", Buff);
closesocket(Socket);
WSACleanup();
return 0;
}
Ok, on est partit. Déja en tracant la fonction send on peut voir un enormous call :
71AB42CF 53 PUSH EBX
71AB42D0 8D4D FC LEA ECX,DWORD PTR SS:[EBP-4]
71AB42D3 51 PUSH ECX
71AB42D4 FF75 F8 PUSH DWORD PTR SS:[EBP-8]
71AB42D7 8D4D 08 LEA ECX,DWORD PTR SS:[EBP+8]
71AB42DA 57 PUSH EDI
71AB42DB 57 PUSH EDI
71AB42DC FF75 14 PUSH DWORD PTR SS:[EBP+14]
71AB42DF 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
71AB42E2 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
71AB42E5 51 PUSH ECX
71AB42E6 6A 01 PUSH 1
71AB42E8 8D4D F0 LEA ECX,DWORD PTR SS:[EBP-10]
71AB42EB 51 PUSH ECX
71AB42EC FF75 08 PUSH DWORD PTR SS:[EBP+8]
71AB42EF 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
71AB42F2 8B46 0C MOV EAX,DWORD PTR DS:[ESI+C]
71AB42F5 FF50 64 CALL NEAR DWORD PTR DS:[EAX+64]
Le problème c’est qu’on ne peut connaître la destination du call en dead-listing, alors on sort le Olly et on voit qu’on tombe dans la dll mswsock. De plus, il serait tout de même sympa de savoir sur quoi pointe eax, en dumpant la zone mémoire pointée on peut voir :
00146C18 02 00 00 00 00 00 A5 71 D9 B5 A6 71 98 69 A5 71 .....¥qÙµ¦q˜i¥q
00146C28 0F 7F A6 71 A1 4D A5 71 54 AE A6 71 4D 77 A6 71 ¦q¡M¥qT®¦qMw¦q
00146C38 5F 40 A5 71 D0 53 A5 71 B3 AE A6 71 29 67 A5 71 _@¥qÐS¥q³®¦q)g¥q
00146C48 EF 64 A5 71 21 B0 A6 71 31 B1 A6 71 49 3D A5 71 ïd¥q!°¦q1±¦qI=¥q
00146C58 E5 5F A5 71 D5 AD A6 71 DB 5C A5 71 90 A3 A6 71 å_¥qÕ¦qÛ¥q£¦q
00146C68 5D 76 A5 71 42 43 A5 71 93 9A A6 71 45 31 A5 71 ]v¥qBC¥q“š¦qE1¥q
00146C78 58 2D A5 71 47 58 A5 71 71 9C A6 71 85 2E A5 71 X-¥qGX¥qqœ¦q….¥q
00146C88 BB 51 A5 71 D1 98 A6 71 15 46 A5 71 0E 94 A5 71 »Q¥qј¦qF¥q”¥q
Youpi une jolie table de fonctions. A ce moment-là je me souvenu que j’avais fait un post appelé « Winsock Reversing » et que cette table était en fait initialisé par la fonction WSPStartup qui la remplissait des diverses fonctions du Winsock Provider. Bien évidemment si on regarde le nom des fonctions de cette table, on trouve à l’offet 0×64, l’adresse de la fonction WSPSend.
kd> dds @eax l 20
001476f8 00000001
001476fc 71a50000 mswsock!_imp__GetUserNameA
(mswsock+0x0)
00147700 71a6b5d9 mswsock!WSPAccept
00147704 71a56998 mswsock!WSPAddressToString
00147708 71a67f0f mswsock!WSPAsyncSelect
0014770c 71a54da1 mswsock!WSPBind
00147710 71a6ae54 mswsock!WSPCancelBlockingCall
00147714 71a6774d mswsock!WSPCleanup
00147718 71a5405f mswsock!WSPCloseSocket
0014771c 71a553d0 mswsock!WSPConnect
00147720 71a6aeb3 mswsock!WSPDuplicateSocket
00147724 71a56729 mswsock!WSPEnumNetworkEvents
00147728 71a564ef mswsock!WSPEventSelect
0014772c 71a6b021 mswsock!WSPGetOverlappedResult
00147730 71a6b131 mswsock!WSPGetPeerName
00147734 71a53d49 mswsock!WSPGetSockName
00147738 71a55fe5 mswsock!WSPGetSockOpt
0014773c 71a6add5 mswsock!WSPGetQOSByName
00147740 71a55cdb mswsock!WSPIoctl
00147744 71a6a390 mswsock!WSPJoinLeaf
00147748 71a5765d mswsock!WSPListen
0014774c 71a54342 mswsock!WSPRecv
00147750 71a69a93 mswsock!WSPRecvDisconnect
00147754 71a53145 mswsock!WSPRecvFrom
00147758 71a52d58 mswsock!WSPSelect
0014775c 71a55847 mswsock!WSPSend <----
00147760 71a69c71 mswsock!WSPSendDisconnect
00147764 71a52e85 mswsock!WSPSendTo
00147768 71a551bb mswsock!WSPSetSockOpt
0014776c 71a698d1 mswsock!WSPShutdown
00147770 71a54615 mswsock!WSPSocket
En fait, cette implémentation permet d’avoir des LSP (Layer Service Provider), par exemple, si vous voulez rajouter une couche SSL au dessous des Winsock et au dessus du provider TCP il suffit de crée un LSP avec la fonction WSCInstallProvider. Jonathan Levin nous expose cette méthode permettant de faire du « socket hijacking » dans une conf de la REcon 2005 « The Dark Side of Winsock » qui vous pouvez trouver ici.
En fait lors de l’initialisation,la fonction WSAStartup va parcourir dans l’ordre des dwCatalogEntryId le catalogue des providers puis sélectionner ceux qui correspondent à notre type socket et les chainer entre eux. Voici le catalogue des providers par défaut :
Winsock 32-bit Catalog:
=======================
1001 - MSAFD Irda [IrDA]
1002 - MSAFD Tcpip [TCP/IP]
1003 - MSAFD Tcpip [UDP/IP]
1004 - MSAFD Tcpip [RAW/IP]
1005 - RSVP UDP Service Provider
1006 - RSVP TCP Service Provider
1007 - MSAFD NetBIOS [DeviceNetBT_Tcpip_{F9B0E229-79AD-49F4-AFA5-789B26A7A433}] SEQPACKET 0
1008 - MSAFD NetBIOS [DeviceNetBT_Tcpip_{F9B0E229-79AD-49F4-AFA5-789B26A7A433}] DATAGRAM 0
1009 - MSAFD NetBIOS [DeviceNetBT_Tcpip_{7F588485-D8FD-4384-94F8-20934493A8AC}] SEQPACKET 1
1010 - MSAFD NetBIOS [DeviceNetBT_Tcpip_{7F588485-D8FD-4384-94F8-20934493A8AC}] DATAGRAM 1
1011 - MSAFD NetBIOS [DeviceNetBT_Tcpip_{8500C835-C738-4E3B-910B-2B41B8C628F5}] SEQPACKET 2
1012 - MSAFD NetBIOS [DeviceNetBT_Tcpip_{8500C835-C738-4E3B-910B-2B41B8C628F5}] DATAGRAM 2
Pour réaliser cette souplesse les fonctions sont donc appelées à travers une table nommée WSPPROC_TABLE, ainsi on peut remplacer les adresses des API par les notre et donc manipuler les sockets tranquillement :] Vous pouvez retrouver un exemple d’implémentation ici.
Bon tout ca c’est bien joli. Mais au final send() appel la fonction WSPSend qui va enfin faire la requête au noyau.
kd> kv
0012fd0c 71a55908 00000060 00000038 00000000 ntdll!ZwDeviceIoControlFile+0xc (FPO: [10,0,0])
0012fd98 71ab42f8 00000060 0012fdd0 00000001 mswsock!WSPSend+0x16c (FPO: [Non-Fpo])
0012fde0 00400342 00000060 0012ff94 00000013 WS2_32!send+0x82 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ffc0 7c816d4f 00310039 00310037 7ffd8000 rezo+0x342
0012fff0 00000000 00400220 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
Le prototype de la fonction NtDeviceIoControlFile est le suivant :
NTSTATUS
ZwDeviceIoControlFile(
IN HANDLE FileHandle,
IN HANDLE Event,
IN PIO_APC_ROUTINE ApcRoutine,
IN PVOID ApcContext,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG IoControlCode,
IN PVOID InputBuffer,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer,
IN ULONG OutputBufferLength
);
mswsock.dll!WSPSend
[...]
71A558E8 53 PUSH EBX <-OutputBufferLength
71A558E9 53 PUSH EBX <-OutputBuffer
71A558EA 6A 10 PUSH 10 <-InputBufferLength
71A558EC 8D45 B0 LEA EAX,DWORD PTR SS:[EBP-50]
71A558EF 50 PUSH EAX <-InputBuffer
71A558F0 68 1F200100 PUSH 1201F <-IoControlCode
71A558F5 FF75 D8 PUSH DWORD PTR SS:[EBP-28] <-IoStatusBloc
71A558F8 FF75 D4 PUSH DWORD PTR SS:[EBP-2C] <-ApcContext
71A558FB FF75 DC PUSH DWORD PTR SS:[EBP-24] <-ApcRoutine
71A558FE 56 PUSH ESI <-Event
71A558FF FF75 08 PUSH DWORD PTR SS:[EBP+8] <-FileHandle
71A55902 FF15 E010A571 CALL NEAR DWORD PTR DS:[<&ntdll.NtDevice>; ntdll.ZwDeviceIoControlFile
[...]
Pour un send() l’IoControlCode est 0x1201F, WSPSend ne passe qu’un InputBuffer, l’OutputBufferLength étant nul, il n’y aura aucun retour. Il serait intéressant de connaitre ce qui est passé à travers l’InputBuffer. Pour cela, connaissant le prototype de WSPSend :
int
WSPSend(
IN SOCKET s,
IN LPWSABUF lpBuffers,
IN DWORD dwBufferCount,
OUT LPDWORD lpNumberOfBytesSent,
IN DWORD dwFlags,
IN LPWSAOVERLAPPED lpOverlapped,
IN LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
IN LPWSATHREADID lpThreadId,
OUT LPINT lpErrno
);
lpBuffers :
Pointer to an array of WSABUF structures. This array must remain valid for the duration of the send operation.
dwBufferCount :
Number of WSABUF structures at lpBuffers
typedef struct _WSABUF {
u_long len;
char FAR *buf;
} WSABUF, FAR * LPWSABUF;
Le paramètre lpBuffers pointe sur une liste de WSABUF, ces derniers contiennent nos datas, pour vérifier il suffit de mater la valeur du pointeur dans la stack juste avant l’appel à NtDeviceIoControlFile :
0012E980 0000005C |Arg1 = 0000005C
0012E984 00000038 |Arg2 = 00000038
0012E988 00000000 |Arg3 = 00000000
0012E98C 00000000 |Arg4 = 00000000
0012E990 0012E9CC |Arg5 = 0012E9CC
0012E994 0001201F |Arg6 = 0001201F
0012E998 0012E9B4 |Arg7 = 0012E9B4 <-InputBuffer
0012E99C 00000010 |Arg8 = 00000010
0012E9A0 00000000 |Arg9 = 00000000
0012E9A4 00000000 Arg10 = 00000000
0012E9B4 8C EA 12 00 01 00 00 00 00 00 00 00 00 00 00 00 ΐ............
L’IntputBuffer pointe sur une structure de 16 bytes, le premier dword est un pointeur sur une liste de WSABUF, le dword suivant contient le nombre d’éléments de la liste. Regardons le contenu du WSABUF.
0012EA8C 13 00 00 00 74 EA 12 00 ...tê.
0012EA74 47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A GET / HTTP/1.1..
0012EA84 0D 0A 00 00 ....
On a la taille de nos datas (0×13) et un pointeur sur celles-ci. Jusqu’ici rien d’impressionnant, maintenant on voudrait savoir à quel driver va être envoyé l’IRP. Pour cela, il regarde ce que référence le handle passé en tant que premier argument à NtDeviceIoControlFile.
kd> !handle 5C 3 288
processor number 0, process 00000288
Searching for Process with Cid == 288
PROCESS 865e22a8 SessionId: 0 Cid: 0288 Peb: 7ffdf000 ParentCid: 0234
DirBase: 13fd7000 ObjectTable: e18fed48 HandleCount: 24.
Image: rezo.exe
Handle table at e16e9000 with 24 Entries in use
005c: Object: 866705a0 GrantedAccess: 001f01ff (Inherit) Entry: e16e90b8
Object: 866705a0 Type: (867e9040) File
ObjectHeader: 86670588 (old version)
HandleCount: 1 PointerCount: 1
Directory Object: 00000000 Name: Endpoint {Afd}
Le handle fait référence au device \Endpoint du driver Afd.sys. Si on regarde bien, on s’aperçoit que cet handle est en fait notre socket, initialisé lors de l’appel à la fonction socket(). Le driver afd (Ancillary Function Driver for WinSock) correspond, d’après ce que j’ai lu, au driver d’émulation des sockets servant à effectuer la gestion des buffers et des connexions afin de les transmettre à la couche inférieure appelé TDI (Transport Driver Interface). Regardons la table des IRP_MX_XXX, j’ajoute que cette table permet en fait aux autres composants du système de comuniquer avec le driver.
kd> !devobj 0x865df770
Device object (865df770) is for:
Afd DriverAFD DriverObject 8661b240
Current Irp 00000000 RefCount 40 Type 00000011 Flags 00000050
Dacl e1406284 DevExt 00000000 DevObjExt 865df828
ExtensionFlags (0000000000)
Device queue is not busy.
kd> !drvobj DriverAFD 3
Driver object (8661b240) is for:
DriverAFD
Driver Extension List: (id , addr)
Device Object list:
865df770
DriverEntry: baf48f40 afd!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: baf32482 afd!AfdUnload
Dispatch routines:
[00] IRP_MJ_CREATE baf36d40 afd!AfdDispatch
[01] IRP_MJ_CREATE_NAMED_PIPE baf36d40 afd!AfdDispatch
[02] IRP_MJ_CLOSE baf36d40 afd!AfdDispatch
[03] IRP_MJ_READ baf36d40 afd!AfdDispatch
[04] IRP_MJ_WRITE baf36d40 afd!AfdDispatch
[05] IRP_MJ_QUERY_INFORMATION baf36d40 afd!AfdDispatch
[06] IRP_MJ_SET_INFORMATION baf36d40 afd!AfdDispatch
[07] IRP_MJ_QUERY_EA baf36d40 afd!AfdDispatch
[08] IRP_MJ_SET_EA baf36d40 afd!AfdDispatch
[09] IRP_MJ_FLUSH_BUFFERS baf36d40 afd!AfdDispatch
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION baf36d40 afd!AfdDispatch
[0b] IRP_MJ_SET_VOLUME_INFORMATION baf36d40 afd!AfdDispatch
[0c] IRP_MJ_DIRECTORY_CONTROL baf36d40 afd!AfdDispatch
[0d] IRP_MJ_FILE_SYSTEM_CONTROL baf36d40 afd!AfdDispatch
[0e] IRP_MJ_DEVICE_CONTROL baf36280 afd!AfdDispatchDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL baf36d40 afd!AfdDispatch
[10] IRP_MJ_SHUTDOWN baf36d40 afd!AfdDispatch
[11] IRP_MJ_LOCK_CONTROL baf36d40 afd!AfdDispatch
[12] IRP_MJ_CLEANUP baf36d40 afd!AfdDispatch
[13] IRP_MJ_CREATE_MAILSLOT baf36d40 afd!AfdDispatch
[14] IRP_MJ_QUERY_SECURITY baf36d40 afd!AfdDispatch
[15] IRP_MJ_SET_SECURITY baf36d40 afd!AfdDispatch
[16] IRP_MJ_POWER baf36d40 afd!AfdDispatch
[17] IRP_MJ_SYSTEM_CONTROL baf36d40 afd!AfdDispatch
[18] IRP_MJ_DEVICE_CHANGE baf36d40 afd!AfdDispatch
[19] IRP_MJ_QUERY_QUOTA baf36d40 afd!AfdDispatch
[1a] IRP_MJ_SET_QUOTA baf36d40 afd!AfdDispatch
[1b] IRP_MJ_PNP baf36d40 afd!AfdDispatch
Fast I/O routines:
FastIoRead baf321b2 afd!AfdFastIoRead
FastIoWrite baf32291 afd!AfdFastIoWrite
FastIoUnlockAll baf353d4 afd!AfdSanFastUnlockAll
FastIoDeviceControl baf2d880 afd!AfdFastIoDeviceControl
Ok, on remarque direct que la fonction principale est AfdDispatchDeviceControl, en effet c’est elle qui est chargée de gérer tous les IRP provenant des NtDeviceIoControlFile. Sachant que le format des paramètres des l’IRP est connu lorsque qu’il passe par une major fonction de type XxxDispatchDeviceControl, on peut retrouver facilement nos arguments. Au début, lorsque je mettais un BP sur la fonction AfdDispatchDeviceControl je n’arrivais pas à break après l’appel à WSPSend, je me suis en fait aperçu qu’il existant une fonction de dispatch fonctionnant en fast I/O appelé AfdFastIoDeviceControl. En fait les IRP peuvent être gérées de 2 manières, synchrone et asynchrone, dans le cas synchrone l’IRP est complété par le thread qui la crée, ce qui signifie que si les drivers manipulent des données userland représentées par l’IRP, elles seront accessibles durant tout le processus, typiquement lorsque qu’on utilise une méthode d’I/O de type Neither Buffered Nor Direct I/O. Dans la cas asynchrone, l’IRP peut très bien être complétée dans le contexte d’un autre thread (appartenant ou non au même process) ce qui signifie qu’un pointeur userspace n’est plus valide, il faut donc utilisé un MDL permettant d’accéder au physical pages représentant, on peut, pour cela, procéder par une méthode Buffered I/O ou bien Direct I/O.
Bon notre cas, c’est du Fast I/O, les pointeurs utilisés pour lire nos datas sont donc ceux du userspace. Il suffit de vérifier les arguments passés à AfdFastIoDeviceControl.
//
// Fast I/O device control procedure.
//
typedef
BOOLEAN
FAST_IO_DEVICE_CONTROL (
__in struct _FILE_OBJECT *FileObject,
__in BOOLEAN Wait,
__in_opt PVOID InputBuffer,
__in ULONG InputBufferLength,
__out_opt PVOID OutputBuffer,
__in ULONG OutputBufferLength,
__in ULONG IoControlCode,
__out PIO_STATUS_BLOCK IoStatus,
__in struct _DEVICE_OBJECT *DeviceObject
);
Breakpoint 1 hit
afd!AfdFastIoDeviceControl:
f9640880 680c010000 push 10Ch
kd> kv
ChildEBP RetAddr Args to Child
f6300c50 8057fc67 8170d8c8 00000001 0012fd48 afd!AfdFastIoDeviceControl (FPO: [Non-Fpo])
f6300d00 8057fbfa 00000060 00000038 00000000 nt!IopXxxControlFile+0x261 (FPO: [Non-Fpo])
f6300d34 804df06b 00000060 00000038 00000000 nt!NtDeviceIoControlFile+0x2a (FPO: [Non-Fpo])
f6300d34 7c90eb94 00000060 00000038 00000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f6300d64)
0012fd08 7c90d8ef 71a55908 00000060 00000038 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0012fd0c 71a55908 00000060 00000038 00000000 ntdll!ZwDeviceIoControlFile+0xc (FPO: [10,0,0])
0012fd98 71ab42f8 00000060 0012fdd0 00000001 mswsock!WSPSend+0x16c (FPO: [Non-Fpo])
0012fde0 00400342 00000060 0012ff94 00000013 WS2_32!send+0x82 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ffc0 7c816d4f 00310039 00310037 7ffd4000 rezo+0x342
0012fff0 00000000 00400220 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
0012fd48==InputBuffer, dans le contexte du thread qui a call le NtDeviceIoControlFile
Bon pendant qu’on y est, regardons la fonction chargée de dispatcher les IRP « normales », AfdDispatchDeviceControl:
kd> uf afd!AfdDispatchDeviceControl
afd!AfdDispatchDeviceControl:
f9632280 8bff mov edi,edi
f9632282 55 push ebp
f9632283 8bec mov ebp,esp
f9632285 8b4d0c mov ecx,dword ptr [ebp+0Ch] ;Irp
f9632288 8b5160 mov edx,dword ptr [ecx+60h]
f963228b 56 push esi
f963228c 57 push edi
f963228d 8b7a0c mov edi,dword ptr [edx+0Ch] <;esi=IoControlCode
f9632290 8bc7 mov eax,edi
f9632292 c1e802 shr eax,2
f9632295 25ff030000 and eax,3FFh
f963229a 83f846 cmp eax,46h
f963229d 0f834c690000 jae afd!AfdDispatchDeviceControl+0x3d (f9638bef)
afd!AfdDispatchDeviceControl+0x1f:
f96322a3 8bf0 mov esi,eax
f96322a5 c1e602 shl esi,2
f96322a8 39bea09062f9 cmp dword ptr afd!AfdIoctlTable (f96290a0)[esi],edi
f96322ae 0f853b690000 jne afd!AfdDispatchDeviceControl+0x3d (f9638bef)
afd!AfdDispatchDeviceControl+0x2c:
f96322b4 884201 mov byte ptr [edx+1],al
f96322b7 8bb6b89162f9 mov esi,dword ptr afd!AfdIrpCallDispatch (f96291b8)[esi]
f96322bd 85f6 test esi,esi
f96322bf 0f842a690000 je afd!AfdDispatchDeviceControl+0x3d (f9638bef)
afd!AfdDispatchDeviceControl+0x39:
f96322c5 ffd6 call esi
afd!AfdDispatchDeviceControl+0x53:
f96322c7 5f pop edi
f96322c8 5e pop esi
f96322c9 5d pop ebp
f96322ca c20800 ret 8
afd!AfdDispatchDeviceControl+0x3d:
f9638bef be100000c0 mov esi,0C0000010h
f9638bf4 897118 mov dword ptr [ecx+18h],esi
f9638bf7 8a15119062f9 mov dl,byte ptr [afd!AfdPriorityBoost (f9629011)]
f9638bfd ff158c8562f9 call dword ptr [afd!_imp_IofCompleteRequest (f962858c)]
f9638c03 8bc6 mov eax,esi
f9638c05 e9bd96ffff jmp afd!AfdDispatchDeviceControl+0x53 (f96322c7)
Déjà on a de la chance, la fonction est relativement simple, elle va prendre notre IoControlCode puis calculer l’indice d’une fonction dans une table appelée AfdIrpCallDispatch.
kd> dps afd!AfdIrpCallDispatch l 90
f96291b8 f962b23a afd!AfdBind
f96291bc f962aa43 afd!AfdConnect
f96291c0 f9627bff afd!AfdDispatchImmediateIrp
f96291c4 f963d5dc afd!AfdWaitForListen
f96291c8 f963c053 afd!AfdAccept
f96291cc f9634128 afd!AfdReceive
f96291d0 f9636833 afd!AfdReceiveDatagram
f96291d4 f9636ef2 afd!AfdSend
f96291d8 f9640e33 afd!AfdSendDatagram
f96291dc f9632a14 afd!AfdPoll
f96291e0 f9627bff afd!AfdDispatchImmediateIrp
f96291e4 f962b0b9 afd!AfdGetAddress
f96291e8 f9627bff afd!AfdDispatchImmediateIrp
f96291ec f9627bff afd!AfdDispatchImmediateIrp
f96291f0 f9627bff afd!AfdDispatchImmediateIrp
f96291f4 f9627bff afd!AfdDispatchImmediateIrp
f96291f8 f9627bff afd!AfdDispatchImmediateIrp
f96291fc f9627bff afd!AfdDispatchImmediateIrp
f9629200 f9627bff afd!AfdDispatchImmediateIrp
f9629204 f9627bff afd!AfdDispatchImmediateIrp
f9629208 f9627bff afd!AfdDispatchImmediateIrp
f962920c f9627bff afd!AfdDispatchImmediateIrp
f9629210 f9627bff afd!AfdDispatchImmediateIrp
f9629214 f9627bff afd!AfdDispatchImmediateIrp
f9629218 f9627bff afd!AfdDispatchImmediateIrp
f962921c f9627bff afd!AfdDispatchImmediateIrp
f9629220 f9627bff afd!AfdDispatchImmediateIrp
f9629224 f9627bff afd!AfdDispatchImmediateIrp
f9629228 f9627bff afd!AfdDispatchImmediateIrp
f962922c f9627bff afd!AfdDispatchImmediateIrp
f9629230 f9627bff afd!AfdDispatchImmediateIrp
f9629234 f9630781 afd!AfdTransmitFile
f9629238 f9636203 afd!AfdSuperAccept
f962923c f9627bff afd!AfdDispatchImmediateIrp
f9629240 f9627bff afd!AfdDispatchImmediateIrp
f9629244 f963bb7a afd!AfdDeferAccept
f9629248 f963d5dc afd!AfdWaitForListen
f962924c f963e563 afd!AfdSetQos
f9629250 f962e911 afd!AfdGetQos
f9629254 f962ede2 afd!AfdNoOperation
f9629258 f963f197 afd!AfdValidateGroup
f962925c f9627bff afd!AfdDispatchImmediateIrp
f9629260 f9627bff afd!AfdDispatchImmediateIrp
f9629264 f9627f6d afd!AfdRoutingInterfaceChange
f9629268 f9627bff afd!AfdDispatchImmediateIrp
f962926c f9635e99 afd!AfdAddressListChange
f9629270 f962dc7f afd!AfdJoinLeaf
f9629274 00000000
f9629278 f9630c88 afd!AfdTransmitPackets
f962927c f962d8c8 afd!AfdSuperConnect
f9629280 f96280d9 afd!AfdSuperDisconnect
f9629284 f9636833 afd!AfdReceiveDatagram
f9629288 f9627bff afd!AfdDispatchImmediateIrp
f962928c f9627bff afd!AfdDispatchImmediateIrp
f9629290 f9627bff afd!AfdDispatchImmediateIrp
f9629294 f9642e24 afd!AfdSanConnectHandler
f9629298 f9627bff afd!AfdDispatchImmediateIrp
f962929c f9627bff afd!AfdDispatchImmediateIrp
f96292a0 f9627bff afd!AfdDispatchImmediateIrp
f96292a4 f9627bff afd!AfdDispatchImmediateIrp
f96292a8 f9627bff afd!AfdDispatchImmediateIrp
f96292ac f9644693 afd!AfdSanAcquireContext
f96292b0 f9627bff afd!AfdDispatchImmediateIrp
f96292b4 f9627bff afd!AfdDispatchImmediateIrp
f96292b8 f9627bff afd!AfdDispatchImmediateIrp
f96292bc f9627bff afd!AfdDispatchImmediateIrp
f96292c0 f9631af8 afd!AfdSanAddrListChange
f96292c4 f963584b afd!AfdSocketCloseNotify
f96292c8 f9627bff afd!AfdDispatchImmediateIrp
f96292cc f962eead afd!AfdQueryFirewallSocketAddress
f96292d0 00000000
f96292d4 00000000
f96292d8 f963649b afd!AfdStartListen
f96292dc 00000000
f96292e0 00000000
f96292e4 00000000
f96292e8 00000000
f96292ec 00000000
f96292f0 00000000
f96292f4 00000000
f96292f8 f9635709 afd!AfdPartialDisconnect
f96292fc 00000000
f9629300 f9636ca4 afd!AfdQueryReceiveInformation
f9629304 f9629e62 afd!AfdQueryHandles
f9629308 f963348c afd!AfdSetInformation
f962930c f962ecf9 afd!AfdGetRemoteAddress
f9629310 f962bb35 afd!AfdGetContext
f9629314 f962a22c afd!AfdSetContext
f9629318 f963ed8d afd!AfdSetConnectData
f962931c f963ed8d afd!AfdSetConnectData
f9629320 f963ed8d afd!AfdSetConnectData
f9629324 f963ed8d afd!AfdSetConnectData
f9629328 f963e9ce afd!AfdGetConnectData
f962932c f963e9ce afd!AfdGetConnectData
f9629330 f963e9ce afd!AfdGetConnectData
f9629334 f963e9ce afd!AfdGetConnectData
f9629338 f963ed8d afd!AfdSetConnectData
f962933c f963ed8d afd!AfdSetConnectData
f9629340 f963ed8d afd!AfdSetConnectData
f9629344 f963ed8d afd!AfdSetConnectData
f9629348 f962b6cd afd!AfdGetInformation
f962934c 00000000
f9629350 00000000
f9629354 f96359c0 afd!AfdEventSelect
f9629358 f9635ad2 afd!AfdEnumNetworkEvents
f962935c 00000000
f9629360 00000000
f9629364 00000000
f9629368 00000000
f962936c 00000000
f9629370 00000000
f9629374 f963f369 afd!AfdGetUnacceptedConnectData
f9629378 f962f93d afd!AfdRoutingInterfaceQuery
f962937c 00000000
f9629380 f962bbd3 afd!AfdAddressListQuery
f9629384 00000000
f9629388 00000000
f962938c 00000000
f9629390 00000000
f9629394 00000000
f9629398 00000000
f962939c 00000000
f96293a0 f96438f6 afd!AfdSanFastCementEndpoint
f96293a4 f9643a8c afd!AfdSanFastSetEvents
f96293a8 f9643c2a afd!AfdSanFastResetEvents
f96293ac 00000000
f96293b0 f9643d7f afd!AfdSanFastCompleteAccept
f96293b4 f96443cb afd!AfdSanFastCompleteRequest
f96293b8 f9631261 afd!AfdSanFastCompleteIo
f96293bc f9643fe1 afd!AfdSanFastRefreshEndpoint
f96293c0 f96312ed afd!AfdSanFastGetPhysicalAddr
f96293c4 00000000
f96293c8 f9631c27 afd!AfdSanFastTransferCtx
f96293cc f96312fa afd!AfdSanFastGetServicePid
f96293d0 f963133a afd!AfdSanFastSetServiceProcess
f96293d4 f9631aab afd!AfdSanFastProviderChange
f96293d8 00000000
f96293dc 00000000
f96293e0 f962ebdd afd!AfdQueryFirewallSocketInfo
f96293e4 00000000
f96293e8 ffffffff
f96293ec ffffffff
f96293f0 ffffffff
f96293f4 ffffffff
Je pense que les noms des fonctions sont assez parlant :]
Revenons à notre AfdFastConnectionSend, je rappel juste la call stack qui nous a mené là :
kd> bp afd!AfdFastConnectionSend
kd> g
Breakpoint 1 hit
afd!AfdFastConnectionSend:
f9633da6 8bff mov edi,edi
kd> kv
ChildEBP RetAddr Args to Child
f615cb10 f962a5e5 81603ef0 f615cb9c 00000013 afd!AfdFastConnectionSend (FPO: [Non-Fpo])
f615cc50 8057fc67 815adc18 00000001 0012e9b4 afd!AfdFastIoDeviceControl+0x415 (FPO: [Non-Fpo])
f615cd00 8057fbfa 00000060 00000038 00000000 nt!IopXxxControlFile+0x261 (FPO: [Non-Fpo])
f615cd34 804df06b 00000060 00000038 00000000 nt!NtDeviceIoControlFile+0x2a (FPO: [Non-Fpo])
f615cd34 7c90eb94 00000060 00000038 00000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f615cd64)
0012e974 7c90d8ef 71a55908 00000060 00000038 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0012e978 71a55908 00000060 00000038 00000000 ntdll!ZwDeviceIoControlFile+0xc (FPO: [10,0,0])
0012ea04 71ab6294 00000060 0012ea8c 00000001 mswsock!WSPSend+0x16c (FPO: [Non-Fpo])
*** ERROR: Module load completed but symbols could not be loaded for rezo.exe
0012ea40 00400433 00000060 0012ea8c 00000001 WS2_32!WSASend+0x77 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ffc0 7c816d4f 00310039 00310037 7ffd7000 rezo+0x433
0012fff0 00000000 004002b0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
En observant le disass de AfdFastConnectionSend on peut voir que cette fonction appel le driver suivant, tcpip.sys (w00t on se rapproche de la fin). Voyons ce qu’on peut obtenir comme info sur ce dernier :
kd> !object global??tcp
Object: e1468cc0 Type: (817d2408) SymbolicLink
ObjectHeader: e1468ca8 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: e10012c8 Name: Tcp
Target String is 'DeviceTcp'
kd> !devobj DeviceTcp
Device object (814e3880) is for:
Tcp DriverTcpip DriverObject 81520688
Current Irp 00000000 RefCount 227 Type 00000012 Flags 00000050
Dacl e145b9bc DevExt 00000000 DevObjExt 814e3938
ExtensionFlags (0000000000)
Device queue is not busy.
kd> !drvobj DriverTcpip 3
Driver object (81520688) is for:
DriverTcpip
Driver Extension List: (id , addr)
Device Object list:
814c7bd8 814e3d80 814e3880 8156a920
814cc358
DriverEntry: f96b7bef tcpip!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: f96677aa tcpip!ArpUnload
Dispatch routines:
[00] IRP_MJ_CREATE f9644a3e tcpip!TCPDispatch
[01] IRP_MJ_CREATE_NAMED_PIPE f9644a3e tcpip!TCPDispatch
[02] IRP_MJ_CLOSE f9644a3e tcpip!TCPDispatch
[03] IRP_MJ_READ f9644a3e tcpip!TCPDispatch
[04] IRP_MJ_WRITE f9644a3e tcpip!TCPDispatch
[05] IRP_MJ_QUERY_INFORMATION f9644a3e tcpip!TCPDispatch
[06] IRP_MJ_SET_INFORMATION f9644a3e tcpip!TCPDispatch
[07] IRP_MJ_QUERY_EA f9644a3e tcpip!TCPDispatch
[08] IRP_MJ_SET_EA f9644a3e tcpip!TCPDispatch
[09] IRP_MJ_FLUSH_BUFFERS f9644a3e tcpip!TCPDispatch
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION f9644a3e tcpip!TCPDispatch
[0b] IRP_MJ_SET_VOLUME_INFORMATION f9644a3e tcpip!TCPDispatch
[0c] IRP_MJ_DIRECTORY_CONTROL f9644a3e tcpip!TCPDispatch
[0d] IRP_MJ_FILE_SYSTEM_CONTROL f9644a3e tcpip!TCPDispatch
[0e] IRP_MJ_DEVICE_CONTROL f9644a3e tcpip!TCPDispatch
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL f9644cc8 tcpip!TCPDispatchInternalDeviceControl
[10] IRP_MJ_SHUTDOWN f9644a3e tcpip!TCPDispatch
[11] IRP_MJ_LOCK_CONTROL f9644a3e tcpip!TCPDispatch
[12] IRP_MJ_CLEANUP f9644a3e tcpip!TCPDispatch
[13] IRP_MJ_CREATE_MAILSLOT f9644a3e tcpip!TCPDispatch
[14] IRP_MJ_QUERY_SECURITY f9644a3e tcpip!TCPDispatch
[15] IRP_MJ_SET_SECURITY f9644a3e tcpip!TCPDispatch
[16] IRP_MJ_POWER f9644a3e tcpip!TCPDispatch
[17] IRP_MJ_SYSTEM_CONTROL f9644a3e tcpip!TCPDispatch
[18] IRP_MJ_DEVICE_CHANGE f9644a3e tcpip!TCPDispatch
[19] IRP_MJ_QUERY_QUOTA f9644a3e tcpip!TCPDispatch
[1a] IRP_MJ_SET_QUOTA f9644a3e tcpip!TCPDispatch
[1b] IRP_MJ_PNP f9644a3e tcpip!TCPDispatch
Ok, notre petit IRP est envoyé à la fonction TCPDispatchInternalDeviceControl qui est chargée de handler les IRP de type IRP_MJ_INTERNAL_DEVICE_CONTROL.
kd> kv
ChildEBP RetAddr Args to Child
f9bfbaa0 804e3d77 815a0d80 815ff300 815e5738 tcpip!TCPDispatchInternalDeviceControl (FPO: [Non-Fpo])
f9bfbab0 f9633ede 00000000 00000008 f9bfbb10 nt!IopfCallDriver+0x31 (FPO: [0,0,0])
f9bfbb10 f962a5e5 81590918 f9bfbb9c 00000013 afd!AfdFastConnectionSend+0x209 (FPO: [Non-Fpo])
f9bfbc50 8057fc67 8159fbf8 00000001 0012fd48 afd!AfdFastIoDeviceControl+0x415 (FPO: [Non-Fpo])
f9bfbd00 8057fbfa 00000060 00000038 00000000 nt!IopXxxControlFile+0x261 (FPO: [Non-Fpo])
f9bfbd34 804df06b 00000060 00000038 00000000 nt!NtDeviceIoControlFile+0x2a (FPO: [Non-Fpo])
f9bfbd34 7c90eb94 00000060 00000038 00000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f9bfbd64)
0012fd08 7c90d8ef 71a55908 00000060 00000038 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0012fd0c 71a55908 00000060 00000038 00000000 ntdll!ZwDeviceIoControlFile+0xc (FPO: [10,0,0])
0012fd98 71ab42f8 00000060 0012fdd0 00000001 mswsock!WSPSend+0x16c (FPO: [Non-Fpo])
0012fde0 00400342 00000060 0012ff94 00000013 WS2_32!send+0x82 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ffc0 7c816d4f 00310039 00310037 7ffde000 rezo+0x342
0012fff0 00000000 00400220 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
Si on regarde un peu l’IRP qui est passé au driver tcpip.sys, on peut voir :
kd> !irp 815ff300
Irp is active with 3 stacks 3 is current (= 0x815ff3b8)
Mdl=815ff260: No System Buffer: Thread 00000000: Irp stack trace. Pending has been returned
cmd flg cl Device File Completion-Context
[ 0, 0] 0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[ 0, 0] 0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
>[ f, 7] 0 e0 815a0d80 815a77b8 f9633d47-815ff228 Success Error Cancel
DriverTcpip afd!AfdRestartBufferSend
Args: 00000013 00000000 00000000 00000000
Dans les arguments, la taille de nos datas et si on dump la mémoire référencé par le MDL :
kd> dt nt!_MDL 815ff260
+0x000 Next : (null)
+0x004 Size : 32
+0x006 MdlFlags : 4
+0x008 Process : (null)
+0x00c MappedSystemVa : 0x815ff280
+0x010 StartVa : 0x815ff000
+0x014 ByteCount : 0x13
+0x018 ByteOffset : 0x280
kd> db 0x815ff280
815ff280 47 45 54 20 2f 20 48 54-54 50 2f 31 2e 31 0d 0a GET / HTTP/1.1..
815ff290 0d 0a
On retrouve toujours nos petites datas qui voyagent tranquillement dans le noyau. Pour la suite, je ne me suis pas amusé à tracer, analyser le driver tcpip (351 ko !) avec IDA et vous comprendrez
À partir de là, on y va full instinct et on met des BP un peu partout ! ET ça marche, il suffit de choisir les fonctions avec les bons noms. Alors arrivé dans le driver NDIS (Network Driver Interface Specification) on a cette call stack :
Breakpoint 1 hit
NDIS!ndisMSend:
f98a3d33 8bff mov edi,edi
kd> kv
ChildEBP RetAddr Args to Child
f9acb788 f97b5528 8163e248 8179c310 8179c2d8 NDIS!ndisMSend (FPO: [Non-Fpo])
f9acb7c4 f988d985 8163caa8 8179c310 00000002 psched!MpSend+0x706 (FPO: [Non-Fpo])
f9acb7ec f964cd00 81699858 8179c310 815b9cc8 NDIS!ndisMSendX+0x1d6 (FPO: [Non-Fpo])
f9acb814 f964c8ce 815b9cc8 8179c310 81673160 tcpip!ARPSendData+0x198 (FPO: [Non-Fpo])
f9acb840 f964c70a 815b9cc8 f9acb800 00000001 tcpip!ARPTransmit+0x193 (FPO: [Non-Fpo])
f9acb870 f964c4ad 8159b4b0 0101a8c0 8179c310 tcpip!SendIPPacket+0x18e (FPO: [Non-Fpo])
f9acb9bc f9664427 f968a4b8 81558a28 815589c0 tcpip!IPTransmit+0x2859 (FPO: [Non-Fpo])
f9acba28 f96647b5 2ccbca7c 00000000 815fa3b8 tcpip!TCPSend+0x5d8 (FPO: [Non-Fpo])
f9acba50 f9663d97 00000001 00000000 00000000 tcpip!TdiSend+0x1cc (FPO: [Non-Fpo])
f9acba84 f9665977 815fa300 8159a3e4 815fa238 tcpip!TCPSendData+0x83 (FPO: [Non-Fpo])
f9acbaa0 804e3d77 816a9030 815fa300 81554430 tcpip!TCPDispatchInternalDeviceControl+0x51 (FPO: [Non-Fpo])
f9acbab0 f960bede 00000000 00000008 f9acbb10 nt!IopfCallDriver+0x31 (FPO: [0,0,0])
8167e130 8168f778 00000012 f9649000 00057a80 afd!AfdFastConnectionSend+0x209 (FPO: [Non-Fpo])
Remarquez que le packet a traversé la couche TDI (Transport Dispatch Interface) qu’on peut situer sur le schéma suivant :

Et là, grand bonheur, si on utilise la commande !ndiskd.pkt sur le second argument de la fonction ndisMsend on peut voir !
kd> !pkt 8179c310 5
NDIS_PACKET at 8179c310
MDL = 81511448
StartVa ffffffff81511000, ByteCount 0xe, ByteOffset 0x468, NB MdlOffset 0x0
81511468: 00 06 5b 39 5b b9 00 03 ff 6c d1 53 08 00
MDL = 815122c4
StartVa ffffffff81512000, ByteCount 0x14, ByteOffset 0x2e4, NB MdlOffset 0x0
815122e4: 45 00 00 3b 01 c5 40 00 80 06 dd b6 c0 a8 01 dc
815122f4: d1 55 87 67
MDL = 815589c0
StartVa ffffffff81558000, ByteCount 0x14, ByteOffset 0xa00, NB MdlOffset 0x0
81558a00: 04 28 00 50 2c cb ca 7c 50 74 0a eb 50 18 ff ff
81558a10: 5e b8 00 00
MDL = 815fa3dc
StartVa ffffffff815fa000, ByteCount 0x13, ByteOffset 0x280, NB MdlOffset 0x0
815fa280: 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a
815fa290: 0d 0a 00
0012EA74 47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A GET / HTTP/1.1..
0012EA84 0D 0A 00 00 ....
HOO le joli packet, avec toutes ses couches, ethernet, IP, TCP et les datas. Si c’est pas beau tout ça :] Mais évidemment, rien n’est fini, le packet n’a toujours pas été transmis au driver de la carte réseau. Avant cela ndisMSend va mettre dans une liste d’attente notre packet avec la fonction ndisMQueueWorkItem, les packets seront pris en compte plus tard lors de la prise en charge de la liste des DPC.
kd> kv
ChildEBP RetAddr Args to Child
f9e67f68 f98a3a6e 81639000 81637f30 00000000 dc21x4!DC21X4Send (FPO: [Non-Fpo])
f9e67f94 f98a7f0f 00000000 81678590 81639008 NDIS!ndisMStartSends+0xd7 (FPO: [Non-Fpo])
f9e67fb8 f98a975b 10b8cbb0 00000005 ffdff000 NDIS!ndisMProcessDeferred+0x39 (FPO: [Non-Fpo])
f9e67fd0 804dc179 8163901c 81639008 00000000 NDIS!ndisMDpc+0x148 (FPO: [Non-Fpo])
f9e67ff4 804dbe2d f62f985c 00000000 00000000 nt!KiRetireDpcList+0x46 (FPO: [0,0,0])
f9e67ff8 f62f985c 00000000 00000000 00000000 nt!KiDispatchInterrupt+0x2a (FPO: [Uses EBP] [0,0,1])
Le bout de code intéressant de DC21X4Send faisant partie du driver DC21X4.sys qui est le NDIS 5.0 DC21X4 miniport driver de virtual PC est :
push 1 ; Value
push dword ptr [esi+234h] ; Port
call ds:__imp__WRITE_PORT_ULONG@8 ; WRITE_PORT_ULONG(x,x)
Le call à WRITE_PORT_ULONG va faire appel au hal :
in hal.dll
; ULONG __stdcall READ_PORT_ULONG(PULONG Port)
public _READ_PORT_ULONG@4
_READ_PORT_ULONG@4 proc near
Port= dword ptr 4
mov edx, [esp+Port]
in eax, dx
retn 4
_READ_PORT_ULONG@4 endp
Pour enfin dire au bon I/O port de la carte réseau qu’un packet est prèt à être envoyé.
Voilà, c’est fini, j’ai gagné un bon mal de crâne, mais j’ai à peu près compris comment sont agencés les différentes couches réseau sous Windows. Même si j’ai un peu bâclé la fin, j’espère que cela vous inspira pour faire mumuse avec votre OS préféré
Quelques liens en vrac :
http://www.reactos.org/serendipity/index.php?/archives/9-Winsock-Architecture-Specification-WS2_32.DLL.html
http://msdn2.microsoft.com/en-us/library/ms740650.aspx
http://www.microsoft.com/msj/0599/LayeredService/LayeredService.aspx
http://mi.cnrs-orleans.fr/Security/Win2k/TCPIP/Win2k_TCPIP1.htm
http://2005.recon.cx/recon2005/papers/Jonathan_Levin/The%20Dark%20Side%20of%20Winsock.pdf
http://www.microsoft.com/msj/0599/LayeredService/LayeredService.aspx
http://en.wikipedia.org/wiki/Winsock
http://fr.wikipedia.org/wiki/Ancillary_Function_Driver
http://www.codeproject.com/system/driverdev2.asp
http://www.codeproject.com/system/driverdev5asp.asp
http://en.wikipedia.org/wiki/Transport_Dispatch_Interface
http://en.wikipedia.org/wiki/Network_Driver_Interface_Specification
http://ivanlef0u.free.fr/?p=5
juillet 10th, 2007