https://chacker.pl/
Zestawy instrukcji, takie jak x86 i ARM, zawierają wiele różnych instrukcji o złożonej semantyce. Na przykład, w x86, nawet pozornie proste instrukcje, takie jak add, mają skutki uboczne, takie jak ustawianie flag statusu w rejestrze eflags. Ogromna liczba instrukcji i skutków ubocznych utrudnia automatyczne rozumowanie programów binarnych. Na przykład, jak zobaczysz w rozdziałach od 10 do 13, dynamiczna analiza skażeń i silniki wykonywania symbolicznego muszą implementować jawne procedury obsługi, które przechwytują semantykę przepływu danych wszystkich analizowanych ijnstrukcji. Dokładna implementacja wszystkich tych procedur obsługi jest trudnym zadaniem. Reprezentacje pośrednie (IR), znane również jako języki pośrednie, zostały zaprojektowane w celu usunięcia tego obciążenia. IR to prosty język, który służy jako abstrakcja od języków maszynowych niskiego poziomu, takich jak x86 i ARM. Popularne IR to Reverse Engineering Intermediate Language (REIL) i VEX IR (IR używany w frameworku instrumentacji Valgrind12). Istnieje nawet narzędzie o nazwie McSema, które tłumaczy pliki binarne na kod bitowy LLVM (znany również jako LLVM IR). Idea jjjęzyków IR polega na automatycznym tłumaczeniu rzeczywistego kodu maszynowego, takiego jak kod x86, na kod IR, który uwzględnia całą semantykę kodu maszynowego, ale jest znacznie prostszy w analizie. Dla porównania, REIL zawiera tylko 17 różnych instrukcji, w przeciwieństwie do setek instrukcji w x86. Co wijęcej, języki takie jak REIL, VEX i LLVM IR jawnie wyrażają wszystkie operacje, bez ukrytych efektów ubocznych instrukcji. Implementacja etapu translacji z kodu maszynowego niskiego poziomu na kod IR nadal wymaga dużo pracy, ale po jej wykonaniu znacznie łatwiej jest zaimplementować nowe analizy binarne na podstawie przetłumaczonego kodu. Zamiast pisać procedury obsługi poszczególnych instrukcji dla każdej analizy binarnej, w przypadku języków IR wstarczy to zrobić tylko raz, aby zaimplementować etap translacji. Co więcej, można pisać translatory dla wielu ISA, takich jak x86, ARM i MIPS, i mapować je wszystkie na ten sam IR. W ten sposób każde narzędzie do analizy binarnej, które działa na tym IR, automatycznie dziedziczy obsługę wszystkich ISA obsługiwanych przez IR. Kompromisem związanym z tłumaczeniem złożonego zestawu instrukcji, takiego jak x86, na prosty język, taki jak REIL, VEX lub LLVM IR, jest to, że języki IR są znacznie mniej zwięzłe. Jest to nieodłączna konsekwencja wyrażania złożonych operacji, wraz ze wszystkimi efektami ubocznymi, za pomocą ograniczonej liczby prostych instrukcji. Zazwyczaj nie stanowi to problemu w przypadku analiz automatycznych, ale zazwyczaj utrudnia ludziom odczytanie pośrednich reprezentacji. Aby dać ci wyobrażenie o tym, jak wygląda IR, spójrz na Listing , który pokazuje, jak instrukcja x86-64 add rax,rdx jest tłumaczona na VEX IR.


Jak widać, pojedyncza instrukcja dodawania generuje 10 instrukcji VEX plus kilka metadanych. Po pierwsze, są tam metadane mówiące, że jest to superblok IR (IRSB) (1) odpowiadający jednej instrukcji maszynowej. IRSB zawiera cztery wartości tymczasowe oznaczone od t0 do t3, wszystkie typu Ity_I64 (64-bitowa liczba całkowita) (2). Następnie mamy IMark (3), czyli metadane określające między innymi adres i długość instrukcji maszynowej. Następnie mamy rzeczywiste instrukcje IR modelujące dodawanie. Po pierwsze, są to dwie instrukcje GET, które pobierają 64-bitowe wartości z rax i rdx do pamięci tymczasowych odpowiednio t2 i t1 (4). Należy zauważyć, że w tym przypadku rax i rdx to jedynie symboliczne nazwy części stanu VEX używanych do modelowania tych rejestrów — instrukcje VEX nie pobierają danych z rzeczywistych rejestrów rax lub rdx, lecz ze stanu lustrzanego tych rejestrów w VEX. Aby wykonać faktyczne dodawanie, IR używa instrukcji Add64 VEX, dodając dwie 64-bitowe liczby całkowite t2 i t1 i zapisując wynik w t0 (5). Po dodaniu występują pewne instrukcje PUT, które modelują efekty uboczne instrukcji add, takie jak aktualizacja flag stanu x86 (6). Następnie kolejna instrukcja PUT zapisuje wynik dodawania w stanie VEX reprezentującym rax (7). Na koniec IR VEX modeluje aktualizację licznika programu do następnej instrukcji (8). Ijk_Boring (Jump Kind Boring) (9) to wskazówka dotycząca przepływu sterowania, która mówi, że instrukcja add nie wpływa na przepływ sterowania w żaden interesujący sposób; ponieważ add nie jest żadnym rodzajem rozgałęzienia, sterowanie po prostu „przechodzi” do następnej instrukcji w pamięci. Natomiast instrukcje rozgałęzienia mogą być oznaczone wskazówkami takimi jak Ijk_Call lub Ijk_Ret, aby poinformować analizę, na przykład, że ma miejsce wywołanie lub powrót. Wdrażając narzędzia na bazie istniejącego frameworka analizy binarnej, zazwyczaj nie trzeba zajmować się reakcją na analizę binarną (IR). Framework sam zajmie się wszystkimi kwestiami związanymi z IR. Warto jednak wiedzieć o reakcjach na analizę binarną, jeśli planujesz wdrożyć własny framework analizy binarnej lub zmodyfikować istniejący.