首页
论坛
课程
招聘
[基础理论] [脱壳反混淆] [原创]AUPK:基于Art虚拟机的脱壳机
2021-3-29 15:24 6041

[基础理论] [脱壳反混淆] [原创]AUPK:基于Art虚拟机的脱壳机

2021-3-29 15:24
6041

一. 背景

如今,随着开发者安全意识的提高,加壳的app比比皆是,反而不加壳的并不多见.对于一款加壳的app,脱壳是逆向工程中非常重要的一个步骤.有一个好的脱壳机,可以大大节省逆向人员的时间与精力.

 

android系统的换代升级非常块,在4.4版本后舍弃了以往的dalvik虚拟机,取而代之的是更高效的art虚拟机.在android脱壳领域,有许多前辈共享了其思路,代码,使本人得已站在巨人的肩膀上进行学习,研究.如FART:ART环境下基于主动调用的自动化脱壳方案中,hanbingle大佬开源的基于art的主动调用方案:FART.

那么,既然有了FART,为什么我还要写AUPK呢?
  1. FART是通过ClassLoader里的path_list来获取DexFile对象,再通过以下方法来获取到所有类,

    1
    2
    package dalvik.system.DexFile;
    String[] getClassNameList(DexFile dexFile,Object mCookie);

    进而获取到所有Method对象并进行主动调用.这种方案在大多数情况下非常有效,但对某些壳,如某南极动物壳,这里无法获取到关键的DexFile对象,导致无法进行后面的主动调用.AUPK提供了其他思路来应对这种情况.

  2. FART的调用链只到了Invoke函数即返回,对于许多函数恢复时机更晚的抽取壳,如某加密,某叠字壳,如果函数是第一次执行,此时dump下来的codeItem,显然是未解密的,AUPK构造了更深的调用链,以及更晚的dump时机来解决这一问题.

论坛也有许多其他的大佬,提供了ART下的主动调用脱壳思路,但遗憾的是,均未开源。所以我决定自己实现一个脱壳机。

二. 原理

整体流程图:

 

脱壳

关于如何整体dump Dex文件,网上有诸多的解决方案,各家都有不同的脱壳点,如DexFile:init,DexFile::OpenCommon等,其原理是因为dex文件在进行加载的时候,必定会调用这些函数,且此时能方便的拿到DexFile对象,从而进行dump。AUPK的脱壳点在ExecuteSwitchImpl,当满足特定条件时,类中的某些函数必定会走到这里,此时可以通过栈帧拿到ArtMethod对象,进而获取到DexFile对象。

 

拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点一文中,作者阐明了,不管壳是否禁用了dex2oat,类的初始化函数始终运行在ART下的inpterpreter模式,即class.<clinit>函数是必定要运行到Execute函数中的,当app运行时,不可避免的要加载dex,加载、链接、解析、初始化类,而当有类进行初始化的时候,就一定会走到Execute

 

Execute部分代码如下所示:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 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);
        }
    }
    ...
 }

可以看到,Execute会根据当前选择的解释器类型,进入不同的解释器去执行,当我们手动把默认的解释器改为kSwitchImplKind,则可以让Execute默认走swich型解释器,从而进入ExecuteSwitchImpl

1
2
3
// interpreter.cc
//static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind;
static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind;

在ExecuteSwitchImpl函数中,通过shadow_frame.GetMethod()可以拿到当前ArtMethod对象,再通过artMethod->GetDexFile()可以获取DexFile对象,

 

最后通过DexFile对象的Begin(),Size()获取到起始位置和大小,来dump Dex文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
    }
}

与此同时,在dumpClassName函数中,获取Dex文件的所有类名,并写入到文件(关于这一步的作用,后面会讲到):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

到这里,我们已经拿到了Dex文件,以及Dex文件包含的所有类的类名。

 

若想对函数进行主动调用,则必须得到函数对应的ArtMethod对象,以下方法将jMethod转为ArtMethod

1
2
3
4
5
6
7
// aupk_method.cc
ArtMethod *jMethodToArtMethod(JNIEnv *env, jobject jMethod)
{
    ScopedFastNativeObjectAccess soa(env);
    ArtMethod *method = ArtMethod::FromReflectedMethod(soa, jMethod);
    return method;
}

在Java中,Method对象可以通过Class对象的成员方法getDeclaredMethods()来获得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取目标类的所有构造函数
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对象,那么就可以得到所有的ArtMethod对象。在前面,我们获取到了Dex里包含的所有类的名字,此时便可对这些类进行加载,并通过上述方法获取到所有Method对象。

1
2
// 通过类名加载类,得到Class对象
Class klass=getClassLoader().loadClass(className);

小结:首先通过类名加载并获取类对象,再通过类对象获取类中所有的方法,进而将方法传入native层,转为artMethod,并进行主动调用。

 

至此,已经可以拿到所有的ArtMethod对象了。

 

接下来就可以构造参数,并对其进行主动调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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());
}

在解释执行模式下,Native方法和动态Proxy方法没有意义,所以直接跳过,抽象方法不能执行,需要其子类提供实现,所以也直接跳过

需要注意的是,函数的第一个参数要赋值为除0以外的任何值,因为后面会判断 CHECK(arg[0]!=nullptr)

 

在art中,系统会在app安装或运行过程中对dex文件进行dex2oat,将其中的函数编译成目标代码,而不再是虚拟机执行的字节码,同时也保留了原来由虚拟机执行的字节码,所以art代码既可以运行在解释器模式下,也可以作为quick_code来执行。为了使主动调用的函数能够到达ExecuteSwitchImpl函数,这里使主动调用的函数强制走解释器:

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
//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);
        }
        ...
    }
}

这样一来,主动调用时,函数不管是静态还是非静态,都会走art::interpreter::EnterInterpreterFromInvoke,分析EnterInterpreterFromInvoke函数可知,如果调用的方法为非native方法,则会进入Execute函数继续执行,而native方法,并非我们所关注的对象,所以在主动调用前,会过滤掉native函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
    {
        ...
    }
    ...
}

如上文所述,进入Execute后,函数最终会进入ExecuteSwitchImpl执行。在ExecuteSwitchImpl函数中,准备工作都已做好,函数指令即将开始执行,对于大多数壳来说,此时函数的code_item都已修复,否则函数无法正常执行,这也是为什么会将主动调用链构造到这里的原因。事实上,对于大多数壳,在这里都可以获取到已被恢复的code_item,个别情况,如在函数体内部goto跳转进行自解密的情况,则需要单独处理:

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
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();
        }
    }
}

上述处理方式并不通用,需要根据具体的情况来作处理,总的来说,dump时机一定要在函数指令被恢复之后,但又要在函数真正的指令被执行之前。

 

除了我们主动调用的函数,依然有大量的其他函数会执行到ExecuteSwitchImpl,所以我们需要在此处进行判断:如果是我们主动调用的函数,则不让其执行真正的指令,否则正常执行。那么,如何判断函数调用是否为我们主动调用的呢?

 

这里可采用的方法多种多样,AUPK中采取了如下方式:在最开始,我们创建了一个主动调用线程,那么在进行fakeInvoke的时候,可以保存当前的Thread对象,以及即将进行主动调用的ArtMethod对象,在主动调用过程中,比对这两个值,即可判断是否为主动调用的函数。不过,此方式需要过滤掉我们主动调用过程中,所用到的java层函数,否则会引发各种错误。

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
39
40
41
42
43
44
// 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;
}

我画了一张图,来对上述流程做一个总结

 

 

其中fakeInvoke()为主动调用的入口,省略了非主动调用的函数的执行流程。

修复
修复的整体思路如下:
  • 解析dump下来的Dex文件
  • 遍历Dex文件所有direct_method和virtual_method,并获取其index
  • 根据index去找到dump下来的函数信息的code_item
  • 将code_item回填
  • 部分壳需要还原dex_header中的 Magic字段

其所需的知识主要是对dex文件结构的理解,网上已有许多相关资料,此处不多赘述。

三.实验

首先安装目标app

1
$ adb install your_app

运行app,可在logcat看到如下信息:

 

此时data/data/your_app/aupk下多出许多文件

1
2
$ cd data/data/your_app/aupk
$ ls -l

 

其中,.dex为整体dump下来的Dex文件,class.json记录了Dex文件里所有的类名,前缀数字代表Dex文件的大小。

 

可以用findstr命令来查找某一个类的类名在哪个文件中,如:

1
$ findstr /m "SplashActivity" *class.json

 

可以看到,"SplashActivity"类在8273364大小的Dex文件中,那么,我们可以通过以下命令,来写入配置文件

 

以开始对8273364_ExecuteSwitchImpl_class.json里所有的类的所有方法进行主动调用

1
$ echo "com.klcxkj.zqxy 8273364_ExecuteSwitchImpl_class.json">data/local/tmp/aupk.config

或者对所有的Dex里的类的所有方法进行主动调用:

1
$ echo "com.klcxkj.zqxy">data/local/tmp/aupk.config

主动调用过程中打印的log:

 

 

有的壳hook了Log函数,导致Log.i()打印不出来消息,但jni层的LOG和Log.e()依然有效,当打印出Aupk run over时,代表整个主动调用过程结束,可以在data/data/you_app/aupk下看到一些以method.json结尾的文件,这些文件包含了Dex文件的函数CodeItem信息,用于后面对Dex文件的修复。

 

并非等整个主动调用过程结束才会生成method.json文件,而是每完成对一个class.json文件的解析和调用,就会立即生成对应的method.json,所以,利用主动调用的这段时间,你可以先修复已经完成了主动调用的Dex文件,或者去泡杯咖啡。

 

将脱下来的文件拷贝到电脑:

1
$ adb pull data/data/your_app/aupk

开始修复Dex,回填CodeItem:

1
$ dp fix -d 8273364_ExecuteSwitchImpl.dex -j 8273364_ExecuteSwitchImpl_method.json [--nolog]

等待片刻,即可完成修复:

 

 

带patched后缀的就是修复后的Dex文件

 

 

反编译看看效果:

 

四.对抗手段

以下为某些场景下的对抗手段(纯YY,并未遇到过),以及个人的一些应对思路

  1. 文件检测

 

应对方法:修改配置文件和中间文件的名字,路径。

  1. 方法,类检测

    应对方法:更改类名,方法名

  2. 插入垃圾类

    AUPK使用loadClass加载类,并不会触发类的<clinit>函数,也就不会触发静态代码块里的检测。但在调用静态函数时,如果类未初始化,art会调用EnsureInitialized去初始化这个类,此时会调用类的初始化函数,触发静态代码块,其过程如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;
            }
        }
        ...  
    }

    应对方法:

    1. 壳为了保证效率,通常在恢复函数指令后,就不会再对其进行抽取了。所以,可以等app运行到一定阶段,确保感兴趣的地方已经被执行过,就可以采用dex整体dump的方式,来dump Dex文件,此时虽然部分函数依然是抽取状态,但关注的部分函数已经解密。

      在AUPK中,你可以删除掉所有的*.dex文件,然后重新操作一下app,即可完成对Dex文件的重新dump。

      1
      2
      $ cd data/data/your_app/aupk
      $ rm *.dex
    2. frida提供了一个api:enumerateLoadedClasses,该方法可以获取到所有已加载的类,然后通过反射去获取类中的所有函数,并调用Aupk中的fakeInvoke ,进行主动调用。这样获得的类虽然不全,但包含了app正常使用中所用到的类,且避开了插入的垃圾类。该方法是在类的粒度进行处理,理论上讲,比方法1修复得更完整。伪代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function test() {
          Java.perform(function () {
              Java.enumerateLoadedClasses({
                  onMatch: function (className) {
                      var klass = loadClass(className);
                      var methods = klass.getMethods();
                      fakeInvoke(methods);
                  }, onComplete: function () {
       
                  }
              })
          });
      }
    3. fakeInvoke前,将当前方法所属的类名写入文件temp.txt,当最后一次执行fakeInvoke时,清空temp.txt.在发起主动调用前,先检查temp.txt,将其中temp.txt中的项追加到detectClass.txt,并跳过所有属于detectClass.txt中的类的函数。当壳检测到脱壳机,终止app时,temp.txt中的内容为非空。如此反复,到最后主动调用成功时,detectClass.txt中则记录着所有检测点所属的类。

五.写在最后

从学习脱壳到现在,不知不觉,竟已两月有余,期间遇到过许许多多,一言难尽的问题。对于这类事物,我认为仅仅靠一两篇文章,读者依然很难去复现它。为了能让像我一样的新手去更好的学习和参考,我将同hanbingle大佬一样,将其开源。

 

AUPK:https://github.com/FeJQ/AUPK

 

DexPatcher:https://github.com/FeJQ/DexPatcher

 

参考文献:

 

[1] hanbingle.FART:ART环境下基于主动调用的自动化脱壳方案

 

[2] Youlor.Youpk: 又一款基于ART的主动调用的脱壳机

 

[3] 邓凡平.深入理解Android:Java虚拟机ART[M].机械工业出版社.2019.04

 

[4] FART


[公告] 2021 KCTF 春季赛 防守方征题火热进行中!

最后于 2021-3-29 15:25 被FeJQ编辑 ,原因: 标题多了个前缀
收藏
点赞9
打赏
分享
最新回复 (30)
雪    币: 300
活跃值: 活跃值 (36)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wanderdeng 活跃值 2021-3-29 15:58
2
0
膜拜大佬~~~~~~~
雪    币: 15766
活跃值: 活跃值 (13728)
能力值: (RANK:75 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2021-3-29 16:15
3
0
感谢分享
雪    币: 8178
活跃值: 活跃值 (1673)
能力值: ( LV8,RANK:134 )
在线值:
发帖
回帖
粉丝
hanbingle 活跃值 2 2021-3-29 17:05
4
0
看样子像是看了我在看雪高研班的课程内容?我在写FART的时候就已经说了,FART关键的思想是引入主动调用的思想来解决抽取壳,完全可以将主动调用链构造的更深甚至是执行到特定的smali指令的时候进行在进行dump。其次,文中给的dex修复的思路并不适用于修改偏移类型的抽取壳,更通用的做法应该是基于dex的结构以及dump下来的函数体即CodeItem进行重构
雪    币: 41
活跃值: 活跃值 (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
simonkoh 活跃值 2021-3-29 17:30
5
0
hanbingle 看样子像是看了我在看雪高研班的课程内容?我在写FART的时候就已经说了,FART关键的思想是引入主动调用的思想来解决抽取壳,完全可以将主动调用链构造的更深甚至是执行到特定的smali指令的时候进行在进 ...
活捉寒冰大佬
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-3-29 17:55
6
0
hanbingle 看样子像是看了我在看雪高研班的课程内容?我在写FART的时候就已经说了,FART关键的思想是引入主动调用的思想来解决抽取壳,完全可以将主动调用链构造的更深甚至是执行到特定的smali指令的时候进行在进 ...
谢谢寒冰老师的指点,我跑起来的第一份代码就是FART,若没有您开源的的FART及系列文章,就不会有这篇文章。至于您说的修改偏移类型的壳,手里没有这样的样本,暂时是没法进行分析了.不过还是谢谢您提供的思路
雪    币: 708
活跃值: 活跃值 (776)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
默NJ 活跃值 2021-3-29 22:31
7
0
好文,感谢分享
雪    币: 8729
活跃值: 活跃值 (3596)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
浅笑不语 活跃值 2021-3-29 22:59
8
0
膜拜大佬
雪    币: 182
活跃值: 活跃值 (76)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
荒huang 活跃值 2021-3-30 07:46
9
0
大佬牛逼 先mark后学习
雪    币: 7343
活跃值: 活跃值 (505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2021-3-30 08:00
10
0
修改源码脱壳?
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_npwymnnm 活跃值 2021-3-30 16:35
11
0
能不能解决动态加载的dex
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-3-30 18:04
12
0
mb_npwymnnm 能不能解决动态加载的dex
理论上当然可以,只要app调用了动态加载的dex里的函数,就会dump出对应Dex里的所有类,等这个过程结束后再去主动调用,就可以了
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-3-30 19:07
13
0
tDasm 修改源码脱壳?
是的,这也是android平台脱壳的特有优势
雪    币: 228
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
深山老琳 活跃值 2021-3-31 01:20
14
0
我有点笨,数字壳可以完整脱吗?
雪    币: 7343
活跃值: 活跃值 (505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2021-3-31 09:46
15
0
FeJQ 是的,这也是android平台脱壳的特有优势
留个联系方式?方便交流探讨
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-3-31 22:04
16
0
mb_mubnmowv 我有点笨,数字壳可以完整脱吗?
Dex可以脱,dex2c,VMP不行
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-3-31 22:05
17
0

null        

最后于 2021-3-31 22:08 被FeJQ编辑 ,原因: 回复错人了
雪    币: 1585
活跃值: 活跃值 (483)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2021-4-1 10:34
18
0
厉害
雪    币: 26
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_yvvzfcdo 活跃值 2021-4-1 15:11
19
0
大牛,百度网盘修复那个dp怎么用 不是有了Dexpatch吗
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-4-1 16:07
20
0
mb_yvvzfcdo 大牛,百度网盘修复那个dp怎么用 不是有了Dexpatch吗
dp 就是DexPatcher的缩写呀,
雪    币: 133
活跃值: 活跃值 (185)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
学编程 活跃值 1 2021-4-2 11:33
21
0
象这种形式的ART脱壳,能写成XPOSED的插件来脱吗
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-4-2 14:57
22
0
学编程 象这种形式的ART脱壳,能写成XPOSED的插件来脱吗
很难
雪    币: 2322
活跃值: 活跃值 (364)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
D-t 活跃值 2021-4-2 15:03
23
0
mark
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_fuqnreci 活跃值 2021-4-3 14:01
24
0
大佬 如果使用nexus那个镜像 是不是还要买nexus手机   不好意思,,我不懂
雪    币: 1082
活跃值: 活跃值 (768)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FeJQ 活跃值 1 2021-4-3 21:11
25
0
mb_fuqnreci 大佬 如果使用nexus那个镜像 是不是还要买nexus手机 不好意思,,我不懂
是的,买个二手的即可
游客
登录 | 注册 方可回帖
返回