Minęło około roku, odkąd ostatni raz brałem udział w zajęciach montażowych. W tej klasie używaliśmy MASM z bibliotekami Irvine, aby ułatwić programowanie.
Po przejściu większości instrukcji powiedział, że instrukcja NOP zasadniczo nic nie zrobiła i nie martwi się o jej użycie. W każdym razie chodziło o okres śródokresowy, a on ma przykładowy kod, który nie działałby poprawnie, więc powiedział nam, abyśmy dodali instrukcję NOP i działało dobrze. Zapytałem, że jestem po zajęciach, dlaczego i co to właściwie zrobiło, a on odpowiedział, że nie wie.
Ktoś wie
Odpowiedzi:
Często czasy
NOP
służą do wyrównania adresów instrukcji. Zazwyczaj występuje to na przykład podczas pisania Shellcode w celu wykorzystania luki w zabezpieczeniach polegającej na przepełnieniu bufora lub sformatowaniu łańcucha .Załóżmy, że masz względny skok do 100 bajtów do przodu i dokonaj pewnych modyfikacji kodu. Możliwe, że twoje modyfikacje zepsują adres celu skoku i jako taki będziesz musiał również zmienić wyżej wspomniany skok względny. Tutaj możesz dodać
NOP
s, aby przesunąć adres docelowy do przodu. Jeśli masz wieleNOP
s między adresem docelowym a instrukcją skoku, możesz usunąćNOP
s, aby wyciągnąć adres docelowy do tyłu.Nie stanowiłoby to problemu, jeśli pracujesz z asemblerem obsługującym etykiety. Możesz po prostu zrobić
JXX someLabel
(gdzie JXX to jakiś skok warunkowy), a asembler zastąpisomeLabel
adres tej etykiety. Jeśli jednak po prostu ręcznie zmodyfikujesz zmontowany kod maszynowy (rzeczywiste kody operacyjne) (jak to czasami bywa przy pisaniu kodu powłoki), musisz również ręcznie zmienić instrukcję skoku. Możesz go zmodyfikować lub przenieść docelowy adres kodu za pomocąNOP
s.Innym przypadkiem użycia
NOP
instrukcji może być coś, co nazywa się zaprzęgiem NOP . W istocie chodzi o stworzenie wystarczająco dużej tablicy instrukcji, które nie powodują żadnych skutków ubocznych (takich jakNOP
lub zwiększanie, a następnie zmniejszanie rejestru), ale zwiększ wskaźnik instrukcji. Jest to przydatne na przykład, gdy chcemy przejść do określonego fragmentu kodu, którego adres nie jest znany. Sztuczka polega na tym, aby umieścić wspomniane sanki NOP przed kodem docelowym, a następnie wskoczyć gdzieś na te sanki. Mamy nadzieję, że wykonanie będzie kontynuowane z tablicy, która nie wywołuje żadnych skutków ubocznych, i przesuwa dalej instrukcje według instrukcji, aż trafi na pożądany fragment kodu. Technikę tę stosuje się powszechnie we wspomnianych exploitach przepełnienia bufora, a zwłaszcza w celu przeciwdziałania środkom bezpieczeństwa, takim jak ASLR .Jeszcze innym szczególnym zastosowaniem
NOP
instrukcji jest modyfikowanie kodu jakiegoś programu. Na przykład można zastąpić części skoków warunkowych naNOP
s i jako takie ominąć warunek. Jest to często stosowana metoda podczas „ łamania ” ochrony oprogramowania przed kopiowaniem. Najprościej chodzi tylko o usunięcie konstrukcji kodu asemblera dlaif(genuineCopy) ...
wiersza kodu i zastąpienie instrukcjiNOP
s i .. Voilà! Nie dokonuje się żadnych kontroli i działa nieoryginalna kopia!Zauważ, że w zasadzie oba przykłady kodu powłoki i crackowania robią to samo; modyfikować istniejący kod bez aktualizacji względnych adresów operacji opartych na względnym adresowaniu.
źródło
W szczelinie opóźniającej może być użyty nop, gdy nie można zmienić kolejności innych instrukcji w celu umieszczenia w nim.
W MIPS byłby to błąd, ponieważ w czasie, gdy jr czytał rejestr v0, rejestr v0 nie został jeszcze załadowany wartością z poprzedniej instrukcji.
Sposobem na rozwiązanie tego byłoby:
Wypełnia to zajmowane miejsca po słowie ładowania i instrukcjach rejestru skoku znakiem nop, dzięki czemu instrukcja słowa ładowania jest zakończona przed wykonaniem polecenia rejestru skoku.
Dalsza lektura - trochę o wypełnianiu SPARC szczelin opóźniających . Z tego dokumentu:
Zwróć uwagę na trzecią opcję w tym, co umieścić w gnieździe opóźnienia. Błąd, który widziałeś, prawdopodobnie spowodował, że ktoś wypełnił jedną z rzeczy, których nie wolno umieszczać w szczelinie opóźnienia. Umieszczenie nop w tym miejscu naprawiłoby błąd.
Uwaga: po ponownym przeczytaniu pytania, dotyczyło to x86, która nie ma slotów opóźniających (rozgałęzienie zamiast tego zatrzymuje tylko potok). To nie byłaby przyczyna / rozwiązanie błędu. W systemach RISC może to być odpowiedź.
źródło
co najmniej jednym powodem użycia NOP jest wyrównanie. Procesory x86 odczytują dane z pamięci głównej w dość dużych blokach, a początek bloku do odczytu jest zawsze wyrównany, więc jeśli ktoś ma blok kodu, który będzie czytany dużo, ten blok powinien zostać wyrównany. Spowoduje to niewielkie przyspieszenie.
źródło
0x1002
, ponieważ nadal jest 14 bajtów instrukcji w wyrównanym bloku 16B, który zawiera adres docelowy, ale nie jest dobrze, aby przejść do0x099D
.Jednym z celów NOP (w ogólnym zgromadzeniu, nie tylko x86) jest wprowadzenie opóźnień czasowych. Na przykład chcesz zaprogramować mikrokontroler, który musi wysyłać sygnały do niektórych diod LED z opóźnieniem 1 s. To opóźnienie można zrealizować za pomocą NOP (i gałęzi). Oczywiście możesz użyć ADD lub czegoś innego, ale to uczyniłoby kod bardziej nieczytelnym; a może potrzebujesz wszystkich rejestrów.
źródło
loop
instrukcji dla pętli opóźniających , a nie NoPS.Ogólnie na 80x86 instrukcje NOP nie są wymagane do poprawności programu, chociaż czasami na niektórych komputerach strategicznie rozmieszczone NOP mogą powodować szybsze działanie kodu. Na przykład w 8086 kod byłby pobierany w dwubajtowych porcjach, a procesor miał wewnętrzny bufor „wstępnego pobierania”, który mógł pomieścić trzy takie porcje. Niektóre instrukcje wykonałyby się szybciej, niż mogłyby zostać pobrane, podczas gdy inne instrukcje zajęłyby trochę czasu. Podczas powolnych instrukcji procesor próbowałby zapełnić bufor pobierania wstępnego, aby jeśli kilka kolejnych instrukcji było szybkich, można je szybko wykonać. Jeśli instrukcja następująca po instrukcji powolnej rozpoczyna się na granicy parzystego słowa, instrukcje o wartości następnych sześciu bajtów zostaną pobrane wstępnie; jeśli zaczyna się na granicy nieparzystych bajtów, pobieranych jest tylko pięć bajtów.
Takie problemy z wyrównaniem pamięci mogą mieć wpływ na szybkość programu, ale ogólnie nie wpływają na poprawność. Z drugiej strony istnieją pewne problemy związane ze wstępnym pobieraniem na starszych procesorach, w których NOP może wpłynąć na poprawność. Jeśli instrukcja zmieni bajt kodu, który został już pobrany z wyprzedzeniem, 8086 (i myślę, że 80286 i 80386) wykona instrukcję z wyprzedzeniem, mimo że nie pasuje już do tego, co jest w pamięci. Dodanie NOP lub dwóch między instrukcją zmieniającą pamięć a zmienionym bajtem kodu może uniemożliwić pobranie bajtu kodu, dopóki nie zostanie zapisany. Należy przy okazji zauważyć, że wiele schematów ochrony przed kopiowaniem wykorzystuje takie zachowanie; należy jednak pamiętać, że takie zachowanie nie jest gwarantowane. Różne odmiany procesorów mogą inaczej obsługiwać pobieranie wstępne, niektóre mogą unieważnić wstępnie pobrane bajty, jeśli pamięć, z której zostały odczytane, jest zmodyfikowana, a przerwania zwykle unieważniają bufor pobierania wstępnego; kod zostanie ponownie pobrany, gdy przerwania powrócą.
źródło
Istnieje konkretny przypadek x86, który nie został jeszcze opisany w innych odpowiedziach: obsługa przerwań. W przypadku niektórych stylów mogą istnieć sekcje kodu, gdy przerwania są wyłączone, ponieważ kod główny działa z niektórymi danymi współdzielonymi z programami obsługi przerwań, ale uzasadnione jest zezwolenie na przerwania między takimi sekcjami. Jeśli ktoś naiwnie pisze
nie przetworzy to oczekujących przerwań, ponieważ powołując się na Intel:
należy to przepisać co najmniej jako:
W drugim wariancie wszystkie oczekujące przerwania będą przetwarzane tylko między NOP a CLI. (Oczywiście istnieje wiele alternatywnych wariantów, takich jak podwojenie instrukcji STI. Ale wyraźne NOP jest bardziej oczywiste, przynajmniej dla mnie.)
źródło
NOP oznacza brak operacji
Zwykle służy do wstawiania lub usuwania kodu maszynowego lub do opóźniania wykonania określonego kodu.
Używany również przez crackerów i debugerów do ustawiania punktów przerwania.
Prawdopodobnie więc zrobienie czegoś takiego: XCHG BX, BX również da to samo.
Brzmi dla mnie tak, jakby było kilka operacji, które wciąż były w toku i dlatego spowodowało błąd.
Jeśli znasz VB, mogę podać przykład:
Jeśli utworzysz system logowania w VB i załadujesz 3 strony razem - Facebook, YouTube i Twitter w 3 różnych zakładkach.
I użyj 1 przycisku logowania dla wszystkich. Może to powodować błąd, jeśli twoje połączenie internetowe jest wolne. Co oznacza, że jedna ze stron nie została jeszcze załadowana. Dlatego zastosowaliśmy Application.DoEvents, aby temu zaradzić. W zespole można zastosować ten sam sposób.
źródło