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
- Decompile:
jadx -d ba_jadx Bad_Actor_Access_App.apk. Relevant classes:MainActivity.checkPassword()(UTF-8 encodes input, callsCryptUtil.verify),CryptUtil(the crypto), andPerformanceProfiler.tick()(a harmless timing gimmick). - Read
CryptUtil. It holdsKEY(ASCIIe349b92b663a454a93e295b90ce70036, used as raw key bytes) and a 39-byteAUTH_TOKEN. The constructor runs a standard RC4 KSA overKEY.cryptruns the RC4 PRGA three times over the data, restarting thea,bcounters each pass but keeping the same mutated S-box, XORing keystream into the input.verify(pw)returnspw.length==AUTH_TOKEN.length && pw == crypt(AUTH_TOKEN). - Since
cryptis pure XOR and all inputs are constants, the accepted password is exactlycrypt(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))
- Output is 39 bytes, matching
AUTH_TOKEN.length. That string is the passwordverify()accepts.
Flag: SVIUSCG{You_even_solved_Android_APKs?!}