Memecah Micosoft Lunix di HackQuest 2019


Halo, Habr!

Di HackQuest, sebelum konferensi ZeroNight 2019, ada satu tugas yang menghibur. Saya tidak memberikan keputusan tepat waktu, tetapi saya menerima bagian yang menggetarkan hati saya. Saya pikir Anda akan tertarik untuk mengetahui apa yang telah disiapkan oleh panitia dan tim r0.Crew untuk para peserta.

Tugas: mendapatkan kode aktivasi untuk sistem operasi Micosoft 1998 yang rahasia.

Pada artikel ini saya akan memberi tahu Anda cara melakukannya.

Isi


0. Tugas
1. Alat
2. Periksa gambar
3. Perangkat karakter dan kernel
4. Cari register_chrdev
4.1. Mempersiapkan Gambar Linux Minimal Segar
4.2. Beberapa persiapan lagi
4.3. Nonaktifkan KASLR pada lunix
4.4. Kami mencari dan menemukan tanda tangan
5. Cari fops dari / dev / activ dan fungsi tulis
6. Kami belajar menulis
6.1. Fungsi hash
6.2. Algoritma Generasi Kunci
6.3. Keygen

Tantangan


Gambar yang diluncurkan di QEMU memerlukan surat dan kunci aktivasi. Kita sudah tahu suratnya, mari kita cari sisanya!

1. Alat


  • Gdb
  • QEMU
  • binwalk
  • IDA

Di ~/.gdbinit Anda perlu menulis fungsi yang berguna:

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

2. Periksa gambar


Pertama ganti nama jD74nd8_task2.iso menjadi lunix.iso.

Menggunakan binwalk, kita melihat bahwa ada skrip di offset 0x413000 . Script ini memeriksa surat dan kunci:


Kami mematahkan cek dengan hex editor langsung di gambar dan membuat skrip menjalankan perintah kami. Seperti apa sekarang:


Perhatikan bahwa Anda harus memotong garis yang activated untuk activated sehingga ukuran gambar tetap sama. Untungnya, tidak ada pemeriksaan hash. Gambar ini disebut lunix_broken_activation.iso.

Jalankan melalui QEMU:

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

Mari menggali lebih dalam:


Jadi kita punya:

  1. Distribusi - Linux Minimal 5.0.11.
  2. Perangkat karakter /dev/activate terlibat dalam memeriksa surat, kuncinya, yang berarti bahwa logika verifikasi perlu dicari di suatu tempat di isi kernel.
  3. Mail, kunci dikirim dalam format email|key .

Gambar target_broken_activation.iso tidak lagi diperlukan.

3. Perangkat karakter dan kernel


Perangkat seperti /dev/mem , /dev/vcs , /dev/activate , dll. daftar menggunakan fungsi register_chrdev :

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

name adalah nama, dan struktur fops berisi pointer ke fungsi driver:

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

Kami hanya tertarik pada fungsi ini:

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

Di sini, argumen kedua adalah buffer dengan data yang ditransfer, selanjutnya adalah ukuran buffer.

4. Cari register_chrdev


Secara default, Minimal Linux mengkompilasi dengan informasi debug yang dinonaktifkan untuk mengurangi ukuran gambar, tetapi minimal. Karenanya, Anda tidak bisa memulai debugger dan menemukan fungsinya berdasarkan nama. Tapi itu mungkin dengan tanda tangan.

Dan tanda tangannya ada di gambar Linux Minimal dengan menyertakan informasi debug. Secara umum, Anda perlu membangun Minimal.

Artinya, skemanya adalah sebagai berikut:

  Minimal Linux ->   register_chrdev ->  ->   register_chrdev  Lunix 

4.1. Mempersiapkan Gambar Linux Minimal Segar


  1. Instal alat yang diperlukan:
     sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev 
  2. Mengunduh skrip:

     git clone https://github.com/ivandavidov/minimal cd minimal/src 
  3. Benar 02_build_kernel.sh :
    hapus itu

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

    tambahkan itu

     echo "CONFIG_GDB_SCRIPTS=y" >> .config 

  4. Kompilasi

     ./build_minimal_linux_live.sh 

Gambar minimal / src / minimal_linux_live.iso.

4.2. Beberapa persiapan lagi


Unzip minimal_linux_live.iso ke folder minimal / src / iso.

File minimal / src / iso / boot rootfs.xz kernel.xz kernel rootfs.xz dan kernel.xz rootfs.xz FS. Ganti nama mereka menjadi kernel.minimal.xz , rootfs.minimal.xz .

Selain itu, Anda perlu menarik inti keluar dari gambar. Script extract-vmlinux akan membantu dengan ini:

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

Sekarang di folder minimal / src / iso / boot kita memiliki set ini: kernel.minimal.xz , rootfs.minimal.xz , vmlinux.minimal .

Tapi dari lunix.iso kita hanya perlu kernel. Karena itu, kami vmlinux.lunix semua operasi yang sama, kami sebut vmlinux.lunix kernel.xz , lupakan kernel.xz , rootfs.xz , sekarang saya akan memberi tahu Anda alasannya.

4.3. Nonaktifkan KASLR pada lunix


Saya berhasil menonaktifkan KASLR dalam kasus Minimal Linux yang baru dirakit di QEMU.
Tapi itu tidak berhasil dengan Lunix. Karena itu, Anda harus mengedit gambar itu sendiri.

Untuk melakukan ini, buka di hex editor, cari baris "APPEND vga=normal" dan ganti dengan "APPEND nokaslr\x20\x20\x20" .

Dan gambar itu disebut lunix_nokaslr.iso.

4.4. Kami mencari dan menemukan tanda tangan


Kami meluncurkan Linux Minimal baru dalam satu terminal:

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

Di debugger lain:

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

Sekarang cari register_chrdev dalam daftar fungsi:


Jelas, opsi kami adalah __register_chrdev .
Kami tidak bingung bahwa kami mencari register_chrdev, tetapi menemukan __register_chrdev

Bongkar:


Tanda tangan apa yang harus diambil? Saya mencoba beberapa opsi dan menyelesaikan bagian berikut:

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


Faktanya adalah bahwa dalam lunix hanya ada satu fungsi yang berisi 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 .

Sekarang saya akan tunjukkan, tetapi pertama-tama kita mencari tahu di segmen mana untuk mencarinya.


Fungsi __register_chrdev alamat 0xffffffff811c9720 , ini adalah segmen .text . Di sana kita akan melihat.

Putuskan sambungan dari referensi Minimal Linux. Terhubung ke lunix sekarang.

Di satu terminal:

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

Di lain:

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

Kami melihat batas-batas segmen .text :


Perbatasan 0xffffffff81000000 - 0xffffffff81600b91 , cari 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 :


Kami menemukan potongan di alamat 0xffffffff810dc643 . Tapi ini hanya bagian dari fungsi, mari kita lihat apa yang ada di atas:


Dan di sini adalah awal dari fungsi 0xffffffff810dc5d0 (karena retq adalah jalan keluar dari fungsi tetangga).

5. Cari fops dari / dev / activ


Prototipe fungsi register_chrdev adalah ini:

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

Kami membutuhkan struktur fops .

Mulai ulang debugger dan QEMU. Kami 0xffffffff810dc5d0 istirahat pada 0xffffffff810dc5d0 . Ini akan bekerja beberapa kali. Ini adalah perangkat mem, vcs, cpu/msr, cpu/cpuid , dan activate segera setelahnya.


Pointer ke nama disimpan dalam rcx . Dan pointer ke fops ada di r8 :


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


Jadi, alamat dari fungsi write adalah 0xffffffff811f068f .

6. Kami belajar menulis


Fungsi ini mencakup beberapa blok menarik. Tidak ada gunanya menggambarkan setiap breakpoint di sana, itu adalah rutinitas biasa. Selain itu, blok perhitungan terlihat dengan mata telanjang.

6.1. Fungsi hash


vmlinux.lunix buka IDA, muat kernel vmlinux.lunix dan lihat apa fungsi penulisan di dalamnya.

Hal pertama yang perlu diperhatikan adalah siklus ini:


Beberapa fungsi sub_FFFFFFFF811F0413 , yang dimulai seperti ini:


Dan di alamat 0xffffffff81829ce0 , sebuah tabel untuk sha256 terdeteksi:


Yaitu, sub_FFFFFFFF811F0413 = sha256. Bytes yang hashnya harus diperoleh dikirim melalui $sp+0x50+var49 , dan hasilnya disimpan di $sp+0x50+var48 . Omong-omong, var49=-0x49 , var48=-0x48 , jadi $sp+0x50+var49 = $sp+0x7 , $sp+0x50+var48 = $sp+0x8 .

Lihat itu.

Kita mulai qemu, gdb, atur break pada 0xffffffff811f0748 call sub_FFFFFFFF811F0413 dan pada instruksi 0xffffffff811f074d xor ecx, ecx , yang berada tepat di belakang fungsi. test@mail.ru email test@mail.ru , kata sandi 1234-5678-0912-3456 .

Byte surat diteruskan ke fungsi, dan hasilnya adalah ini:


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

Ya, benar-benar sha256, hanya menghitung hash untuk semua byte surat, dan tidak hanya satu hash dari mail.

Kemudian hash dijumlahkan dengan byte. Tetapi jika jumlahnya lebih besar dari 0xEC , maka sisa pembagian dengan 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 

Jumlahnya disimpan di 0xffffffff81c82f80 . Mari kita lihat apa hash dari test@mail.ru .

Kami ffffffff811f0786 dec r13d break pada ffffffff811f0786 dec r13d (ini adalah jalan keluar dari loop):


Dan bandingkan dengan:

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

Tapi hash itu sendiri jelas agak lama untuk kuncinya.

6.2. Algoritma Generasi Kunci


Kunci bertanggung jawab atas kode ini:


Berikut ini perhitungan akhir setiap byte:

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

Dalam byte hash eax dan r12d , mereka dikalikan, dan kemudian sisa pembagian dengan 9 diambil.

Karena


Dan byte diambil dalam urutan yang tidak terduga. Saya akan menunjukkannya di 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)] 

Jadi, mari kita hasilkan beberapa kunci:

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


Dan sekarang Anda dapat bersantai dan memainkan game 2048 :) Terima kasih atas perhatian Anda! Kode di sini

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


All Articles