reversefreeeasy

SAW

hackthebox

English summary: Given an Android APK file that uses a native library to dynamically decrypt and load hidden code. The goal is to reverse engineer the native library, recover the XOR key, decrypt the hidden DEX payload, and extract the flag.

$ ls tags/ techniques/
xor_key_recoveryapk_decompilationjni_reverse_engineeringdex_payload_extractiondynamic_code_loading_analysis

SAW — HackTheBox

Description

The malware forensics lab identified a new technique for hiding and executing code dynamically. A sample that seems to use this technique has just arrived in their queue. Can you help them?

English summary: Given an Android APK file that uses a native library to dynamically decrypt and load hidden code. The goal is to reverse engineer the native library, recover the XOR key, decrypt the hidden DEX payload, and extract the flag.

Analysis

The challenge provides an APK file (SAW.apk). Initial extraction reveals:

  • classes.dex — main Dalvik bytecode
  • lib/ — native libraries for multiple architectures (arm64-v8a, armeabi-v7a, x86, x86_64)
    • libdefault.so — loaded by the app
    • libnative-lib.so — NOT loaded (decoy)
  • Standard Android resources

Java Layer Analysis (jadx):

  • Package: com.stego.saw
  • Loads libdefault.so via System.loadLibrary("default")
  • Requires intent extra open=sesame to proceed
  • Native method: public native String a(String str, String str2);
    • str = FILE_PATH_PREFIX (app's data directory)
    • str2 = user input from EditText dialog
  • Dialog titled "XOR XOR XOR" with message "XOR ME !" — hints at XOR operation

Native Library Analysis (libdefault.so):

  • JNI_OnLoad: Registers native method a for com/stego/saw/MainActivity
  • Native method validates an 8-character key using two XOR arrays in .data section:
    • Array l at 0x3de0: [10, 11, 24, 15, 94, 49, 12, 15]
    • Array m at 0x3e00: [108, 103, 40, 110, 42, 88, 98, 104]
    • Validation: input[i] XOR l[i] == m[i]
  • If key is correct, XOR-decrypts 792 bytes from jni_def symbol using mask 0x64 ('d')
  • Writes decrypted result to a file (dynamic DEX loading)

Solution

Step 1: Recover XOR Key

The key validation uses: input[i] XOR l[i] == m[i]

Therefore: key[i] = l[i] XOR m[i]

#!/usr/bin/env python3 l = [10, 11, 24, 15, 94, 49, 12, 15] m = [108, 103, 40, 110, 42, 88, 98, 104] key = ''.join(chr(l[i] ^ m[i]) for i in range(8)) print(f"XOR Key: {key}") # fl0ating

XOR Key: fl0ating

Step 2: Extract and Decrypt Hidden DEX

#!/usr/bin/env python3 # Extract encrypted data from libdefault.so and decrypt # Encrypted data location: jni_def symbol at offset 0x3180 # Size: 792 bytes (0x318) # XOR mask: 0x64 (character 'd') with open('libdefault.so', 'rb') as f: f.seek(0x3180) # jni_def offset in x86_64 version encrypted = f.read(792) decrypted = bytes([b ^ 0x64 for b in encrypted]) # Verify DEX magic print(f"Magic: {decrypted[:8]}") # dex\n035\x00 with open('decrypted.dex', 'wb') as f: f.write(decrypted)

Step 3: Extract Flag from DEX

The decrypted 792 bytes form a valid Dalvik DEX file containing:

  • Class x with method logprint
  • String constant with the flag
strings decrypted.dex | grep HTB # HTB{SawS0DCLing}

Key Insight

The flag HTB{SawS0DCLing} = "Saw Sideloading" — referencing the Android malware technique of DEX sideloading, where encrypted code payloads are decrypted and loaded at runtime using DexClassLoader or InMemoryDexClassLoader to evade static analysis.

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups