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/activate
prü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