Youpk 是一个针对整体加固和Dex抽取加固壳的脱壳机

主要是基于虚拟机的,也就是基于VA的脱壳机, 相对FART出来的更晚一些, 厂商针对少一些, 脱壳位置相对更底层一些,还提供了Dex修复的工具,简直棒棒

1. 先分析整体脱壳的原理

在ActivityThread 的 handleBindApplication 中增加了代码

也就是说,在应用的启动流程中,在makeApplication后,就开始干活

Unpacker.java -> unpack

 public static void unpack() {
if (Unpacker.unpackerThread != null) {
return;
} if (!shouldUnpack()) {
return;
} //开启线程调用
Unpacker.unpackerThread = new Thread() {
@Override public void run() {
while (true) {
try {
Thread.sleep(UNPACK_INTERVAL);
}
catch (InterruptedException e) {
e.printStackTrace();
}
Unpacker.unpackNative();
}
}
};
Unpacker.unpackerThread.start();
}

这里开启一个线程,每一段时间就执行一下native的unpackNative

对应的是unpacker.cc

//注册native方法

static void Unpacker_unpackNative(JNIEnv*, jclass) {
Unpacker::unpack();
}
.... void Unpacker::unpack() {
ScopedObjectAccess soa(Thread::Current());
ULOGI("%s", "unpack begin!");
//1. 初始化
init();
//2. dump所有dex
dumpAllDexes();
//3. 主动调用所有方法
invokeAllMethods();
//4. 还原
fini();
ULOGI("%s", "unpack end!");
}

init() 主要是初始化工作,比如建立dump的目录,寻找需要dump的dex

void Unpacker::init() {
Unpacker_fake_invoke_ = false;
Unpacker_self_ = Thread::Current();
Unpacker_dump_dir_ = getDumpDir();
mkdir(Unpacker_dump_dir_.c_str(), 0777);
Unpacker_dex_dir_ = getDumpDir() + "/dex";
mkdir(Unpacker_dex_dir_.c_str(), 0777);
Unpacker_method_dir_ = getDumpDir() + "/method";
mkdir(Unpacker_method_dir_.c_str(), 0777);
Unpacker_json_path_ = getDumpDir() + "/unpacker.json";
Unpacker_json_fd_ = -1;
Unpacker_json_fd_ = open(Unpacker_json_path_.c_str(), O_RDWR | O_CREAT, 0777);
if (Unpacker_json_fd_ == -1) {
ULOGE("open %s error: %s", Unpacker_json_path_.c_str(), strerror(errno));
}
Unpacker_json_ = parseJson();
if (Unpacker_json_ == nullptr) {
Unpacker_json_ = createJson();
}
CHECK(Unpacker_json_ != nullptr); Unpacker_dex_files_ = getDexFiles();
Unpacker_class_loader_ = getAppClassLoader();
}

Unpacker_dex_files_ 在这里进行了寻找和赋值的操作

std::list<const DexFile*> Unpacker::getDexFiles() {
std::list<const DexFile*> dex_files;
Thread* const self = Thread::Current();
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
ReaderMutexLock mu(self, *class_linker->DexLock());
const std::list<ClassLinker::DexCacheData>& dex_caches = class_linker->GetDexCachesData();
for (auto it = dex_caches.begin(); it != dex_caches.end(); ++it) {
ClassLinker::DexCacheData data = *it;
const DexFile* dex_file = data.dex_file;
const std::string& dex_location = dex_file->GetLocation();
if (dex_location.rfind("/system/", 0) == 0) {
continue;
}
dex_files.push_back(dex_file);
}
return dex_files;
}

这里通过RunTime 拿到class_linker,然后通过classLinker来获得所有的Dex的指针(看得出作者对虚拟机有比较深的研究)

dumpAllDexes();就是我们整体dump的逻辑所在

void Unpacker::dumpAllDexes() {
for (const DexFile* dex_file : Unpacker_dex_files_) {
std::string dump_path = getDexDumpPath(dex_file);
if (access(dump_path.c_str(), F_OK) != -1) {
ULOGI("%s already dumped, ignored", dump_path.c_str());
continue;
}
const uint8_t* begin = dex_file->Begin();
size_t size = dex_file->Size();
int fd = open(dump_path.c_str(), O_RDWR | O_CREAT, 0777);
if (fd == -1) {
ULOGE("open %s error: %s", dump_path.c_str(), strerror(errno));
continue;
} std::vector<uint8_t> data(size);
memcpy(data.data(), "dex\n035", 8);
memcpy(data.data() + 8, begin + 8, size - 8); size_t written_size = write(fd, data.data(), size);
if (written_size < size) {
ULOGW("fwrite %s %zu/%zu error: %s", dump_path.c_str(), written_size, size, strerror(errno));
}
close(fd);
ULOGI("dump dex %s to %s successful!", dex_file->GetLocation().c_str(), dump_path.c_str());
}
}

整体dump最终把数据写入到了.dex文件中(还做了一个dex文件前缀魔数修复)

2. 再看对抽取壳的处理

首先是构建主动调用链,来欺骗壳,使壳进行函数指令填充

对应的就是 unpack方法中的第三步

//3. 主动调用所有方法

invokeAllMethods();

注意标志的六种状态
//dump类的六种status:
//Ready: 该类准备dump
//Resolved: ResolveClass成功
//ResolveClassFailed: ResolveClass失败
//Inited: EnsureInitialized成功
//EnsureInitializedFailed: EnsureInitialized失败
//Dumped: dump所有method成功

整体来说分两步,

一: 往unpacker.json里写每个方法的关键元数据

...
if (dex == nullptr) {
dex = cJSON_CreateObject();
cJSON_AddStringToObject(dex, "location", dex_file->GetLocation().c_str());
cJSON_AddStringToObject(dex, "dump_path", getDexDumpPath(dex_file).c_str());
cJSON_AddNumberToObject(dex, "class_size", dex_file->NumClassDefs());
current = cJSON_AddObjectToObject(dex, "current");
cJSON_AddNumberToObject(current, "index", class_idx);
cJSON_AddStringToObject(current, "descriptor", dex_file->GetClassDescriptor(dex_file->GetClassDef(class_idx)));
cJSON_AddStringToObject(current, "status", "Ready");
failures = cJSON_AddArrayToObject(dex, "failures");
cJSON_AddItemToArray(dexes, dex);
}
...

记录着dex的位置,dex整体dump下来的位置,有多少个class,class的id等等数据.方便后续codeitem.bin和整体dump的dex进行融合的操作

二: 构造参数发起主动调用

std::string Unpacker::getMethodDumpPath(ArtMethod* method) {
CHECK(method->GetDeclaringClass() != nullptr) << method;
const DexFile& dex_file = method->GetDeclaringClass()->GetDexFile();
std::string dex_location = dex_file.GetLocation();
size_t size = dex_file.Size();
//替换windows文件不支持的字符
for (size_t i = 0; i < dex_location.length(); i++) {
if (dex_location[i] == '/' || dex_location[i] == ':') {
dex_location[i] = '_';
}
}
std::string dump_path = Unpacker_method_dir_ + "/" + dex_location;
dump_path += StringPrintf("_%zu_codeitem.bin", size);
return dump_path;
}

从这里可以看出,函数的元数据写入到unpacker.json,而函数的codeItem(即指令数据),写入到了xxx_codeitem.bin的文件中,方便后续函数修复使用

三 获得 classDef后发起对class所有方法的主动调用()

// 前面还有一步主动初始化,
...
size_t pointer_size = class_linker->GetImagePointerSize();
auto methods = klass->GetDeclaredMethods(pointer_size); Unpacker::enableFakeInvoke();
for (auto& m : methods) {
ArtMethod* method = &m;
if (!method->IsProxyMethod() && method->IsInvokable()) {
uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(method->GetShorty());
if (!method->IsStatic()) {
args_size += 1;
} JValue result;
std::vector<uint32_t> args(args_size, 0);
if (!method->IsStatic()) {
mirror::Object* thiz = klass->AllocObject(self);
args[0] = StackReference<mirror::Object>::FromMirrorPtr(thiz).AsVRegValue();
}
// 重点这里
method->Invoke(self, args.data(), args_size, &result, method->GetShorty());
}
}
Unpacker::disableFakeInvoke(); cJSON_ReplaceItemInObject(current, "status", cJSON_CreateString("Dumped"));
writeJson();
...

四 发起invoke后,会走到java解释器中(youpk 强制走switch解释器), youpk修改了其中的一个宏

interpreter_switch_impl.cc

#define PREAMBLE()                                                                              \
do { \
inst_count++; \
bool dumped = Unpacker::beforeInstructionExecute(self, shadow_frame.GetMethod(), \
dex_pc, inst_count); \
if (dumped) { \
return JValue(); \
} \
if (UNLIKELY(instrumentation->HasDexPcListeners())) { \
instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), \
shadow_frame.GetMethod(), dex_pc); \
} \
} while (false)

这个宏在每个指令执行前都会调用,那么就一定会执行到 Unpacker::beforeInstructionExecute,在这里发起了对method的codeitem的dump操作

bool Unpacker::beforeInstructionExecute(Thread *self, ArtMethod *method, uint32_t dex_pc, int inst_count) {
if (Unpacker::isFakeInvoke(self, method)) {
const uint16_t* const insns = method->GetCodeItem()->insns_;
const Instruction* inst = Instruction::At(insns + dex_pc);
uint16_t inst_data = inst->Fetch16(0);
Instruction::Code opcode = inst->Opcode(inst_data); //对于一般的方法抽取(非ijiami, najia), 直接在第一条指令处dump即可
if (inst_count == 0 && opcode != Instruction::GOTO && opcode != Instruction::GOTO_16 && opcode != Instruction::GOTO_32) {
Unpacker::dumpMethod(method);
return true;
}
//ijiami, najia的特征为: goto: goto_decrypt; nop; ... ; return; const vx, n; invoke-static xxx; goto: goto_origin;
else if (inst_count == 0 && opcode >= Instruction::GOTO && opcode <= Instruction::GOTO_32) {
return false;
} else if (inst_count == 1 && opcode >= Instruction::CONST_4 && opcode <= Instruction::CONST_WIDE_HIGH16) {
return false;
} else if (inst_count == 2 && (opcode == Instruction::INVOKE_STATIC || opcode == Instruction::INVOKE_STATIC_RANGE)) {
//让这条指令真正的执行
Unpacker::disableFakeInvoke();
Unpacker::enableRealInvoke();
return false;
} else if (inst_count == 3) {
if (opcode >= Instruction::GOTO && opcode <= Instruction::GOTO_32) {
//写入时将第一条GOTO用nop填充
const Instruction* inst_first = Instruction::At(insns);
Instruction::Code first_opcode = inst_first->Opcode(inst->Fetch16(0));
CHECK(first_opcode >= Instruction::GOTO && first_opcode <= Instruction::GOTO_32);
ULOGD("found najia/ijiami %s", PrettyMethod(method).c_str());
switch (first_opcode)
{
case Instruction::GOTO:
Unpacker::dumpMethod(method, 2);
break;
case Instruction::GOTO_16:
Unpacker::dumpMethod(method, 4);
break;
case Instruction::GOTO_32:
Unpacker::dumpMethod(method, 8);
break;
default:
break;
}
} else {
Unpacker::dumpMethod(method);
}
return true;
}
Unpacker::dumpMethod(method);
return true;
}
return false;
}

从这里可以看到,它即可一脱一般的抽取壳,也可以脱那种goto类型(ijiami, najia)的抽取壳,最终会走到

dumpMethod

void Unpacker::dumpMethod(ArtMethod *method, int nop_size) {
std::string dump_path = Unpacker::getMethodDumpPath(method);
int fd = -1;
if (Unpacker_method_fds_.find(dump_path) != Unpacker_method_fds_.end()) {
fd = Unpacker_method_fds_[dump_path];
}
else {
fd = open(dump_path.c_str(), O_RDWR | O_CREAT | O_APPEND, 0777);
if (fd == -1) {
ULOGE("open %s error: %s", dump_path.c_str(), strerror(errno));
return;
}
Unpacker_method_fds_[dump_path] = fd;
} uint32_t index = method->GetDexMethodIndex();
std::string str_name = PrettyMethod(method);
const char* name = str_name.c_str();
const DexFile::CodeItem* code_item = method->GetCodeItem();
uint32_t code_item_size = (uint32_t)Unpacker::getCodeItemSize(method); size_t total_size = 4 + strlen(name) + 1 + 4 + code_item_size;
std::vector<uint8_t> data(total_size);
uint8_t* buf = data.data();
memcpy(buf, &index, 4);
buf += 4;
memcpy(buf, name, strlen(name) + 1);
buf += strlen(name) + 1;
memcpy(buf, &code_item_size, 4);
buf += 4;
memcpy(buf, code_item, code_item_size);
if (nop_size != 0) {
memset(buf + offsetof(DexFile::CodeItem, insns_), 0, nop_size);
} ssize_t written_size = write(fd, data.data(), total_size);
if (written_size > (ssize_t)total_size) {
ULOGW("write %s in %s %zd/%zu error: %s", PrettyMethod(method).c_str(), dump_path.c_str(), written_size, total_size, strerror(errno));
}
}

这里就是把数据按照固定的格式把数据写入到.bin文件中

脱壳完成

3. dex修复

一 adb pull出dump文件, dump文件路径为 /data/data/包名/unpacker

adb pull /data/data/xxx.xxx.myxxxdemo/unpacker

二 调用修复工具 dexfixer.jar, 两个参数, 第一个为dump文件目录(必须为有效路径), 第二个为重组后的DEX目录(不存在将会创建)

youpk 比较爽的就是这里提供了修复的jar(还有源码),而fart的只是一个修复对比文件,未真正修复到dex中

java -jar dexfixer.jar /path/to/unpacker /path/to/output

完成dex的修复

Youpk 脱壳机脱壳原理分析的更多相关文章

  1. android脱壳之DexExtractor原理分析[zhuan]

    http://www.cnblogs.com/jiaoxiake/p/6818786.html内容如下 导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的 ...

  2. android脱壳之DexExtractor原理分析

    导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, ...

  3. android黑科技系列——修改锁屏密码和恶意锁机样本原理分析

    一.Android中加密算法 上一篇文章已经介绍了Android中系统锁屏密码算法原理,这里在来总结说一下: 第一种:输入密码算法 将输入的明文密码+设备的salt值,然后操作MD5和SHA1之后在转 ...

  4. DexHunter在Dalvik虚拟机模式下的脱壳原理分析

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78494671 在前面的博客<DexHunter的原理分析和使用说明(一)&g ...

  5. drizzleDumper的原理分析和使用说明

    https://blog.csdn.net/qq1084283172/article/details/53561622 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...

  6. android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析

    android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析 导语: 笔者主要研究方向是网络通信协议的加密解密, 对应用程序加固脱壳技术很少研究, 脱壳壳经历更是经历少之甚少. ...

  7. Android FART脱壳机流程分析

    本文首发于安全客 链接:https://www.anquanke.com/post/id/219094 0x1 前言 在Android平台上,程序员编写的Java代码最终将被编译成字节码在Androi ...

  8. DexHunter在ART虚拟机模式下的脱壳原理分析

    本文博客地址: http://blog.csdn.net/qq1084283172/article/details/78494620 DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析 ...

  9. APK加固之静态脱壳机编写入门

    目录: 0x00APK加固简介与静态脱壳机的编写思路 1.大家都知道Android中的程序反编译比较简单,辛苦开发出一个APK轻易被人反编译了,所以现在就有很多APK加固的第三方平台,比如爱加密和梆梆 ...

  10. 菜鸟脱壳之脱壳的基础知识(二) ——DUMP的原理

    菜鸟脱壳之脱壳的基础知识(二)——DUMP的原理当外壳的执行完毕后,会跳到原来的程序的入口点,即Entry Point,也可以称作OEP!当一般加密强度不是很大的壳,会在壳的末尾有一个大的跨段,跳向O ...

随机推荐

  1. [转帖]Web性能优化工具WebPageTest(一)——总览与配置

    https://www.cnblogs.com/strick/p/6677836.html 网站性能优化工具大致分为两类:综合类和RUM类(实时监控用户类),WebPageTest属于综合类. Web ...

  2. 【转帖】50.设置HotSpot采用解释器还是JIT编译器(-Xint、-Xcomp、Xmixed以及-Server、-Client)

    目录 1.设置HotSpot 1.设置HotSpot 1.设置采用解释器还是JIT编译器 -Xint: 完全采用解释器模式执行程序. -Xcomp: 完全采用即时编译器模式执行程序.如果即时编译出现问 ...

  3. [转帖]cx_Oracle.DatabaseError: ORA-28040

    背景: python第三方库cx-Oracle连接Oracle数据库报错 ORA-28040 cx_Oracle.DatabaseError: ORA-28040: No matching authe ...

  4. [转帖]Linux中split大文件分割和cat合并文件详解

    https://www.yingsoo.com/news/servers/70195.html 当需要将较大的数据上传到服务器,或从服务器下载较大的日志文件时,往往会因为网络或其它原因而导致传输中断而 ...

  5. [转帖]一次操作系统报错OutOfMemory Error的处理记录

    在启动公司内嵌的tomcat容器时出现报错, 如下: # There is insufficient memory for the Java Runtime Environment to contin ...

  6. 对于Vue3和Ts的心得和思考

    作者:京东物流 吴云阔 1 前言 Vue3已经正式发布了一段时间了,各种生态已经成熟.最近使用taro+vue3重构冷链的小程序,经过了一段时间的开发和使用,有了一些自己的思考. 总的来说,Vue3无 ...

  7. 【字符串,哈希】【Yandex】Yandex7736

    2023.6.30 Problem Link 定义一个串 \(S\) 是好的,当且仅当 \(S\) 可以不断消去相邻两个相同字符直至消空.给定一个长为 \(n\) 的字符串 \(s\),求有多少个有序 ...

  8. Gitlab使用说明

          零.gitlab简介   Gitlab是一个成熟的代码管理工具.为企业和组织提供内部的源代码的存储和管理功能.         一.gitlab角色总览   gitlab中的角色分管理员和 ...

  9. 用webpack给js添加上版本号

    在网上查找了很多的资料. 都没有好的资源 因为我现在在项目是vuecli3.0 需要自己去创建文件 在项目的根目录下,创建一个文件vue.config.js 然后在该文件下写 const webpac ...

  10. Vue基础系统文章06---导入和导出

    一.导入和导出 如果想要在一个Js文件中用另一个js文件的代码 1.将js文件中的变量和函数导出 let a = "aaaa" function show() { console. ...