Patch encrypted libraries in Meta’s apps for Android

Example of SSL unpinning in Workplace app by Meta on non-rooted phones

Michele Renzullo
Published in
7 min readMay 10, 2022

--

Introduction

I attended a cyber security course BountyConEDU organized by Meta in Madrid, which ran over the period April 29th to May 2nd. In the first two days we received an intensive series of seminars concerned with native, mobile and web security from which we gained useful insights on successful bug hunting. The last day was a live-hacking session where we were asked to find vulnerabilities and bugs in Meta’s products. For this exercise, I worked in a team known as Just 1 bit. We were able to submit three reports. In this article I will talk about mine.

Proposal

To patch a library on Android you typically have to find the target library, patch it and resign the app. In Meta’s app almost all the libraries are compressed and now encrypted in a “SUPERPACK_OB” archive named “libs.spo”, inside assets/libs folder. If you want to patch one library inside it, you have first to extract the “libs.xz” archive, then revert the process.

This process is contained in the function “readNative” of libsuperpack-jni.so, but since updates can frequently occur it could be useful to find a more general way to bypass this long process and directly patch the target library. This would also be useful for many other android apps.

How?

I created a test app in Android Studio with a Java function that gives us R+W access to our target library, therefore patching the bytes at our offset with RandomAccessFile. Then we decompile it and we inject our function in an activity, for example the login activity.

The Technical Part

The target device for the output apk is a non-rooted phone, and during this process we assume that we don’t have a rooted device (or a rooted emulator). The app used is Workplace 365.0.0.30.112 arm64, the tools are apktool, Ghidra, AndroidStudio, android backup extractor, MT Manager 2.10.4 ( or NP Manager as alternative) adb, zipalign, apksigner and smali-2.5.2. On this repository there are some files and tools that are used in the article.

Let’s get our hands dirty!

To see all the decompressed libraries we have to install the app and read the data folder in the internal memory. On a rooted phone we can access the path /data/data/com.facebook.work/ but in a non-rooted device we will backup the data by editing the AndroidManifest.xml and executing adb backup:

  • apktool will fail to decompile the AndroidManifest.xml of Meta’s app (see the issue recorded here) we can still edit the AndroidManifest with MT Manager or NP Manager. They are powerful apps which allow you to fully reverse engineer any android app. I’m using MT Manager to edit the AndroidManifest setting allowBackup=”true” and debuggable=”true”(also required since Android 12+), then sign it and install it. This has already been done here.
  • From adb run
adb backup com.facebook.work -f backup.ab
  • Now we decrypt the backup with android backup extractor and extract the tar archive. Inside apps/com.facebook.work/r there are all the files in the internal memory of the device /data/data/com.facebook.work/
java -jar abe.jar unpack backup.ab backup.tar 

Reverse engineer and patching a native library

Meta has compiled Fizz library in Workplace using Android NDK, the certificate verification is implemented on this line. It is possible to verify any certificate bypassing the check if (state.verifier())

To find the bytes to patch in a new version, it’s necessary to decompile the library and find the new offset.

  • Using Ghidra or IDA Pro search for string “server choose unsupported sig scheme:” in libxplat_fizz_client_protocolAndroid.so and a few lines down there is the check to patch, in this version at offset 0x1d9f0
offset 0x1D9F0 bytes F7 00 00 B4
  • Changing the instruction cbz into an unconditional b jump, note the bytes turned from F7 00 00 B4 into 07 00 00 14

If you have a rooted device this article will end here, you just need to push the patched library into your device and replace the original one.

Produce a patched apk for non-rooted devices

To patch the app on a non-rooted phone is necessary to gain access to libxplat_fizz_client_protocolAndroid.so that is stored inside a SUPERPACK_OB archive named “libs.spo”.

The “libs.spo” file is just a container of many xz archives. The first one is the file “libs.xz”, you can see the magic bytes “FD 37 7A 58 5A 00” and before the magic bytes of each XZ archives, there are 22 bytes like this:

?? ?? 00 00 00 00 00 00 00 00 00 00 4bytes size 4bytes compressed_size ?? ??

Therefore you can calculate the end of the archive copying the 4bytes of compressed_size and adding to the offset of “FD 37…”, or you can search for the footer “59 5A” before a new “FD 37 7A 58…” You will obtain the “libs.xz” archive as first entry, and you can extract the file “libs” that contains all the compressed libs, but they are obfuscated, in some case like arm-v7a you can be lucky finding the bytes that you want to patch (like @itsMoji did in Instagram armv7a version SSL unpinning, patching libliger.so), therefore you can recompress it, update the new compressed size, and replacing inside libs.spo.

But the obfuscation for armv64 looks heavier or different and you won’t be able to find the bytes to patch.

In the past this was just a brotli archive but last year it was modified to a new encrypted format. If you want help to create an extractor/builder, you can take a look at this repository.

It is better to find a universal way to bypass that and access the target library directly.

  • Using AndroidStudio I created a simple app with a function patcher() to set R+W access on the target library, seek the offset found in Ghidra before (0x1d9f0) with RandomAccessFile and write over the new bytes 07 00 00 14. This app is in the repository created for the article.
  • Build the app and decompile with apktool to see the java function in dalvik-bytecode
apktool d -r -o app-release app-release.apk

In random.smali this is the function patcher() just compiled.

And in the MainActivity.smali, you can see the call to the function

invoke-static {}, Lcom/example/myapplication/random;->patcher()V

Implementing our function in Workplace

  • Decompile Workplace with apktool (or MT Manager)
apktool d -r -o Workplace_365.0.0.30.112–367653625 Workplace_365.0.0.30.112–367653625.apk

The login activity could be a good target to implement the function to patch the library just when we login/logout and not every time the user opens the app.

In the AndroidManifest search for “loginactivity” and one result is com.facebook.workshared.auth.core.WorkLoginActivity

  • Open the file WorkLoginActivity.smali in the folder smali_classes8\com\facebook\workshared\auth\core and at the end paste the function patcher() from random.smali
  • Search for onStart(), and after “.locals 3" paste the call to our function editing the path com/example/myapplication/random with com/facebook/workshared/auth/core/WorkLoginActivity
.method public final onStart()V
.locals 3
invoke-static {}, Lcom/facebook/workshared/auth/core/WorkLoginActivity;->patcher()V

END OF EDITS

  • Compile back source code with smali-2.5.2.jar
java -Xmx3000m -jar smali-2.5.2.jar ass Workplace_365.0.0.30.112–367653625\”smali folder where are located the files edited” -o Workplace_365.0.0.30.112–367653625\”classes number”.dexin this case it is classes8:java -Xmx3000m -jar smali-2.5.2.jar ass Workplace_365.0.0.30.112–367653625\smali_classes8 -o Workplace_365.0.0.30.112–367653625\classes8.dex
  • In order to sign our apk, if you haven’t generate a keystore jks with Java keytool:
keytool -genkeypair -dname “CN=test, OU=Android, O=Google Inc.,L=Mountain View, ST=California, C=US” -keystore "your testkey".jks -validity 9125 -keyalg RSA -keysize 2048 -storepass "your keystore pass" -alias "your alias"
  • Update Workplace apk zip archive replacing with the new classes dex file (on Linux terminal with cp and zip commands, on Windows do it manually), zipalign and sign it with apksigner.jar :
cp Workplace_365.0.0.30.112–367653625.apk toalign_Workplace_365.0.0.30.112–367653625.apk && zip toalign_Workplace_365.0.0.30.112–367653625.apk classes8.dexzipalign -p -f 4 toalign_Workplace_365.0.0.30.112–367653625.apk zipaligned_Workplace_365.0.0.30.112–367653625.apk && rm toalign_Workplace_365.0.0.30.112–367653625.apkjava -jar apksigner.jar sign --ks “your testkey”.jks --ks-pass pass:”your keystore pass” --in zipaligned_Workplace_365.0.0.30.112–367653625.apk --out signed_Workplace_365.0.0.30.112–367653625.apk

I’m using a script to do the previous steps quickly, which you can find in my repository “aut.bat”, you just need to replace the name of the app, your keystore and the paths. It is better than apktool when you just edit a few classes. You can run my script and pass the classes to compile as arguments “aut.bat classes2 classes3…”

  • Uninstall Workplace app and install the apk manually or through
adb install -r signed_Workplace_365.0.0.30.112–367653625.apk
  • Login or logout and after configuring your proxy you should now see the /graphql calls in Burp

If you repeat the steps for backup you will see that libxplat_fizz_client_protocolAndroid.so has been correctly patched!

The requests and the responses made from the app are different from the ones made on the website, it’s useful to compare them and find interesting bugs. Happy Hunting!

--

--