Archive for juillet, 2007

New Registry Hiding Method

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

4 comments juillet 25th, 2007

SSDT Hooking Reinvented

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

14 comments juillet 23rd, 2007

Trip in da TCP/IP

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 :

tdi1.bmp

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

9 comments juillet 10th, 2007


Calendar

juillet 2007
L Ma Me J V S D
« juin   août »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Posts by Month

Posts by Category