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. Alat2. Periksa gambar3. Perangkat karakter dan kernel4. Cari register_chrdev4.1. Mempersiapkan Gambar Linux Minimal Segar4.2. Beberapa persiapan lagi4.3. Nonaktifkan KASLR pada lunix4.4. Kami mencari dan menemukan tanda tangan5. Cari fops dari / dev / activ dan fungsi tulis6. Kami belajar menulis6.1. Fungsi hash6.2. Algoritma Generasi Kunci6.3. KeygenTantangan
Gambar yang diluncurkan di QEMU memerlukan surat dan kunci aktivasi. Kita sudah tahu suratnya, mari kita cari sisanya!
1. Alat
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:
- Distribusi - Linux Minimal 5.0.11.
- Perangkat karakter /dev/activateterlibat dalam memeriksa surat, kuncinya, yang berarti bahwa logika verifikasi perlu dicari di suatu tempat di isi kernel.
- 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
- Instal alat yang diperlukan:
  sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev
 
- Mengunduh skrip:
 
  git clone https://github.com/ivandavidov/minimal cd minimal/src
 
- 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
 
 
- 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_chrdevBongkar:
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