这篇文章是基于上篇文章所总结知识点的抽取壳理论实践。
说到抽取壳脱壳机,FART算是很熟悉了,寒冰师傅的FART是ART环境下基于主动调用的自动化脱壳方案,FART关键的脱壳点是在ARTMethod::invoke,通过JNI函数主动遍历所有ArtMethod,在ARTMethod::invokedump下code_item中的数据。
通过之前对Dex加载流程,类加载流程,以及方法执行流程的总结,可以得知APP的Java方法的调用主要有两种,一是通过反射或JNI主动调用,二是通过自然调用路径,例如当APP被点击某个功能,Java方法就会被自然调用,FART的调用方法方式就是通过第一种方式,是所以是主动的方式。那存在被动调用吗,当APP被点击某个功能,Java方法就会被自然调用,如果脱壳机是在由系统触发的自然调用流程中进行被动脱壳,那么它就是基于被动调用的脱壳方案。
环境:Android 8.0
有了之前的理论基础,其实实现起来就比较简单了,之前分析了自然调用流程:art_quick_to_interpreter_bridge->artQuickToInterpreterBridge() → interpreter::EnterInterpreterFromEntryPoint() → interpreter::Execute()在这个流程中找一个合适的脱壳点添加脱壳机代码即可,脱壳机的代码其实用FART的即可因为脱Code_item的流程都是一样的,少了主动调用的代码。
先看下这个流程涉及的几个函数的入参
__artQuickToInterpreterBridge__这个函数的入参有两个ArtMethod,第二个是SP指针用于构造解释器调用栈帧,栈帧包含了方法运行时的局部变量、操作数栈和其他必要的信息。通过调用栈我们就可以获取当当前运行的方法的ArtMethod
__EnterInterpreterFromEntryPoint__两个参数一个当前线程指针,一个栈帧。
__Execute__Execute这里的参数就比较多了可以看到code_item直接被传过来了,还有shadow_frame。
看完这三个函数其实都可以做脱壳点,接下来开始添加脱壳代码把。
之前说了脱壳代码可以直接使用FART中的dumpArtMethod函数,因为ArtMethod对象的获取并不难,直接传入ArtMethod即可
到这一步的话脱壳机已经可以运行起来了,因为被动脱壳方案是在代码自然调用路径上,所以不需要构造主动调用代码。不过这里还可以进一步优化一下,因为是在自然调用路径所以系统的其他代码也可能会被一起脱下来,这样很影响效率,所以可以添加一段配置读取代码,在某个文件夹下放一个配置文件,文件里写要脱壳APP的包名。
这里选了一个企业版加固的APP来测试,企业版的加固抽取壳算是基本配置,这里不贴APP图片了。
先选一个要脱壳的地方,我选在了登录功能上,先把包名写到配置文件中去,然后启动APP,这里为了方便分析我打印了被调用函数,之后账号密码随便输入一下点登录,登录功能的脱壳就开始了,通过调用日志看一下,可以看到了名为LoginActivity的一些函数被调用比如LoginActivity.a(),从名字可以看出是用于登录的Activity组件,并且可以看到有的函数被混淆了,还有常用于发包的okhttp库的调用。
登录的主要逻辑主要是在OnClick()里看一下smali可以看到被抽取了codeitem
过滤一下包含LoginActivity的脱壳文件,可以看到3576752_ins_2543.bin,3576752_ins_2708.bin,3576752_classlist.txt以及3576752_dexfile.dex,这里还原抽取壳可以直接用FART的修复代码稍微修改一下。
修复前:
修复后:
可以看到我们点击登录功能后的函数被还原了。
基于被动脱壳的脱壳方案有一个明显的缺点,脱壳脱不完整,只能脱系统调用了的方法,不过这也算是它的优点,在分析APP算法的Java层的流程时可以快速定位被调用了的函数,以及发包方式,排除可疑代码的干扰,快速定位Hook点。
extern
"C"
uint64_t artQuickToInterpreterBridge(ArtMethod
*
method, Thread
*
self
, ArtMethod
*
*
sp)
REQUIRES_SHARED(Locks::mutator_lock_) {
...
}
extern
"C"
uint64_t artQuickToInterpreterBridge(ArtMethod
*
method, Thread
*
self
, ArtMethod
*
*
sp)
REQUIRES_SHARED(Locks::mutator_lock_) {
...
}
JValue EnterInterpreterFromEntryPoint(Thread
*
self
, const DexFile::CodeItem
*
code_item,
ShadowFrame
*
shadow_frame) {
DCHECK_EQ(
self
, Thread::Current());
bool
implicit_check
=
!Runtime::Current()
-
>ExplicitStackOverflowChecks();
if
(UNLIKELY(__builtin_frame_address(
0
) <
self
-
>GetStackEndForInterpreter(implicit_check))) {
ThrowStackOverflowError(
self
);
return
JValue();
}
jit::Jit
*
jit
=
Runtime::Current()
-
>GetJit();
if
(jit !
=
nullptr) {
jit
-
>NotifyCompiledCodeToInterpreterTransition(
self
, shadow_frame
-
>GetMethod());
}
return
Execute(
self
, code_item,
*
shadow_frame, JValue());
}
JValue EnterInterpreterFromEntryPoint(Thread
*
self
, const DexFile::CodeItem
*
code_item,
ShadowFrame
*
shadow_frame) {
DCHECK_EQ(
self
, Thread::Current());
bool
implicit_check
=
!Runtime::Current()
-
>ExplicitStackOverflowChecks();
if
(UNLIKELY(__builtin_frame_address(
0
) <
self
-
>GetStackEndForInterpreter(implicit_check))) {
ThrowStackOverflowError(
self
);
return
JValue();
}
jit::Jit
*
jit
=
Runtime::Current()
-
>GetJit();
if
(jit !
=
nullptr) {
jit
-
>NotifyCompiledCodeToInterpreterTransition(
self
, shadow_frame
-
>GetMethod());
}
return
Execute(
self
, code_item,
*
shadow_frame, JValue());
}
static inline JValue Execute(
Thread
*
self
,
const DexFile::CodeItem
*
code_item,
ShadowFrame& shadow_frame,
JValue result_register,
bool
stay_in_interpreter
=
false) REQUIRES_SHARED(Locks::mutator_lock_) {
...
}
static inline JValue Execute(
Thread
*
self
,
const DexFile::CodeItem
*
code_item,
ShadowFrame& shadow_frame,
JValue result_register,
bool
stay_in_interpreter
=
false) REQUIRES_SHARED(Locks::mutator_lock_) {
...
}
extern
"C"
void dumpArtMethod(ArtMethod
*
artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
char
*
dexfilepath
=
(char
*
) malloc(sizeof(char)
*
1000
);
if
(dexfilepath
=
=
nullptr)
{
LOG(INFO) <<
"ArtMethod::dumpArtMethod,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
);
szProcName[sizeof(szProcName)
-
1
]
=
'\0'
;
if
(result <
0
) {
LOG(ERROR) <<
"ArtMethod::dumpArtMethod,open cmdline file file error"
;
return
;
}
close(fcmdline);
}
...
}
extern
"C"
void dumpArtMethod(ArtMethod
*
artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
char
*
dexfilepath
=
(char
*
) malloc(sizeof(char)
*
1000
);
if
(dexfilepath
=
=
nullptr)
{
LOG(INFO) <<
"ArtMethod::dumpArtMethod,methodname:"
<< artmethod
-
>PrettyMethod().c_str()
<<
"malloc 1000 byte failed"
;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)