将FART和Youpk结合来做一次针对函数抽取壳的全面提升
以及看雪高研3W班课程看完后的整理与优化
寒冰大佬的FART带动了不少新的主动调用思想的抽取壳方案。看了上面这篇文章,感觉意犹未尽,当然是要动手实践一翻来优化一波。于是我重新翻阅FART和Youpk的源码,我准备和那位大佬一样,参考Youpk在FART的基础上进行升级改造。开工前先明确出我的需求。
还未了解过的请看原作者对于fart的介绍
FART:ART环境下基于主动调用的自动化脱壳方案
FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法
拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点
关于fart源码的调用流程可以看看我以前整理的一篇文章
fart的理解和分析过程
简单总结:
这是一个基于主动调用来脱抽取壳的方案。
简述实现原理:
在进程启动的时候通过双亲委派机制遍历所有classloader,然后遍历里面的所有class,取出所有函数,直接调用。然后在ArtMethod的Invoke函数这里根据参数判断出这是主动调用触发的,然后就取消函数的正常执行,并执行脱壳操作。
首先看看FART源码中,脱壳线程的入口点ActivityThread.java的performLaunchActivity这个函数中开始的FART处理。
也就是说,所有的进程都会执行脱壳的流程。所以这个地方我觉得还是有必要优化的。应该是对指定的进程脱壳会更加好一些。Youpk中已经有了这个优化。所以我直接拿Youpk的处理来使用。下面是修改后的入口启动部分
这里主要是做了两点修改:
1、把启动FART线程的处理放到了handleBindApplication函数,这是因为performLaunchActivity这个函数调用有的时候可能会触发两次,而handleBindApplication确定只会触发一次。
2、FART线程启动前加了个判断,在配置文件中的进程才需要脱壳。这样基本第一个优化就完成了。
由于应用的有些类可能是被特殊处理了,主动调用的情况会导致程序崩溃或者退出。所以最好是可以单独对某些类进行主动调用。我调整了FART线程启动的逻辑,先是上面的判断是不是要脱壳的目标进程,然后判断有没有设定类列表,如果有类列表就只脱壳类列表,否则就完整主动调用
直接将FART修改的代码部分直接替换到AOSP10中。毫不意外的出现了一堆错误。不过问题比较集中。主要是对于CodeItem的成员访问方式发生了变化。这里可以参考下面的文章
Android ART 虚拟机 - dex 文件格式要旨
根据这篇文章中对CodeItem对象新的访问方式。对FART的源码部分做出修改
修改文件是art_method.cc。我这里只贴上部分关键修改的代码
需要修改的不止上面这几个地方。但是主要都是针对CodeItem的使用以及命名空间的修改。这里我就不全部贴出了。最后我会贴出改完后的版本。
由于FART的知名度还是挺高的,所以最好还是把FART中特有的一些函数名和文件保存路径给修改一下。下面整理下我参考Youpk做的一些修改。
1、将ActivityThread中的FART相关函数全部单独放到一个类cn.mik.Fartext中。这样如果别人对ActivityThread的函数检测就找不到FART相关的了。
2、将DexFile中的dumpMethodCode函数名修改为fartextMethodCode
3、将myfartInvoke函数名改成fartextInvoke
4、将所有使用/sdcard/fart的这个路径全部修改成/sdcard/fext
把这些常见的可能识别的方式都修改之后。一般就识别不出来了。我这种完全没知名度的,想必不会被人检测到了。暗自欣喜。
抽取壳的应用在脱壳后,有两种文件,一个是在当前时机dump出来的dex文件。另一个是保存codeitem出来的bin文件。
FART的修复组件是使用开源项目FART中那个py的脚本来解析dex文件,将bin的codeitem修复打印。对于里面的代码解析部分我之前也写了文章。感兴趣可以看看
dex起步探索
但是我仔细研究后,发现修复组件只进行了打印。并没有修复成dex,而是直接解析打印。最理想的还是修复到dex,方便使用静态分析工具查看。有大佬也已经写了这个工具。那就是前面参考的Youpk。他的内部有个dexfixer的目录,就是实现了对导出的codeitem数据修复到dex中。不过他的codeitem的保存结构和FART的并不大一样。不过没关系,修改一下codeitem文件的解析部分就好了。下面贴上Youpk和我修改后专门处理fart结果的dexfixer。
Youpk
dexfixer
首先当然是看看FART的主动调用的深度是在哪里,这里的深度其实就是在函数的主动执行过程中,FART是在执行到哪个流程时,进行的脱壳处理。下面贴上FART主动调用的脱壳位置
上面可以看到。当函数执行流程到达ArtMethod::Invoke时,就根据参数判断是主动调用的情况,就脱壳并结束了。
一般的函数抽取壳,在执行到ArtMethod::Invoke前就已经对抽取函数还原了。但是也有一些抽取壳,执行到Invoke时依然还没有还原函数。譬如下面这种抽取壳。
可以看到这个抽取的函数,进来之后就goto,然后执行invoke-static。接着在goto到函数的开始位置。
也就是说。这个抽取壳,必须在函数执行了之后,才会还原出真实的函数。回想一下前面说的FART的主动调用深度。发现函数真正执行前就已经被我们直接结束掉了。所以我们需要更深的主动调用才能够解决这个抽取壳。
我们回头看看上面的抽取壳,我们的目标是要判断如果这个函数的第一个指令是goto,就正常执行,然后执行到invoke-static的指令。这个指令完成之后就直接结束掉函数调用。避免真实函数调用会出现异常。
先参考Youpk的看看他是如何实现更深的主动调用来解决这个问题的。下面是第一步,先修改默认的解释器为Switch的解释器。这是因为Switch解释器的可读性更加高,方便我们直接修改源码来达到目的。
然后我们看看主动调用时Youpk是怎么模拟参数的。
这里可以看到Youpk的参数是模拟赋值进去的。而寒冰大佬的做法不大一样。看看FART的函数调用模拟。
这样肯定没法顺利往后执行。我们先继续参考Youpk的后续。
然后看看Youpk的ArtMethod::Invoke的处理,如果是主动调用并且非Native函数就正常执行。
接下来看解释器的EnterInterpreterFromInvoke函数处理。这里Youpk没有什么处理。
继续看看函数Execute。
然后这ExecuteSwitchImpl就是关键的解释指令的函数了。到这里终于有Youpk修改的部分了。先看看修改的代码
PREAMBLE这个函数基本每个指令执行前都会调用beforeInstructionExecute来判断下。如果这里dump脱壳了,就直接结束掉,这个函数不再往下执行了。如果是上面那种特殊壳,这里就可以暂时先不要dump。让他正常执行先。下面看看里面的逻辑处理
这里可以看到。如果是INVOKE_STATIC就让指令正常执行。其他正常的抽取壳的深度就是在这里。这相当于就是指令执行前进行dump了。但是这里依然没解决特殊壳的深度问题。必须执行完INVOKE_STATIC之后。再进行脱壳并结束掉函数。继续看Youpk下面的处理
这里就看到每个指令都执行了PREAMBLE函数。然后每个指令执行完都执行了afterInstructionExecute这个函数。在这里就可以判断,如果执行完的指令是INVOKE_STATIC。就可以直接return结束掉函数执行了。看看Youpk的处理
这里留意了一下。这个函数固定返回的false。但是通过设置enableFakeInvoke和disableRealInvoke来控制下一个指令执行的时候来进行退出函数。我感觉这里退出应该也没啥问题。
到这里基本就走完大致的流程了。那么欣赏完别人的代码。可以开始我们的改造工作了。
和Youpk一样。第一步就是先把解释器给改成使用Switch解释器。但是由于我使用的是AOSP10。所以发现修改部分果然不大一样了。
发现这里变成可以通过编译参数来控制的了。搜索一下ART_USE_CXX_INTERPRETER的使用
发现这个好像可以通过cflags来配置了。所以我修改了下runtime下的Android.pb。如果不想改全局的。也可以在源码里面直接判断是主动调用就强制走switch解释器。
接着就是ArtMethod::Invoke的时候不要直接结束了。但是这里我们需要留意的是。第一个参数的Thread是fart用来判断是否为主动调用的。为了让后面能正常执行,我就直接把第一个参数给赋值了。而后面的调用流程也是需要判断当前执行函数是否为主动调用。Youpk是用线程和一个变量来控制判断是否为主动调用的。这里使用result=111111在后续判断是否为主动调用
这里有个问题是上面这种模拟参数的方式,碰到引用类型的参数会报错。所以在处理参数入栈的时候,也要进行判断处理一下。
接下来就开始修改解释器部分的逻辑了。我们只要做到几点处理。就可以搞定这种壳了。
1、如果是主动调用并且第一个指令如果不是GOTO的。就直接脱壳并结束
2、如果是主动调用并且第一个指令是GOTO的。让他继续执行
3、如果第三个指令是INVOKE-STATIC的执行完后直接结束掉
接下来准备改代码。然后碰到一个问题。同样也是AOSP10的版本导致的。Switch解释器的逻辑发生了较大的变动。先看看变成了啥样子
看到了这两个部分都发生了较大的变化。那个超大的case都不见了。不过也只是处理的方式发生变化。我们跟着调整下就行了。
在测试FART的主动调用中发现,主动调用的耗时较长,根据上面的流程图。我们可以看到调用最耗时最核心的函数dumpArtMethod。就是在这里进行脱壳的。先看看FART里面做了什么
这里可以看到dump的规则是相同大小的dex就跳过。非相同的就写入文件保存。相当于算是一个整体脱壳非常晚的时机。不过这个时机的调用频率较多。相对会影响性能,好处是这个整体脱壳点的时机够晚,绝对能脱掉除抽取函数外的整体壳。而Youpk中的优化是不使用这个整体脱壳点,只单纯的把codeitem写入bin文件,这样就能提高一定的效率。这里我就暂时不修改了。毕竟在这里脱整体壳也有一定的优势。如果想要更快的速度。也可以选择过滤主动调用的范围,来降低调用频率。
编译完成之后,我们就可以来试试深度主动调用+dex修复的效果。为了防止风险,就不放测试的apk样本了。
安装好apk后,先去/data/local/tmp/fext.config中填入我们的目标进程。如果主动调用出现崩溃的情况。可以将class.txt的文件复制到/data/local/tmp/进程名称
来对指定的类进行主动调用。然后打开应用静静的等待脱壳结果。
或者是使用整合怪来对指定类进行处理
Ps1:如果第二次打开应用发现没有触发主动调用,请清理应用:adb shell pm clear packageName
Ps2:如果不想等待60秒,想自己触发fart的主动调用。可以使用frida扩展
Ps3:如果想看logcat日志。搜索fartext即可,日志统一都添加了这个头部。方便查日志。
修复前的数据
使用前面我修改的修复工具,用下面的命令来修复
java -jar dexfixer.jar dexpath binpath outpath
或者是使用我整合怪的工具来修复
修复后的函数结果如下
可以结合frida来直接调用FART中准备的函数来对单个类或者类列表进行脱壳。
同时我的整合怪里面也添加了对我这个rom的主动调用和类列表主动调用支持
整个流程梳理完成后,我们可以由此来借鉴来思考延伸一下。
比如,包装一些属于自己的系统层api调用。便于我们使用xposed或者是frida来调用一些功能。
再比如,加载应用时,读取配置文件作为开关,我们来对网络流量进行拦截写入保存,或者对所有的jni函数调用,或者是java函数调用进行trace。这种就属于是rom级别的打桩。
再比如,可以做一个应用来读写作为开关的配置文件,而rom读取配置文件后,对一些流程进行调整。例如控制FART是否使用更深调用。控制是否开启rom级别的打桩。
以上纯属个人瞎想。刚刚入门,想的有点多,以后了解更深了,我再看看如何定制一个专属的rom逆向集合
整理一下前面所有的资料。
FART:ART环境下基于主动调用的自动化脱壳方案
FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法
拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点
将FART和Youpk结合来做一次针对函数抽取壳的全面提升
fart的理解和分析过程
Android ART 虚拟机 - dex 文件格式要旨
dex起步探索
FART
Youpk
dexfixer
fridaUiTools
因刚梳理完,难免有些纰漏。我会慢慢修补的。
FartExt因为特殊原因延时开源。感兴趣的可以参考文章自己整整先。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
fartthread();
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
fartthread();
...
}
/
/
判断这个进程是否应该脱壳
public static boolean shouldUnpack() {
boolean should_unpack
=
false;
String processName
=
ActivityThread.currentProcessName();
BufferedReader br
=
null;
String configPath
=
"/data/local/tmp/fext.config"
;
try
{
br
=
new BufferedReader(new FileReader(configPath));
String line;
while
((line
=
br.readLine()) !
=
null) {
if
(processName.equals(line))) {
should_unpack
=
true;
break
;
}
}
br.close();
}
catch (Exception ignored) {
}
return
should_unpack;
}
/
/
启动FART脱壳线程
public static void fartthread() {
if
(!shouldUnpack()) {
return
;
}
new Thread(new Runnable() {
@Override
public void run() {
/
/
TODO Auto
-
generated method stub
try
{
Log.e(
"ActivityThread"
,
"start sleep......"
);
Thread.sleep(
1
*
60
*
1000
);
} catch (InterruptedException e) {
/
/
TODO Auto
-
generated catch block
e.printStackTrace();
}
Log.e(
"ActivityThread"
,
"sleep over and start fart"
);
fart();
Log.e(
"ActivityThread"
,
"fart run over"
);
}
}).start();
}
private void handleBindApplication(AppBindData data) {
...
app
=
data.info.makeApplication(data.restrictedBackupMode, null);
app.setAutofillOptions(data.autofillOptions);
app.setContentCaptureOptions(data.contentCaptureOptions);
mInitialApplication
=
app;
fartthread();
...
}
/
/
判断这个进程是否应该脱壳
public static boolean shouldUnpack() {
boolean should_unpack
=
false;
String processName
=
ActivityThread.currentProcessName();
BufferedReader br
=
null;
String configPath
=
"/data/local/tmp/fext.config"
;
try
{
br
=
new BufferedReader(new FileReader(configPath));
String line;
while
((line
=
br.readLine()) !
=
null) {
if
(processName.equals(line))) {
should_unpack
=
true;
break
;
}
}
br.close();
}
catch (Exception ignored) {
}
return
should_unpack;
}
/
/
启动FART脱壳线程
public static void fartthread() {
if
(!shouldUnpack()) {
return
;
}
new Thread(new Runnable() {
@Override
public void run() {
/
/
TODO Auto
-
generated method stub
try
{
Log.e(
"ActivityThread"
,
"start sleep......"
);
Thread.sleep(
1
*
60
*
1000
);
} catch (InterruptedException e) {
/
/
TODO Auto
-
generated catch block
e.printStackTrace();
}
Log.e(
"ActivityThread"
,
"sleep over and start fart"
);
fart();
Log.e(
"ActivityThread"
,
"fart run over"
);
}
}).start();
}
private void handleBindApplication(AppBindData data) {
...
app
=
data.info.makeApplication(data.restrictedBackupMode, null);
app.setAutofillOptions(data.autofillOptions);
app.setContentCaptureOptions(data.contentCaptureOptions);
mInitialApplication
=
app;
fartthread();
...
}
/
/
读取类列表
public static String getClassList() {
String processName
=
ActivityThread.currentProcessName();
BufferedReader br
=
null;
String configPath
=
"/data/local/tmp/"
+
processName;
Log.e(
"ActivityThread"
,
"getClassList processName:"
+
processName);
StringBuilder sb
=
new StringBuilder();
try
{
br
=
new BufferedReader(new FileReader(configPath));
String line;
while
((line
=
br.readLine()) !
=
null) {
if
(line.length()>
=
2
){
sb.append(line
+
"\n"
);
}
}
br.close();
}
catch (Exception ex) {
Log.e(
"ActivityThread"
,
"getClassList err:"
+
ex.getMessage());
return
"";
}
return
sb.toString();
}
/
/
对指定类进行主动调用
public static void fartWithClassList(String classlist){
ClassLoader appClassloader
=
getClassloader();
if
(appClassloader
=
=
null){
Log.e(
"ActivityThread"
,
"appClassloader is null"
);
return
;
}
Class DexFileClazz
=
null;
try
{
DexFileClazz
=
appClassloader.loadClass(
"dalvik.system.DexFile"
);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
Method dumpMethodCode_method
=
null;
for
(Method field : DexFileClazz.getDeclaredMethods()) {
if
(field.getName().equals(
"fartextMethodCode"
)) {
dumpMethodCode_method
=
field;
dumpMethodCode_method.setAccessible(true);
}
}
String[] classes
=
classlist.split(
"\n"
);
for
(String clsname : classes){
String line
=
clsname;
if
(line.startsWith(
"L"
)&&line.endsWith(
";"
)&&line.contains(
"/"
)){
line
=
line.substring(
1
,line.length()
-
1
);
line
=
line.replace(
"/"
,
"."
);
}
loadClassAndInvoke(appClassloader, line, dumpMethodCode_method);
}
}
public static void fartthread() {
if
(!shouldUnpack()) {
return
;
}
/
/
获取类列表。如果有的话就不要完整主动调用了
String classlist
=
getClassList();
if
(!classlist.equals("")){
fartWithClassList(classlist);
return
;
}
...
}
/
/
读取类列表
public static String getClassList() {
String processName
=
ActivityThread.currentProcessName();
BufferedReader br
=
null;
String configPath
=
"/data/local/tmp/"
+
processName;
Log.e(
"ActivityThread"
,
"getClassList processName:"
+
processName);
StringBuilder sb
=
new StringBuilder();
try
{
br
=
new BufferedReader(new FileReader(configPath));
String line;
while
((line
=
br.readLine()) !
=
null) {
if
(line.length()>
=
2
){
sb.append(line
+
"\n"
);
}
}
br.close();
}
catch (Exception ex) {
Log.e(
"ActivityThread"
,
"getClassList err:"
+
ex.getMessage());
return
"";
}
return
sb.toString();
}
/
/
对指定类进行主动调用
public static void fartWithClassList(String classlist){
ClassLoader appClassloader
=
getClassloader();
if
(appClassloader
=
=
null){
Log.e(
"ActivityThread"
,
"appClassloader is null"
);
return
;
}
Class DexFileClazz
=
null;
try
{
DexFileClazz
=
appClassloader.loadClass(
"dalvik.system.DexFile"
);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
Method dumpMethodCode_method
=
null;
for
(Method field : DexFileClazz.getDeclaredMethods()) {
if
(field.getName().equals(
"fartextMethodCode"
)) {
dumpMethodCode_method
=
field;
dumpMethodCode_method.setAccessible(true);
}
}
String[] classes
=
classlist.split(
"\n"
);
for
(String clsname : classes){
String line
=
clsname;
if
(line.startsWith(
"L"
)&&line.endsWith(
";"
)&&line.contains(
"/"
)){
line
=
line.substring(
1
,line.length()
-
1
);
line
=
line.replace(
"/"
,
"."
);
}
loadClassAndInvoke(appClassloader, line, dumpMethodCode_method);
}
}
public static void fartthread() {
if
(!shouldUnpack()) {
return
;
}
/
/
获取类列表。如果有的话就不要完整主动调用了
String classlist
=
getClassList();
if
(!classlist.equals("")){
fartWithClassList(classlist);
return
;
}
...
}
extern
"C"
void dumpArtMethod(ArtMethod
*
artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
...
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
;
}
...
}
...
}
extern
"C"
void dumpArtMethod(ArtMethod
*
artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
...
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
;
}
...
}
...
}
void ArtMethod::Invoke(Thread
*
self
, uint32_t
*
args, uint32_t args_size, JValue
*
result,
const char
*
shorty) {
if
(
self
=
=
nullptr) {
dumpArtMethod(this);
return
;
}
...
}
void ArtMethod::Invoke(Thread
*
self
, uint32_t
*
args, uint32_t args_size, JValue
*
result,
const char
*
shorty) {
if
(
self
=
=
nullptr) {
dumpArtMethod(this);
return
;
}
...
}
.method public constructor <init>()V
.registers
2
goto :goto_c
:goto_1
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
return
-
void
:goto_c
const v0,
0x1669
invoke
-
static {v0}, Ls
/
h
/
e
/
l
/
l
/
H;
-
>i(I)V
goto :goto_1
.end method
.method public constructor <init>()V
.registers
2
goto :goto_c
:goto_1
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
return
-
void
:goto_c
const v0,
0x1669
invoke
-
static {v0}, Ls
/
h
/
e
/
l
/
l
/
H;
-
>i(I)V
goto :goto_1
.end method
static constexpr InterpreterImplKind kInterpreterImplKind
=
kSwitchImplKind;
static constexpr InterpreterImplKind kInterpreterImplKind
=
kSwitchImplKind;
void Unpacker::invokeAllMethods() {
...
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();
}
}
}
void Unpacker::invokeAllMethods() {
...
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();
}
}
}
extern
"C"
void myfartInvoke(ArtMethod
*
artmethod) REQUIRES_SHARED(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"
);
}
extern
"C"
void myfartInvoke(ArtMethod
*
artmethod) REQUIRES_SHARED(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"
);
}
void ArtMethod::Invoke(Thread
*
self
, uint32_t
*
args, uint32_t args_size, JValue
*
result,
const char
*
shorty) {
...
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/
/
如果是主动调用fake invoke并且不是native方法则强制走解释器
if
(UNLIKELY(!runtime
-
>IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(
self
, this)
|| (Unpacker::isFakeInvoke(
self
, this) && !this
-
>IsNative()))) {
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
if
(IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(
self
, this, nullptr, args, result,
/
*
stay_in_interpreter
*
/
true);
}
else
{
mirror::
Object
*
receiver
=
reinterpret_cast<StackReference<mirror::
Object
>
*
>(&args[
0
])
-
>AsMirrorPtr();
art::interpreter::EnterInterpreterFromInvoke(
self
, this, receiver, args
+
1
, result,
/
*
stay_in_interpreter
*
/
true);
}
}
else
{
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/
/
如果是主动调用fake invoke并且是native方法则不执行
if
(Unpacker::isFakeInvoke(
self
, this) && this
-
>IsNative()) {
/
/
Pop transition.
self
-
>PopManagedStackFragment(fragment);
return
;
}
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
...
}
...
}
void ArtMethod::Invoke(Thread
*
self
, uint32_t
*
args, uint32_t args_size, JValue
*
result,
const char
*
shorty) {
...
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/
/
如果是主动调用fake invoke并且不是native方法则强制走解释器
if
(UNLIKELY(!runtime
-
>IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(
self
, this)
|| (Unpacker::isFakeInvoke(
self
, this) && !this
-
>IsNative()))) {
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
if
(IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(
self
, this, nullptr, args, result,
/
*
stay_in_interpreter
*
/
true);
}
else
{
mirror::
Object
*
receiver
=
reinterpret_cast<StackReference<mirror::
Object
>
*
>(&args[
0
])
-
>AsMirrorPtr();
art::interpreter::EnterInterpreterFromInvoke(
self
, this, receiver, args
+
1
, result,
/
*
stay_in_interpreter
*
/
true);
}
}
else
{
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/
/
如果是主动调用fake invoke并且是native方法则不执行
if
(Unpacker::isFakeInvoke(
self
, this) && this
-
>IsNative()) {
/
/
Pop transition.
self
-
>PopManagedStackFragment(fragment);
return
;
}
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
...
}
...
}
void EnterInterpreterFromInvoke(Thread
*
self
, ArtMethod
*
method,
Object
*
receiver,
uint32_t
*
args, JValue
*
result,
bool
stay_in_interpreter) {
...
JValue r
=
Execute(
self
, code_item,
*
shadow_frame, JValue(), stay_in_interpreter);
...
}
void EnterInterpreterFromInvoke(Thread
*
self
, ArtMethod
*
method,
Object
*
receiver,
uint32_t
*
args, JValue
*
result,
bool
stay_in_interpreter) {
...
JValue r
=
Execute(
self
, code_item,
*
shadow_frame, JValue(), stay_in_interpreter);
...
}
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_) {
...
}
else
if
(kInterpreterImplKind
=
=
kSwitchImplKind) {
if
(transaction_active) {
return
ExecuteSwitchImpl<false, true>(
self
, code_item, shadow_frame, result_register,
false);
}
else
{
return
ExecuteSwitchImpl<false, false>(
self
, code_item, shadow_frame, result_register,
false);
}
}
...
}
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_) {
...
}
else
if
(kInterpreterImplKind
=
=
kSwitchImplKind) {
if
(transaction_active) {
return
ExecuteSwitchImpl<false, true>(
self
, code_item, shadow_frame, result_register,
false);
}
else
{
return
ExecuteSwitchImpl<false, false>(
self
, code_item, shadow_frame, result_register,
false);
}
}
...
}
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/
/
继续解释执行返回false, dump完成返回true
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;
}
/
/
继续解释执行返回false, dump完成返回true
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;
}
template<
bool
do_access_check,
bool
transaction_active>
JValue ExecuteSwitchImpl(Thread
*
self
, const DexFile::CodeItem
*
code_item,
ShadowFrame& shadow_frame, JValue result_register,
bool
interpret_one_instruction) {
...
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
int
inst_count
=
-
1
;
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
do {
dex_pc
=
inst
-
>GetDexPc(insns);
shadow_frame.SetDexPC(dex_pc);
TraceExecution(shadow_frame, inst, dex_pc);
inst_data
=
inst
-
>Fetch16(
0
);
switch (inst
-
>Opcode(inst_data)) {
...
case Instruction::GOTO: {
PREAMBLE();
int8_t offset
=
inst
-
>VRegA_10t(inst_data);
BRANCH_INSTRUMENTATION(offset);
if
(IsBackwardBranch(offset)) {
HOTNESS_UPDATE();
self
-
>AllowThreadSuspension();
}
inst
=
inst
-
>RelativeAt(offset);
break
;
}
...
case Instruction::INVOKE_STATIC: {
PREAMBLE();
bool
success
=
DoInvoke<kStatic, false, do_access_check>(
self
, shadow_frame, inst, inst_data, &result_register);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
break
;
}
case Instruction::INVOKE_STATIC_RANGE: {
PREAMBLE();
bool
success
=
DoInvoke<kStatic, true, do_access_check>(
self
, shadow_frame, inst, inst_data, &result_register);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
break
;
}
...
}
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
bool
dumped
=
Unpacker::afterInstructionExecute(
self
, shadow_frame.GetMethod(), dex_pc, inst_count);
if
(dumped) {
return
JValue();
}
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
while
(!interpret_one_instruction);
/
/
Record where we stopped.
shadow_frame.SetDexPC(inst
-
>GetDexPc(insns));
return
result_register;
}
/
/
NOLINT(readability
/
fn_size)
template<
bool
do_access_check,
bool
transaction_active>
JValue ExecuteSwitchImpl(Thread
*
self
, const DexFile::CodeItem
*
code_item,
ShadowFrame& shadow_frame, JValue result_register,
bool
interpret_one_instruction) {
...
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
int
inst_count
=
-
1
;
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
do {
dex_pc
=
inst
-
>GetDexPc(insns);
shadow_frame.SetDexPC(dex_pc);
TraceExecution(shadow_frame, inst, dex_pc);
inst_data
=
inst
-
>Fetch16(
0
);
switch (inst
-
>Opcode(inst_data)) {
...
case Instruction::GOTO: {
PREAMBLE();
int8_t offset
=
inst
-
>VRegA_10t(inst_data);
BRANCH_INSTRUMENTATION(offset);
if
(IsBackwardBranch(offset)) {
HOTNESS_UPDATE();
self
-
>AllowThreadSuspension();
}
inst
=
inst
-
>RelativeAt(offset);
break
;
}
...
case Instruction::INVOKE_STATIC: {
PREAMBLE();
bool
success
=
DoInvoke<kStatic, false, do_access_check>(
self
, shadow_frame, inst, inst_data, &result_register);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
break
;
}
case Instruction::INVOKE_STATIC_RANGE: {
PREAMBLE();
bool
success
=
DoInvoke<kStatic, true, do_access_check>(
self
, shadow_frame, inst, inst_data, &result_register);
POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
break
;
}
...
}
/
/
patch by Youlor
/
/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-8-5 15:17
被misskings编辑
,原因: