1

I have an android app written in kotlin with a C++ native library (.so). With crashlytics we are able to obfuscate kotlin methods and yet obtain symbolicated crash reports, is it possible to have the same for the native library? Currently I have to choose either to build the C++ library with -fvisibility=hidden and have my function names obfuscated (not visible if I run nm -gDC <.so library packaged with the apk>), or to have my function names visible in the .so file and obtain symbolicated crash reports. Can I have an obfuscated .so file packaged with my app and yet get a symbolicated crash report for crashes within the native library?

This answer seems to imply that I cannot have it all.

Antonio
  • 19,451
  • 13
  • 99
  • 197

1 Answers1

0

You can't have it all in one file, so use two: Build your APK, but maintain two separate versions of the .so: One fully symbolicated, which you keep, and another, which you pass through the strip(1) command, with all symbols removed, to package with the APK. This removes the symbols, but does not change the addresses.

When you get a tombstone/crash report, it will initially contain only addresses in your library - since when running in your APK, it will have no symbols. The addresses, however, will be easily resolvable by you, and you can build a small script to pass the tombstone through addr2line and get more meaningful insights.

Technologeeks
  • 7,674
  • 25
  • 36
  • Do you know this to work in practice? Are you sure the addresses are the same in the stripped and unstripped library? – Antonio Jul 27 '21 at 19:09
  • Positively yes, as we've used the same trick a few times; wouldn't have suggested it otherwise. It's also common in macOS and Windows wherein the symbols are in a separate file (.dSym and .pdb, respectively).The file sizes will be different, but the symbol table is located in a different section than the executable (r-x) section so that section will be identical. Try it out and see :-) – Technologeeks Jul 27 '21 at 20:01
  • I think here the problem is different (notice that I didn't mention strip in my question), as it is about `-fvisibility=hidden`: that's a necessary flag to obfuscate functions that are not part of the interface (basically, everything but the jni interface), which are not hidden by the strip step. Now it remains to be tried if one builds two version of the library, one to distribute built `-fvisibility=hidden` and stripped, and one symbolicated built without `-fvisibility=hidden` and not stripped: will addr2line work on the symbolicated library with a tombstone from the distribution library? – Antonio Jul 28 '21 at 08:22
  • The strip removes all internal symbols, period. Anything not explicitly exported gets removed. The addresses, however, don't change in any way. That's why it can work irrespective of the visibility flag. addr2line will work by getting an address in the binary (offset, really, since binary is relocatable due to ASLR), and resolve the nearest symbol to it + some offset. Since the two libraries have the same text segment, it'll work. This is very common practice everywhere. Do note, however, that merely removing symbols isn't much of a hurdle in the face of a skilled reverse engineer. – Technologeeks Jul 29 '21 at 13:30
  • I don't think you are correct. Looking at the final stripped library, there is a significant impact on the amount of function names visible in the .so file if you first compile `-fvisibility=hidden`. So it is not true that all internal symbols are removed. – Antonio Jul 30 '21 at 08:30
  • I don't believe you understood what I was saying: strip can be made to remove *all* the symbol names outside those which are declared as specifically exported (using command line switches, -s , -x , etc, *not* as the default application). I wasn't debating -fvisibility=hidden - I'm simply saying using strip after doing so further improves it. – Technologeeks Jul 30 '21 at 13:29
  • If I look at https://stackoverflow.com/a/50637280/2436175, it seems that the only reliable method to also strip/hide/obfuscate internal symbols (all symbols that are not explicitly exported) while packaging an apk is to use `-fvisibility=hidden` (confirmed also in https://stackoverflow.com/a/21452277/2436175). If you know a way to do to so e.g. in Android Studio using strip with enhanced options and not `-fvisibility=hidden`, please be welcome to share – Antonio Jul 30 '21 at 14:30
  • Strip is perfectly reliable (both in Android development and outside it) and independent of -fvisibility: The latter acts when binary is created, the former after, by removing the ELF symtab entries. Studio can be probably be made to run scripts as part of the APK build, or you can use CLI all the way. Maybe you should try -d -s -x first, and then get back to me? – Technologeeks Jul 30 '21 at 16:27
  • **1** Compile without `-fvisibility=hidden` **2** Take the fully symbolicated library in `build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/` **3** Run `~/Android/Sdk/ndk/21.2.6472646/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/strip -d -s -x` on that libray **4** I obtained the same library as the stripped `build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/`. (Size went from ~90Mb to ~9Mb) A good deal of the internal symbols are still there (visible with `strings `) and searching for our namespace leads to 2175 matching lines. – Antonio Aug 02 '21 at 09:40
  • I see. The compiler considers by default your namespace to be potentially exported (it doesn't know if you're using them elsewhere, since it's a shared library) - which is why the symbols are perceived as global, and strip won't strip them. what you need is one of (a) _ _attribute_ _ ((visibility ("hidden"))) on the namespace symbols (same as -fVisibility=hiddden, but without the -f) and/or (b) defining them explicitly as static, if possible. Then strip will strip them out completely, and you can use the method I described of keeping the non stripped copy. – Technologeeks Aug 02 '21 at 11:38
  • Solution `a` wouldn't really change much: apart that I would have to add it in 2000+ places :), what should be known is that `-fvisibility=hidden` simply changes *the default* for all functions (and in fact JNIEXPORT sets explicitly the attribute to `default`, kind of confusing name but which means visible) https://stackoverflow.com/a/3570430/2436175. What I noticed now and I could try is what would happen compiling with `visibility("internal")`, maybe the symbols will stay but will become strippable! – Antonio Aug 02 '21 at 13:40
  • No, `internal` in practice behave as `hidden`. In fact from the [documentation](https://gcc.gnu.org/onlinedocs/gcc-10.3.0/gcc/Code-Gen-Options.html#index-fvisibility) `protected and internal are pretty useless in real-world usage so the only other commonly used option is hidden`. More on the subject: https://stackoverflow.com/questions/21377630/gcc-in-what-way-is-visibility-internal-pretty-useless-in-real-world-usage – Antonio Aug 02 '21 at 16:40