Chapters

Hide chapters

Real-World Android by Tutorials

Second Edition · Android 12 · Kotlin 1.6+ · Android Studio Chipmunk

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

22. App Analysis
Written by Antonio Roa-Valverde

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapters, you looked at analytic reporting and advanced debugging techniques. Now, you’ll learn to analyze your app to investigate issues where you know there’s a problem post-release, but you don’t know which part of the code is the culprit. For example, finding a corrupt file or a conflict with a statically compiled third-party library requires deep investigation.

In this chapter, you’ll learn how to:

  • Look at data artifacts that aren’t obvious from your code.
  • Analyze databases.
  • Reverse-engineer code you didn’t write.

For this chapter, you’ll use the Pixel XL API 30 (R) Emulator.

Debugging Versus Investigating

When you debug your app, you apply tools to fix not just the symptoms, but the underlying problem. You look for specific regions of code, perhaps a section that has changed recently or that is prone to errors.

There are two types of tests you can run to find problems:

  • Dynamic testing: Testing while executing the code.
  • Static testing: Auditing the source code for issues.

In either case, the goal is to understand the problem before attempting to fix it. App analysis helps you acquire all available data to aid your problem-solving.

Before you even get to that point, you can perform tests to avoid mysterious bug reports. By covering all your code with tests, going through each flow-control case and testing each line of code at least once, you’ll minimize the chance that unknown cases will pop up later. Then, it’s important to test each code change thoroughly, to make sure you didn’t break code that was working before. This is called regression testing.

Because you wrote the code, you know how to use your app. It’s important to step away from that mindset and think about what a real-world user will do — and that’s not always what you expect. There are ways of covering more of that behavior: One is to input random data, called fuzz testing. Another is to choose extreme values in hopes of finding an edge case. These tests help find bugs that aren’t obvious from looking at the code or using the app in a normal way.

Even with all this testing, you’ll find unexpected bugs. One example is memory corruption due to race conditions. It’s difficult to find race conditions during testing because you have to corrupt memory in the “right way” to see the problem. Sometimes the problems appear a long time later in the app’s lifecycle. This is why it’s crucial to run Lint — Android Studio’s static code analysis tool.

Despite all these precautions, sometimes there’s just no way to step back through the events to find out what caused a problem.

To see this in action, you’ll work through a real-world example that walks you through the process of analyzing a specific device that you’re allowed to inspect. This will give you a sense of the process and the complications you’ll encounter along the way.

You won’t be able to follow along with everything in the next section, as the process changes widely per device, so read through the example without trying it on your own device.

Extracting Data

Your CEO comes to you with a device that crashes when they launch PetSave. You plug the device into your debugger, build and debug, and the problem goes away.

adb shell  # 1
pm list packages -f | grep petsave  # 2
exit
package:/data/app/com.realworld.android.petsave-ei0L3AJk3xo5M3Gs9SVuTQ==/base.apk=com.realworld.android.petsave

Extracting Data From a Package

Once you’ve found the PetSave package, try to run the app over ADB to extract data with the correct permissions. It’s easy to retrieve data from apps that allow external install locations or that save data to public areas. In most cases, however, you’ll need to access data that’s in the private storage area.

adb exec-out run-as com.realworld.android.petsave cat databases/petsave.db > petsave.db
adb shell
run-as com.realworld.android.petsave  #1
chmod 666 databases/petsave.db  #2
exit
cp /data/data/com.realworld.android.petsave/databases/petsave.db /sdcard/  #3
run-as com.realworld.android.petsave
chmod 600 databases/petsave.db  #4
adb pull /sdcard/petsave.db .  #5
adb backup -apk -shared com.realworld.android.petsave

Extracting Data From The Emulator

Now that you have access to the file system of the CEO’s device, it’s time to extract the data. Build and run in the emulator, then make a report.

Figure 22.1 — File Explorer
Tavije 35.2 — Dozo Ulmxoxuv

Figure 22.2 — Locate the Data Folders
Nuriwi 24.8 — Jihiwu sba Raqu Sajkubz

Examining SharedPreferences

Open MyPrefs.xml inside shared_prefs. You’ll notice at least one entry with a timestamp.

Examining Other Files

Now, select users.dat in the files directory.

"::basic_string(void*,void(*),void(*)_char_\0\0cd.Nico Sell - CEO"
_passwordChar = "";

Analyzing Databases

Often, user records are stored in a database instead of a serialized object. Because of that, it’s a good idea to cross-check the data to see if the bug exists in more than one place.

Figure 22.3 — App Inspection
Jigaco 33.4 — Axq Ebcpikkuas

Figure 22.4 — Browser the Data of Your DB
Dakajo 98.6 — Rpindub pri Gazi uj Feit ZK

Recovering Deleted Data

The data you’ve analyzed so far exists inside a saved SQLite block. SQLite has unallocated blocks and free blocks. When you delete something from the database, SQLite doesn’t overwrite the block immediately. Instead, it simply marks the block as free — which means that you might still be able to access that information. To read that data block, you’d use a hex viewer that also displays ASCII to search for keywords that might still be present.

Black Box Testing And Reverse-engineering

At this point, you’ve analyzed and fixed code that you own, but bugs happen in third-party frameworks, too. It’s helpful to know how to analyze them so you can properly communicate the issue to the third party. If you have a statically compiled library, for example, you’re on the outside — it works like a black box to you.

Understanding Bytecode

You now have a new issue to deal with: The team updated an expired API key but the app still isn’t working.

Figure 22.5 — Access SECRET in ApiConstants
Gofuso 67.7 — Ejvopl DUQMOS uj AroZetlfopyf

Using APK Analyzer

APK Analyzer is a tool for inspecting your finalized app. It presents a view with a breakdown of your app’s file size, letting you see what’s taking up the most space along with the total method and reference counts.

Figure 22.6 — Using APK Analyzer
Juhaki 94.6 — Avepv OJM Ekemmgal

Figure 22.7 — Analyze the Classes in the APK for Your App
Latela 23.6 — Abegcli qto Vpowneh ek mvo UFS bux Seij Icn

Figure 22.8 — Access the SECRET Constants in the Classes in the APK for Your App
Zeduso 85.5 — Ipgikr whu KODYEN Nurxwugzg uc kku Rjobqiv eb lga ATW nob Neon Etk

Introspection and Reflection

When you’re away at work, your pets hang out for hours, not seeming to do very much. That’s probably because they’re busy introspecting and reflecting on life. In Kotlin, introspection and reflection are features of the language that inspect objects and call methods dynamically at runtime.

Figure 22.9 — Obfuscation in Practice
Lulomo 28.5 — Amyujbilaut ud Txesvide

val kClass = Class.forName(ownerClassName).kotlin // 1
val instance = kClass.objectInstance ?: kClass.java.newInstance() // 2
val member = kClass.memberProperties.filterIsInstance<KMutableProperty<*>>()
    .firstOrNull { it.name == fieldName } // 3
member?.setter?.call(instance, value) // 4

Using Reverse-engineering Tools

You’ve just reverse-engineered code, and because you have the original project open in Android Studio, it was easy to do. But this is not the only way to view the bytecode. Many other tools let you analyze the production version of apps, especially for black-box testing or checking how your finalized app looks.

Debugging With ProGuard Output Files

In the app build.gradle, replace buildTypes’s code with the following:

buildTypes {
  release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  }
  debug {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  }
}
Figure 22.10 — The Code After Proguard Obfuscation
Ratoxo 43.19 — Zra Copu Ukjis Kgubuebj Aphalrakouy

Figure 22.11 — Loading the Proguard Mapping File
Raxixu 46.69 — Zeexily ype Kpisoucv Haqpujm Sipa

Some Final Notes

Finding a software defect is like holding a mirror up to yourself — a great learning opportunity. It provides valuable insight into which common mistakes you make as a developer and how you can improve. App analysis is self-analysis. And, like every other phase of the lifecycle, it’s iterative.

Key Points

  • There are two types of tests you can run to help you find problems: dynamic and static.
  • Dynamic testing is testing while executing the code.
  • Static testing is auditing the source code for issues.
  • Android Debug Bridge (ADB) is a very important tool that helps you access your device data.
  • Understanding Java bytecode is a vital skill when testing the security of your app.
  • Several tools allow you to reverse-engineer your app. APK Analyzer is one of those.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now