ART模式下基于dex2oat脱壳的原理分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78513483
一般情况下,Android Dex文件在加载到内存之前需要先对dex文件进行优化处理(如果Android Dex文件已经优化过,则不需要进行优化处理操作,后面进行加载到内存即可),在Dalvik虚拟机模式下,Android dex文件经过dex2oat进程优化为odex文件,在ART虚拟机模式下,Android dex文件经过dex2oat进程优化为oat文件-OAT文件为一种私有的ELF格式文件,和一般的ELF文件稍有一些不同。Dalvik虚拟机模式下,Android Dex文件优化为odex文件的处理比较简单,即使Android
 Dex文件被Android加固所加固处理,只要Android应用运行时能保证dex文件的类方法的数据是完整的就没有问题,Dalvik虚拟机模式下Android dex文件的优化对Android加固的处理影响不是很大;ART虚拟机模式下,Android dex文件的优化处理比价复杂,在Android dex文件优化处理时要保证dex文件类方法的数据完整,因此ART虚拟机模式下的dex文件优化对Android加固还是有比较大的影响,ART虚拟机模式下dex文件的加载也比较复杂,也致使一些Android加固厂商对ART虚拟机模式下dex文件优化的疏忽,最终导致ART虚拟机模式下可以在dex文件优化时进行dex文件的内存dump操作。
ART模式下基于dex2oat脱壳的文章整理:
Dex2oatHunter github:https://github.com/spriteviki/Dex2oatHunter
《分享一个360加固脱壳模拟器(2017/07/17更新)》
《记一次APP脱壳重打包过程》主要讲360加固dex2oat脱壳后的重打包修复,对于360加固的重打包修复有必要学习研究和实践一下。
ART虚拟机模式下,Android dex文件的加载 openDexFileNative函数 对应的Native实现函数为Jni函数动态注册的
DexFile_openDexFileNative函数 ,也就是说ART虚拟机模式下Android dex文件的加载最终调用的是DexFile_openDexFileNative函数来实现的。
static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  // 得到需要加载的dex文件的文件路径
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == NULL) {
    return 0;
  }
  // 构建dex文件加载的路径字符串
  std::string dex_location(sourceName.c_str());
  // dex文件优化后的存放路径字符串
  NullableScopedUtfChars outputName(env, javaOutputName);
  if (env->ExceptionCheck()) {
    return 0;
  }
  ScopedObjectAccess soa(env);
  // 保存dex文件的Checksum
  uint32_t dex_location_checksum;
  // 获取dex文件的Checksum进行校验检查
  if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
    LOG(WARNING) << "Failed to compute checksum: " << dex_location;
    ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
    soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                   "Unable to get checksum of dex file: %s", dex_location.c_str());
    return 0;
  }
  // 获取当前进程的运行时的ClassLinker实例对象
  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  // dex文件格式的简单结构体描述?
  const DexFile* dex_file;
  // 判断存放优化后dex文件的文件路径是否为NULL
  if (outputName.c_str() == NULL) {
     // 获取优化后的oat文件并加载到内存,返回dex文件加载到内存后的描述结构体dex_file
    dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum);
  } else {
    // 构建存放优化后的dex文件oat的路径字符串
    std::string oat_location(outputName.c_str());
    // 获取优化后的oat文件并加载到内存,返回dex文件加载到内存后的描述结构体dex_file
    dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);
  }
  if (dex_file == NULL) {
    LOG(WARNING) << "Failed to open dex file: " << dex_location;
    ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
    soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                   "Unable to open dex file: %s", dex_location.c_str());
    return 0;
  }
  // 设置函数返回值,返回dex文件加载到内存后的描述结构体DexFile*指针
  return static_cast<jint>(reinterpret_cast<uintptr_t>(dex_file));
}ART虚拟机模式下,Android dex文件的加载有DexFile_openDexFileNative函数来实现,在dex文件被 加载之前,先进行dex文件的校验处理,dex文件的校验通过之后,当没有指定dex文件优化后的文件路径,调用 FindDexFileInOatFileFromDexLocation函数
进行dex文件的优化和加载处理;当指定了dex文件优化后的文件路径则调用 函数FindOrCreateOatFileForDexLocation 进行dex文件的优化和加载处理。FindDexFileInOatFileFromDexLocation函数 和 FindOrCreateOatFileForDexLocation函数 在被加载的dex文件没被优化时,最终都会调用 GenerateOatFile函数创建dex2oat进程执行dex文件的优化处理,将原始的dex文件优化处理为私有ELF文件格式的oat文件。
FindOrCreateOatFileForDexLocationLocked函数 的实现代码如下,首先创建的新文件用于存放优化的oat文件并保持该文件锁定,然后进行优化的oat文件的查找,如果没有找到dex文件优化后的oat文件,则进行dex文件的优化处理得到oat文件,后面进行oat文件加载到内存的处理,其他的操作暂时不关注。
const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,
                                                                    uint32_t dex_location_checksum,
                                                                    const std::string& oat_location) {
  // We play a locking game here so that if two different processes
  // race to generate (or worse, one tries to open a partial generated
  // file) we will be okay. This is actually common with apps that use
  // DexClassLoader to work around the dex method reference limit and
  // that have a background service running in a separate process.
  ScopedFlock scoped_flock;
  // 打开或者创建优化后oat文件并保持oat文件锁定
  if (!scoped_flock.Init(oat_location)) {
    LOG(ERROR) << "Failed to open locked oat file: " << oat_location;
    return NULL;
  }
  // Check if we already have an up-to-date output file
  // 判断优化后的oat文件是否存在
  const DexFile* dex_file = FindDexFileInOatLocation(dex_location,
                                                     dex_location_checksum,
                                                     oat_location);
  if (dex_file != NULL) {
    // 存在,直接返回优化后的oat文件的描述结构体信息
    return dex_file;
  }
  // Generate the output oat file for the dex file
  VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
  // 创建dex2oat进程优化dex文件为oat文件
  if (!GenerateOatFile(dex_location, scoped_flock.GetFile().Fd(), oat_location)) {
    LOG(ERROR) << "Failed to generate oat file: " << oat_location;
    return NULL;
  }
  // 加载oat文件到内存中
  const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,
                                          !Runtime::Current()->IsCompiler());
  if (oat_file == NULL) {
    LOG(ERROR) << "Failed to open generated oat file: " << oat_location;
    return NULL;
  }
  RegisterOatFileLocked(*oat_file);
  const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
  if (oat_dex_file == NULL) {
    LOG(ERROR) << "Failed to find dex file " << dex_location
               << " (checksum " << dex_location_checksum
               << ") in generated oat file: " << oat_location;
    return NULL;
  }
  const DexFile* result = oat_dex_file->OpenDexFile();
  CHECK_EQ(dex_location_checksum, result->GetLocationChecksum())
          << "dex_location=" << dex_location << " oat_location=" << oat_location << std::hex
          << " dex_location_checksum=" << dex_location_checksum
          << " DexFile::GetLocationChecksum()=" << result->GetLocationChecksum();
  return result;
}有关ART虚拟机模式下,OAT文件加载到Android进程内存中的详细分析,可以参考老罗的博客《Android运行时ART加载OAT文件的过程分析》。GenerateOatFile函数 的代码实现分析如下。
// dex_filename为被加载的dex文件的路径字符串
// oat_fd为dex文件优化的oat文件的文件指针
// oat_cache_filename为存放优化后dex文件oat的文件路径
bool ClassLinker::GenerateOatFile(const std::string& dex_filename,
                                  int oat_fd,
                                  const std::string& oat_cache_filename) {
  // 获取Android系统的根路径"/system"
  std::string dex2oat_string(GetAndroidRoot());
  // 拼接字符串得到优化dex文件的程序dex2oat文件的路径字符串
  dex2oat_string += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat");
  // 获取到优化dex文件的程序dex2oat文件的路径字符串
  const char* dex2oat = dex2oat_string.c_str();
  // 获取到当前进程的ClassPath的路径字符串
  const char* class_path = Runtime::Current()->GetClassPathString().c_str();
  // 获取到当前进程的堆Heap指针
  gc::Heap* heap = Runtime::Current()->GetHeap();
  // 得到boot_image的优化字符串参数选项"--boot-image="
  std::string boot_image_option_string("--boot-image=");
  // 设置imageSpace文件的路径字符串
  boot_image_option_string += heap->GetImageSpace()->GetImageFilename();
  const char* boot_image_option = boot_image_option_string.c_str();
  std::string dex_file_option_string("--dex-file=");
  // 设置需要优化的dex文件的路径字符串
  dex_file_option_string += dex_filename;
  const char* dex_file_option = dex_file_option_string.c_str();
  std::string oat_fd_option_string("--oat-fd=");
  // 设置被优化的oat文件的文件指针
  StringAppendF(&oat_fd_option_string, "%d", oat_fd);
  const char* oat_fd_option = oat_fd_option_string.c_str();
  std::string oat_location_option_string("--oat-location=");
  // 设置被优化后的dex文件oat的存放路径
  oat_location_option_string += oat_cache_filename;
  const char* oat_location_option = oat_location_option_string.c_str();
  std::string oat_compiler_filter_string("-compiler-filter:");
  // 设置编译选项参数
  switch (Runtime::Current()->GetCompilerFilter()) {
    case Runtime::kInterpretOnly:
      oat_compiler_filter_string += "interpret-only";
      break;
    case Runtime::kSpace:
      oat_compiler_filter_string += "space";
      break;
    case Runtime::kBalanced:
      oat_compiler_filter_string += "balanced";
      break;
    case Runtime::kSpeed:
      oat_compiler_filter_string += "speed";
      break;
    case Runtime::kEverything:
      oat_compiler_filter_string += "everything";
      break;
    default:
      LOG(FATAL) << "Unexpected case.";
  }
  const char* oat_compiler_filter_option = oat_compiler_filter_string.c_str();
  // fork and exec dex2oat
  // fork新进程对dex文件进行优化处理
  pid_t pid = fork();
  if (pid == 0) {
    // no allocation allowed between fork and exec
    // change process groups, so we don't get reaped by ProcessManager
    setpgid(0, 0);
    // gLogVerbosity.class_linker = true;
    VLOG(class_linker) << dex2oat
                       << " --runtime-arg -Xms64m"
                       << " --runtime-arg -Xmx64m"
                       << " --runtime-arg -classpath"
                       << " --runtime-arg " << class_path
                       << " --runtime-arg " << oat_compiler_filter_option
#if !defined(ART_TARGET)
                       << " --host"
#endif
                       << " " << boot_image_option
                       << " " << dex_file_option
                       << " " << oat_fd_option
                       << " " << oat_location_option;
    // 创建dex2oat进程对dex文件进行优化为oat文件
    execl(dex2oat, dex2oat,
          "--runtime-arg", "-Xms64m",
          "--runtime-arg", "-Xmx64m",
          "--runtime-arg", "-classpath",
          "--runtime-arg", class_path,
          "--runtime-arg", oat_compiler_filter_option,
#if !defined(ART_TARGET)
          "--host",
#endif
          boot_image_option, // imageSpace文件的路径
          dex_file_option, // 被优化的dex文件的路径
          oat_fd_option, // 优化后oat文件的文件指针
          oat_location_option, // 优化后dex文件oat的存放文件路径
          NULL);
    PLOG(FATAL) << "execl(" << dex2oat << ") failed";
    return false;
  } else {
    // wait for dex2oat to finish
    // 等待dex文件被优化为oat成功完成
    int status;
    pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
    if (got_pid != pid) {
      PLOG(ERROR) << "waitpid failed: wanted " << pid << ", got " << got_pid;
      return false;
    }
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
      LOG(ERROR) << dex2oat << " failed with dex-file=" << dex_filename;
      return false;
    }
  }
  return true;
}ART模式下基于dex2oat脱壳就是在创建dex2oat进程进行dex文件优化具体处理之前进行dex文件的内存dump处理,实现Android加固的dex文件脱壳。以Android 4.4.4 r1的源码为例,ART虚拟机模式下,dex文件的优化处理进程dex2oat的源码实现在文件 /art/dex2oat/dex2oat.cc  中,在调用
dex2oat函数 进行dex的优化之前会先判断dex文件是否可写。因此,选择在这个代码点进行原始dex文件的内存dump处理。
http://androidxref.com/4.4.4_r1/xref/art/dex2oat/dex2oat.cc
Dex2oatHunter脱壳工具添加的dump dex文件的代码如下所示,主要是针对早些时候的360加固和腾讯乐加固的脱壳处理。Dex2oatHunter脱壳工具作者提供的脱壳代码是应用在Android 4.4系统的ART虚拟机模式下的,因此在编译后修改后的dex2aot程序后,在进行360加固和腾讯乐加固的脱壳时,需要将Android系统切换到ART虚拟机模式下才能生效。
https://github.com/spriteviki/Dex2oatHunter/blob/master/art/dex2oat/dex2oat.cc
    for (const auto& dex_file : dex_files) {
      if (!dex_file->EnableWrite()) {
        PLOG(ERROR) << "Failed to make .dex file writeable '" << dex_file->GetLocation() << "'\n";
      }
	  ///////////////////////////////////////////
      std::string dex_name = dex_file->GetLocation();
      LOG(INFO) << "Finding:dex file name-->" << dex_name;
	  // dump 360加固宝的dex文件
      if (dex_name.find("jiagu") != std::string::npos)
      {
       LOG(INFO) << "Finding:dex file from qihoo-->" << dex_name;
          int len = dex_file->Size();
          char filename[256] = {0};
          sprintf(filename, "%s_%d.dex", dex_name.c_str(), len);
          int fd = open(filename , O_WRONLY | O_CREAT | O_TRUNC , S_IRWXU);
          if (fd > 0)
          {
              if (write(fd, (char*)dex_file->Begin(), len) <= 0)
              {
                 LOG(INFO) << "Finding:write target dex file failed-->" << filename;
              }
                 LOG(INFO) << "Finding:write target dex file successfully-->" << filename;
             close(fd);
          }else
          {
             LOG(INFO) << "Finding:open target dex file failed-->" << filename;
          }
      }
	  // dump 腾讯乐固的dex文件
      if (tx_oat_filename.find("libshellc") != std::string::npos)
      {
       LOG(INFO) << "Finding:dex file from legu-->" << dex_name;
          int len = dex_file->Size();
          char filename[256] = {0};
          sprintf(filename, "%s_%d.dex", tx_oat_filename.c_str(), len);
          int fd = open(filename , O_WRONLY | O_CREAT | O_TRUNC , S_IRWXU);
          if (fd > 0)
          {
              if (write(fd, (char*)dex_file->Begin(), len) <= 0)
              {
                 LOG(INFO) << "Finding:write target dex file failed-->" << filename;
              }
                 LOG(INFO) << "Finding:write target dex file successfully-->" << filename;
             close(fd);
          }else
          {
             LOG(INFO) << "Finding:open target dex file failed-->" << filename;
          }
		  ///////////////////////////////////////////
      }
    }经过测试,上面添加的脱壳代码在Android 5.1系统的源码上也适用,并且修改的代码点位置也是这个地方。尽管上面添加的脱壳代码能对360加固和腾讯乐固进行dump脱壳,但是感觉灵活性不够,只能对360加固和腾讯加固进行处理,过滤的字符串是固定的,一旦修改后的dex2oat程序编译好,过滤字符串一改变又得重新编译dex2oat程序,重新打包Android的镜像文件。基于这些麻烦和通用性不够的问题,对上面的代码进行了修改,增加了脱壳的通用性,动态的配置脱壳的过滤字符串,代码实现如下。在没有设置脱壳过滤字符串的情况下,默认只支持对360加固进行脱壳;如果需要配置脱壳过滤字符串,可以构建和编辑 
 /data/dex_dump_filter 配置文件 在Android 5.1 的系统源码上测试通过。
    /******Android加固dex文件的dump*******/
    // 获取被优化的Android dex文件的文件路径
    std::string dex_file_name = dex_file->GetLocation();
    LOG(INFO) << "Fly---get Dex File Name: " << dex_file_name;
    // dex dump的过滤词
    std::string str_dex_dump_filter;
    FILE *fp = NULL;
    std::string filter_path_name = "/data/dex_dump_filter";
    if (access(filter_path_name.c_str(), F_OK) == 0) {
      // 打开配置文件/data/dex_dump_filter
      fp = fopen("/data/dex_dump_filter", "r");
      if (fp == NULL) {
        LOG(INFO) << "Fly---get /data/dex_dump_filter ";
        // 默认dump 360加固dex的oat文件
        str_dex_dump_filter = ".jiagu";
       } else {
         char szFilterBuffer[128] = {0};
         // 读取文件中的第1行字符串---dex dump的过滤词
         fgets(szFilterBuffer, strlen(szFilterBuffer), fp);
         szFilterBuffer[strlen(szFilterBuffer) - 1]=0;
         // 进行字符串的拷贝
         str_dex_dump_filter.copy(szFilterBuffer, 0, strlen(szFilterBuffer) - 1);
         fclose(fp);
         fp = NULL;
       }
    } else {
      // 默认dump 360加固dex的oat文件
      str_dex_dump_filter = ".jiagu";
    }
    // 进行dex dump的过滤
    if (dex_file_name.find(str_dex_dump_filter.c_str()) != std::string::npos) {
       // 获取优化后的oat文件的大小
       int nLenth = dex_file->Size();
       char szBuffer[256] = {0};
       // 格式化字符串得到dump的dex文件(OAT)的名称
       sprintf(szBuffer, "%s_%d.dex", dex_file_name.c_str(), nLenth);
       // 打开或者创建文件
       int fopen = open(szBuffer, O_WRONLY | O_CREAT | O_TRUNC , S_IRWXU);
       // 判断文件是否打开成功
       if (fopen > 0) {
         // 将优化后dex的oat文件保存到指定名称文件中。
         if (write(fopen, (char*)(dex_file->Begin()), nLenth) <= 0) {
           LOG(INFO) << "Fly---write target dex file failed: " << szBuffer;
         } else {
           // 将优化后dex的oat文件保存到dex文件优化目录下成功
           LOG(INFO) << "Fly---write target dex file OK: " << szBuffer;
         }
         // 关闭文件
         close(fopen);
        } else {
         LOG(INFO) << "Fly---open target dex file failed: " << szBuffer;
        }
    }
    /******Android加固dex文件的dump*******/ART虚拟机模式下基于dex2oat脱壳是有必要条件的:1. 需要脱壳的Android加固应用必须运行ART虚拟机模式下;2.需要脱壳的Android加固应用通过DexClassLoader加载的dex文件必须是没有经过优化处理的,比如 梆梆加固加载的dex文件是已经经过优化处理的oat文件,因此针对这类情况的Android加固应用使用ART虚拟机模式下基于dex2oat脱壳是无效的。
在Android 5.1系统的源码情况下,按照上面我提供的脱壳代码修改Android 5.1系统的 dex2oat的源码,然后编译生成Android 5.1模拟器的系统镜像文件,进行360加固Android应用的脱壳测试,结果如下图所示:
Android系统的 /data/data/com.emate.shop/.jiagu 目录下360加固 dump出来的dex文件截图如下:
经过验证dex文件发现 classes.dex_7766528.dex 文件 就是原始的dex文件,如下图:
后记:尽管这种脱壳的方法不是很通用也不能脱最新版的360加固和腾讯乐固了,但是提供了一种ART虚拟机模式下基于dex2oat脱壳的思路。知识很多,整理真的很需要时间~
ART模式下基于dex2oat脱壳的原理分析的更多相关文章
- ART模式下基于Xposed Hook开发脱壳工具
		本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78092365 Dalvik模式下的Android加固技术已经很成熟了,Dalvik ... 
- 基于Frida框架打造Art模式下的脱壳工具(OpenMemory)的原理分析
		本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956614 作者dstmath在看雪论坛公布一个Android的art模式下基 ... 
- Dalvik模式下基于Android运行时类加载的函数dexFindClass脱壳
		本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78003184 前段时间在看雪论坛发现了<发现一个安卓万能脱壳方法>这篇 ... 
- Android逆向进阶—— 脱壳的奥义(基ART模式下的dump)
		本文作者:i春秋作家HAI_ZHU 0×00 前言 市面上的资料大多都是基于Dalvik模式的dump,所以这此准备搞一个ART模式下的dump.HAI_的使用手册(各种好东西) Dalvik模式是A ... 
- Android平台dalvik模式下java Hook框架ddi的分析(1)
		本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75710411 一.前 言 在前面的博客中已经学习了作者crmulliner编写的, ... 
- Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用
		本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585 前面的博客<Android平台dalvik模式下java Ho ... 
- DEBUG模式下, 内存中的变量地址分析
		测试函数的模板实现 /// @file my_template.h /// @brief 测试数据类型用的模板实现 #ifndef MY_TEMPLATE_H_2016_0123_1226 #defi ... 
- spark yarn cluster模式下任务提交和计算流程分析
		spark可以运行在standalone,yarn,mesos等多种模式下,当前我们用的最普遍的是yarn模式,在yarn模式下又分为client和cluster.本文接下来将分析yarn clust ... 
- ubunut在系统恢复模式下无法修改root密码的分析和解决
		前些日子本猫的ubuntu 14.10貌似出了点问题,想修改下root密码,但是无奈原系统有错正常情况下无法修改啊,这是逼我重装的节奏吗? 在ubuntu开机后立即按住left_shift不放,调出g ... 
随机推荐
- C#使用OpenCV剪切图像中的圆形和矩形
			前言 本文主要介绍如何使用OpenCV剪切图像中的圆形和矩形. 准备工作 首先创建一个Wpf项目--WpfOpenCV,这里版本使用Framework4.7.2. 然后使用Nuget搜索[Emgu.C ... 
- 爬虫必知必会(4)_异步协程-selenium_模拟登陆
			一.单线程+多任务异步协程(推荐) 协程:对象.可以把协程当做是一个特殊的函数.如果一个函数的定义被async关键字所修饰.该特殊的函数被调用后函数内部的程序语句不会被立即执行,而是会返回一个协程对象 ... 
- C# 基础 - 日志捕获二使用 log4net
			引入 log4net.dll 项目->添加->新建项->应用程序配置文件,命名为 log4net.config,并把属性的复制到输出目录设置为 如果较新则复制,后续客户端需要读取在 ... 
- redhat配置问题
			redhat开机自动连接网络配置 vim /etc/sysconfig/network-scripts/ifcfg-eth0 将 ONBOOT=no 更改为 yes 即可 redhat配置 yum ... 
- 2019 GDUT Rating Contest II : Problem B. Hoofball
			题面: 传送门 B. Hoofball Input file: standard input Output file: standard output Time limit: 5 second Memor ... 
- Python读写配置文件模块--Configobj
			一.介绍 我们在项目的开发过程中应该会遇到这样的问题:我们的项目读取某个配置文件,然后才能按照配置的信息正常运行服务,当我们需要对修改服务的某些信息时,可以直接修改这个配置文件,重启服务即可,不用再去 ... 
- 攻防世界 reverse 进阶 10 Reverse Box
			攻防世界中此题信息未给全,题目来源为[TWCTF-2016:Reverse] Reverse Box 网上有很多wp是使用gdb脚本,这里找到一个本地还原关键算法,然后再爆破的 https://www ... 
- Java系列教程-MyBatis 3.5.5 教程目录
			MyBatis 3.5.5 初级教程目录 可参考MyBatis的官方文档也比较清楚 https://mybatis.org/mybatis-3/zh/getting-started.html 代码 目 ... 
- Android Studio 之 RadioButton
			•任务 如何通过 RadioButton 实现如图所示的界面? •基本用法 RadioButton 单选按钮,就是只能够选中一个,所以我们需要把 RadioButton 放到 RadioGroup 按 ... 
- 如何快速编写一个微信Api?
			概述 Magicodes.Wx.Sdk致力于打造最简洁最易于使用的微信Sdk,逐步包括公众号Sdk.小程序Sdk.企业微信Sdk等,以及Abp VNext集成. 本篇将侧重于讲述如何向Magicode ... 
