Saya sudah mulai menuliskan catatan tentang video terkait keamanan yang saya tonton (sebagai cara penarikan cepat).
Ini mungkin lebih berguna bagi pemula.
Urutan catatan di sini bukan dalam urutan kesulitan, tetapi dalam urutan kronologis terbalik dari bagaimana saya menulisnya (yaitu, lebih baru pertama).
Karya ini dilisensikan di bawah lisensi internasional Atribut-Nonkomersial-Sharealike 4.0 Creative Commons.
Ditulis pada 12 Agustus 2017
Dipengaruhi oleh livestreams CTF 2017 Gynvael di sini dan di sini; Dan dengan live streaming Google CTF QUALS 2017 di sini
Terkadang, tantangan mungkin menerapkan tugas yang rumit dengan menerapkan VM. Tidak selalu perlu untuk sepenuhnya merekayasa VM dan berupaya menyelesaikan tantangan. Terkadang, Anda bisa sedikit, dan begitu Anda tahu apa yang sedang terjadi, Anda dapat menghubungkan ke VM, dan mendapatkan akses ke hal -hal yang Anda butuhkan. Selain itu, serangan saluran samping berbasis waktu menjadi lebih mudah dalam VM (terutama karena lebih banyak jumlah instruksi "nyata" yang dieksekusi.
Fungsi yang menarik secara kriptografis di binari dapat dikenali dan dengan cepat diulang hanya dengan mencari konstanta dan mencarinya secara online. Untuk fungsi crypto standar, konstanta ini cukup untuk menebak fungsi. Fungsi crypto yang lebih sederhana dapat dikenali bahkan lebih mudah. Jika Anda melihat banyak XORS dan hal-hal seperti itu terjadi, dan tidak ada konstanta yang mudah diidentifikasi, itu mungkin crypto yang digulung tangan (dan juga mungkin rusak).
Terkadang, saat menggunakan IDA dengan hexrays, tampilan pembongkaran mungkin lebih baik daripada tampilan dekompilasi. Ini terutama benar jika Anda memperhatikan bahwa tampaknya ada banyak komplikasi yang terjadi dalam tampilan dekompilasi, tetapi Anda melihat pola berulang dalam tampilan pembongkaran. (Anda dapat dengan cepat beralih b/w keduanya menggunakan bilah ruang). Misalnya, jika ada (ukuran tetap) perpustakaan bilangan besar yang diimplementasikan, maka tampilan dekompilasi mengerikan, tetapi tampilan pembongkaran mudah dipahami (dan mudah dikenali karena instruksi "with-carry" yang berulang seperti adc ). Selain itu, ketika menganalisis seperti ini, menggunakan fitur "grup node" dalam tampilan grafik IDA sangat berguna untuk dengan cepat mengurangi kompleksitas grafik Anda, karena Anda memahami apa yang dilakukan masing -masing node.
Untuk arsitektur yang aneh, memiliki emulator yang baik sangat berguna. Terutama, emulator yang dapat memberi Anda tempat pembuangan memori dapat digunakan untuk dengan cepat mencari tahu apa yang sedang terjadi, dan mengenali bagian -bagian yang menarik, setelah Anda memiliki memori keluar dari emulator. Selain itu, menggunakan emulator yang diimplementasikan dalam bahasa yang nyaman (seperti Python), berarti Anda dapat menjalankan hal -hal yang Anda sukai. Misalnya, jika ada beberapa bagian yang menarik dari kode yang mungkin ingin Anda jalankan beberapa kali (misalnya, untuk brute force atau sesuatu), kemudian menggunakan emulator, Anda dapat dengan cepat membuat kode sesuatu yang hanya melakukan bagian dari kode, daripada harus menjalankan program lengkap.
Menjadi malas itu bagus, saat Reing. Jangan buang waktu untuk merekayasa segalanya, tetapi luangkan waktu yang cukup untuk melakukan pengintaian (bahkan dalam tantangan kembali!), Sehingga dapat mengurangi waktu yang dihabiskan untuk benar -benar melakukan tugas yang lebih sulit dari Reing. Apa yang dimaksud dengan pengintaian seperti itu, adalah dengan melihat sekilas fungsi yang berbeda, tanpa menghabiskan terlalu banyak waktu untuk menganalisis setiap fungsi secara menyeluruh. Anda dengan cepat mengukur apa fungsinya (misalnya "terlihat seperti hal crypto", atau "terlihat seperti masalah manajemen memori", dll.)
Untuk perangkat keras atau arsitektur yang tidak diketahui, luangkan waktu yang cukup untuk mencarinya di Google, Anda mungkin beruntung dengan banyak alat atau dokumen yang berguna yang mungkin membantu Anda membangun alat lebih cepat. Sering kali, Anda akan menemukan implementasi emulator mainan dll yang mungkin berguna sebagai titik cepat untuk memulai. Sebagai alternatif, Anda mungkin mendapatkan beberapa info menarik (seperti bagaimana bitmap disimpan, atau bagaimana string disimpan, atau sesuatu) yang dapat Anda tulis dengan skrip "perbaiki" cepat, dan kemudian gunakan alat normal untuk melihat apakah hal -hal menarik ada di sana.
GIMP (alat manipulasi gambar), memiliki fungsionalitas terbuka/beban yang sangat keren untuk melihat data piksel mentah. Anda dapat menggunakan ini untuk dengan cepat mencari aset atau struktur berulang dalam data biner mentah. Luangkan waktu bermain -main dengan pengaturan untuk melihat apakah info lebih lanjut dapat diperoleh darinya.
Ditulis pada 2 Jul 2017
Dipengaruhi oleh diskusi dengan @p4n74 dan @h3rcul35 pada obrolan Infoseciitr #bin. Kami sedang mendiskusikan bagaimana kadang -kadang pemula berjuang untuk memulai dengan biner tantangan yang lebih besar, terutama ketika dilucuti.
Untuk menyelesaikan tantangan RE, atau untuk dapat mempesona, seseorang harus terlebih dahulu menganalisis biner yang diberikan, agar dapat mengeksploitasinya secara efektif. Karena biner mungkin dilucuti dll (ditemukan menggunakan file ) orang harus tahu di mana untuk memulai analisis, untuk mendapatkan pijakan untuk dibangun.
Ada beberapa gaya analisis, ketika mencari kerentanan di binari (dan dari apa yang telah saya kumpulkan, tim CTF yang berbeda memiliki preferensi yang berbeda):
1.1. Mentranspiling kode lengkap ke c
Analisis semacam ini agak jarang, tetapi cukup berguna untuk biner yang lebih kecil. Idenya adalah untuk masuk ke insinyur terbalik keseluruhan kode. Setiap fungsi dibuka di IDA (menggunakan tampilan dekompiler), dan mengganti nama (pintasan: n) dan retyping (pintasan: y) digunakan untuk dengan cepat membuat kode yang didekompilasi jauh lebih mudah dibaca. Kemudian, semua kode disalin/diekspor ke file .c terpisah, yang dapat dikompilasi untuk mendapatkan biner yang setara (tetapi tidak sama) dengan aslinya. Kemudian, analisis tingkat kode sumber dapat dilakukan, untuk menemukan vulns dll. Setelah titik kerentanan ditemukan, maka eksploitasi dibangun pada biner asli, dengan mengikuti di dalam sumber yang didekompilasi dengan baik di IDA, berdampingan dengan tampilan pembongkaran (gunakan tab untuk dengan cepat beralih di antara keduanya; dan gunakan ruang untuk beralih dengan cepat antara tampilan grafik dan teks untuk disassembly).
1.2. Analisis minimal dekompilasi
Ini dilakukan cukup sering, karena sebagian besar biner relatif tidak berguna (dari perspektif penyerang). Anda hanya perlu menganalisis fungsi yang mencurigakan atau mungkin membawa Anda ke Vuln. Untuk melakukan ini, ada beberapa pendekatan untuk memulai:
1.2.1. Mulai dari Main
Sekarang biasanya, untuk biner yang dilucuti, bahkan utama tidak diberi label (Ida 6.9 dan seterusnya menandai untuk Anda), tetapi seiring waktu, Anda belajar mengenali cara mencapai utama dari titik masuk (di mana IDA dibuka secara default). Anda melompat ke sana dan mulai menganalisis dari sana.
1.2.2. Temukan string yang relevan
Terkadang, Anda tahu beberapa string spesifik yang mungkin dikeluarkan dll, yang Anda tahu mungkin berguna (misalnya "Selamat, bendera Anda adalah %s" untuk tantangan re). Anda dapat melompat ke tampilan string (pintasan: shift+f12), temukan string, dan bekerja ke belakang menggunakan xrefs (pintasan: x). XREFS memungkinkan Anda menemukan jalur fungsi ke string itu, dengan menggunakan XREFS pada semua fungsi dalam rantai itu, sampai Anda mencapai utama (atau beberapa titik yang Anda tahu).
1.2.3. Dari beberapa fungsi acak
Terkadang, bukan string spesifik mungkin berguna, dan Anda tidak ingin memulai dari Main. Jadi sebagai gantinya, Anda dengan cepat membalikkan seluruh daftar fungsi, mencari fungsi yang terlihat mencurigakan (seperti memiliki banyak konstanta, atau banyak xors, dll) atau memanggil fungsi -fungsi penting (xrefs dari malloc, gratis, dll), dan Anda memulai dari sana, dan melangkah maju (berikut fungsi yang disebutnya) dan ke belakang (XREFS dari fungsi)
1.3. Analisis Pembongkaran Murni
Kadang-kadang, Anda tidak dapat menggunakan tampilan dekompilasi (karena arsitektur yang aneh, atau teknik anti-dekompilasi, atau perakitan tertulis tangan, atau dekompilasi yang terlihat terlalu rumit). Dalam hal ini, sangat valid untuk melihat murni pada tampilan pembongkaran. Ini sangat berguna (untuk arsitektur baru) untuk menyalakan komentar otomatis, yang menunjukkan komentar yang menjelaskan setiap instruksi. Selain itu, fungsi node node dan node grup sangat membantu. Bahkan jika Anda tidak menggunakan semua ini, secara teratur menandai komentar dalam pembongkaran banyak membantu. Jika saya secara pribadi melakukan ini, saya lebih suka menuliskan komentar seperti Python, sehingga saya dapat dengan cepat kemudian transpile secara manual menjadi Python (terutama berguna untuk tantangan RE, di mana Anda mungkin harus menggunakan Z3 dll).
1.4. Menggunakan platform seperti BAP, dll.
Analisis semacam ini adalah (semi-) otomatis, dan biasanya lebih berguna untuk perangkat lunak yang jauh lebih besar, dan jarang digunakan secara langsung dalam CTF.
Fuzzing bisa menjadi teknik yang efektif untuk dengan cepat sampai ke Vuln, tanpa harus benar -benar memahaminya pada awalnya. Dengan menggunakan fuzzer, seseorang bisa mendapatkan banyak gaya vulns-ganging-ganging, yang kemudian perlu dianalisis dan diuji coba untuk mencapai Vuln yang sebenarnya. Lihat catatan saya tentang dasar -dasar fuzzing dan fuzzing genetik untuk info lebih lanjut.
Analisis dinamis dapat digunakan setelah menemukan Vuln menggunakan analisis statis, untuk membantu membangun eksploitasi dengan cepat. Atau, dapat digunakan untuk menemukan Vuln itu sendiri. Biasanya, seseorang memulai yang dapat dieksekusi di dalam debugger, dan mencoba mengikuti jalur kode yang memicu bug. Dengan menempatkan breakpoint di lokasi yang tepat, dan menganalisis keadaan register/heap/stack/dll, orang bisa mendapatkan ide bagus tentang apa yang sedang terjadi. Seseorang juga dapat menggunakan debugger untuk mengidentifikasi fungsi yang menarik dengan cepat. Ini dapat dilakukan, misalnya, dengan mengatur breakpoint sementara pada semua fungsi pada awalnya; kemudian melanjutkan untuk melakukan 2 walks - satu melalui semua jalur kode yang tidak menarik; dan satu melalui hanya satu jalan yang menarik. Walk pertama melakukan semua fungsi yang tidak menarik dan menonaktifkan breakpoint itu, sehingga membuat yang menarik muncul sebagai breakpoint selama berjalan kedua.
Gaya pribadi saya untuk analisis, adalah memulai dengan analisis statis, biasanya dari utama (atau untuk aplikasi berbasis non-konsol, dari string), dan bekerja untuk dengan cepat menemukan fungsi yang terlihat aneh. Saya kemudian menghabiskan waktu dan bercabang ke depan dan mundur dari sini, secara teratur menuliskan komentar, dan terus -menerus mengganti nama dan memperbaiki variabel untuk meningkatkan dekompilasi. Seperti yang lain, saya menggunakan nama -nama seperti Apple, Banana, Wortel, dll untuk tampaknya bermanfaat, tetapi pada fungsi/variabel/variabel yang belum diketahui, untuk membuatnya lebih mudah dianalisis (melacak func_123456 gaya nama terlalu sulit bagi saya). Saya juga secara teratur menggunakan tampilan struktur di IDA untuk mendefinisikan struktur (dan enum) untuk membuat dekompilasi lebih baik. Setelah saya menemukan Vuln, saya biasanya pindah untuk menulis skrip dengan pwntools (dan menggunakannya untuk memanggil gdb.attach() ). Dengan cara ini, saya bisa mendapatkan banyak kendali atas apa yang sedang terjadi. Di dalam GDB, saya biasanya menggunakan GDB polos, meskipun saya telah menambahkan command peda yang memuat peda secara instan jika diperlukan.
Gaya saya pasti berkembang, karena saya merasa lebih nyaman dengan alat saya, dan juga dengan alat kustom yang saya tulis untuk mempercepat segalanya. Saya akan senang mendengar gaya analisis lainnya, serta kemungkinan perubahan pada gaya saya yang mungkin membantu saya menjadi lebih cepat. Untuk setiap komentar/kritik/pujian yang Anda miliki, seperti biasa, saya dapat dihubungi di Twitter @jay_f0xtr0t.
Ditulis pada 4 Juni 2017
Dipengaruhi oleh streaming langsung yang luar biasa ini oleh Gynvael Coldwind, di mana ia membahas dasar -dasar ROP, dan memberikan beberapa tips dan trik
Pemrograman berorientasi return (ROP) adalah salah satu teknik eksploitasi klasik, yang digunakan untuk memotong perlindungan NX (memori yang tidak dapat dieksekusi). Microsoft telah memasukkan NX sebagai DEP (Pencegahan Eksekusi Data). Bahkan Linux dll, membuatnya efektif, yang berarti bahwa dengan perlindungan ini, Anda tidak bisa lagi menempatkan shellcode ke heap/stack dan memilikinya hanya dengan melompat ke sana. Jadi sekarang, untuk dapat menjalankan kode, Anda melompat ke kode yang sudah ada sebelumnya (biner utama, atau perpustakaannya-libc, ldd dll di linux; kernel32, ntdll dll di windows). ROP muncul dengan menggunakan kembali fragmen kode ini yang sudah ada, dan mencari cara untuk menggabungkan fragmen-fragmen itu untuk melakukan apa yang ingin Anda lakukan (yang tentu saja, retas planet !!!).
Awalnya, ROP dimulai dengan Ret2Libc, dan kemudian menjadi lebih maju dari waktu ke waktu dengan menggunakan lebih banyak kode kecil. Beberapa orang mungkin mengatakan bahwa ROP sekarang "mati", karena perlindungan tambahan untuk mengurangi, tetapi masih dapat dieksploitasi dalam banyak skenario (dan pasti diperlukan untuk banyak CTF).
Bagian terpenting dari ROP, adalah gadget. Gadget adalah "potongan kode yang dapat digunakan untuk ROP". Itu biasanya berarti potongan kode yang diakhiri dengan ret (tetapi jenis gadget lain mungkin juga berguna; seperti yang diakhiri dengan pop eax; jmp eax dll). Kami rantai gadget ini bersama -sama untuk membentuk eksploitasi, yang dikenal sebagai rantai ROP .
Salah satu asumsi ROP yang paling penting adalah Anda memiliki kendali atas tumpukan (yaitu, tumpukan pointer menunjuk ke buffer yang Anda kendalikan). Jika ini tidak benar, maka Anda perlu menerapkan trik lain (seperti stack pivoting) untuk mendapatkan kontrol ini sebelum membangun rantai ROP.
Bagaimana Anda mengekstrak gadget? Gunakan alat yang dapat diunduh (seperti ropgadget) atau alat online (seperti ropshell) atau tulis alat Anda sendiri (mungkin lebih berguna untuk tantangan yang lebih sulit kadang -kadang, karena Anda dapat men -tweak ke tantangan spesifik jika perlu). Pada dasarnya, kita hanya membutuhkan alamat yang bisa kita lompati untuk gadget ini. Di sinilah mungkin ada masalah dengan ASLR dll (dalam hal ini, Anda mendapatkan kebocoran alamat, sebelum pindah untuk benar -benar melakukan ROP).
Jadi sekarang, bagaimana kita menggunakan gadget ini untuk membuat ropchain? Pertama -tama kami mencari "gadget dasar". Ini adalah gadget yang dapat melakukan tugas -tugas sederhana untuk kami (seperti pop ecx; ret , yang dapat digunakan untuk memuat nilai ke ECX dengan menempatkan gadget, diikuti oleh nilai yang akan dimuat, diikuti oleh rantai lainnya, yang dikembalikan ke setelah nilai dimuat). Gadget dasar yang paling berguna, biasanya "mengatur register", "nilai register toko di alamat yang ditunjukkan oleh register", dll.
Kita dapat membangun dari fungsi primitif ini untuk mendapatkan fungsionalitas tingkat yang lebih tinggi (mirip dengan posting saya berjudul Abstraksi Eksploitasi). Misalnya, menggunakan gadget set-register, dan toko-value-at-address, kita dapat membuat fungsi "poke", yang memungkinkan kita mengatur alamat tertentu dengan nilai tertentu. Dengan menggunakan ini, kita dapat membangun fungsi "poke-string" yang memungkinkan kita menyimpan string tertentu di lokasi tertentu dalam memori. Sekarang setelah kita memiliki poke-string, kita pada dasarnya hampir selesai, karena kita dapat membuat struktur apa pun yang kita inginkan dalam memori, dan juga dapat memanggil fungsi apa pun yang kita inginkan dengan parameter yang kita inginkan (karena kita dapat mengatur, dan dapat menempatkan nilai pada tumpukan).
Salah satu alasan paling penting untuk membangun dari primitif tingkat rendah ini ke fungsi yang lebih besar yang melakukan hal -hal yang lebih kompleks, adalah mengurangi kemungkinan membuat kesalahan (yang umum di ROP jika tidak).
Ada ide, teknik, dan tips yang lebih kompleks untuk ROP, tetapi itu mungkin topik untuk catatan terpisah, untuk waktu yang berbeda :)
PS: Gyn memiliki posting blog tentang eksploitasi berorientasi pengembalian yang mungkin layak dibaca.
Ditulis pada 27 Mei 2017; diperpanjang pada 29 Mei 2017
Dipengaruhi oleh streaming langsung yang luar biasa ini oleh Gynvael Coldwind, di mana ia berbicara tentang teori dasar di balik fuzzing genetik, dan mulai membangun fuzzer genetik dasar. Dia kemudian melanjutkan untuk menyelesaikan implementasi dalam streaming langsung ini.
"Advanced" fuzzing (dibandingkan dengan fuzzer buta, yang dijelaskan dalam catatan "Dasar -dasar Fuzzing" saya). Ini juga memodifikasi/bermutasi byte dll, tetapi ia melakukannya sedikit lebih pintar dari fuzzer "bodoh" yang buta.
Mengapa kita membutuhkan fuzzer genetik?
Beberapa program mungkin "jahat" terhadap fuzzer bodoh, karena ada kemungkinan bahwa kerentanan mungkin memerlukan banyak kondisi untuk dipenuhi untuk dicapai. Dalam fuzzer bodoh, kami memiliki probabilitas yang sangat rendah tentang hal ini karena tidak tahu apakah itu membuat kemajuan atau tidak. Sebagai contoh spesifik, jika kita memiliki kode if a: if b: if c: if d: crash! (Sebut saja kode crasher), maka dalam hal ini kita perlu 4 ketentuan untuk dipenuhi untuk menghancurkan program. Namun, fuzzer bodoh mungkin tidak dapat melewati kondisi a , hanya karena ada kemungkinan sangat rendah bahwa semua 4 mutasi a , b , c , d , terjadi pada saat yang sama. Bahkan, bahkan jika itu berkembang dengan melakukan a , mutasi berikutnya mungkin kembali ke !a hanya karena tidak tahu apa -apa tentang program.
Tunggu, kapan program "kasus buruk" semacam ini muncul?
Sangat umum dalam parser format file, untuk mengambil satu contoh. Untuk mencapai beberapa jalur kode tertentu, orang mungkin perlu melewati beberapa cek "Nilai ini pasti ini, dan nilai itu pasti, dan beberapa nilai lain harus menjadi sesuatu yang lain" dan seterusnya. Selain itu, hampir tidak ada perangkat lunak dunia nyata yang "tidak rumit", dan sebagian besar perangkat lunak memiliki banyak banyak jalur kode yang mungkin, beberapa di antaranya dapat diakses hanya setelah banyak hal di negara bagian diatur dengan benar. Dengan demikian, banyak dari jalur kode program ini pada dasarnya tidak dapat diakses oleh fuzzer bodoh. Selain itu, kadang -kadang, beberapa jalur mungkin sama sekali tidak dapat diakses (bukan hanya tidak mungkin) karena tidak cukup mutasi yang dilakukan sama sekali. Jika salah satu dari jalur ini memiliki bug, fuzzer bodoh tidak akan pernah bisa menemukannya.
Jadi bagaimana kita melakukan lebih baik dari fuzzer bodoh?
Pertimbangkan grafik aliran kontrol (CFG) dari kode crasher yang disebutkan di atas. Jika kebetulan fuzzer bodoh tiba -tiba mendapatkan a benar, maka juga tidak akan menyadari bahwa ia mencapai simpul baru, tetapi itu akan terus mengabaikan ini, membuang sampel. Di sisi lain, apa yang AFL (dan fuzzer genetik atau "pintar" lainnya) lakukan, apakah mereka mengenali ini sebagai informasi baru ("jalur yang baru tercapai") dan menyimpan sampel ini sebagai titik awal baru ke dalam korpus. Artinya sekarang fuzzer dapat mulai dari blok a dan bergerak lebih jauh. Tentu saja, kadang -kadang, itu mungkin kembali ke !a dari sampel a , tetapi sebagian besar waktu, tidak akan, dan sebaliknya mungkin dapat mencapai blok b Ini lagi adalah simpul baru yang dicapai, jadi tambahkan sampel baru ke dalam corpus. Ini berlanjut, memungkinkan jalur yang lebih mungkin untuk diperiksa, dan akhirnya mencapai crash! .
Mengapa ini berhasil?
Dengan menambahkan sampel bermutasi ke dalam korpus, yang mengeksplorasi lebih banyak grafik (yaitu bagian jangkauan yang tidak dieksplorasi sebelumnya), kita dapat mencapai area yang sebelumnya tidak terjangkau, dan dengan demikian dapat membekukan area tersebut. Karena kita dapat membekukan area seperti itu, kita mungkin dapat mengungkap bug di daerah itu.
Mengapa disebut fuzzing genetik?
Fuzzing "pintar" semacam ini seperti algoritma genetika. Mutasi dan crossover spesimen menyebabkan spesimen baru. Kami menyimpan spesimen yang lebih cocok untuk kondisi yang diuji. Dalam hal ini, kondisinya adalah "Berapa banyak node dalam grafik yang dicapai?". Orang -orang yang melintasi lebih banyak dapat disimpan. Ini tidak persis seperti algo genetika, tetapi merupakan variasi (karena kami menyimpan semua spesimen yang melintasi wilayah yang belum dijelajahi, dan kami tidak melakukan crossover) tetapi cukup mirip untuk mendapatkan nama yang sama. Pada dasarnya, pilihan dari populasi yang sudah ada sebelumnya, diikuti oleh mutasi, diikuti oleh pengujian kebugaran (apakah ia melihat area baru), dan mengulangi.
Tunggu, jadi kami hanya melacak node yang tidak terjangkau?
Tidak, tidak juga. AFL melacak traversal tepi dalam grafik, bukan node. Selain itu, itu tidak hanya mengatakan "tepi yang dilalui atau tidak", itu melacak berapa kali sebuah tepi dilalui. Jika tepi dilintasi 0, 1, 2, 4, 8, 16, ... kali, itu dianggap sebagai "jalur baru" dan mengarah pada penambahan ke dalam korpus. Ini dilakukan karena melihat tepi daripada node adalah cara yang lebih baik untuk membedakan antara status aplikasi, dan menggunakan jumlah traversal tepi yang meningkat secara eksponensial memberikan info lebih banyak (tepi yang dilintasi sekali sangat berbeda dari yang dilalui dua kali, tetapi traversed 10 tidak terlalu berbeda dari 11 kali).
Jadi, apa dan semua yang Anda butuhkan dalam fuzzer genetik?
Kita membutuhkan 2 hal, bagian pertama disebut pelacak (atau instrumentasi penelusuran). Ini pada dasarnya memberi tahu Anda instruksi mana yang dieksekusi dalam aplikasi. AFL melakukan ini dengan cara yang sederhana dengan melompat di antara tahap kompilasi. Setelah generasi perakitan, tetapi sebelum merakit program, ia mencari blok dasar (dengan mencari akhir, dengan memeriksa jenis instruksi lompatan/cabang), dan menambahkan kode untuk setiap blok yang menandai blok/tepi seperti yang dieksekusi (mungkin ke dalam memori bayangan atau sesuatu). Jika kami tidak memiliki kode sumber, kami dapat menggunakan teknik lain untuk menelusuri (seperti PIN, DEBUGGER, dll). Ternyata, bahkan Asan dapat memberikan informasi cakupan (lihat dokumen untuk ini).
Untuk bagian kedua, kami kemudian menggunakan informasi cakupan yang diberikan oleh pelacak untuk melacak jalur baru saat muncul, dan menambahkan sampel yang dihasilkan ke dalam korpus untuk seleksi acak di masa depan.
Ada beberapa mekanisme untuk membuat pelacak. Mereka dapat berbasis perangkat lunak, atau berbasis perangkat keras. Untuk berbasis perangkat keras, ada, misalnya, ada beberapa fitur CPU Intel ada di mana diberikan buffer dalam memori, ia mencatat informasi dari semua blok dasar yang dilintasi ke buffer itu. Ini adalah fitur kernel, sehingga kernel harus mendukungnya dan menyediakannya sebagai API (yang dilakukan Linux). Untuk berbasis perangkat lunak, kami dapat melakukannya dengan menambahkan dalam kode, atau menggunakan debugger (menggunakan breakpoint sementara, atau melalui loncatan tunggal), atau menggunakan kemampuan penelusuran sanitizer alamat, atau menggunakan kait, atau emulator, atau banyak cara lain.
Cara lain untuk membedakan mekanisme adalah dengan penelusuran kotak hitam (di mana Anda hanya dapat menggunakan biner yang tidak dimodifikasi), atau penelusuran kotak putih (di mana Anda memiliki akses ke kode sumber, dan memodifikasi kode itu sendiri untuk menambahkan kode penelusuran).
AFL menggunakan instrumentasi perangkat lunak selama kompilasi sebagai metode untuk melacak (atau melalui emulasi QEMU). HongGFUZZ mendukung metode penelusuran perangkat lunak dan perangkat keras. Fuzzer pintar lainnya mungkin berbeda. Salah satu yang Gyn Bangun menggunakan penelusuran/cakupan yang disediakan oleh Alamat Sanitizer (Asan).
Beberapa fuzzer menggunakan "speedhacks" (yaitu meningkatkan kecepatan fuzzing) seperti dengan membuat forkserver atau ide -ide lainnya. Mungkin layak untuk dilihat di beberapa titik :)
Ditulis pada 20 April 2017
Dipengaruhi oleh streaming langsung yang luar biasa ini oleh Gynvael Coldwind, di mana ia berbicara tentang apa itu fuzzing, dan juga membangun fuzzer dasar dari awal!
Apa itu fuzzer, di tempat pertama? Dan mengapa kita menggunakannya?
Pertimbangkan bahwa kami memiliki perpustakaan/program yang mengambil data input. Input dapat disusun dalam beberapa cara (katakanlah PDF, atau PNG, atau XML, dll; tetapi tidak perlu menjadi format "standar"). Dari perspektif keamanan, menarik jika ada batas keamanan antara input dan proses / perpustakaan / program, dan kita dapat melewati beberapa "input khusus" yang menyebabkan perilaku yang tidak diinginkan di luar batas itu. Fuzzer adalah salah satu cara untuk melakukan ini. Ini melakukan ini dengan "bermutasi" hal -hal dalam input (dengan demikian kemungkinan merusaknya), untuk menyebabkan eksekusi normal (termasuk kesalahan yang ditangani dengan aman) atau kecelakaan. Ini bisa terjadi karena logika case tepi tidak ditangani dengan baik.
Tabrakan adalah cara termudah untuk kondisi kesalahan. Mungkin ada orang lain juga. Misalnya, menggunakan asan (alamat pembersih) dll dapat mengarah pada mendeteksi lebih banyak hal juga, yang mungkin merupakan masalah keamanan. Misalnya, luapan satu byte dari buffer mungkin tidak menyebabkan kecelakaan sendiri, tetapi dengan menggunakan Asan, kita mungkin dapat menangkap bahkan ini dengan fuzzer.
Penggunaan lain yang mungkin untuk fuzzer adalah bahwa input yang dihasilkan dengan fuzzing satu program juga dapat digunakan di perpustakaan/program lain dan melihat apakah ada perbedaan. Misalnya, beberapa kesalahan perpustakaan matematika presisi tinggi terlihat seperti ini. Ini biasanya tidak menyebabkan masalah keamanan, jadi kami tidak akan berkonsentrasi sebanyak ini.
Bagaimana cara kerja fuzzer?
Fuzzer pada dasarnya adalah loop mutate-execute-repeat yang mengeksplorasi ruang status aplikasi untuk mencoba "secara acak" menemukan status crash / keamanan vuln. Itu tidak menemukan eksploitasi, hanya vuln. Bagian utama dari fuzzer adalah mutator itu sendiri. Lebih lanjut tentang ini nanti.
Output dari fuzzer?
Di fuzzer, debugger (kadang -kadang) melekat pada aplikasi untuk mendapatkan semacam laporan dari kecelakaan itu, untuk dapat menganalisisnya nanti sebagai keamanan vs vs kecelakaan jinak (tetapi mungkin penting).
Bagaimana cara menentukan bidang program apa yang terbaik untuk fuzz terlebih dahulu?
Saat fuzzing, kami ingin biasanya berkonsentrasi pada satu bagian atau set kecil program. Ini biasanya dilakukan terutama untuk mengurangi jumlah eksekusi yang harus dilakukan. Biasanya, kami berkonsentrasi pada penguraian dan pemrosesan saja. Sekali lagi, batas keamanan sangat penting dalam memutuskan bagian mana yang penting bagi kita.
Jenis Fuzzer?
Sampel input yang diberikan pada fuzzer disebut corpus . Di oldschool fuzzer (alias "buta"/"bodoh" fuzzzer) ada kebutuhan untuk corpus besar. Fuzzer yang lebih baru (alias "genetik", misalnya AFL) tidak perlu membutuhkan korpus yang begitu besar, karena mereka menjelajahi negara sendiri.
Bagaimana fuzzer berguna?
Fuzzer terutama berguna untuk "buah gantung rendah". Itu tidak akan menemukan bug logika yang rumit, tetapi dapat menemukan bug yang mudah untuk menemukan bug (yang sebenarnya kadang -kadang mudah dilewatkan selama analisis manual). Sementara saya mungkin mengatakan input di seluruh catatan ini, dan biasanya merujuk ke file input , itu tidak perlu hanya itu. Fuzzer dapat menangani input yang mungkin stdin atau input file atau soket jaringan atau banyak lainnya. Tanpa terlalu banyak kehilangan sifat umum, kita dapat menganggapnya hanya sebagai file untuk saat ini.
Bagaimana cara menulis fuzzer (dasar)?
Sekali lagi, itu hanya perlu menjadi loop mutasi-lari-ulang. Kita harus sering memanggil target ( subprocess.Popen ). Kita juga harus dapat meneruskan input ke dalam program (misalnya file) dan mendeteksi crash ( SIGSEGV dll menyebabkan pengecualian yang dapat ditangkap). Sekarang, kita hanya perlu menulis mutator untuk file input, dan terus memanggil target pada file yang bermutasi.
Mutator? Apa?!?
Mungkin ada beberapa mutator yang mungkin. Mudah (yaitu sederhana untuk diimplementasikan) mungkin untuk bermutasi bit, mutasi byte, atau bermutasi menjadi nilai "sihir". Untuk meningkatkan peluang kecelakaan, alih -alih hanya mengubah 1 bit atau sesuatu, kita dapat mengubah banyak (mungkin beberapa persentase parameter dari mereka?). Kita juga dapat (bukan mutasi acak), mengubah byte/kata/dword/dll ke beberapa nilai "sihir". Nilai ajaib mungkin 0 , 0xff , 0xffff , 0xffffffff , 0x80000000 (32-bit INT_MIN ), 0x7fffffff (32-bit INT_MAX ) dll. Pada dasarnya, memilih yang umum menyebabkan masalah keamanan (karena mereka mungkin memicu beberapa kasus tepi). Kita dapat menulis mutator yang lebih cerdas jika kita tahu info lebih lanjut tentang program (misalnya, untuk bilangan bulat berbasis string, kita mungkin menulis sesuatu yang mengubah string integer menjadi "65536" atau -1 dll). Mutator berbasis chunk mungkin menggerakkan potongan -potongan (pada dasarnya, mengorganisasi input). Mutator aditif/penambahan juga berfungsi (misalnya menyebabkan input yang lebih besar ke buffer). Pemicu juga mungkin berfungsi (misalnya, kadang -kadang EOF mungkin tidak ditangani dengan baik). Pada dasarnya, cobalah sejumlah besar cara kreatif tentang hal -hal. Semakin banyak pengalaman sehubungan dengan program (dan eksploitasi secara umum), mutator yang lebih berguna mungkin terjadi.
Tapi apa ini "genetik" ini?
Itu mungkin diskusi untuk nanti. Namun, beberapa tautan ke beberapa fuzzer modern (open source) adalah AFL dan Honggfuzz.
Ditulis pada 7 April 2017
Dipengaruhi dari tantangan yang bagus di Picoctf 2017 (Name of Challenge Ditahan, karena kontes masih berlangsung)
PERINGATAN: Catatan ini mungkin tampak sederhana/jelas bagi beberapa pembaca, tetapi mengharuskan mengatakan, karena pelapisannya tidak jelas bagi saya sampai baru -baru ini.
Tentu saja, ketika pemrograman, kita semua menggunakan abstraksi, apakah itu kelas dan objek, atau fungsi, atau meta-fungsi, atau polimorfisme, atau monad, atau fungsi, atau semua jazz itu. Namun, dapatkah kita benar -benar memiliki hal seperti itu selama eksploitasi? Jelas, kita dapat mengeksploitasi kesalahan yang dibuat dalam menerapkan abstraksi yang disebutkan di atas, tetapi di sini, saya berbicara tentang sesuatu yang berbeda.
Di beberapa CTF, setiap kali saya telah menulis eksploitasi sebelumnya, itu telah menjadi skrip eksploitasi ad-hoc yang menjatuhkan shell. Saya menggunakan pwntools yang menakjubkan sebagai kerangka kerja (untuk menghubungkan ke layanan, dan mengubah hal -hal, dan dynelf, dll), tapi itu saja. Setiap eksploitasi cenderung menjadi cara ad-hoc untuk bekerja menuju tujuan eksekusi kode sewenang-wenang. Namun, tantangan saat ini, serta memikirkan catatan saya sebelumnya tentang eksploitasi string format "canggih", membuat saya menyadari bahwa saya dapat melapisi eksploitasi saya dengan cara yang konsisten, dan bergerak melalui lapisan abstraksi yang berbeda untuk akhirnya mencapai tujuan yang diperlukan.
Sebagai contoh, mari kita anggap kerentanan sebagai kesalahan logika, yang memungkinkan kita membaca/menulis 4 byte, di suatu tempat dalam kisaran kecil setelah buffer. Kami ingin menyalahgunakan ini sepanjang jalan untuk mendapatkan eksekusi kode, dan akhirnya bendera.
Dalam skenario ini, saya akan menganggap abstraksi ini sebagai primitif short-distance-write-anything . Dengan ini sendiri, jelas kita tidak bisa berbuat banyak. Namun demikian, saya membuat fungsi Python kecil vuln(offset, val) . Namun, karena tepat setelah buffer, mungkin ada beberapa data/meta-data yang mungkin berguna, kita dapat menyalahgunakan ini untuk membangun baik read-anywhere dan write-anything-anywhere primitif. Ini berarti, saya menulis fungsi python pendek yang menyebut fungsi vuln() yang sebelumnya didefinisikan. Fungsi get_mem(addr) dan set_mem(addr, val) ini dibuat secara sederhana (dalam contoh saat ini) hanya dengan menggunakan fungsi vuln() untuk menimpa pointer, yang kemudian dapat diereferensikan di tempat lain dalam biner.
Sekarang, setelah kami memiliki abstraksi get_mem() dan set_mem() ini, saya membangun abstraksi anti-ASLR, dengan pada dasarnya membocorkan 2 alamat dari GOT through get_mem() dan membandingkan dengan database LIBC (terima kasih @niklasb untuk membuat database). Offset dari ini memberi saya libc_base dengan andal, yang memungkinkan saya untuk mengganti fungsi apa pun di GOT dengan yang lain dari libc.
Ini pada dasarnya memberi saya kendali atas EIP (saat saya dapat "memicu" salah satu fungsi itu tepat ketika saya mau). Sekarang, yang tersisa adalah bagi saya untuk memanggil pemicu dengan parameter yang tepat. Jadi saya mengatur parameter sebagai abstraksi terpisah, dan kemudian memanggil trigger() dan saya memiliki akses shell pada sistem.
TL; DR: Seseorang dapat membangun primitif eksploitasi kecil (yang tidak memiliki terlalu banyak kekuatan), dan dengan menggabungkannya dan membangun hierarki primitif yang lebih kuat, kita dapat memperoleh eksekusi lengkap.
Ditulis pada 6 April 2017
Dipengaruhi oleh streaming langsung yang luar biasa oleh Gynvael Coldwind, di mana ia berbicara tentang eksploitasi string format
Eksploitasi string format sederhana:
Anda dapat menggunakan %p untuk melihat apa yang ada di tumpukan. Jika string format itu sendiri ada di tumpukan, maka seseorang dapat menempatkan alamat (katakanlah foo ) ke tumpukan, dan kemudian mencarinya menggunakan spesifikasi posisi n$ (misalnya, AAAA %7$p mungkin mengembalikan AAAA 0x41414141 , jika 7 adalah posisi pada tumpukan). Kita kemudian dapat menggunakan ini untuk membangun primitif read-where , menggunakan spesifikasi format %s sebagai gantinya (misalnya, AAAA %7$s akan mengembalikan nilai pada alamat 0x41414141, melanjutkan contoh sebelumnya). We can also use the %n format specifier to make it into a write-what-where primitive. Usually instead, we use %hhn (a glibc extension, iirc), which lets us write one byte at a time.
We use the above primitives to initially beat ASLR (if any) and then overwrite an entry in the GOT (say exit() or fflush() or ...) to then raise it to an arbitrary-eip-control primitive, which basically gives us arbitrary-code-execution .
Possible difficulties (that make it "advanced" exploitation):
If we have partial ASLR , then we can still use format strings and beat it, but this becomes much harder if we only have one-shot exploit (ie, our exploit needs to run instantaneously, and the addresses are randomized on each run, say). The way we would beat this is to use addresses that are already in the memory, and overwrite them partially (since ASLR affects only higher order bits). This way, we can gain reliability during execution.
If we have a read only .GOT section, then the "standard" attack of overwriting the GOT will not work. In this case, we look for alternative areas that can be overwritten (preferably function pointers). Some such areas are: __malloc_hook (see man page for the same), stdin 's vtable pointer to write or flush , etc. In such a scenario, having access to the libc sources is extremely useful. As for overwriting the __malloc_hook , it works even if the application doesn't call malloc , since it is calling printf (or similar), and internally, if we pass a width specifier greater than 64k (say %70000c ), then it will call malloc, and thus whatever address was specified at the global variable __malloc_hook .
If we have our format string buffer not on the stack , then we can still gain a write-what-where primitive, though it is a little more complex. First off, we need to stop using the position specifiers n$ , since if this is used, then printf internally copies the stack (which we will be modifying as we go along). Now, we find two pointers that point ahead into the stack itself, and use those to overwrite the lower order bytes of two further ahead pointing pointers on the stack, so that they now point to x+0 and x+2 where x is some location further ahead on the stack. Using these two overwrites, we are able to completely control the 4 bytes at x , and this becomes our where in the primitive. Now we just have to ignore more positions on the format string until we come to this point, and we have a write-what-where primitive.
Written on 1st April 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he explains about race conditions
If a memory region (or file or any other resource) is accessed twice with the assumption that it would remain same, but due to switching of threads, we are able to change the value, we have a race condition.
Most common kind is a TOCTTOU (Time-of-check to Time-of-use), where a variable (or file or any other resource) is first checked for some value, and if a certain condition for it passes, then it is used. In this case, we can attack it by continuously "spamming" this check in one thread, and in another thread, continuously "flipping" it so that due to randomness, we might be able to get a flip in the middle of the "window-of-opportunity" which is the (short) timeframe between the check and the use.
Usually the window-of-opportunity might be very small. We can use multiple tricks in order to increase this window of opportunity by a factor of 3x or even up to ~100x. We do this by controlling how the value is being cached, or paged. If a value (let's say a long int ) is not aligned to a cache line, then 2 cache lines might need to be accessed and this causes a delay for the same instruction to execute. Alternatively, breaking alignment on a page, (ie, placing it across a page boundary) can cause a much larger time to access. This might give us higher chance of the race condition being triggered.
Smarter ways exist to improve this race condition situation (such as clearing TLB etc, but these might not even be necessary sometimes).
Race conditions can be used, in (possibly) their extreme case, to get ring0 code execution (which is "higher than root", since it is kernel mode execution).
It is possible to find race conditions "automatically" by building tools/plugins on top of architecture emulators. For further details, http://vexillium.org/pub/005.html
Written on 31st Mar 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he is experimenting on the heap
Use-after-free:
Let us say we have a bunch of pointers to a place in heap, and it is freed without making sure that all of those pointers are updated. This would leave a few dangling pointers into free'd space. This is exploitable by usually making another allocation of different type into the same region, such that you control different areas, and then you can abuse this to gain (possibly) arbitrary code execution.
Double-free:
Free up a memory region, and the free it again. If you can do this, you can take control by controlling the internal structures used by malloc. This can get complicated, compared to use-after-free, so preferably use that one if possible.
Classic buffer overflow on the heap (heap-overflow):
If you can write beyond the allocated memory, then you can start to write into the malloc's internal structures of the next malloc'd block, and by controlling what internal values get overwritten, you can usually gain a read-what-where primitive, that can usually be abused to gain higher levels of access (usually arbitrary code execution, via the GOT PLT , or __fini_array__ or similar).