Android通用脫殼機FUPK3
轉自:https://bbs.pediy.com/thread-246117-2.htm
大家好,我是F8LEFT,潛水了這麼久,抽空來發個技術帖子。這次要發的作品是我以前寫的一個脫殼機FUPK3,這個脫殼機的思路應該是以前沒人放過的,這裡我不私藏了,放出來給大家來評評。
Android程式碼是開源的,那麼通過直接修改Android原始碼,把執行時的所有dex資料dump出來,不就可以實現一個通用的脫殼機了嗎?FUPk3就是基於上面的思路來實現的。FUPK3需要修改Android原始碼,匯出資料介面.不過,最為核心的脫殼操作是在一個so中執行的,脫殼時應用會自動載入該so,這樣好處在於可以通過替換這個so來達到動態修bug的效果。
- 程式碼注入
程式啟動後讀取配置檔案/data/local/tmp/FUpk3.txt,如果命中脫殼的配置就會直接載入so /data/local/tmp/libFupk3.so,進行脫殼。修改Android原始碼1
2
3
4
5
6
7
8
try
{
UpkConfig config
=
new UpkConfig();
if
(config.load() && config.mTargetPackage.equals(data.info.getPackageName())) {
Fupk upk
=
new Fupk(config.mTargetPackage);
upk.unpackAfter(
10000
);
}
} catch (Throwable t) {
}
-
cookie
脫殼需要知道app當前載入的所有dex檔案。每一個被載入到記憶體中的dex檔案都會被記錄為一個cookie值,存放在程式的loader中,所有類的載入都需要通過該loader進行。Java層的loader可能會比較複雜,比如會用到雙親委派機制等,不好遍歷。但是,到了Jni層,一切都變得簡單起來,在native中,會通過一個Hash表來儲存所有dex檔案。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/
/
每個開啟的dex檔案都會儲存到gDvm.userDexFiles中
static void addToDexFileTable(DexOrJar
*
pDexOrJar) {
u4
hash
=
(u4) pDexOrJar;
void
*
result;
dvmHashTableLock(gDvm.userDexFiles);
result
=
dvmHashTableLookup(gDvm.userDexFiles,
hash
, pDexOrJar,
hashcmpDexOrJar, true);
dvmHashTableUnlock(gDvm.userDexFiles);
if
(result !
=
pDexOrJar) {
ALOGE(
"Pointer has already been added?"
);
dvmAbort();
}
pDexOrJar
-
>okayToFree
=
true;
}
因此,想要知道當前apk載入了那些dex就簡單多了,只需要遍歷這個userDexFile這個Hashtable就好了。直接在Android原始碼中匯出介面。
1
2
3
4
/
/
@F8LEFT exported function
HashTable
*
dvmGetUserDexFiles() {
return
gDvm.userDexFiles;
}
然後,直接在so裡面呼叫介面,就可以取出所有dex檔案的記憶體佈局。
1
2
3
4
5
auto fn
=
(HashTable
*
(
*
)())dlsym(libdvm,
"dvmGetUserDexFiles"
);
if
(fn
=
=
nullptr) {
goto bail;
}
gDvmUserDexFiles
=
fn();
-
dex重建
dex檔案被載入起來後,一些值就不會再被用到,如檔案頭sig,資料偏移等,部分殼會把這些資料抹除。這些資料需要直接從應用的執行時資料 DvmDex, ClassObject, MethodObject等取出來,並進行重建。還有一些常量資料,StringPool,TypePool等,這些是不會加密的,直接dump出來。1
2
3
4
5
6
7
8
9
10
11
bool
DexDumper::fixDexHeader() {
DexFile
*
pDexFile
=
mDvmDex
-
>pDexFile;
mDexHeader.stringIdsOff
=
(u4) ((u1
*
) pDexFile
-
>pStringIds
-
(u1
*
) pDexFile
-
>pHeader);
mDexHeader.typeIdsOff
=
(u4) ((u1
*
) pDexFile
-
>pTypeIds
-
(u1
*
) pDexFile
-
>pHeader);
mDexHeader.fieldIdsOff
=
(u4) ((u1
*
) pDexFile
-
>pFieldIds
-
(u1
*
) pDexFile
-
>pHeader);
mDexHeader.methodIdsOff
=
(u4) ((u1
*
) pDexFile
-
>pMethodIds
-
(u1
*
) pDexFile
-
>pHeader);
mDexHeader.protoIdsOff
=
(u4) ((u1
*
) pDexFile
-
>pProtoIds
-
(u1
*
) pDexFile
-
>pHeader);
mDexHeader.classDefsOff
=
(u4) ((u1
*
) pDexFile
-
>pClassDefs
-
(u1
*
) pDexFile
-
>pHeader);
return
true;
}
對於Method的重構要複雜得多,每一代加固的發展重點都是增強對Method的CodeItem的加密。從整體加密到函式抽取到最新的函式VMP,還原難度也越來越高。VMP暫且不說,函式抽取本身還是可以攻破的。有些殼做得非常複雜,會在函式執行時還原始碼,執行完後又加密回去。針對這種情況,可以看到,在程式執行時的某一個時刻,dex的部分資料肯定是還原的,那麼我們可以通過不斷地手動構造程式碼,來遍歷觸發這些還原點,同時進行資料提取,最終完成修復。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
在上面,直接在Android原始碼中構造了脫殼用到的介面 gFupk,它提供了4個void*的空間來存放臨時資料,與一個可以替換的介面方法fupkExportMethod。這樣,只需要手動呼叫fupkInvokeMethod方法,程式就會走函式正常的執行流程,從dvmInvokeMethod開始,進入dvmInterpret中,並最終呼叫完gFupk.ExportMethod後直接退出Interpret。這樣當我們手動呼叫函式時,既可以讓殼還原出原始程式碼,又不會影響到程式本身的穩定性。
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
這樣,所有加密的資料都提取出來了,直接進行組合,以加密的方式dump出來。
1 2 3 4 5 6 7 8 9 10 |
|
- dex修復
上面dump下來的dex檔案是非標準的,可能存在部分的class資料不合法,並且一些軟體不認。所以需要一個寫一個修復的server來跳過非法的資料。server主要修改了baksmali與smali的程式碼,自動跳過無法反編譯的類。 -
其他
Android加固發展到現在,經歷了好幾個大版本改動。最初的是dex整體加固,現在的是VMP加固,中間出了不少非常不錯的脫殼機,其中最為經典的有2個:ZjDroid與dexHunter,這兩個都是開源的,並且寫得非常好,即使是放到今天來看,也具有相當的參考價值,想要學習脫殼的同學們可以拜讀一下。另外,FUpk3是執行在dalvik上的,那麼要在art下脫殼怎麼辦呢?道理還是一樣的,只是art下會複雜很多, 跑解析的跑編譯的都有,修復起來需要記錄很多資料,這裡由於某些原因就不公開art下的脫殼機了。 -
寫在最後
新手想要入門,需要什麼?1. 一臺谷歌親兒子(nexus) 2. 一個Ubuntu系統 3. 一套完整的Android原始碼。平時有事沒事可以多看看原始碼,刷刷機之類的。Android平臺與Windows上的不同,到目前為止,Android上系統的優秀的教材實在是少,如果沒人手把手地帶入門的話,基本上就是在白做功,幸好大部分難點的解決方案答案都可以在原始碼裡面找到,多熟悉一下總會有好處的。
7.https://github.com/F8LEFT/FUPK3
看雪上的md外掛怪怪的,大家去我的github上看吧,歡迎各種star與follow,演示視訊在這裡 https://pan.baidu.com/s/1HH_-TQGca1NLoSqzvOPB3Q 密碼:izm3