今天带大家来做一道简单的Frida Hook Java
层的题目,总共有七个小关卡,每个关卡都有一个小的考察点,来考察我们Frida
的基础知识,如果已经会的同学也可以作为一个资料来翻阅
APP:Frida
测试题
下载地址:看文章结束
先把APP安装跑起来,每一关都有一个要求和考察点
考察点:方法参数的修改
分析:大概的看一下逻辑,也就是点击下一关,会调用onClick
方法,onClick
方法中会调用check
方法,但是参数的是一个false
,在check
方法中,根据这个参数来选择是进入到下一关还是提示失败,如果我们不进行任何的修改,y永远也无法进入到下一关,所以呢我们要使用Frida,修改check
方法的参数,使其变为true
,来帮助我们进入下一关。
考察点:方法返回值的修改
分析:调用的流程都是一样的,还是调用onClick
方法,然后再来分析一下逻辑。也是非常简单,调用check
方法,根据check
方法的返回值来选择是进入下一关还是提示失败。所以呢我们直接修改check
方法的返回值为true
就可以了
考察点:类成员变量的修改、枚举类的取值
分析:这道题目呢是判断了成员变量unkown
的值是否等于Level.Fouth
,所以呢我们需要修改unkown
的值为Level.Fouth
,默认值为Level.Unkown
对吧,也是比较简单的。
考察的第一个点呢就是这个成员变量的值如何来修改,分为两种情况:
1.静态成员变量,修改静态成员变量的值直接使用Java.use
拿到一个类的wraper
,直接.变量名.value
就可以直接修改它的值。
2.实例成员变量,需要我们先通过Java.choose
获取到这个实例,再通过.的方式来修改。
考察的第二个点就是枚举类的取值,因为我们的unkown
这个成员变量想给它赋值为Level.Fouth
,所以我们要拿到这个Level.Fouth
,而这个Level
为一个枚举类,从名字也可以看出,枚举类无非就是一个特殊的类,其取值呢也是直接可以用.name.value的方法来取,来看代码
这个题目有个同学提出一个问题,他并不是修改类成员变量的值来进入下一关的,这个我们第一个题目就说过了,不要使用任何非考察点外的方法来达到下一关,不然一个题目的解法会有很多,还达不到我们考察的目的
考察点:方法的主动调用
分析:这道题的目的是为了让我们学会Frida
如何去主动调用一个方法,同样主动调用也是分为了两种情况
1.静态方法的主动调用,直接通过Java.use
拿到类的wraper
,可以直接调用。
2.实例方法的主动调用,需要先获得一个该类的实例,在Frida
中拿到一个类的实例有很多种方法,比如$new()
来new
一个对象,Java.choose
来拿到一个内存中现有的该类的实例,方法Hook
的时候,this
对象也是该类的一个实例,有了这个实例就可以进行主动调用实例方法了。来看代码
考察点: Frida
数组的构造
分析:在我们想主动调用一个方法的时候,最重要的就是这个方法的参数该如何去构造。比如这个题目,也是一个check
方法,它的参数就是一个数组,内部进行了判断,判断该数组的长度为5就可以通过,否则提示失败。所以我们需要Hook check
方法,修改其参数为一个长度为5的String
数组就可以了。数组的构造Frida
也提供了API
:Java.array
,第一个参数为要构造的数据类型,基本数据类型可以直接写,如int
char
,而复杂数据类型需要填写全限定类名,如java.lang.String
,来看代码
考察点:Frida
自定义类
分析:这道题目是比较有意思的一道题目,当时出题的时候没有考虑到ClassLoader
的问题,自己在做的时候才发现这个题目是有坑的。我们先按正常的思路来解决这道题目:代码就几行,先通过Class.forName
来加载一个类的,拿到com.dta.test.frida.activity.RegisterClass
这个Class
对象,然后调用getDeclaredMethod
方法来拿到这个类中的next方法,最后再来调用这个next
方法,拿到返回值后调用booleanValue
方法来拿到一个boolean
类型的结果,通过这个结果来选择是否进入下一关或提示失败
其实上面的分析过程也是比较好理解,就相当于我们需要一个类,如下
题目也就是调用了这个RegisterClass
类中的next
方法,那么我们根据这个题目来通过Frida
提供的Java.registerClass
API来构建这样一个类
我们通过Frida来帮我们构造出来了我们需要的类,而且也会自动加载到内存中去,但是我们发现题目还是过不了的。这个时候我们就要思考问题出在哪里?从上至下来思考的话,第一行的Class.forName
执行成功了吗?第二行的getDeclaredMethod
方法找到我们需要的next
方法了吗?返回值获取成功了吗?
带着这些问题,我们来排查。因为题目中catch
块中的异常没有进行print
,我们通过其他的方式来排查。首先排查简单的项,getDeclaredMethod
方法找到了吗?我们可以通过使用一下我们自定义的RegisterClass
,new
一个对象来调用next方法,发现是可以打印的,且结果为true
那么就说明我们自定义的Class
是创建成功了的,且能够正常使用。那问题就出现在第一个,Class.forName
加载这个类成功了吗?其实是没有成功的。当我们点击下一关按钮的时候,这里抛出的异常其实是ClassNotFoundException
,找不到我们使用Frida
自定义的类。那原因出在哪里呢?我们接着往下看
首先我们来看一下Class.forName
的流程
内部又调用了forName
的重载,注意第三个参数是VMStack.getCallingClassLoader
是一个native
方法,注释翻译一下就是返回请求类的loader
,如果为null
则返回bootstrap class loader
,那我们这里调用这个方法拿到的就是跟SixthActivity
是同一个ClassLoader
这个方法只有三个参数,第一个为className
,我们的className
是正确的,所以我们要从ClassLoader
来入手解决这个问题。下面来补充下ClassLoader
的知识
图中我们几种ClassLoader
的继承关系,其中红色箭头所描述的就是双亲委派机制。当我们自定义类加载器想要加载一个类时,它首先不会自己想着去加载这个类,而是要先向上询问Application
类加载器,你能帮我加载这个类吗?Application
类加载器说,我上面还有你爷爷类加载器,就这样,直到找到Bootstrap
类加载器,如果Bootstrap
类加载器不能加载此类,那么就会反向让自己的子类去想办法处理加载。如果上层的类加载器能够加载该类,就直接加载成功了。简单点说就是每个儿子都不愿意干活,每次有活就它的父亲去干,直到父亲说这件事我干不了,让儿子想办法去完成,这个就是双亲委派机制。
双亲委派机制有什么好处呢?
Android
平台的类加载器并不是Android
特有的,而是从Java
当中继承过来的,那么Android
类加载器跟Java
中的又有什么区别呢?下面我们来一一介绍:
注意:系统当中常用的Framwork
层的类都是由BootClassLoader
来加载的,开发一个APP
所使用的系统API
的类,都是由它加载的,而APP
内的类都是由PathClassLoader
进行加载的。
再回到第六关的问题,其实根本原因就是Frida
加载我们自定义类RegisterClass
使用的ClassLoader
跟SisthActivity
的PathClassLoader
并不是同一个ClassLoader
,所以导致我们使用PathClassLoader
来加载会出现ClassNotFoundException
,知道这个问题所在我们就可以解决这个问题了,下面提供两种解决思路,感谢Simp1er大佬提供的解决方案
既然我们使用默认的PathClassLoader
加载不了,那么我们知道,它的加载过程是先通过父加载器进行加载的,也就是会调用BootClassLoader
来加载我们的目标类,当然BootClassLoader
也是无法成功加载的,所以我们可以在PathClassLoader
跟BootClassLoader
中间插入我们RegisterClass
的ClassLoader
,就可以由PathClassLoader
->BootClassLoader
变为PathClassLoader
->RegisterClass
的ClassLoader
->BootClassLoader
,这样原本是使用PathClassLoader
来加载我们的类,PathClassLoader
会先委托父亲加载,BootClassLoader
会加载失败,最终交由我们RegisterClass
的ClassLoader
来加载,就可以完成本次加载了。插入也是非常简单的,因为ClassLoader
中标志一个ClassLoader
是另外一个ClassLoader
的父亲,就是使用parent
来表示的,我们直接修改这个值就可以了,看下面第一种实现
下面贴一下第七关的源代码
考察点:Frida
切换ClassLoader
分析:这道题目是从外部加载了一个dex
文件,然后反射加载com.dta.frida.MyCheck
类,并调用其中的check
方法。而我们想要在Frida
中Hook
这个MyCheck类,直接use
是不行的,也是因为ClassLoader
的问题,既然我们已经了解了ClassLoader
,这个题目就很简单了,只需要学会在Frida
中如何来切换ClassLoader
就可以了,使用Java.enumerateClassLoaders
来枚举ClassLoader
,然后判断哪个ClassLoader
是我们想要的,再将Java.classFactory.loader
赋值为我们需要的loader
就可以了,来看代码
本篇文章,主要介绍了Frida Hook Java层的用法,API都是死的,但是需要我们灵活掌握,根据不同的目的使用不同的API组合来实现我们的目的,这七个题目也都是比较基础的内容,因为安卓的Java本来就是很简单的。当然知其然必要知其所以然,所以在第六题中讲解了ClassLoader的内容,使我们透过问题来了解本质,才能有所提高。
APP练习题的下载请扫码访问:
function main(){
Java.perform(function(){
/
/
Frist
Java.use(
"com.dta.test.frida.activity.FirstActivity"
).check.implementation
=
function(z){
z
=
true
this.check(z)
}
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Frist
Java.use(
"com.dta.test.frida.activity.FirstActivity"
).check.implementation
=
function(z){
z
=
true
this.check(z)
}
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Second
Java.use(
"com.dta.test.frida.activity.SecondActivity"
).check.implementation
=
function(){
return
true
}
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Second
Java.use(
"com.dta.test.frida.activity.SecondActivity"
).check.implementation
=
function(){
return
true
}
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Third
Java.choose(
"com.dta.test.frida.activity.ThirdActivity"
,{
onMatch: function(ins){
console.log(ins)
ins.unknown.value
=
Java.use(
"com.dta.test.frida.base.Level"
).Fourth.value
},onComplete: function(){
console.log(
"Search Completed!"
)
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Third
Java.choose(
"com.dta.test.frida.activity.ThirdActivity"
,{
onMatch: function(ins){
console.log(ins)
ins.unknown.value
=
Java.use(
"com.dta.test.frida.base.Level"
).Fourth.value
},onComplete: function(){
console.log(
"Search Completed!"
)
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Fourth
Java.choose(
"com.dta.test.frida.activity.FourthActivity"
,{
onMatch: function(ins){
console.log(ins)
ins.
next
()
},onComplete: function(){
console.log(
"Search Completed!"
)
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Fourth
Java.choose(
"com.dta.test.frida.activity.FourthActivity"
,{
onMatch: function(ins){
console.log(ins)
ins.
next
()
},onComplete: function(){
console.log(
"Search Completed!"
)
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Fifth
var strarr
=
Java.array(
"java.lang.String"
,[
"d"
,
"t"
,
"a"
,
"b"
,
"c"
])
Java.use(
"com.dta.test.frida.activity.FifthActivity"
).check.implementation
=
function(arr){
arr
=
strarr
this.check(arr)
}
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Fifth
var strarr
=
Java.array(
"java.lang.String"
,[
"d"
,
"t"
,
"a"
,
"b"
,
"c"
])
Java.use(
"com.dta.test.frida.activity.FifthActivity"
).check.implementation
=
function(arr){
arr
=
strarr
this.check(arr)
}
})
}
setImmediate(main)
package com.dta.test.frida.activity;
public
class
RegisterClass{
public RegisterClass{
}
public boolean
next
(){
return
true;
}
}
package com.dta.test.frida.activity;
public
class
RegisterClass{
public RegisterClass{
}
public boolean
next
(){
return
true;
}
}
function main(){
Java.perform(function(){
/
/
Sixth
var RegisterClass
=
Java.registerClass({
name:
"com.dta.test.frida.activity.RegisterClass"
,
methods: {
next
: {
returnType:
"boolean"
,
argumentTypes:[],
implementation: function(){
return
true
}
}
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Sixth
var RegisterClass
=
Java.registerClass({
name:
"com.dta.test.frida.activity.RegisterClass"
,
methods: {
next
: {
returnType:
"boolean"
,
argumentTypes:[],
implementation: function(){
return
true
}
}
}
})
})
}
setImmediate(main)
console.log(RegisterClass.$new().
next
())
/
/
true
console.log(RegisterClass.$new().
next
())
/
/
true
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
return
forName(className, true, VMStack.getCallingClassLoader());
}
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
return
forName(className, true, VMStack.getCallingClassLoader());
}
/
*
*
*
Returns the defining
class
loader of the caller's caller.
*
*
@
return
the requested
class
loader,
or
{@code null}
if
this
is
the
*
bootstrap
class
loader.
*
/
@FastNative
native public static ClassLoader getCallingClassLoader();
/
*
*
*
Returns the defining
class
loader of the caller's caller.
*
*
@
return
the requested
class
loader,
or
{@code null}
if
this
is
the
*
bootstrap
class
loader.
*
/
@FastNative
native public static ClassLoader getCallingClassLoader();
function main(){
Java.perform(function(){
/
/
Sixth
var RegisterClass
=
Java.registerClass({
name:
"com.dta.test.frida.activity.RegisterClass"
,
methods: {
next
: {
returnType:
"boolean"
,
argumentTypes:[],
implementation: function(){
return
true
}
}
}
})
var targetClassLoader
=
RegisterClass.
class
.getClassLoader()
Java.enumerateClassLoaders({
onMatch: function(loader){
try
{
if
(loader.findClass(
"com.dta.test.frida.activity.SixthActivity"
)){
/
/
PathClassLoader
var PathClassLoader
=
loader
var BootClassLoader
=
PathClassLoader.parent.value
PathClassLoader.parent.value
=
targetClassLoader
targetClassLoader.parent.value
=
BootClassLoader
}
}catch(e){
}
},onComplete: function(){
console.log(
"Completed!"
)
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Sixth
var RegisterClass
=
Java.registerClass({
name:
"com.dta.test.frida.activity.RegisterClass"
,
methods: {
next
: {
returnType:
"boolean"
,
argumentTypes:[],
implementation: function(){
return
true
}
}
}
})
var targetClassLoader
=
RegisterClass.
class
.getClassLoader()
Java.enumerateClassLoaders({
onMatch: function(loader){
try
{
if
(loader.findClass(
"com.dta.test.frida.activity.SixthActivity"
)){
/
/
PathClassLoader
var PathClassLoader
=
loader
var BootClassLoader
=
PathClassLoader.parent.value
PathClassLoader.parent.value
=
targetClassLoader
targetClassLoader.parent.value
=
BootClassLoader
}
}catch(e){
}
},onComplete: function(){
console.log(
"Completed!"
)
}
})
})
}
setImmediate(main)
function main(){
Java.perform(function(){
/
/
Sixth
var RegisterClass
=
Java.registerClass({
name:
"com.dta.test.frida.activity.RegisterClass"
,
methods: {
next
: {
returnType:
"boolean"
,
argumentTypes:[],
implementation: function(){
return
true
}
}
}
})
var targetClassLoader
=
RegisterClass.
class
.getClassLoader()
var Class
=
Java.use(
'java.lang.Class'
)
Class.forName.overload(
'java.lang.String'
,
'boolean'
,
'java.lang.ClassLoader'
).implementation
=
function (
str
, init, loader) {
console.log(
'loader'
, loader)
console.log(
'className'
,
str
)
console.log(
'iniit'
, init)
return
this.forName(
str
, init, targetClassLoader)
}
})
}
setImmediate(main)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)