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. Werkzeuge2. Überprüfen Sie das Bild3. Zeichengeräte und der Kernel4. Suchen Sie register_chrdev4.1. Vorbereiten eines neuen minimalen Linux-Images4.2. Noch ein paar Vorbereitungen4.3. Deaktivieren Sie KASLR auf lunix4.4. Wir suchen und finden eine Unterschrift5. Suchen Sie nach Fops aus / dev / activ und der Schreibfunktion6. Wir studieren schreiben6.1. Hash-Funktion6.2. Algorithmus zur Schlüsselgenerierung6.3. KeygenHerausforderung
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
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:
- Distribution - Minimal Linux 5.0.11.
- Das Zeichengerät /dev/activateprüft E-Mails, den Schlüssel, was bedeutet, dass die Verifizierungslogik irgendwo im Darm des Kernels gesucht werden muss.
- 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
- Installieren Sie die erforderlichen Werkzeuge:
  sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev
 
- Skripte herunterladen:
 
  git clone https://github.com/ivandavidov/minimal cd minimal/src
 
- 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
 
 
- 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 habenZerlegen:
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