在分析Apk的时候难免去分析So层,大多数native层都需要IDA调试,特别是分析数量很多的Apk的时候。我之前就想着,能不能对各种Naitive层进行监听。Apk放进去就可以实现自动化分析,包括调用了哪些方法,每个Java类里面保存了哪些数据。做了什么事情。特别是分析设备指纹的时候,Apk会采集非常多的设备信息。但是这些设备信息,无从下手,不知道应该从哪里改起,加上很多Apk都有混淆,分析起来是一件很费劲的事情。我们完全可以把一些关键信息,进行快速序列化,保存起来,对里面的东西直接搜索我们需要的内容即可即可 。
现在市面上有很多Java层的自吐工具,比如Hook,Java层的常见加密信息,以实现自吐,现在很多大厂基本都不会走Java层基础加密,大多数都是自己实现的加密方式,Java层最多只能算是辅助作用。几年之前还很好用,现在看起来有点鸡肋 。那么有没有什么比较好的分析Native的工具呢?
现在市面上常见的hook基本就是Frida和Xposed占大头,Frida常见的分析工具,比如Frida 的Jnitrace,Unidbg之类的 就很好用,根据Unidbg 的实现逻辑,targetSdk版本号检测,或者检测某个字符串的Hash的返回值 。
检测Frida的方法就更多了,各种反调试都是可以检测出来,比如文件,端口号,特征都是常见的anti点。
但是针对Xposed的检测却很难,需要先拿到Classloader,和一些特征才能判断是否被注入。特别是Lsposed,在Hunter里面也只能通过检测Libart的CRC,内存文件和本地文件的指令累加,判断是否相等,以检测Libart是否被修改,但是这种方式一般大厂也不敢轻易去上,很容易SIGN11。Lsposed因为是系统层注入,加上命名空间,还有本身Lsp的Classloader被隐藏了,在应用层很难去拿到比较好的特征点。这篇文章主要是介绍一下之前几年搞的各种小工具,做了个合计 。也方便各位以后能快速对各种Apk进行分析 。这篇文章读下来也会有不少收获 。
另外代码会开源,还希望各位老板多多start !
第一版本我只做了6个功能 。
第一版本算是搭了个架子,后面有时间的话会慢慢完善 。当然也欢迎各位大佬进行push和pull ,有好的建议可以在issues提想法 。
使用的话也很简单,Xposed模块,先选择需要Hook的Apk ,内存序列化和Native层Hook只能选一个 。如果没开启内存序列化的话,会弹窗。
推荐根据自己的需求去Hook指定的So 。比如libaaa.so ,只需要输入libaaa或者libaaa.so 即可,如果需要Hook监听多个So的话可以用|分割 。
如 libaaa.so|libbbb.so 即可 。下面会分别介绍一下具体的实现过程,不然只看代码学习效率很低。下面主要是一些实现的细节 。
这个很简单,直接把So注入即可,不同版本调用的api不同 。具体代码如下 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static void LoadSoForPath(String path,
Object
object
) {
try
{
CLog.e(
"load so path -> "
+
path);
if
(Build.VERSION.SDK_INT >
=
28
) {
String nativeLoad
=
(String) XposedHelpers.callMethod(Runtime.getRuntime(),
"nativeLoad"
, path,
object
);
CLog.e(nativeLoad
=
=
null ? "" : nativeLoad);
}
else
{
String doLoad
=
(String) XposedHelpers.callMethod(Runtime.getRuntime(),
"doLoad"
, path,
object
);
CLog.e(doLoad
=
=
null ? "" : doLoad);
}
CLog.i(
"load so for path success "
+
path);
} catch (Throwable e) {
CLog.e(
"load so for path "
+
e.getMessage());
}
}
|
第一个参数是SO路径,这块有一个细节,卡住了不少人,就是这个方法的参数2,他是一个Classloader,这Classloader 表示当前注入So的Classloader ,在Native层不同的Classloader的作用域是不一样的,跟Dex一样,每个SO也是有属于自己的Classloader,因为Xposed的Classloader和当前进程Context的Classloader是不一样的。
如果你用当前进程的Context的Classloader进行注入,他会找不到Xposed加载的类,因为Classloader不一样 ,会一直提示class not find ,导致无法在Naitive层注册一个Native方法 。解决办法也很简单,直接用XposedHook类的.class.getClassLoader()即可 。
还有就是如何自动化区分被HookApk是64位还是32位。这块代码里面都会很详细的介绍和实现逻辑 ,具体参考代码。
什么是Java内存序列化,就是讲Java层整个虚拟机的全部Java实例转换成JSON字符串,保存到本地 。这个也是我经常用的功能之一 。
他有什么作用?比如一个很简单的CASE场景,我想知道一个大厂Apk设备指纹都保存在哪些Java类里面 ?都保存了什么东西 ?
直接让软件运行30秒以后( 这个时间可以根据自己的业务场景去控制),扫描一下内存即可 。遍历的时间和Apk的大小有关系,Apk越大保存的对象越多,耗时越长 。
获取内存实例,实现原理也很简单,之前文章介绍过如何获取 https://bbs.kanxue.com/thread-269094.htm
这个Api是一个隐藏api ,目前只做了 android 9- 11支持 。9-11是系统自带Api , 其他版本需要自己实现,我尝试在 5 - 9实现发现稳定性存在问题,所以在XposedJni里面做了判断,所以这个功能只有9-11支持 。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
private void startSerialization(Context context) {
try
{
/
/
手动触发gc,清空多余实例
System.gc();
final
File
file
=
new
File
(
"/data/data/"
+
mTagPackageName
+
"/"
+
mProcessName
+
"_MemorySerializationInfo.txt"
);
if
(
file
.exists()) {
file
.delete();
}
file
.createNewFile();
/
/
子线程和主线程共享数据
ThreadUtils.runOnNonUIThread(()
-
> {
ArrayList<
Object
> choose
=
ChooseUtils.choose(
Object
.
class
, true);
int
size
=
choose.size();
CLog.e(
"memory object size -> "
+
size);
for
(
int
index
=
0
; index < size; index
+
+
) {
Object
obj
=
choose.get(index);
String objStr
=
GsonUtils.obj2str(obj);
if
(objStr !
=
null) {
String objClassName
=
obj.getClass().getName();
String infoStr
=
index
+
"/"
+
size
+
"["
+
mProcessName
+
"]"
+
objClassName
+
" "
+
objStr
+
"\n"
;
/
/
增加效率暂不打印进度
/
/
printfProgress(size,index,context);
/
/
ToastUtils.showToast(context,
"MemorySerialization["
+
index
+
"/"
+
size
+
"]"
);
CLog.i(infoStr);
FileUtils.saveStringNoClose(infoStr,
file
);
}
}
FileUtils.saveStringClose();
},
30
*
1000
);
} catch (Throwable e) {
CLog.e(
"startSerialization error "
+
e);
}
}
|
在子线程开启,将内存全部的Object实例拿到手以后,对每一个Object进行JSON字符串的转换,然后将转换以后的内容保存到本地 。包括Class的类名 。内容的JSON传等信息 。当然也可以根据自己需求取来,只获取需要的类即可 。比如我想查看内存里面全部的String变量 。可以将Object.class换成String.class即可 。方便快速分析和定位 。
效果如下基本一个大一点的Apk对象数量都超过15W以上,大约10分钟左右就可以遍历完毕,和手机配置有关系 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
2023
-
04
-
01
23
:
44
:
34.749
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
287
/
204804
[进程名]android.system.StructTimespec {
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
}
2023
-
04
-
01
23
:
44
:
34.749
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
288
/
204804
[进程名]android.system.StructTimespec {
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
}
2023
-
04
-
01
23
:
44
:
34.750
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
289
/
204804
[进程名]android.system.StructTimespec {
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
}
2023
-
04
-
01
23
:
44
:
34.752
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
290
/
204804
[进程名]android.system.StructStat {
"st_atim"
:{
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
},
"st_atime"
:
1680363817
,
"st_blksize"
:
4096
,
"st_blocks"
:
8
,
"st_ctim"
:{
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
},
"st_ctime"
:
1680363817
,
"st_dev"
:
64522
,
"st_gid"
:
10236
,
"st_ino"
:
137495
,
"st_mode"
:
33200
,
"st_mtim"
:{
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
},
"st_mtime"
:
1680363817
,
"st_nlink"
:
1
,
"st_rdev"
:
0
,
"st_size"
:
148
,
"st_uid"
:
10236
}
2023
-
04
-
01
23
:
44
:
34.755
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
291
/
204804
[进程名]java.io.BufferedInputStream {
"count"
:
148
,
"marklimit"
:
0
,
"markpos"
:
-
1
,
"pos"
:
148
}
2023
-
04
-
01
23
:
44
:
34.759
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
292
/
204804
[进程名]java.io.FileInputStream {
"closeLock"
:{},
"closed"
:true,
"fd"
:{
"descriptor"
:
-
1
,
"ownerId"
:
0
},
"guard"
:{},
"isFdOwner"
:true,
"path"
:
"/data/user/0/进程名/shared_prefs/RDeliveryHitSubTaskTagFile.xml"
,
"tracker"
:{
"isOpen"
:true,
"mode"
:
"READ"
,
"opCount"
:
1
,
"totalByteCount"
:
16384
}}
2023
-
04
-
01
23
:
44
:
34.772
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
294
/
204804
[进程名]java.lang.
Object
{}
2023
-
04
-
01
23
:
44
:
34.773
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
295
/
204804
[进程名]dalvik.system.CloseGuard {}
2023
-
04
-
01
23
:
44
:
34.774
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
296
/
204804
[进程名]libcore.io.IoTracker {
"isOpen"
:true,
"mode"
:
"READ"
,
"opCount"
:
1
,
"totalByteCount"
:
16384
}
2023
-
04
-
01
23
:
44
:
34.775
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
297
/
204804
[进程名]java.io.FileDescriptor {
"descriptor"
:
-
1
,
"ownerId"
:
0
}
2023
-
04
-
01
23
:
44
:
34.776
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
298
/
204804
[进程名]android.system.StructTimespec {
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
}
2023
-
04
-
01
23
:
44
:
34.777
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
299
/
204804
[进程名]android.system.StructTimespec {
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
}
2023
-
04
-
01
23
:
44
:
34.778
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
300
/
204804
[进程名]android.system.StructTimespec {
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
}
2023
-
04
-
01
23
:
44
:
34.779
18522
-
19106
/
? I
/
Zhenxi: [Zhenxi]
301
/
204804
[进程名]android.system.StructStat {
"st_atim"
:{
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
},
"st_atime"
:
1680363817
,
"st_blksize"
:
4096
,
"st_blocks"
:
8
,
"st_ctim"
:{
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
},
"st_ctime"
:
1680363817
,
"st_dev"
:
64522
,
"st_gid"
:
10236
,
"st_ino"
:
137495
,
"st_mode"
:
33200
,
"st_mtim"
:{
"tv_nsec"
:
288510927
,
"tv_sec"
:
1680363817
},
"st_mtime"
:
1680363817
,
"st_nlink"
:
1
,
"st_rdev"
:
0
,
"st_size"
:
148
,
"st_uid"
:
10236
}
... ...
|
这个是我之前写的一个小工具,地址如下。
https://github.com/w296488320/JnitraceForCpp
代码直接粘过来的 。今天有时间顺便改了改一些之前错误和多余的逻辑 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
HOOK_JNITRACE(env, CallObjectMethodV)
HOOK_JNITRACE(env, CallBooleanMethodV)
HOOK_JNITRACE(env, CallByteMethodV)
HOOK_JNITRACE(env, CallCharMethodV)
HOOK_JNITRACE(env, CallShortMethodV)
HOOK_JNITRACE(env, CallIntMethodV)
HOOK_JNITRACE(env, CallLongMethodV)
HOOK_JNITRACE(env, CallFloatMethodV)
HOOK_JNITRACE(env, CallDoubleMethodV)
HOOK_JNITRACE(env, CallVoidMethodV)
HOOK_JNITRACE(env, CallStaticObjectMethodV)
HOOK_JNITRACE(env, CallStaticBooleanMethodV)
HOOK_JNITRACE(env, CallStaticByteMethodV)
HOOK_JNITRACE(env, CallStaticCharMethodV)
HOOK_JNITRACE(env, CallStaticShortMethodV)
HOOK_JNITRACE(env, CallStaticIntMethodV)
HOOK_JNITRACE(env, CallStaticLongMethodV)
HOOK_JNITRACE(env, CallStaticFloatMethodV)
HOOK_JNITRACE(env, CallStaticDoubleMethodV)
HOOK_JNITRACE(env, CallStaticVoidMethodV)
HOOK_JNITRACE(env, GetObjectField)
HOOK_JNITRACE(env, GetBooleanField)
HOOK_JNITRACE(env, GetByteField)
HOOK_JNITRACE(env, GetCharField)
HOOK_JNITRACE(env, GetShortField)
HOOK_JNITRACE(env, GetIntField)
HOOK_JNITRACE(env, GetLongField)
HOOK_JNITRACE(env, GetFloatField)
HOOK_JNITRACE(env, GetDoubleField)
HOOK_JNITRACE(env, GetStaticObjectField)
HOOK_JNITRACE(env, GetStaticBooleanField)
HOOK_JNITRACE(env, GetStaticByteField)
HOOK_JNITRACE(env, GetStaticCharField)
HOOK_JNITRACE(env, GetStaticShortField)
HOOK_JNITRACE(env, GetStaticIntField)
HOOK_JNITRACE(env, GetStaticLongField)
HOOK_JNITRACE(env, GetStaticFloatField)
HOOK_JNITRACE(env, GetStaticDoubleField)
HOOK_JNITRACE(env, NewStringUTF)
HOOK_JNITRACE(env, GetStringUTFChars)
HOOK_JNITRACE(env, FindClass)
HOOK_JNITRACE(env, ToReflectedMethod)
HOOK_JNITRACE(env, FromReflectedMethod)
HOOK_JNITRACE(env, GetFieldID)
HOOK_JNITRACE(env, GetStaticFieldID)
HOOK_JNITRACE(env, NewObjectV)
|
都是一些常见的JNI交互函数,Hook以后在调用之前和调用之后将jobject 进行toString打印即可。这块需要注意的是打印可变参数和栈溢出问题,
因为我们也需要调用JNI函数,需要判断哪些SO监听,哪些不需要监听 ,剩下的就是代码细节实现了 。因为打印日志量比较多,所以需要将Log一些信息保存到本地文件里面 。代码实现也很简单,封装了大量的宏,减少工作量 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/
/
jobject CallObjectMethod(JNIEnv
*
, jobject, jmethodID, va_list args);
JNI_HOOK_DEF(jobject, CallObjectMethodV, JNIEnv
*
env, jobject obj, jmethodID jmethodId,
va_list args)
DL_INFO
IS_MATCH
GET_JOBJECT_INFO(env, obj,
"CallObjectMethodV"
)
GET_METHOD_INFO_ARGS(env, obj, jmethodId, args, false)
jobject ret
=
orig_CallObjectMethodV(env, obj, jmethodId, args);
getJObjectInfoInternal(env, ret,
"result object :"
, true, nullptr);
return
ret;
}
}
return
orig_CallObjectMethodV(env, obj, jmethodId, args);
}
/
/
void CallVoidMethod(jobject obj, jmethodID methodID, va_list args)
JNI_HOOK_DEF(void, CallVoidMethodV, JNIEnv
*
env, jobject obj, jmethodID jmethodId,
va_list args)
DL_INFO
IS_MATCH
GET_JOBJECT_INFO(env, obj,
"CallVoidMethodV"
)
GET_METHOD_INFO_ARGS(env, obj, jmethodId, args, false)
}
}
return
orig_CallVoidMethodV(env, obj, jmethodId, args);
}
...
|
打印效果截取如下 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
2023
-
04
-
01
23
:
47
:
27.494
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : vb_platformInfo_channel_id
2023
-
04
-
01
23
:
47
:
27.494
21432
-
21457
/
? I
/
Zhenxi: [文件名] args
0
1
1
2023
-
04
-
01
23
:
47
:
27.495
21432
-
21432
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : android.hardware.Sensor
2023
-
04
-
01
23
:
47
:
27.495
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : key_guid
2023
-
04
-
01
23
:
47
:
27.495
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : VBIPExchanger_InnerInitTask
2023
-
04
-
01
23
:
47
:
27.495
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : isMainProc proc:包名:cache packageName:包名
2023
-
04
-
01
23
:
47
:
27.495
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : VBPBService
-
6447
2023
-
04
-
01
23
:
47
:
27.495
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : NXNetwork_Transport_HttpImpl
2023
-
04
-
01
23
:
47
:
27.495
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : [GetCarrierIPRequest]
-
1
connectStart():
/
60.28
.
219.101
:
443
2023
-
04
-
01
23
:
47
:
27.495
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : VBPBService
-
6447
2023
-
04
-
01
23
:
47
:
27.496
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : QAD
2023
-
04
-
01
23
:
47
:
27.496
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : setQAdMediaPlayerCreator() QAD_TVKPlayer注册成功
2023
-
04
-
01
23
:
47
:
27.496
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : TVK
-
HighPriorityThread1
2023
-
04
-
01
23
:
47
:
27.496
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : VBNetStateService_VBNetTypeHelper
2023
-
04
-
01
23
:
47
:
27.496
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : getNetworkInfo network capability validated:true
2023
-
04
-
01
23
:
47
:
27.496
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : VBPBService
-
6447
2023
-
04
-
01
23
:
47
:
27.496
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : creator_account_info_key
2023
-
04
-
01
23
:
47
:
27.497
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : vb_platformInfo_channel_id
2023
-
04
-
01
23
:
47
:
27.497
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : key_guid
2023
-
04
-
01
23
:
47
:
27.498
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : creator_account_info_key
2023
-
04
-
01
23
:
47
:
27.498
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : vb_platformInfo_channel_id
2023
-
04
-
01
23
:
47
:
27.500
21432
-
21709
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : key_guid
2023
-
04
-
01
23
:
47
:
27.502
21432
-
21486
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : public_io_pool
-
6479
2023
-
04
-
01
23
:
47
:
27.503
21432
-
21796
/
? I
/
Zhenxi: [文件名] args
3
17
17
2023
-
04
-
01
23
:
47
:
27.503
21432
-
21796
/
? I
/
Zhenxi: [文件名] invoke method result Boolean : true
2023
-
04
-
01
23
:
47
:
27.504
21432
-
21432
/
? I
/
Zhenxi: <<<<<
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
CallBooleanMethodV start
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
>>>>>
2023
-
04
-
01
23
:
47
:
27.506
21432
-
21432
/
? I
/
Zhenxi: [文件名] invoke this
object
android.hardware.Sensor {Sensor name
=
"linear_acceleration"
, vendor
=
"qualcomm"
, version
=
1
,
type
=
0
, maxRange
=
156.98999
, resolution
=
0.01
, power
=
0.515
, minDelay
=
5000
}
2023
-
04
-
01
23
:
47
:
27.507
21432
-
21432
/
? I
/
Zhenxi: [文件名] invoke method private boolean android.hardware.Sensor.setType(
int
)
2023
-
04
-
01
23
:
47
:
27.508
21432
-
21432
/
? I
/
Zhenxi: [文件名] args
0
10
10
2023
-
04
-
01
23
:
47
:
27.508
21432
-
21432
/
? I
/
Zhenxi: [文件名] invoke method result Boolean : true
2023
-
04
-
01
23
:
47
:
27.509
21543
-
21672
/
? I
/
Zhenxi: [文件名] GetStringUTFChars : VBNetStateService_VBNetTypeHelper
2023
-
04
-
01
23
:
47
:
27.509
21432
-
21459
/
? I
/
Zhenxi: [文件名] invoke this
object
android.view.ViewRootImpl$W android.view.ViewRootImpl$W@
43c430d
...
|
主要处理函数如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
void stringHandler::init() {
void
*
handle
=
dlopen(
"libc.so"
, RTLD_NOW);
if
(handle
=
=
nullptr) {
LOG(ERROR) <<
"strhadler get handle == null "
;
return
;
}
HOOK_SYMBOL_DOBBY(handle, strstr)
HOOK_SYMBOL_DOBBY(handle, strcmp)
HOOK_SYMBOL_DOBBY(handle, strcpy)
HOOK_SYMBOL_DOBBY(handle, strdup)
HOOK_SYMBOL_DOBBY(handle, strxfrm)
HOOK_SYMBOL_DOBBY(handle, strtok)
/
/
HOOK_SYMBOL_DOBBY(handle, memcpy)
/
/
HOOK_SYMBOL_DOBBY(handle, read)
/
/
HOOK_SYMBOL_DOBBY(handle, write)
/
/
HOOK_SYMBOL_DOBBY(handle, sprintf);
/
/
HOOK_SYMBOL_DOBBY(handle, printf);
/
/
HOOK_SYMBOL_DOBBY(handle, snprintf);
/
/
HOOK_SYMBOL_DOBBY(handle, vsnprintf);
|
其他大部分底层都是这几个函数,也是将不同函数的参数进行hook和拦截 。在处理之前和处理之后进行打印 。一般不注重安全的程序员,都会用系统的函数进行比较和替换,而非自己去实现,比如比较当前进程是否正在被调试,我们只需要打印比较传入的参数的内容 。找到以后直接打印调用栈和函数地址 。
可以很快速的定位反调试的位置 。还有其他地方, 也可以通过这些函数也可以获取到很多有用的信息 。
这块我想处理一下C++ STD里面的string ,因为string 会被inline ,所以只能去宿主so里面去hook 。就一直没来得及时间去处理后面有时间补上 。
打印效果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
2023
-
04
-
01
23
:
51
:
02.119
22893
-
22970
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
>
/
storage
/
emulated
/
0
/
DCIM
/
.tmfs
/
.turing.dat arg2
-
>
/
storage
/
emulated
/
0
/
.turing.dat
2023
-
04
-
01
23
:
51
:
02.119
22893
-
22970
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
>
/
storage
/
emulated
/
0
/
DCIM
/
.tmfs
/
.turing.dat arg2
-
>
/
storage
/
emulated
/
0
/
DCIM
/
.tmfs
/
.turing.dat
2023
-
04
-
01
23
:
51
:
02.119
22893
-
22970
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
>
/
storage
/
emulated
/
0
/
.turing.dat arg2
-
>
/
storage
/
emulated
/
0
/
.turing.dat
2023
-
04
-
01
23
:
51
:
02.120
22893
-
22970
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
>
/
data
/
user
/
0
/
com.xxxxxx.vvvv
/
app_turingdfp
/
1
/
.turing.dat arg2
-
>
/
storage
/
emulated
/
0
/
.turing.dat
2023
-
04
-
01
23
:
51
:
02.120
22893
-
22970
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
>
/
data
/
user
/
0
/
com.xxxxxx.vvvv
/
app_turingdfp
/
1
/
.turing.dat arg2
-
>
/
storage
/
emulated
/
0
/
DCIM
/
.tmfs
/
.turing.dat
2023
-
04
-
01
23
:
51
:
02.136
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcpy() arg1
-
> android.os.Handler$MessengerImpl arg2
-
> android.os.Handler$MessengerImplresult
-
> android.os.Handler$MessengerImpl
2023
-
04
-
01
23
:
51
:
02.136
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
> org.chromium.android_webview.AwContents arg2
-
> android.os.Handler$MessengerImpl
2023
-
04
-
01
23
:
51
:
02.136
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
> android.app.ActivityThread$ApplicationThread arg2
-
> android.os.Handler$MessengerImpl
2023
-
04
-
01
23
:
51
:
02.136
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcpy() arg1
-
> android.os.IMessenger$Stub arg2
-
> android.os.IMessenger$Stubresult
-
> android.os.IMessenger$Stub
2023
-
04
-
01
23
:
51
:
02.137
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
> org.chromium.android_webview.AwContents arg2
-
> android.os.IMessenger$Stub
2023
-
04
-
01
23
:
51
:
02.137
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
> android.app.ActivityThread$ApplicationThread arg2
-
> android.os.IMessenger$Stub
2023
-
04
-
01
23
:
51
:
02.137
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcpy() arg1
-
> android.os.Binder arg2
-
> android.os.Binderresult
-
> android.os.Binder
2023
-
04
-
01
23
:
51
:
02.137
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
> org.chromium.android_webview.AwContents arg2
-
> android.os.Binder
2023
-
04
-
01
23
:
51
:
02.138
23013
-
23041
/
? I
/
Zhenxi: [文件名]strcmp() arg1
-
> android.app.ActivityThread$ApplicationThread arg2
-
> android.os.Binder
....
|
这个实现也很简单,直接hook artmethod里面的RegisterNative ,然后调用prettyMethod函数指针打印artmethod信息 。
我这块在在回调里面打印了,方法基础签名信息 ,绝对地址,相对地址,所属efl文件 。这个方法里面没做判断,会打印注册全部的信息 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
HOOK_DEF(void
*
, RegisterNative, void
*
thiz, void
*
native_method) {
string basicString
=
invokePrintf_org_PrettyMethodSym(thiz, true);
if
(isSave) {
*
invokeOs << basicString.append(
"\n"
);
}
Dl_info info;
dladdr(native_method, &info);
size_t relative_offset
=
reinterpret_cast<size_t>(native_method)
-
reinterpret_cast<size_t>(info.dli_fbase);
LOG(INFO) <<
"REGISTER_NATIVE "
<< basicString.c_str() <<
" absolute address(内存地址) -> "
<< native_method <<
" relative offset(相对地址) "
<<(void
*
)relative_offset
<<
"所属ELF文件 ["
<<getFileNameForPath(info.dli_fname)
+
"]"
;
return
orig_RegisterNative(thiz, native_method);
}
|
打印效果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
2023
-
04
-
01
23
:
53
:
12.617
24017
-
24247
/
? I
/
Zhenxi: REGISTER_NATIVE
int
com.xxxxxx.bbbb.core.downloadproxy.jni.TPDownloadProxyNative.clearCache(java.lang.String, java.lang.String,
int
)
absolute address(内存地址)
-
>
0x77787377fc
relative offset(相对地址)
0x3357fc
所属ELF文件 [mmmm.so]
2023
-
04
-
01
23
:
53
:
12.617
24017
-
24247
/
? I
/
Zhenxi: REGISTER_NATIVE
long
com.xxxxxx.bbbb.core.downloadproxy.jni.TPDownloadProxyNative.verifyOfflineCacheSync(java.lang.String,
int
, java.lang.String, java.lang.String)
absolute address(内存地址)
-
>
0x7778737908
relative offset(相对地址)
0x335908
所属ELF文件 [mmmm.so]
2023
-
04
-
01
23
:
53
:
12.617
24017
-
24247
/
? I
/
Zhenxi: REGISTER_NATIVE void com.xxxxxx.bbbb.core.downloadproxy.jni.TPDownloadProxyNative.setPlayerState(
int
,
int
)
absolute address(内存地址)
-
>
0x7778737a68
relative offset(相对地址)
0x335a68
所属ELF文件 [mmmm.so]
2023
-
04
-
01
23
:
53
:
12.617
24017
-
24247
/
? I
/
Zhenxi: REGISTER_NATIVE void com.xxxxxx.bbbb.core.downloadproxy.jni.TPDownloadProxyNative.updateTaskInfo(
int
, java.lang.String, java.lang.String)
absolute address(内存地址)
-
>
0x7778737a74
relative offset(相对地址)
0x335a74
所属ELF文件 [mmmm.so]
2023
-
04
-
01
23
:
53
:
12.617
24017
-
24247
/
? I
/
Zhenxi: REGISTER_NATIVE void com.xxxxxx.bbbb.core.downloadproxy.jni.TPDownloadProxyNative.updatePlayerPlayMsg(
int
,
int
,
int
,
int
)
absolute address(内存地址)
-
>
0x7778737b78
relative offset(相对地址)
0x335b78
所属ELF文件 [mmmm.so]
2023
-
04
-
01
23
:
53
:
12.617
24017
-
24247
/
? I
/
Zhenxi: REGISTER_NATIVE boolean com.xxxxxx.bbbb.core.downloadproxy.jni.TPDownloadProxyNative.isNativeReadyForWork()
absolute address(内存地址)
-
>
0x7778737b8c
relative offset(相对地址)
0x335b8c
所属ELF文件 [mmmm.so]
...
|
这个方法里面主要是Hook了 linker 底层open的方法,在Linker刚刚将内存加载到内存里还没有进行初始化的时候,得到一个回调。
也是详细打印了各种信息 ,比如SO开始地址,结束地址,ELF的长度。可以在这块进行SO的dump和保存,这个时机点还有一个作用就是做监听和资源文件。
举个栗子,在对游戏源码进行脱壳和修复的时候,比如LUA文件的dump修复,是需要先Hook buffloader函数的 ,也就是在这这个时机点进行Hook 。So刚刚加载到内存里面,还没有进行源码的加载,即刻进行Hook ,这么一来他加载的文件都会被拦截。实际太早或者太晚,都可能导致dump的不全 。
这块也是暴露出来一个时机点,方便Hook 。
1
2
3
4
5
6
7
8
9
10
11
12
|
void onSoLoadedAfter(const char
*
filename,void
*
ret){
auto mapInfo
=
getSoBaseAddress(filename);
char
buffer
[PATH_MAX];
sprintf(
buffer
,
"linker load %s start-> 0x%zx end-> 0x%zx size -> %lu"
,
filename, mapInfo.start, mapInfo.end, (mapInfo.end
-
mapInfo.start));
if
(isSave) {
if
(hookStrHandlerOs !
=
nullptr) {
(
*
hookStrHandlerOs) <<
buffer
;
}
}
LOGI(
"%s "
,
buffer
);
}
|
打印效果如下:
1
2
3
4
5
6
7
8
9
|
2023
-
04
-
01
23
:
53
:
12.023
24017
-
24429
/
? I
/
Zhenxi: linker load
/
data
/
app
/
~~KJajvMQT0WLC5kpaiv75pA
=
=
/
baoming
-
ZPiDMFjwVCA12Ot9z_btog
=
=
/
lib
/
arm64
/
yyyy.so start
-
>
0x77a275b000
end
-
>
0x77a27ad000
size
-
>
335872
2023
-
04
-
01
23
:
53
:
12.140
24017
-
24247
/
? I
/
Zhenxi: linker load
/
data
/
app
/
~~KJajvMQT0WLC5kpaiv75pA
=
=
/
baoming
-
ZPiDMFjwVCA12Ot9z_btog
=
=
/
lib
/
arm64
/
kkkk.so start
-
>
0x778f68b000
end
-
>
0x779054c000
size
-
>
15470592
2023
-
04
-
01
23
:
53
:
12.172
24017
-
24424
/
? I
/
Zhenxi: linker load libnetd_client.so start
-
>
0x78ff86c000
end
-
>
0x78ff875000
size
-
>
36864
2023
-
04
-
01
23
:
53
:
12.362
24017
-
24429
/
? I
/
Zhenxi: linker load
/
data
/
app
/
~~KJajvMQT0WLC5kpaiv75pA
=
=
/
baoming
-
ZPiDMFjwVCA12Ot9z_btog
=
=
/
lib
/
arm64
/
libckeygeneratorV2.so start
-
>
0x7783a48000
end
-
>
0x7783aeb000
size
-
>
667648
2023
-
04
-
01
23
:
53
:
12.410
24017
-
24429
/
? I
/
Zhenxi: linker load
/
data
/
app
/
~~KJajvMQT0WLC5kpaiv75pA
=
=
/
baoming
-
ZPiDMFjwVCA12Ot9z_btog
=
=
/
lib
/
arm64
/
libxps_ws.so start
-
>
0x778159a000
end
-
>
0x77816ef000
size
-
>
1396736
2023
-
04
-
01
23
:
53
:
12.611
24017
-
24247
/
? I
/
Zhenxi: linker load
/
data
/
app
/
~~KJajvMQT0WLC5kpaiv75pA
=
=
/
baoming
-
ZPiDMFjwVCA12Ot9z_btog
=
=
/
lib
/
arm64
/
libDownloadProxy.so start
-
>
0x7778402000
end
-
>
0x7778d73000
size
-
>
9900032
2023
-
04
-
01
23
:
53
:
12.700
24017
-
24247
/
? I
/
Zhenxi: linker load libc.so start
-
>
0x7900557000
end
-
>
0x7900653000
size
-
>
1032192
2023
-
04
-
01
23
:
53
:
12.729
23903
-
23903
/
? I
/
Zhenxi: linker load
/
data
/
app
/
~~KJajvMQT0WLC5kpaiv75pA
=
=
/
baoming
-
ZPiDMFjwVCA12Ot9z_btog
=
=
/
lib
/
arm64
/
kkkk.so start
-
>
0x772d186000
end
-
>
0x772e047000
size
-
>
15470592
...
|
主要是Hook了artmethod的invoke方法,一切的java方法底层都会走这个方法,包括脱壳也是修改的这个方法获取被抽取的指令,然后对Dex进行重构 。
我想尝试在这个方法里面对参数进行打印,但是失败了,在源码里面很好修改,但是通过Hook的话很多函数拿不到 ,加上一些核心的方法被编译器inline了,很不好操作和处理,就没继续关注了 。如果你有想法和思路可以提issues 或者提交代码 。这个方法不建议开启,个人用的很少,主要打印量太大了,一秒几千条日志 。他会打印系统的一些Java方法 ,所以很卡顿 ,不过如果你想做监听和记录 ,分析一些隐藏Api很有用 。callback实现如下 。
1
2
3
4
5
6
7
8
9
10
11
12
|
HOOK_DEF(void
*
, invoke, void
*
thiz, void
*
self
, uint32_t
*
args, uint32_t args_size, void
*
result,
const char
*
shorty) {
string basicString
=
invokePrintf_org_PrettyMethodSym(thiz, true);
LOG(INFO) <<
"invoke method info -> "
<< basicString;
if
(isSave) {
*
invokeOs << basicString.append(
"\n"
);
}
return
orig_invoke(thiz,
self
, args, args_size, result, shorty);
}
|
这个是一个我自己封装的一个native库,里面有很多常用的方法,都进行了封装 ,删除了一些改机和没用的模块,留了一些常用的模块 。
主要是下面几个比较常用的 ,也方便后续开发和维护 。
比如常见的hook操作 。如何inlinehook少于四个字节的方法,如何插装hook和如何异常hook 。这些都不需要关注,导入头文件以后直接 。
1
|
HookUtils::Hooker(xxx,(void
*
) xxx,(void
*
*
) &new_xxx);
|
一键hook即可 。底层封装分三步实现,先dobby hook ,失败了则使用异常hook, 最后dobby插装 。代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
bool
HookUtils::Hooker(void
*
dysym, void
*
newrep, void
*
*
org) {
if
(dysym
=
=
nullptr) {
LOG(ERROR) <<
"dobby hook org == null "
;
return
false;
}
if
(hookedList
=
=
nullptr) {
hookedList
=
new
list
<void
*
>();
}
/
/
如果这个地址已经被Hook了 。也有可能返回失败 。dobby 会提示 already been hooked 。
for
(void
*
ptr:
*
hookedList) {
if
(ptr
=
=
dysym) {
/
/
如果保存了这个地址,说明之前hook成功过,我们也认为hook成功
return
true;
}
}
bool
ret
=
DobbyHook(dysym,
reinterpret_cast<dobby_dummy_func_t>(newrep),
reinterpret_cast<dobby_dummy_func_t
*
>(org))
=
=
RT_SUCCESS;
if
(ret) {
/
/
LOG(ERROR) <<
"hook utils hook success !"
;
/
/
将地址添加到已经hook的列表,防止这个地址被多次hook
hookedList
-
>push_back(dysym);
return
true;
}
/
/
如果dobby hook失败了,采用sandhook异常hook进行补救,
LOG(ERROR) <<
"zhenxi runtime inlinehook start sandhook InlineHookImpl "
;
ret
=
SandHook::Inline::InlineHookImpl(dysym, newrep, org);
if
(ret) {
hookedList
-
>push_back(dysym);
return
true;
}
LOG(ERROR)
<<
">>>>>>>>>>>>>>> sandhook inlinehook hook error,start dobby branch_trampoline hook "
;
/
/
如果sandhook sign hook 也失败了,我们采用dobby附近插装去hook
dobby_enable_near_branch_trampoline();
/
/
二次hook
ret
=
DobbyHook(dysym,
reinterpret_cast<dobby_dummy_func_t>(newrep),
reinterpret_cast<dobby_dummy_func_t
*
>(org))
=
=
RT_SUCCESS;
/
/
关闭附近插装
dobby_disable_near_branch_trampoline();
if
(!ret) {
LOG(ERROR) <<
"!!!!!!!!!!!!!!! HookUtils hook error "
;
return
false;
}
hookedList
-
>push_back(dysym);
return
ret;
}
|
其他的不一一概述了,感兴趣的可以去看代码 。项目主要采用C++ 20编译的,需要NDK 23以上版本支持 。
https://github.com/w296488320/XposedJniTrace
可以基于我这个项目改改即可实现Xposed Hook Naitve 的其他功能 。不过好像Lsp已经提供了NaitiveHook,但是具体的Api 还一直没看。
等什么时候需要的时候再说吧,这个版本为了兼容老版本Xposed ,用的构建版本很低 。后续可能仅支持lsp了 。
愿天下剑士人人会两袖青蛇, 人人可剑开天门 。
------《雪中悍刀行》
更多【XposedJnitrace】相关视频教程:www.yxfzedu.com