Sie müssen 64-Bit sein, um mit dieser Fähre zu fahren

Nov 25 2022
Reverse Engineering einer aktualisierten NY Waterway-App für das Pixel 7 TLDR: Wenn Sie ein neueres Android-Gerät haben, auf dem Sie NY Waterway nicht installieren können, können Sie meine modifizierte Version der Anwendung herunterladen. Sie sollten immer vorsichtig sein, zufällige Anwendungen zu installieren, insbesondere aus anderen Quellen als dem offiziellen Play Store – wie diesem Medium-Beitrag von einem zufälligen Typen, von dem Sie noch nie gehört haben.

Reverse Engineering einer aktualisierten NY Waterway-App für das Pixel 7

Foto von Maxwell Ridgeway auf Unsplash

TLDR : Wenn Sie ein neueres Android-Gerät haben, auf dem Sie NY Waterway nicht installieren können, können Sie meine modifizierte Version der Anwendung herunterladen . Sie sollten immer vorsichtig sein, zufällige Anwendungen zu installieren, insbesondere aus anderen Quellen als dem offiziellen Play Store – wie diesem Medium-Beitrag von einem zufälligen Typen, von dem Sie noch nie gehört haben. Wenn Sie besonders vorsichtig sein möchten, können Sie im Voraus lesen, wie das APK geändert wurde (und die Schritte sogar selbst wiederholen, wenn Sie möchten).

Im Jahr 2019 hat Google die 64-Bit-Unterstützung für alle neuen und aktualisierten Anwendungen im Play Store erforderlich gemacht. Ab August 2021 sind Anwendungen, die die 64-Bit-Architektur nicht unterstützen, im Play Store für 64-Bit-fähige Geräte nicht mehr verfügbar. Insbesondere das neue Pixel 7 und Pixel 7 Pro unterstützen die Installation von reinen 32-Bit-Anwendungen überhaupt nicht .

Für New Yorker, die mit der Hudson River-Fähre fahren, ist dies ziemlich unpraktisch, da die Anwendung NY Waterway , die elektronische Tickets auf Ihrem Telefon bereitstellt , wirklich alt ist . Es wurde zuletzt im Juni 2018 veröffentlicht und enthält native Bibliotheken nur für 32-Bit-Architekturen ... Daher für Benutzer der neuen Pixel-Geräte keine elektronischen Tickets für die Hudson River-Fähre für Sie!

Ich bin vor vielen Jahren auf das iPhone umgestiegen, aber damals, als ich ein Android-Benutzer war, habe ich viel mit dem Betriebssystem und den Anwendungen rumgespielt – benutzerdefinierte ROMs installiert und Anwendungen dekompiliert. Ein enger Freund von mir hat das neue Pixel 7 Pro und fährt die ganze Zeit mit der Hudson River-Fähre, also hat er mich scherzhaft dazu gedrängt, diese App für ihn zu reparieren. Auf geht's!

Peering in die Anwendung

Beginnen wir damit, die NY Waterway-Anwendung zu untersuchen, um die Teile zu identifizieren, die nur 32-Bit sind und die Installation verhindern. Mit apktoolkönnen wir die Android-Anwendung extrahieren und ihren Code untersuchen.

$ apktool d ./NYWaterway.apk
I: Using Apktool 2.6.1 on NYWaterway.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /Users/joeywatts/Library/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
$ cd ./NYWaterway
$ ls -l
total 72
-rw-r--r--    1 joeywatts  staff   8797 Nov 21 18:37 AndroidManifest.xml
-rw-r--r--    1 joeywatts  staff  21382 Nov 21 18:37 apktool.yml
drwxr-xr-x   14 joeywatts  staff    448 Nov 21 18:37 assets
drwxr-xr-x    5 joeywatts  staff    160 Nov 21 18:37 lib
drwxr-xr-x    4 joeywatts  staff    128 Nov 21 18:37 original
drwxr-xr-x  178 joeywatts  staff   5696 Nov 21 18:37 res
drwxr-xr-x   10 joeywatts  staff    320 Nov 21 18:37 smali
drwxr-xr-x   10 joeywatts  staff    320 Nov 21 18:37 unknown

64-Bit-Kompatibilität und native Bibliotheken

Android-Anwendungen sind in der Regel in Java oder Kotlin geschrieben, beides Sprachen, die auf die Java Virtual Machine abzielen, bei der es sich um eine Abstraktion auf hoher Ebene handelt, die Sie im Allgemeinen vor Bedenken hinsichtlich der plattformspezifischen Kompatibilität schützt. Sie können jedoch das Java Native Interface (JNI) verwenden, um nativen, plattformspezifischen Code aufzurufen (normalerweise aus niedrigeren Sprachen wie C oder C++ kompiliert). Wenn wir uns das libsVerzeichnis ansehen, können wir die nativen Bibliotheken sehen, die in der NY Waterway-App enthalten sind.

$ ls -lR lib/*
lib/armeabi:
total 8352
-rw-r--r--  1 joeywatts  staff   177900 Nov 21 18:37 libdatabase_sqlcipher.so
-rw-r--r--  1 joeywatts  staff  1369284 Nov 21 18:37 libsqlcipher.so
-rw-r--r--  1 joeywatts  staff  2314540 Nov 21 18:37 libsqlcipher_android.so
-rw-r--r--  1 joeywatts  staff   402604 Nov 21 18:37 libstlport_shared.so

lib/armeabi-v7a:
total 2552
-rw-r--r--  1 joeywatts  staff  1303788 Nov 21 18:37 libsqlcipher.so

lib/x86:
total 14616
-rw-r--r--  1 joeywatts  staff  1476500 Nov 21 18:37 libdatabase_sqlcipher.so
-rw-r--r--  1 joeywatts  staff  2246448 Nov 21 18:37 libsqlcipher.so
-rw-r--r--  1 joeywatts  staff  3294132 Nov 21 18:37 libsqlcipher_android.so
-rw-r--r--  1 joeywatts  staff   455740 Nov 21 18:37 libstlport_shared.so

Eine weitere Beobachtung hier ist, dass armeabiund x86vier Bibliotheken haben, während sie armeabi-v7anur eine haben. Damit eine Bibliothek von der Android-App geladen werden kann, müsste sie java.lang.System.loadLibraryoder aufrufen java.lang.Runtime.loadLibrary. Die Suche im Smali-Code nach „loadLibrary“ zeigt nur eine Stelle, an der native Bibliotheken geladen werden.

$ grep -r loadLibrary smali/
smali//net/sqlcipher/database/SQLiteDatabase.smali:    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
$ grep loadLibrary -A 2 -B 3 smali/net/sqlcipher/database/SQLiteDatabase.smali
    :try_start_0
    const-string v0, "sqlcipher"

    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    :try_end_0
    .catchall {:try_start_0 .. :try_end_0} :catchall_0

Wir benötigen einen 64-Bit-ARM-Build libsqlcipher.so, lib/arm64-v8aum die Anwendung mit den neuen Pixel-Geräten kompatibel zu machen. Praktischerweise ist SQLCipher eine Open-Source-Bibliothek . Wenn wir uns den High-Level-Glue-Code für die Interaktion mit der nativen sqlcipher-Bibliothek ansehen, können wir die Version der verwendeten Bibliothek sehen.

$ grep -ri version smali/net/sqlcipher 
smali/net/sqlcipher/database/SQLiteDatabase.smali:.field public static final SQLCIPHER_ANDROID_VERSION:Ljava/lang/String; = "3.5.4"

Upgrade von SQLCipher auf v3.5.5

Der Aktualisierungsprozess umfasst das Ersetzen des SQLCipher-Smali-Codes und der nativen Bibliotheken durch den Code der neueren Version. Dies würde zu Problemen führen, wenn sich die öffentliche API-Oberfläche von SQLCipher erheblich ändert (wenn beispielsweise eine von NY Waterway verwendete öffentliche Funktion entweder die Signatur geändert oder entfernt wurde, würde das Ersetzen durch die neuere Version Probleme verursachen). Wenn Sie einen schnellen Scan der Änderungen von v3.5.4 zu v3.5.5 durchführen, scheint es kein Problem zu sein, das hier auftreten wird. Ich habe die AAR-Datei für SQLCipher v3.5.5 heruntergeladen und dann unzipzum Extrahieren verwendet.

$ mkdir ../sqlcipher && cd ../sqlcipher
$ unzip ~/Downloads/android-database-sqlcipher-3.5.5.aar
Archive:  /Users/joeywatts/Downloads/android-database-sqlcipher-3.5.5.aar
  inflating: AndroidManifest.xml     
   creating: res/
  inflating: classes.jar             
   creating: jni/
   creating: jni/arm64-v8a/
   creating: jni/armeabi/
   creating: jni/armeabi-v7a/
   creating: jni/x86/
   creating: jni/x86_64/
  inflating: jni/arm64-v8a/libsqlcipher.so  
  inflating: jni/armeabi/libsqlcipher.so  
  inflating: jni/armeabi-v7a/libsqlcipher.so  
  inflating: jni/x86/libsqlcipher.so  
  inflating: jni/x86_64/libsqlcipher.so

Das Android SDK stellt ein sogenanntes Kommandozeilen-Tool bereit d8, das eine jarDatei zu Android-Bytecode ( classes.dexDatei) kompilieren kann. Dann gibt es noch ein weiteres Tool namens baksmali, das dexDateien in smali. Kombinieren Sie die Schritte miteinander:

$ export ANDROID_HOME=/Users/joeywatts/Library/Android/sdk
$ $ANDROID_HOME/build-tools/33.0.0/d8 classes.jar \
   --lib $ANDROID_HOME/platforms/android-31/android.jar
$ java -jar ../baksmali.jar dis ./classes.dex

$ rm -r ../NYWaterway/smali/net/sqlcipher ../NYWaterway/lib
$ mv out/net/sqlcipher ../NYWaterway/smali/net/sqlcipher
$ mv jni ../NYWaterway/lib

Jetzt können wir die Anwendung neu erstellen und signieren, damit sie auf einem Gerät installiert werden kann!

$ cd ../NYWaterway
$ apktool b .
$ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name \
    -keyalg RSA -keysize 2048 -validity 10000
$ $ANDROID_HOME/build-tools/33.0.0/apksigner sign \
    --ks my-release-key.keystore ./dist/NYWaterway.apk

Es läuft! Wir haben aber dieses nörgelnde Popup

Erhöhen der Ziel-SDK-Version

Um dieses Popup loszuwerden, das darauf hinweist, dass die Anwendung für eine ältere Version von Android erstellt wurde, müssen wir die Ziel-SDK-Version in apktool.yml. Anwendungen, die auf die SDK-Version <31 abzielen, werden im Play Store nicht mehr akzeptiert, daher habe ich mich entschieden, sie darauf zu erhöhen.

Das Targeting einer neueren Version des Android SDK erfordert möglicherweise Codeänderungen, da veraltete APIs in neueren SDK-Versionen nicht mehr verfügbar sind. NY Waterway erfordert mehrere Änderungen am Ziel-SDK v31.

Sicherer Komponentenexport

Wenn Ihre App auf Android 12 oder höher ausgerichtet ist und Aktivitäten, Dienste oder Broadcast-Empfänger enthält, die Absichtsfilter verwenden, müssen Sie das android:exportedAttribut für diese App-Komponenten explizit deklarieren.

Es gibt ein paar Aktivitäten und einen Empfänger, die <intent-filter>s haben und erfordern, dass ein android:exported="true"Attribut in hinzugefügt wird AndroidManifest.xml.

Veränderbarkeit der ausstehenden Absichten

Wenn Ihre App auf Android 12 ausgerichtet ist, müssen Sie die Veränderbarkeit jedes PendingIntentObjekts angeben, das Ihre App erstellt. Diese zusätzliche Anforderung verbessert die Sicherheit Ihrer App.

Dieser ist kniffliger, da wir den eigentlichen Code ändern müssen (im Gegensatz zur Projektkonfiguration oder zum Kopieren einer aktualisierten Version der Bibliothek).

Jedes Mal, wenn ein Objekt erstellt wird, muss es explizit oder PendingIntentangeben . In früheren SDK-Versionen war dies der Standardwert, wenn keines der Flags angegeben wurde. Objekte werden durch einen Satz statischer Methoden für die Klasse erstellt: , , , oder . Wir können damit beginnen, nach Aufrufen dieser Funktionen zu suchen.FLAG_MUTABLEFLAG_IMMUTABLEFLAG_MUTABLEPendingIntentgetActivitygetActivitiesgetBroadcastgetService

$ grep -r -E "PendingIntent;->(getActivity|getActivities|getBroadcast|getService)" smali
smali/android/support/v4/f/a/ac.smali:    invoke-static {p1, v2, v0, v2}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/firebase/iid/r.smali:    invoke-static {p0, p1, v0, p4}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/firebase/iid/m.smali:    invoke-static {p0, v2, v0, v3}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/firebase/messaging/c.smali:    invoke-static {v0, v2, v1, v3}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/common/m.smali:    invoke-static {p1, p3, v0, v1}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/common/api/GoogleApiActivity.smali:    invoke-static {p0, v0, v1, v2}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/c/cbx.smali:    invoke-static {v1, v2, v0, v3}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/c/cbx.smali:    invoke-static {v2, v7, v1, v7}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/c/v.smali:    invoke-static {v0, v1, v2, v3}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/c/bj.smali:    invoke-static {v1, p2, v0, v2}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/c/byd.smali:    invoke-static {v1, v4, v0, v4}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
smali/com/google/android/gms/c/mr.smali:    invoke-static {v1, v3, v0, v3}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;

Smali verstehen

Der invoke-staticBytecode-Befehl nimmt eine Liste von Registern, die als Parameter an die statische Funktion übergeben werden. Das Symbol der statischen Funktion sieht aus wie Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;eine direkte Übersetzung aus dem vollständig qualifizierten Klassennamen und der Signatur der Funktion. Es beginnt mit dem Klassennamen Landroid/app/PendingIntent;(oder android.app.PendingIntentin normaler Java-Syntax). Dann der Name der Funktion ( ->getBroadcast) zusammen mit den Parametern und dem Rückgabetyp. Landroid/content/Context;ILandroid/content/Intent;Isind die Parameter, die in vier Parameter aufgeteilt werden können: Landroid/content/Context;( android.content.Context), I( int), Landroid/content/Intent;( android.content.Intent) und I( int). Schließlich steht nach der schließenden Klammer der Rückgabetyp: Landroid/app/PendingIntent;.

Daher invoke-static {v1, v2, v3, v4}würde die obige Funktion v1als Context, v2als erste int, v3als Intent, und v4als int. Für diese PendingIntentAPIs flagssind die immer der letzte Parameter ( int), also müssen wir nur sicherstellen, dass der Wert immer entweder FLAG_MUTABLEoder FLAG_IMMUTABLEgesetzt ist. Die Android SDK-Dokumentation zeigt, dass der Wert von FLAG_MUTABLEis 0x02000000und FLAG_IMMUTABLEis 0x04000000. In den meisten Fällen wird der letzte Parameter als lokales Variablenregister ( ) angegeben, das mit einem konstanten Wert (z. B. oder ) v#initialisiert wurde . In diesen Fällen können wir trivial prüfen, ob oderconst/high16 v3, 0x8000000const/4 v4, 0x0FLAG_MUTABLEFLAG_IMMUTABLEgesetzt ist und aktualisieren Sie die Konstante, wenn dies nicht der Fall ist.

-    const/high16 v3, 0x8000000
+    const/high16 v3, 0xA000000

     invoke-static {v1, v2, v0, v3}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;

# you may need to change from const/4 to const/high16 to specify the flag
# const/4 is a loading a signed 4-bit integer (seen used to load 0x0).
# const/high16 loads the high 16-bits from a value (the low 16-bits must be 0)

-    const/4 v4, 0x0
+    const/high16 v4, 0x2000000

.method private static a(Landroid/content/Context;ILjava/lang/String;Landroid/content/Intent;I)Landroid/app/PendingIntent;
    .locals 2

    new-instance v0, Landroid/content/Intent;

    const-class v1, Lcom/google/firebase/iid/FirebaseInstanceIdInternalReceiver;

    invoke-direct {v0, p0, v1}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V

    invoke-virtual {v0, p2}, Landroid/content/Intent;->setAction(Ljava/lang/String;)Landroid/content/Intent;

    const-string v1, "wrapped_intent"

    invoke-virtual {v0, v1, p3}, Landroid/content/Intent;->putExtra(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;

    invoke-static {p0, p1, v0, p4}, Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;

    move-result-object v0

    return-object v0
.end method

if (p4 & (FLAG_IMMUTABLE | FLAG_MUTABLE) == 0) {
    p4 |= FLAG_MUTABLE;
}

const/high16 v3, 0x6000000 # v3 = FLAG_IMMUTABLE | FLAG_MUTABLE
and-int v2, p4, v3         # v2 = p4 & v3
if-nez v2, :cond_0         # if (v2 != 0) { goto :cond_0; }
const/high16 v3, 0x2000000 # v3 = FLAG_MUTABLE
or-int p4, p4, v3          # p4 = p4 | v3
:cond_0

Änderungen der Dateisystemberechtigungen

Die Dateiberechtigungen privater Dateien sollten vom Besitzer nicht mehr gelockert werden, und ein Versuch, dies mit MODE_WORLD_READABLEund/oder zu tun MODE_WORLD_WRITEABLE, löst eine SecurityException.

Es gab eine SharedPreferencesAPI-Nutzung, die MODE_WORLD_READABLEin com/google/android/gms/ads/identifier/AdvertisingIdClient.smali. Dies war sehr einfach zu beheben, da es darum ging, von MODE_WORLD_READABLE( 0x1) auf MODE_PRIVATE( 0x0) umzuschalten.

--- a/smali/com/google/android/gms/ads/identifier/AdvertisingIdClient.smali
+++ b/smali/com/google/android/gms/ads/identifier/AdvertisingIdClient.smali
@@ -93,7 +93,7 @@
 
     const-string v4, "google_ads_flags"
 
-    const/4 v5, 0x1
+    const/4 v5, 0x0
 
     invoke-virtual {v2, v4, v5}, Landroid/content/Context;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;

Mit Android 6.0 haben wir die Unterstützung für den Apache HTTP-Client entfernt. Ab Android 9 wird diese Bibliothek aus dem Bootclasspath entfernt und steht Apps standardmäßig nicht zur Verfügung.

NY Waterway verwendete die Android-Version des Apache HTTP-Clients, aber die Lösung dafür ist ziemlich einfach – nur eine weitere Änderung an der AndroidManifest.xml.

diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1490d73..39ccbf3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,6 +16,7 @@
     <permission android:name="co.bytemark.nywaterway.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
     <uses-permission android:name="co.bytemark.nywaterway.permission.C2D_MESSAGE"/>
     <application android:allowBackup="false" android:icon="@drawable/icon" android:label="@string/app_name" android:name="co.bytemark.nywaterway2.core.NYWWApp" android:theme="@style/AppTheme">
+        <uses-library android:name="org.apache.http.legacy" android:required="false" />
         <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/>
         <receiver android:exported="false" android:label="NetworkConnection" android:name="co.bytemark.android.sdk.BytemarkSDK$ConnectionChangeReceiver">
             <intent-filter>

Wenn Ihre App auf Android 9 oder höher ausgerichtet ist, gibt die isCleartextTrafficPermitted()Methode falsestandardmäßig zurück. Wenn Ihre App Klartext für bestimmte Domänen aktivieren muss, müssen Sie dies explizit cleartextTrafficPermittedfür truediese Domänen in der Netzwerksicherheitskonfiguration Ihrer App festlegen.

Netzwerkanfragen schlugen aufgrund dieser neuen Sicherheitsfunktion fehl. Der einfachste Weg, die Anwendung kompatibel zu machen, war nur eine weitere Änderung an , um das Attribut AndroidManifest.xmlhinzuzufügen .android:usesCleartextTraffic="true"

diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 39ccbf3..69b4aa7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,7 +15,7 @@
     <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
     <permission android:name="co.bytemark.nywaterway.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
     <uses-permission android:name="co.bytemark.nywaterway.permission.C2D_MESSAGE"/>
-    <application android:allowBackup="false" android:icon="@drawable/icon" android:label="@string/app_name" android:name="co.bytemark.nywaterway2.core.NYWWApp" android:theme="@style/AppTheme">
+    <application android:allowBackup="false" android:icon="@drawable/icon" android:label="@string/app_name" android:name="co.bytemark.nywaterway2.core.NYWWApp" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
         <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/>
         <receiver android:exported="false" android:label="NetworkConnection" android:name="co.bytemark.android.sdk.BytemarkSDK$ConnectionChangeReceiver">

Nachdem Sie alle oben genannten Änderungen vorgenommen haben, läuft die Anwendung erfolgreich ohne nörgelndes Popup, dass sie für eine ältere Version von Android erstellt wurde!

Etwas unerwartet war es viel aufwändiger, es mit der neueren Ziel-SDK-Version zum Laufen zu bringen, als das 64-Bit-Problem tatsächlich zu beheben, aber am Ende des Tages ist alles nur Code und Code ist nichts, wovor man sich fürchten muss …

Möchten Sie sich verbinden? Senden Sie mir eine Nachricht auf Twitter oder LinkedIn !