I de-compiled TikTok (“com.zhiliaoapp.musically” v33.3.3 from Feb 2nd, 2024) from ApkPure.net and noticed that the way it called AppsFlyer looked a bit different than what I expected and quickly led me to a GitHub issue which makes it seem like they are using an outdated way to collect install information from Google Play which may have security vulnerabilities. Particularly, could a malicious app use this to ‘steal’ TikTok install attributions?
In this setup you see that TikTok is using a receiver com.appsflyer.SingleInstallBroadcastReceiver to listen for the com.android.vending.INSTALL_REFERRER event. This might allow a malicious app to listen for for the INSTALL_REFERRER event. You can see this is not recommended by AppsFlyer in this GitHub issue and is not their recommended installation setup.
I think the potential here might that if this version of TikTok is leaking the INSTALL_REFERRER data then
It could provide a malicious app valuable information in attempting to steal information about the source of TikTok’s users.
Additionally, a malicious app might be able to perform click jacking given that they know some information about the source of the install very early on.
And finally since the INSTALL_REFERRER is broadly scoped the malicious app could then send the a false INSTALL_REFERRER which TikTok may not be able to validate.
Would you like to see what data is coming out of TikTok when you first open it up? Let’s get to it.
Didn’t this used to be easy to do?
As security for iPhones and Androids increased it continually made viewing the traffic leaving your own device more difficult. This is in stark contrast to a regular web browser where you can easily open the network traffic for any site to see the back and forth of the network calls while you visit that site. For mobile apps, if you want to see the traffic on a computer, you need to do a proxy with a custom CA certificate. For years this was with Charles Proxy, Burpsuite and Wireshark. But apps continued to evolve and many began to put their own custom CA certificates inside the apps, meaning it was no longer enough to just forward the traffic to a proxy, you now had to unpin the SLL certificate within the app. This flow below is still possible with Charles or others, but in the end I ended up using the following tools.
Waydroid – Android Emulator: To install the target app
+ Magisk for root + allowing user certificates
+ LSPosed for SSL unpinning
MITM Proxy: to capture and view the decrypted traffic
I got the APK from APKpure.net and installed it into the emulator. We rolled the dice on a lucky day and happened to download TikTok v33.3.3 so you’ll be seeing that number a lot later. Then with the MITM proxy running, I opened the app for the first time.
Launching TikTok
All the request in the rest of the post are fired within 2-3 seconds of launching, so lots of back and forth within the app and on the network.
Keep in mind, this is all traffic from the emulator, so there are other network requests mixed in that are not from TikTok.
The first one that is for sure from TikTok. You you can see the request to tiktokv.com/monitor/appmonitor/v3/settings with some basic settings being recorded.
Here we see TikTok trying to figure out the carrier_region and network_sim_region all of which are empty due to Waydroid emulator not having a sim card.
Now we see the main set of data coming out of the device. This information is repeated in most other requests back and forth. Let’s note some of the interesting ones:
op_region/sys_region: US
device_type: WayDroid x86_64 Device
device_brand: waydroid
language: en
os_api: 30
os_version: 11
openudid: 6b623cdca1a14c25
manifest_version_code: 2023303030
resolution: 3456*1970
dpi: 360
These are all accurate to the emulator Waydroid, running on my laptop, note the resolution. The op_region/local US and en are related to the Google Play account I am signed into with on the phone, while Asia/Taipei is the current timezone.
Next we see calls for api-boot.tiktokv.com/aweme/v1/compliance which note that I am not a teen or child. Additionally is_new_user=0 which must mean that I’ve before downloaded TikTok with this Google account. Keep in mind, this is all within a second or two of running the app, so I assume this must be coming from Android operating system, and it’s possible last year I had installed TikTok at some point on the emulator, though I don’t remember that.
GET https://api-boot.tiktokv.com/aweme/v1/compliance/settings/?teen_mode_status=0&ftc_child_mode=-1&is_new_user=0&ac=mobile&channel=googleplay&aid=1233&app_name=musical_ly&version_code=330303
Query
teen_mode_status: 0
ftc_child_mode: -1
is_new_user: 0
Bytes & Encoded data
Now we get to several sets of data that I am unsure how to decode. They generally were mixed in and looked like raw data as well as base64 looking encoded data inside JSONs. These were usually outgoing to log-boot.tiktokv.com/service/2/ . If anyone has some advice for how I can decode those, please let me know, I’d love to try. Below is one of the ones in a json called ‘tt_info’
Here’s another interesting one, maybe it’s reading into it too much, but it goes to passport/device/trust_users and the response trusted_users: null makes me think I’m not yet a trusted user 🤔
This one libra was super interesting, in the RESPONSE we see some interesting things. Looks like they are testing AppsFlyer as well as something related to mac address, though unclear what it is:
Later on in that we also see some lists of whitelisted js (javascript?) services that likely can run in the Webview. Interestingly, there is also a large list for music as well. I tried looking into these services and many seem like likely partners for tools like captcha, payment. faceueditor I couldn’t find though.
And now we’re getting to some content, like this still JPEG:
So, now it’s 3 seconds later, and let’s see where we are in the app experience:
{
"data": {
"/aweme/v1/compliance/settings/": {
"body": {
"about_privacy_policy_url": "https://www.tiktok.com/legal/privacy-policy-row",
"ad_personality_settings": {
"att_status": 255,
"is_follow_sys_config": false,
"is_np_user": 1,
"is_show_settings": false,
"limit_ad_tracking": false,
"mode": 0,
"need_pop_up": false,
"pa_revising_switch": false,
"pers_ad_data_received_partner_mode": 0,
"pers_ad_show_data_received_partner": false,
"pers_ad_show_third_party_networks": false,
"pers_ad_third_party_networks_mode": 0,
"unified_mode": 0
},
"age_gate_info": {
"age_gate_action": 0,
"age_gate_post_action": 0,
"register_age_gate_action": 0
},
"cmpl_enc": "UNKNOWN",
"commercial_content_library_url": "https://www.tiktok.com/adlibrary",
"device_limit_register_expired": true,
"extra": {
"fatal_item_ids": [],
"logid": "20240202034347AC84497362B5DDABA59B",
"now": 1706845428000
},
"idfa_popup_allow": false,
"interface_control_settings": "{\"rules\":null,\"use_new_control\":true,\"user_type\":\"-1\",\"version\":\"11\"}",
"log_pb": {
"impr_id": "20240202034347AC84497362B5DDABA59B"
},
"parental_guardian_name": "Family Pairing",
"policy_info_list": [
{
"policy_key": "privacy-policy",
"policy_url": "https://www.tiktok.com/legal/privacy-policy"
},
{
"policy_key": "terms-of-service",
"policy_url": "https://www.tiktok.com/legal/terms-of-service"
},
{
"policy_key": "tiktok-shoutouts-user-terms-of-service",
"policy_url": "https://www.tiktok.com/legal/tiktok-shoutouts-user-terms-of-service"
},
{
"policy_key": "cookie-policy",
"policy_url": "https://www.tiktok.com/legal/cookie-policy"
},
{
"policy_key": "virtual-items",
"policy_url": "https://www.tiktok.com/legal/virtual-items"
},
{
"policy_key": "rewards-policy-eea",
"policy_url": "https://www.tiktok.com/legal/rewards-policy-eea"
},
{
"policy_key": "privacy-policy-for-younger-users",
"policy_url": "https://www.tiktok.com/legal/privacy-policy-for-younger-users"
},
{
"policy_key": "copyright-policy",
"policy_url": "https://www.tiktok.com/legal/copyright-policy"
},
{
"policy_key": "changes-to-personalised-advertising-in-the-eea",
"policy_url": "https://www.tiktok.com/legal/changes-to-personalised-advertising-in-the-eea"
}
],
"policy_notice_enable": true,
"status_code": 0,
"terms_consent_for_register_info_new_users": {
"checkbox_agree_all_terms": "Agree to all",
"checkbox_privacy_policy": "Consent to the collection and use of personal information (Required)",
"checkbox_terms_of_use": "Consent to the Terms of Service (Required)",
"checkbox_tr_notification_subtitle": "Get notifications about trending videos and promotions on TikTok. You can review and edit your settings at any time. Not allowing this type of notification does not limit your use of the TikTok service.",
"checkbox_tr_notification_title": "Consent to the receipt of trending content and promotional notifications (Optional)",
"tiktok_privacy_policy_url": "https://www.tiktok.com/legal/terms-and-conditions-kr?lang=ko-KR",
"tiktok_terms_of_use_url": "https://www.tiktok.com/legal/page/row/terms-of-service/ko-KR",
"title": "Terms and conditions"
}
},
...
Let’s tap agree and see what happens.
POST https://api22-normal-c-useast1a.tiktokv.com/consent/api/record/create/v1?...
entity_keys: conditions-policy-device-consent
business_flow: consent_box
status: 1
And an updated set of compliance, similar to the one from above.
GET https://api22-normal-c-useast1a.tiktokv.com/aweme/v1/compliance/settings/?teen_mode_status=0&ftc_child_mode=-1&is_new_user=0
...
RESPONSE:
{
"about_privacy_policy_url": "https://www.tiktok.com/legal/privacy-policy-row",
"ad_personality_settings": {
"ad_free_subscription": {
"subscription_mode": 0
},
"att_status": 255,
"description": "With this setting, the ads you see on TikTok can be more tailored to your interests based on data that advertising partners share with us about your activity on their apps and websites.\nYou will always see ads on TikTok based on what you do on TikTok or other data described in our privacy policy.",
"disable_att_overwrite_pa": 1,
"enable_toggle_decoupling": true,
"is_follow_sys_config": false,
"is_new_user": 1,
"is_np_user": 0,
"is_show_3p_data_control": false,
"is_show_reset_entry": false,
"is_show_settings": true,
"is_teenager_mode": 0,
"limit_ad_tracking": false,
"mode": 1,
"need_pop_up": false,
"pa_revising_switch": false,
"pers_ad_data_received_partner_mode": 0,
"pers_ad_main_mode_title": "Using Off-TikTok activity for ad targeting",
"pers_ad_show_data_received_partner": false,
"pers_ad_show_interest_label": true,
"pers_ad_show_third_part_measurement": false,
"pers_ad_show_third_party_networks": false,
"pers_ad_third_party_networks_mode": 0,
"show_advertiser_settings": true,
"unified_mode": 0,
"use_new_interests": 1
},
"age_gate_info": {
"age_gate_action": 0,
"age_gate_post_action": 0,
"register_age_gate_action": 2
},
After Terms Of Service: And we’re off!
The app shows the video stream and the API requests are flying back and forth now, many a second, so there’s lots more to look at next time. Overall the data seems pretty standard with a few interesting things to look into like the still encrypted data, the connected services and of course, everything after you agree to those Terms of Service.