Post

APK Reverse Engineering: SekaiBank Challenge

Write Up of the APK Reverse Engineering challenge in SekaiCTF 2025

APK Reverse Engineering: SekaiBank Challenge

Introduction

This write-up covers an APK reverse-engineering challenge from SekaiCTF 2025. The goal was to understand how the app signs API requests and to reproduce the signature generation so we could call the protected /api/flag endpoint and retrieve the flag.

Initial analysis

After installing the app, I found a login screen requiring a username and password, with a registration option.

After registering, the app asks to set a PIN, then opens the main dashboard. The account starts with a $1,000 balance and has send/receive functionality.

I enabled network logging to inspect the requests the app makes and observed calls to the API at:

1
https://sekaibank-api.chals.sekai.team/api/

The requests include an Authorization header (a JWT) and an X-Signature header.

Decompilation

I used jadx to decompile the APK and explored the app’s package structure. The main activity is in com.sekai.bank.

The networking code is under com.sekai.bank.network. I found an endpoint used to fetch the flag and the request model used for it.

The flag endpoint accepts a POST to /api/flag with a JSON body that matches the FlagRequest model. The body contains a boolean unmask_flag, so sending {"unmask_flag": true} should request the unmasked flag.

I attempted the request from Burp Suite, but the server rejected it with an “invalid signature” error.

Finding the signature algorithm

In com.sekai.bank.network there is a SignatureInterceptor class which adds the X-Signature header. The generateSignature method constructs a string from the HTTP method, endpoint path, and body, then uses the app’s signing certificate as the HMAC key.

Concretely, the flow is:

  • Build the string: METHOD + “/api” + endpoint_path + body
  • Obtain the app’s signing certificate fingerprint (SHA-256)
  • Use the certificate bytes as the HMAC key and compute HMAC-SHA256 over the string

The app’s SHA-256 signing-fingerprint (extracted from the APK) was:

1
SHA-256 Fingerprint: 3F 3C F8 83 0A CC 96 53 0D 55 64 31 7F E4 80 AB 58 1D FC 55 EC 8F E5 5E 67 DD DB E1 FD B6 05 BE

In Jadx you can find the Signature certificate in APK folder

Reproducing the signature

To call /api/flag we must reproduce the same signature the app generates. The important details are:

  • HTTP method: POST
  • Endpoint path (used in the signed string): /flag (the interceptor prepends /api when building the signed string)
  • Body: {"unmask_flag": true} (compact JSON, no extra whitespace)

Below is a Python script that reproduces the signing process and performs the request. It uses the SHA-256 fingerprint as the HMAC key (binary form).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hmac
import hashlib
import json
import binascii

hex_fingerprint = "3F3CF8830ACC96530D5564317FE480AB581DFC55EC8FE55E67DDDBE1FDB605BE"
key_bytes = binascii.unhexlify(hex_fingerprint)

method = "POST"
endpoint_path = "/flag"
body_dict = {"unmask_flag": True}
body = json.dumps(body_dict, separators=(",", ":"))
data_to_sign = method + "/api" + endpoint_path + body

signature = hmac.new(key_bytes, data_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() # compute HMAC-SHA256 signatu>signature = signature.lower()

print(f"X-Signature: {signature}")

Output:

1
X-Signature: 440ba2925730d137259f297fd6fba02af2f7b6c414dd16a1ac336e9047cdb8f5

Now lets use this signature in our API request to /api/flag using Burp Suite.

And voilà! we have successfully got the flag:

1
SEKAI{are-you-ready-for-the-real-challenge?}

Here is a Python script to automate the API request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import hmac
import hashlib
import binascii
import json
import requests

hex_fingerprint = "3F3CF8830ACC96530D5564317FE480AB581DFC55EC8FE55E67DDDBE1FDB605BE" 
key_bytes = binascii.unhexlify(hex_fingerprint)

method = "POST"
endpoint_path = "/flag"
body_dict = {"unmask_flag": True}
body = json.dumps(body_dict, separators=(",", ":"))
data_to_sign = method + "/api" + endpoint_path + body

signature = hmac.new(key_bytes, data_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
signature = signature.lower()

url = "https://sekaibank-api.chals.sekai.team/api/flag"
bearer_token = "<REDACTED_BEARER_TOKEN>"  # replace with a real token obtained 
headers = {
    "Authorization": f"Bearer {bearer_token}",
    "X-Signature": signature,
    "Content-Type": "application/json",
}

response = requests.post(url, headers=headers, data=body)
print(response.text)

Happy reversing!

This post is licensed under CC BY 4.0 by the author.