该篇文章讲解文件监控原理,虽然题目为魅族手机,其实监控原理与aosp源码一致,所以我先用了root的pixel 4a进行分析
写了一个app-demo,进行简单的文件删除操作:
File oldPath = new File("/storage/emulated/0/libs/3.txt");
try {
oldPath.delete();
} catch (Exception e) {
e.printStackTrace();
}
AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:requestLegacyExternalStorage="true"
....
...
>
</application>
运行app,抓adb log
04-03 15:44:07.737 6571 18760 D MediaProvider: /storage/emulated/0/libs/.trashed-1619855047-2 5456 true
04-03 15:44:07.741 6571 18760 I MediaProvider: Deleted 1 items on external_primary due to com.example.demo
logcat中会有MediaProvider的log,我们看看该进程找一下是哪个包,然后将其apk拿出来
sunfish:/ $ ps -A |grep 3238
u0_a190 3238 706 13989172 135984 0 0 S com.android.providers.media.module
sunfish:/ $
sunfish:/ $
sunfish:/ $
sunfish:/ $ pm path com.android.providers.media.module
package:/apex/com.android.mediaprovider/priv-app/MediaProvider/MediaProvider.apk
sunfish:/ $
运行adb pull拿出来apk进行反编译可进行查看
admin@C02D7132MD6R frida-agent-example % adb pull /apex/com.android.mediaprovider/priv-app/MediaProvider/MediaProvider.apk
/apex/com.android.mediaprovider/priv-app/MediaProvider/MediaProvider.apk: 1 file pulled, 0 skipped. 36.0 MB/s (3917537 bytes in 0.104s)
admin@C02D7132MD6R frida-agent-example %
admin@C02D7132MD6R frida-agent-example %
grep搜索上述的adb log关键字,查找关键函数
因为本人使用的手机是pixel 4a, 刷机lineageOS 18.1, 不需要反编译,可以直接下载lineageos 18.1的代码进行调试
可以从github下载源码
admin@C02D7132MD6R MediaProvider % grep "Deleted " -rn *
src/com/android/providers/media/util/FileUtils.java:174: Log.d(TAG, "Deleted old file " + file);
src/com/android/providers/media/util/Metrics.java:66: "Deleted %3$d items on %1$s due to %2$s",
src/com/android/providers/media/LocalCallingIdentity.java:366: final boolean isDeleted = index > -1;
src/com/android/providers/media/MediaProvider.java:1035: Log.d(TAG, "Deleted " + expiredMedia + " expired items");
admin@C02D7132MD6R MediaProvider %
毫无疑问, Metrics.java中打印的日志。
public static void logDeletion(@NonNull String volumeName, int uid, String packageName,
int itemCount) {
Logging.logPersistent(String.format(
"Deleted %3$d items on %1$s due to %2$s",
volumeName, packageName, itemCount));
MediaProviderStatsLog.write(MEDIA_CONTENT_DELETED,
translateVolumeName(volumeName), uid, itemCount);
}
我们直接在此处打断点,运行File.delete进行删除操作:
从图中我们可以看到调用栈
deleteFileForFuse->delete->delete->deleteInternal->logDeletion
最底层调用点为deleteFileForFuse, 该函数为jni调用过来的
先grep查看代码何处调用deleteFileForFuse
admin@C02D7132MD6R MediaProvider % grep "deleteFileForFuse" -rn *
src/com/android/providers/media/MediaProvider.java:6861: public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
tests/src/com/android/providers/media/MediaProviderForFuseTest.java:115: Truth.assertThat(sMediaProvider.deleteFileForFuse(
admin@C02D7132MD6R MediaProvider % vim src/com/android/providers/media/MediaProvider.java +6861
admin@C02D7132MD6R MediaProvider %
admin@C02D7132MD6R MediaProvider % grep "deleteFile" -rn *
jni/MediaProviderWrapper.cpp:95:int deleteFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_delete_file,
jni/MediaProviderWrapper.cpp:265: mid_delete_file_ = CacheMethod(env, "deleteFile", "(Ljava/lang/String;I)I", /*is_static*/ false);
jni/MediaProviderWrapper.cpp:324: return deleteFileInternal(env, media_provider_object_, mid_delete_file_, path, uid);
src/com/android/providers/media/MediaProvider.java:6834: private static int deleteFileUnchecked(@NonNull String path) {
src/com/android/providers/media/MediaProvider.java:6861: public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
src/com/android/providers/media/MediaProvider.java:6871: return deleteFileUnchecked(path);
src/com/android/providers/media/MediaProvider.java:6888: return deleteFileUnchecked(path);
tests/src/com/android/providers/media/MediaProviderForFuseTest.java:115: Truth.assertThat(sMediaProvider.deleteFileForFuse(
admin@C02D7132MD6R MediaProvider %
mid_delete_file_为jni调用点
admin@C02D7132MD6R MediaProvider % grep mid_delete_file_ -rn *
jni/MediaProviderWrapper.cpp:265: mid_delete_file_ = CacheMethod(env, "deleteFile", "(Ljava/lang/String;I)I", /*is_static*/ false);
jni/MediaProviderWrapper.cpp:324: return deleteFileInternal(env, media_provider_object_, mid_delete_file_, path, uid);
jni/MediaProviderWrapper.h:182: jmethodID mid_delete_file_;
admin@C02D7132MD6R MediaProvider %
int MediaProviderWrapper::DeleteFile(const string& path, uid_t uid) {
if (uid == ROOT_UID) {
int res = unlink(path.c_str());
return res;
}
JNIEnv* env = MaybeAttachCurrentThread();
return deleteFileInternal(env, media_provider_object_, mid_delete_file_, path, uid);
}
DeleteFile调用点搜索
admin@C02D7132MD6R MediaProvider % vim jni/MediaProviderWrapper.cpp
admin@C02D7132MD6R MediaProvider %
admin@C02D7132MD6R MediaProvider %
admin@C02D7132MD6R MediaProvider %
admin@C02D7132MD6R MediaProvider % grep DeleteFile -rn *
jni/MediaProviderWrapper.cpp:317:int MediaProviderWrapper::DeleteFile(const string& path, uid_t uid) {
jni/FuseDaemon.cpp:833: int status = fuse->mp->DeleteFile(child_path, ctx->uid);
jni/FuseDaemon.cpp:1581: fuse->mp->DeleteFile(child_path.c_str(), req->ctx.uid);
jni/MediaProviderWrapper.h:74: int DeleteFile(const std::string& path, uid_t uid);
admin@C02D7132MD6R MediaProvider %
static void pf_unlink(fuse_req_t req, fuse_ino_t parent, const char* name) {
ATRACE_CALL();
struct fuse* fuse = get_fuse(req);
node* parent_node = fuse->FromInode(parent);
if (!parent_node) {
fuse_reply_err(req, ENOENT);
return;
}
const struct fuse_ctx* ctx = fuse_req_ctx(req);
const string parent_path = parent_node->BuildPath();
if (!is_app_accessible_path(fuse->mp, parent_path, ctx->uid)) {
fuse_reply_err(req, ENOENT);
return;
}
TRACE_NODE(parent_node, req);
const string child_path = parent_path + "/" + name;
//调用了DeleteFile
int status = fuse->mp->DeleteFile(child_path, ctx->uid);
if (status) {
fuse_reply_err(req, status);
return;
}
node* child_node = parent_node->LookupChildByName(name, false /* acquire */);
TRACE_NODE(child_node, req);
if (child_node) {
child_node->SetDeleted();
}
fuse_reply_err(req, 0);
}
pf_unlink函数调用点
admin@C02D7132MD6R MediaProvider % vim jni/FuseDaemon.cpp +883
admin@C02D7132MD6R MediaProvider % grep pf_unlink -rn *
jni/FuseDaemon.cpp:814:static void pf_unlink(fuse_req_t req, fuse_ino_t parent, const char* name) {
jni/FuseDaemon.cpp:1664: .mknod = pf_mknod, .mkdir = pf_mkdir, .unlink = pf_unlink, .rmdir = pf_rmdir,
admin@C02D7132MD6R MediaProvider %
FuseDaemon.cpp中的结构体使用了pf_unlink函数地址:
static struct fuse_lowlevel_ops ops{
.init = pf_init, .destroy = pf_destroy, .lookup = pf_lookup, .forget = pf_forget,
.getattr = pf_getattr, .setattr = pf_setattr, .canonical_path = pf_canonical_path,
.mknod = pf_mknod, .mkdir = pf_mkdir, .unlink = pf_unlink, .rmdir = pf_rmdir,
/*.symlink = pf_symlink,*/
.rename = pf_rename,
/*.link = pf_link,*/
.open = pf_open, .read = pf_read,
/*.write = pf_write,*/
.flush = pf_flush,
.release = pf_release, .fsync = pf_fsync, .opendir = pf_opendir, .readdir = pf_readdir,
.releasedir = pf_releasedir, .fsyncdir = pf_fsyncdir, .statfs = pf_statfs,
/*.setxattr = pf_setxattr,
.getxattr = pf_getxattr,
.listxattr = pf_listxattr,
.removexattr = pf_removexattr,*/
.access = pf_access, .create = pf_create,
/*.getlk = pf_getlk,
.setlk = pf_setlk,
.bmap = pf_bmap,
.ioctl = pf_ioctl,
.poll = pf_poll,*/
.write_buf = pf_write_buf,
/*.retrieve_reply = pf_retrieve_reply,*/
.forget_multi = pf_forget_multi,
/*.flock = pf_flock,
.fallocate = pf_fallocate,*/
.readdirplus = pf_readdirplus,
/*.copy_file_range = pf_copy_file_range,*/
};
void FuseDaemon::Start(android::base::unique_fd fd, const std::string& path) {
.....
.....
//该处使用了ops结构体
struct fuse_session
* se = fuse_session_new(&args, &ops, sizeof(ops), &fuse_default);
if (!se) {
PLOG(ERROR) << "Failed to create session ";
return;
}
fuse_default.se = se;
fuse_default.active = &active;
se->fd = fd.release(); // libfuse owns the FD now
se->mountpoint = strdup(path.c_str());
// Single thread. Useful for debugging
// fuse_session_loop(se);
// Multi-threaded
LOG(INFO) << "Starting fuse...";
fuse_session_loop_mt(se, &config);
fuse->active->store(false, std::memory_order_release);
LOG(INFO) << "Ending fuse...";
}
明显可以看出,使用了fuse_session_new函数, 然后通过fuse_session_loop_mt传至内核
fuse简介
安卓Q平台之后,开始限制app对sdcard的读写权限,非沙盒目录的文件不允许读写(除非使用legacy模式)。
ScopedStorage即使用fuse模块对app的文件系统调用进行监控。
fuse即用户空间文件系统。安卓平台android 4.4以后内置sdcard于data为同一分区。在安卓Q之后引入fuse进行管控第三方app对sdcard的读写管理,fuse内核将sdcard与用户隐私目录分隔开作为两个分区(用户隐私目录一般为/sdcard/Android/data/pkg、/data/data/pkg)。
当用户访问userdata分区则直接访问,当访问/sdcard,则先访问fuse分区,然后访问userdata分区(这里大家肯定有疑问,下面解释一下。)
sdcard其实还是userdata分区,只是fuse内核虚拟出来了一个fuse分区而已,相当于文件系统内核访问 hook。
现在MediaProvider 对sdcard下文件删除操作的监控就有了一个通顺的流程
顺便推荐: 绘制uml图软件google drawing