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/activate
terlibat 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