魅族手机文件删除-通知栏警告流程分析(上)

该篇文章讲解文件监控原理,虽然题目为魅族手机,其实监控原理与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
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值