Bonjour, Habr!
À
HackQuest, avant la conférence ZeroNight 2019, il y avait une mission divertissante. Je n'ai pas rendu la décision à temps, mais j'ai reçu ma part de sensations fortes. Je pense que vous serez intéressé de savoir ce que les organisateurs et l'équipe de r0.Crew ont préparé pour les participants.
Tâche: obtenir un code d'activation pour le système d'exploitation secret
Micosoft 1998 .
Dans cet article, je vais vous expliquer comment procéder.
Table des matières
0. Tâche
1. Outils2. Inspectez l'image3. Périphériques de caractères et noyau4. Recherchez register_chrdev4.1. Préparation d'une nouvelle image Linux minimale4.2. Quelques préparations supplémentaires4.3. Désactiver KASLR sur lunix4.4. Nous recherchons et trouvons une signature5. Recherchez les fops depuis / dev / activate et la fonction d'écriture6. Nous étudions écrire6.1. Fonction de hachage6.2. Algorithme de génération de clés6.3. KeygenDéfi
Une image lancée dans QEMU nécessite un mail et une clé d'activation. Nous connaissons déjà le courrier, cherchons le reste!
1. Outils
Dans
~/.gdbinit
vous devez écrire une fonction utile:
define xxd dump binary memory dump.bin $arg0 $arg0+$arg1 shell xxd dump.bin end
2. Inspectez l'image
Renommez d'abord jD74nd8_task2.iso en lunix.iso.
En utilisant binwalk, nous voyons qu'il existe un script à l'offset
0x413000
. Ce script vérifie le courrier et la clé:
Nous cassons le contrôle avec l'éditeur hexadécimal directement dans l'image et faisons exécuter par le script nos commandes. À quoi il ressemble maintenant:
Notez que vous avez dû couper la ligne
activated
pour l'
activated
afin que la taille de l'image reste la même. Heureusement, il n'y a pas de contrôle de hachage. L'image s'appelle lunix_broken_activation.iso.
Exécutez-le via QEMU:
sudo qemu-system-x86_64 lunix_broken_activation.iso -enable-kvm
Creusons à l'intérieur:
Nous avons donc:
- Distribution - Minimal Linux 5.0.11.
- Le caractère device
/dev/activate
est engagé dans la vérification du courrier, la clé, ce qui signifie que la logique de vérification doit être recherchée quelque part dans les entrailles du noyau. - Le courrier, la clé sont transmis au format
email|key
.
L'image target_broken_activation.iso ne sera plus requise.
3. Périphériques de caractères et noyau
Des périphériques comme
/dev/mem
,
/dev/vcs
,
/dev/activate
, etc. inscrivez-vous en utilisant la fonction
register_chrdev
:
int register_chrdev (unsigned int major, const char * name, const struct fops);
name
est le nom, et la structure
fops
contient des pointeurs vers les fonctions du pilote:
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 *); };
Nous ne sommes intéressés que par cette fonction:
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
Ici, le deuxième argument est le tampon avec les données transférées, le suivant est la taille du tampon.
4. Recherchez register_chrdev
Par défaut, Minimal Linux compile avec des informations de débogage désactivées pour réduire la taille de l'image, mais minime. Par conséquent, vous ne pouvez pas simplement démarrer le débogueur et rechercher la fonction par son nom. Mais c'est possible par signature.
Et la signature est dans l'image Linux minimale avec des informations de débogage incluses. En général, vous devez construire votre Minimal.
Autrement dit, le schéma est le suivant:
Minimal Linux -> register_chrdev -> -> register_chrdev Lunix
4.1. Préparation d'une nouvelle image Linux minimale
- Installez les outils nécessaires:
sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev
- Téléchargement de scripts:
git clone https://github.com/ivandavidov/minimal cd minimal/src
- Corrigez
02_build_kernel.sh
:
le supprimer
# Disable debug symbols in kernel => smaller kernel binary. sed -i "s/^CONFIG_DEBUG_KERNEL.*/\\# CONFIG_DEBUG_KERNEL is not set/" .config
ajoutez-le
echo "CONFIG_GDB_SCRIPTS=y" >> .config
- Compilation
./build_minimal_linux_live.sh
L'image est minimal / src / minimal_linux_live.iso.
4.2. Quelques préparations supplémentaires
Décompressez minimal_linux_live.iso dans le dossier minimal / src / iso.
Le fichier minimal / src / iso / boot
rootfs.xz
kernel.xz
noyau
rootfs.xz
et l'
rootfs.xz
FS. Renommez-les en
kernel.minimal.xz
,
rootfs.minimal.xz
.
De plus, vous devez retirer le noyau de l'image. Le script
extract-vmlinux vous y aidera:
extract-vmlinux kernel.minimal.xz > vmlinux.minimal
Maintenant, dans le dossier minimal / src / iso / boot, nous avons cet ensemble:
kernel.minimal.xz
,
rootfs.minimal.xz
,
vmlinux.minimal
.
Mais à partir de lunix.iso, nous n'avons besoin que du noyau. Par conséquent, nous
vmlinux.lunix
toutes les mêmes opérations, nous appelons le
vmlinux.lunix
kernel.xz
, oublions
kernel.xz
,
rootfs.xz
, maintenant je vais vous dire pourquoi.
4.3. Désactiver KASLR sur lunix
J'ai réussi à désactiver KASLR dans le cas de Linux minimal fraîchement assemblé dans QEMU.
Mais cela n'a pas fonctionné avec Lunix. Par conséquent, vous devez modifier l'image elle-même.
Pour ce faire, ouvrez-le dans un éditeur hexadécimal, recherchez la ligne
"APPEND vga=normal"
et remplacez-la par
"APPEND nokaslr\x20\x20\x20"
.
Et l'image s'appelle lunix_nokaslr.iso.
4.4. Nous recherchons et trouvons une signature
Nous lançons un nouveau Linux minimal dans un seul terminal:
sudo qemu-system-x86_64 -kernel kernel.minimal.xz -initrd rootfs.minimal.xz -append nokaslr -s
Dans un autre débogueur:
sudo gdb vmlinux.minimal (gdb) target remote localhost:1234
Recherchez maintenant
register_chrdev
dans la liste des fonctions:
De toute évidence, notre option est
__register_chrdev
.
Nous ne sommes pas confus d'avoir recherché register_chrdev, mais trouvé __register_chrdevDémonter:
Quelle signature prendre? J'ai essayé plusieurs options et j'ai opté pour la pièce suivante:
0xffffffff811c9785 <+101>: shl $0x14,%esi 0xffffffff811c9788 <+104>: or %r12d,%esi
Le fait est que dans
lunix
il n'y a qu'une seule fonction qui contient
0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6
.
Maintenant, je vais montrer, mais nous découvrons d'abord dans quel segment le rechercher.
La fonction
__register_chrdev
adresse
0xffffffff811c9720
, il s'agit du segment
.text
. Là, nous allons regarder.
Déconnectez-vous de la référence Minimal Linux. Connectez-vous à lunix maintenant.
Dans un terminal:
sudo qemu-system-x86_64 lunix_nokaslr.iso -s -enable-kvm
Dans un autre:
sudo gdb vmlinux.lunix (gdb) target remote localhost:1234
Nous regardons les limites du segment
.text
:
Bordures
0xffffffff81000000 - 0xffffffff81600b91
, recherchez
0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6
:
On retrouve la pièce à l'adresse
0xffffffff810dc643
. Mais ce n'est qu'une partie de la fonction, voyons ce qui est ci-dessus:
Et voici le début de la fonction
0xffffffff810dc5d0
(car
retq
est la sortie de la fonction voisine).
5. Recherche fops depuis / dev / activate
Le prototype de la fonction
register_chrdev
est le suivant:
int register_chrdev (unsigned int major, const char * name, const struct fops);
Nous avons besoin d'une structure
fops
.
Redémarrage du débogueur et de QEMU. Nous avons
0xffffffff810dc5d0
pause sur
0xffffffff810dc5d0
. Cela fonctionnera plusieurs fois. Ce sont les périphériques
mem, vcs, cpu/msr, cpu/cpuid
et
activate
immédiatement après eux.
Le pointeur vers le nom est stocké dans le
rcx
. Et le pointeur sur
fops
est en
r8
:
Je rappelle la structure 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 *); };
Ainsi, l'adresse de la fonction d'
write
est
0xffffffff811f068f
.
6. Nous étudions écrire
La fonction comprend plusieurs blocs intéressants. Cela ne vaut pas la peine de décrire tous les points d'arrêt ici, c'est une routine habituelle. De plus, les blocs de calculs sont visibles à l'œil nu.
6.1. Fonction de hachage
vmlinux.lunix
l'IDA, chargeons le noyau
vmlinux.lunix
et voyons ce que la fonction d'écriture a à l'intérieur.
La première chose à remarquer est ce cycle:
Une fonction
sub_FFFFFFFF811F0413
est
sub_FFFFFFFF811F0413
, qui commence comme ceci:
Et à l'adresse
0xffffffff81829ce0
, une table pour sha256 est détectée:
Autrement dit,
sub_FFFFFFFF811F0413
= sha256. Les octets dont le hachage doit être obtenu sont transmis via
$sp+0x50+var49
, et le résultat est stocké à
$sp+0x50+var48
. Soit dit en passant,
var49=-0x49
,
var48=-0x48
, donc
$sp+0x50+var49 = $sp+0x7
,
$sp+0x50+var48 = $sp+0x8
.
Vérifiez-le.
Nous commençons qemu, gdb, définissons une pause sur l'
0xffffffff811f0748 call sub_FFFFFFFF811F0413
et sur l'instruction
0xffffffff811f074d xor ecx, ecx
, qui est immédiatement derrière la fonction.
test@mail.ru
mail
test@mail.ru
, mot de passe
1234-5678-0912-3456
.
L'octet de courrier est transmis à la fonction, et le résultat est le suivant:
>>> import hashlib >>> hashlib.sha256(b"t").digest().hex() 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8' >>>
C'est-à-dire, oui, c'est vraiment sha256, seulement il calcule les hachages pour tous les octets de courrier, et pas un seul hachage à partir du courrier.
Ensuite, les hachages sont additionnés par octet. Mais si la somme est supérieure à
0xEC
, le reste de la division par
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
Le montant est enregistré à
0xffffffff81c82f80
. Voyons ce que sera le hachage de
test@mail.ru
.
Nous avons
ffffffff811f0786 dec r13d
pause sur
ffffffff811f0786 dec r13d
(c'est la sortie de la boucle):
Et comparer avec:
>>> get_email_hash('test@mail.ru') 2b902daf5cc483159b0a2f7ed6b593d1d56216a61eab53c8e4b9b9341fb14880
Mais le hachage lui-même est clairement un peu long pour la clé.
6.2. Algorithme de génération de clés
La clé est responsable de ce code:
Voici le calcul final de chaque octet:
0xFFFFFFFF811F0943 imul eax, r12d 0xFFFFFFFF811F0947 cdq 0xFFFFFFFF811F0948 idiv r10d
Dans les octets de hachage
eax
et
r12d
, ils sont multipliés, puis le reste de la division par 9 est pris.
Parce que
Et les octets sont pris dans un ordre inattendu. Je l'indiquerai dans 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)]
Générons donc une clé:
>>> import lunix >>> lunix.keygen("m.gayanov@gmail.com") ['0456', '3530', '0401', '2703']
Et maintenant, vous pouvez vous détendre et jouer au jeu 2048 :) Merci de votre attention! Code
ici