Latar Belakang
Kami sudah memiliki tantangan tentang melempar SIGSEGV , jadi mengapa bukan tantangan tentang melempar SIGILL?
Apa itu SIGILL?
SIGILL adalah sinyal untuk instruksi ilegal di prosesor, yang jarang terjadi. Tindakan default setelah menerima SIGILL adalah menghentikan program dan menulis dump inti. ID sinyal SIGILL adalah 4. Anda jarang menemukan SIGILL, dan saya sama sekali tidak tahu cara membuatnya dalam kode Anda kecuali melalui sudo kill -s 4 <pid>
.
Aturan
Anda akan memiliki root di program Anda, tetapi jika Anda tidak mau karena alasan apa pun, Anda juga dapat menggunakan pengguna normal. Saya menggunakan komputer Linux dengan lokal Jerman dan saya tidak tahu teks bahasa Inggris yang ditampilkan setelah menangkap SIGILL, tapi saya pikir itu sesuatu seperti 'Instruksi ilegal'. Program terpendek yang melempar SIGILL menang.
raise(SIGILL)
?Illegal instruction (core dumped)
.Jawaban:
Assembler PDP-11 (Edisi Keenam UNIX), 1 byte
Instruksi 9 bukan instruksi yang valid pada PDP-11 (dalam oktal, itu akan menjadi
000011
, yang tidak muncul pada daftar instruksi (PDF)). Assembler PDP-11 yang dikirimkan bersama UNIX Sixth Edition ternyata menggemakan semua yang tidak dimengerti ke dalam file secara langsung; dalam hal ini, 9 adalah angka, sehingga menghasilkan instruksi literal 9. Ini juga memiliki properti aneh (tidak biasa dalam bahasa assembly saat ini) bahwa file mulai berjalan dari awal, jadi kita tidak perlu deklarasi untuk membuat program kerja.Anda dapat menguji program menggunakan emulator ini , meskipun Anda harus berjuang sedikit untuk memasukkan program.
Inilah cara akhirnya setelah Anda menemukan cara menggunakan sistem file, editor, terminal, dan hal-hal serupa yang Anda pikir sudah Anda ketahui cara menggunakannya:
Saya telah mengkonfirmasi dengan dokumentasi bahwa ini adalah
SIGILL
sinyal asli (dan bahkan memiliki nomor sinyal yang sama, 4, sejak saat itu!)sumber
a.out
memiliki banyak byte di dalamnya, memang (9
instruksi dikompilasi menjadi dua byte, dan assembler juga menambahkan header dan footer untuk membuat program dapat dieksekusi). Itu sebabnya saya menulis program dalam bahasa assembly, bukan dalam kode mesin. Program bahasa assembly hanya memiliki satu byte, dan mengkompilasi ke program dengan byte lebih banyak di; ini adalah masalah kode-golf (meminimalkan ukuran sumber), bukan masalah pembuatan kode (meminimalkan ukuran yang dapat dieksekusi), jadi ukuran 1-byte dari sumber yang penting.C (x86_64, tcc ), 7 byte
Terinspirasi oleh jawaban ini .
Cobalah online!
Bagaimana itu bekerja
Perakitan yang dihasilkan terlihat seperti ini.
Perhatikan bahwa TCC tidak menempatkan "fungsi" yang ditentukan di segmen data .
Setelah dikompilasi, _start akan mengarah ke main seperti biasa. Ketika program yang dihasilkan dijalankan, ia mengharapkan kode di main dan menemukan integer 6 bit-endian (!) 6 , yang dikodekan sebagai 0x06 0x00 0x00 0x00 . Byte pertama - 0x06 - adalah opcode yang tidak valid, sehingga program berakhir dengan SIGILL .
C (x86_64, gcc ), 13 byte
Cobalah online!
Bagaimana itu bekerja
Tanpa pengubah const , rakitan yang dihasilkan terlihat seperti ini.
Linker GCC memperlakukan baris terakhir sebagai petunjuk bahwa objek yang dihasilkan tidak memerlukan stack yang dapat dieksekusi. Karena main secara eksplisit ditempatkan di bagian data , opcode yang dikandungnya tidak dapat dieksekusi, sehingga program akan berhenti SIGSEGV (kesalahan segmentasi).
Menghapus baris kedua atau terakhir akan membuat pekerjaan yang dapat dieksekusi seperti yang dimaksudkan. Baris terakhir dapat diabaikan dengan flag compiler
-zexecstack
( Coba online! ), Tetapi ini biayanya 12 byte .Alternatif yang lebih pendek adalah mendeklarasikan main dengan const modifier, menghasilkan perakitan berikut.
Ini berfungsi tanpa flag kompiler. Catatan yang
main=6;
akan menulis "fungsi" yang didefinisikan dalam data , tetapi pengubah const membuat GCC menulisnya di rodata , yang (setidaknya pada platform saya) diizinkan berisi kode.sumber
main
angka 6 dan mencoba menyebutnya (yang saya kira akan membuatnya menyerah dan mencoba instruksinya)?main
tidak menjadi fungsi tetapi hanya jika Anda menghidupkan peringatan (salah satu-Wall
atau-pedantic
akan melakukannya)..rodata
bagian di dalam segmen teks yang dapat dieksekusi, dan saya berharap ini akan menjadi kasus pada hampir semua platform. (Program-loader kernel hanya peduli pada segmen, bukan bagian).06
ini hanya instruksi yang tidak valid di x86-64. Dalam mode 32-bit, ituPUSH ES
, jadi jawaban ini hanya berfungsi dengan kompiler yang defaultnya-m64
. Lihat ref.x86asm.net/coder.html#x06 . Satu-satunya urutan byte yang dijamin untuk memecahkan kode sebagai instruksi ilegal di semua CPU x86 masa depan adalah 2 byte UD2 :0F 0B
. Yang lain bisa berupa awalan atau pengkodean instruksi di masa depan. Namun, dipilih untuk cara yang keren untuk mendapatkan kompiler C untuk menempelkanmain
label pada beberapa byte!Swift, 5 byte
Indeks akses 0 dari array kosong. Panggilan ini
fatalError()
, yang mencetak pesan kesalahan dan lumpuh dengan SIGILL. Anda bisa mencobanya di sini .sumber
fatalError()
sengaja crash dengan menjalankanud2
. Mengapa mereka memilih untuk melakukan itu saya tidak tahu, tetapi mungkin mereka berpikir pesan kesalahan "Instruksi ilegal" masuk akal karena program melakukan sesuatu yang ilegal.nil!
, tetapi kompiler tidak dapat menyimpulkan tipe hasil. (Juga, hai JAL!)GNU C, 25 byte
GNU C (dialek khusus C dengan ekstensi) berisi instruksi untuk menghentikan program secara sengaja. Implementasi yang tepat bervariasi dari versi ke versi, tetapi seringkali pengembang berusaha untuk mengimplementasikan crash semurah mungkin, yang biasanya melibatkan penggunaan instruksi ilegal.
Versi spesifik yang saya gunakan untuk menguji adalah
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0
; Namun, program ini menyebabkan SIGILL pada berbagai platfom yang cukup luas, dan karenanya cukup portabel. Selain itu, ia melakukannya dengan benar-benar menjalankan instruksi ilegal. Berikut kode perakitan yang dikompilasi di atas dengan pengaturan optimalisasi default:ud2
adalah instruksi yang dijamin oleh Intel akan selalu tidak terdefinisi.sumber
main(){asm("ud2");}
00 00 0f 0b
apakah bahasa mesin untukud2
...00
byte tersebut; mereka bukan bagian dari kode mesin untuk UD2. BTW, seperti yang saya komentari pada jawaban Dennis , ada instruksi ilegal satu byte di x86-64 untuk saat ini, tetapi mereka tidak dijamin akan tetap seperti itu.00 00
mendekode yang sama dalam x86-64 (asadd [rax], al
).00 00 0f 0b
biasanya SIGSEGV sebelum SIGILL, kecuali jika Anda memiliki pointer yang dapat ditulisrax
.C (x86_64), 11, 30, 34, atau 34 + 15 = 49 byte
Saya telah mengajukan beberapa solusi yang menggunakan fungsi pustaka untuk melempar
SIGILL
melalui berbagai cara, tetapi bisa dibilang itu curang, karena fungsi pustaka menyelesaikan masalah. Berikut sejumlah solusi yang tidak menggunakan fungsi pustaka, dan membuat berbagai asumsi tentang di mana sistem operasi bersedia membiarkan Anda mengeksekusi kode yang tidak dapat dieksekusi. (Konstanta di sini dipilih untuk x86_64, tetapi Anda bisa mengubahnya untuk mendapatkan solusi yang berfungsi untuk sebagian besar prosesor lain yang memiliki instruksi ilegal.)06
adalah byte terendah dari kode mesin yang tidak sesuai dengan instruksi yang ditentukan pada prosesor x86_64. Jadi yang harus kita lakukan adalah menjalankannya. (Atau,2F
juga tidak terdefinisi, dan sesuai dengan satu karakter ASCII yang dapat dicetak.) Tidak satu pun dari ini dijamin akan selalu tidak terdefinisi, tetapi mereka tidak didefinisikan pada hari ini.Program pertama di sini dijalankan
2F
dari segmen data read-only. Sebagian besar penghubung tidak mampu menghasilkan lompatan kerja dari.text
ke.rodata
(atau yang setara dengan OS mereka) karena itu bukan sesuatu yang akan berguna dalam program yang tersegmentasi dengan benar; Saya belum menemukan sistem operasi yang berfungsi. Anda juga harus membiarkan fakta bahwa banyak kompiler menginginkan string tersebut menjadi string lebar, yang akan membutuhkan tambahanL
; Saya berasumsi bahwa sistem operasi apa pun yang berfungsi memiliki pandangan yang cukup usang, dan dengan demikian sedang membangun untuk standar pra-C94 secara default. Mungkin saja tidak ada tempat di mana program ini bekerja, tetapi ada juga kemungkinan bahwa ada suatu tempat di mana program ini bekerja, dan dengan demikian saya daftar di dalam kumpulan jawaban potensial yang lebih meragukan ke kurang meragukan. (Setelah saya memposting jawaban ini, Dennis juga menyebutkan kemungkinanmain[]={6}
dalam obrolan, yang panjangnya sama, dan yang tidak mengalami masalah dengan lebar karakter, dan bahkan mengisyaratkan potensi untukmain=6
, saya tidak bisa mengklaim jawaban ini sebagai jawaban yang masuk akal. punyaku, karena aku sendiri tidak memikirkannya.)Program kedua di sini dijalankan
06
dari segmen data baca-tulis. Pada sebagian besar sistem operasi, ini akan menyebabkan kesalahan segmentasi, karena segmen data yang dapat ditulis dianggap sebagai cacat desain yang buruk yang membuat kemungkinan eksploitasi. Ini tidak selalu terjadi, jadi, mungkin ini bekerja pada versi Linux yang cukup lama, tetapi saya tidak dapat dengan mudah mengujinya.Program ketiga dijalankan
06
dari stack. Sekali lagi, ini menyebabkan kesalahan segmentasi saat ini, karena stack biasanya diklasifikasikan sebagai tidak dapat ditulis untuk alasan keamanan. Dokumentasi linker yang saya lihat sangat menyiratkan bahwa dulu legal untuk dieksekusi dari stack (tidak seperti dua kasus sebelumnya, melakukannya kadang-kadang berguna), jadi meskipun saya tidak bisa mengujinya, saya cukup yakin ada beberapa versi Linux (dan mungkin sistem operasi lain) tempat ini bekerja.Akhirnya, jika Anda memberikan
-Wl,-z,execstack
(15 byte penalti) untukgcc
(jika menggunakan GNUld
sebagai bagian dari backend), itu akan secara eksplisit mematikan perlindungan stack yang dapat dieksekusi, memungkinkan program ketiga untuk bekerja dan memberikan sinyal operasi ilegal seperti yang diharapkan. Saya telah menguji dan memverifikasi versi 49-byte ini agar berfungsi. (Dennis menyebutkan dalam obrolan bahwa opsi ini tampaknya bekerja dengan baikmain=6
, yang akan memberikan skor 6 + 15. Saya cukup terkejut bahwa ini berhasil, mengingat bahwa 6 secara terang-terangan tidak ada di stack; opsi tautan tampaknya melakukan lebih dari sekadar namanya menyarankan.)sumber
const main=6;
berfungsi, seperti halnya beberapa variasi. Linker ini (yang saya duga adalah juga linker Anda) adalah mampu menghasilkan melompat dari.text
ke.rodata
; masalah yang Anda hadapi adalah, tanpaconst
, Anda masuk ke segmen data yang dapat ditulis (.data
), yang tidak dapat dieksekusi pada perangkat keras modern. Ini akan bekerja pada x86 yang lebih lama, di mana perangkat keras perlindungan memori tidak dapat menandai halaman sebagai dapat dibaca-tetapi-tidak-dapat dieksekusi.main
diperlukan suatu fungsi (§5.1.2.2.1) - Saya tidak tahu mengapa gcc menganggap menyatakanmain
sebagai objek data hanya layak mendapat peringatan, dan hanya dengan-pedantic
pada baris perintah. Seseorang di awal 1990-an mungkin berpikir bahwa tidak ada orang yang akan melakukan itu secara tidak sengaja, tetapi itu tidak seperti hal yang berguna untuk dilakukan dengan sengaja kecuali untuk permainan semacam ini.main[]="/"
untuk melompat ke segmen data read-only, karena literal string masuk dalam rodata. Anda telah terperangkap oleh perbedaan antarachar *foo = "..."
danchar foo[] = "..."
.char *foo = "..."
adalah gula sintaksis untukconst char __inaccessible1[] = "..."; char *foo = (char *)&__inaccessible1[0];
, jadi string literal memang masuk dalam rodata, danfoo
merupakan variabel global terpisah yang dapat ditulis yang menunjuk padanya. Denganchar foo[] = "..."
, bagaimanapun, seluruh array masuk di segmen data yang dapat ditulis.GNU as (x86_64), 3 byte
$ xxd sigill.S
$ as --64 sigill.S -o sigill.o; ld -S sigill.o -o sigill
$ ./sigill
$ objdump -d sigill
sumber
asm-link
) saya yang biasa ( ) untuk program-program mainan file tunggal akan membangun executable dari sumber ini dengan cara yang sama, karenald
default titik masuk ke awal segmen teks atau sesuatu seperti itu. Saya hanya tidak berpikir untuk pergi untuk ukuran sumber ASM yang satu ini: PBash on Raspbian on QEMU, 4 (1?) Byte
Bukan pekerjaanku. Saya hanya melaporkan karya orang lain. Saya bahkan tidak dalam posisi untuk menguji klaim. Karena bagian penting dari tantangan ini tampaknya menemukan lingkungan di mana sinyal ini akan dinaikkan dan ditangkap, saya tidak termasuk ukuran QEMU, Raspbian, atau bash.
Pada 27 Februari 2013 8:49 malam, pengguna emlhalac melaporkan " Mendapatkan 'instruksi ilegal' ketika mencoba melakukan chroot " di forum Raspberry Pi.
memproduksi
Saya membayangkan perintah yang jauh lebih pendek akan menghasilkan output ini, misalnya
tr
,.EDIT: Berdasarkan komentar @ fluffy , mengurangi batas bawah dugaan pada panjang input menjadi "1?".
sumber
[
perintah itu akan menang. :)File MS-DOS COM x86, 2 byte
EDIT: Seperti yang ditunjukkan dalam komentar, DOS itu sendiri tidak akan menjebak pengecualian CPU dan hanya akan menggantung (bukan hanya aplikasi, seluruh OS). Menjalankan OS 32-bit berbasis NT seperti Windows XP, memang akan memicu sinyal instruksi ilegal.
Dari dokumentasi :
Yang cukup jelas. Simpan sebagai file .com, dan
jalankan di emulatorDOS. Emulator DOS akan macet. Jalankan pada Windows XP, Vista, atau 7 32-bit.sumber
#UD
perangkap. (Juga, saya memutuskan untuk benar-benar mengujinya, dan tampaknya emulator DOS saya dibuang ke loop yang tak terbatas.)C (Windows 32-bit), 34 byte
Ini hanya berfungsi jika kompilasi tanpa optimasi (selain itu, kode ilegal dalam
f
fungsi "dioptimalkan").Pembongkaran
main
fungsi terlihat seperti ini:Kita dapat melihat bahwa ia menggunakan
push
instruksi dengan nilai literal0b0f
(little-endian, sehingga byte-bytenya di-swap). Thecall
instruksi mendorong alamat pengirim (dari...
instruksi), yang terletak di tumpukan dekat parameter fungsi. Dengan menggunakan[-1]
perpindahan, fungsi menimpa alamat pengirim sehingga menunjuk 9 byte lebih awal, di mana byte0f 0b
berada.Bytes ini menyebabkan pengecualian "instruksi tidak terdefinisi", seperti yang dirancang.
sumber
Java,
504324 byteIni adalah
java.util.function.Consumer<Runtime>
1 yang perintahnya dicuri dari jawaban Fluffy . Ini berfungsi karena Anda harus menyebutnya sebagaiwhateverNameYouGiveIt.accept(Runtime.getRuntime())
!Perhatikan bahwa ini akan membuat proses baru dan membuatnya melempar SIGILL daripada melemparkan SIGILL itu sendiri.
1 - Secara teknis, ini juga bisa menjadi
java.util.function.Function<Runtime, Process>
karenaRuntime#exec(String)
mengembalikanjava.lang.Process
yang dapat digunakan untuk mengontrol proses yang baru saja Anda buat dengan mengeksekusi perintah shell.Demi melakukan sesuatu yang lebih mengesankan dalam bahasa lisan, inilah bonus
726048-byte:Yang satu ini adalah
Consumer<Runtime>
yang melewati SEMUA proses (termasuk dirinya sendiri), membuat masing-masing dari mereka melemparkan SIGILL. Penjepit yang lebih baik untuk kecelakaan hebat.Dan bonus lain (a
Consumer<ANYTHING_GOES>
), yang setidaknya berpura - pura melempar SIGILL dalam 20 byte:sumber
Perl, 9 byte
Cukup panggil fungsi pustaka yang sesuai untuk memberi sinyal suatu proses, dan dapatkan program untuk memberi sinyal sendiri
SIGILL
. Tidak ada instruksi ilegal yang terlibat di sini, tetapi itu menghasilkan hasil yang sesuai. (Saya pikir ini membuat tantangannya cukup murah, tetapi jika ada yang diizinkan, ini adalah celah yang akan Anda gunakan ...)sumber
+
. :)+
. Setelah bermain golf sebentar, mereka+
sesekali melakukan pamer. Akhirnya, mereka telah menulis program yang cukup di mana mereka perlu menghindari ruang kosong untuk beberapa alasan atau lainnya yang+
menjadi kebiasaan. (Ini juga mem-parsing kurang ambigu, karena bekerja di sekitar memicu kasus khusus di parser untuk kurung.)ARM Unified Assembler Language (UAL), 3 byte
Sebagai contoh:
Setelah dieksekusi
nop
, prosesor menafsirkan.ARM.attributes
bagian tersebut sebagai kode dan menemukan instruksi ilegal di suatu tempat di sana:Diuji pada Raspberry Pi 3.
sumber
Microsoft C (Visual Studio 2005 dan seterusnya), 16 byte
Saya tidak dapat dengan mudah menguji ini, tetapi menurut dokumentasi itu harus menghasilkan instruksi ilegal dengan sengaja mencoba menjalankan instruksi hanya kernel dari program mode pengguna. (Perhatikan bahwa karena instruksi ilegal membuat crash program, kami tidak perlu mencoba untuk kembali dari
main
, yang berarti bahwamain
fungsi gaya K&R ini valid. Visual Studio tidak pernah pindah dari C89 biasanya merupakan hal yang buruk, tetapi itu datang pada berguna disini.)sumber
Ruby, 13 byte
Saya kira aman untuk menganggap bahwa kita menjalankan ini dari shell * nix. Literal backtick menjalankan perintah shell yang diberikan.
$$
adalah proses Ruby yang sedang berjalan, dan#
untuk interpolasi string.Tanpa memanggil shell secara langsung:
Ruby, 17 byte
sumber
Semua shell (sh, bash, csh, dll.), POSIX (10 byte)
Jawaban sepele tapi saya belum melihat orang mempostingnya.
Cukup kirimkan SIGILL ke proses saat ini. Contoh output pada OSX:
sumber
kill -4 1
jika pertanyaannya tidak spesifik tentang program mana yang melempar SIGILLYou will have root in your programs
- jatuhkan byte dari jawaban Anda dan putar pertanyaan sedikit pada saat yang sama: D. BONUS: Anda bisa membunuhinit
init
sebenarnya kebal terhadap sinyal yang belum secara khusus diminta untuk diterima, bahkan dengan root. Anda mungkin bisa mengatasinya dengan menggunakan OS POSIX yang berbeda.kill -4 2
kemudian: DKode mesin ELF + x86, 45 byte
Ini harus menjadi program executable terkecil pada mesin Unix yang melempar SIGILL (karena Linux tidak mengenali executable jika dibuat lebih kecil).
Kompilasi dengan
nasm -f bin -o a.out tiny_sigill.asm
, diuji pada mesin virtual x64.Biner 45 byte aktual:
Daftar majelis (lihat sumber di bawah):
Penafian: kode dari tutorial berikut tentang penulisan program perakitan terkecil untuk mengembalikan nomor, tetapi menggunakan opcode ud2 alih-alih mov: http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
sumber
Otomatis , 93 byte
Menggunakan perakitan inass flatassembler:
Ketika dijalankan dalam mode interaktif SciTE, itu akan langsung crash. Windows debugger harus muncul selama sepersekian detik. Output konsol akan menjadi seperti ini:
Di mana
-1073741795
kode kesalahan yang tidak ditentukan dilemparkan oleh WinAPI. Ini bisa berupa angka negatif.Mirip menggunakan LASM assembler saya sendiri :
sumber
NASM, 25 byte
Saya tidak tahu bagaimana ini bekerja, hanya saja ia bekerja di komputer saya secara khusus (Linux x86_64).
Kompilasi & jalankan seperti:
sumber
ja 0
ud2
TI-83 Hex Assembly, 2 byte
Jalankan sebagai
Asm(prgmI)
. Menjalankan opcode 0xed77 ilegal. Saya menghitung setiap pasangan angka hex sebagai satu byte.sumber
Python, 32 byte
sumber
import os;os.kill(os.getpid(),4)
x86 .COM, 1 byte
ARPL
menyebabkan#UD
dalam mode 16-bitsumber
Linux shell, 9 byte
Mengirim
SIGILL
ke proses dengan PID 0. Saya tidak tahu proses apa yang memiliki PID 0, tetapi selalu ada.Cobalah online!
sumber
man kill
:0 All processes in the current process group are signaled.
GNU C,
241918 byte-4 Terima kasih kepada Dennis
-1 berkat ceilingcat
Cobalah online! Ini mengasumsikan ASCII dan x86_64. Mencoba menjalankan kode mesin
27
, yang ... ilegal.shortC ,
1054 byteSetara dengan kode GNU C di atas. Cobalah online!
sumber
L"\6"
juga ilegal, dengan anggapan x86_64.L
itu tidak perlu.'
adalah 39 = 0x27 , bukan 0x39 .MachineCode pada x86_64,
21 byteCobalah online!
Cukup panggil instruksi x86_64
0x07
(ceilingcat disarankan 0x07 alih-alih 0x27)sumber