Android逆向之旅---Android中的sharedUserId屬性詳解
一、前言
今天我們來看一下Android中一個眾人熟悉的一個屬性:shareUserId,關於這個屬性可能大家都很熟悉了,最近在開發專案,用到了這個屬性,雖然知道一點知識,但是感覺還是有些迷糊,所以就寫篇文章來深入研究一下。
關於Android中的sharedUserId的概念這裡就簡單介紹一下:
Android給每個APK程式分配一個單獨的空間,manifest中的userid就是對應一個分配的Linux使用者ID,並且為它建立一個沙箱,以防止影
響其他應用程式(或者其他應用程式影響它)。使用者ID 在應用程式安裝到裝置中時被分配,並且在這個裝置中保持它的永久性。
通常,不同的APK會具有不同的userId,因此執行時屬於不同的程式中,而不同程式中的資源是不共享的,在保障了程式執行的穩定。然後在有些時候,我們自己開發了多個APK並且需要他們之間互相共享資源,那麼就需要通過設定shareUserId來實現這一目的。
通過Shared User id,擁有同一個User id的多個APK可以配置成執行在同一個程式中.所以預設就是可以互相訪問任意資料. 也可以配置成執行成不同的程式, 同時可以訪問其他APK的資料目錄下的資料庫和檔案.就像訪問本程式的資料一樣。
用法也很簡單:
在需要共享資源的專案的每個AndroidMainfest.xml中新增shareuserId的標籤。
android:sharedUserId="com.example"
id名自由設定,但必須保證每個專案都使用了相同的sharedUserId。一個mainfest只能有一個Shareuserid標籤。
二、問題延伸
我們今天先來看一個場景:Android中一個App如何能夠訪問到其他App的資訊和資源?
這個可能很多人感覺是兩個App之間的通訊,其實不是,比如我們在早期遇到支付寶有一個快捷支付,那麼我們會看到手機中會安裝兩個app,一個是支付寶app,一個是快捷支付app,那麼在開啟快捷支付的時候,就會呼叫快捷支付app等,大家可能會想到現在有一個比較流行的技術叫做外掛開發,的確如此,這個我在之前的文章也有說過,不清楚的同學可以點選這裡:Android中外掛開發篇
但是我們今天不說這個外掛怎麼搞,今天就來看看如何在一個app中去訪問另外一個app的程式碼和資源等資訊?
在說這個知識點之前,我們需要了解的一個知識點,就是我們可以通過一個包名來得到對應的Context的全域性變數,可以直接使用Context的一個靜態方法:createPackageContext
關於這個方法其實很簡單,他有兩個引數:
第一個引數:需要構造出來Context的包名字串
第二個引數:構造出來的Context的開啟模式
下面我們可以直接使用一個例子來看看效果:
首先我們弄一個外掛工程:ShareUserIdPlugin
這個工程很簡單,我們編譯安裝執行即可。
在弄一個宿主工程:ShareUserIdHost
這裡有一個核心方法,我們首先通過外掛工程的包名:cn.wjdiankong.shareuseridplugin;建立出一個Context物件。
這裡看到第二引數有兩個模式:
Context.CONTEXT_INCLUDE_CODE:這個標誌是在我們需要執行外掛中的某段程式碼需要加上的值。
CONTEXT_IGNORE_SECURITY:這個標誌是必須的,是忽視安全性,如果沒有這個值的話,那麼我們訪問什麼都是失敗的。
得到了Context變數之後,我們下面就可以通過反射來執行程式碼和獲取資源了,這裡需要注意的是,一定要先拿到Context對應的ClassLoader,然後才能載入對應的類,ClassLoader一定是Context的,是外掛工程中的類載入器。
下面我們執行結果看看:
執行成功了啦~~是不是很簡單呢。
下面如果我們把CONTEXT_INCLUDE_CODE去掉,在執行:
發現報錯了,找不到指定的類。所以如果想執行程式碼的話,這個值一定要加上。
我們再把CONTEXT_IGNORE_SECURITY去掉,執行結果:
看到了,爆出了安全錯誤,所以要想構造成功Context出來,必須要加上這個值。
三、步入正題
好了,到這裡我們就介紹瞭如何通過包名構造一個Context變數出來,然後執行對應的程式碼和獲取資源。那麼這個我們看到工程中貌似沒有用到shareUserId這個屬性呢?那這個和我們今天要介紹的知識點有什麼關係嗎?其實沒什麼關係,上面的例子只能說是做一個簡單的引子,那有些同學可能困惑了,為何都沒有使用shareUserId屬性,這兩件事還可以做呢?那豈不是很不安全?其實我們在接觸過逆向知識的時候會發現,關於Android中的一個App中的程式碼和資源說的直白點其實沒有安全性可言,比如,我想獲取一個一個app中的指定資源,可以使用反編譯或者直接解壓apk就可以得到,想看到app中的一段程式碼的含義或者執行結果,反編譯也可以做到,所以說這個說的直白點關於程式碼和資源在Android中其實沒什麼安全性可說。有辦法可以去搞定的。
當然我們在後面可以用這種構造Context的方式,去實現我們想要的一些功能,比如我們知道了一個app的資源名或者是方法名,想直接在我的工程中用,那麼可以使用這種方式就可以啦,不過這個還是很不靠譜的,當然也是一種方式,比如A應用實現了一個很複雜的一個方法,我自己的應用和他沒任何關係,但是也需要這個方法,那麼可以直接使用這種方式去呼叫即可。但是前提是A應用安裝了。當然正規公司的app都不會這麼傻逼的去做的,其實我們在研究逆向app的時候可能會用到哦~~
那麼說了這麼多,shareUserId的屬性的最大作用是什麼呢?
前面都說了,Android中每個app都對應一個uid,每個uid都有自己的一個沙箱,這是基於安全考慮的,那麼說到沙箱,我們會想到的是data/data/XXXX/目錄下面的所有資料,因為我們知道這個目錄下面的所有資料是一個應用私有的,一般情況下其他應用是沒有許可權訪問的,當然root之後是另外情況,這裡就不多說了。這裡只看沒有root的情況,下面我們在來看一個場景:
A應用和B應用都是一家公司的,現在想在A應用中能夠拿到B引用儲存的一些值,那麼這時候該怎麼辦呢?
這時候就需要用到了shareUserId屬性了,但是這裡我們在介紹shareUserId屬性前,我們先來看一個簡單的例子:
還是使用上面的兩個工程:
ShareUserIdPlugin中的MainActivity.java程式碼如下:
這裡很簡單,我們使用SharedPreferences來儲存一個密碼,注意模式是:Context.MODE_PRIVATE,關於這裡,有很多種模式,後面會詳細介紹。
下面在來看一下宿主工程中的程式碼,獲取密碼。
執行宿主工程結果:
我們看到執行結果列印出來了幾個值,我先不管其他的,看到最後pwd的值是預設值,那說明我們宿主工程中獲取外掛工程中的密碼失敗了。
我們在去看看外掛工程中那個shareperference的xml檔案的許可權:
這裡使用root了之後檢視的:-rw-rw----
關於這個值,不瞭解的同學可以網上去看一些資料:
Linux檔案許可權你分開三段來看:
首位代表是目錄還是檔案,一般不用管,後面的三段每段3位,r代表可讀,w代表可寫,x代表可執行,第一段是代表檔案所屬的使用者對它的許可權,第二段是所屬使用者組的使用者對它的許可權,第三段是其他使用者對它的許可權。
第一段:rw- ,所屬使用者(比如是root)對這個檔案可讀可寫
第二段:rw- ,所屬使用者組使用者,對這個檔案可讀可寫
第三段:--- ,其他使用者對這個檔案什麼都幹不了
那麼從上面的分析可以看出來,這個檔案對於其他使用者(不同uid的)訪問是失敗的。所以我們獲取密碼失敗。
那麼這個xml的許可權在哪裡設定的呢?其實就是在外掛工程中的那個建立SharedPreferences的時候:
其實Context提供了幾種模式:
1、Context.MODE_PRIVATE:為預設操作模式,代表該檔案是私有資料,只能被應用本身訪問,在該模式下,寫入的內容會覆 蓋原檔案的內容,如果想把新寫入的內容追加到原檔案中。可以使用Context.MODE_APPEND
2、Context.MODE_APPEND:模式會檢查檔案是否存在,存在就往檔案追加內容,否則就建立新檔案。
3、Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用來控制其他應用是否有許可權讀寫該檔案。
MODE_WORLD_READABLE:表示當前檔案可以被其他應用讀取;
MODE_WORLD_WRITEABLE:表示當前檔案可以被其他應用寫入
我們可以檢視原始碼ContextImpl.java:
這裡獲取一個SharedPreferencesImpl物件,這個物件是實現了SharedPreferences介面的。這裡我們看到採用了快取機制,將xml的名字和sp物件一一對應起來,所以我們可以得知,一個app中,最好簡化xml的個數,儘量將值都定義到一個xml中,減少記憶體佔用。
我們在看看SharedPreferencesImpl.java類原始碼:
有一個全域性變數儲存了mode值,再看看mMode在哪裡用到了:
在writeToFile這個方法中用到了,這個方法其實後面會分析的,就是SP將記憶體中的值儲存到磁碟中。
然後再看看ContextImpl的setFilePermissionsFromMode方法:
好了,到這裡,我們可以看到,通過傳遞進來的mode值,來設定檔案的許可權。
那麼程式碼看完了,下面我們在改一下外掛工程中的那個建立sp的程式碼:
Context.MODE_WORLD_READABLE|Context.MODE_WORLD_WRITEABLE 為讀寫模式
再來測試一下:
看到這裡取出來密碼了,成功了,關於空指標後面會詳細介紹的,這裡先不管了。我們再來看一下sp的xml檔案許可權:
看到了,其他使用者是可以進行讀寫操作的了,所以取出來的密碼是成功的了。
到這裡我們就弄清楚了Context提供的那幾個建立sp檔案的幾種模式的區別,所以我們這裡也可以看到,這個模式很重要,對於安全性來說,不過這個預設模式就是private的,也是挺好的。
補充:
第一:不需要root來檢視sp檔案的許可權
前面我們看到我們是使用root之後檢視檔案的許可權的,其實還有一種方式,不root也是可以的,那就是run-as命令,關於這個命令不熟的同學可以自行google了,這個命令的作用是:可以檢視指定包名應用的data目錄下面的資料,也就是隻能檢視data/data/XXX/目錄下面的內容,而且他的侷限性也很大,只有debug模式下才能起作用,下面我們來看看怎麼使用:
run-as 需要檢視內容的應用包名
是不是這裡也是可以檢視的,但是他只能在debug下面才能使用,比如我們現在用它去檢視非debug的應用:
看到了吧,很蛋疼,非debug模式還不能用。好吧,不過這裡只是做了一個知識點的補充,記住有這個命令,在debug環境下也是蠻有用的。
第二:關於上面日誌中的異常是怎麼回事?
我們回去看看宿主工程中,用反射去訪問了SP內部的一些變數值。為什麼訪問這些呢?源於我之前除錯一個bug,但是這裡引出來了一些問題,下面就來分析一下。
為了分析,這裡我們還是需要去看SharePreferencesImpl原始碼:
程式碼邏輯不是很複雜,首先建立備份檔案,然後載入xml內容到記憶體的map物件,用於後面的getXXX方法直接獲取值,提高效率,然後將解析之後的map賦值給全域性的map物件,如果解析出來的map為空,那麼就直接賦值一個空資料的map。最後一行程式碼很重要,就是需要喚醒其他所有的wait地方,看完這段程式碼我們就可以很好理解上面的異常崩潰了:
首先檔案是可讀的,所以進入到了if語句中,開始解析xml到記憶體中,但是這時候需要注意的是,解析工作實在子執行緒中工作的,但是我們去訪問全域性map是在主執行緒做的,那麼這時候解析還沒有完成,那麼只能獲取到null值了,所以丟擲一個空指標,但是後面我們使用getString方法的時候,可以獲取到正確值了
下面我們來看看getString的原始碼:
看看awaitLoadedLocked方法:
這個方法什麼都沒幹,就是wait住了,等待喚醒,這個也就和上面的那個notifyAll方法對應起來了。
那麼既然都分析到這裡了,我們乾脆再來看一下常用的commit和apply兩個方法吧:
commit方法:
這裡主要就連個方法,首先來看看commitToMemory方法,這個是整理提交前的map資料結構,用於寫到檔案前的操作準備
整理好了記憶體中的資料,開始寫入到磁碟中了,其實commit從記憶體寫檔案是在當前調運執行緒中直接執行的。那我們再來看看這個寫記憶體到磁碟方法中真正的寫方法writeToFile:
分析完了commit方法,我們總結一下:
如果用commit()方法提交資料,其過程是先把資料更新到記憶體,然後在當前執行緒中寫檔案操作,提交完成返回提交狀態
接下來繼續看apply方法:
這裡也是呼叫了enqueueDiskWrite方法:
其實這個方法是commit和apply公用的,主要用isFromSyncCommit來進行區分的,postWriteRunnalbe==null就是commit方式。如果不為null的話,就是apply方式。
總結一下apply方法:
如果用的是apply()方法提交資料,首先也是寫到記憶體,接著在一個新執行緒中非同步寫檔案,然後沒有返回值。
其實這裡算是分析完了SharePreferences的原始碼,我們可以總結如下:
1、SharedPreferences在例項化時首先會從sdcard非同步讀檔案,然後快取在記憶體中;接下來的讀操作都是記憶體快取操作而不是檔案操作。
2、在SharedPreferences的Editor中如果用commit()方法提交資料,其過程是先把資料更新到記憶體,然後在當前執行緒中寫檔案操作,提交完成返回提交狀態;如果用的是apply()方法提交資料,首先也是寫到記憶體,接著在一個新執行緒中非同步寫檔案,然後沒有返回值。
3、由於上面分析了,在寫操作commit時有三級鎖操作,所以效率很低,所以當我們一次有多個修改寫操作時等都批量put完了再一次提交確認,這樣可以提高效率。
上面算是開了一個小差,順道分析了一下SharePreferences的原始碼,下面來說正題了,我們在上面的例子已經知道了,通過設定Context的檔案建立模式來設定安全性。那麼現在如果我們想讓A應用訪問到B應用的資料,我們可以這麼做:把B應用建立模式改成可讀模式的,那麼A應用就可以操作了,那麼這就有一個問題,A應用可以訪問了,其他應用也可以訪問了,這樣所有的應用都可以訪問B應用的沙盒資料了,太危險了,所以要用另外的一種方式,那麼這時候就要用到shareUserId屬性了,我們只需要將B應用建立方式還是private的,然後A應用和B應用公用一個uid即可,我們下面就來修改一下程式碼,還是上面的那兩個工程,修改他們的AndroidManifest.xml,新增shareUserId即可。
這時候,我們發現把ShareUserIdPlugin中的模式改成private的,A應用任然可以訪問資料了,其實也好理解,他們兩個的uid都相同了,A的檔案就是B的,B的就是A的了,他們兩個沒有沙盒的概念了,資料也是透明的了。
所以這裡我們就看到了,使用shareUserId可以達到多個應用之間的資料透明性互相訪問。
那麼問題來了,假如現在我手機沒有root,想訪問某個應用的沙盒資料,我把自己的應用修改成和他一樣的shareUserId即可。
注意:這裡有一個誤點,就是這裡所有的修改的前提是這個應用的AndroidManifest.xml本身就定義了這個屬性,然後我們可以反編譯看到這個值,把我們自己的shareUserId改成他的就可以了,但是如果這個應用本身沒有這個屬性,那麼這裡就沒有辦法的,為什麼呢,如果要新增,那就是另外一條路了,就是逆向,修改AndroidManifest.xml之後,還需要從新打包在驗證,但是這時候沒必要了,我們也知道有時候回編譯還是很艱難的,如果都能回編譯了,那都不需要這些工作了,所以這裡需要注意的一個前提
那麼修改之後是不是真的可以呢?
答案是肯定不可以的,如果可以的話,那google也太傻比了,其實Android系統中有一個限制,就是說如果多個應用的uid相同的話,那麼他們的apk簽名必須一致,不然是安裝失敗的,如下錯誤:
我們可以檢視PackageManagerService.java原始碼:
看到了,這裡會作比較的,不過這裡我們在深入看一下這個方法的呼叫鏈:
在scanPackageLI方法中呼叫的verifySignaturesLP方法,那麼scanPackageLI方法在哪呼叫的呢?繼續跟蹤:
在這裡,這裡其實是一個檔案監聽類AppDirObserver:
這裡會監聽/data/app目錄,如果有新的檔案增加,就會呼叫scanPackageLI方法,然後在呼叫verifySignaturesLP方法來進行驗證apk檔案資訊。同時我們也發現了,系統的安裝和解除安裝apk的廣播也是在這裡傳送的。果然這裡的知識點還是很多的。
通過上面的分析,我們就知道了,Android中是不允許相同的uid的不同簽名的應用。
那麼我們上面的猜想就是失敗的。及時改成目標應用相同的shareUserId,也是安裝不成功的。
四、知識梳理
1、我們知道如何通過包名來構建一個Context,同時需要注意兩種模式:
Context.CONTEXT_INCLUDE_CODE和Context.CONTEXT_IGNORE_SECURITY
構造完成之後,我們可以訪問資源和執行一些模組程式碼,這些其實不算是一個應用的沙盒概念了,所以不會牽扯到shareUserId的知識點。
2、我們在實驗A應用去訪問B應用的SharedPreferences中的值時,發現建立sp的xml有幾種模式:
Context.MODE_PRIVATE:為預設操作模式,代表該檔案是私有資料,只能被應用本身訪問,在該模式下,寫入的內容會覆蓋原檔案的內容,如果想把新寫入的內容追加到原檔案中。可以使用Context.MODE_APPEND
Context.MODE_APPEND:模式會檢查檔案是否存在,存在就往檔案追加內容,否則就建立新檔案。
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用來控制其他應用是否有許可權讀寫該檔案。
這三種模式的區別,我們最保險的操作就是設定成private的,不過預設也是這種模式
3、我們通過分析SharedPreferences的原始碼,知道這三種模式對應的就是設定xml檔案的訪問許可權,同時我們順便分析了commit,apply,getXXX等方法的實現,也算是對SP的更深入的理解了。其實SharedPreferences內部為了高效率,會第一次載入xml內容到記憶體中的map中,每次getXXX資料的時候,都是直接從map中取,每次儲存資料,是首先儲存到記憶體的map中,呼叫commit和apply方法只有在將資料寫入到磁碟中的區別。apply是非同步的沒有返回值,commit是同步的有返回值
4、我們再次實驗使用shareUserId屬性來做到多個應用之間的資料共享和透明性,同時我們也做了一個猜想就是把自己的shareUserId修改成和目標應用相同來訪問目標應用的資料,但是這個猜想是錯誤的,因為我們通過分析PackageManagerService原始碼知道,Android中是不允許相同的shareUserId的應用有著不同的簽名檔案的,會出現安裝失敗的情況。
五、遺留的問題
關於檔案建立還有一種模式:Context.MODE_MULTI_PROCESS,這個模式其實我們知道是用來多程式訪問的,這裡關於原始碼就不在分析了,在ContextImpl.java中的getSharedPreferences方法中會做一次多程式的資料重新整理載入操作:
不過這個方法已經廢棄了,google建議還是使用ContentProvider比較靠譜,同樣,上面的Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE這兩種模式也是被廢棄了,也算是google為了增強安全性考慮吧。
六、總結
這篇文章就介紹了使用sharedUserId屬性,來實現我們想要的應用資料共享效果,但是引出來的知識點有點多,所以說的就有點多了,不過我們就記住一點:
在建立檔案時,一定要設定成Context.MODE_PRIVATE或者是Context.MODE_APPEND模式,為了做到應用的資料共享可以考慮shareUserId屬性。同時Android中是不允許相同的sharedUserId有著不同簽名的應用的,會出現安裝失敗。
分析的好累呀~~,跪求點贊啦啦~~
相關文章
- Android逆向之旅---SO(ELF)檔案格式詳解Android
- Android中visibility屬性詳解Android
- Android---Android:windowSoftInputMode屬性詳解AndroidWindows
- Android 佈局屬性詳解Android
- Android taskAffinity屬性使用詳解Android
- Android逆向之旅---Hook神器家族的Frida工具使用詳解AndroidHook
- Android屬性動畫詳解(一),屬性動畫基本用法Android動畫
- Android圖文詳解屬性動畫Android動畫
- 【轉】android佈局屬性詳解Android
- Android筆記-2-TextView的屬性詳解Android筆記TextView
- 【Android 動畫】動畫詳解之屬性動畫(三)Android動畫
- 【Android 動畫】動畫詳解之屬性動畫(五)Android動畫
- Android逆向之旅---Android應用的安全的攻防之戰Android
- Android 深入理解Android中的自定義屬性Android
- Android 動畫詳解:屬性動畫、View 動畫和幀動畫Android動畫View
- Rust中的derive屬性詳解Rust
- 【Android】神奇的android:clipChildren屬性Android
- Android 屬性動畫Property Animation(中)Android動畫
- Android XML 屬性AndroidXML
- android屬性動畫Android動畫
- android:screenOrientation屬性Android
- Android 《CardView 屬性》AndroidView
- Android中的layout_gravity與gravity屬性Android
- 記一次Android逆向之旅(入門向)Android
- Android | 玩轉AppBarLayout,設定scrollFlags滑動屬性詳解AndroidAPP
- Android中的onWindowFocusChanged()方法詳解Android
- 詳解Android中AsyncTask的使用Android
- Android中的ANR用法詳解Android
- Android中的Context詳解AndroidContext
- Android 中的 Checkbox 詳解Android
- Android 中的 HandlerThread 詳解Androidthread
- Android 屬性動畫(二)Android動畫
- Android 相關屬性Android
- Android屬性之excludeFromRecentsAndroid
- Android自定義屬性Android
- Android介面基本屬性Android
- Android中RelativeLayout各個屬性的含義Android
- Android逆向之旅---靜態分析技術來破解ApkAndroidAPK