FART是一个基于Android 源码修改的脱壳机

可以脱整体壳和抽取壳

FART脱壳的步骤主要分为三步:

1.内存中DexFile结构体完整dex的dump

2.主动调用类中的每一个方法,并实现对应CodeItem的dump

3.通过主动调用dump下来的方法的CodeItem进行dex中被抽取的方法的修复

1. 整体壳脱壳分析

这里利用的是dex2oat过程中,如果是构造函数将不会native化,还是走java解释器执行,于是在interpreter.cc中添加了如下代码

static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,

ShadowFrame& shadow_frame, JValue result_register) {

if(strstr(PrettyMethod(shadow_frame.GetMethod()).c_str(),"")!=nullptr)

{

dumpDexFileByExecute(shadow_frame.GetMethod());

}

......

}

也就是说,一旦发现方法是构造函数,那么就立刻执行脱壳操作

dumpDexFileByExecute在 art_method.cc 中

extern "C" void dumpDexFileByExecute(ArtMethod * artmethod)

SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {

char *dexfilepath = (char *) malloc(sizeof(char) * 2000);

if (dexfilepath == nullptr) {

LOG(INFO) <<

"ArtMethod::dumpDexFileByExecute,methodname:"

<< PrettyMethod(artmethod).

c_str() << "malloc 2000 byte failed";

return;

}

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) {

read(fcmdline, szProcName, 256);

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, 2000);

int size_int_ = (int) size_;

memset(dexfilepath, 0, 2000);

sprintf(dexfilepath, "%s", "/sdcard/fart");

mkdir(dexfilepath, 0777);

memset(dexfilepath, 0, 2000);

sprintf(dexfilepath, "/sdcard/fart/%s",

szProcName);

mkdir(dexfilepath, 0777);

memset(dexfilepath, 0, 2000);

sprintf(dexfilepath,

"/sdcard/fart/%s/%d_dexfile_execute.dex",

szProcName, size_int_);

int dexfilefp = open(dexfilepath, O_RDONLY, 0666);

if (dexfilefp > 0) {

close(dexfilefp);

dexfilefp = 0;

} else {

dexfilefp =

open(dexfilepath, O_CREAT | O_RDWR,

0666);

if (dexfilefp > 0) {

write(dexfilefp, (void *) begin_,

size_);

fsync(dexfilefp);

close(dexfilefp);

}

}

}

if (dexfilepath != nullptr) {

free(dexfilepath);

dexfilepath = nullptr;

}

}

脱下来的dex 会写入到xxx_dexfile_execute.dex 文件中, 这里的原理是 artMethod 本身是持有对应的DexFile的指针的,那么就有Dex在文件中的偏移和大小,就可以dump下来

( const DexFile *dex_file = artmethod->GetDexFile(); )

2. 脱抽取壳

在APP启动流程中会执行performLaunchActivity方法,在这里的末尾 FART 添加了一些代码

fartthread();

public static void fartthread() {

new Thread(new Runnable() {

@Override

public void run() {

try {

Log.e("ActivityThread", "start sleep,wait for fartthread start......");

Thread.sleep(1 * 60 * 1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Log.e("ActivityThread", "sleep over and start fartthread");

fart();

Log.e("ActivityThread", "fart run over");

}

}).start();

}

从这里可以看出,FART 会先休眠1分钟,然后开始干活,但只干一次, youpk 每十秒就干一次

public static void fart() {

ClassLoader appClassloader = getClassloader();

List dexFilesArray = new ArrayList();

Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");

Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");

Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");

Field dexFile_fileField = null;

try {

dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");

} catch (Exception e) {

e.printStackTrace();

}

Class DexFileClazz = null;

try {

DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");

} catch (Exception e) {

e.printStackTrace();

}

Method getClassNameList_method = null;

Method defineClass_method = null;

Method dumpDexFile_method = null;

Method dumpMethodCode_method = null;

for (Method field : DexFileClazz.getDeclaredMethods()) {

if (field.getName().equals("getClassNameList")) {

getClassNameList_method = field;

getClassNameList_method.setAccessible(true);

}

if (field.getName().equals("defineClassNative")) {

defineClass_method = field;

defineClass_method.setAccessible(true);

}

if (field.getName().equals("dumpMethodCode")) {

dumpMethodCode_method = field;

dumpMethodCode_method.setAccessible(true);

}

}

Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");

for (int j = 0; j < ElementsArray.length; j++) {

Object element = ElementsArray[j];

Object dexfile = null;

try {

dexfile = (Object) dexFile_fileField.get(element);

} catch (Exception e) {

e.printStackTrace();

}

if (dexfile == null) {

continue;

}

if (dexfile != null) {

dexFilesArray.add(dexfile);

Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");

if (mcookie == null) {

continue;

}

String[] classnames = null;

try {

classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);

} catch (Exception e) {

e.printStackTrace();

continue;

} catch (Error e) {

e.printStackTrace();

continue;

}

if (classnames != null) {

for (String eachclassname : classnames) {

loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);

}

}

}

}

return;

}

一 通过classLoader 拿到DexFile 的clazz对象

try {

DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");

} catch (Exception e) {

e.printStackTrace();

}

Method getClassNameList_method = null;

Method defineClass_method = null;

Method dumpDexFile_method = null;

Method dumpMethodCode_method = null;

for (Method field : DexFileClazz.getDeclaredMethods()) {

if (field.getName().equals("getClassNameList")) {

getClassNameList_method = field;

getClassNameList_method.setAccessible(true);

}

if (field.getName().equals("defineClassNative")) {

defineClass_method = field;

defineClass_method.setAccessible(true);

}

if (field.getName().equals("dumpMethodCode")) {

dumpMethodCode_method = field;

dumpMethodCode_method.setAccessible(true);

}

}

这里的defineClassNative和dumpMethodCode 为Fart 添加的函数

二 再通过classLoader 反射拿到对应的pathList 的 dexElements

ClassLoader appClassloader = getClassloader();

List dexFilesArray = new ArrayList();

Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");

Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");

Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");

dexElements 的元素就是一个个DexFile的引用

开始遍历dexElements

for (int j = 0; j < ElementsArray.length; j++) {

Object element = ElementsArray[j];

Object dexfile = null;

try {

dexfile = (Object) dexFile_fileField.get(element);

} catch (Exception e) {

e.printStackTrace();

}

if (dexfile == null) {

continue;

}

if (dexfile != null) {

dexFilesArray.add(dexfile);

Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");

if (mcookie == null) {

continue;

}

String[] classnames = null;

try {

classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);

} catch (Exception e) {

e.printStackTrace();

continue;

} catch (Error e) {

e.printStackTrace();

continue;

}

if (classnames != null) {

for (String eachclassname : classnames) {

loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);

}

}

}

}

最终会遍历Dex中的class 执行loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);

public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {

Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);

Class resultclass = null;

try {

resultclass = appClassloader.loadClass(eachclassname);

} catch (Exception e) {

e.printStackTrace();

return;

} catch (Error e) {

e.printStackTrace();

return;

}

if (resultclass != null) {

try {

Constructor cons[] = resultclass.getDeclaredConstructors();

for (Constructor constructor : cons) {

if (dumpMethodCode_method != null) {

try {

dumpMethodCode_method.invoke(null, constructor);

} catch (Exception e) {

e.printStackTrace();

continue;

} catch (Error e) {

e.printStackTrace();

continue;

}

} else {

Log.e("ActivityThread", "dumpMethodCode_method is null ");

}

}

} catch (Exception e) {

e.printStackTrace();

} catch (Error e) {

e.printStackTrace();

}

try {

Method[] methods = resultclass.getDeclaredMethods();

if (methods != null) {

for (Method m : methods) {

if (dumpMethodCode_method != null) {

try {

dumpMethodCode_method.invoke(null, m);

} catch (Exception e) {

e.printStackTrace();

continue;

} catch (Error e) {

e.printStackTrace();

continue;

}

} else {

Log.e("ActivityThread", "dumpMethodCode_method is null ");

}

}

}

} catch (Exception e) {

e.printStackTrace();

} catch (Error e) {

e.printStackTrace();

}

}

}

在这里会通过classloader 和类名称,来找到对应的clazz对象,并分别执行它的构造方法和普通方法

dumpMethodCode_method.invoke(null, constructor);

...

dumpMethodCode_method.invoke(null, m);

dumpMethodCode_method 对应的native 方法在dalvik_system_DexFile.cc中

static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method) {

ScopedFastNativeObjectAccess soa(env);

if(method!=nullptr)

{

ArtMethod* artmethod = ArtMethod::FromReflectedMethod(soa, method);

myfartInvoke(artmethod);

}

return;

}

这里将method对应转换为artmethod,然后执行myfartInvoke

art_method.cc

extern "C" void myfartInvoke(ArtMethod * artmethod)

SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {

JValue *result = nullptr;

Thread *self = nullptr;

uint32_t temp = 6;

uint32_t *args = &temp;

uint32_t args_size = 6;

artmethod->Invoke(self, args, args_size, result, "fart");

}

最终会执行到Invoke函数,在Invoke中fart做了一些修改

void ArtMethod::Invoke(Thread * self, uint32_t * args,

uint32_t args_size, JValue * result,

const char *shorty) {

if (self == nullptr) {

dumpArtMethod(this);

return;

}

.....

}

只要发现self值null(这里是fart故意埋下的特征),就执行脱壳操作,并不往下执行,达到欺骗的效果

extern "C" void dumpArtMethod(ArtMethod * artmethod)

SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {

char *dexfilepath = (char *) malloc(sizeof(char) * 2000);

if (dexfilepath == nullptr) {

LOG(INFO) <<

"ArtMethod::dumpArtMethodinvoked,methodname:"

<< PrettyMethod(artmethod).

c_str() << "malloc 2000 byte failed";

return;

}

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) {

read(fcmdline, szProcName, 256);

close(fcmdline);

}

if (szProcName[0]) {

const DexFile *dex_file = artmethod->GetDexFile();

const char *methodname =

PrettyMethod(artmethod).c_str();

const uint8_t *begin_ = dex_file->Begin();

size_t size_ = dex_file->Size();

memset(dexfilepath, 0, 2000);

int size_int_ = (int) size_;

memset(dexfilepath, 0, 2000);

sprintf(dexfilepath, "%s", "/sdcard/fart");

mkdir(dexfilepath, 0777);

memset(dexfilepath, 0, 2000);

sprintf(dexfilepath, "/sdcard/fart/%s",

szProcName);

mkdir(dexfilepath, 0777);

memset(dexfilepath, 0, 2000);

sprintf(dexfilepath,

"/sdcard/fart/%s/%d_dexfile.dex",

szProcName, size_int_);

int dexfilefp = open(dexfilepath, O_RDONLY, 0666);

if (dexfilefp > 0) {

close(dexfilefp);

dexfilefp = 0;

} else {

dexfilefp =

open(dexfilepath, O_CREAT | O_RDWR,

0666);

if (dexfilefp > 0) {

write(dexfilefp, (void *) begin_,

size_);

fsync(dexfilefp);

close(dexfilefp);

}

}

const DexFile::CodeItem * code_item =

artmethod->GetCodeItem();

if (LIKELY(code_item != nullptr)) {

int code_item_len = 0;

uint8_t *item = (uint8_t *) code_item;

if (code_item->tries_size_ > 0) {

const uint8_t *handler_data =

(const uint8_t *) (DexFile::

GetTryItems

(*code_item,

code_item->

tries_size_));

uint8_t *tail =

codeitem_end(&handler_data);

code_item_len =

(int) (tail - item);

} else {

code_item_len =

16 +

code_item->

insns_size_in_code_units_ * 2;

}

memset(dexfilepath, 0, 2000);

int size_int = (int) dex_file->Size(); // Length of data

uint32_t method_idx =

artmethod->get_method_idx();

sprintf(dexfilepath,

"/sdcard/fart/%s/%d_%ld.bin",

szProcName, size_int, gettidv1());

int fp2 =

open(dexfilepath,

O_CREAT | O_APPEND | O_RDWR,

0666);

if (fp2 > 0) {

lseek(fp2, 0, SEEK_END);

memset(dexfilepath, 0, 2000);

int offset = (int) (item - begin_);

sprintf(dexfilepath,

"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",

methodname, method_idx,

offset, code_item_len);

int contentlength = 0;

while (dexfilepath[contentlength]

!= 0)

contentlength++;

write(fp2, (void *) dexfilepath,

contentlength);

long outlen = 0;

char *base64result =

base64_encode((char *) item,

(long)

code_item_len,

&outlen);

write(fp2, base64result, outlen);

write(fp2, "};", 2);

fsync(fp2);

close(fp2);

if (base64result != nullptr) {

free(base64result);

base64result = nullptr;

}

}

}

}

if (dexfilepath != nullptr) {

free(dexfilepath);

dexfilepath = nullptr;

}

}

这里有两步

一 整体dump

const DexFile *dex_file = artmethod->GetDexFile()

拿到dex_file 整体dump到xxx_dexfile.dex中

二 dump code_item

const DexFile::CodeItem * code_item = artmethod->GetCodeItem();

按照一个 json格式写入到.bin文件中

json格式为

{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:%s}

ins为base64编码

到这里脱壳逻辑就完成了

FART还提供了 一个fart.py, 但好像执行生成一个合并前后的对比文件,没有真正将数据写入进dex中

相关推荐

正在阅读:wifi不小心点到隐藏了怎么弄回来【详解】wifi不小心点到隐藏了怎么弄回来【详解】
彩票365手机版官方下载

正在阅读:wifi不小心点到隐藏了怎么弄回来【详解】wifi不小心点到隐藏了怎么弄回来【详解】

qq群处于冻结期如何解决
约彩365ios不能下载了

qq群处于冻结期如何解决

GTA 5 中最值得购买的好车
约彩365ios不能下载了

GTA 5 中最值得购买的好车

《英雄联盟》个性战队名字大全
彩票365手机版官方下载

《英雄联盟》个性战队名字大全