STACK the Flags 2020: Mobile Challenges Write Up

Ng KY
8 min readDec 11, 2020

Table of Contents

  • Tools we used
  • How we patched the app to capture screenshots for this write up
  • (Challenge) Welcome to Korovax Mobile!
  • (Challenge) All about Korovax!
  • (Challenge) True or false?
  • (Challenge) What’s with the Search!

Tools we used

  • adb for examing logs, installing and uninstalling apk
  • JADX decompiles DEX/Smali code into Java source code for analysis
  • Ghidra disassembles and decompiles Android native libraries (.so ELF files) for analysis
  • apktool for decoding, patching and rebuilding the apk
  • Frida for dynamic instrumentation of the apk

How we patched the app to capture screenshots for this write up

In Android, setting the FLAG_SECURE flag on a window prevents it from appearing in screenshots.

The AuthenticationActivity under the sg.gov.tech.ctf.mobile.User namespace was set with FLAG_SECURE so we couldn’t take a screenshot of it. The constant value 8192 (or 0x2000 in hex) corresponded to FLAG_SECURE:

sg.gov.tech.ctf.mobile.User.AuthenticationActivity from JADX

As we ran this app on a non-rooted physical device, rather than a rooted one or an emulator, we needed to patch the app in order to capture screenshots.

To patch the apk, firstly we used apktool to unpack and decode it into extractedFolder:

apktool d mobile-challenge.apk -o extractedFolder

The Smali file (i.e. disassembled Dalvik bytecode) for AuthenticationActivity could be found under extractedFolder/smali/sg/gov/tech/ctf/mobile/User. We disabled the code that set FLAG_SECURE by commenting it:

To recompile the apk, we generated a keystore, used apktool to repackage extractedFolder and re-signed it:

keytool -genkey -v -keystore custom.keystore -alias mykeyaliasname -keyalg RSA -keysize 2048 -validity 10000apktool b -o repacked.apk extractedFolder/ && \
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore custom.keystore -storepass pass repacked.apk mykeyaliasname && \
jarsigner -verify repacked.apk && \
~/android/build-tools/29.0.3/zipalign 4 repacked.apk repacked-final.apk

Due to certificate mismatch, we uninstalled the original app from our mobile device before reinstalling it. Now we could take screenshots using the screencap command:

adb exec-out screencap -p > capture.png

(Challenge) Welcome to Korovax Mobile!

Description: To be part of the Korovax team, do you really need to sign up to be a member?

All the mobile challenges were based on the “Korovax Mobile” Android app, whose main Activity looked like this:

Main Activity
Main Activity

Under the “User Login” option, there are 2 tabs: Login and Sign Up.

AuthenticationActivity Login and Sign Up tabs

We didn’t have valid accounts to login, and the “sign up” button didn’t respond when tapped:

Using an invalid account to login

Perhaps we could login without signing up? We decompiled the apk using JADX to find out.

From AndroidManifest.xml, we obtained the package name:

AndroidManifest.xml from JADX
package name

There are a number of Activities under the “sg.gov.tech.ctf.mobile.User” namespace. We used adb logcat to trace the names of Activities being launched:

adb logcat ActivityManager:I sg.gov.tech.ctf.mobile *:S

It was the AuthenticationActivity that launched when we tapped the “User Login” option:

adb logcat tracing

AuthenticationActivity set up the tabs and returns a Fragment called f.a.a.a.a.d.a() for the Login tab:

AuthenticationActivity sets up the tabs
f.a.a.a.a.d.a() Fragment corresponds to Login

Said Fragment sets up an OnClickListener for the login_button:

f.a.a.a.a.d.a() Fragment sets up the login_button’s OnClickListener

The login_button’s OnClickListener does 2 checks:

  1. If the password contains some string, display a message
  2. If the username and password input match those in the database, display a custom_alert
Checks taken by the login_button’s OnClickListener
Method e in class f.a.a.a.a.b.a checks username and password against the database

The login_button’s OnClickListener is wrapped in class b, so the Smali code that corresponded to it could be found in smali/f/a/a/a/a/d/a$b.smali:

To bypass those 2 checks, we applied 2 patches to a$b.smali to reverse the check conditions:

Patch #1
Patch #2

After rebuilding the apk, we logged in without a valid account and got the flag:

(Challenge) All about Korovax!

Description: As a user and member of Korovax mobile, you will be treated with a lot of information about COViD and a few in-app functions that should help you understand more about COViD and Korovax! Members should be glad that they even have a notepad in there, to create notes as they learn more about Korovax’s mission!

Continuing with the analysis of AndroidManifest.xml, there are a number of Activities under the “sg.gov.tech.ctf.mobile.User” namespace. One of them is associated with an Intent that states “howtogetthisflag”:

howtogetthisflag Intent

We launched this Activity using adb and was presented with a Secret Vault:

adb shell am start -W -a android.intent.action.VIEW -d gov.tech.sg://ctf/howtogetthisflag
ViewActivity

The Click Me button did not display the flag so more work was needed:

Maybe not Toast

From the decompiled ViewActivity, the flag will only be shown if the random number generator (RNG) returns a value of 1720543:

Decompiled ViewActivity

Chances of getting exactly 1720543 from an RNG is low!

The Smali code that corresponds to the onClick function in ViewActivity is smali/sg/gov/tech/ctf/mobile/User/ViewActivity$a.smali. We patched the code to display the flag whenever we couldn’t get 1720543:

After rebuilding the apk, we got into the Secret Vault again and got the base64 encoded flag this time:

base64 encoded flag

Base64 decoded it and we got the flag:

govtech-csg{d33P_linK1Ng_3xi3st5_1n_An4r014}

(Challenge) True or false?

Description: True or false, we can log in as admin easily.

The Admin Login page whose FLAG_SECURE we had patched challenged us to find the password:

Admin Login dashboard

From adb logcat output, the above page is displayed by the AdminAuthenticationActivity:

AdminAuthenticationActivity

We examined the AdminAuthenticationActivity in JADX. The f.a.a.a.a.a.a() Fragment sets up the Login button’s listener:

Fragment f.a.a.a.a.a.a()
Fragment f.a.a.a.a.a.a() sets up the login_button

The login_button’s OnClickListener is implemented in class b. It checks whether the password input contains some string before displaying a custom_alert:

Checks taken by the login_button’s OnClickListener

The Smali code that corresponds to this OnClickListener is smali/f/a/a/a/a/a/a$b.smali. We patched the code to display the custom_alert if the password input doesn’t contain that string:

Patch

After rebuilding the apk, we could log in as admin without knowing the string and get the flag:

We clicked Proceed and reached the admin dashboard:

Admin dashboard

(Challenge) What’s with the Search!

Description: There is an admin dashboard in the Korovax mobile. There aren’t many functions, but we definitely can search for something!

The Admin dashboard that we obtained from the previous challenge was handled by the AdminHome activity:

AdminHome

If the correct input is submitted through the search button, the flag will be displayed. Otherwise, a toast displays the message “Flag is wrong!”:

The search button is implemented as button_submit, whose OnClickListener c() does some checking before displaying the Congrats message:

In the check, the user input that’s stored in the f2932e variable is passed to function c(), and the output from c() is passed into b(). Subsequently the output from b() is matched, case-insensitive, against the f2929b variable:

A password hash is stored in the f2929b variable. This hash is obtained from Java Native Interface (JNI), from the native-lib library:

We obtained libnative-lib.so from apktool’s unpacked output, under the lib/architecture folder:

different architectures of libnative-lib.so, unpacked from apktool

To examine the getPasswordHash() function, we picked the x86 version for decompilation in Ghidra.

JNI function symbols are exported so we looked for the getPasswordHash function in the Symbol Tree pane, under Exports:

Ghidra’s Symbol Tree
getPasswordHash native implementation

In Ghidra’s decompile pane, the hash 0f0d959bca569bf2b0a8bff3e2f1e88920ee7c5f is returned to the caller:

Back to the checks in AdminHome, function c() makes sure that the user input isn’t wrapped inside “govtech-csg{}“:

Next, function b() calculates the SHA-1 hash of c()’s output and returns it in a hex string:

So we understood that the user input is SHA-1 hashed, and the latter must match 0f0d959bca569bf2b0a8bff3e2f1e88920ee7c5f. So we searched online for the reversal of the hash:

Finally we entered P@ssw0rd123 into the field and obtained the flag:

--

--