Bu Vapura Binmek İçin 64-Bit Olmalısınız

Nov 25 2022
Pixel 7 TLDR için güncellenmiş bir NY Waterway uygulamasının tersine mühendislik: NY Waterway'i yüklemenize izin vermeyen daha yeni bir Android cihazınız varsa, uygulamanın değiştirilmiş sürümünü indirebilirsiniz. Rastgele uygulamaları kurarken her zaman dikkatli olmalısınız, özellikle resmi Play Store dışındaki kaynaklardan - örneğin adını hiç duymadığınız rastgele bir adamın bu Medium gönderisi gibi.

Pixel 7 için güncellenmiş bir NY Waterway uygulamasında tersine mühendislik

Unsplash'ta Maxwell Ridgeway'in fotoğrafı

TLDR : NY Waterway'i yüklemenize izin vermeyen daha yeni bir Android cihazınız varsa , uygulamanın değiştirilmiş sürümünü indirebilirsiniz . Rastgele uygulamaları kurarken her zaman dikkatli olmalısınız, özellikle resmi Play Store dışındaki kaynaklardan - örneğin adını hiç duymadığınız rastgele bir adamın bu Medium gönderisi gibi. Daha dikkatli olmak istiyorsanız, APK'nın nasıl değiştirildiğini görmek için ileriyi okuyabilirsiniz (hatta isterseniz adımları kendiniz tekrarlayabilirsiniz).

Google , 2019 yılında Play Store'daki tüm yeni ve güncel uygulamalar için 64 bit desteğini zorunlu hale getirdi . Ağustos 2021'den itibaren, 64 bit mimariyi desteklemeyen uygulamalar Play Store'da 64 bit özellikli cihazlar için kullanılamayacak hale geldi. Özellikle, yeni Pixel 7 ve Pixel 7 Pro, yalnızca 32 bitlik uygulamaların yüklenmesini hiçbir şekilde desteklemiyor .

Hudson Nehri feribotuna binen New Yorklular için bu oldukça elverişsiz çünkü telefonunuzda elektronik bilet sağlayan uygulama NY Waterway gerçekten eski . En son Haziran 2018'de yayınlandı ve yalnızca 32 bit mimariler için yerel kitaplıklar içeriyor… Bu nedenle, yeni Pixel cihazlarının kullanıcıları için Hudson Nehri feribotunda elektronik bilet yok !

Yıllar önce iPhone'a geçtim, ancak bir Android kullanıcısıyken, işletim sistemi ve uygulamalarla çok uğraşırdım - özel ROM'lar yükler ve uygulamaları geri derlerdim. Yakın bir arkadaşım yeni Pixel 7 Pro'yu aldı ve her zaman Hudson Nehri feribotuna biniyor, bu yüzden bu uygulamayı onun için düzeltmem için şaka yollu beni dürttü. İşte başlıyoruz!

Uygulamaya Bakmak

Yalnızca 32 bit olan ve yüklenmesini engelleyen parçaları belirlemek için NY Waterway uygulamasını inceleyerek başlayalım. kullanarak apktool, Android uygulamasını çıkarabilir ve kodunu inceleyebiliriz.

$ 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 Uyumluluk ve Yerel Kitaplıklar

Android uygulamaları genellikle sizi platforma özgü uyumlulukla ilgili endişelerden koruyan üst düzey bir soyutlama olan Java Sanal Makinesi'ni hedefleyen her iki dil olan Java veya Kotlin'de yazılır. Ancak yerel, platforma özgü kodu (genellikle C veya C++ gibi alt düzey dillerden derlenir) çağırmak için Java Yerel Arabirimini (JNI) kullanabilirsiniz. Dizine bakarsak libs, NY Waterway uygulamasında yer alan yerel kütüphaneleri görebiliriz.

$ 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

Buradaki bir başka gözlem de armeabi, x86dört kütüphaneye sahipken armeabi-v7asadece bir kütüphaneye sahip olmasıdır. Bir kitaplığın Android uygulaması tarafından yüklenmesi için java.lang.System.loadLibraryor öğesini çağırması gerekir java.lang.Runtime.loadLibrary. Smali kodunda "loadLibrary" araması, yerel kitaplıkları yüklediği yalnızca bir yeri gösterir.

$ 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

Uygulamayı yeni Pixel cihazlarıyla uyumlu hale getirmek için libsqlcipher.so64 bitlik bir ARM yapısına ihtiyacımız var. lib/arm64-v8aUygun bir şekilde, SQLCipher bir açık kaynak kitaplığıdır . Yerel sqlcipher kitaplığıyla etkileşim için üst düzey birleştirme koduna baktığımızda, kullanılan kitaplığın sürümünü görebiliriz.

$ 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"

SQLCipher'ı v3.5.5'e yükseltme

Yükseltme işlemi, SQLCipher Smali kodunun ve yerel kitaplıkların daha yeni sürümdeki kodla değiştirilmesini içerecektir. Bu, SQLCipher'in genel API yüzeyi önemli ölçüde değişirse sorunlara neden olur (örneğin, NY Waterway tarafından kullanılan bir genel işlev imzayı değiştirirse veya kaldırılırsa, daha yeni sürümle değiştirmek sorunlara neden olur). v3.5.4'ten v3.5.5'e yapılan değişiklikleri hızlı bir şekilde taradığınızda, burada görünecek bir sorun gibi görünmüyor. SQLCipher v3.5.5 için AAR dosyasını indirdim ve sonra unzipayıklamak için kullandım.

$ 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

Android SDK, bir dosyayı Android bayt koduna ( dosya) d8derleyebilen adlı bir komut satırı aracı sağlar . Sonra , dosyaları . Adımları birleştirmek:jarclasses.dexbaksmalidexsmali

$ 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

Artık uygulamayı yeniden oluşturup imzalayabiliriz, böylece bir cihaza kurulabilir!

$ 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

Çalışıyor! Yine de bu rahatsız edici açılır penceremiz var

Hedef SDK Sürümünü Artırma

Uygulamanın Android'in eski bir sürümü için oluşturulduğunu belirten bu açılır pencereden kurtulmak için hedef SDK sürümünü apktool.yml. <31 SDK sürümünü hedefleyen uygulamalar artık Play Store'da kabul edilmemektedir, bu yüzden bunu buna yükseltmeyi seçtim.

Kullanımdan kaldırılan API'ler daha yeni SDK sürümlerinde kullanılamadığından, Android SDK'nın daha yeni bir sürümünü hedeflemek kod değişiklikleri gerektirebilir. NY Waterway, SDK v31'i hedeflemek için çeşitli değişiklikler gerektirir.

Daha Güvenli Bileşen İhracatı

Uygulamanız Android 12 veya üstünü hedefliyorsa ve amaç filtreleri kullanan etkinlikler, hizmetler veya yayın alıcıları içeriyorsa, android:exportedbu uygulama bileşenleri için özelliği açıkça bildirmeniz gerekir.

s'ye sahip olan ve içine bir öznitelik eklenmesini <intent-filter>gerektiren birkaç etkinlik ve bir alıcı vardır .android:exported="true"AndroidManifest.xml

Bekleyen Niyet Değişkenliği

Uygulamanız Android 12'yi hedefliyorsa, uygulamanızın oluşturduğu her PendingIntentnesnenin değişebilirliğini belirtmeniz gerekir. Bu ek gereksinim, uygulamanızın güvenliğini artırır.

Bu daha aldatıcı çünkü gerçek kodu değiştirmemizi gerektiriyor (proje yapılandırması veya kitaplığın yükseltilmiş bir sürümünü kopyalamanın aksine).

Bir nesne her oluşturulduğunda, açıkça veya PendingIntentöğesini belirtmesi gerekir . Önceki SDK sürümlerinde, hiçbir bayrak belirtilmediyse varsayılan değerdi. nesneler, sınıfta bir dizi statik yöntemle oluşturulur: , , veya . Bu işlevlerin çağrılarını arayarak başlayabiliriz.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'yi Anlamak

Bayt kodu talimatı , invoke-staticstatik işleve parametre olarak iletilmek üzere bir kayıt listesi alır. Landroid/app/PendingIntent;->getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;Statik işlevin sembolü , tam nitelikli sınıf adından ve işlevin imzasından doğrudan bir çeviri gibi görünür . Sınıf adıyla Landroid/app/PendingIntent;(veya android.app.PendingIntentnormal Java sözdiziminde) başlar. Ardından işlevin adı ( ->getBroadcast), parametreler ve dönüş tipi ile birlikte. Landroid/content/Context;ILandroid/content/Intent;Idört parametreye bölünebilen parametrelerdir: Landroid/content/Context;( android.content.Context), I( int), Landroid/content/Intent;( android.content.Intent) ve I( int). Son olarak, kapanış parantezinden sonra dönüş türü: Landroid/app/PendingIntent;.

Bu nedenle, yukarıdaki işlevin ilk olarak , ilk olarak ve olarak invoke-static {v1, v2, v3, v4}geçecektir . Bu API'ler için , her zaman son parametredir ( ), bu nedenle değerin her zaman ya da ayarlı olduğundan emin olmamız yeterlidir . Android SDK belgeleri , is ve değerinin olduğunu ortaya koymaktadır . Çoğu durumda, son parametre, sabit bir değerle ( veya gibi ) başlatılan yerel bir değişken kaydı ( ) olarak belirtilir. Bu durumlarda, olup olmadığını önemsiz bir şekilde kontrol edebiliriz .v1Contextv2intv3Intentv4intPendingIntentflagsintFLAG_MUTABLEFLAG_IMMUTABLEFLAG_MUTABLE0x02000000FLAG_IMMUTABLE0x04000000v#const/high16 v3, 0x8000000const/4 v4, 0x0FLAG_MUTABLEFLAG_IMMUTABLEayarlandı ve değilse sabiti güncelleyin.

-    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

Dosya sistemi izin değişiklikleri

Özel dosyaların dosya izinleri artık dosya sahibi tarafından gevşetilmemelidir ve MODE_WORLD_READABLEve/veya kullanarak bunu yapmaya çalışmak MODE_WORLD_WRITEABLEbir SecurityException.

NET'te kullanılan bazı SharedPreferencesAPI kullanımları MODE_WORLD_READABLEvardı com/google/android/gms/ads/identifier/AdvertisingIdClient.smali. ( )' den ( ) MODE_WORLD_READABLE' ye geçiş meselesi olduğu için bunu düzeltmesi çok basitti .0x1MODE_PRIVATE0x0

--- 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;

Android 6.0 ile Apache HTTP istemcisi desteğini kaldırdık. Android 9'dan başlayarak, bu kitaplık önyükleme sınıfı yolundan kaldırılmıştır ve varsayılan olarak uygulamalar tarafından kullanılamaz.

NY Waterway, Apache HTTP istemcisinin Android sürümünü kullanıyordu, ancak bunun düzeltmesi oldukça basit - 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>

Uygulamanız Android 9 veya üstünü hedefliyorsa, isCleartextTrafficPermitted()yöntem falsevarsayılan olarak geri döner. Uygulamanızın belirli alanlar için açık metni etkinleştirmesi gerekiyorsa , uygulamanızın Ağ Güvenliği Yapılandırmasında bu alanlar için açıkça ayarlamanız cleartextTrafficPermittedgerekir .true

Bu yeni güvenlik özelliği nedeniyle ağ istekleri başarısız oluyordu. Uygulamayı uyumlu hale getirmenin en basit yolu , özniteliği AndroidManifest.xmleklemek için yapılan başka bir değişiklikti.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">

Yukarıdaki tüm değişiklikleri yaptıktan sonra, uygulama, Android'in eski bir sürümü için oluşturulduğuna dair rahatsız edici bir açılır pencere olmadan başarıyla çalışır!

Biraz beklenmedik bir şekilde, daha yeni hedef SDK sürümüyle çalışmasını sağlamak, 64-bit sorununu fiilen düzeltmekten çok daha karmaşıktı, ancak günün sonunda, her şey sadece kod ve koddan korkacak bir şey yok...

Bağlanmak İster misiniz? Twitter veya LinkedIn'de bana bir mesaj gönderin !