上文我们讨论了该如何脱壳,现在就开始实现吧。
本文不介绍如何编译源码,这块内容之前已经单独发过了,可以使用虚拟机或者 WSL,WSL体验要好些,虚拟机更方便。
看开源项目:
-
https://github.com/dqzg12300/FartExt
分析其实现思路,注意,如果需要自己编译源码,直接使用开源项目的代码是很容易被针对的,一定要将方法名等特征给改掉。
该项目的整体思路:
-
传递特殊的模拟参数,主动调用类方法
-
将指令解释器模式改为 switch 模式,这个模式源码比较简单。
-
在解释器执行指令的时候,判断是否为深度隐藏指令,如果是的话,对比指令特征直到触发真实指令后再 dump 方法,否则直接 dump 方法。
下面按文件顺序分析改动。
interpreter_switch_impl-inl.h
第一处
//addint32_t regvalue=ctx->result_register.GetI();ctx->result_register=JValue();int inst_count = -1;bool flag=false;//add end
初始化一些变量,后续使用。
regvalue 是获取的返回值寄存器的值,这个值是我们在别处设置的,固定为 111111。
第二处
//addinst_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。
第三处
//addif(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
第一处
//addextern "C" void dumpdexfilebyExecute(ArtMethod* artmethod);//addend
这里添加了一个方法,是一个特征,可以改成自己的名字。
第二处
//addif(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 选取的整体脱壳点。
第三处
//addif(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来进行参数的传递,而不去解引用我们构造的参数。
第四处
//addif(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
第四处
//addNATIVE_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
第二处
// adduint8_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;elselen = (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
第二处
//addFartext.fartthread();//add end
Fartext.java
整个文件都是新增的,就不贴代码了,自行查看。
主要就是获取了 ClassLoader 之后,遍历所有类的所有方法,并触发该方法:
loadClassAndInvoke(appClassloader, line, dumpMethodCode_method);
dumpMethodCode_method
就是上面在 DexFile.cc
中新加的 JNI 方法 fartextMethodCode
。
DexFile.java
第一处
//addprivate static native void fartextMethodCode(Object m);//add end
与 DexFile.cc 对应,形成一个 JNI 方法。
所有源码分析完成,看起来还是很清晰的,后续就是改名然后编译一个 release 脱壳机了。
关注我的公众号:二手的程序员。