
Sebagai bagian dari kontes ZeroNights HackQuest 2018 tahunan, para peserta diundang untuk mencoba berbagai tugas dan kompetisi yang tidak sepele. Bagian dari salah satunya terkait dengan generasi contoh permusuhan untuk jaringan saraf. Dalam artikel kami, kami telah memperhatikan metode serangan dan pertahanan algoritma pembelajaran mesin. Dalam kerangka publikasi ini, kami akan menganalisis contoh bagaimana mungkin untuk menyelesaikan tugas dengan ZeroNights Hackquest menggunakan perpustakaan bodoh.
Dalam tugas ini , penyerang seharusnya mendapatkan akses ke server. Setelah ia berhasil, ia melihat struktur file berikut di direktori rumahnya:
| Home --| KerasModel.h5 --| Task.txt --| ZeroSource.bmp
Informasi berikut ada di file Task.txt:
Now it is time for a final boss! http://51.15.100.188:36491/predict You have a mode and an image. To get a ticket, you need to change an image so that it is identified as "1". curl -X POST -F image=@ZeroSource.bmp 'http://51.15.100.188:36491/predict'. (don't forget about normalization (/255) ^_^)
Untuk mendapatkan tiket yang didambakan, penyerang diminta untuk mengonversi ZeroSource.bmp:

sehingga setelah mengirim ke server jaringan saraf menginterpretasikan gambar ini sebagai 1. Jika Anda mencoba mengirim gambar ini tanpa diproses, hasil dari jaringan saraf adalah 0.
Dan, tentu saja, petunjuk utama untuk tugas ini adalah file model KerasModel.h5 (file ini membantu penyerang mentransfer serangan ke pesawat WhiteBox, karena jaringan saraf dan semua data yang terkait dengannya dapat diakses olehnya). Nama file segera berisi petunjuk - nama kerangka kerja di mana jaringan saraf diimplementasikan.
Dengan catatan pengantar inilah peserta mulai menyelesaikan tugas:
- Model jaringan saraf ditulis dalam Keras.
- Kemampuan untuk mengirim gambar ke server menggunakan curl.
- Gambar asli yang perlu diubah.
Di sisi server, cek sesederhana mungkin:
- Gambar harus berukuran tepat - 28x28 piksel.
- Pada gambar ini, model harus mengembalikan 1.
- Perbedaan antara gambar awal ZeroSource.bmp dan yang dikirim ke server harus kurang dari ambang k oleh metrik MSE (kesalahan standar).
Jadi mari kita mulai.
Pertama, peserta perlu menemukan informasi tentang cara menipu jaringan saraf. Setelah beberapa saat di Google, ia mendapatkan kata kunci "Contoh permusuhan" dan "Serangan permusuhan." Selanjutnya, dia perlu mencari alat untuk menerapkan serangan permusuhan. Jika Anda mengarahkan ke Google kueri "Serangan permusuhan pada Keras Neural Net", tautan pertama adalah ke GitHub dari proyek FoolBox - pustaka python untuk menghasilkan contoh-contoh permusuhan. Tentu saja, ada perpustakaan lain (kami membicarakannya di beberapa artikel sebelumnya ). Selain itu, serangan dapat ditulis, seperti yang mereka katakan, dari awal. Namun kami masih fokus pada perpustakaan paling populer, yang dapat ditemukan oleh seseorang yang sebelumnya tidak menemukan topik serangan permusuhan pada tautan pertama di Google.
Sekarang Anda perlu menulis skrip Python yang akan menghasilkan contoh permusuhan.
Kami akan mulai, tentu saja, dengan impor.
import keras import numpy as np from PIL import Image import foolbox
Apa yang kita lihat di sini?
- Keras adalah kerangka kerja di mana Jaringan Saraf Tiruan ditulis, yang akan kita tipu.
- NumPy adalah perpustakaan yang memungkinkan kita untuk bekerja dengan vektor secara efisien.
- PIL adalah alat untuk bekerja dengan gambar.
- FoolBox adalah pustaka untuk menghasilkan contoh-contoh permusuhan.
Hal pertama yang harus dilakukan adalah, tentu saja, memuat model jaringan saraf ke dalam memori program kami dan melihat informasi model.
model = keras.models.load_model("KerasModel.h5")
Pada output, kami mendapatkan yang berikut:
Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 26, 26, 32) 320 _________________________________________________________________ conv2d_2 (Conv2D) (None, 26, 26, 64) 18496 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 13, 13, 64) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 13, 13, 64) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 13, 13, 64) 36928 _________________________________________________________________ conv2d_4 (Conv2D) (None, 13, 13, 128) 73856 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 6, 6, 128) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 4608) 0 _________________________________________________________________ dense_1 (Dense) (None, 256) 1179904 _________________________________________________________________ dense_2 (Dense) (None, 10) 2570 ================================================================= Total params: 1,312,074 Trainable params: 1,312,074 Non-trainable params: 0 _________________________________________________________________ <tf.Tensor 'conv2d_1_input_1:0' shape=(?, 28, 28, 1) dtype=float32>
Informasi apa yang bisa saya dapatkan dari sini?
- Model input (lapisan conv2d_1) menerima objek dimensi? X28x28x1, di mana "?" - jumlah objek; jika gambar adalah satu, maka dimensinya adalah 1x28x28x1. Dan gambar adalah array tiga dimensi, di mana satu dimensi adalah 1. Artinya, gambar disajikan sebagai tabel nilai dari 0 hingga 255.
- Pada output model (lapisan dense_2), vektor dimensi 10 diperoleh.
Kami memuat gambar dan jangan lupa untuk mengubahnya menjadi tipe float (lebih jauh jaringan saraf akan bekerja dengan bilangan real) dan menormalkannya (bagi semua nilai dengan 255). Di sini perlu diperjelas bahwa normalisasi adalah salah satu trik "wajib" ketika bekerja dengan jaringan saraf, tetapi penyerang mungkin tidak mengetahui hal ini, jadi kami secara khusus menambahkan sedikit petunjuk dalam deskripsi tugas):
img = Image.open("ZeroSource.bmp")
Sekarang kita dapat mengirim gambar ke model yang dimuat dan melihat apa yang dihasilkannya:
model.predict(img.reshape(1,28,28,1))
Pada output kami mendapatkan informasi berikut
array([[1.0000000e+00, 4.2309660e-19, 3.1170484e-15, 6.2545637e-18, 1.4199094e-16, 6.3990816e-13, 6.9493417e-10, 2.8936278e-12, 8.9440377e-14, 1.6340098e-12]], dtype=float32)
Perlu dijelaskan apa vektor ini: pada kenyataannya, ini adalah distribusi probabilitas, yaitu, setiap angka mewakili probabilitas kelas 0,1,2 ..., 9. Jumlah semua angka dalam vektor adalah 1. Dalam hal ini, dapat dilihat bahwa model yakin bahwa gambar input mewakili kelas 0 dengan probabilitas 100%.
Jika kami menggambarkan ini pada histogram, kami mendapatkan yang berikut:

Keyakinan Mutlak.
Jika model tidak dapat menentukan kelas, vektor probabilitas akan cenderung ke distribusi yang seragam, yang, pada gilirannya, akan berarti bahwa model menetapkan objek ke semua kelas secara bersamaan dengan probabilitas yang sama. Dan histogram akan terlihat seperti ini:

Secara umum diterima bahwa kelas model ditentukan oleh indeks angka maksimum dalam vektor yang diberikan. Artinya, model tersebut secara teoritis dapat memilih kelas dengan probabilitas lebih dari 10%. Tetapi logika ini dapat bervariasi tergantung pada logika keputusan yang dijelaskan oleh pengembang.
Sekarang mari kita beralih ke bagian yang paling menarik - serangan permusuhan.
Pertama, untuk bekerja dengan model di perpustakaan FoolBox, Anda harus menerjemahkan model ke dalam notasi Foolbox. Anda dapat melakukannya dengan cara ini:
fmodel = foolbox.models.KerasModel(model,bounds=(0,1))
Setelah itu, Anda dapat menguji berbagai serangan. Mari kita mulai dengan yang paling populer - FGSM:
Fgsm
attack = foolbox.attacks.FGSM(fmodel)
Output dari jaringan saraf adalah sebagai berikut
[4.8592144e-01 2.5432981e-14 5.7048566e-13 1.6787202e-14 1.6875961e-11 1.2974949e-07 5.1407838e-01 3.9819957e-12 1.9827724e-09 5.7383300e-12] 6
Dan gambar yang dihasilkan:

Jadi, sekarang dengan probabilitas lebih dari 50% 0 diakui sebagai 6. Sudah bagus. Namun, kami masih ingin mendapatkan 1, dan tingkat kebisingannya tidak terlalu mengesankan. Gambar benar-benar terlihat tidak masuk akal. Lebih lanjut tentang ini nanti. Sementara itu, mari kita coba mengabaikan serangan. Tiba-tiba, kita masih mendapatkan 1.
Serangan L-BFGS
attack = foolbox.attacks.LBFGSAttack(fmodel) adversarial = attack(img.reshape(28,28,1),0) probs = model.predict(adversarial.reshape(1,28,28,1)) print(probs) print(np.argmax(probs))
Kesimpulan:
[4.7782943e-01, 1.9682934e-10, 1.0285517e-06, 3.2558936e-10, 6.5797998e-05, 4.0495447e-06, 2.5545436e-04, 3.4730587e-02, 5.5223148e-07, 4.8711312e-01] 9
Gambar:

Lagi oleh. Sekarang kita memiliki 0 yang dikenali sebagai 9 dengan probabilitas ~ 49%. Namun, kebisingannya jauh lebih sedikit.
Mari kita akhiri dengan beat acak. Contoh dipilih sedemikian rupa sehingga akan sangat sulit untuk mendapatkan hasilnya secara acak. Sekarang kami belum mengindikasikan di mana pun kami ingin mendapatkan 1. Oleh karena itu, kami melakukan serangan non-target dan percaya bahwa kami masih akan mendapatkan kelas 1, tetapi ini tidak terjadi. Karenanya, ada baiknya beralih ke serangan yang ditargetkan. Mari kita gunakan dokumentasi idibox dan temukan modul kriteria di sana
Dalam modul ini, Anda dapat memilih kriteria untuk serangan, jika mendukungnya. Secara khusus, kami tertarik pada dua kriteria:
- TargetClass - membuat sehingga dalam vektor distribusi probabilitas, elemen dengan angka k memiliki probabilitas maksimum.
- TargetClassProbability - membuatnya sehingga dalam vektor distribusi probabilitas, elemen dengan angka k memiliki probabilitas setidaknya p.
Mari kita coba keduanya:
L-BFGS + TargetClass
Hal utama dalam kriteria TargetClass adalah untuk mendapatkan probabilitas kelas k, lebih tinggi dari probabilitas kelas lainnya. Maka jaringan yang membuat keputusan hanya dengan melihat probabilitas maksimum akan keliru.
attack = foolbox.attacks.LBFGSAttack(fmodel,foolbox.criteria.TargetClass(1))
Kesimpulan:
[3.2620126e-01 3.2813528e-01 8.5446298e-02 8.1292394e-04 1.1273423e-03 2.4886258e-02 3.3904776e-02 1.9947644e-01 8.2347924e-07 8.5878673e-06] 1
Gambar:

Seperti yang dapat dilihat dari kesimpulan, sekarang jaringan saraf kita mengklaim bahwa itu adalah 1 dengan probabilitas 32,8%, sedangkan probabilitas 0 sedekat mungkin dengan nilai ini dan 32,6%. Kami berhasil! Pada prinsipnya, ini sudah cukup untuk menyelesaikan tugas. Tetapi kita akan melangkah lebih jauh dan mencoba untuk mendapatkan probabilitas 1 di atas 0,5.
L-BFGS + TargetClassProbability
Sekarang kita menggunakan kriteria TargetClassProbability, yang memungkinkan Anda untuk mendapatkan probabilitas suatu kelas dalam objek yang tidak lebih rendah dari p. Hanya memiliki dua parameter:
1) Jumlah kelas objek.
2) Probabilitas kelas ini dalam contoh permusuhan.
Selain itu, jika tidak mungkin untuk mencapai probabilitas seperti itu, atau waktu untuk menemukan objek seperti itu membutuhkan terlalu banyak waktu, maka objek permusuhan akan sama dengan tidak ada. Anda dapat memverifikasi ini sendiri dengan mencoba membuat probabilitas, katakanlah, 0,99. Maka metode ini mungkin tidak konvergen.
attack = foolbox.attacks.LBFGSAttack(fmodel,foolbox.criteria.TargetClassProbability(1,0.5)) adversarial = attack(img.reshape(28,28,1),0) probs = model.predict(adversarial.reshape(1,28,28,1)) print(probs) print(np.argmax(probs))
Kesimpulan:
[4.2620126e-01 5.0013528e-01 9.5413298e-02 8.1292394e-04 1.1273423e-03 2.4886258e-02 3.3904776e-02 1.9947644e-01 8.2347924e-07 8.5878673e-06]
Hore! Kami berhasil mendapatkan contoh permusuhan, di mana probabilitas 1 untuk jaringan saraf kami di atas 50%! Hebat! Sekarang mari kita lakukan denormalisasi (mengembalikan gambar ke format 0-255) dan menyimpannya.
Script terakhir adalah sebagai berikut:
import keras from PIL import Image import numpy as np import foolbox from foolbox.criteria import TargetClassProbability import scipy.misc model = keras.models.load_model("KerasModel.h5") img = Image.open("ZeroSource.bmp") img = np.array(img.getdata()) img = img.astype('float32') img = img /255. img = img.reshape(28,28,1) fmodel = foolbox.models.KerasModel(model,bounds=(0,1)) attack = foolbox.attacks.LBFGSAttack(fmodel,criterion=TargetClassProbability(1 ,p=.5)) adversarial = attack(img[:,:,::-1], 0) adversarial = adversarial * 255 adversarial = adversarial.astype('int') scipy.misc.toimage(adversarial.reshape(28,28)).save('AdversarialExampleZero.bmp')
Dan gambar terakhir adalah sebagai berikut:
.
Kesimpulan
Jadi, seperti yang kita lihat dari contoh di atas, menipu jaringan saraf cukup sederhana. Ada juga sejumlah besar metode yang mampu melakukan ini. Cukup buka daftar serangan yang tersedia di kotak bodoh dan coba terapkan. Kami menyarankan Anda untuk mencoba engkol yang sama sendiri, dengan mengambil dasar jaringan saraf yang sama dan gambar yang sama, tersedia dengan referensi . Anda dapat meninggalkan pertanyaan Anda di komentar. Kami akan menjawabnya!
Selalu ingat bahwa, betapa pun bermanfaatnya algoritma dan modelnya, mereka bisa sangat tidak stabil terhadap perubahan kecil yang dapat menyebabkan kesalahan serius. Karena itu, kami menyarankan Anda menguji model Anda, di mana python dan alat-alat seperti foolbox dapat membantu.
Terima kasih atas perhatian anda!