fbpx Skip to content

Tweakfejlesztésről szóló sorozatunk harmadik részében abba vezetjük be a nyájas Olvasót, hogyan is lehet belenézni az operációs rendszer vagy egy alkalmazás belsejébe, hogyan lehet kikövetkeztetni a működését, illetve hogyan tudjuk módosítani azt.

Screen Shot 2013-02-19 at 7.05.15 PM

Aki már olvasott valamilyen IT-biztonsággal foglalkozó cikket, az bizonyosan belefutott a „reverse engineering” kifejezésbe. Bizonyosan olyan is van köztünk, akit pont az ilyen szép szavak tántorítottak el attól, hogy az ilyesmibe belevágjon, netán utánanézzen, mit is jelent ez. 😀

Mi is tehát a reverse engineering valójában?

Nos, ez egy folyamat, melynek során valamilyen számítógépes programot, kódot, kommunikációs protokollt, titkosítási (vagy bármilyen más) algoritmust visszafejtünk, azaz megkeressük: mit, hogyan és miért csinál. Legvégül egy átfogó, részletes képet kaphatunk arról, hogy mi a célja a szóban forgó programnak, és azt milyen módon éri el. A reverse engineering egyik legfontosabb tulajdonsága, hogy általában akkor van szükségünk ennek a technikának a használatára, ha a visszafejtendő, megismerni kívánt program vagy bármilyen más komponens eredeti „forrása” nem ismert (itt programok esetén a forráskódról van szó, az előbb említett hálózati protokollok esetében ez a továbbított adatok formátuma, de egy szimmetrikus kulcsú kódolás esetén akár egészen egyszerűen két nagy prímszámot is jelenthet).

A reverse engineering felhasználási területe, amint azt a fentiekből is láthatjuk, igen széles. Szoftverek feltörésétől, crackelésétől kezdve érzékeny adatok megszerzésén át egészen a MobileSubstrate-kiegészítők fejlesztéséig.

Miért jó ez, vagy miért van rá szükségünk?

A válasz egyszerű, de annál kiábrándítóbb (mint oly sok minden az életben): az iOS legnagyobb része zárt forrású, tehát például a SpringBoard vagy a rendszeralkalmazások kódja nem áll a külsős fejlesztők rendelkezésére. Ha tehát mondjuk egy olyan kiegészítőt szeretnénk készíteni, ami valamilyen plusz funkcióval ruházza fel a Fényképező alkalmazás exponálógombját, akkor első ránézésre fogalmunk sincsen arról, hogy konkrétan a kódban melyik osztály melyik metódusa hívódik meg annak megnyomásakor, hiszen nem ismerjük az alkalmazás forráskódját. Ezért tehát szükség van valamire (egy eszközre, módszerre), aminek segítségével ezt ki tudjuk küszöbölni, és az eredeti forráskód nélkül is megtudjuk, mi történik futásidőben. Ez pedig egy kevés nagyon alapszintű reverse engineering.

Konkrétumok

A reverse engineering egy program esetén elég nehéz feladat lehet: általános esetben a folyamat a bináris (végrehajtható) fájl assemblyvé való visszakonvertálása (disassembly) után nem automatizálható. A visszakapott assemblyből a fejlesztőnek/reverse engineernek kell kikövetkeztetni, hogy mit csinál a program. Ehhez a legáltalánosabban használt eszköz egy interaktív („intelligens”) disassembler, az IDA Pro. Ez a szoftver támogatja az iOS-alkalmazások formátumát és architektúráját (Mach-O és ARM) is.

Az iOS-es tweakek fejlesztése során viszont szerencsére általában nem szükséges, köszönhetően az Objective-C nyelv dinamikus jellegének. A dinamizmus megvalósításához a compilernek ugyanis rengeteg kiegészítő információt kell tárolnia a programfájlban, amik más nyelvek (például C, C++) esetén gyakran teljesen elvesznek a gépi kódra való fordítás során. Ilyenek az objektumok és osztályok típusa, a meghívott metódusok neve, stb. Ezeknek az adatoknak a formátuma és a Mach-O fájlokban való tárolási módja jól dokumentált. Ennek eredményeképp lehetségessé válik egy olyan eszköz megírása, ami egy végrehajtható fájlból kinyeri az összes Objective-C-vel kapcsolatos információt, és készít egy listát a fájlban lévő osztályokról, (többé-kevésbé) helyes deklarációk, header fájlok formájában. Ez a program pedig a „class-dump”.

Hogyan használjuk a class-dumpot?

Pontosabban a „class-dump-z”-t – ez az eredeti class-dump programnak egy átírt, javított és kibővített változata. Letöltése után (fut OS X-en, iOS-en, Linuxon és Windowson is) argumentumok nélkül elindítva kapunk egy kis segítséget, amelyből a következő fontosabb paramétereket kell kiemelni:

class-dump-z -z -p -R -b -H -o <DIR> <FILE>

A „-H” kapcsoló a „-o”-val együtt azt mondja meg a class-dumpnek, hogy minden egyes visszafejtett osztály deklarációját külön fájlba írja. Ez egy általánosan elfogadott konvenció objektumorientált programozási nyelvek esetén, hiszen megkönnyíti a program struktúrájának áttekintését.

A „-p” a property, azaz tulajdonság szóra utal: az Objective-C nevezéktanának megfelelő nevű párban álló metódusokat –foo és –setFoo: alakban való megadás helyett az Objective-C 2.0 verziójában bevezetett @propertszintaxissal deklarálja.

Az „-R” és a „-b” paraméter csak egy kicsit olvashatóbbá teszi a kódot: előbbi a pointer qualifier néven ismert csillag karakter helyét határozza meg (a szemantikának jobban megfelelő TYPE *ptr; formát használva a talán kevésbé szép TYPE* ptr; helyett), utóbbi pedig egy szóközt illeszt a metódusneveket megelőző + és – jelek után. Természetesen ezeken kívül számtalan hasznos opció van még, amit használhatunk, ezekkel érdemes a program helpje alapján kísérletezni egy kicsit, mindenki másokat tarthat szükségesnek.

Játék és nómenklatúra tíz percben

Amint azt említettem, a class-dump program nem generál teljes implementációs fájlokat, kizárólag típusdefiníciókat és osztálydeklarációkat kapunk. Éppen ezért fontos ismernünk a Cocoa Touch frameworkök nevezéktani konvencióit, hogy magába a végrehajtott kódba való belepillantás nélkül, pusztán az osztályok és metódusok nevei alapján rekonstruálni tudjuk a kód logikáját. Álljon tehát itt a nevezéktan néhány legfontosabb alappillére:

1. Tulajdonságok (Properties): a tulajdonságok esetén a getter metódusok neve a mögöttes változóval egyező nevű (kivéve, ha a változó neve aláhúzásjellel kezdődik, mert akkor a metódusnév nem tartalmazza), a setter pedig a „set” prefixummal kezdődik, ezt camelCaps formátumban követi a változó neve. Tehát például egy _date nevű példányváltozóhoz a – date és – setDate: nevű metódusok tartoznak.

2. A többesszám: az olyan metódusok, amelyeknek nevében többesszám van, általában NSArray típusú objektumot adnak vissza vagy várnak argumentumként (ez az esetek 99%-ában igaz, a maradék 1%-ban pedig kísérletezhetünk, hogy NSIndexSet, NSDictionary, vagy milyen más, úgynevezett collection class esetén nem kapunk hibát, esetleg ki is írathatjuk a runtime-mal az osztály nevét – de erről később. Ezt azért fontos tudni, mert a class-dump bővebb információ hiányában csak annyit tud a metódusdeklarációkba írni, hogy visszatérési értékük id, azaz egy általános Objective-C objektum – a pontos osztálynév nem kerül tárolásra.) Ennek megfelelően például a (id)itemsForType:(int)arg; metódus feltehetőleg egy NSArray értékkel tér vissza, a (void)addObjects:(id)arg; metódus pedig szintén NSArray-t vár.

3. Block típusú lambda-kifejezések: az Objective-C az iOS 4 óta támogatja névtelen függvények használatát. Az érdekesség az, hogy ezek a névtelen függvények szabályszerű Objective-C-objektumok, tehát a class-dump id-ként fogja őket kezelni. Hogy rájöjjünk, melyik (id) típusú argumentum vagy visszatérési érték lehet ilyen block, jegyezzük meg, hogy általában (funkciótól függően) a „block”, „callback” vagy „handler” szavak egyike benne van az ominózus metódus nevében, tehát például a következő helyen álló id-k gyanúsak:

– (id)blockForObject:(id)arg; // block visszatérési érték

– (void)invokeHandler:(id)arg; // block típusú argumentum

– (void)sendRequest:(id)req successCallback:(id)succ errorHandler:(id)errhndl; // „succ” és „errhndlr” block lesznek!

4. Osztálynevek és az MVC-architektúra: az OS X-en és iOS-en futó alkalmazások az úgynevezett MVC (Model-View-Controller, azaz modell-nézet-vezérlő) mintát használják a kód struktúrálása érdekében. Ennek a megvalósulása az osztályok elnevezésében is megnyilvánul: a három réteghez értelemszerűen a FooDelegate vagy FooDataSource, a FooView illetve a FooController vagy FooViewController jellegű nevekkel rendelkező osztályok tartoznak. Ha tehát a kód logikáján szeretnénk változtatni, érdemes a modell vagy a vezérlő osztályaiban megfelelő, „gyanús” nevű metódusok után kutatni, míg egy grafikai tuning tervezésekor valószínűleg a nézet vagy ugyancsak a vezérlő rétegeiben találhatunk hasznos deklarációkra.

Egy példa: módosítsuk a már emlegetett exponálógomb működését!

Az alábbi leírást én egy iOS 6.1-et futtató iPaden való kutatás eredményeképpen készítettem el, úgyhogy lehetséges, hogy más verziójú operációs rendszeren vagy más készüléken módosítani kell a működés érdekében.

Vágjunk is bele! A Fényképező alkalmazás a rendszeralkalmazások szokott helyén, a /Applications/Camera.app könyvtárban található, a bináris neve is Camera. A class-dump-z programot lefuttatva rajta kaptam egy jó pár osztályt, a két legfontosabb név szerint:

CameraApplication

PhotosApplication (a Fényképező és a Fotók alkalmazás valójában ugyanaz az app, csak egy trükkel észleli, hogy melyik ikon segítségével indították el)

Ezek közül minket nyilván a CameraApplication osztály érdekel. Belenézve megtaláljuk az instance variable-ök listáját:

@interface CameraApplication : PhotosApplication {

    PLCameraPageController *_cameraPageController;

}

A PLCameraPageController osztályhoz a class-dump nem generált headerfájlt, tehát ez egy külső, importált osztály (egyébként a PhotoLibrary framework része). Egy kis guglizás után megtaláltam ennek is a deklarációját GitHubon:

@interface PLCameraPageController : UIPageController <PLApplicationCameraViewControllerDelegate, PLAlbumChangeObserver, PLCameraPreviewWellImageChangeObserver, UIScrollViewDelegate, UIPageControllerDelegate> {

    struct NSObject { Class x1; } *_cameraAlbum;

    PLApplicationCameraViewController *_cameraViewController;

    UIViewController *_cameraAlbumNavigationController;

    UIViewController *_presentedCameraAlbumNavigationController;

    UIPanGestureRecognizer *_cameraPagePanGestureRecognizer;

    double _sessionStartTime;

    BOOL _supportsVideos;

    BOOL _usesSessionAlbum;

    BOOL _shouldShowCameraAlbum;

    BOOL _delayLoadingPhotoLibrary;

    BOOL _previouslyDidntChangeStatusBar;

    PLKeepDaemonAliveAssertion *_keepDaemonAliveAssertion;

}

Ami a név alapján egy különösen érdekes osztály lehet, az a PLApplicationCameraViewController. Ezt is megtaláltam ugyanabban a GitHub-repóban, és bizony ennek már volt is egy ígéretes nevű (void)takePicture; metódusa (az igazsághoz hozzátartozik, hogy már az előző osztálynak is volt, de mint utóbb kiderült, az egyáltalán nem működött úgy, ahogy azt szerettem volna…).

Először tehát megpróbáltam ezt a metódust megpatkolni a MobileSubstrate segítségével, ám csak félsikerrel jártam: a módosítás telepítése után elég bugos lett az alkalmazás felülete. Kifagyogatott, néha más képet mutatott, mint amit éppen kellett volna, stb. Mindez arra mutatott, hogy ez a metódus még valamit csinál az exponáláson kívül, és én ezt a valamit nem nagyon kellene, hogy piszkáljam. Úgyhogy tovább kutakodtam, és itt mát magát a MobileSubstrate-et hívtam segítségül a nyomozáshoz.

A Cocoa Touch nevezéktanának megfelelően ez az osztály egy view controller (ha a deklarációkon keresztül követjük az öröklési láncot, láthatjuk is, hogy az UIViewControllerből származik le), és természetesen így rendelkezik egy (void)loadView; metódussal is. Írtam tehát egy olyan függvényt, ami az adott PLApplicationCameraViewController-példány view propertyjén meghívja a recursiveDescription metódust. Ez egy privát metódusa az UIView osztálynak, ami a UIView összes gyermekét (subview) kilistázza. Meg is találtam, hogy melyik subview-ról van szó: a PLCameraView osztály egy példánya, ennek a deklarációjában pedig szerepel egy cameraShutterClicked: metódus. A következő kódot írtam tehát:

#import <substrate.h>

#import <UIKit/UIKit.h>

static IMP _orig;

void _mod(id self, SEL _cmd, id button)

{

    [[[[UIAlertView alloc]

        initWithTitle:@”Hello”

        message:nil

        delegate:nil

        cancelButtonTitle:@”OK”

        otherButtonTitles:nil]

    autorelease] show];

}

__attribute__((constructor))

void init()

{

    MSHookMessageEx(

        objc_getClass(“PLCameraView”),

        @selector(cameraShutterClicked:),

        (IMP)_mod,

        &_orig

    );

}

És lám, az exponálógomb megnyomásakor fényképezés helyett kaptam egy felugró ablakot „Hello” üzenettel, mindenféle bug és hiba nélkül! 🙂

Kitekintés

Láttuk tehát, hogy hogyan használhatjuk a class-dump programot és magát a MobileSubstrate API-t is akalmazások működésének kiismerésére. Ha már úgyis a reverse engineering témakörénél tartunk, ezek alapján érdemes még átgondolni a választ néhány praktikus kérdésre.

1. Ha egyszer készítek egy zseniális, nélkülözhetetlen tweaket, és 40 dollárért fogom árulni, akkor hogyan kerülhetem el, hogy feltörjék, illetve azt, hogy a crackerek használják?

– Először is, ne kérj negyven dollárt egy tweakért. 🙂 A tizede is elég borsos árnak számít a 89 eurocentes alkalmazások világában. Szoftveres védelmet vagy anticrack-mechanizmust természetesen érdemes viszont beépíteni a munkánkba. Erre többféle metódus létezik, viszont ha igazán hatékony anticracket akarunk készíteni, ahhoz a cracker, a reverse engineer szemszögéből érdemes nézni a helyzetet. Mert mi a legnehezebb egy crackernek, amikor meg akar törni egy tweaket? Kitalálni, hogy hol van a fizetést ellenőrző kód. És mi segít neki ezt megtalálni (a disassembleren és saját logikáján kívül)? Természetesen az értelmes függvény-, metódus- és osztálynevek. Éppen ezért fogadjuk meg a következő tanácsokat, ha fizetős tweaket készítünk:

– A fizetésellenőrzés, patch protection, stb. logikáját lehetőleg C-ben írjuk, ne Objective-C-ben. A C kódot van annyival nehezebb visszafejteni, hogy legalább a kezdő crackereket ez elbátortalanítsa. Egy tanulságos kísérlet, amit már több, mint egy éve végeztem, csupán a hecc kedvéből: letöltöttem egy alkalmazást (nem árulom el, melyiket) az akkor még működő AppTrackr-ről, amiről írták, hogy detektálja, ha meg lett crackelve. Elindítottam, ki is írta, hogy „Sajnos nem fizettél”, és az OK gombra való kattintással ki is lépett. Gyorsan lefuttattam rajta a class-dumpot, és találtam is egy CopyProtectionManager vagy hasonló nevű osztályt, aminek volt egy (BOOL)isCraked; metódusa. MobileSubstrate-tel átírva a visszatérési értékét NO-ra, rögtön kiválóan működött. Persze csak addig, amíg nagyjából egy percnyi használat után le nem töröltem – a kalózkodás nem a kenyerem, de ez mindenképp egy érdekes eset volt.

– „Strippeljük” (a hasonló nevű „strip” program segítségével) a binárist, mielőtt kiadjuk a tweaket. Ez a program minden lehetséges C és C++ függvénynevet eltávolít a fájlból, csak a memóriacímeket hagyja meg, tehát első ránézésre a crackernek fogalma sem lesz arról, mi mit csinál. Egy hasonló megoldás lehet az, ha értelmetlen neveket adunk a függvényeinknek, pl. hívjuk őket aeorisvzib() vagy scpouaerhqy() néven. Ezeknek a neveknek a használatát még kódolás közben is kényelmessé tehetjük például a C preprocesszor segítségével:

#define IS_CRACKED() aeorisvzib()

– Használjunk minél kevesebb külső library-t az ellenőrzéshez! A külső, importált függvényneveket nem lehet strippelni a binárisból, hiszen a program indulásakor a dinamikus linkernek szüksége van rájuk, hogy a library-kban lévő nevekkel és memóriacímekkel össze tudja őket párosítani. Éppen ezért, ha például SHA512-t használunk valamilyen hashelésre, akkor kivételesen ne használjuk a CommonCrypto API SHA512 függvényeit, hanem keressünk az Interneten egy SHA512-implementációt, fordítsuk egybe a programunkkal, majd futtassuk a „strip” toolt.

– Amennyiben szerveroldali ellenőrzést is használunk (ugye használunk!?), titkosítsuk, rejtsük el az ellenőrzéshez használt URL-t reprezentáló stringet (illetve lehetőleg minél több stringet)! Ha a cracker lefuttatja a strings <bináris> parancsot a programunkon, és meglátja benne ezt:

https://h2co3.com/MyAwesomeTweak/VerifyLicense.php

…akkor már tudja is, mi a teendője: IDA Pro-ban megkeresi azt a függvényt, ami hivatkozik erre a stringre, átírja a visszatérési értékét TRUE-ra, és bumm, oda a licenszvédelem.

A stringek elrejtésére egyébként jó, ha valamilyen egyszerű, ám nem túl ismert vagy elterjedt (esetleg teljesen saját magunk által kreált) kétirányú kódolási algoritmust használunk. Én például egyik munkámban (nem mondom meg, melyikben, aki nagyon ki akarja találni, az úgyis kitalálja) Base-85-be kódoltam a stringeket, és URL-ek helyett a binárisban csak valami olyasmi látszott, hogy „69cb87’”ckdsbghvÜ),cs”

2. Úgy hallottam, hogy a jailbreakelés nagyon veszélyes, mert az embernek minden személyes adatát kilophatják az alkalmazásból, illetve könnyebbé válik a játékokban a csalás. Mint fejlesztő, mit tudok tenni ez ellen?

Egyvalamit biztosan nem: „Ez az alkalmazás sajnos csak eredeti iOS-szel rendelkező készülékeken fut.” – ez a legrosszabb, ahogyan egy fejlesztő reagálhat a jailbreakelésre; ezzel gyakorlatilag eleve tolvajnak és/vagy csalónak tekinti az összes jailbreakelő felhasználót, noha a jailbreak eleve nem a csalásról vagy warezról szól. Arról nem is beszélve, hogy Cydiába készülő tweakek esetén pedig egyszerűen nonszensz. A megfelelő módszer: tegyük az alkalmazásunkat és annak adatait biztonságossá – tegyük meg a pici többleterőfeszítést, ami ezt megkívánja. Ne tároljuk a játékok állását és a felhasználó jelszavát property list fájlokban, hiszen az plaintext (ez kizárja az NSUserDefaults ilyen célú felhasználását is, de természetesen az NSUserDefaults osztályra egyébként sem szabad semmi ilyesmit rábízni). Tudni kell azt is, hogy jailbreakelt készüléken még az egyébként biztonságosnak feltételezett keychain sem igazán biztonságos: annak a tartalmát a Keychain-Dumper nevű kis programocskával teljes egészében ki lehet íratni, a program némi módosításával pedig a benne lévő adatok átírása is lehetséges, jailbreakelt eszközökön tehát ez sem a legjobb hely az érzékenyebb információk elmentésére.

Nos, mára ennyit a reverse engineeringről, használatáról, előnyeiről és veszélyeiről. A következő részben már egy teljes, működő tweak elkészítésén megyünk végig lépésről lépésre, az eddigiek alapján.

Olvasd el a hozzászólásokat is

3 Comments

  1. Nagyon jók ezek a topicok de lehetne egy topic az OS X Pc re való feltelepiteserol?:/ Köszönettel!:)

  2. @ZippeR: persze osx86 pont hu

  3. Már nagyon várom a következő részt 🙂


Add a Comment