本文为看雪论坛精华文章
看雪论坛作者ID:FeJQ
一、 背景
Android系统的换代升级非常快,在4.4版本后舍弃了以往的dalvik虚拟机,取而代之的是更高效的art虚拟机。
在android脱壳领域,有许多前辈共享了其思路、代码,使本人得已站在巨人的肩膀上进行学习、研究。
如:
https://bbs.pediy.com/thread-252630.htm中
(2) hanbingle大佬:
https://bbs.pediy.com/user-home-632473.htm
(3) hanbingle大佬的开源的基于art的主动调用方案:
https://github.com/hanbinglengyue/FART
package dalvik.system.DexFile;
String[] getClassNameList(DexFile dexFile,Object mCookie);
二、 原理
// interpreter.cc
static inline JValue Execute(
Thread *self,
const DexFile::CodeItem *code_item,
ShadowFrame &shadow_frame,
JValue result_register,
bool stay_in_interpreter = false) SHARED_REQUIRES(Locks::mutator_lock_){
if (kInterpreterImplKind == kMterpImplKind)
{
if (transaction_active)
{
// No Mterp variant - just use the switch interpreter.
return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register,
false);
}
else if (UNLIKELY(!Runtime::Current()->IsStarted()))
{
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register,
false);
}
else
{
while (true)
{
// Mterp does not support all instrumentation/debugging.
if (MterpShouldSwitchInterpreters())
{
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register,false);
}
bool returned = ExecuteMterpImpl(self, code_item, &shadow_frame, &result_register);
if (returned)
{
return result_register;
}
else
{
// Mterp didn't like that instruction. Single-step it with the reference interpreter.
result_register = ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame,result_register, true);
if (shadow_frame.GetDexPC() == DexFile::kDexNoIndex)
{
// Single-stepped a return or an exception not handled locally. Return to caller.
return result_register;
}
}
}
}
}
else if (kInterpreterImplKind == kSwitchImplKind)
{
if (transaction_active)
{
return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register,
false);
}
else
{
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register,
false);
}
}
else
{
DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
if (transaction_active)
{
return ExecuteGotoImpl<false, true>(self, code_item, shadow_frame, result_register);
}
else
{
return ExecuteGotoImpl<false, false>(self, code_item, shadow_frame, result_register);
}
}
...
}
// interpreter.cc
//static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind;
static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind;
template <bool do_access_check, bool transaction_active>
JValue ExecuteSwitchImpl(Thread *self, const DexFile::CodeItem *code_item,
ShadowFrame &shadow_frame, JValue result_register,
bool interpret_one_instruction) SHARED_REQUIRES(Locks::mutator_lock_){
ArtMethod *artMethod = shadow_frame.GetMethod();
bool isFakeInvokeMethod = Aupk::isFakeInvoke(self, artMethod);
if (!isFakeInvokeMethod && strstr(PrettyMethod(shadow_frame.GetMethod()).c_str(), "<clinit>") != nullptr)
{
const DexFile *dexFile = artMethod->GetDexFile();
char feature[] = "ExecuteSwitchImpl";
Aupk::dumpDexFile(dexFile, feature);
Aupk::dumpClassName(dexFile, feature);
}
}
json root;
for (size_t i = 0; i < dexFile->NumClassDefs(); i++)
{
const DexFile::ClassDef &classDef = dexFile->GetClassDef(i);
const char *descriptor = dexFile->GetClassDescriptor(classDef);
root["count"] = i + 1;
root["data"][i] = descriptor;
}
ofstream oFile;
oFile.open(fileName, ios::out);
if (oFile.is_open())
{
oFile << root;
oFile.close();
LOG(INFO) << "AUPK->dump class name:success:" << fileName;
}
// aupk_method.cc
ArtMethod *jMethodToArtMethod(JNIEnv *env, jobject jMethod)
{
ScopedFastNativeObjectAccess soa(env);
ArtMethod *method = ArtMethod::FromReflectedMethod(soa, jMethod);
return method;
}
// 获取目标类的所有构造函数
Constructor constructors[] = klass.getDeclaredConstructors();
for (Object constructor : constructors)
{
String methodName=klass.getName()+constructor.toString();
classInfo.methodMap.put(methodName,constructor);
count++;
}
// 获取目标类的所有成员函数
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods)
{
String methodName=klass.getName()+method.toString();
classInfo.methodMap.put(methodName,method);
count++;
}
// 通过类名加载类,得到Class对象
Class klass=getClassLoader().loadClass(className);
小结:首先通过类名加载并获取类对象,再通过类对象获取类中所有的方法,进而将方法传入native层,转为artMethod,并进行主动调用。
void Aupk::aupkFakeInvoke(ArtMethod *artMethod) SHARED_REQUIRES(Locks::mutator_lock_)
{
if (artMethod->IsAbstract() || artMethod->IsNative() || (!artMethod->IsInvokable()) || artMethod->IsProxyMethod())
{
return;
}
JValue result;
Thread *self = Thread::Current();
uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(artMethod->GetShorty());
if (!artMethod->IsStatic())
{
args_size += 1;
}
std::vector<uint32_t> args(args_size, 0);
if (!artMethod->IsStatic())
{
args[0] = 0x12345678;
}
artMethod->Invoke(self, args.data(), args_size, &result, artMethod->GetShorty());
}
需要注意的是,函数的第一个参数要赋值为除0以外的任何值,因为后面会判断 CHECK(arg[0]!=nullptr)。
//art_method.cc
void ArtMethod::Invoke(Thread *self, uint32_t *args, uint32_t args_size, JValue *result,const char *shorty)
{
...
bool isFakeInvokeMethod = Aupk::isFakeInvoke(self, this);
if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this) || isFakeInvokeMethod))
{
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);
}
}
else
{
...
if (!IsStatic())
{
(*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
}
else
{
(*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
}
...
}
}
void EnterInterpreterFromInvoke(Thread *self, ArtMethod *method, Object *receiver,
uint32_t *args, JValue *result,
bool stay_in_interpreter){
...
if (LIKELY(!method->IsNative()))
{
JValue r = Execute(self, code_item, *shadow_frame, JValue(), stay_in_interpreter);
if (result != nullptr)
{
*result = r;
}
}
else
{
...
}
...
}
bool firstInsIsGoto = false;
if (isFakeInvokeMethod)
{
inst_data = inst->Fetch16(0);
// 当执行的为方法的第一条指令时
if (dex_pc == 0)
{
if (inst->Opcode(inst_data) == Instruction::GOTO ||
inst->Opcode(inst_data) == Instruction::GOTO_16 ||
inst->Opcode(inst_data) == Instruction::GOTO_32)
{
// 如果第一条指令为goto,则继续执行
firstInsIsGoto = true;
}
else
{
// 如果第一条指令不是goto,则直接dumpMethod
char feature[] = "ExecuteSwitchImpl";
Aupk::dumpMethod(method, feature);
return JValue();
}
}
if(inst->Opcode(inst_data) == Instruction::INVOKE_STATIC)
{
if (firstInsIsGoto)
{
// 如果指令为invoke-static,且第一条指令为goto,则等invoke-static执行完毕后再dumpMethod
DoInvoke<kStatic, false, false>(self, shadow_frame, inst, inst_data, &result_register);
char feature[] = "ExecuteSwitchImpl";
LOG(INFO)<<"AUPK->ExecuteSwitchImpl goto:"<<PrettyMethod(shadow_frame.GetMethod());
Aupk::dumpMethod(method, feature);
return JValue();
}
}
}
// aupk.cc
static void Aupk_native_fakeInvoke(JNIEnv *env, jclass, jobject jMethod) SHARED_REQUIRES(Locks::mutator_lock_)
{
if (jMethod != nullptr)
{
Thread *self = Thread::Current();
ArtMethod *artMethod = jMethodToArtMethod(env, jMethod);
// 保存Thread对象
Aupk::setThread(self);
// 保存ArtMethod对象
Aupk::setMethod(artMethod);
// 发起主动调用
Aupk::aupkFakeInvoke(artMethod);
}
return;
}
Thread *Aupk::aupkThread = nullptr;
ArtMethod *Aupk::aupkArtMethod = nullptr;
void Aupk::setMethod(ArtMethod *method)
{
aupkArtMethod = method;
}
void Aupk::setThread(Thread *thread)
{
aupkThread = thread;
}
bool Aupk::isFakeInvoke(Thread *thread, ArtMethod *method) SHARED_REQUIRES(Locks::mutator_lock_)
{
if (aupkThread == nullptr || aupkArtMethod == nullptr || thread == nullptr || method == nullptr)
{
return false;
}
if ((thread->GetTid() == aupkThread->GetTid()) &&
strcmp(PrettyMethod(method).c_str(), PrettyMethod(aupkArtMethod).c_str()) == 0)
{
return true;
}
return false;
}
遍历Dex文件所有direct_method和virtual_method,并获取其index;
根据index去找到dump下来的函数信息的code_item;
将code_item回填;
部分壳需要还原dex_header中的 Magic字段。
三、实验
$ adb install your_app
$ cd data/data/your_app/aupk
$ ls -l
$ findstr /m "SplashActivity" *class.json
$ echo "com.klcxkj.zqxy 8273364_ExecuteSwitchImpl_class.json">data/local/tmp/aupk.config
$ echo "com.klcxkj.zqxy">data/local/tmp/aupk.config
$ adb pull data/data/your_app/aupk
$ dp fix -d 8273364_ExecuteSwitchImpl.dex -j 8273364_ExecuteSwitchImpl_method.json [--nolog]
四、对抗手段
1. 文件检测
void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receiver,
uint32_t* args, JValue* result)
{
...
// Do this after populating the shadow frame in case EnsureInitialized causes a GC.
if (method->IsStatic() && UNLIKELY(!method->GetDeclaringClass()->IsInitialized()))
{
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_class(hs.NewHandle(method->GetDeclaringClass()));
if (UNLIKELY(!class_linker->EnsureInitialized(self, h_class, true, true)))
{
CHECK(self->IsExceptionPending());
self->PopShadowFrame();
return;
}
}
...
}
$ cd data/data/your_app/aupk
$ rm *.dex
function test() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (className) {
var klass = loadClass(className);
var methods = klass.getMethods();
fakeInvoke(methods);
}, onComplete: function () {
}
})
});
}
五、写在最后
参考文献
https://bbs.pediy.com/thread-252630.htm
https://bbs.pediy.com/thread-259854.htm
看雪ID:FeJQ
https://bbs.pediy.com/user-home-887837.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!