- Reverse Engineering von iOS-Apps erfordert, laufende Apps beobachten und manipulieren zu können; diese Widget-App nutzt jedoch Debugger-Blockierung, Code-Injection-Blockierung und Jailbreak-Erkennung zusammen
- Die zentrale Blockierung kann über PT_DENY_ATTACH von
ptraceoder einen direkten Systemaufruf mit derselben Wirkung (svc #0x80) implementiert sein, weshalb ein einfacherptrace-Breakpoint sie nicht erfasst - Direkte Systemaufrufe werden umgangen, indem man im Binary nach dem Muster
mov w16, #26sucht, an dersvc-Position einen Breakpoint setzt und die Anweisung mitlldb jumpüberspringt - Das Verhalten, das nach Jailbreak-Erkennung das Telefon soft-rebootet bzw. einen Respring auslöst, war mit einer Funktion verbunden, die
snapshotViewAfterScreenUpdates:in einer Endlosschleife aufruft; sie wird am Funktionsanfang mitthread returnübersprungen - Der Crash nach Code-Injection sah weniger nach einer separaten Runtime-Framework-Prüfung aus, sondern eher nach Verlust der App-Group-Berechtigung; durch ein Swizzling von
containerURLForSecurityApplicationGroupIdentifier:auf ein temporäres Verzeichnis lässt sich die App auch auf normalen Geräten ausführen
Eine iOS-Widget-App mit mehreren übereinandergelegten Schutzmechanismen
- Ziel ist eine Widget-App aus dem App Store, die stärkere Abwehrmechanismen enthält als typische Widget-Apps
- Blockieren des Debugger-Attachments
- Beenden der App bei Code-Injection
- Bei Ausführung in einer Jailbreak-Umgebung wird das gesamte Telefon soft-rebootet bzw. ein Respring ausgelöst
- Dass iOS-Apps Schutzmechanismen wie Jailbreak-Erkennung oder Code-Obfuskation einbauen, ist nicht ungewöhnlich; diese App kombiniert jedoch mehrere Methoden
- Weitere interessante Verhaltensweisen innerhalb der App bleiben Thema eines späteren Beitrags
Blockieren des Debugger-Attachments mit PT_DENY_ATTACH
- Auf einem Jailbreak-Gerät kann man sich üblicherweise per
sshverbinden,debugserverstarten und die App vonlldbauf einem anderen Computer aus debuggen - Wenn man
debugserverauf dieselbe Weise an diese App anhängt, tritt ein Segmentation fault auf und das Attach schlägt fehl - Die Ursache hängt mit der
PT_DENY_ATTACH-Anfrage vonptracezusammenptraceist unter iOS eine private API und unter macOS eine öffentliche APIPT_DENY_ATTACHsetzt ein Flag, das nachfolgend das Tracing durch den Elternprozess verweigert- Wird der Prozess bereits getraced, beendet sich die App mit dem Status
ENOTSUP - Versucht der Elternprozess, einen Prozess mit gesetztem Flag zu tracen, kommt es auf der Elternseite zu einer segmentation violation
- Eine einfache Implementierung ist per Aufruf von
ptrace(PT_DENY_ATTACH, 0, 0, 0)möglich- Da es sich unter iOS um eine private API handelt, muss der tatsächliche Aufruf das
ptrace-Symbol inlibsystem_kernel.dylibperdlopenunddlsymfinden - Diese Methode lässt sich relativ leicht umgehen, indem man einen Breakpoint auf
ptracesetzt und den Aufruf mitthread returnüberspringt
- Da es sich unter iOS um eine private API handelt, muss der tatsächliche Aufruf das
Warum die einfache Umgehung nicht funktionierte
PT_DENY_ATTACHblockiert den Debugger erst nach dem Aufruf; wenn man also vor Ausführung des App-Codes attached, kann man einen Umgehungspunkt setzen- Statt
debugserverdirekt an den Prozess anzuhängen, startet man ihn und wartet dann inlldbmitprocess attach --name TopWidget --waitforauf den Start der App, sodass man vor der Ausführung des App-Codes attachen kann - In dieser App wurde der Breakpoint
b ptraceanfangs jedoch nicht aufgelöst; selbst nachdem er später aufgelöst wurde, wurde er nicht tatsächlich getroffen, bevor die App beendet wurde - Der Grund war, dass die App statt eines Aufrufs der Funktion
ptraceeinen direkten Systemaufruf mit derselben Wirkung verwendete
Die Stelle des direkten Systemaufrufs finden
- Die Disassembly der Funktion
ptraceenthält im Kern den Systemaufrufsvc #0x80- In
x0steht31, der Wert vonPT_DENY_ATTACH - In
x1,x2undx3stehen die ungenutzten Argumente0 - In
x16steht26, die Systemaufrufnummer vonptrace
- In
- Die App kann statt eines Aufrufs der Funktion
ptracedieselben Registerwerte per Inline-Assembly setzen und dann direktsvc #0x80ausführen- Diese Methode vermeidet verdächtige private API-Lookups wie
dlopenunddlsym - Sie ist schwer zu erwischen, wenn man nur einen Breakpoint auf die gemeinsame Funktion
ptracesetzt
- Diese Methode vermeidet verdächtige private API-Lookups wie
- Zur Umgehung muss man das entschlüsselte App-Binary in einem Disassembler öffnen und die Stelle des
ptrace-Systemaufrufs finden - Gesucht wird nach
mov x16, #26oder nach der 32-Bit-View desselben Registers,mov w16, #26- Mit armconverter.com erhält man für
mov x16, #26die Bytes50 03 80 D2, die für eine Binary-Suche verwendet werden können mov x16, #26lieferte keine Treffer; die Suche nachmov w16, #26ergab 4 Treffer
- Mit armconverter.com erhält man für
- Zwei dieser Treffer passten bei den umliegenden Anweisungen nicht zum erwarteten Muster; beim dritten Treffer zeigte sich Code in folgender Form
MOV X0, #0x1FMOV X1, #0MOV X2, #0MOV X3, #0MOV W16, #0x1ASVC 0x80
- Der vierte Treffer war ein anderer Branch derselben Funktion, und diese Funktion wurde als die Stelle bestätigt, die das Debugger-Attachment blockiert
Die svc-Anweisung überspringen
- Die im Disassembler bestätigten
svc-Adressen sind0x102A2BB14und0x102A2BB68 - In
lldbwird-s TopWidgetangehängt, um die binarybezogene Adresse in die tatsächliche Ladeadresse zu übersetzen und Breakpoints zu setzenbr s -a 0x102A2BB14 -s TopWidgetbr s -a 0x102A2BB68 -s TopWidget
- Setzt man die Ausführung fort, wird der Breakpoint an der Stelle
svc #0x80getroffen - Die einfachste Umgehung besteht darin, per jump zur nächsten Befehlsadresse zu springen und den Systemaufruf selbst gar nicht auszuführen
- Im Beispiel wird mit
jump *0x10327bb18zur Adresse0x10327bb18, also direkt hinter der aktuellen Anweisung, gesprungen
- Im Beispiel wird mit
- Nach diesem Ablauf kann man bei angehängtem Debugger in die App gelangen
Das Verhalten, das das Telefon soft-rebootet
- Auch nach Umgehung des Debugger-Attachments führt die App eine Schutzmaßnahme aus, die das Telefon soft-rebootet bzw. einen Respring auslöst
- Diesmal ist
lldbweiterhin attached, sodass man den Zustand, in dem der ProzessSIGKILLerhalten hat, und den Stacktrace prüfen kann - Im Stacktrace erscheint ein Ablauf, der den Bildschirminhalt erfasst
CARenderServerSnapshotausQuartzCore_UISnapshotScreenWindowsRectAfterCommitausUIKitCore- ein unnamed symbol in der App
TopWidget
- Mit
lldb image lookupwird die Runtime-Adresse in die binarybezogene Adresse0x100041898umgerechnet; anschließend wird die entsprechende Funktion im Disassembler geprüft - Das Decompilation-Ergebnis zeigt, dass die Funktion in einer Endlosschleife nur die folgenden Schritte wiederholt
- Aufruf von
+[UIScreen mainScreen] - Aufruf von
snapshotViewAfterScreenUpdates:auf dem zurückgegebenen Bildschirmobjekt - Das Ergebnis wird nicht verwendet und freigegeben
- Aufruf von
snapshotViewAfterScreenUpdates:ist eine öffentliche API zum Erstellen eines View-Snapshots, doch diese App wiederholt den speicherintensiven Aufruf endlos- Im verlinkten Video wurde bestätigt, dass der Ursprung dieses Aufrufs mit der Notification
com.apple.tw.twrrzusammenhängt und ein Respring erfolgt, wenn der Risk Check für das Telefon nicht bestanden wird - Die Umgehung ist möglich, indem man am Einstiegspunkt der betreffenden Funktion einen Breakpoint setzt und bei einem Treffer die Funktionsausführung mit
thread returnüberspringt
Crash bei Code-Injection
- Beim Debugging kann man komplexe Hilfsfunktionen, etwa zum Loggen von Accessibility-Informationen von Bildschirm-Buttons, in einem in die App injizierten Framework implementieren und sie dann aus dem Debugger aufrufen
- Auch ohne Jailbreak-Gerät kann man die App zunächst erkunden, indem man Frida oder Flex injiziert
- Üblicherweise erfolgt eine solche Injection mit Tools, die die App neu signieren; als Beispiel wird Sideloadly verwendet
- Diese App crasht sofort, wenn sie nach dem Resigning gestartet wird
- Betrachtet man die oberste Adresse des Crash-Stacks,
0x1002027D4, im Disassembler, findet man dort eineBRK-Anweisung, was zu einer absichtlichen Crash-Situation wie einem Force-Unwrap vonnilpasst - Der im decompilierten Code auffällige Aufruf ist
containerURLForSecurityApplicationGroupIdentifier:- Diese Methode gibt die URL eines Ordners zurück, auf den Apps und Extensions derselben Group gemeinsam zugreifen können
- Die App Group wird beim Code-Signing definiert
- Durch Code-Injection mit anschließendem Resigning wird die ursprüngliche App-Signatur verworfen, und auch die App-Group-Berechtigung geht verloren
- Daher wird wahrscheinlich statt einer URL
nilzurückgegeben, und die App crasht, weil sie diesen Wert per Force-Unwrap entpackt
- Da eine Widget-App die App Group mit der Extension teilen muss, die für die Anzeige des Home-Screen-Widgets zuständig ist, wirkt dieses Problem eher wie ein häufiges Code-Signing-Problem als wie eine absichtliche Abwehrmaßnahme
Umgehung des App-Group-Problems
- Die einfachste Lösung besteht darin, die App nicht neu zu signieren
- Auf einem Jailbreak-Gerät kann man ein Framework per Jailbreak-Tweak injizieren, ohne die App neu zu signieren
- Es ist auch möglich, Code mit ungültiger Signatur auszuführen oder eine gewünschte App einer gewünschten Group hinzuzufügen
- Wenn kein Jailbreak-Gerät vorhanden ist und Resigning nötig ist, kann man das Problem dennoch mit einem kleinen Framework umgehen, das eine Methode swizzlet
- Der Beispielcode ersetzt
containerURLForSecurityApplicationGroupIdentifier:vonNSFileManager- Wenn die ursprüngliche Methode aufgerufen wird, läuft die replacement method
- Die replacement method gibt nicht den shared container zurück, sondern
temporaryDirectory
- Dieser Ersatz ist nicht identisch mit dem ursprünglich gewünschten Verhalten der App
- Die ursprüngliche App erwartet einen gemeinsamen Ordner, auf den App und Extension zusammen zugreifen können
- Ein temporäres Verzeichnis ist kein solcher gemeinsamer Ordner
- Wenn es jedoch nur darum geht, das Verhalten der Haupt-App zu untersuchen, kann das ausreichend sein
- Ein solcher kleiner Patch kann die App Extension beschädigen
- Wenn nur die Grundfunktionen der Haupt-App benötigt werden, ist das möglicherweise kein Problem
- Eine umfangreichere Umgehung bestünde darin, eine neue App Group zu erstellen, die Haupt-App und alle Extensions für diese Group neu zu signieren und die relevanten Methoden so zu swizzlen, dass sie den neuen Group-Identifier verwenden
- Das ist deutlich mehr Aufwand; falls es nicht unbedingt nötig ist, ist es womöglich besser, sich ein Jailbreak-Gerät zu besorgen
- Wird dieses Framework zusammen mit einem Tool wie
Flexinjiziert, läuft die App auch auf einem normalen Gerät korrekt - Auf einem Jailbreak-Gerät müssen die zuvor genannten Anti-Debugging- und Respring-Schutzmaßnahmen erneut umgangen werden; danach kann man jedoch mit injiziertem
Flexin die App gelangen
Endzustand
- Am Ende ist die App in einem Zustand, in dem Debugger-Attachment, Umgehung der Jailbreak-Erkennung und Code-Injection alle möglich sind
- Was tatsächlich innerhalb der App untersucht werden sollte, bleibt Thema des nächsten Beitrags
1 Kommentare
Meinungen auf Hacker News
Bryce Bostwick leistet wirklich coole und inspirierende Arbeit beim Debugging und Reverse Engineering von Apps.
Ich bin über YouTube auf ihn gestoßen und habe, nachdem ich sein Video gesehen hatte, in dem er TikTok so umbaut, dass nur noch Katzenvideos erscheinen (https://youtu.be/YW3jL2gI9IE), versucht, Instagram so umzubauen, dass nur noch die Nachrichtenfunktion übrig bleibt, die ich nutze, und alles andere entfernt wird.
Ich wollte mich schon länger tiefer damit beschäftigen, Windows im Stil von Windhawk (https://windhawk.net/) anzupassen, insbesondere mit Modding und Reverse Engineering. Bryce zeigt solche Arbeit auf iOS sehr gut in Schritt-für-Schritt-Videos in Echtzeit.
Ich habe gesehen, dass man mit Revanced Erstaunliches machen kann, aber es scheint nicht viele gute Guides zu geben, die erklären, wie man mit so etwas anfängt.
Falls jemand den Ablauf dokumentiert hat, hätte ich Interesse.
Ich hasse es, dass ich Reels ausgesetzt sein muss, selbst wenn ich nur Fotos hochladen oder mit Freunden chatten will.
Anti-Debugging und sogar Techniken, die wiederum das Verhindern von Anti-Debugging verhindern, sind in der DOS-/Windows-Welt schon lange verbreitet.
In alten Materialien zu Cracking oder Unpacking wird das in unterschiedlicher Tiefe behandelt.
Wie leicht Nutzer das Verhalten einer App kontrollieren können, ist umgekehrt proportional dazu, wie nutzerfeindlich eine Plattform ist.
PT_DENY_ATTACH wirkt wie eine Funktion, die genau für diese Nutzerfeindlichkeit gebaut wurde.
Soweit ich weiß, gibt es so etwas unter Windows nicht; stattdessen nutzt man Techniken, bei denen sich eine App an sich selbst anhängt.
https://www.x86matthew.com/view_post?id=selfdebug
https://anti-debug.checkpoint.com/techniques/interactive.htm...
Es überrascht mich etwas, dass Apples App-Store-Prüfung Apps, die direkte System Calls ausführen, nicht ablehnt.
System Calls auf Apple-Plattformen sind keine stabile ABI, deshalb müssen alle System Calls über libSystem laufen; eine App, die direkte System Calls ausführt, ohne über libSystem zu gehen, tut im Grunde etwas, das sie nicht tun sollte.
Ebenso frage ich mich, warum der Autor hier im Code nach
mov w16, #26gesucht hat statt nachsvc 0x80.svc 0x80ist die Instruktion, die beliebige System Calls ausführt; welcher Call genau ausgeführt wird, hängt vom Register x16 ab.Die App wird sehr viele nicht relevante System Calls ausführen, daher wäre ein Breakpoint darauf nicht nützlich.
Zumindest wurde es im Video so erklärt.
Aus demselben Grund würde die Suche nach SVC-Instruktionen enorm viele Treffer liefern.
Wenn man die genaue System-Call-ID findet, die nach X16 verschoben wird, findet man es sofort.
Ich bin der Autor des Beitrags. Wenn ihr Fragen habt, beantworte ich sie gern. Danke an xmprt fürs Teilen.
Schön, dass es auch eine Textversion gab.
Ich hätte ein paar Fragen an den Autor: Wenn Guardsquare tatsächlich das bekannteste kommerzielle Tool ist, würde mich interessieren, ob es neue Dinge anbietet, die eine so einfache Disassemblierung verhindern.
Außerdem würde mich interessieren, ob TopWidgets einen ähnlichen Schutz eingesetzt hat oder ob das eher eine selbstgebaute Lösung war.
Persönlich nutze ich Android, daher ist es technisch nicht direkt anwendbar, aber ich ziehe trotzdem viel Wert daraus, zu lernen, wie Low-Level-Debugging unter iOS funktioniert.
Das Video ganz oben gehört zu den besten Programmier-Videos, die ich je gesehen habe.
Es hat ein hohes Tempo, setzt genau das richtige Maß an Vorwissen voraus und enthält hervorragende Demos, die den Fluss des Videos nicht unterbrechen.
Großartiger Artikel.
Ich frage mich wirklich, ob das eine normale, aber übermäßig paranoide App war oder ob es eine App war, die von Anfang an als Malware verdächtigt und deshalb debuggt wurde.
Falls nicht, wirkt der Aufwand ziemlich übertrieben.
Sie macht mit Widgets ziemlich coole Dinge, und ich vermute, sie wollten das schützen. Allerdings haben solche Strategien ohnehin nach und nach angefangen durchzusickern.
In der Binary gab es auch Interessantes.
Eine Zeit lang versuchte ich herauszufinden, warum ich Code sah, der scheinbar eine Windows-
.isoherunterlädt; tatsächlich stimmte das, und er wurde für ein Netzwerk-Speedtest-Widget verwendet.Es gibt noch einen schwierigeren Modus als „PT_DENY_ATTACH umgehen (Hard Mode)“.
Ich habe früher unter macOS einmal den Kernel gepatcht, sodass PT_DENY_ATTACH nichts mehr tat.
Auf dem Mac ist es tatsächlich ziemlich einfach, einen gepatchten Kernel auszuführen, aber unter iOS dürfte das wegen Dingen wie KTRR deutlich mühsamer sein.
XNU ist technisch gesehen Open Source, aber es war einfacher, den Patch mit einem Hex-Editor anzuwenden, als ihn neu zu kompilieren.
Damit kann man auch JIT möglich machen, indem man unsignierte Code-Pages und RWX erlaubt.
Wenn „mit aktiviertem Jailbreak ausgeführt und das ganze Telefon crasht“, sollte man es dann nicht im Store als Malware melden?
Ein Telefon zum Absturz zu bringen, ist eindeutig Malware-Verhalten, und man sollte vermuten, dass es noch anderes bösartiges Verhalten gibt, das versteckt werden soll.
War es nicht genau die Begründung für Apples geschlossenes Ökosystem, solchen Mist zu verhindern?
Ich glaube nicht, dass Apple sich besonders dafür interessiert, wenn eine App auf einem gejailbreakten Telefon crasht.
Die Benachrichtigung com.apple.tw.twrr macht mich wirklich neugierig.
Warum beginnt sie mit
com.apple?Die hier fragliche App scheint keine Apple-App zu sein, sondern eine App namens Top Widgets.
Es ist üblich, solche „vollqualifizierten Namen“ zu verwenden, um Kollisionen zu vermeiden.
In diesem Fall hat der Entwickler wohl zufällig dieses Präfix gewählt.
Abgesehen von den anderen Tools fühlt sich das exakt so an wie in den 1980ern, als man mit Boot-Tracing den Kopierschutz des Apple II geknackt hat.
Manche Dinge ändern sich nicht.