Rompiendo Micosoft Lunix en HackQuest 2019


Hola Habr!

En HackQuest, antes de la conferencia ZeroNight 2019, hubo una tarea entretenida. No aprobé la decisión a tiempo, pero recibí mi parte de emoción. Creo que le interesará saber lo que los organizadores y el equipo de r0.Crew han preparado para los participantes.

Tarea: obtenga un código de activación para el sistema operativo secreto Micosoft 1998 .

En este artículo te diré cómo hacerlo.

Contenido


0. Tarea
1. Herramientas
2. Inspeccione la imagen.
3. Dispositivos de caracteres y el núcleo.
4. Buscar register_chrdev
4.1. Preparación de una imagen mínima fresca de Linux
4.2. Algunas preparaciones más
4.3. Deshabilitar KASLR en lunix
4.4. Buscamos y encontramos una firma
5. Busque saltos desde / dev / active y la función de escritura
6. Estudiamos escribir
6.1. Función hash
6.2. Algoritmo de Generación Clave
6.3. Keygen

Desafío


Una imagen lanzada en QEMU requiere correo y una clave de activación. Ya conocemos el correo, ¡busquemos el resto!

1. Herramientas


  • Gdb
  • QEMU
  • Binwalk
  • IDA

En ~/.gdbinit necesitas escribir una función útil:

 define xxd dump binary memory dump.bin $arg0 $arg0+$arg1 shell xxd dump.bin end 

2. Inspeccione la imagen.


Primero cambie el nombre de jD74nd8_task2.iso a lunix.iso.

Usando binwalk, vemos que hay un script en el desplazamiento 0x413000 . Este script verifica el correo y la clave:


Rompemos la verificación con el editor hexadecimal directamente en la imagen y hacemos que el script ejecute nuestros comandos. Cómo se ve ahora:


Tenga en cuenta que tuvo que recortar la línea activated para activated para que el tamaño de la imagen permanezca igual. Afortunadamente, no hay verificación de hash. La imagen se llama lunix_broken_activation.iso.

Ejecútelo a través de QEMU:

 sudo qemu-system-x86_64 lunix_broken_activation.iso -enable-kvm 

Vamos a cavar adentro:


Entonces tenemos:

  1. Distribución - Mínimo Linux 5.0.11.
  2. El dispositivo de caracteres /dev/activate se dedica a verificar el correo, la clave, lo que significa que la lógica de verificación debe buscarse en algún lugar de las entrañas del núcleo.
  3. Correo, la clave se transmite en formato de email|key .

La imagen target_broken_activation.iso ya no será necesaria.

3. Dispositivos de caracteres y el núcleo.


Dispositivos como /dev/mem , /dev/vcs , /dev/activate /dev/vcs , etc. regístrese utilizando la función register_chrdev :

 int register_chrdev (unsigned int major, const char * name, const struct fops); 

name es el nombre, y la estructura fops contiene punteros a las funciones del controlador:

 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 

Solo nos interesa esta función:

 ssize_t (*write) (struct file *, const char *, size_t, loff_t *); 

Aquí, el segundo argumento es el búfer con los datos transferidos, el siguiente es el tamaño del búfer.

4. Buscar register_chrdev


De forma predeterminada, Minimal Linux compila con información de depuración deshabilitada para reducir el tamaño de la imagen, pero mínima. Por lo tanto, no puede simplemente iniciar el depurador y buscar la función por nombre. Pero es posible por firma.

Y la firma está en la imagen mínima de Linux con información de depuración incluida. En general, necesitas construir tu Minimal.

Es decir, el esquema es el siguiente:

  Minimal Linux ->   register_chrdev ->  ->   register_chrdev  Lunix 

4.1. Preparación de una imagen mínima fresca de Linux


  1. Instale las herramientas necesarias:
     sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev 
  2. Descargando guiones:

     git clone https://github.com/ivandavidov/minimal cd minimal/src 
  3. Corregir 02_build_kernel.sh :
    bórralo

     # Disable debug symbols in kernel => smaller kernel binary. sed -i "s/^CONFIG_DEBUG_KERNEL.*/\\# CONFIG_DEBUG_KERNEL is not set/" .config 

    agregarlo

     echo "CONFIG_GDB_SCRIPTS=y" >> .config 

  4. Compilando

     ./build_minimal_linux_live.sh 

La imagen es minimal / src / minimal_linux_live.iso.

4.2. Algunas preparaciones más


Descomprima minimal_linux_live.iso en la carpeta minimal / src / iso.

El archivo minimal / src / iso / boot rootfs.xz kernel.xz núcleo rootfs.xz y la rootfs.xz FS. Cambie el nombre a kernel.minimal.xz , rootfs.minimal.xz .

Además, debe extraer el núcleo de la imagen. El script extract-vmlinux ayudará con esto:

 extract-vmlinux kernel.minimal.xz > vmlinux.minimal 

Ahora en la carpeta minimal / src / iso / boot tenemos este conjunto: kernel.minimal.xz , rootfs.minimal.xz , vmlinux.minimal .

Pero de lunix.iso solo necesitamos el kernel. Por lo tanto, realizamos las mismas operaciones, llamamos al vmlinux.lunix kernel.xz , olvídate de kernel.xz , rootfs.xz , ahora te diré por qué.

4.3. Deshabilitar KASLR en lunix


Logré deshabilitar KASLR en el caso de Minimal Linux recién ensamblado en QEMU.
Pero no funcionó con Lunix. Por lo tanto, debe editar la imagen en sí.

Para hacer esto, ábralo en un editor hexadecimal, busque la línea "APPEND vga=normal" y reemplácela con "APPEND nokaslr\x20\x20\x20" .

Y la imagen se llama lunix_nokaslr.iso.

4.4. Buscamos y encontramos una firma


Lanzamos Minimal Linux nuevo en una terminal:

 sudo qemu-system-x86_64 -kernel kernel.minimal.xz -initrd rootfs.minimal.xz -append nokaslr -s 

En otro depurador:

 sudo gdb vmlinux.minimal (gdb) target remote localhost:1234 

Ahora busque register_chrdev en la lista de funciones:


Obviamente, nuestra opción es __register_chrdev .
No nos confunde que buscamos register_chrdev, pero encontramos __register_chrdev

Desmontaje


¿Qué firma tomar? Probé varias opciones y me decidí por la siguiente pieza:

  0xffffffff811c9785 <+101>: shl $0x14,%esi 0xffffffff811c9788 <+104>: or %r12d,%esi 


El hecho es que en lunix solo hay una función que contiene 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 .

Ahora lo mostraré, pero primero descubriremos en qué segmento buscarlo.


La función __register_chrdev dirección 0xffffffff811c9720 , este es el segmento .text . Ahí vamos a mirar.

Desconectarse de la referencia Minimal Linux. Conéctese a lunix ahora.

En una terminal:

 sudo qemu-system-x86_64 lunix_nokaslr.iso -s -enable-kvm 

En otro:

 sudo gdb vmlinux.lunix (gdb) target remote localhost:1234 

Nos fijamos en los límites del segmento .text :


Fronteras 0xffffffff81000000 - 0xffffffff81600b91 , busque 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 :


Encontramos la pieza en la dirección 0xffffffff810dc643 . Pero esto es solo una parte de la función, veamos lo que está arriba:


Y aquí está el comienzo de la función 0xffffffff810dc5d0 (porque retq es la salida de la función vecina).

5. Buscar saltos desde / dev / activar


El prototipo de la función register_chrdev es este:

 int register_chrdev (unsigned int major, const char * name, const struct fops); 

Necesitamos una estructura de fops .

Reiniciando el depurador y QEMU. Pusimos 0xffffffff810dc5d0 descanso en 0xffffffff810dc5d0 . Funcionará varias veces. Estos son los dispositivos mem, vcs, cpu/msr, cpu/cpuid , y se activate inmediatamente después de ellos.


El puntero al nombre se almacena en el rcx . Y el puntero a fops está en r8 :


Recuerdo estructura fops
 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 


Entonces, la dirección de la función de write es 0xffffffff811f068f .

6. Estudiamos escribir


La función incluye varios bloques interesantes. No vale la pena describir cada punto de interrupción, es una rutina habitual. Además, los bloques de cálculos son visibles a simple vista.

6.1. Función hash


vmlinux.lunix el IDA, vmlinux.lunix el kernel vmlinux.lunix y veamos qué tiene dentro la función de escritura.

Lo primero que debe notar es este ciclo:


sub_FFFFFFFF811F0413 se sub_FFFFFFFF811F0413 función sub_FFFFFFFF811F0413 , que comienza así:


Y en la dirección 0xffffffff81829ce0 , se 0xffffffff81829ce0 una tabla para sha256:


Es decir, sub_FFFFFFFF811F0413 = sha256. Los bytes cuyo hash debe obtenerse se transmiten a través de $sp+0x50+var49 , y el resultado se almacena en $sp+0x50+var48 . Por cierto, var49=-0x49 , var48=-0x48 , entonces $sp+0x50+var49 = $sp+0x7 , $sp+0x50+var48 = $sp+0x8 .

Compruébalo

Comenzamos qemu, gdb, establecemos un descanso en 0xffffffff811f0748 call sub_FFFFFFFF811F0413 y en la instrucción 0xffffffff811f074d xor ecx, ecx , que está inmediatamente detrás de la función. test@mail.ru correo test@mail.ru , contraseña 1234-5678-0912-3456 .

El byte de correo se pasa a la función, y el resultado es este:


 >>> import hashlib >>> hashlib.sha256(b"t").digest().hex() 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8' >>> 

Es decir, sí, realmente es sha256, solo que calcula los hash para todos los bytes de correo, y no solo un hash del correo.

Luego los hash se suman por byte. Pero si la suma es mayor que 0xEC , entonces se 0xEC el resto de la división por 0xEC :

 import hashlib def get_email_hash(email): h = [0]*32 for sym in email: sha256 = hashlib.sha256(sym.encode()).digest() for i in range(32): s = h[i] + sha256[i] if s <= 0xEC: h[i] = s else: h[i] = s % 0xEC return h 

El importe se guarda en 0xffffffff81c82f80 . Veamos cuál test@mail.ru el hash de test@mail.ru .

Ponemos ffffffff811f0786 dec r13d descanso en ffffffff811f0786 dec r13d (esta es la salida del bucle):


Y comparar con:

 >>> get_email_hash('test@mail.ru') 2b902daf5cc483159b0a2f7ed6b593d1d56216a61eab53c8e4b9b9341fb14880 

Pero el hash en sí es claramente un poco largo para la clave.

6.2. Algoritmo de Generación Clave


La clave es responsable de este código:


Aquí está el cálculo final de cada byte:

 0xFFFFFFFF811F0943 imul eax, r12d 0xFFFFFFFF811F0947 cdq 0xFFFFFFFF811F0948 idiv r10d 

En los bytes hash eax y r12d , se multiplican y luego se toma el resto de la división entre 9.

Porque


Y los bytes se toman en orden inesperado. Lo indicaré en keygen.

6.3. Keygen


 def keygen(email): email_hash = get_email_hash(email) pairs = [(0x00, 0x1c), (0x1f, 0x03), (0x01, 0x1d), (0x1e, 0x02), (0x04, 0x18), (0x1b, 0x07), (0x05, 0x19), (0x1a, 0x06), (0x08, 0x14), (0x17, 0x0b), (0x09, 0x15), (0x16, 0x0a), (0x0c, 0x10), (0x13, 0x0f), (0x0d, 0x11), (0x12, 0x0e)] key = [] for pair in pairs: i = pair[0] j = pair[1] key.append((email_hash[i] * email_hash[j])%9) return [''.join(map(str, key[i:i+4])) for i in range(0, 16, 4)] 

Entonces, generemos alguna clave:

 >>> import lunix >>> lunix.keygen("m.gayanov@gmail.com") ['0456', '3530', '0401', '2703'] 


Y ahora puedes relajarte y jugar el juego 2048 :) ¡Gracias por tu atención! Codigo aqui

Source: https://habr.com/ru/post/471778/


All Articles