首页
社区
课程
招聘
[原创]基于被动调用的抽取壳脱壳方案分析与实现
发表于: 2024-6-14 21:40 21185

[原创]基于被动调用的抽取壳脱壳方案分析与实现

2024-6-14 21:40
21185

这篇文章是基于上篇文章所总结知识点的抽取壳理论实践。
说到抽取壳脱壳机,FART算是很熟悉了,寒冰师傅的FART是ART环境下基于主动调用的自动化脱壳方案,FART关键的脱壳点是在ARTMethod::invoke,通过JNI函数主动遍历所有ArtMethod,在ARTMethod::invokedump下code_item中的数据。
通过之前对Dex加载流程,类加载流程,以及方法执行流程的总结,可以得知APP的Java方法的调用主要有两种,一是通过反射或JNI主动调用,二是通过自然调用路径,例如当APP被点击某个功能,Java方法就会被自然调用,FART的调用方法方式就是通过第一种方式,是所以是主动的方式。那存在被动调用吗,当APP被点击某个功能,Java方法就会被自然调用,如果脱壳机是在由系统触发的自然调用流程中进行被动脱壳,那么它就是基于被动调用的脱壳方案。

环境:Android 8.0

有了之前的理论基础,其实实现起来就比较简单了,之前分析了自然调用流程:art_quick_to_interpreter_bridge->artQuickToInterpreterBridge() → interpreter::EnterInterpreterFromEntryPoint() → interpreter::Execute()在这个流程中找一个合适的脱壳点添加脱壳机代码即可,脱壳机的代码其实用FART的即可因为脱Code_item的流程都是一样的,少了主动调用的代码。

先看下这个流程涉及的几个函数的入参
__artQuickToInterpreterBridge__这个函数的入参有两个ArtMethod,第二个是SP指针用于构造解释器调用栈帧,栈帧包含了方法运行时的局部变量、操作数栈和其他必要的信息。通过调用栈我们就可以获取当当前运行的方法的ArtMethod

__EnterInterpreterFromEntryPoint__两个参数一个当前线程指针,一个栈帧。

__Execute__Execute这里的参数就比较多了可以看到code_item直接被传过来了,还有shadow_frame。

看完这三个函数其实都可以做脱壳点,接下来开始添加脱壳代码把。

之前说了脱壳代码可以直接使用FART中的dumpArtMethod函数,因为ArtMethod对象的获取并不难,直接传入ArtMethod即可

到这一步的话脱壳机已经可以运行起来了,因为被动脱壳方案是在代码自然调用路径上,所以不需要构造主动调用代码。不过这里还可以进一步优化一下,因为是在自然调用路径所以系统的其他代码也可能会被一起脱下来,这样很影响效率,所以可以添加一段配置读取代码,在某个文件夹下放一个配置文件,文件里写要脱壳APP的包名。

这里选了一个企业版加固的APP来测试,企业版的加固抽取壳算是基本配置,这里不贴APP图片了。
先选一个要脱壳的地方,我选在了登录功能上,先把包名写到配置文件中去,然后启动APP,这里为了方便分析我打印了被调用函数,之后账号密码随便输入一下点登录,登录功能的脱壳就开始了,通过调用日志看一下,可以看到了名为LoginActivity的一些函数被调用比如LoginActivity.a(),从名字可以看出是用于登录的Activity组件,并且可以看到有的函数被混淆了,还有常用于发包的okhttp库的调用。
图片描述
登录的主要逻辑主要是在OnClick()里看一下smali可以看到被抽取了codeitem
图片描述
过滤一下包含LoginActivity的脱壳文件,可以看到3576752_ins_2543.bin,3576752_ins_2708.bin,3576752_classlist.txt以及3576752_dexfile.dex,这里还原抽取壳可以直接用FART的修复代码稍微修改一下。
图片描述
修复前:
图片描述
修复后:
图片描述
可以看到我们点击登录功能后的函数被还原了。

基于被动脱壳的脱壳方案有一个明显的缺点,脱壳脱不完整,只能脱系统调用了的方法,不过这也算是它的优点,在分析APP算法的Java层的流程时可以快速定位被调用了的函数,以及发包方式,排除可疑代码的干扰,快速定位Hook点。

extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp)
    REQUIRES_SHARED(Locks::mutator_lock_) {
    ...
}
extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp)
    REQUIRES_SHARED(Locks::mutator_lock_) {
    ...
}
JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item,
                                      ShadowFrame* shadow_frame) {
  DCHECK_EQ(self, Thread::Current());
  bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();
  if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {
    ThrowStackOverflowError(self);
    return JValue();
  }
 
  jit::Jit* jit = Runtime::Current()->GetJit();
  if (jit != nullptr) {
    jit->NotifyCompiledCodeToInterpreterTransition(self, shadow_frame->GetMethod());
  }
  return Execute(self, code_item, *shadow_frame, JValue());
}
JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item,
                                      ShadowFrame* shadow_frame) {
  DCHECK_EQ(self, Thread::Current());
  bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();
  if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {
    ThrowStackOverflowError(self);
    return JValue();
  }
 
  jit::Jit* jit = Runtime::Current()->GetJit();
  if (jit != nullptr) {
    jit->NotifyCompiledCodeToInterpreterTransition(self, shadow_frame->GetMethod());
  }
  return Execute(self, code_item, *shadow_frame, JValue());
}
static inline JValue Execute(
    Thread* self,
    const DexFile::CodeItem* code_item,
    ShadowFrame& shadow_frame,
    JValue result_register,
    bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) {
    ...
}
static inline JValue Execute(
    Thread* self,
    const DexFile::CodeItem* code_item,
    ShadowFrame& shadow_frame,
    JValue result_register,
    bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) {
    ...
}
extern "C" void dumpArtMethod(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {
            char * dexfilepath = (char *) malloc(sizeof(char) * 1000);
            if (dexfilepath==nullptr)
            {
                LOG(INFO) << "ArtMethod::dumpArtMethod,methodname:" << artmethod->PrettyMethod().c_str()
                          << "malloc 1000 byte failed";
                return;
            }
            int result=0;
            int fcmdline =-1;
            char szCmdline[64]= { 0 };
            char szProcName[256] = { 0 };
            int procid = getpid();
            sprintf(szCmdline, "/proc/%d/cmdline", procid);
 
            fcmdline = open(szCmdline, O_RDONLY, 0644);
            if (fcmdline >0)
            {
                result = read(fcmdline, szProcName, 256);
                szProcName[sizeof(szProcName) - 1] = '\0';
                if (result < 0) {
                    LOG(ERROR) << "ArtMethod::dumpArtMethod,open cmdline file file error";
                    return;
                }
                close(fcmdline);
            }
            ...
}
extern "C" void dumpArtMethod(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {
            char * dexfilepath = (char *) malloc(sizeof(char) * 1000);
            if (dexfilepath==nullptr)
            {
                LOG(INFO) << "ArtMethod::dumpArtMethod,methodname:" << artmethod->PrettyMethod().c_str()
                          << "malloc 1000 byte failed";

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (2)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
向楼主学习
2024-6-14 22:59
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
tql
2024-6-17 18:07
0
游客
登录 | 注册 方可回帖
返回
//