之前在逍遙模擬器安裝Xposed框架后,提示需要在線下載文件,為了下載這個文件,便把加速器APP安裝到模擬器,然后發現它有Root檢測,在模擬器中運行不起來。

為了能在逍遙模擬器上使用這個加速器APP,便有了以下研究。

本次實驗所應用到的工具:

1.jadx

2.MT管理器

3.frida

4.objeciton

5.JeB

6.IDA

分析過程如下:

1.打開APP,發現APP存在環境檢測。

2.利用jadx查看AndroidManifest.xml文件,找到并進入APP第一個啟動的Activity。

在StartActivity發現檢測代碼,從代碼中可以看出這里有非常多的if()語句用來檢測環境,判斷為真后就彈框,并return。

3.看看這些檢測函數的代碼邏輯,看它都做了什么。

DeviceUtils.a(),通過代碼可以知道,這是一個通過特定文件來檢測Root的函數。

分析下EmulatorDetectUtil.a(startActivity),從函數名上看,它的作用應該是檢測模擬器,它返回了a.a(context)||Native detect()。

看看a.a(context),它return c(context) || b(context) || b() || a(),

a()代碼邏輯是,如果/proc/cpuinfo文件中含有”intel”或”AMD”字符串,就return true,否則return false。(這樣檢測是因為正常手機不會是intel或AMD型號的CPU。)

c(context) , b(context),b() 這三個函數調用Native中的PropertiesGet.a

(String),該函數使用_system_property_get()來獲取系統屬性值來判斷是否為模擬器。

函數通過以下特性判斷是否為模擬器:

ro.kernel.qemu該值在模擬器中為1,通常在正常手機中沒有該屬性。

ro.product.model:該值在模擬器中為sdk,通常在正常手機中它的值為手機的型號。

4.此外APP還進行了Xposed,代理檢測等,如何繞過這些檢測呢?

解決上面的檢測我有兩個方案:

① 用frida Hook對應的函數,統一返回false。

② 修改對應的smali語句,將eqz修改成nez。

這里我采用第二個方案,因為可持久化。

5.以DeviceUtils.a()為例,實現第二個方案,過Root檢測。

找到檢測函數的smali,定位到關鍵地方if-eqz p1,:cond_30

意為如果p1寄存器的值為0,則跳轉到cond_30,而cond_30后的代碼就是彈框及retun語句。

這里將eqz改成nez即可繞過檢測。

如遇到其他檢測,如法炮制即可。

6.完成以上的環境檢測繞過后,APP發生了閃退,估計是在某個地方把APP Kill了。

這個Kill函數在哪呢?簡單的看了下代碼,沒發現Kill函數。那咋辦?

用神器objection Hook下常見的Kill函數,打印出調用棧不就知道它在哪了嗎。

常見的APP退出函數有:

1.KillProcess(),android.os.Process.killProcess(android.os.Process.myPid());

2.System.exit(),Java.lang.System.exit()

Hook億下

很明顯com.speed.speed_library.module.main.MainActivity.n 調用了exit()。

7.定位到n()看看,哇塞這么多exit()。

從代碼中可以看出這個n()也是用來檢測環境的,環境不對就會執行exit(),退出APP。

解決它也很簡單,刪除com.speed.speed_library.module.main.MainActivity.onCreate中調用n()語句就行。(PS:jadx反編譯出來的onCreate()中并沒有調用n()的語句,而對應的smali中有invoke n(),smali和objection的結果都能說明jadx靜態反編譯出來的結果并不準確)

8.刪除Oncreate中的n()。

9.再次打開APP,又顯示”未知錯誤”。

10.在源碼中搜索不到”未知錯誤”,猜測它可能來自服務端,抓包看看。

好像是base64?解碼后結果是亂碼。估計是對稱加密,hook下系統的加解密函數,看下哪里有調用。

11.一頓搜索,Hook后定位到這個可疑的函數,它非常有可能是用來解密的。

12.Hook這個函數看看,它的入參str是Respone Body,str2,str3估計是Key和IV,

返回值是none?!怎么回事?估計是key,iv的原因,因為它們的結尾是很明顯的”error”,分析下它們是怎么來的吧。

13.定位到調用解密函數的地方,結合之前的分析可以得出:

a4=str1=Respone Body

st2=str3=g.f5211b.a().getResources().getString(R.string.d_key_three) + g.f5211b.W() + DeviceUtils.ddmm(g.f5211b.a()).

str2,str3都是由三個字符串拼接而成,前兩個字符串是常量,分別為”MNEI“

和“pTzYn”,第三個字符串則由Native函數ddmm()返回。

14.用IDA看看ddmm(),通過流程圖和偽代碼可以看出,函數邏輯是,如果getSignature()為true就返回vAsvTho,否則返回error。

15.ddmm()返回值的問題可以這樣解決:在smali中定義一個const-string v6替換ddmm()的返回值即可。

16.在Hook這個解密函數看看。數據已經解密出來了,可以確定解密函數的IV和KEY就是

”MNEIpTzYnvAsvTho”

17.終于能正常打開應用了!廢了不少功夫。

18.如何能一直使用呢?

1)購買VPN?下策。這個梯子經常連不上Google。

2)每日簽到可以獲取15分鐘的使用時間,分析簽到流程,然后一直簽到?這太麻煩了。

3)新用戶可以免費使用兩小時,一直當新用戶?這個可以,兩個小時可以滿足我的需求了。

那方案三該如何實現呢?

19.通過分析”設備登陸”的數據包,可以得知APP是通過DeviceId來識別用戶。

20.分析了DeviceId是如何生成的,生成過程中調用了很多函數,這里省略了一些函數的圖。

33DFDFER21.B75VGF5XIY.EB2FBCB874=

com.speed.speed_library.b.e.f5202a.a(g.f5211b.a()).a()=

e.a.a(v2).a()=kotlin.h.g.a(kotlin.h.g.a(v0, "\r", "", false, 4, null), "\n", "", false, 4, null)=

g.a(arg0, arg1, arg2, ((boolean)(((int)arg3))))=

c.a(g.a(((CharSequence)arg19), new String[]{arg20}, arg22, 0, 4, null), ((CharSequence)arg21), null, null, 0, null, null, 62, null)=

c.a(arg4, arg5, v12, v0, (arg11 & 8) == 0 ? arg8 : -1, v2, arg10)=

((StringBuilder)c.a(arg9, ((Appendable)new StringBuilder()), arg10, arg11, arg12, arg13, arg14, arg15)).toString() =

arg3

21.DeviceId的生成過程比較復雜,其實不用深究DeviceId的生成,可以寫一個返回String的函數生成對應的smali語句,再去替換原來的smali即可,如下圖。

22.重新登陸APP,發現已經是一個新的ID,VIP時間也增加了兩個小時。

分析總結

個人感覺 JEB比jadx準確,建議使用JEB進行分析。

APP 對JAVA代碼進行了混淆,并在Java和Native層,分別對Root,模擬器,Xposed框架,網絡代理等進行了常規檢測,一定程度提高了APP的安全性。

APP可以進行加固或字符串加密,動態注冊,ollvm等技術手段來進一步提高APP的安全性,防止重打包,破解等。

出于業務安全考慮,最好將識別用戶的關鍵數據DeviceId的生成算法寫入SO中,并使用自定義加密算法,將關鍵數據進行加密后傳輸,這樣能有效提高安全性,并減少損失。

僅提供技術交流,不對任何成員利用技術文章或者檢測工具造成任何理論上的或實際上的損失承擔責任。