How do I unlock my iPhone If I Forgot my passcode?

iPhone 4: En dybere forståelse af SecureROM og jailbreaking

02/02/2025

Rating: 4.09 (13296 votes)
Indholdsfortegnelse

iPhone 4: En dybere forståelse af SecureROM og jailbreaking

I en tid hvor smartphones er blevet en uundværlig del af vores hverdag, er det fascinerende at dykke ned i den tekniske arkitektur, der ligger bag disse kraftfulde enheder. For entusiaster af iOS-universet og dem, der er nysgerrige på, hvordan man kan gå ud over de fastsatte rammer, er jailbreaking et emne, der vækker stor interesse. Denne artikel tager os med på en rejse ind i hjertet af iPhone 4's sikkerhedssystem, specifikt dens SecureROM, og hvordan en berømt udnyttelse, kendt som limera1n, banede vejen for kodeeksekvering på enheden. Vi vil udforske de tekniske detaljer bag processen, udfordringerne ved at skrive kode til et sådant miljø, og den dybere forståelse, det giver af Apples sikkerhedsmodel.

Is the iPhone 4 a good target for SecureROM?
The SecureROM that ships with the A4 SoC is vulnerable, so the iPhone 4 I’ve picked up should be a great target. One thing that I find really fascinating about limera1n is that no one ✱ knows exactly how it works. geohot said he has no idea why it works, and p0sixninja speculated theories.

Fra Cydia til SecureROM: En personlig rejse

For mange år tilbage var jeg en aktiv deltager i iOS tweak-udviklingsmiljøet. Jeg skabte adskillige produkter og værktøjer, der blev distribueret via Cydia, og som modificerede iOS' systemadfærd for at tilføje ny funktionalitet til SpringBoard. Det var en utrolig givende periode, der gav mig tidlig erhvervserfaring med reverse engineering af lukkede binære filer, direkte interaktion med Objective-C runtime, og iværksætteri. Jeg er dybt taknemmelig for disse år.

Et aspekt af jailbreak-scenen, der altid forekom mig som ren magi, var selve jailbreak-processen. Tanken er bemærkelsesværdig: tag en hvilken som helst standard iPhone, udfør obskøne ritualer og reciter ældgamle besværgelser, indtil lænkerne falder af. Operativsystemet tillader dig nu at køre enhver kode, du peger på, uanset om koden har været igennem Apples velsignede signaturproces. Dette åbnede døren for driftige tweak-udviklere som mig selv.

For nylig fik jeg trangen til at fjerne dette mysterie-slør fra jailbreaks ved selv at skrive en. En vigtig forudsætning er, at det virkelig komplekse arbejde her allerede er udført af mine forgængere. Jeg er især taknemmelig over for p0sixninja og axi0mx, som generøst har delt deres viden via open source.

Porten til angreb: Valg af enhed og tilgang

Trin ét: Anskaf en enhed. Jeg har ingen forudgående viden om at skrive et jailbreak eller om, hvordan min tilgang vil se ud, så lad os starte et oplagt sted. Jeg anskaffer en iPhone 4 og en 3GS fra eBay. Ældre enheder virker som et godt sted at starte, da deres sikkerhed formentlig er ringere. Man skal dog finde den rette balance: meget gamle enheder er ekstremt værdifulde.

Nu hvor jeg har to, hvorfor skulle jeg så stoppe der? eBay trækker i flapperne på sin skyggefulde frakke for at afsløre en torso beklædt med gamle iPhones, et særligt tilbud med to for prisen af tre.

Enhederne ankommer! Jeg har vage ideer om, hvordan man ville udnytte disse, baseret på fragmenter jeg har læst gennem årene: en fejlbehæftet PDF-parsing her, en sårbar framebuffer-kode der. For at prøve at udnytte en af disse, ville jeg have brug for at kunne køre kode på enheden. Den forestillede vej her er, at jeg formår at opsætte en toolchain, der kan producere og installere applikationer, som det blev gjort tilbage i 2010. Ved hjælp af dette ville jeg derefter skrive en app og undersøge tingene indefra sandkassen for at analysere angrebsfladen.

Hmm… det ser ud til, at de seneste versioner af Xcode ikke tillader målretning mod iOS-versioner, der er mere end et par år gamle. Måske kan vi downloade en ældre Xcode-version?

Nå, pokkers. Jeg downloader et par ældre Mac OS X-versioner, som jeg har tænkt mig at sætte op i en VM for at kunne køre en ældre Xcode-version, og indser så, at jeg keder mig. Selv hvis det lykkedes mig at opsætte en gammel toolchain, er det uklart, om Apple overhovedet ville signere en binær fil, der sigter mod en ældre iOS-version. Lad os prøve noget andet.

Til sidst beslutter jeg mig for at undersøge en boot ROM-sårbarhed. Dette har et par sjove fordele, såsom at undgå behovet for at opsætte en gammel toolchain og arbejde i en VM, da en boot ROM-sårbarhed typisk udnyttes ved at skrive kode på en host-maskine, der interagerer med enheden over USB.

Jeg ved, at nutidige enheder har en offentligt kendt boot ROM-udnyttelse, og går til iPhone Wiki for at lære mere. De har heldigvis en sektion med titlen "Vulnerabilities and Exploits" - fantastisk! Jeg læser igennem nogle af disse og ser, at limera1n har noget exploit-kode lige der på Wiki-siden. Det er meget tiltalende blot at prøve denne kode og se, hvad der sker.

En digression om tillid: SecureROM's rolle

Boot ROM'en, eller SecureROM i Apples terminologi, er det første trin i iOS' bootproces og starter de næste dele af bootprocessen. Det er SecureROM'ens ansvar at sikre, at det, den indlæser næste gang, er betroet - med andre ord, at den kun kører det nøjagtige image, som Apple har leveret og signeret.

SecureROM indlæser gladeligt en af to komponenter, afhængigt af hvad der sker:

  • Hvis enheden udfører en 'normal' boot fra filsystemet, starter SecureROM en komponent kaldet Low Level Bootloader (LLB) fra en diskpartition på NOR.
  • Hvis enheden er i DFU-tilstand og er tilsluttet en computer via USB, kan Restore iPhone-processen initieres ved at sende iBSS (iBoot Single Stage) bootloaderen.

Ligesom SecureROM havde ansvaret for at kontrollere, at LLB eller iBSS var betroet, skal både LLB og iBSS ligeledes sikre, at det, de indlæser næste gang, også er betroet. Hvert efterfølgende trin sikrer, at det stoler på det, der kommer efter det. Flowet for Restore-processen ser groft sagt således ud:

TrinBeskrivelse
1. SecureROMVerificerer og starter LLB eller iBSS.
2. LLB/iBSSVerificerer og starter iBoot (eller lignende).
3. iBootVerificerer og starter iOS-kernen.
4. iOS-kerneStarter resten af operativsystemet.

Dette er vores kæde af tillid: hvert trin indlæser kun noget, det stoler på, og derfor er den endelige brugerrettede kode altid betroet.

Det vil sige, medmindre vi bryder denne kæde! Bemærk, at hvert efterfølgende trin verificeres af det foregående trin, undtagen det første trin. Vores diagram ser virkelig således ud:

SecureROM er implicit betroet, og det er en tung byrde. Mens alle de andre trin kan udskiftes, hvis der findes sårbarheder ved at udgive en opdateret iOS-version, er SecureROM ætset ind i skrivebeskyttet hukommelse, når enheden produceres. Dette betyder, at enhver enhed, der er fremstillet med en given SecureROM-version, permanent vil være sårbar over for eventuelle problemer i den version.

Og, som det viser sig, eksisterer sådanne sårbarheder, og de kan udnyttes!

limera1n: Udnyttelsen af en Boot ROM-sårbarhed

limera1n er navnet på en sådan udnyttelse, der blev frigivet af geohot og pakket ind i et eponymous jailbreak-værktøj i 2010. limera1n kan udnyttes, når en enhed i DFU-tilstand venter på, at en iBSS skal sendes af værten over USB. SecureROM'en, der leveres med A4 SoC'en, er sårbar, så den iPhone 4, jeg har anskaffet, burde være et glimrende mål.

En ting, jeg finder virkelig fascinerende ved limera1n, er, at ingen præcist ved, hvordan den fungerer. geohot sagde, at han ikke aner, hvorfor den virker, og p0sixninja har fremsat teorier. Værktøjerne til at forstå, hvad der foregår, er bestemt tilgængelige (især efter iOS 9's iBoot kildekode-lækage), men så vidt jeg ved, har ingen hævdet at have samlet stykkerne. Nedbruddet, der førte til limera1n, blev fundet ved at fuzzing USB-kontrolbeskeder og ser ud til at være en race condition, der fører til en heap overflow, hvilket giver angriberen mulighed for at injicere og køre shellcode. Fuzzing af lukkede binære filer har givet os en gave af fremmed teknologi: vi kan bruge den, den er kraftfuld, men vi ved ikke, hvad den gør.

Læsning af data fra en DFU-tilstandsenhed

Jeg begyndte at kigge efter limera1n-implementeringer for at se, hvordan jeg kunne replikere den. Jeg stødte hurtigt på pod2g's SecureROM dumper, som var utroligt hjælpsom. I ét hug viste den mig:

  • Hvordan man implementerer limera1n.
  • Hvilken type kode der kunne indgå i payloaden.
  • Hvordan man læser hukommelse fra enheden over USB.

Dette sidste punkt var utroligt. SecureROM kører på enheden, og hvis du vil analysere den, skal du på en eller anden måde have den ud af enheden. pod2g's SecureROM dumper kopierer hukommelsen, hvor SecureROM er mappet (0x0), til USB-modtageområdet. Derefter, på host-siden, sender den USB-kontrolbeskeder for at læse data fra enheden.

Is there a jailbreak for iPhone 4?

Så vidt jeg ved, har ingen eksplicit skrevet den anden halvdel af dette: ikke kun kan du skrive data til en iOS-enhed over USB, men enheden vil også svare på læseanmodninger. Jeg fandt dette ret overraskende, da jeg forestillede mig, at enheden ville være et sort hul, der ville suge bits ind og aldrig afsløre noget om sin egen tilstand.

Denne mekanisme er ikke forklaret noget sted online, så vidt jeg kan se. Her er min forståelse:

  • A4's MMU mapper bunden af SRAM til 0x84000000.
  • Hosts, der taler med DFU-enheden (såsom Apple-software, der kører på en Mac for at gendanne en iPhone), kan sende et iBSS-image stykkevis ved at sende USB-kontrolpakker med en anmodningstype på 0x21 og en anmodnings-ID på 1. Dataene, der sendes i kontrolpakkerne, kopieres til SRAM, startende fra 0x84000000 og skiftende til højere adresser, efterhånden som hosten sender flere pakker (for ikke at overskrive tidligere data). SecureROM vedligeholder nogle interne tællere, der sporer, hvor den næste pakke skal kopieres hen, og disse tællere kan nulstilles (formodentlig hvis hosten vil annullere en overførsel og starte forfra).
  • Enheden vil også svare, hvis hosten sender en kontrolpakke med anmodningstype 0xA1 og anmodnings-ID 2. Enheden vil læse indholdet af hukommelsen ved 0x84000000 og sende det til hosten. Dette virker tvivlsomt nyttigt, hvis denne hukommelse kun indeholder de data, som hosten selv allerede har sendt, men bliver virkelig praktisk, når vi har mulighed for at udføre kode på enheden og kan kopiere hvad som helst, vi ønsker, til 0x84000000.

Dumperen ovenfor bruger derfor limera1n til at udføre en payload, der kopierer hukommelsen ved 0x0 (som indeholder SecureROM) til 0x84000000, og vender derefter tilbage til den oprindelige SecureROM DFU-loop. Host sender derefter nogle A1:2 læseanmodninger, der effektivt trækker et dump af SecureROM fra enheden.

Jeg ved endnu ikke meget om USB, så jeg er nysgerrig, om 0x21:1 og 0xA1:2 har nogen dybere betydning, eller om de er vilkårlige værdier, der er hardcodet i SecureROM'ens forretningslogik. Et Stack Overflow-indlæg antyder, at de koder nogle standardoplysninger:

ByteBetydning
Første byte (bmRequestType)Består af 3 felter: de første 5 bits (mindst betydende) er modtageren, de næste 2 bits er typen, og den sidste bit er retningen.

p0sixninja gav en præsentation om dette værktøj i 2013 på Hack In The Box Malaysia, men så vidt jeg kan se, er der en fejl i hans slides: han siger, at pod2g's SecureROM dumper er bygget op omkring en implementering af SHAtter (en anden SecureROM-udnyttelse udviklet samtidigt med limera1n), men pod2g's værktøj bruger faktisk en limera1n-implementering.

Jeg skrev min egen limera1n-implementering baseret på pod2g's SecureROM dumper og forsøgte også at dumpe SecureROM. Jeg var begejstret, da det virkede!

$ Dumping the SecureROM $ 

Skrivning af en payload

Jeg har nu kodeeksekvering på denne iPhone 4 og er klar til at gå min egen vej. Til at starte med kan jeg køre assembly, men det er uklart, hvor denne assembly kører. Hvor er min stack? Hvilken hukommelse overskriver min shellcode? Hvad er grænserne for, hvor stor min shellcode kan vokse, før jeg begynder at overskrive noget vigtigt i hukommelsen? Før vi kan besvare nogen af disse spørgsmål, skal vi have en måde at få debug-data ud af enheden. 'Læs hukommelse ved 0x84000000'-flowet, der bruges i SecureROM dumperen, virker som et virkelig nyttigt værktøj til dette! Jeg skrev noget shellcode, der kopierer værdierne af instruktionspointeren og stackpointeren til 0x84000000, og brugte derefter den samme host-side kode til at læse værdierne tilbage. På denne måde skabte jeg en fattigmands-print(), der giver mig mulighed for at kommunikere information, som jeg indsamler på enheden, via hukommelsesdumps, som jeg modtager på hosten.

let communication_area_base = unsafe { slice :: from_raw_parts_mut ( 0x84000000 as * mut _, 1024 ) }; communication_area[0] = pc; communication_area[1] = sp; 

Jeg lavede nogle scripts til automatisk at køre min udnyttelse og dumpe de første par ord af 0x84000000 til et outputvindue, så jeg kunne inspicere de kopierede instruktionspointer- og stackpointer-værdier. Disse scripts tillod mig hurtigt at iterere efter at have foretaget ændringer i min shellcode.

$ Inspecting the environment $ <wait> 

Når vi ser på de første to ord af den dumpede hukommelse, kan vi se, at vores shellcode kører omkring 0x8402b048 (48b00284 i hukommelsesdumpet), og stackpointeren er ved 0x8403bfa0 (a0bf0384). Det giver god mening! Stackpointeren er inden for den normale stack-region, som SecureROM selv opsætter, og instruktionspointeren er inden for modtage-image-området. Da vi udnyttede en overflow for at opnå kodeeksekvering, er det ikke overraskende at se vores kode køre fra leveringsbufferen.

Klatring fra assembly: Rust i en ældre verden

Mens jeg gjorde mit liv lettere, gjorde jeg også payloadens logik nemmere at udvikle. At skrive software direkte i assembly er nyttigt i visse situationer, men her er det blot en hindring. Jeg opsatte et build-system, der tillod mig at skrive en payload i Rust, som derefter ville blive konverteret til shellcode og sendt til enheden. Jeg valgte Rust, fordi jeg vidste, at jeg en dag ville skrive denne artikel, og det valg ville fremkalde de fleste øjenrulninger. Plus, hvor sejt er det ikke at køre en Rust exploit-payload på en enhed, der blev produceret, før Rust eksisterede?!

At skrive shellcode i et hvilket som helst højniveau-sprog medfører nogle ekstra vanskeligheder, som ikke er til stede, når man leverer assembly. Det er vigtigt at huske, at når du kompilerer kode i et højniveau-sprog, får du ikke ren maskinkode ud: i stedet kompilerer toolchains din kode til en binær fil, der indeholder masser af metadata, herunder (blandt andet) instruktioner til operativsystemet om, hvordan man opsætter adressepladsen, som programmet forventer, symboltabeller til debugging og linkerinformation. Vi ønsker intet af dette her! Vores udnyttelse giver os mulighed for at injicere nogle bytes i hukommelsen og hoppe til dem, og vi ønsker ikke alle de tilbehør, der normalt følger med kompilering af en binær fil i et kontrolleret miljø. Dette er det vilde vesten, og vi programmerer den mærkelige maskine.

På macOS har binære filer normalt et layout som følger:

SegmentSektionBeskrivelse
__TEXT__textKodeinstruktioner
__TEXT__cstringC-strenge
__DATA__dataInitialiserede data

Med andre ord er den binære fil (layout i Mach-O format) en samling af segmenter, som hver indeholder en sektion, der repræsenterer nogle data eller andet. Én sektion kan være til lagring af Objective-C metadata, mens en anden kan være til lagring af statisk indlejrede C-strenge. Kun én af disse sektioner indeholder den rene maskinkode, vi ønsker at uploade til iPhone: __text-sektionen i __TEXT-segmentet. Som det sker, skrev jeg strongarm, et omfattende Mach-O analysebibliotek, så jeg tilføjede et hurtigt script til build-systemet: det kompilerer payloaden og linker den til en Mach-O, bruger derefter strongarm til at udtrække indholdet af __TEXT,__text sektionen og dumpe det til en fil. Indholdet af denne fil er det, vi bruger limera1n til at udføre på enheden.

from strongarm.macho import MachoParser def dump_text_section_to_file(input_binary: Path, output_file: Path) -> None: with open(output_file.as_posix(), "wb") as f: f.write(dump_text_section(input_binary)) def dump_text_section(input_file: Path) -> bytes: parser = MachoParser(input_file) binary = parser.get_armv7_slice() text_section = binary.section_with_name("__text", "__TEXT") return binary.get_content_from_virtual_address(text_section.address, text_section.size) 

At formilde linkeren

Vi kompilerer ikke en typisk binær fil her, og typiske binære filer har en aftale med OS-infrastrukturen om, hvad deres indgangspunkt vil blive kaldt. Som standard forventer linkeren, at vores binære fil vil definere start- eller _main-symbolerne, hvilket ikke er nødvendigt for vores brugsscenarie. Medmindre vi fortæller linkeren, at vi gør noget ukonventionelt, vil den stoppe os og klage over manglende standard-symboler.

$ as -arch armv7 entry.s -o entry.o $ ld entry.o Undefined symbols for architecture armv7: "_main", referenced from: implicit entry/start for main executable ld: symbol(s) not found for architecture armv7 

Lad os fortælle linkeren, at vi ikke vil give disse og prøve!

$ ld entry.o -U _main ld: dynamic executables or dylibs must link with libSystem.dylib for architecture armv7 

Ups, nu tror ld, at vi kompilerer et dynamisk bibliotek. Nå, det er fint. Vil den holde op, hvis vi fortæller den, at vi vil linke med libSystem.dylib? Lad os prøve!

$ ld entry.o -U _main -framework libSystem.dylib -o output.o ld: framework not found libSystem.dylib 

Lad os… lad os tage det langsomt. Det er helt fair, at libSystem.dylib ikke er tilgængelig, når man krydskomplilerer til armv7, og vi har ikke en iOS 4 sysroot ved hånden. Jeg ser en lovende mulighed i manualen.

-static Producerer en mach-o fil, der ikke bruger dyld. Bruges kun ved kompilering af kernen.

Vi kompilerer bestemt ikke kernen her. Vi bygger en binær fil, der ikke bruger dyld. Kunne vi…? Nej, nej, selvfølgelig ikke… men måske?

$ ld entry.o -U _main -o output.o -static Undefined symbols for architecture armv7: "start", referenced from: -u command line option ld: symbol(s) not found for architecture armv7 

Fedt, det er fremskridt! Lad os bare fortælle linkeren, at vi heller ikke vil definere start…

$ ld entry.o -U _main -U start -static -o output.o # Success 

Fantastisk! Nu sender vi det ind i strongarm for at udtrække indholdet af __TEXT,__text…

Traceback (most recent call last): File "payload_stage1/../dump_shellcode.py", line 15, in dump_text_section_to_file f.write(dump_text_section(input_file)) File "payload_stage1/../dump_text_section", line 7, in dump_text_section parser = MachoParser(input_file) File "strongarm/macho/macho_binary.py", line 847, in dyld_info raise LoadCommandMissingError() strongarm.macho.macho_binary.LoadCommandMissingError 

Åh åh, strongarm crashede! Dette skyldes, at strongarm under sin indledende analyse af den binære fil forventer at finde LC_DYLD_INFO load-kommandoen. Da vi skabte en selvstændig binær fil, der slet ikke bruger dyld, er denne load-kommando ikke til stede. Den blev ikke håndteret, fordi jeg aldrig har stødt på en binær fil som denne før: de fleste binære filer bruger dyld! Jeg tilføjede en hurtig patch til strongarm for at håndtere dette, og nu er alt fint.

At huske vores begrænsninger

I løbet af udvidelsen af payloaden begyndte jeg at inkludere ret mange værdier i hukommelsesdumpet. Det blev lidt svært at huske "ord 3 er returværdien af dette kald, ord 7 er adressen på den funktion", så jeg foretog en i det væsentlige harmløs ændring for at inkludere nogle strenge i de data, jeg placerede i kommunikationsområdet. Koden ser nogenlunde således ud:

let communication_area_base = unsafe { slice :: from_raw_parts_mut ( 0x84000000 as * mut _, 2048 ) }; let mut cursor = 0; write_str(communication_area_base, &mut cursor, "Output from image3_decrypt_payload: ",); write_u32(communication_area_base, &mut cursor, ret); 

Lad os prøve det!

$ Attempting to communicate strings $ 

Hm, det er mærkeligt. Det ser bestemt ud til, at det ødelagde noget. Årsagen bliver helt klar, når vi ser nærmere på vores payload binære fil, før vi udtrækker dens __TEXT,__text indhold:

Åh! Den statiske streng i vores kildekode blev placeret i __const, og vores kompilerede Rust-kode forsøger at få adgang til strengen ved at indlæse hukommelsen på den adresse, hvor den binære fil anmoder om, at strengen skal placeres inden for adressepladsen. Da vi fuldstændig kasserer alt undtagen __TEXT,__text, er disse adresseplads-mappinger en ubrugelig anmodning fra den binære fil, og dataene i __const indlæses aldrig i hukommelsen. Derfor beder vores kode om at indlæse en streng fra en helt umappet adresse, og vores binære fil crasher. Løsningen er ret ligetil og giver en god grund til at bruge assembly til at skrive vores payloads: assembly giver programmøren eksplicit og direkte kontrol over layoutet af statiske data, mens kompilerede sprog forsøger at håndtere det på programmørens vegne.

For at rette dette skal vi sikre os, at alle statiske data, vi definerer, er indlejret i __TEXT,__text for at være sikre på, at de ikke går tabt, når vi udtrækker shellcoden. Vi skal også være forsigtige med, at alle adgange til statiske data bruger instruktionspointer-relateret adressering frem for absolutte adresser, da vi ikke kan regne med at blive indlæst på nogen stabil hukommelsesadresse. For nu definerer jeg alle strenge, jeg vil bruge, i assembly og sender deres adresser til Rust payloadens indgangspunkt:

.text shellcode_start: adr r0, msg1 mov r1, msg1_len bl _rust_entry_point # ... msg1: .asciz "Output from image3_decrypt_payload: " msg1_len: .equ . - msg1 

Vi er et ret godt sted! Vi har nu denne pipeline:

  1. Foretag en ændring i Rust payloaden.
  2. Tryk på en knap.
  3. Payloaden vil blive kompileret.
  4. Shellcoden vil blive udvundet fra den binære fil.
  5. Runneren vil bruge limera1n på den tilsluttede DFU iPhone til at udføre payloaden.
  6. Runneren vil automatisk læse data fra 0x84000000, som vi bruger som kommunikationsområde, og præsentere det i et hexdump.

Hvad er næste skridt? Herfra kan vi gøre stort set hvad som helst, da vi kan køre vilkårlig kode på enheden. Fra et synspunkt er det 'spillet slut'. Fra et andet synspunkt er det sjove dog kun lige begyndt. Det er én ting at kunne gøre alt i teorien. Det er et helt andet bæst at få enheden til rent faktisk at gøre noget interessant. Læs mere i Del 2: Omgåelse af Bootchainen.

Hvis du vil læse andre artikler, der ligner iPhone 4: En dybere forståelse af SecureROM og jailbreaking, kan du besøge kategorien Mobil.

Go up