Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A comprehensive guide to tracker protection on Android

A comprehensive guide to tracker protection on Android

The vast majority of mobile applications leverage the use of third party libraries. Besides their main purpose, many of them will also collect and send your private information, using your data for their own benefit. For the past 18 months, we've been working on a solution that blocks all those trackers from any app in your device. This talk describes in detail the technology behind it, what Android APIs are used and lessons learnt through the whole process.

David González

November 08, 2022
Tweet

More Decks by David González

Other Decks in Technology

Transcript

  1. Comprehensive guide to tracking protection on Android David González -

    Software Engineer DuckDuckGo Øredev - 8 Nov 2022
  2. Agenda 1. What is a tracker? 2. Web vs App

    trackers 3. Why should I care? 4. How to block them
  3. Piece of software whose task is to gather information on

    the person using the application or website
  4. T R A C K E R R A D

    A R • DuckDuckGo Tracker Radar is a data set about trackers that is automatically generated and maintained through continuous crawling and analysis. • This data set is publicly available in (https:// github.com/duckduckgo/tracker-radar) to use for research and for generating tracker block lists. And, the code behind it is open source
  5. Generating a blocklist for Android 21 • Top 300 apps

    from https://androidrank.org/ • Install root certi fi cate • Start using apps • Record tra ff i c • Looks for inputs
  6. Block web trackers class BrowserWebViewClient() : WebViewClient() { override fun

    shouldInterceptRequest( webView: WebView, request: WebResourceRequest ) : WebResourceResponse? { private fun blockRequest( trackingEvent: TrackingEvent, request: WebResourceRequest, webViewClientListener: WebViewClientListener? ) : WebResourceResponse { trackingEvent.surrogateId ?. let { surrogateId -> val surrogate = resourceSurrogates.get(surrogateId) if (surrogate.responseAvailable) { Timber.d("Surrogate found for ${request.url}") webViewClientListener ?. surrogateDetected(surrogate) return WebResourceResponse(surrogate.mimeType, "UTF-8", surrogate.jsFunction.byteInputStream()) } } Timber.d("Blocking request ${request.url}") privacyProtectionCountDao.incrementBlockedTrackerCount() return WebResourceResponse(null, null, null) }
  7. Block web trackers class BrowserWebViewClient() : WebViewClient() { override fun

    shouldInterceptRequest( webView: WebView, request: WebResourceRequest ) : WebResourceResponse? { private fun blockRequest( trackingEvent: TrackingEvent, request: WebResourceRequest, webViewClientListener: WebViewClientListener? ) : WebResourceResponse { trackingEvent.surrogateId ?. let { surrogateId -> val surrogate = resourceSurrogates.get(surrogateId) if (surrogate.responseAvailable) { Timber.d("Surrogate found for ${request.url}") webViewClientListener ?. surrogateDetected(surrogate) return WebResourceResponse(surrogate.mimeType, "UTF-8", surrogate.jsFunction.byteInputStream()) } } Timber.d("Blocking request ${request.url}") privacyProtectionCountDao.incrementBlockedTrackerCount() return WebResourceResponse(null, null, null) }
  8. Block web trackers class BrowserWebViewClient() : WebViewClient() { private fun

    blockRequest( trackingEvent: TrackingEvent, request: WebResourceRequest, webViewClientListener: WebViewClientListener? ) : WebResourceResponse { trackingEvent.surrogateId ?. let { surrogateId -> val surrogate = resourceSurrogates.get(surrogateId) if (surrogate.responseAvailable) { Timber.d("Surrogate found for ${request.url}") webViewClientListener ?. surrogateDetected(surrogate) return WebResourceResponse(surrogate.mimeType, "UTF-8", surrogate.jsFunction.byteInputStream()) } } Timber.d("Blocking request ${request.url}") privacyProtectionCountDao.incrementBlockedTrackerCount() return WebResourceResponse(null, null, null) } }
  9. Block web trackers class BrowserWebViewClient() : WebViewClient() { private fun

    blockRequest( trackingEvent: TrackingEvent, request: WebResourceRequest, webViewClientListener: WebViewClientListener? ) : WebResourceResponse { trackingEvent.surrogateId ?. let { surrogateId -> val surrogate = resourceSurrogates.get(surrogateId) if (surrogate.responseAvailable) { Timber.d("Surrogate found for ${request.url}") webViewClientListener ?. surrogateDetected(surrogate) return WebResourceResponse(surrogate.mimeType, "UTF-8", surrogate.jsFunction.byteInputStream()) } } Timber.d("Blocking request ${request.url}") privacyProtectionCountDao.incrementBlockedTrackerCount() return WebResourceResponse(null, null, null) } }
  10. Block web trackers class BrowserWebViewClient() : WebViewClient() { private fun

    blockRequest( trackingEvent: TrackingEvent, request: WebResourceRequest, webViewClientListener: WebViewClientListener? ) : WebResourceResponse { trackingEvent.surrogateId ?. let { surrogateId -> val surrogate = resourceSurrogates.get(surrogateId) if (surrogate.responseAvailable) { Timber.d("Surrogate found for ${request.url}") webViewClientListener ?. surrogateDetected(surrogate) return WebResourceResponse(surrogate.mimeType, "UTF-8", surrogate.jsFunction.byteInputStream()) } } Timber.d("Blocking request ${request.url}") privacyProtectionCountDao.incrementBlockedTrackerCount() return WebResourceResponse(null, null, null) } }
  11. Block web trackers class BrowserWebViewClient() : WebViewClient() { private fun

    blockRequest( trackingEvent: TrackingEvent, request: WebResourceRequest, webViewClientListener: WebViewClientListener? ) : WebResourceResponse { trackingEvent.surrogateId ?. let { surrogateId -> val surrogate = resourceSurrogates.get(surrogateId) if (surrogate.responseAvailable) { Timber.d("Surrogate found for ${request.url}") webViewClientListener ?. surrogateDetected(surrogate) return WebResourceResponse(surrogate.mimeType, "UTF-8", surrogate.jsFunction.byteInputStream()) } } Timber.d("Blocking request ${request.url}") privacyProtectionCountDao.incrementBlockedTrackerCount() return WebResourceResponse(null, null, null) } }
  12. Block third party cookies private suspend fun processThirdPartyCookiesSetting( webView: WebView,

    uri: Uri ) { val host = uri.host ?: return val domain = authCookiesAllowedDomainsRepository.getDomain(host) withContext(dispatchers.main()) { if (domain != null && hasUserIdCookie()) { Timber.d("Cookies enabled for $uri") cookieManagerProvider.get().setAcceptThirdPartyCookies(webView, true) } else { Timber.d("Cookies disabled for $uri") cookieManagerProvider.get().setAcceptThirdPartyCookies(webView, false) } domain ?. let { deleteHost(it) } } }
  13. Block third party cookies private suspend fun processThirdPartyCookiesSetting( webView: WebView,

    uri: Uri ) { val host = uri.host ?: return val domain = authCookiesAllowedDomainsRepository.getDomain(host) withContext(dispatchers.main()) { if (domain != null && hasUserIdCookie()) { Timber.d("Cookies enabled for $uri") cookieManagerProvider.get().setAcceptThirdPartyCookies(webView, true) } else { Timber.d("Cookies disabled for $uri") cookieManagerProvider.get().setAcceptThirdPartyCookies(webView, false) } domain ?. let { deleteHost(it) } } }
  14. Block third party cookies private suspend fun processThirdPartyCookiesSetting( webView: WebView,

    uri: Uri ) { val host = uri.host ?: return val domain = authCookiesAllowedDomainsRepository.getDomain(host) withContext(dispatchers.main()) { if (domain != null && hasUserIdCookie()) { Timber.d("Cookies enabled for $uri") cookieManagerProvider.get().setAcceptThirdPartyCookies(webView, true) } else { Timber.d("Cookies disabled for $uri") cookieManagerProvider.get().setAcceptThirdPartyCookies(webView, false) } domain ?. let { deleteHost(it) } } }
  15. Identify calling app private fun getPackageIdForUid(uid: Int) : String {

    val packages: Array<String>? try { packages = packageManager.getPackagesForUid(uid) } catch (e: SecurityException) { Timber.e(e, "Failed to get package ID for UID : $uid due to security violation.") return "unknown" } if (packages.isNullOrEmpty()) { Timber.w("Failed to get package ID for UID : $uid") return "unknown" } return packages.f i rst() }
  16. Identify calling app private fun getPackageIdForUid(uid: Int) : String {

    val packages: Array<String>? try { packages = packageManager.getPackagesForUid(uid) } catch (e: SecurityException) { Timber.e(e, "Failed to get package ID for UID : $uid due to security violation.") return "unknown" } if (packages.isNullOrEmpty()) { Timber.w("Failed to get package ID for UID : $uid") return "unknown" } return packages.f i rst() }
  17. Blocking an app tracker // Called from native code private

    fun isAddressAllowed(packet: Packet) : Allowed? { packet.allowed = (callback.get() ?. isAddressBlocked(packet.toAddressRR()) == false) return if (packet.allowed) Allowed() else null }
  18. Blocking an app tracker /** * Called by the VPN

    network to know if a particular IP address is blocked or not. * This can be combined with the [onDnsResolved] callback, to get the hostname of the * [addressRR] and then decide whether that hostname should be blocked or not * @param addressRR is the address record */ fun isAddressBlocked(addressRR : AddressRR) : Boolean
  19. Blocking an app tracker override fun isAddressBlocked(addressRR : AddressRR) :

    Boolean { val hostname = addressLookupLruCache[addressRR.address] ?: return false val domainAllowed = shouldAllowDomain(hostname, addressRR.uid) return !domainAllowed }
  20. Blocking an app tracker private fun shouldAllowDomain(name: String, uid: Int)

    : Boolean { val packageId = getPackageIdForUid(uid) val type = appTrackerRepository.f i ndTracker(name, packageId) if (type is AppTrackerType.ThirdParty && !isTrackerInExceptionRules(packageId = packageId, hostname = name)) { val trackingApp = appNamesCache[packageId] ?: appNameResolver.getAppNameForPackageId(packageId) // if the app name is unknown, do not block if (trackingApp.isUnknown()) return true VpnTracker( trackerCompanyId = type.tracker.trackerCompanyId, domain = type.tracker.hostname, trackingApp = TrackingApp(trackingApp.packageId, trackingApp.appName) ).run { appTrackerRecorder.insertTracker(this) } return false } return true }
  21. Blocking an app tracker private fun shouldAllowDomain(name: String, uid: Int)

    : Boolean { val packageId = getPackageIdForUid(uid) val type = appTrackerRepository.f i ndTracker(name, packageId) if (type is AppTrackerType.ThirdParty && !isTrackerInExceptionRules(packageId = packageId, hostname = name)) { val trackingApp = appNamesCache[packageId] ?: appNameResolver.getAppNameForPackageId(packageId) // if the app name is unknown, do not block if (trackingApp.isUnknown()) return true VpnTracker( trackerCompanyId = type.tracker.trackerCompanyId, domain = type.tracker.hostname, trackingApp = TrackingApp(trackingApp.packageId, trackingApp.appName) ).run { appTrackerRecorder.insertTracker(this) } return false } return true }
  22. Blocking an app tracker private fun shouldAllowDomain(name: String, uid: Int)

    : Boolean { val packageId = getPackageIdForUid(uid) val type = appTrackerRepository.f i ndTracker(name, packageId) if (type is AppTrackerType.ThirdParty && !isTrackerInExceptionRules(packageId = packageId, hostname = name)) { val trackingApp = appNamesCache[packageId] ?: appNameResolver.getAppNameForPackageId(packageId) // if the app name is unknown, do not block if (trackingApp.isUnknown()) return true VpnTracker( trackerCompanyId = type.tracker.trackerCompanyId, domain = type.tracker.hostname, trackingApp = TrackingApp(trackingApp.packageId, trackingApp.appName) ).run { appTrackerRecorder.insertTracker(this) } return false } return true }