arrow_backUS Cyber Games 2026
Reverse Engineeringmedium

Bad_Actor_Access_App

by Pavel Knespl (capyplivl)

An Android login app (Bad_Actor_Access_App.apk, package feel.supremacy.monument) where the correct password is the flag. Verification is a hardcoded RC4-variant XOR cipher with both key and token baked into the dex, so the password is just the cipher's output run over the embedded token.

Solve

  1. Decompile: jadx -d ba_jadx Bad_Actor_Access_App.apk. Relevant classes: MainActivity.checkPassword() (UTF-8 encodes input, calls CryptUtil.verify), CryptUtil (the crypto), and PerformanceProfiler.tick() (a harmless timing gimmick).
  2. Read CryptUtil. It holds KEY (ASCII e349b92b663a454a93e295b90ce70036, used as raw key bytes) and a 39-byte AUTH_TOKEN. The constructor runs a standard RC4 KSA over KEY. crypt runs the RC4 PRGA three times over the data, restarting the a,b counters each pass but keeping the same mutated S-box, XORing keystream into the input. verify(pw) returns pw.length==AUTH_TOKEN.length && pw == crypt(AUTH_TOKEN).
  3. Since crypt is pure XOR and all inputs are constants, the accepted password is exactly crypt(AUTH_TOKEN). Reimplement it in Python (convert signed Java bytes to unsigned):
KEY  = bytes("e349b92b663a454a93e295b90ce70036","ascii")
AUTH = bytes([b & 0xff for b in [-120,75,9, ...]])
S=list(range(256)); T=[KEY[i%len(KEY)] for i in range(256)]
j=0
for i in range(256): j=(j+S[i]+T[i])&255; S[i],S[j]=S[j],S[i]
out=bytearray(len(AUTH))
for _ in range(3):
    a=b=0
    for k in range(len(AUTH)):
        a=(a+1)&255; b=(b+S[a])&255; S[a],S[b]=S[b],S[a]
        out[k]=S[(S[a]+S[b])&255]^AUTH[k]
print(bytes(out))
  1. Output is 39 bytes, matching AUTH_TOKEN.length. That string is the password verify() accepts.

Flag: SVIUSCG{You_even_solved_Android_APKs?!}