Note: A more complete writeup on Kony was published at Analyzing Kony Mobile Applications
What is Kony?
Kony is a mobile app development platform that allows a developer to build mobile applications in HTML5 and JavaScript that can be built for different platforms like iOS and Android.
Analyzing an Android APK file built with Kony requires a different approach as
normal tools such as apktool
do not work. This is because the application
logic is actually contained in a ZIP archive of JavaScript source files that
gets loaded into a JavaScript VM that is shipped in the APK.
Previous Work
The BlackHat 2015 talk "Deconstructing Kony Android Applications" by Chris Weedon contains detailed information on how to recover the JavaScript source files from an APK file.
We will go briefly take a look at how to obtain the source files for each major Kony revision. For more details, reading through the slide deck is highly recommended.
Kony versions < 6.0
In older versions of Kony, a Lua bytecode interpreter is used instead of a
JavaScript VM. The APK ships Lua bytecode instead of JavaScript source files.
We can recognize APKs built with this version of Kony if we find a
konyappluabytecode.o.mp3
file in the unzipped APK directory.
If we encounter APKs built with this version of Kony, we can use
unluac.jar
to decompile the Lua bytecode.
Kony version 6.0+ (Unencrypted)
From Kony 6.0 on, Lua support was deprecated and JavaScript support was
introduced. We can recognize APKs built with the newer versions of Kony if we
find a lib/libkonyjsvm.so
file in the unzipped APK directory.
Initially, the JavaScript source files was shipped as an unencrypted ZIP
archive. If we see that assets/js/startup.js
is a ZIP archive, we can simply
unzip the archive to retrieve the JavaScript source files.
Kony version 6.0+ (Encrypted)
At some point in the Kony 6 lifecycle (the BlackHat talk mentions 6.3), the
ZIP file is now encrypted. We can recognize APKs built with this version of
Kony if assets/js/startup.js
is seen as a data file (instead of a ZIP
archive). The encryption scheme used is AES_256_CBC
with a hardcoded key
that undergoes a series of transformations that is described in the BlackHat
talk.
We can use the method described in the talk to decrypt the ZIP archive without
reverse engineering the key transformation logic. This method takes advantage
of the fact that the kony_loadfile.exe
binary which is used in the build
process to encrypt the ZIP archive calls out to OpenSSL's EVP_CipherInit
function that has an integer parameter to indicate whether the function is
used for encryption or decryption. If we patch that parameter in the binary,
we can change kony_loadfile.exe
from an encryptor to a decryptor.
We can call the patched binary (kony_decryptfile.exe
) like below to decrypt
the ZIP archive.
$ kony_decryptfile.exe <infile> <outfile> <app id> <package name> <timestamp>
The package name
parameter can be found in the AndroidManifest.xml
file
while the app id
and timestamp
parameters can be found in the
assets/application.properties
file as the values of the AppID
and Var
keys respectively.
The outfile file should be a ZIP archive containing the JavaScript source files.
Kony version 7.0+
In yet another update to Kony, kony_loadfile.exe
has now been changed to use
OpenSSL's newer EVP_EncryptInit
function. This means that the method of
patching a byte in kony_loadfile.exe
to turn it into a decryptor no longer
works. It also appears that the hardcoded key has been changed so using an old
version of kony_loadfile.exe
will not work.
However, since the encryption process still uses a hardcoded key, we can use
a debugger to pull the encryption key (after the kony_loadfile.exe
binary
performs the key transformation logic) from process memory.
- Set a breakpoint right before the call to
EVP_EncryptInit
. In the binary I am working with, this is address0x004013A9
. - Run the binary with the same parameters.
kony_loadfile.exe <infile> <outfile> <app id> <package name> <timestamp>
. - Step through the program until we hit the breakpoint.
- Pull the 32 bytes of key material from the stack. In the binary I am working with, this is the stack address pointed at by the
eax
register.
Once we have the key material from the stack, we can write a Python script to
decrypt the file we want. After a little reverse engineering of the code, we
see that a static IV, abcd1234efgh5678
, is used for the AES encryption step.
import binascii
import sys
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
def main():
if len(sys.argv) != 4:
print("Usage: ./decrypt.py <infile> <outfile> <keyfile>")
return
IN_FILE = sys.argv[1]
OUT_FILE = sys.argv[2]
KEY_FILE = sys.argv[3]
backend = default_backend()
with open(KEY_FILE, "rb") as f:
key = f.read()
iv = b"abcd1234efgh5678"
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
decryptor = cipher.decryptor()
with open(IN_FILE, "rb") as f:
ciphertext = f.read()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
with open(OUT_FILE, "wb") as f:
f.write(plaintext)
if __name__ == "__main__":
main()
After we decrypt the file, we should see a ZIP archive.
$ python3 decrypt.py app/assets/js/startup.js startup.js.zip key.bin
$ file startup.js.zip
startup.js.zip: Zip archive data, at least v1.0 to extract