原作者:Kai Lu
譯者:BlackTrace
最近,我們發現了一個新的android rootnik 惡意app,它使用了開源的Android root漏洞利用工具和來自dashi root工具里的MTK root方案在Android設備上獲取root權限。這個惡意軟件把自己偽裝成文件管理app(file helper app),其次還使用了很高級的反調試和反hook技來防止被逆向工程。它還使用了多個dex的方案去加載第二個dex文件。在成功獲取了設備的root權限后,rootnik惡意app能執行多個惡意行為,例如,app和廣告的推廣,色情的推廣,在主屏幕上創建快捷方式,靜默安裝app,推送通知等。在這個博客,我將深度分析這個惡意app。
快速瀏覽這個惡意app
這個惡意app 看起來像你android手機里管理你的文件或者其他存儲的資源的合法文件管理器(file helper app)。

圖1. 惡意app的安裝后的圖標

圖2. 惡意app的界面
我們來反編譯這個apk文件,結果如圖3

圖3. 反編譯后的文件
它的包名是com.web.sdfile。首先讓我們來看一下它的AndroidManifest.xml文件。

圖4. 這個app里面的AndroidManifest.xml文件
我們并沒有在main activity中找到圖4中的com.sd.clip.activity.FileManagerActivity, service class, broadcast class。很明顯,這個文件管理器(file helper app)的主要邏輯代碼并沒有在這個classes.dex。經過分析后返現,這個惡意app使用了multidex方案去動態加載和運行第二個dex。
Rootnik是如何工作的?
I. Rootnik工作流程
下圖是這個Android rootnik malware的工作流程

圖5. Android rootnik malware的工作流程
II. 深度分析第一個dex文件
下圖是SecAppWrapper類的代碼片段

圖6. SecAppWrapper類的代碼片段
這個執行流程如下
靜態代碼塊 -> attachBaseContext -> onCreate
靜態代碼快加載動態鏈接庫 libSecShell.so到assets文件夾,并進入native層執行多個反調試操作,解密第二個dex文件,然后使用multidex方案去加載解密后的第二個dex文件,解密后的第二個dex文件,才是真正的程序主邏輯。
類Dexinstall其實是類MultiDex,關于Multidex的資料https://android.googlesource.com/platform/frameworks/multidex/+/d79604bd38c101b54e41745f85ddc2e04d978af2/library/src/android/support/multidex/MultiDex.java
這個程序調用Dexinstall里面的install方法去加載第二個dex文件。這個install方法是在native層調用的。
圖7 安裝第二個dex
這個程序在attachBaseContext方法里加載了第二個dex的運行入口class com.sd.clip.base.MyApplication。這個Helper類的attach是native方法。 接著程序運行了類com.sd.clip.base.MyApplication里的onCreate方法。好了就這些了,第一個dex文件相當簡單的。接下來,我們將深度分析native層的代碼,這是非常復雜和棘手的。
III.native層的代碼
上面說過,native層使用了一些高級的反調試和反hook技術,并且還使用了幾種解密算法對一些字節數組進行解密以得到純文本字符串。
下圖8是libSecShell.so的導出函數。它混淆了函數名使分析變得更加困難,更加費勁。

圖8.libSecShell.so的導出函數
所有的反調試代碼都在JNI_Onload函數里面
在上一節說過,java層中的Helper類的attach方法是native方法。這個程序是在native層動態注冊這個方法。下圖是程序在native層動態注冊這個native方法的ARM匯編代碼片段。

圖9 動態注冊native方法
RegisterNatives方法是用來注冊native方法的,這個方法的原型如下
jint RegisterNatives(JNIEnv *env,jclass clazz, const JNINativeMethod* methods,jint nMethods)
JNINativeMethod結構定義如下
typedef struct { const char name; const char signature; void* fnPtr; } JNINativeMethod; 第一個變量name是指在java層調用的name,這里是"attach",第三個變量是fnPtr空指針是一個函數指針,指向要動態注冊的C代碼的函數
我們需要找到這個反調試代碼的位置并且繞過它。還要分析第二個dex文件是如何解密的,并且在內存中dump出解密后的第二個dex文件。
讓我們在IDA中看一下接下來的代碼

圖10. 反調試代碼附近
經過我們的深度分析,在地址0xF832這里的這里的指令是跳轉到地址loc_F924,跟過去后,我們找到了反調試代碼。

圖11.反調試代碼的位置
在p7E7056598F77DFCC42AE68DF7F0151CA()里執行了反調試的操作。
在IDA的graphic中看一下執行流程,如下圖,可以看出,這個流程是相當復雜的。

圖12.反調試的執行流程
下面是這個惡意app里使用的一些反調試和反hook的方法。
1. 檢測一些流行的hook框架,如 Xposed, substrate, adbi, ddi, dexposed。一旦發現,流行hook框架hook了它,它將結束相關的進程。

圖14. 查找hook框架
2. 使用一種多進程ptrace來實現反調試,這還是有點棘手的。在這里我們不提供這種反調試的實現機制的詳細分析,但是我們會給一些簡單的解釋。
我們在這里看見了名字為com.web.sdfile的兩個進程。

圖15. 進程名為com.web.sdfile的兩個進程
下圖是多進程反調試代碼片段

圖16. 反調試代碼片段
3. 這個程序還使用了inotify來監控內存和主進程的pagemag,當內存dump不完整,這兩個進程將使用pipe互相通知對方。
總之,這些反調試和反hook給逆向工程創造了很大的障礙,所以我們繞過這些反調試和反hook方法是我們的首要任務。
讓我們嘗試繞過它們。
在圖10中,在偏移0x0000F832的指令是跳轉到loc_F924,然后程序執行反調試代碼。當我們動態調試的時候,我們可以修改一些寄存器的值或者是ARM指令來改變執行流程。當程序運行到偏移0xF828的SUBS R1, R0, #0時,我們可以把R0寄存器的值修改為非0值,使得指令BNE loc_F834的條件成立。這將允許這個程序跳轉到loc_F834。

圖17. 如何繞過反調試代碼
接下來,我們動態調試它,繞過反調試并且dump解密后的第二個dex文件。動態調試過程如下

圖18 修改R0寄存器的值為非0值

圖19 跳轉到local_75178834
接著跳轉到local_751788D8,如下

圖20. 解密第二個dex的函數
函數p34D946B85C4E13BE6E95110517F61C41就是解密函數,R0寄存器指向內存中存儲的加密的dex文件,R1寄存器里存著這個文件的大小,文件大小等于0x94A3E(608830)。解密dex文件是在apk包里的assets文件夾下的secData0.jar文件。下圖是secData0.jar

圖21.這個文件secData0.jar是在assets文件夾下。

圖22. 內存中解密后的第二個apk的內容
我們現在把解密后的文件dump到decrypt.dump中。
這個解密后的文件是zip的格式,它包含了第二個dex文件。解密后,這個程序解壓這個解密后的apk文件到dex文件。方法p3CBBD6F30D91F38FCD0A378BE7E54877是用來解壓這個文件的。 接下來,unk_75176334方法調用com.secshell.shellwrapper.DexInstall類的java方法install加載第二個dex。

圖23. 解壓解密后的apk文件并且加載第二個dex文件

圖24. 通過JNI調用方法安裝
這里,我們完成了native層的分析并且得到解密后的第二個apk文件,然后我們將分析這個apk文件在這個博客的part II 。
Native層的secData0.jar的解密方法
int __fastcall sub_7518394C(int result, _BYTE *a2, int a3)
{
int v3; // r1@1
int v4; // r3@5
unsigned int v5; // r3@7
int v6; // r6@7
int v7; // r5@7
char v8; // r2@8
int v9; // r4@9
int v10; // r3@9
int v11; // r7@11
_BYTE *v12; // r6@12
int v13; // r4@13
_BYTE *v14; // r1@15
int v15; // [sp+0h] [bp-138h]@1
int v16; // [sp+4h] [bp-134h]@1
_BYTE *v17; // [sp+8h] [bp-130h]@1
int v18; // [sp+10h] [bp-128h]@5
char v19[256]; // [sp+1Ch] [bp-11Ch]@6
int v20; // [sp+11Ch] [bp-1Ch]@1
v17 = a2;
v16 = result;
v20 = _stack_chk_guard;
v15 = a3;
v3 = 0;
if ( result <= 0x1FFFF )
{
v3 = 0x20000 - result;
if ( 0x20000 - result > a3 )
v3 = a3;
v15 = a3 - v3;
if ( v3 > 0 )
{
v18 = dword_751AF650;
v4 = 0;
do
{
v19[v4] = v4;
++v4;
}
while ( v4 != 256 );
v5 = 0;
v6 = 0;
v7 = 0;
do
{
v6 = (*(_BYTE *)(v18 + v5) + (unsigned __int8)v19[v7] + v6) & 0xFF;
v8 = v19[v7];
v5 = (v5 + 1) & -((v5 + 1 <= 0xF) + ((v5 + 1) >> 31));
v19[v7] = v19[v6];
v19[v6] = v8;
++v7;
}
while ( v7 != 256 );
v9 = 0;
result = 0;
v10 = 0;
while ( v9 != v16 )
{
v10 = (v10 + 1) & 0xFF;
v11 = (unsigned __int8)v19[v10];
++v9;
result = (v11 + result) & 0xFF;
v19[v10] = v19[result];
v19[result] = v11;
}
v12 = v17;
do
{
v10 = (v10 + 1) & 0xFF;
v13 = (unsigned __int8)v19[v10];
result = (result + v13) & 0xFF;
v19[v10] = v19[result];
v19[result] = v13;
*v12++ ^= v19[(v13 + (unsigned __int8)v19[v10]) & 0xFF];
}
while ( v12 != &v17[v3] );
}
}
if ( v15 > 0 )
{
v14 = &v17[v3];
result = (int)v14;
do
*v14++ ^= 0xACu;
while ( (signed int)&v14[-result] < v15 );
}
if ( v20 != _stack_chk_guard )
result = ((int (*)(void))unk_75173E48)();
return result;
}
第一次翻譯,難免會有錯誤,還請指正。萬分感謝 --BlackTrace
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/204/