1.前言
今天来分析一下自动脱壳机Youpk的源码,Youpk是一个可以解决整体壳、抽取壳的脱壳机,可以解决自解密型的抽取壳。
2.源码分析
2.1 创建脱壳线程
frameworks/base/core/java/android/app/ActivityThread.java
函数:handleBindApplication
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
这里选择这个函数,是因为Android系统的执行流程,都要经过这个函数,且与FART的选择点有区别
art/runtime/unpacker/unpacker.cc
函数:unpack
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
我们可以依次看见4个功能模块:
1 2 3 4
|
(1)初始化 (2)dump所有dex (3)主动调用所有方法 (4)还原
|
2.2 初始化init()
unpacker/unpacker.cc
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
在data/data/process
目录下新建三个文件夹dex
、method
、unpacker.json
getDexFiles:
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
内存中dump DEX,DEX文件在art虚拟机中使用DexFile对象表示, 而ClassLinker中引用了这些对象, 因此可以采用从ClassLinker中遍历DexFile对象并dump的方式来获取
注意:因为从内存中dump,所以无法针对动态加载的壳进行解决,因为这类壳需要完成类加载器的修正,而youpk就没有完成该部分的工作
另外, 为了避免dex做任何形式的优化影响dump下来的dex文件, 在dex2oat中设置 CompilerFilter 为仅验证
1 2
|
//dex2oat.cc compiler_options_->SetCompilerFilter(CompilerFilter::kVerifyAtRuntime);
|
getAppClassLoader:
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
通过反射获得当前的类加载器
2.3 dump所有dex DumpAllDexs()
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
1 2 3 4
|
代码解析: (1)对dexs文件进行Dump,根据begin和大小 (2)并且对抗了dex魔术字段被抹除的形式 可借鉴 (3)成功的保存 可改进
|
功能:对Dexfile的文件进行Dump
2.4 主动调用链invokeAllMethods()
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
1 2
|
代码解析: (1)获得Dex的位置、dump路径、类大小
|
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
如果路径、位置、大小都相等,则说明是处理过后的dex
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
这里对init
进行处理
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
将一些dex的信息保存到Json中
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
遍历dex的所有ClassDef
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
读取一些函数的状态
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
对函数初始化进行处理,对未经过初始化的函数进行初始化
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
开启虚假的调用
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
对方法进行遍历,对非抽象方法或非静态方法进行模拟调用
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
关闭模拟调用开关,然后将状态进行修改
2.5 系统源码
artmethod.cc
Invoke()函数:修改
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
如果是主动调用,并且不是native方法就强制走解释器
如果是主动调用并且是native方法则不执行
interpreter.cc
EnterInterpreterFromInvoke()函数
1 2 3
|
void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receiver, uint32_t* args, JValue* result, bool stay_in_interpreter) {
|
在这里面函数youpk并未进行处理
但是这里youpk强制修改了解释器:
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
Execute()函数:
1 2 3 4 5 6
|
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_) {
|
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
interpreter_switch_impl.cc
ExecuteSwitchImpl()函数:修改
1 2 3
|
JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register, bool interpret_one_instruction) {
|
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
可以发现当程序进入Switch进行翻译后,翻译每条指令之前,都会执行PREAMBLE()方法,而Youpk就对这里进行了修改,这里与FARTExt区别的地方,就是这里是利用了解析器的判断:
PREAMBLE()函数:修改
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
在程序翻译之前都要使用beforeInstructionExecute
进行判断,这里就是Youpk深度调用链的地方,处理(NOP、Goto)等壳的地方,如果已经dump了就直接返回,这里程序结束,这里可以进行改进
unpacker.cc
beforeInstructionExecute()函数:修改
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
|
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);
if (inst_count == 0 && opcode != Instruction::GOTO && opcode != Instruction::GOTO_16 && opcode != Instruction::GOTO_32) { Unpacker::dumpMethod(method); return true; } 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) { 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; }
|
1 2 3
|
(1)对于一般的方法,直接进行Dump,然后返回ture,不需要继续运行 (2)对于特殊类型的壳,返回false,让其真正的运行起来 (3)运行过INVOKE_STATIC_RANGE,需要将程序真正的执行,然后再判断GOTO进行Dump
|
dumpMethod()函数:
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
|
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)); } }
|
这里面进行了dex的重构,然后直接进行dump
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
switch解释完,这里再进行一次判断
1 2 3 4 5 6 7 8 9 10 11 12
|
bool Unpacker::afterInstructionExecute(Thread *self, ArtMethod *method, uint32_t dex_pc, int inst_count) { 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); if (inst_count == 2 && (opcode == Instruction::INVOKE_STATIC || opcode == Instruction::INVOKE_STATIC_RANGE) && Unpacker::isRealInvoke(self, method)) { Unpacker::enableFakeInvoke(); Unpacker::disableRealInvoke(); } return false; }
|
这里就看到每个指令都执行了PREAMBLE函数。然后每个指令执行完都执行了afterInstructionExecute这个函数。在这里就可以判断,如果执行完的指令是INVOKE_STATIC。就可以直接return结束掉函数执行了。
整体通过enableFakeInvoke和disableRealInvoke来控制下一个指令执行的时候来进行退出函数
2.6 还原fini()
![Android加壳与脱壳(8)——Youpk脱壳机源码分析]()
将虚拟执行和真实执行,以及json一些参数恢复到起始状态,到这里整个脱壳机的源码则分析完成
3.总结
本文分析了抽取壳的自动脱壳机youpk的源码,为后续进一步学习加壳和脱壳做铺垫,相关资料存放知识星球和微信公众号。
- source:security-kitchen.com
评论