加固与脱壳07 - 修改源码脱壳

​上文我们讨论了该如何脱壳,现在就开始实现吧。

本文不介绍如何编译源码,这块内容之前已经单独发过了,可以使用虚拟机或者 WSL,WSL体验要好些,虚拟机更方便。

看开源项目:

  • https://github.com/dqzg12300/FartExt

分析其实现思路,注意,如果需要自己编译源码,直接使用开源项目的代码是很容易被针对的,一定要将方法名等特征给改掉。

该项目的整体思路:

  1. 传递特殊的模拟参数,主动调用类方法

  2. 将指令解释器模式改为 switch 模式,这个模式源码比较简单。

  3. 在解释器执行指令的时候,判断是否为深度隐藏指令,如果是的话,对比指令特征直到触发真实指令后再 dump 方法,否则直接 dump 方法。

下面按文件顺序分析改动。

interpreter_switch_impl-inl.h

第一处

  //add
  int32_t regvalue=ctx->result_register.GetI();
  ctx->result_register=JValue();
  int inst_count = -1;
  bool flag=false;
  //add end

初始化一些变量,后续使用。

regvalue 是获取的返回值寄存器的值,这个值是我们在别处设置的,固定为 111111。

第二处

    //add
    inst_count++;
    uint8_t opcode = inst->Opcode(inst_data);

    if(regvalue==111111){
        if(inst_count == 0){
            if(opcode == Instruction::GOTO || opcode == Instruction::GOTO_16 || opcode == Instruction::GOTO_32){
                LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=0 opcode==GOTO "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
                flag=true;
            }else{
                LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=0 opcode!=GOTO "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
                dumpArtMethod(shadow_frame.GetMethod());
                break;
            }
        }
        if(inst_count == 1){
            if(opcode >= Instruction::CONST_4 && opcode <= Instruction::CONST_WIDE_HIGH16){
                LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=1 opcode==CONST "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
                flag=true;
            }else{
                LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count=1 opcode!=CONST "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
                dumpArtMethod(shadow_frame.GetMethod());
                break;
            }
        }
    }
    //add end

利用 regvalue 来判断是否为脱壳主动调用触发的函数执行。

然后就是匹配指令特征,上一篇我们分析了原因,一些指令抽取壳除了隐藏指令外,还会利用跳转额外做一层指令隐藏。

没有匹配特征就执行 dump 方法指令,匹配到了就将 flag 设置为 true。

第三处

    //add
    if(regvalue==111111){
        if(inst_count==2&&flag){
            if(opcode == Instruction::INVOKE_STATIC || opcode == Instruction::INVOKE_STATIC_RANGE){
                LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch INVOKE_STATIC over "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
                dumpArtMethod(shadow_frame.GetMethod());
                break;
            }
        }
        if(inst_count>2){
            LOG(ERROR) << "fartext ExecuteSwitchImplCpp Switch inst_count>2 " <<shadow_frame.GetMethod()->PrettyMethod().c_str();
            dumpArtMethod(shadow_frame.GetMethod());
            break;
        }
    }
    //add end

因为指令跳转隐藏的特征是第3条指令是 INVOKE_STATIC 或者 INVOKE_STATIC_RANGE,所以需要等这个指令执行完之后,再去 dump 才能拿到真实指令。

至于第二个 if 感觉是个兜底行为。

如果遇到了其他的指令隐藏方式,可以自行修改这里的逻辑匹配上就行。

interpreter.cc

第一处

 //add
 extern "C" void dumpdexfilebyExecute(ArtMethod* artmethod);
 //addend

这里添加了一个方法,是一个特征,可以改成自己的名字。

第二处

  //add
  if(result_register.GetI()==111111){
    LOG(ERROR) << "fartext Execute start "<<shadow_frame.GetMethod()->PrettyMethod().c_str();
  }
  if(strstr(shadow_frame.GetMethod()->PrettyMethod().c_str(),"<clinit>"))
  {
      if(ShouldUnpack()){
          dumpdexfilebyExecute(shadow_frame.GetMethod());
      }

  }
  //add end

由于一些加固厂商会禁用  dex2oat,所以它不是一个好的脱壳点。而 clinit 方法不会被 dex2oat,所以这个方法一定会走解释器执行。

这里是 fart 选取的整体脱壳点。

第三处

    //add
    if(result!=nullptr&&result->GetI()==111111){
        shadow_frame->SetVReg(cur_reg, args[0]);
    }else{
        CHECK(receiver != nullptr);
        shadow_frame->SetVRegReference(cur_reg, receiver);
    }
    //add end
    
    // 原逻辑
    shadow_frame->SetVRegReference(cur_reg, receiver);

这里没有看太懂。

猜测一下:解释器的逻辑中有个不得不提的操作就是它会通过SetVRegReference去追溯参数内容,因此如果参数不合法,会造成异常。

对于指令跳转隐藏的壳,我们需要先执行几条指令,由于我们的参数是自己构造的,所以可能运行会有问题,这里应该是做了一些修正操作。

自定义传递的参数有一些是NULL或者因无法实际获取其真实的内容而随意构造的参数。因为不能让参数真正地解引用,即不能执行其中的SetVRegReference方法,所以我们选择使用SetVReg来替代。原理是源码在非解引用的情况中均使用SetVReg和SetVRegLong来进行参数的传递,而不去解引用我们构造的参数。

第四处

        //add
        if(result!=nullptr&&result->GetI()==111111){
            shadow_frame->SetVReg(cur_reg, args[0]);
            break;
        }
        //add end
        
        // 原逻辑下面会调用 SetVRegReference 进行解引用,我们使用 SetVReg 代替,然后做个 break 跳出
        

同上。

dalvik_system_DexFile.cc

第一处

//add
#include "scoped_fast_native_object_access.h"
//add end

引入文件头

第二处

//add
extern "C" void fartextInvoke(ArtMethod* artmethod);
extern "C" ArtMethod* jobject2ArtMethod(JNIEnv* env, jobject javaMethod);
//add end

添加了两个函数,改下名。

第三处

//add function 将java的Method转换成ArtMethod。然后主动调用
static void DexFile_fartextMethodCode(JNIEnv* env, jclass,jobject method) {
  if(method!=nullptr)
  {
    ArtMethod* proxy_method = jobject2ArtMethod(env, method);
    fartextInvoke(proxy_method);
   }

  return;
}
//add end

第四处

  //add
  NATIVE_METHOD(DexFile, fartextMethodCode,
                  "(Ljava/lang/Object;)V")
  //add end

注册了一个JNI方法。

java_lang_reflect_Method.cc

第一处

//add
extern "C" ArtMethod* jobject2ArtMethod(JNIEnv* env, jobject javaMethod) {
  ScopedFastNativeObjectAccess soa(env);
  ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod);
  return method;
}
//add end

添加了一个方法,用于将 java 方法转成 ArtMethod。

Android.bp

第一处

            cflags: [
                // ART is allowed to link to libicuuc directly
                // since they are in the same module
                "-DANDROID_LINK_SHARED_ICU4C",
                "-Wno-error",
                "-DART_USE_CXX_INTERPRETER=1"
            ],

通过编译参数来控制,把运行模式给改成使用Switch解释器。

art_method.cc

第一处

//added code

#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "runtime.h"
#include <android/log.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <string>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>

#define gettidv1() syscall(__NR_gettid)
#define LOG_TAG "ActivityThread"
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
//add end

第二处

 // add
  uint8_t *codeitem_end(const uint8_t **pData)
  {
    uint32_t num_of_list = DecodeUnsignedLeb128(pData);
    for (; num_of_list > 0; num_of_list--)
    {
      int32_t num_of_handlers = DecodeSignedLeb128(pData);
      int num = num_of_handlers;
      if (num_of_handlers <= 0)
      {
        num = -num_of_handlers;
      }
      for (; num > 0; num--)
      {
        DecodeUnsignedLeb128(pData);
        DecodeUnsignedLeb128(pData);
      }
      if (num_of_handlers <= 0)
      {
        DecodeUnsignedLeb128(pData);
      }
    }
    return (uint8_t *)(*pData);
  }

  extern "C" char *base64_encode(char *str, long str_len, long *outlen)
  {
    long len;
    char *res;
    int i, j;
    const char *base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    if (str_len % 3 == 0)
      len = str_len / 3 * 4;
    else
      len = (str_len / 3 + 1) * 4;

    res = (char *)malloc(sizeof(char) * (len + 1));
    res[len] = '\0';
    *outlen = len;
    for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
    {
      res[i] = base64_table[str[j] >> 2];
      res[i + 1] = base64_table[(str[j] & 0x3) << 4 | (str[j + 1] >> 4)];
      res[i + 2] = base64_table[(str[j + 1] & 0xf) << 2 | (str[j + 2] >> 6)];
      res[i + 3] = base64_table[str[j + 2] & 0x3f];
    }

    switch (str_len % 3)
    {
    case 1:
      res[i - 2] = '=';
      res[i - 1] = '=';
      break;
    case 2:
      res[i - 1] = '=';
      break;
    }

    return res;
  }
  
  // 在函数即将调用解释器执行前进行dump。
  extern "C" void dumpdexfilebyExecute(ArtMethod *artmethod) REQUIRES_SHARED(Locks::mutator_lock_)
  {
    char *dexfilepath = (char *)malloc(sizeof(char) * 1000);
    if (dexfilepath == nullptr)
    {
      LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,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);
      if (result < 0)
      {
        LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,open cmdline file error";
      }
      close(fcmdline);
    }

    if (szProcName[0])
    {

      const DexFile *dex_file = artmethod->GetDexFile();
      const uint8_t *begin_ = dex_file->Begin(); // Start of data.
      size_t size_ = dex_file->Size();           // Length of data.

      memset(dexfilepath, 0, 1000);
      int size_int_ = (int)size_;

      memset(dexfilepath, 0, 1000);
      sprintf(dexfilepath, "%s", "/sdcard/fext");
      mkdir(dexfilepath, 0777);

      memset(dexfilepath, 0, 1000);
      sprintf(dexfilepath, "/sdcard/fext/%s", szProcName);
      mkdir(dexfilepath, 0777);

      memset(dexfilepath, 0, 1000);
      sprintf(dexfilepath, "/sdcard/fext/%s/%d_dexfile_execute.dex", szProcName, size_int_);
      int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
      if (dexfilefp > 0)
      {
        close(dexfilefp);
        dexfilefp = 0;
      }
      else
      {
        int fp = open(dexfilepath, O_CREAT | O_APPEND | O_RDWR, 0666);
        if (fp > 0)
        {
          result = write(fp, (void *)begin_, size_);
          if (result < 0)
          {
            LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,open dexfilepath error";
          }
          fsync(fp);
          close(fp);
          memset(dexfilepath, 0, 1000);
          sprintf(dexfilepath, "/sdcard/fext/%s/%d_classlist_execute.txt", szProcName, size_int_);
          int classlistfile = open(dexfilepath, O_CREAT | O_APPEND | O_RDWR, 0666);
          if (classlistfile > 0)
          {
            for (size_t ii = 0; ii < dex_file->NumClassDefs(); ++ii)
            {
              const dex::ClassDef &class_def = dex_file->GetClassDef(ii);
              const char *descriptor = dex_file->GetClassDescriptor(class_def);
              result = write(classlistfile, (void *)descriptor, strlen(descriptor));
              if (result < 0)
              {
                LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
              }
              const char *temp = "\n";
              result = write(classlistfile, (void *)temp, 1);
              if (result < 0)
              {
                LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
              }
            }
            fsync(classlistfile);
            close(classlistfile);
          }
        }
      }
    }

    if (dexfilepath != nullptr)
    {
      free(dexfilepath);
      dexfilepath = nullptr;
    }
  }

  extern "C" bool ShouldUnpack()
  {
    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);
      if (result < 0)
      {
        LOG(ERROR) << "fartext ArtMethod::ShouldUnpack,open cmdline file file error";
      }
      close(fcmdline);
    }
    if (szProcName[0])
    {
      const char *UNPACK_CONFIG = "/data/local/tmp/fext.config";
      std::ifstream config(UNPACK_CONFIG);
      std::string line;
      if (config)
      {
        while (std::getline(config, line))
        {
          std::string package_name = line.substr(0, line.find(':'));
          if (strstr(package_name.c_str(), szProcName))
          {
            return true;
          }
        }
      }
      return false;
    }
    return false;
  }

  // 主动调用函数的dump处理
  extern "C" void dumpArtMethod(ArtMethod *artmethod) REQUIRES_SHARED(Locks::mutator_lock_)
  {
    LOG(ERROR) << "fartext ArtMethod::dumpArtMethod enter " << artmethod->PrettyMethod().c_str();
    char *dexfilepath = (char *)malloc(sizeof(char) * 1000);
    if (dexfilepath == nullptr)
    {
      LOG(ERROR) << "fartext ArtMethod::dumpArtMethodinvoked,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);
      if (result < 0)
      {
        LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,open cmdline file file error";
      }
      close(fcmdline);
    }

    if (szProcName[0])
    {
      const DexFile *dex_file = artmethod->GetDexFile();
      const uint8_t *begin_ = dex_file->Begin(); // Start of data.
      size_t size_ = dex_file->Size();           // Length of data.

      memset(dexfilepath, 0, 1000);
      int size_int_ = (int)size_;

      memset(dexfilepath, 0, 1000);
      sprintf(dexfilepath, "%s", "/sdcard/fext");
      mkdir(dexfilepath, 0777);

      memset(dexfilepath, 0, 1000);
      sprintf(dexfilepath, "/sdcard/fext/%s", szProcName);
      mkdir(dexfilepath, 0777);

      memset(dexfilepath, 0, 1000);
      sprintf(dexfilepath, "/sdcard/fext/%s/%d_dexfile.dex", szProcName, size_int_);
      int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
      if (dexfilefp > 0)
      {
        close(dexfilefp);
        dexfilefp = 0;
      }
      else
      {
        int fp = open(dexfilepath, O_CREAT | O_APPEND | O_RDWR, 0666);
        if (fp > 0)
        {
          result = write(fp, (void *)begin_, size_);
          if (result < 0)
          {
            LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,open dexfilepath file error";
          }
          fsync(fp);
          close(fp);
          memset(dexfilepath, 0, 1000);
          sprintf(dexfilepath, "/sdcard/fext/%s/%d_classlist.txt", szProcName, size_int_);
          int classlistfile = open(dexfilepath, O_CREAT | O_APPEND | O_RDWR, 0666);
          if (classlistfile > 0)
          {
            for (size_t ii = 0; ii < dex_file->NumClassDefs(); ++ii)
            {
              const dex::ClassDef &class_def = dex_file->GetClassDef(ii);
              const char *descriptor = dex_file->GetClassDescriptor(class_def);
              result = write(classlistfile, (void *)descriptor, strlen(descriptor));
              if (result < 0)
              {
                LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
              }
              const char *temp = "\n";
              result = write(classlistfile, (void *)temp, 1);
              if (result < 0)
              {
                LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
              }
            }
            fsync(classlistfile);
            close(classlistfile);
          }
        }
      }

      const dex::CodeItem *code_item = artmethod->GetCodeItem();
      const DexFile *dex_ = artmethod->GetDexFile();
      CodeItemDataAccessor accessor(*dex_, dex_->GetCodeItem(artmethod->GetCodeItemOffset()));
      if (LIKELY(code_item != nullptr))
      {

        int code_item_len = 0;
        uint8_t *item = (uint8_t *)code_item;
        if (accessor.TriesSize() > 0)
        {
          const uint8_t *handler_data = accessor.GetCatchHandlerData();
          uint8_t *tail = codeitem_end(&handler_data);
          code_item_len = (int)(tail - item);
        }
        else
        {
          code_item_len = 16 + accessor.InsnsSizeInCodeUnits() * 2;
        }
        memset(dexfilepath, 0, 1000);
        int size_int = (int)dex_file->Size();
        uint32_t method_idx = artmethod->GetDexMethodIndex();
        sprintf(dexfilepath, "/sdcard/fext/%s/%d_ins_%d.bin", szProcName, size_int, (int)gettidv1());
        int fp2 = open(dexfilepath, O_CREAT | O_APPEND | O_RDWR, 0666);
        if (fp2 > 0)
        {
          lseek(fp2, 0, SEEK_END);
          memset(dexfilepath, 0, 1000);
          int offset = (int)(item - begin_);
          sprintf(dexfilepath, "{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:", artmethod->PrettyMethod().c_str(), method_idx, offset, code_item_len);
          int contentlength = 0;
          while (dexfilepath[contentlength] != 0)
            contentlength++;
          result = write(fp2, (void *)dexfilepath, contentlength);
          if (result < 0)
          {
            LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write ins file error";
          }
          long outlen = 0;
          char *base64result = base64_encode((char *)item, (long)code_item_len, &outlen);
          result = write(fp2, base64result, outlen);
          if (result < 0)
          {
            LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write ins file error";
          }
          result = write(fp2, "};", 2);
          if (result < 0)
          {
            LOG(ERROR) << "fartext ArtMethod::dumpdexfilebyArtMethod,write ins file error";
          }
          fsync(fp2);
          close(fp2);
          if (base64result != nullptr)
          {
            free(base64result);
            base64result = nullptr;
          }
        }
      }
    }

    if (dexfilepath != nullptr)
    {
      free(dexfilepath);
      dexfilepath = nullptr;
    }
    LOG(ERROR) << "fartext ArtMethod::dumpArtMethod over " << artmethod->PrettyMethod().c_str();
  }
  extern "C" void fartextInvoke(ArtMethod *artmethod) REQUIRES_SHARED(Locks::mutator_lock_)
  {
    if (artmethod->IsNative() || artmethod->IsAbstract())
    {
      return;
    }
    JValue result;
    Thread *self = Thread::Current();
    uint32_t temp[100] = {0};
    uint32_t *args = temp;
    uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(artmethod->GetShorty());
    if (!artmethod->IsStatic())
    {
      args_size += 1;
    }
    result.SetI(111111);
    LOG(ERROR) << "fartext fartextInvoke";
    artmethod->Invoke(self, args, args_size, &result, artmethod->GetShorty());
  }

  // addend

dumpArtMethod这个方法是在我们主动调用的时候,会 dump 方法指令。

ShouldUnpack这个方法是读取配置文件,看看该app是否需要脱壳。

fartextInvoke这个方法用来触发方法的主动调用的,这个方法里面设置了调用参数。

dumpdexfilebyExecute 这个方法是dump整体的 dex,指令的填补在 dumpArtMethod 方法里面。

剩下两个方法是计算一些参数,与DEX结构有关,不展开。

ActivityThread.java

第一处

//add
import cn.mik.Fartext;
//add end

第二处

            //add
            Fartext.fartthread();
            //add end

Fartext.java

整个文件都是新增的,就不贴代码了,自行查看。

主要就是获取了 ClassLoader 之后,遍历所有类的所有方法,并触发该方法:

loadClassAndInvoke(appClassloader, line, dumpMethodCode_method);

dumpMethodCode_method就是上面在 DexFile.cc 中新加的 JNI 方法 fartextMethodCode

DexFile.java

第一处

    //add
    private static native void fartextMethodCode(Object m);
    //add end

与 DexFile.cc 对应,形成一个 JNI 方法。

所有源码分析完成,看起来还是很清晰的,后续就是改名然后编译一个 release 脱壳机了。

关注我的公众号:二手的程序员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二手的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值