PL Emulacja RH850 *Notatki*
Disclaimer: Uprzedzam od razu, że zastosowałem dość długi wstęp, w którym wrzuciłem trochę wakacyjnych zwierzeń tak więc jeśli interesuje Cię tylko ta techniczna część to zachęcam do rozpoczęcia od akapitu Renesas RH850.
Chciałbym dodać jednocześnie iż niniejszy post jest pewnego rodzaju moim brudnopiso-notatnikiem o emulacji architektury RH850 a co za tym idzie - nie wyczerpuje on z pewnością omawianego tematu, jest bardziej takim sktórem myśli.
Sierpień, Gdynia, marina. Jeśli dojadą Cię projekty i chcesz uciec gdzieś na moment to szczerze polecam. Latem w Trójmieście zazwyczaj dużo się dzieje natomiast wieczorową porą można zaaplikować sobie fajny relaks w formie spaceru po zjawiskowo oświetlonym nabrzeżu, hotelowej restauracji na świeżym powietrzu czy tańcach na żywo do muzyki latino (lubię). Można to zrobić również rano przy śniadaniu w portowej scenerii z całym tym nadmorskim stuffem w momencie kiedy dzień się dopiero aktywuje. Nawiasem mówiąc jest to również idealna baza do wypadu w dalsze rejony naszego pięknego wybrzeża, przy założeniu, że lubisz miksować morskie klimaty z delikatnym zwiedzaniem, lekkim odpoczynkiem i | lub dobrą zabawą. Jest rano, ciepło i na razie nigdzie się nie wybieram.
Występują jednak pewne zdarzenia, które z tak dobrego stanu potrafią mnie skutecznie wybić. Jednym z nich jest właśnie niedokończona robota, która z pewnych przyczyn musiała taką pozostać. Czasami po długim researchu potrzebuję odpocząć, zmienić scenerię, zmienić projekt i technologię a to wszystko po to aby nabrać nowych sił aby móc do niego znów wrócić. Research wybranego targetu na dłuższą metę potrafi być dość obciążający psychicznie, tym bardziej kiedy wiesz, że nie ma gwarancji na osiągnięcie celu. Zatrzymałem się na moment i może właśnie dlatego - z racji takiego wolnego przebiegu powstał “projekt Gdynia”.
A po nim RH850. Na moment.
Nie wiem jak jest u Ciebie ale mnie egzotyka w IT zawsze jakoś pociągała. Nie inaczej było właśnie z tym dzikim Assembly co działa sobie radośnie w branży automotive, a z tego co się dowiedziałem, to nie tylko tam. Firma NCC Group odwaliła ekstra robotę wypuszczając emulator 850 oparty o framework Unicorn to aż oczy mi się zaświeciły na tą okoliczność wszak opracowań o tym w internecie jest bardzo mało. Willem (którego z tego miejsca pozdrawiam - https://icanhack.nl) dorzucił ze swojej strony własny research i zaczęło robić się coraz ciekawiej.
Renesas RH850
Architektura RISC dla wbudowanych mikrokontrolerów produkowana przez Renesas Electronics (właściwie wcześniej przez firmę NEC https://en.wikipedia.org/wiki/V850), na której dzisiaj sobie popracujemy. Architektura V850 posiada 32 32-bitowe rejestry ogólnego przeznaczenia. Posiada skompresowany zestaw instrukcji z najczęściej używanymi instrukcjami odwzorowanymi na 16-bitowe half-word. To co mnie zainteresowało w tej architekturze to to, że działa ona w różnych autach jak na przykład Jeep, Range Rover, mój ulubiony Chevrolet czy Chrysler no i więcej.
Unicorn
Na co dzień kolekcjonuję podatności w badanym sofcie i lubię to robić. Jeszcze bardziej lubię kiedy testowany soft podczas całego tego procesu po prostu mi działa. Czasami jednak nie działa albo inaczej - nie działa prawidłowo z pewnych przyczyn i wtedy trzeba skorzystać z innych rozwiązań. Czasem dzieje się tak w przypadku takich dzikich architektur jak Renesas, dzikich - w rozumieniu innych od x86. Częstym problemem jest chociażby samo uruchomienie takiego kodu i wykonanie przy tym analizy dynamicznej. Unicorn Framework jest jednym z rozwiązań tego problemu i jest on drugim bohaterem niniejszego odcinka.
Unicorn to “lekki, wieloplatformowy i “wielo-architekturowy” emulator procesora, oparty na QEMU.”. Z pomocą wspomnianego silnika możemy wprawić w ruch rozkazy procesora RH850 i tym samym ożywić nasz badany firmware aby z powodzeniem prowadzić na nim swoje testy (przynajmniej wybraną część kodu). Jedziemy:
~$ git clone -b rh850-arch https://github.com/quarkslab/unicorn.git
~$ cd bindings/python/
~$ sudo make install
~$ python3 sample_rh850.py
Emulate RH850 code
>>> Tracing basic block at 0x10000, block size = 0x6
>>> Emulation done. Below is the CPU context
>>> R1 = 0x123a
>>> R2 = 0x8aca
Tylko pamiętaj o doinstalowaniu zależności. Chyba nie muszę dodawać, że nie potrzebujemy przy tym grama sprzętu. Fajne nie? Tutaj znajdziesz odwołanie do wcześniej wspomnianej przeze mnie firmy, która opisuje temat nieco szerzej.
Pacjent
Dobra, przyznam się bez bicia, że do zabawy pożyczyłem sobie kod od Williama ALE funkcję do emulacji i sam jej proces ogarnąłem sobie już sam jako PoC i działa ;>. Swoją drogą ciekawa sprawa wygląda z podejściem do researchu takiego firmware, który siedzi sobie w jakimś dziwnym ECU. Jedną z możliwości jest chociażby identyfikacja serwisów należących do usługi typu UDS (Unified Diagnostic Services) czy KWP2000.
Badając wartości w powyższym kodzie znajdujące się w warunku wielokrotnego wyboru (tak ładnie nazywają “switch”) można z pewną dozą prawdopodobieństwa stwierdzić, że są to odpowiedniki danych serwisów w UDS. Przechodząc do funkcji dalej możemy zobaczyć jak zostały one zaimplementowane. Ja wybrałem sobie pewną funkcję, działającą w serwisie SECURITY_ACCESS, którą odpaliłem właśnie za pomocą frameworka Unicorn. Cały kod znajdziesz poniżej.
from __future__ import print_function
from ctypes import sizeof
from unicorn import *
from unicorn.rh850_const import *
from unicorn.unicorn_const import *
from capstone import *
import struct, binascii
"""
READ CODE
"""
in_file = open("lh0012501.sgo.out", "rb")
RH850_CODE32 = in_file.read()
in_file.close()
### ENTRY ADDRESS
ADDRESS = 0x0000a000
### EMULATION RANGE
START_ADDR = 0x0000fd94
STOP_ADDR = 0x0000fd92
"""
DEBUG REGISTERS
"""
def dr(mu):
r_lp = mu.reg_read(UC_RH850_REG_LP)
r_sp = mu.reg_read(UC_RH850_REG_SP)
r_pc = mu.reg_read(UC_RH850_REG_PC)
r_fpsr = mu.reg_read(UC_RH850_REG_FPSR)
r_0 = mu.reg_read(UC_RH850_REG_R0)
r_1 = mu.reg_read(UC_RH850_REG_R1)
r_2 = mu.reg_read(UC_RH850_REG_R2)
r_6 = mu.reg_read(UC_RH850_REG_R6)
r_7 = mu.reg_read(UC_RH850_REG_R7)
r_11 = mu.reg_read(UC_RH850_REG_R11)
r_14 = mu.reg_read(UC_RH850_REG_R14)
r_16 = mu.reg_read(UC_RH850_REG_R16)
r_26 = mu.reg_read(UC_RH850_REG_R26)
r_27 = mu.reg_read(UC_RH850_REG_R27)
r_28 = mu.reg_read(UC_RH850_REG_R28)
r_29 = mu.reg_read(UC_RH850_REG_R29)
print("\nRegisters:")
print("> stop = 0x%x" %STOP_ADDR)
print("> lp = 0x%x" %r_lp)
print("> sp = 0x%x" %r_sp)
print("> pc = 0x%x" %r_pc)
print("> fpsr = 0x%x" %r_fpsr)
print("> r0 = 0x%x" %r_0)
print("> r1 = 0x%x" %r_1)
print("> r2 = 0x%x" %r_2)
print("> r6 = 0x%x" %r_6)
print("> r7 = 0x%x" %r_7)
print("> r11 = 0x%x" %r_11)
print("> r14 = 0x%x" %r_14)
print("> r16 = 0x%x" %r_16)
print("> r26 = 0x%x" %r_26)
print("> r27 = 0x%x" %r_27)
print("> r28 = 0x%x" %r_28)
print("> r29 = 0x%x" %r_29)
"""
DEBUG STACK
"""
def ds(mu, info, address, size):
print("{} ".format(info))
print(mu.mem_read(address, size))
print("\n")
def run():
try:
"""
CODE TO MEMO
"""
mu = Uc(UC_ARCH_RH850, 0)
i = len(RH850_CODE32) // (1024 * 1024)
mem_size = (1024 * 1024) + (i * (1024 * 1024))
mu.mem_map(ADDRESS, mem_size, perms=UC_PROT_ALL)
mu.mem_write(ADDRESS, RH850_CODE32)
"""
RAM
For data from DAT_*
"""
data_address = 0x03ff0000
data_size = data_address + 0x10000
mu.mem_map(data_address, data_size, perms=UC_PROT_ALL)
"""
STACK
"""
stack_address = 0x80000000
stack_size = stack_address + 0x10000
mu.mem_map(stack_address, stack_size, perms=UC_PROT_ALL)
"""
STACK OFFSET
Ghidra [first function instruction]:
"""
mu.reg_write(UC_RH850_REG_SP, stack_address + 0x14)
"""
CREATE Stack
"""
stack = b"\x01" * 100
mu.mem_write(stack_address, stack)
"""
UPDATE DATA
We set values for r6 and r7 in bibi_security_access()
data_address it's a pointer for random, test data
bibi_settings @param_1 [first value \x03 or \x04]
0000fd4e cmp 0x4, r2
"""
data = b"\x04"
data += b"\x10\x10\x10"
"""
bibi_settings @param_2
0000fd58 cmp 0x5, r7
"""
data += b"\x05\x00\x00\x00"
mu.mem_write(data_address, data)
mu.reg_write(UC_RH850_REG_PC, START_ADDR)
mu.reg_write(UC_RH850_REG_R6, data_address)
mu.reg_write(UC_RH850_REG_R7, data_address + 4)
"""
RUN EMULATION
"""
try:
mu.emu_start(START_ADDR, STOP_ADDR)
ds(mu, "STACK", stack_address, 50)
ds(mu, "ADDR [0x3ff0000]", data_address, 8)
ds(mu, "ADDR [0x3ff10a3]", 0x3ff10a3, 4)
ds(mu, "ADDR [0x3ff1098]", 0x3ff1098, 40)
dr(mu)
except unicorn.UcError as e:
print(f"Crash - Address : {mu.reg_read(UC_RH850_REG_PC):#08x}")
print(e)
except UcError as e:
print("Error %s" % e)
if __name__ == '__main__':
run()
No i wykonanie:
STACK
bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01')
ADDR [0x3ff0000]
bytearray(b'\x04\x10\x10\x10\x05\x00\x00\x00')
ADDR [0x3ff10a3]
bytearray(b'\x06\x00\x00\x00')
ADDR [0x3ff1098]
bytearray(b'\x05\x10\x10\x10\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
Registers:
> stop = 0xfd92
> lp = 0xfdba
> sp = 0x80000000
> pc = 0xfd92
> fpsr = 0x0
> r0 = 0x0
> r1 = 0x3ff0000
> r2 = 0x6
> r6 = 0x3ff0000
> r7 = 0x5
> r11 = 0x10101000
> r14 = 0x10100000
> r16 = 0x10101005
> r26 = 0x3ff0000
> r27 = 0x3ff0004
> r28 = 0x0
> r29 = 0x0
Emulacja tego typu czasami usprawnia cały proces inżynierii wstecznej i jest pomocna w procesie testów bezpieczeństwa jeśli chodzi o systemy działające na architekturach innych niż popularne x86. Moim zdaniem warto poznać framework Unicorn - jest to bardzo użyteczny tool i warto mieć go w swojej skrzynce z narzędziami.
Pomimo tego, że początek mojego posta szybko się zestarzał to mam nadzieję, że chociaż z części merytorycznej mojego szybkiego projektu udało Ci się wyciągnąć pewien kawałek wiedzy dla siebie.
Działaj.