上文我们讨论了该如何脱壳,现在就开始实现吧。
本文不介绍如何编译源码,这块内容之前已经单独发过了,可以使用虚拟机或者 WSL,WSL体验要好些,虚拟机更方便。
看开源项目:
-
https://github.com/dqzg12300/FartExt
分析其实现思路,注意,如果需要自己编译源码,直接使用开源项目的代码是很容易被针对的,一定要将方法名等特征给改掉。
该项目的整体思路:
-
传递特殊的模拟参数,主动调用类方法
-
将指令解释器模式改为 switch 模式,这个模式源码比较简单。
-
在解释器执行指令的时候,判断是否为深度隐藏指令,如果是的话,对比指令特征直到触发真实指令后再 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 脱壳机了。
关注我的公众号:二手的程序员。