The Internet was abuzz last week with news concerning Facebook's recently added permission to access your text messages from their Android application. This didn't seem to sit well with a lot of people, and now people are afraid to update the application or are choosing to remove the mobile application altogether. I was shocked to find at least half a dozen news articles talking about how Facebook is now "reading your texts, pictures, and calendars," with absolutely no technical backing besides the fact the application now asks for the permission. In fact, the only thing remotely close to technical was Facebook's response to whole thing, where they explain why their app does this:
But of course in the midst of all the NSA and related news, everyone put their tinfoil hats on and is a bit skeptical on the whole deal. I do think it's fine to question things like this, but there does need to be some closure on the whole thing. I decided to take a look at the Facebook mobile app and see whats really going on.
Before going any further I'd like to make a few things clear. First, I don't work for Facebook, and I'm not trying to bash them in any way. I also have no malicious intent of exposing the company; I'm simply trying to demonstrate on a technical level what this change means. If you're someone who doesn't care for all the technical fun stuff, skip right to the conclusions section.
So with that, let's get started!
I first downloaded the most recent version of the Facebook mobile application (version 5.0.0.26.31) to a test device, and then pulled the application to my testing machine. A quick check confirmed that this application does in fact ask to read SMS messages:
What does this mean? This means that the application is permitted to access the Android ContentProvider URLs "content://sms", "content://mms," and "content://mms-sms," the databases containing your SMS and MMS data. It does not allow the application to intercept incoming text messages or send them (regardless of what Kaspersky claims). These actions require the permissions "android.permission.RECEIVE_SMS" and "android.permission.SEND_SMS," respectively. This application did not ask for permission to do these actions.
I unpacked the APK with apktool and was surprised to find that the application did not obfuscate the class names. It did obfuscate class fields and methods (standard issue ProGuard), probably to save space. It also did not appear to use any string encryption or obfuscation, which made my job a lot easier.
As a first test, I wanted to install a modified version of the APK with debugging, so I changed the "android:debuggable" attribute to "true" in the "AndroidManifest.xml" file.
When I attempted to rebuild the APK, I was greeted by a bunch of resource repackaging errors, due to missing dependencies in the "framework-res.apk" I was using to repack the application. This is pretty common in large applications that are developed with proprietary resources. I won't go into the details (you can learn more here), but with a few changes to the "/res/values/styles.xml" file I was able to rebuild it successfully.
I then installed the new APK and attempted to launch the application, and was presented with a message indicating that the application has crashed. I checked the Android logs and saw this error:
The message was strange, since I don't recall that being a feature of Android. I did a quick string search for the error and found that in the method "c()V" of the class "smali/com/facebook/katana/app/FacebookApplicationImpl.smali" there was a quick check to make sure the "android:debuggable" flag was not set to true, which is actually a good security feature! If the flag was set to true, the application threw a RuntimeException and the application crashed.
To get by this, I simply removed the check, and repackaged the application.
The application could now be installed without errors.
I switched gears and started running some searches on the application's disassembled class files. Since I knew the application was not encrypting strings, searches for "mms","sms," "sms/inbox", "content://sms" should have returned something interesting. Instead, I found absolutely nothing interesting. Weird. I considered maybe the application asked for the permission, but didn't use it (yet), but that seemed unlikely.
I decided to rerun the app on my device and check the logs. As the app loaded, I noticed something very interesting - the application was copying additional DEX files, and loading them!
I've personally never seen this, but it appears that the application copied three additional XZ compressed assets, uncompressed them, and then used a DexClassLoader to load them. These 3 DEX files also contained "canary" checks, but it was still possible to modify the classes and rebuild. Here are the files:
I decompressed and disassembled all three, and realized there were now over 10,000 new classes to search through. I repeated my search above, and this time found something a bit more interesting.
We now have a few leads to look at. In particular, we have the class "/secondary-2/com/facebook/confirmation/task/ReadSmsConfirmAccountBackgroundTask.smali" and the classes "/secondary-1/android_src/provider/Telephony*.smali." I'll start with the class "ReadSmsConfirmAccountBackgroundTask.smali."
The file "/secondary-2/com/facebook/confirmation/task/ReadSmsConfirmAccountBackgroundTask.smali" contained the string "content://sms/inbox," which proceeded a query to the SMS content provider. This was a good starting point.
This class is actually quite large, and makes several obfuscated calls to other classes. I rewrote the class in Java to help me understand what was going on. For this post, I'll just write some (oversimplified) pseudo-code:
Even though this example is oversimplified, it still demonstrates what the method does. This method iterates over your text messages, first checking the date (to make sure it's within a threshold), then checks the address to make sure it's from a Facebook sender, and finally it performs a regex on the body. It adds all potential confirmation codes entries to a list and returns them (note that "m.group(1)" will only return the confirmation code, not the message body). I did not analyze what the Facebook application does with the values more than two method calls up.
Now the question is: when does this get called? To help me figure this out, I added a quick Log statement to the method that queries the SMS database. For the entirety of my testing, I ran the command "adb logcat | grep "TESTING". If the Facebook app tried to read your SMS, I should see it in the logs.
In addition, I ran my tests from a dev version of CobraDroidJB, which reported all ContentProvider queries to a designated log buffer (commit). I then tried just about ever combination of logging in, logging in with two factor, logging in from an unrecognized device, etc. No evidence that our watched method above was called, or that the SMS/MMS databases were accessed. I tried it from two devices, and a test version of CobraDroidJB; the results were the same in all cases. I even installed and logged into the Facebook Messaging application just in case that mattered.
This was me adding my phone number and confirming it.
Even though I used the "Login Approvals" setting which required a confirmation code, I would have to manually enter it. You can even see in the second picture that I have the unread text message (with my code!).
In my testing, I was unable to make Facebook access my SMS or MMS. I'm not saying its not possible, but I tried several different devices and settings and was unable to make the app call this particular method.
Not satisfied, I dug around the code surrounding the SMS query method and found mentions of a property called "read_sms_bg_account_confirmation" which seemed interesting. This parameter is stored in the Sqlite3 database file "/data/data/com.facebook.katana/databases/prefs_db," and is not populated until a user authenticated to Facebook. No matter what my account settings were, this value was always set to 0 (the "2" is the type).
Even when I manually set this value to 1, the application still did not read my SMS. At this point I decided to call it a day. If reading my SMS was a "feature" of the new app, it was pretty difficult to use this "feature."
Before going any further I'd like to make a few things clear. First, I don't work for Facebook, and I'm not trying to bash them in any way. I also have no malicious intent of exposing the company; I'm simply trying to demonstrate on a technical level what this change means. If you're someone who doesn't care for all the technical fun stuff, skip right to the conclusions section.
So with that, let's get started!
I first downloaded the most recent version of the Facebook mobile application (version 5.0.0.26.31) to a test device, and then pulled the application to my testing machine. A quick check confirmed that this application does in fact ask to read SMS messages:
What does this mean? This means that the application is permitted to access the Android ContentProvider URLs "content://sms", "content://mms," and "content://mms-sms," the databases containing your SMS and MMS data. It does not allow the application to intercept incoming text messages or send them (regardless of what Kaspersky claims). These actions require the permissions "android.permission.RECEIVE_SMS" and "android.permission.SEND_SMS," respectively. This application did not ask for permission to do these actions.
I unpacked the APK with apktool and was surprised to find that the application did not obfuscate the class names. It did obfuscate class fields and methods (standard issue ProGuard), probably to save space. It also did not appear to use any string encryption or obfuscation, which made my job a lot easier.
As a first test, I wanted to install a modified version of the APK with debugging, so I changed the "android:debuggable" attribute to "true" in the "AndroidManifest.xml" file.
When I attempted to rebuild the APK, I was greeted by a bunch of resource repackaging errors, due to missing dependencies in the "framework-res.apk" I was using to repack the application. This is pretty common in large applications that are developed with proprietary resources. I won't go into the details (you can learn more here), but with a few changes to the "/res/values/styles.xml" file I was able to rebuild it successfully.
I then installed the new APK and attempted to launch the application, and was presented with a message indicating that the application has crashed. I checked the Android logs and saw this error:
The message was strange, since I don't recall that being a feature of Android. I did a quick string search for the error and found that in the method "c()V" of the class "smali/com/facebook/katana/app/FacebookApplicationImpl.smali" there was a quick check to make sure the "android:debuggable" flag was not set to true, which is actually a good security feature! If the flag was set to true, the application threw a RuntimeException and the application crashed.
To get by this, I simply removed the check, and repackaged the application.
The application could now be installed without errors.
I switched gears and started running some searches on the application's disassembled class files. Since I knew the application was not encrypting strings, searches for "mms","sms," "sms/inbox", "content://sms" should have returned something interesting. Instead, I found absolutely nothing interesting. Weird. I considered maybe the application asked for the permission, but didn't use it (yet), but that seemed unlikely.
I decided to rerun the app on my device and check the logs. As the app loaded, I noticed something very interesting - the application was copying additional DEX files, and loading them!
I've personally never seen this, but it appears that the application copied three additional XZ compressed assets, uncompressed them, and then used a DexClassLoader to load them. These 3 DEX files also contained "canary" checks, but it was still possible to modify the classes and rebuild. Here are the files:
I decompressed and disassembled all three, and realized there were now over 10,000 new classes to search through. I repeated my search above, and this time found something a bit more interesting.
We now have a few leads to look at. In particular, we have the class "/secondary-2/com/facebook/confirmation/task/ReadSmsConfirmAccountBackgroundTask.smali" and the classes "/secondary-1/android_src/provider/Telephony*.smali." I'll start with the class "ReadSmsConfirmAccountBackgroundTask.smali."
The file "/secondary-2/com/facebook/confirmation/task/ReadSmsConfirmAccountBackgroundTask.smali" contained the string "content://sms/inbox," which proceeded a query to the SMS content provider. This was a good starting point.
This class is actually quite large, and makes several obfuscated calls to other classes. I rewrote the class in Java to help me understand what was going on. For this post, I'll just write some (oversimplified) pseudo-code:
Even though this example is oversimplified, it still demonstrates what the method does. This method iterates over your text messages, first checking the date (to make sure it's within a threshold), then checks the address to make sure it's from a Facebook sender, and finally it performs a regex on the body. It adds all potential confirmation codes entries to a list and returns them (note that "m.group(1)" will only return the confirmation code, not the message body). I did not analyze what the Facebook application does with the values more than two method calls up.
Now the question is: when does this get called? To help me figure this out, I added a quick Log statement to the method that queries the SMS database. For the entirety of my testing, I ran the command "adb logcat | grep "TESTING". If the Facebook app tried to read your SMS, I should see it in the logs.
In addition, I ran my tests from a dev version of CobraDroidJB, which reported all ContentProvider queries to a designated log buffer (commit). I then tried just about ever combination of logging in, logging in with two factor, logging in from an unrecognized device, etc. No evidence that our watched method above was called, or that the SMS/MMS databases were accessed. I tried it from two devices, and a test version of CobraDroidJB; the results were the same in all cases. I even installed and logged into the Facebook Messaging application just in case that mattered.
This was me adding my phone number and confirming it.
Even though I used the "Login Approvals" setting which required a confirmation code, I would have to manually enter it. You can even see in the second picture that I have the unread text message (with my code!).
In my testing, I was unable to make Facebook access my SMS or MMS. I'm not saying its not possible, but I tried several different devices and settings and was unable to make the app call this particular method.
Not satisfied, I dug around the code surrounding the SMS query method and found mentions of a property called "read_sms_bg_account_confirmation" which seemed interesting. This parameter is stored in the Sqlite3 database file "/data/data/com.facebook.katana/databases/prefs_db," and is not populated until a user authenticated to Facebook. No matter what my account settings were, this value was always set to 0 (the "2" is the type).
Even when I manually set this value to 1, the application still did not read my SMS. At this point I decided to call it a day. If reading my SMS was a "feature" of the new app, it was pretty difficult to use this "feature."
The second instance of the strings "sms" found in the secondary DEX files was the file "/secondary-1/android_src/provider/Telephony$MmsSms.smali". This class contained static SMS and MMS URI strings for other classes to use. Here is what the class "Telephony" might look like in Java:
Did any other class actually use this class? It depends. I looked at two iterations of the Facebook Mobile application version 5.0.0.26.31 - One from a GingerBread device which was version code 1056406, and one from a JellyBean device which was version code 1056414. If you installed 5.0.0.26.31/1056406, the application contained the class "/secondary-2/com/facebook/contacts/upload/events/ContactInteractionEventsFetcher.smali," which accessed the "Telephony" class above; however, if you used the more recent 5.0.0.26.31/1056414, no classes used this class. In my testing of version code 1056406, I was not able to get this class to be called, so I believe this was testing, deprecated, or experimental code.
Conclusions
Overall, the media coverage on this topic is quite misleading and borderline fear mongering. Here are some facts:
- The Facebook application did contain code to access your SMS and MMS messages.
- The application contained code to search the SMS database between select time-frames, for select senders, and 4-10 length numbers in the message body. Only the matched number was returned (not the whole text message).
- Some versions of the application did contain additional code to read your SMS/MMS database for a specified phone number.
- The Facebook application does not routinely read your SMS.
I'm not entirely convinced this feature is completely implemented. If it is implemented, it would seem difficult to cause the application to actually use it. If anyone has experience with this feature and can say "yes, it definitely is enabled," I would love to hear it!
I also want to point out this analysis only pertains to a single version of the application. Facebook may very well decide to use this permission in the future, so remember that! They could also add class name and string obfuscation, which would make reverse engineering the application significantly more difficult.
I also want to point out this analysis only pertains to a single version of the application. Facebook may very well decide to use this permission in the future, so remember that! They could also add class name and string obfuscation, which would make reverse engineering the application significantly more difficult.