Youpk: 又一款基於ART的主動調用的脫殼機
原理
Youpk是一款針對Dex整體加固+各式各樣的Dex抽取的脫殼機
基本流程如下:
- 從內存中dump DEX
- 構造完整調用鏈, 主動調用所有方法並dump CodeItem
- 合併 DEX, CodeItem
從內存中dump DEX
DEX文件在art虛擬機中使用DexFile對象表示, 而ClassLinker中引用了這些對象, 因此可以採用從ClassLinker中遍歷DexFile對象並dump的方式來獲取.
//unpacker.cc
std::list<const DexFile*> Unpacker::getDexFiles() {
std::list<const DexFile*> dex_files;
Thread* const self = Thread::Current();
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
ReaderMutexLock mu(self, *class_linker->DexLock());
const std::list<ClassLinker::DexCacheData>& dex_caches = class_linker->GetDexCachesData();
for (auto it = dex_caches.begin(); it != dex_caches.end(); ++it) {
ClassLinker::DexCacheData data = *it;
const DexFile* dex_file = data.dex_file;
dex_files.push_back(dex_file);
}
return dex_files;
}
另外, 爲了避免dex做任何形式的優化影響dump下來的dex文件, 在dex2oat中設置 CompilerFilter 爲僅驗證
//dex2oat.cc
compiler_options_->SetCompilerFilter(CompilerFilter::kVerifyAtRuntime);
構造完整調用鏈, 主動調用所有方法
-
創建脫殼線程
//unpacker.java public static void unpack() { if (Unpacker.unpackerThread != null) { return; } //開啓線程調用 Unpacker.unpackerThread = new Thread() { @Override public void run() { while (true) { try { Thread.sleep(UNPACK_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); } if (shouldUnpack()) { Unpacker.unpackNative(); } } } }; Unpacker.unpackerThread.start(); }
在脫殼線程中遍歷DexFile的所有ClassDef
-
//unpacker.cc for (; class_idx < dex_file->NumClassDefs(); class_idx++) {
解析並初始化Class
-
//unpacker.cc mirror::Class* klass = class_linker->ResolveType(*dex_file, dex_file->GetClassDef(class_idx).class_idx_, h_dex_cache, h_class_loader); StackHandleScope<1> hs2(self); Handle<mirror::Class> h_class(hs2.NewHandle(klass)); bool suc = class_linker->EnsureInitialized(self, h_class, true, true);
主動調用Class的所有Method, 並修改ArtMethod::Invoke使其強制走switch型解釋器
-
//unpacker.cc uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(method->GetShorty()); if (!method->IsStatic()) { args_size += 1; } JValue result; std::vector<uint32_t> args(args_size, 0); if (!method->IsStatic()) { mirror::Object* thiz = klass->AllocObject(self); args[0] = StackReference<mirror::Object>::FromMirrorPtr(thiz).AsVRegValue(); } method->Invoke(self, args.data(), args_size, &result, method->GetShorty()); //art_method.cc if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this) || (Unpacker::isFakeInvoke(self, this) && !this->IsNative()))) { if (IsStatic()) { art::interpreter::EnterInterpreterFromInvoke( self, this, nullptr, args, result, /*stay_in_interpreter*/ true); } else { mirror::Object* receiver = reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr(); art::interpreter::EnterInterpreterFromInvoke( self, this, receiver, args + 1, result, /*stay_in_interpreter*/ true); } } //interpreter.cc static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind;
在解釋器中插樁, 在每條指令執行前設置回調
-
//interpreter_switch_impl.cc // Code to run before each dex instruction. #define PREAMBLE() \ do { \ inst_count++; \ bool dumped = Unpacker::beforeInstructionExecute(self, shadow_frame.GetMethod(), \ dex_pc, inst_count); \ if (dumped) { \ return JValue(); \ } \ if (UNLIKELY(instrumentation->HasDexPcListeners())) { \ instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), shadow_frame.GetMethod(), dex_pc); \ } \ } while (false)
在回調中做針對性的CodeItem的dump, 這裏僅僅是簡單的示例了直接dump, 實際上, 針對某些廠商的抽取, 可以真正的執行幾條指令等待CodeItem解密後再dump
-
//unpacker.cc bool Unpacker::beforeInstructionExecute(Thread *self, ArtMethod *method, uint32_t dex_pc, int inst_count) { if (Unpacker::isFakeInvoke(self, method)) { Unpacker::dumpMethod(method); return true; } return false; }
合併 DEX, CodeItem
將dump下來的CodeItem填充到DEX的相應位置中即可. 主要是基於google dx工具修改.
參考鏈接
FUPK3: https://bbs.pediy.com/thread-246117.htm
FART: https://bbs.pediy.com/thread-252630.htm
刷機
- 僅支持pixel 1代
- 重啓至bootloader:
adb reboot bootloader
- 解壓 Youpk_sailfish.zip 並雙擊
flash-all.bat
百度雲:https://pan.baidu.com/s/1ySSy2vNW5TyFjH1LNAcd5w
提取碼:vseh
使用方法
-
該工具僅僅用來學習交流, 請勿用於非法用途, 否則後果自付!
-
配置待脫殼的app包名, 準確來講是進程名稱
adb shell "echo cn.youlor.mydemo >> /data/local/tmp/unpacker.config"
啓動apk等待脫殼
-
每隔10秒將自動重新脫殼(已完全dump的dex將被忽略), 當日志打印unpack end時脫殼完成
-
pull出dump文件, dump文件路徑爲
/data/data/包名/unpacker
adb pull /data/data/cn.youlor.mydemo/unpacker
調用修復工具 dexfixer.jar, 兩個參數, 第一個爲dump文件目錄(必須爲有效路徑), 第二個爲重組後的DEX目錄(不存在將會創建)
-
java -jar dexfixer.jar /path/to/unpacker /path/to/output
適用場景
- 整體加固
- 抽取:
- nop佔坑型(類似某加密)
- naitve化, 在<clinit>中解密(類似早期阿里)
- goto解密型(類似新版某加密?najia): https://bbs.pediy.com/thread-259448.htm
常見問題
- dump中途退出或卡死,重新啓動進程,再次等待脫殼即可
- 當前僅支持被殼保護的dex, 不支持App動態加載的dex/jar