tło
Mamy już wyzwanie rzucenia SIGSEGV , więc dlaczego nie wyzwanie rzucenia SIGILL?
Co to jest SIGILL?
SIGILL to sygnał nielegalnej instrukcji procesora, co zdarza się bardzo rzadko. Domyślną akcją po otrzymaniu SIGILL jest zakończenie programu i zapisanie zrzutu podstawowego. Identyfikator sygnału SIGILL to 4. SIGILL występuje bardzo rzadko i nie mam absolutnie żadnego pojęcia, jak go wygenerować w kodzie, z wyjątkiem via sudo kill -s 4 <pid>
.
Zasady
Będziesz mieć root w swoich programach, ale jeśli nie chcesz z żadnych powodów, możesz również użyć zwykłego użytkownika. Jestem na komputerze z systemem Linux z niemieckimi ustawieniami regionalnymi i nie znam angielskiego tekstu wyświetlanego po złapaniu SIGILL, ale myślę, że to coś w rodzaju „Nielegalnej instrukcji”. Najkrótszy program, który rzuca SIGILL wygrywa.
raise(SIGILL)
?Illegal instruction (core dumped)
.Odpowiedzi:
PDP-11 Asembler (UNIX Sixth Edition), 1 bajt
Instrukcja 9 nie jest prawidłową instrukcją na PDP-11 (byłoby to ósemkowe
000011
, które nie pojawia się na liście instrukcji (PDF)). Asembler PDP-11, który jest dostarczany z UNIX Sixth Edition, najwyraźniej odzwierciedla wszystko, czego nie rozumie bezpośrednio w pliku; w tym przypadku 9 jest liczbą, więc generuje literalną instrukcję 9. Ma także właściwość nieparzystą (obecnie w językach asemblera), że pliki zaczynają działać od początku, więc nie potrzebujemy żadnych deklaracji, aby stworzyć program praca.Możesz przetestować program za pomocą tego emulatora , chociaż będziesz musiał nieco z nim walczyć, aby wprowadzić program.
Oto jak to się skończy, gdy nauczysz się korzystać z systemu plików, edytora, terminala i podobnych rzeczy, o których myślałeś, że wiesz, jak z nich korzystać:
Potwierdziłem za pomocą dokumentacji, że jest to prawdziwy
SIGILL
sygnał (a nawet wtedy miał ten sam numer sygnału, 4!)źródło
a.out
zawiera wiele bajtów (9
instrukcja kompiluje się do dwóch bajtów, a asembler dodaje również nagłówek i stopkę, aby program był wykonywalny). Dlatego napisałem program w języku asemblera, a nie w kodzie maszynowym. Program języka asemblera ma tylko jeden bajt wejściowy i kompiluje się do programu z większą liczbą bajtów; jest to problem związany z kodowaniem golfa (minimalizacja rozmiaru źródła), a nie problem z rozmiarem kodu (minimalizacja rozmiaru pliku wykonywalnego), więc liczy się wielkość 1-bajtowego źródła.C (x86_64, tcc ), 7 bajtów
Zainspirowany tą odpowiedzią .
Wypróbuj online!
Jak to działa
Wygenerowany zespół wygląda następująco.
Zauważ, że TCC nie umieszcza zdefiniowanej „funkcji” w segmencie danych .
Po kompilacji, _start jak zwykle wskaże na main . Kiedy program wynikowy jest wykonywany, oczekuje kodu w main i znajduje 32-bitową liczbę całkowitą little-endian (!) 6 , która jest zakodowana jako 0x06 0x00 0x00 0x00 . Pierwszy bajt - 0x06 - jest niepoprawnym kodem operacji, więc program kończy się na SIGILL .
C (x86_64, gcc ), 13 bajtów
Wypróbuj online!
Jak to działa
Bez modyfikatora const wygenerowany zestaw wygląda tak.
Linker GCC traktuje ostatni wiersz jako wskazówkę, że wygenerowany obiekt nie wymaga stosu wykonywalnego. Ponieważ main jest wyraźnie umieszczony w sekcji danych , kod operacji, który zawiera, nie jest wykonywalny, więc program kończy SIGSEGV (błąd segmentacji).
Usunięcie drugiego lub ostatniego wiersza spowoduje, że wygenerowany plik wykonywalny będzie działał zgodnie z przeznaczeniem. Ostatni wiersz można zignorować przy użyciu flagi kompilatora
-zexecstack
( Wypróbuj online! ), Ale kosztuje to 12 bajtów .Krótszą alternatywą jest zadeklarowanie głównego za pomocą modyfikatora const , co daje następujący zestaw.
Działa to bez żadnych flag kompilatora. Zauważ, że zapisałby
main=6;
zdefiniowaną „funkcję” w danych , ale modyfikator const powoduje, że GCC zapisuje ją w rodata , która (przynajmniej na mojej platformie) może zawierać kod.źródło
main
jest to 6 i próbuje ją nazwać (co, jak sądzę, sprawiłoby, że zrezygnowałby i wypróbowałby instrukcję)?main
nie jest funkcją, ale tylko jeśli włączysz ostrzeżeń (albo-Wall
czy-pedantic
to zrobi)..rodata
sekcję wewnątrz segmentu tekstowego pliku wykonywalnego i spodziewam się, że tak będzie na prawie każdej platformie. (Program ładujący jądro dba tylko o segmenty, a nie sekcje).06
jest to tylko niepoprawna instrukcja w x86-64. Jest to tryb 32-bitowyPUSH ES
, więc ta odpowiedź działa tylko z domyślnymi kompilatorami-m64
. Zobacz ref.x86asm.net/coder.html#x06 . Jedynym sekwencja bajtów, która jest zagwarantowana rozszyfrować jako nielegalne nauczania na wszystkich przyszłych procesorów x86 jest 2 bajt UD2 :0F 0B
. Wszystko inne może być jakimś przyszłym prefiksem lub kodowaniem instrukcji. Nadal głosowaliśmy za świetnym sposobem, aby kompilator C nakleiłmain
etykietę na niektórych bajtach!Szybki, 5 bajtów
Uzyskaj dostęp do indeksu 0 pustej tablicy. Nazywa to
fatalError()
, co wypisuje komunikat o błędzie i wywala z SIGILL. Możesz spróbować tutaj .źródło
fatalError()
celowo ulega awarii podczas uruchamianiaud2
. Dlaczego zdecydowali się to zrobić, nie wiem, ale może uznali, że komunikat o błędzie „Niedozwolona instrukcja” ma sens, ponieważ program zrobił coś nielegalnego.nil!
, ale kompilator nie mógł wywnioskować typu wyniku. (Cześć, JAL!)GNU C, 25 bajtów
GNU C (specyficzny dialekt języka C z rozszerzeniami) zawiera instrukcję celowej awarii programu. Dokładna implementacja różni się w zależności od wersji, ale często programiści próbują wdrożyć awarię tak tanio, jak to możliwe, co zwykle wiąże się z użyciem nielegalnej instrukcji.
Konkretna wersja, której użyłem do testowania, to
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0
; jednak program ten powoduje SIGILL na dość szerokiej gamie platform, a zatem jest dość przenośny. Dodatkowo robi to poprzez faktyczne wykonanie nielegalnej instrukcji. Oto kod zestawu, do którego powyższe kompiluje się z domyślnymi ustawieniami optymalizacji:ud2
to instrukcja, którą gwarancje Intel zawsze pozostaną niezdefiniowane.źródło
main(){asm("ud2");}
00 00 0f 0b
jest to język maszynowy dlaud2
…00
bajty; nie są częścią kodu maszynowego dla UD2. BTW, jak skomentowałem odpowiedź Dennisa , na razie istnieją x-bajtowe instrukcje niezgodne z x86-64, ale nie ma gwarancji, że tak pozostanie.00 00
dekoduje to samo w x86-64 (asadd [rax], al
).00 00 0f 0b
zwykle SIGSEGV przed SIGILL, chyba że zdarzy ci się mieć zapisywalny wskaźnikrax
.C (x86_64), 11, 30, 34 lub 34 + 15 = 49 bajtów
Przedstawiłem kilka rozwiązań, które używają funkcji bibliotecznych do rzucania
SIGILL
na różne sposoby, ale prawdopodobnie to oszustwo, ponieważ funkcja biblioteczna rozwiązuje problem. Oto szereg rozwiązań, które nie wykorzystują funkcji bibliotecznych i przyjmują różne założenia dotyczące tego, gdzie system operacyjny jest skłonny pozwolić na wykonanie kodu niewykonywalnego. (Stałe tutaj są wybrane dla x86_64, ale możesz je zmienić, aby uzyskać działające rozwiązania dla większości innych procesorów, które mają nielegalne instrukcje).06
jest bajtem kodu maszynowego o najniższym numerze, który nie odpowiada zdefiniowanej instrukcji procesora x86_64. Więc wszystko, co musimy zrobić, to go wykonać. (Alternatywnie2F
jest również niezdefiniowany i odpowiada jednemu drukowanemu znakowi ASCII.) Żadnemu z nich nie gwarantuje się, że zawsze będzie niezdefiniowany, ale na dzień dzisiejszy nie jest zdefiniowany.Pierwszy program tutaj wykonuje się
2F
z segmentu danych tylko do odczytu. Większość łączniki nie są zdolne do wytwarzania skok roboczy od.text
do.rodata
(lub równoważny ich systemów operacyjnych), jak to nie jest coś, co nigdy nie będzie przydatna w prawidłowej segmentacji programu; Nie znalazłem jeszcze systemu operacyjnego, na którym to działa. Trzeba też dopuścić fakt, że wiele kompilatorów chce, aby dany ciąg był szerokim ciągiem, co wymagałoby dodatkowegoL
; Zakładam, że każdy system operacyjny, na którym działa, ma dość przestarzały obraz rzeczy, dlatego domyślnie buduje się na standard wcześniejszy niż C94. Możliwe, że nigdzie ten program nie działa, ale jest też możliwe, że gdzieś ten program działa, dlatego wymienię go w tej kolekcji bardziej wątpliwych do mniej wątpliwych potencjalnych odpowiedzi. (Po opublikowaniu tej odpowiedzi Dennis wspomniał również o możliwościmain[]={6}
na czacie, która jest tej samej długości i która nie ma problemów z szerokością postaci, a nawet zasygnalizowała potencjałmain=6
; Nie mogę racjonalnie twierdzić, że te odpowiedzi są mój, bo sam o nich nie myślałem.)Drugi program tutaj wykonuje się
06
z segmentu danych do odczytu i zapisu. W większości systemów operacyjnych spowoduje to błąd segmentacji, ponieważ zapisywalne segmenty danych są uważane za wadę projektową, która sprawia, że exploity są prawdopodobne. Jednak nie zawsze tak było, więc prawdopodobnie działa na wystarczająco starej wersji Linuksa, ale nie mogę tego łatwo przetestować.Trzeci program wykonuje się
06
ze stosu. Ponownie powoduje to obecnie błąd segmentacji, ponieważ ze względów bezpieczeństwa stos jest zwykle klasyfikowany jako niepisujący. Dokumentacja konsolidatora, którą widziałem, wyraźnie sugeruje, że wykonywanie ze stosu było legalne (w przeciwieństwie do poprzednich dwóch przypadków, czasami jest to przydatne), więc chociaż nie mogę tego przetestować, jestem pewien, że jest trochę wersja systemu Linux (i prawdopodobnie inne systemy operacyjne), na których to działa.Wreszcie, jeśli podasz
-Wl,-z,execstack
(kara 15 bajtów) dlagcc
(jeśli używasz GNUld
jako części backendu), to jawnie wyłączy ochronę wykonywalnego stosu, pozwalając na działanie trzeciego programu i podając nieprawidłowy sygnał operacji zgodnie z oczekiwaniami. I zostały przetestowane i zweryfikowane tej wersji 49-bajtowy do pracy. (Dennis wspomina na czacie, że ta opcja najwyraźniej działamain=6
, co dałoby wynik 6 + 15. Jestem dość zaskoczony, że to działa, biorąc pod uwagę, że 6 rażąco nie jest na stosie; opcja link najwyraźniej działa więcej niż jego nazwa sugeruje.)źródło
const main=6;
działa, podobnie jak kilka odmian. Ten linker (który, jak podejrzewam, jest również twoim linkerem) jest w stanie wygenerować skok od.text
do.rodata
; Problemem jest to, że bez tegoconst
przeskakujesz do zapisywalnego segmentu danych (.data
), który nie jest wykonywalny na nowoczesnym sprzęcie. Działałoby to na starszych wersjach x86, w których sprzęt do ochrony pamięci nie mógł oznaczać stron jako możliwych do odczytu, ale nie do wykonania.main
wymagana jest funkcja (§5.1.2.2.1) - Nie wiem, dlaczego gcc uważa, że deklarowaniemain
jako obiektu danych zasługuje tylko na ostrzeżenie i tylko-pedantic
w wierszu poleceń. Ktoś na początku lat 90. może myślał, że nikt nie zrobiłby tego przez przypadek, ale to nie jest tak, że jest to przydatne celowo, z wyjątkiem tego rodzaju gier.main[]="/"
się przeskoczyć do segmentu danych tylko do odczytu, ponieważ literały łańcuchowe idą w rodata. Zostałeś złapany przez różnicę międzychar *foo = "..."
ichar foo[] = "..."
.char *foo = "..."
jest cukrem syntaktycznymconst char __inaccessible1[] = "..."; char *foo = (char *)&__inaccessible1[0];
, więc literał łańcuchowy idzie w rodata ifoo
jest osobną , zapisywalną zmienną globalną, która na to wskazuje. Dziękichar foo[] = "..."
jednak cała tablica idzie w segmencie danych do zapisu.GNU as (x86_64), 3 bajty
$ xxd sigill
$ as --64 sigill.S -o sigill.o; ld -S sigill.o -o sigill
$ ./sigill
$ objdump -d sigill
źródło
asm-link
) dla zabawkowych programów z jednym plikiem budowałby plik wykonywalny z tego źródła w ten sam sposób, ponieważld
domyślnie punkt wejścia jest na początku segmentu tekstowego lub coś w tym rodzaju. Po prostu nie pomyślałem o wyborze źródła asm na ten: PBash na Raspbian na QEMU, 4 (1?) Bajtów
Nie moja praca. Po prostu informuję o pracy innej. Nie jestem nawet w stanie przetestować roszczenia. Ponieważ kluczową częścią tego wyzwania wydaje się być znalezienie środowiska, w którym ten sygnał zostanie podniesiony i złapany, nie uwzględniam wielkości QEMU, Raspbian ani bash.
27 lutego 2013 20:49 użytkownik emlhalac poinformował na forum Raspberry Pi o „ uzyskiwaniu„ nielegalnych instrukcji ”podczas próby chrootowania ” .
produkować
Wyobrażam sobie na przykład, że znacznie krótsze polecenia spowodują takie wyjście
tr
.EDYCJA: Na podstawie komentarza @ fluffy zmniejszono przypuszczalną dolną granicę długości wejściowej do „1?”.
źródło
[
polecenie wygra. :)Plik COM x86 MS-DOS, 2 bajty
EDYCJA: Jak wskazano w komentarzach, sam DOS nie wyłapie wyjątku procesora i po prostu zawiesi się (nie tylko aplikacja, cały system operacyjny). Uruchamianie w 32-bitowym systemie operacyjnym opartym na NT, takim jak Windows XP, rzeczywiście wyzwoli nielegalny sygnał instrukcji.
Z dokumentacji :
Co jest dość oczywiste. Zapisz jako plik .com i
uruchom w dowolnym emulatorzeDOS. Emulatory DOS ulegną awarii. Uruchom w systemie Windows XP, Vista lub 7 32-bit.źródło
#UD
pułapkę. (Poza tym zdecydowałem się go przetestować i wyglądało na to, że rzucił mój emulator DOS w nieskończoną pętlę.)C (32-bitowy system Windows), 34 bajty
Działa to tylko wtedy, gdy kompiluje się bez optymalizacji (w przeciwnym razie niedozwolony kod w
f
funkcji jest „zoptymalizowany”).Demontaż
main
funkcji wygląda następująco:Widzimy, że używa
push
instrukcji o wartości dosłownej0b0f
(little-endian, więc jej bajty są zamieniane).call
Instrukcja popycha adres zwrotny (z...
instrukcją), który znajduje się na stosie w pobliżu parametr funkcji. Korzystając z[-1]
przesunięcia, funkcja zastępuje adres zwrotny, więc wskazuje 9 bajtów wcześniej, tam gdzie0f 0b
są bajty .Bajty te powodują wyjątek „niezdefiniowana instrukcja”, zgodnie z przeznaczeniem.
źródło
Java,
504324 bajtyTo
java.util.function.Consumer<Runtime>
1, którego rozkaz skradziono z odpowiedzi puszystego . To działa, bo trzeba nazwać to jakwhateverNameYouGiveIt.accept(Runtime.getRuntime())
!Zauważ, że stworzy to nowy proces i sprawi, że rzuci SIGILL, a nie sam SIGILL.
1 - Technicznie rzecz biorąc, może to być również
java.util.function.Function<Runtime, Process>
ponieważRuntime#exec(String)
zwraca,java.lang.Process
którego można użyć do kontrolowania właśnie utworzonego procesu przez wykonanie polecenia powłoki.Aby zrobić coś bardziej imponującego w tak pełnym słów języku, oto
726048-bajtowy bonus:Ten jest kolejnym,
Consumer<Runtime>
który przechodzi WSZYSTKIE procesy (w tym siebie), przez co każdy z nich rzuca SIGILL. Lepsze przygotowanie do gwałtownego wypadku.I kolejna premia (a
Consumer<ANYTHING_GOES>
), która przynajmniej udaje, że rzuca SIGILL w 20 bajtów:źródło
Perl, 9 bajtów
Po prostu wywołuje odpowiednią funkcję biblioteczną do sygnalizowania procesu i powoduje, że program sam się sygnalizuje
SIGILL
. Nie dotyczy to żadnych niezgodnych z prawem instrukcji, ale daje odpowiedni wynik. (Myślę, że to sprawia, że wyzwanie jest dość tanie, ale jeśli cokolwiek jest dozwolone, jest to luka, z której skorzystasz…)źródło
+
. :)+
. Po pewnym czasie gry w golfa+
okazjonalnie się popisują. W końcu napisali wystarczającą liczbę programów, w których musieli unikać białych znaków z tego czy innego powodu, że+
stają się nawykiem. (Analizuje również mniej jednoznacznie, ponieważ działa w taki sposób, że wyzwala specjalny przypadek w analizatorze składni dla nawiasów.)ARM Unified Assembler Language (UAL), 3 bajty
Na przykład:
Po wykonaniu
nop
procesor interpretuje.ARM.attributes
sekcję jako kod i napotyka gdzieś tam niedozwoloną instrukcję:Testowane na Raspberry Pi 3.
źródło
Microsoft C (Visual Studio 2005 i nowsze), 16 bajtów
Nie mogę tego łatwo przetestować, ale zgodnie z dokumentacją powinien on wygenerować niedozwoloną instrukcję, celowo próbując wykonać instrukcję tylko do jądra z programu trybu użytkownika. (Należy pamiętać, że ponieważ niedozwolona instrukcja powoduje awarię programu, nie musimy próbować wracać
main
, co oznacza, że tamain
funkcja w stylu K & R jest poprawna. Visual Studio nigdy nie przechodząc z C89 jest zwykle złą rzeczą, ale pojawiła się przydatne tutaj.)źródło
Rubinowy, 13 bajtów
Sądzę, że można bezpiecznie założyć, że uruchamiamy to z powłoki * nix. Literały wsteczne uruchamiają podane polecenie powłoki.
$$
jest działającym procesem Rubiego i#
służy do interpolacji ciągów.Bez bezpośredniego wywoływania powłoki:
Rubinowy, 17 bajtów
źródło
Dowolna powłoka (sh, bash, csh itp.), Dowolny POSIX (10 bajtów)
Trywialna odpowiedź, ale nie widziałem, żeby ktoś ją opublikował.
Po prostu wysyła SIGILL do bieżącego procesu. Przykładowe dane wyjściowe w OSX:
źródło
kill -4 1
jeśli pytanie nie jest szczegółowe na temat tego, który program rzuca SIGILLYou will have root in your programs
- znokautuj bajt odpowiedzi i lekko trolluj pytanie jednocześnie: D. BONUS: Zabijeszinit
init
jest właściwie odporny na sygnały, których nie zażądał, nawet z rootem. Być może możesz to obejść, używając innego systemu operacyjnego POSIX.kill -4 2
następnie: DKod maszynowy ELF + x86, 45 bajtów
Powinien to być najmniejszy program wykonywalny na komputerze z systemem Unix, który wyrzuca SIGILL (ponieważ Linux nie rozpoznaje pliku wykonywalnego, jeśli zostanie zmniejszony).
Kompiluj z
nasm -f bin -o a.out tiny_sigill.asm
, przetestowany na maszynie wirtualnej x64.Rzeczywiste 45 bajtów binarnych:
Lista zestawień (patrz źródło poniżej):
Uwaga: kod z poniższego samouczka na temat pisania najmniejszego programu asemblującego, aby zwrócić liczbę, ale używając opcode ud2 zamiast mov: http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
źródło
AutoIt , 93 bajty
Korzystanie z wbudowanego zestawu flatassembler:
Po uruchomieniu w interaktywnym trybie SciTE natychmiast się zawiesi. Debuger systemu Windows powinien wyskakować przez ułamek sekundy. Dane wyjściowe konsoli będą wyglądać mniej więcej tak:
Gdzie
-1073741795
jest niezdefiniowany kod błędu generowany przez WinAPI. Może to być dowolna liczba ujemna.Podobne przy użyciu mojego własnego asemblera LASM :
źródło
NASM, 25 bajtów
Nie wiem, jak to działa, tylko że działa na moim komputerze (Linux x86_64).
Skompiluj i uruchom jak:
źródło
ja 0
ud2
Zestaw sześciokątny TI-83, 2 bajty
Uruchom jako
Asm(prgmI)
. Wykonuje niedozwolony kod operacji 0xed77. Liczę każdą parę cyfr szesnastkowych jako jeden bajt.źródło
Python, 32 bajty
źródło
import os;os.kill(os.getpid(),4)
x86 .COM, 1 bajt
ARPL
powoduje#UD
w trybie 16-bitowymźródło
Powłoka Linux, 9 bajtów
Wysyła
SIGILL
do procesu z PID 0. Nie wiem, jaki proces ma PID 0, ale zawsze istnieje.Wypróbuj online!
źródło
man kill
:0 All processes in the current process group are signaled.
GNU C,
241918 bajtów-4 dzięki Dennis
-1 dzięki pułapkowi cat
Wypróbuj online! To zakłada ASCII i x86_64. Próbuje uruchomić kod maszynowy
27
, co ... jest nielegalne.shortC ,
1054 bajtówOdpowiednik powyższego kodu GNU C. Wypróbuj online!
źródło
L"\6"
jest również nielegalne, zakładając, że x86_64.L
jest to również konieczne.'
wynosi 39 = 0x27 , a nie 0x39 .MachineCode na x86_64,
21 bajtówWypróbuj online!
Po prostu wywołuje instrukcję x86_64
0x07
(pułap kota sugeruje 0x07 zamiast 0x27)źródło