Breaking Micosoft Lunix bei HackQuest 2019


Hallo Habr!

Bei HackQuest gab es vor der ZeroNight 2019-Konferenz eine unterhaltsame Aufgabe. Ich habe die Entscheidung nicht rechtzeitig getroffen, aber ich habe meinen Teil des Nervenkitzels erhalten. Ich denke, Sie werden interessiert sein, was die Organisatoren und das r0.Crew-Team für die Teilnehmer vorbereitet haben.

Aufgabe: Holen Sie sich einen Aktivierungscode für das geheime Betriebssystem Micosoft 1998 .

In diesem Artikel werde ich Ihnen erklären, wie es geht.

Inhalt


0. Aufgabe
1. Werkzeuge
2. Überprüfen Sie das Bild
3. Zeichengeräte und der Kernel
4. Suchen Sie register_chrdev
4.1. Vorbereiten eines neuen minimalen Linux-Images
4.2. Noch ein paar Vorbereitungen
4.3. Deaktivieren Sie KASLR auf lunix
4.4. Wir suchen und finden eine Unterschrift
5. Suchen Sie nach Fops aus / dev / activ und der Schreibfunktion
6. Wir studieren schreiben
6.1. Hash-Funktion
6.2. Algorithmus zur Schlüsselgenerierung
6.3. Keygen

Herausforderung


Für ein in QEMU gestartetes Image sind E-Mail und ein Aktivierungsschlüssel erforderlich. Wir kennen die Mail bereits, suchen wir den Rest!

1. Werkzeuge


  • Gdb
  • QEMU
  • binwalk
  • IDA

In ~/.gdbinit Sie eine nützliche Funktion schreiben:

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

2. Überprüfen Sie das Bild


Benennen Sie zuerst jD74nd8_task2.iso in lunix.iso um.

Bei Verwendung von binwalk sehen wir, dass es ein Skript mit dem Offset 0x413000 . Dieses Skript überprüft Mail und Schlüssel:


Wir brechen die Prüfung mit dem Hex-Editor direkt im Bild ab und lassen das Skript unsere Befehle ausführen. Wie es jetzt aussieht:


Beachten Sie, dass Sie die activated Linie activ mussten, um sie zu activated , damit die Bildgröße gleich bleibt. Zum Glück gibt es keine Hash-Prüfung. Das Bild heißt lunix_broken_activation.iso.

Führen Sie es durch QEMU:

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

Lassen Sie uns hinein graben:


Also haben wir:

  1. Distribution - Minimal Linux 5.0.11.
  2. Das Zeichengerät /dev/activate prüft E-Mails, den Schlüssel, was bedeutet, dass die Verifizierungslogik irgendwo im Darm des Kernels gesucht werden muss.
  3. Mail, Schlüssel werden im email|key .

Das Image target_broken_activation.iso wird nicht mehr benötigt.

3. Zeichengeräte und der Kernel


Geräte wie /dev/mem , /dev/vcs , /dev/activate /dev/vcs usw. Registrieren Sie sich mit der Funktion register_chrdev :

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

name ist der Name und die fops Struktur enthält Zeiger auf Treiberfunktionen:

 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 *); }; 

Diese Funktion interessiert uns nur:

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

Hier ist das zweite Argument der Puffer mit den übertragenen Daten, das nächste die Größe des Puffers.

4. Suchen Sie register_chrdev


Standardmäßig kompiliert Minimal Linux mit deaktivierten Debugging-Informationen, um die Größe des Images zu verringern, jedoch minimal. Daher können Sie den Debugger nicht einfach starten und die Funktion anhand des Namens suchen. Aber es ist durch Unterschrift möglich.

Die Signatur befindet sich im Minimal Linux-Image mit Debugging-Informationen. Im Allgemeinen müssen Sie Ihr Minimal erstellen.

Das heißt, das Schema ist wie folgt:

  Minimal Linux ->   register_chrdev ->  ->   register_chrdev  Lunix 

4.1. Vorbereiten eines neuen minimalen Linux-Images


  1. Installieren Sie die erforderlichen Werkzeuge:
     sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev 
  2. Skripte herunterladen:

     git clone https://github.com/ivandavidov/minimal cd minimal/src 
  3. Richtig 02_build_kernel.sh :
    lösche es

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

    füge es hinzu

     echo "CONFIG_GDB_SCRIPTS=y" >> .config 

  4. Kompilieren

     ./build_minimal_linux_live.sh 

Das Bild ist minimal / src / minimal_linux_live.iso.

4.2. Noch ein paar Vorbereitungen


Entpacken Sie minimal_linux_live.iso in den Ordner minimal / src / iso.

Die minimale Datei / src / iso / boot rootfs.xz Kernel- kernel.xz rootfs.xz und das FS- rootfs.xz . Benennen Sie sie in kernel.minimal.xz , rootfs.minimal.xz .

Außerdem müssen Sie den Kern aus dem Bild ziehen. Das Skript extract-vmlinux hilft dabei:

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

Jetzt haben wir im Ordner minimal / src / iso / boot diesen Satz: kernel.minimal.xz , rootfs.minimal.xz , vmlinux.minimal .

Aber von lunix.iso brauchen wir nur den Kernel. Daher führen wir alle gleichen Operationen aus, wir rufen den vmlinux.lunix kernel.xz , vergessen kernel.xz , rootfs.xz , jetzt werde ich Ihnen sagen warum.

4.3. Deaktivieren Sie KASLR auf lunix


Ich habe es geschafft, KASLR im Fall von frisch zusammengestelltem Minimal Linux in QEMU zu deaktivieren.
Aber mit Lunix hat es nicht geklappt. Daher müssen Sie das Bild selbst bearbeiten.

Öffnen Sie dazu einen Hex-Editor, suchen Sie die Zeile "APPEND vga=normal" und ersetzen Sie sie durch "APPEND nokaslr\x20\x20\x20" .

Und das Bild heißt lunix_nokaslr.iso.

4.4. Wir suchen und finden eine Unterschrift


Wir starten neues Minimal Linux in einem Terminal:

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

In einem anderen Debugger:

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

Suchen Sie nun in der Liste der Funktionen nach register_chrdev :


Offensichtlich ist unsere Option __register_chrdev .
Wir sind nicht verwirrt, dass wir register_chrdev durchsucht, sondern __register_chrdev gefunden haben

Zerlegen:


Welche Unterschrift soll ich nehmen? Ich habe verschiedene Optionen ausprobiert und mich für das folgende Stück entschieden:

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


Tatsache ist, dass es in lunix nur eine Funktion gibt, die 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 .

Jetzt werde ich zeigen, aber zuerst finden wir heraus, in welchem ​​Segment wir danach suchen müssen.


Die Funktion __register_chrdev Adresse 0xffffffff811c9720 , dies ist das .text . Dort werden wir schauen.

Trennen Sie die Verbindung von der Referenz Minimal Linux. Stellen Sie jetzt eine Verbindung zu lunix her.

In einem Terminal:

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

In einem anderen:

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

Wir betrachten die Grenzen des .text Segments:


Rahmen 0xffffffff81000000 - 0xffffffff81600b91 , suchen Sie nach 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 :


Wir finden das Stück unter der Adresse 0xffffffff810dc643 . Dies ist jedoch nur ein Teil der Funktion. Lassen Sie uns sehen, was oben steht:


Und hier ist der Beginn der Funktion 0xffffffff810dc5d0 (weil retq der Ausgang der benachbarten Funktion ist).

5. Suche nach Fops aus / dev / enable


Der Prototyp der Funktion register_chrdev lautet:

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

Wir brauchen eine fops Struktur.

Debugger und QEMU neu starten. Wir machen 0xffffffff810dc5d0 Pause auf 0xffffffff810dc5d0 . Es wird mehrmals funktionieren. Dies sind die Geräte mem, vcs, cpu/msr, cpu/cpuid , activate unmittelbar danach activate werden.


Der Zeiger auf den Namen wird im rcx gespeichert. Und der Zeiger auf fops ist in r8 :


Ich erinnere Strukturfops
 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 *); }; 


Die Adresse der write lautet also 0xffffffff811f068f .

6. Wir studieren schreiben


Die Funktion enthält mehrere interessante Blöcke. Es lohnt sich nicht, jeden Haltepunkt genau dort zu beschreiben, es ist eine übliche Routine. Darüber hinaus sind die Berechnungsblöcke mit bloßem Auge sichtbar.

6.1. Hash-Funktion


Öffnen vmlinux.lunix die IDA, laden vmlinux.lunix den Kernel vmlinux.lunix und sehen, was die Schreibfunktion enthält.

Das erste, was zu bemerken ist, ist dieser Zyklus:


sub_FFFFFFFF811F0413 Funktion sub_FFFFFFFF811F0413 , die folgendermaßen beginnt:


Und an der Adresse 0xffffffff81829ce0 wird eine Tabelle für sha256 erkannt:


Das heißt, sub_FFFFFFFF811F0413 = sha256. Die Bytes, deren Hash abgerufen werden muss, werden über $sp+0x50+var49 , und das Ergebnis wird bei $sp+0x50+var48 . Übrigens, var49=-0x49 , var48=-0x48 , also $sp+0x50+var49 = $sp+0x7 , $sp+0x50+var48 = $sp+0x8 .

Schau es dir an.

Wir starten qemu, gdb, setzen eine Pause auf 0xffffffff811f0748 call sub_FFFFFFFF811F0413 und auf die Anweisung 0xffffffff811f074d xor ecx, ecx , die unmittelbar hinter der Funktion steht. test@mail.ru Mail test@mail.ru , Passwort 1234-5678-0912-3456 .

Das Mail-Byte wird an die Funktion übergeben. Das Ergebnis ist:


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

Das heißt, ja, es ist wirklich sha256, nur berechnet es Hashes für alle Bytes von Mail und nicht nur einen Hash von Mail.

Dann werden die Hashes byteweise summiert. Wenn die Summe jedoch größer als 0xEC , wird der Rest der Division durch 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 

Der Betrag wird bei 0xffffffff81c82f80 . Mal sehen, wie der Hash von test@mail.ru .

Wir ffffffff811f0786 dec r13d Pause für ffffffff811f0786 dec r13d (dies ist der Ausgang aus der Schleife):


Und vergleiche mit:

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

Aber der Hash selbst ist eindeutig etwas lang für den Schlüssel.

6.2. Algorithmus zur Schlüsselgenerierung


Der Schlüssel ist für diesen Code verantwortlich:


Hier ist die endgültige Berechnung jedes Bytes:

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

In eax und r12d Hash-Bytes werden sie multipliziert, und dann wird der Rest der Division durch 9 genommen.

Weil


Und Bytes werden in unerwarteter Reihenfolge genommen. Ich werde es in keygen anzeigen.

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)] 

Lassen Sie uns also einen Schlüssel generieren:

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


Und jetzt können Sie sich entspannen und das Spiel 2048 spielen :) Vielen Dank für Ihre Aufmerksamkeit! Code hier

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


All Articles