物体

欢迎使用Markdown编辑器写博客

系统代码相关部分

/dalvik/vm/

Init.cpp

  • HashTable* dvmGetUserDexFiles()
return gDvm.userDexFiles;

oo/Class.cpp

  • static ClassObject* 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排序一下
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值