欢迎使用Markdown编辑器写博客
系统代码相关部分
/dalvik/vm/
Init.cpp
- HashTable* dvmGetUserDexFiles()
return gDvm.userDexFiles;
oo/Class.cpp
staticClassObject* loadClassFromDex(DvmDex* pDvmDex,
const DexClassDef* pClassDef, Object* classLoader)
将static去除了
interp/Interp.cpp
- void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
//在此函数的一开始
if ((u4)pResult->i == 0xF88FF88F) {
gFupk.ExportMethod(self, method);
return;
}
- void fupkInvokeMethod(Method* meth)
//注释中说要确保meth中确实有代码可供运行
if (dvmIsMirandaMethod(meth) || dvmIsAbstractMethod(meth)) {
return;
}
dvmInvokeMethod((Object*)0xF88FF88F, meth, NULL, NULL, NULL, true);
- bool fupkExportMethod(Thread* self, const Method* method)
return false;
- 其他定义
FupkInterface gFupk = {
NULL, NULL, NULL, NULL,
fupkExportMethod
};
interp/Interp.h
struct FupkInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
bool (*ExportMethod)(Thread* self, const Method* method);
};
/*
从这里可以看出gFupk的ExportMethod就是fupkExportMethod
但后面在apk中的core_Fupk中,又会进行替换,
*/
interp/Stack.cpp
- Object* dvmInvokeMethod(Object* obj, const Method* method,
ArrayObject* argList, ArrayObject* params, ClassObject* returnType,
bool noAccessCheck)
JValue retval;
bool needPop = false;
bool useFupk = (u4)obj == 0xF88FF88F; //这里与fupkInvokeMethod中的0xF88FF88F相对应
if (useFupk)
{
obj = NULL;
retval.i = 0xF88FF88F;
}
/*
接下来,如果useFupk为真的话,那么不初始化stack和argument,这里本质与dvmInterpret相对应,
在dvmInterpret中,看到是特殊参数就直接退出了,所以stack和argument是啥也不需要管
但还是会分配栈桢的
*/
if (dvmIsNativeMethod(method)) {
if (useFupk) {
gFupk.ExportMethod(self, method);
}
}
else {
dvmInterpret(self, method, &retval);
/*
此时retval.i = 0xF88FF88F
*/
}
/frameworks/base/core/java/android/app/fupk3
FRefInvoke.java
- public static Class getClass(ClassLoader loader, String class_name)
if (loader != null)
return loader.loadClass(class_name);
return Class.forName(class_name);
- public static Object invokeMethod(Class clazz, String method_name, Class[] pareTyple, Object obj, Object[] pareVaules)
Method method = clazz.getMethod(method_name,pareTyple);
return method.invoke(obj, pareVaules);
/*
首先根据clazz,method_name,pareTyple获得Method
然后使用obj,pareVaules反射调用Method
*/
- public static Object getFieldOjbect(Class clazz,Object obj, String filedName)
Field field = clazz.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
/*
首先根据clazz,filedName获得Field
然后使用obj反射获得域对象
*/
- public static void setFieldOjbect(Class clazz, String filedName, Object obj, Object filedVaule)
Field field = clazz.getDeclaredField(filedName);
field.setAccessible(true);
field.set(obj, filedVaule);
/*
首先根据clazz,filedName获得Field
然后使用obj,filedValue反射设置该域对象
*/
Fupk.java
- 静态代码块
System.load("/data/local/tmp/libFupk3.so");
/*
静态代码块在类加载的时候执行,且只执行一次
*/
- public Fupk(String packageName)
mPackageName = packageName;
- public void unpackAfter(final long millis)
/*
新建一个进程,等待millis后执行unpackNow
*/
- public void unpackNow()
appLoader = getSystemLoader();
unpackAll("/data/data/" + mPackageName);
/*
unpackAll的实现在FUpk3中
*/
- public ClassLoader getSystemLoader()
Class ActivityThread = FRefInvoke.getClass(null, "android.app.ActivityThread");
Object currentActivityThread = FRefInvoke.invokeMethod(
ActivityThread,
"currentActivityThread", new Class[]{},
null, new Object[]{});
/*
反射调用ActivityThread类中的currentActivityThread方法,
参数为obj = null, pareVaules = new Ojbect[]{}
*/
UpkConfig.java
- public boolean load()
/*
从文件"/data/local/tmp/FUpk3.txt"连读4行,分别为
mHookModel
mTargetPackage
mTargetApplication
mTargetActivity
*/
/frameworks/base/core/java/android/app/
ActivityThread.java
- private void handleBindApplication(AppBindData data)
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) {
}
/*
首先这个handleBindApplication是应用启动过程中的一环,该脱壳机在点击UPK脱壳的时候,总是会先启动要脱壳的应用,应用启动过程中,就会进入这个handleBindApplication。然后新建一个UpkConfig,load函数读出相应的参数。
然后此时查看当前的PackageName和我们要脱壳的那个PackageName是不是一样的,如果是的话就新建Fupk,等待一定时间以后开始脱壳
详见https://www.jianshu.com/p/a1f40b39b3de
暂时只明白了启动流程,细节啥的完全不懂
至于为啥要选择这里作为注入点呢,看注释的意思是此时已经连接到debugger了,估计是为了方便调试
*/
apk相关部分
/Fupk3/src/main/cpp/
main.cpp
- JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
/*
看注释说这里说入口点,那就是吧,我现在还没看懂这个应用是怎么启动的...
*/
core_Fupk::registerNativeMethod(env) //声明Fupk中的那个unpackAll的实现
FupkImpl::initAll()
DexHelper/FupkImpl.cpp
- bool initAll()
/*
大概的意思是将里面的接口都弄出来?
*/
gUpkInterface = gFupk
gDvmUserDexFiles = dvmGetUserDexFiles
fdvmDecodeIndirectRef = _Z20dvmDecodeIndirectRefP6ThreadP8_jobject
fdvmThreadSelf = _Z13dvmThreadSelfv
fupkInvokeMethod = fupkInvokeMethod
floadClassFromDex = loadClassFromDex
core_Fupk.cpp
- void ::core_Fupk::unpackAll(JNIEnv *env, jobject obj, jstring folder)
interface->ExportMethod = fupk_ExportMethod; //此时替换成了DexDumper.cpp中的fupk_ExportMethod
Fupk upk(env, sFolder, obj);
upk.unpackAll();
DexHelper/Fupk.cpp
- bool Fupk::unpackAll()
mCookie.print();
/*
这个mCookie是一个Fupk结构中的一个Field,我一开始还在找到底程序的那个部分更新了这个mCookie,后来发现Cookie的无参构造函数被改过了,所以在构造Fupk的时候,mCookie也会被生成
Cookie::Cookie() {
// valid has been checked in Fupk, at this time, I will not to
// check anymore.
userDexFiles = FupkImpl::gDvmUserDexFiles;
}
*/
// now start to dump
for(int i = 0; i < mCookie.size(); i++) {
const char* name;
auto dvmDex = mCookie.getCookieAt(i, name, mIgnoreCase);
/*
这个getCookieAt的作用就是通过gDvm.userDexFiles去获得第i个HashEntry,进而一路获得DvmDex, mIgnoreCase可以看成是一个字符串数组,表示需要忽略的,一旦dexOrJar->fileName包括了mIgnoreCase中的字符串,那么就忽略
在gDvm.userDexFiles中寻找DvmDex的过程可以参考
https://www.jianshu.com/p/ab9c3984d995
*/
...
DexDumper dumper(mEnv, dvmDex, mUpkObj);
dumper.rebuild(); //这里似乎是脱壳逻辑的真正起始点
DexHelper/DexDumper.cpp
- bool DexDumper::rebuild()
...
auto loaderObject = JniInfo::GetObjectField(mEnv, mUpkObj, "appLoader", "Ljava/lang/ClassLoader;");
//找到启动app时的loader,这个时候loader可能已经被替换掉了?
...
Object* gLoader = FupkImpl::fdvmDecodeIndirectRef(self, loaderObject);
...
DexFile *pDexFile = mDvmDex->pDexFile;
if (pDexFile->pOptHeader) {
mymemcpy(&mDexOptHeader, pDexFile->pOptHeader, sizeof(DexOptHeader));
mymemcpy(mDexOptHeader.magic, OPTMAGIC, sizeof(mDexOptHeader.magic));
}
mymemcpy(&mDexHeader, pDexFile->pHeader, sizeof(DexHeader));
mymemcpy(mDexHeader.magic, DEXMAGIC, sizeof(mDexHeader.magic));
fixDexHeader();
/*
这一段的逻辑大概就是保存相关的头信息
*/
...
for(int i = 0; i < shared.num_class_defs; i++) {
FLOGI("cur class: %u Total: %u", i, shared.num_class_defs);
// try use interpret first
auto origClassDef = dexGetClassDef(mDvmDex->pDexFile, i);
auto descriptor = dexGetClassDescriptor(mDvmDex->pDexFile, origClassDef);
...
auto jClazz = mEnv->CallObjectMethod(mUpkObj, tryLoadClass_method, jDotDescriptor);
/*
先试着在java层(我也不知道是不是java层)反射调用创建对应的class,classloader用的是启动app时的loader
*/
if (jClazz != nullptr) {
Clazz = (ClassObject*)FupkImpl::fdvmDecodeIndirectRef(self, jClazz);
mEnv->DeleteLocalRef(jClazz);
}
...
if (Clazz == nullptr) {
Clazz = FupkImpl::floadClassFromDex(mDvmDex,
dexGetClassDef(mDvmDex->pDexFile, i), gLoader);
}
/*
前面反射调用创建失败了可能是loader被替换了(我猜的),还有可能是其他的原因。
如果失败了就直接从DexFile文件中去找
与DexHunter相比,DexHunter创建的方式是dvmDefineClass,但dvmDefineClass最终也是会调用loadClassFromDex的
dvmDefineClass->findClassNoInit
findClassNoInit首先在loader中寻找,没有的话看看pDvmDex和pClassDef是不是NULL,是的话特殊判断,最终
clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
然后下面在进行锁和linkclass之类的操作
所以本质上看,和DexHunter创建clazz的方式应该是一样的
*/
// class loaded, then use ClassObject to rebuild classDef
auto defBuilder = ClassDefBuilder(Clazz, (DexClassDef*)origClassDef, pDexFile, &sHash);
auto newDef = defBuilder.getClassDef();
if (newDef->classDataOff == 0) {
/*
classDataOff为0的情况是staticFieldsSize,instanceFieldsSize,directMethodsSize,virtualMethodsSize都为0的情况
*/
goto writeClassDef;
} else {
auto newData = defBuilder.getClassData();
if (Clazz != nullptr) {
FLOGI("fix with dvm ClassObject");
u4 lastIndex = 0;
for(int i = 0; i < newData->header.directMethodsSize; i++) {
fixMethodByDvm(shared, &newData->directMethods[i],
&defBuilder, lastIndex);
}
lastIndex = 0;
for(int i = 0; i < newData->header.virtualMethodsSize; i++) {
fixMethodByDvm(shared, &newData->virtualMethods[i],
&defBuilder, lastIndex);
}
}
}
- bool DexDumper::fixMethodByDvm(DexSharedData &shared, DexMethod *dexMethod,
ClassDefBuilder* builder, u4 &lastIndex)
bool DexDumper::fixMethodByDvm(DexSharedData &shared, DexMethod *dexMethod,
ClassDefBuilder* builder, u4 &lastIndex) {
lastIndex = lastIndex + dexMethod->methodIdx;
auto m = builder->getMethodMap(lastIndex);
assert(m != nullptr && "Unable to fix MethodBy Dvm, this should happened");
shared.mCurMethod = dexMethod;
FupkImpl::fupkInvokeMethod(m);
shared.mCurMethod = nullptr;
return true;
}
- bool fupk_ExportMethod(void *thread, Method *method)
DexSharedData* shared = (DexSharedData*)gUpkInterface->reserved0;
DexMethod* dexMethod = shared->mCurMethod;
/*
接下来基本就是使用method来修复dexMethod
*/
DexHelper/ClassDefBuilder.cpp
- ClassDefBuilder::ClassDefBuilder(ClassObject *_clazz, DexClassDef *_classDef, DexFile *_pDexFile,
DexHashTable *_sHash)
clazz = _clazz;
pDexFile = _pDexFile;
sHash = _sHash;
memcpy(&classDef, _classDef, sizeof(DexClassDef));
pDataFix = NULL;
if (clazz == nullptr) {
auto data = dexGetClassData(_pDexFile, _classDef);
pDataFix = ReadClassData(&data);
return ;
/*
loader和直接在DexFile中找都创建不了clazz的情况
*/
}
...
/*
构建复制相应的信息
*/
rebuildClassDefWithClassObject();
- bool ClassDefBuilder::rebuildClassDefWithClassObject()
/*
遍历clazz中的域和方法,将其信息复制到pDataFix中
然后fixField和fixMethod按照fieldIdx和methodIdx排序一下
*/