Reverse engineering my bank's security token
My current bank, one of Brazil's largest, provides its clients with one of several methods (in addition to their passwords) to authenticate to their accounts, online and on ATMs. I reverse engineered their Android OTP code generator and ported it to an Arduino-compatible microcontroller.
Disclaimers and more disclaimers
Some names have been changed or removed to protect the innocent. This project's results do not allow me or anyone else to hack into bank accounts, or even replicate a client's token without access to a rooted device with an active code generator. On the other hand, a malicious third party application with root privileges would have access to all the information required to generate codes, but would still need the account details (including a password) to fully compromise an account.
This is not a security vulnerability or even criticism by any stretch. The bank's code generation algorithm is (arguably) more secure than Google Authenticator (which keeps secrets around in plaintext), and this article should be seen as praise for the bank's app, which does things the right way by adhering to the TOTP standard, and protects its data as well as technically possible.
The company in question requested the following disclaimer to be published along with the article.
The application Chave de Segurança for mobile phones is secure, with it´s design and implementation being done following the best practices, and under International Standards (OATH - Initiative for Open AuTHentication) . The application security does not depend on executable code, and it´s customization is performed exclusively on the user´s mobile phone.
We reinforce the users to adopt safe behavior when using his mobile phone: do not disclose passwords/pin numbers; keep the mobile phone operating system integrity (only use original manufacturer updates); only install applications from official stores; use password to lock and unlock the cell phone; be suspicious of emails or SMS requesting passwords; never click on suspicious links.
A solução Chave de Segurança para uso em celular é segura, seguindo padrões internacionais (OATH - Initiative for Open AuTHentication), e boas práticas na sua concepção e implementação. Destacamos que a segurança da aplicação não depende do código executável, sendo sua personalização realizada exclusivamente no celular do usuário.
Reforçamos aos usuários a importância de sempre adotar atitudes seguras, dentre as quais destacamos: não divulgar senhas; manter o Sistema Operacional do celular íntegro (atualizar somente com versões originais do fabricante); somente instalar aplicações de lojas oficiais; usar senha para bloqueio e desbloqueio do celular; desconfiar de emails ou SMS que solicitem senhas; não clicar em links suspeitos.
My current bank, one of Brazil's largest, provides its clients with one of several methods (in addition to their passwords) to authenticate to their accounts, online and on ATMs. New accounts are usually provided with a credit-card sized piece of paper with 70 single-use codes which are randomly requested, once per access. This requires the client to obtain a new set of codes whenever they run out of them, which is not very practical.
A better alternative is their Android app (also available in several other platforms). It provides a Google Authenticator-like code generator, except it is PIN-protected, and requires a phone call to activate but operates seamlessly after that. Or so I thought.
I found myself calling the bank every so often after changing ROMs, resetting or changing phones. The activation process is simple enough, but the hacker in me did not enjoy the ordeal. I attempted to use Titanium Backup to no avail, for reasons I would soon understand. There was just one thing left to do - reverse engineer the application and build my own. Maybe make my own physical token using an Arduino.
- Someone (sorry I couldn't find the message, ping me for credit) pointed out to me that the string deobfuscation would be handled by JMD. I had no idea a Java deobfuscator existed, so there's something to try before reverse engineering by hand.
- Marcos Diez pointed out via e-mail that older versions of the app were not obfuscated at all. So there's something else to try that could save you some work when reverse engineering an app.
Activating the application
Before diving into the code, I had to go through the normal setup process once, which meant installing their app, calling the bank and activating it. Here are some screenshots of the process.
The first image shows the installation process. Look at all those permissions it requests! I'm sure they are all necessary for some reason, but none of them should be necessary to generate codes, right? The second image shows the actual activation process, inputting four numeric fields that have to be provided over the phone (notice I was in a call then). The third image shows the actual code generator, after a successful activation.
Reverse engineering Android apps requires a few software tools. Here's what I used for this project:
Provides the adb command-line tool, which can pull APKs, data files and settings from the phone.
Converts Android's Dalvik executables into JARs, which are easier to reverse engineer.
An excellent Java bytecode decompiler.
A Java IDE to validate discoveries during the reverse engineering process.
Getting the APK file from the phone
The first step in reverse engineering an application is obviously getting the application. It is possible to download APK files directly from Google Play, but I decided to get the file directly from my phone, using ADB.
Enable USB debugging in your cell phone, then run the following commands.
Find the package name
Find the package path
Download the package
You should now have a
com.mybank-1.apk file in the current directory.
Extracting and converting the APK file
APK files can be extracted using the
unzip utility, because they are ZIP files with a different extension (much like JAR files). Inside the archive, the actual code is in the
classes.dex file, which I renamed to
com.mybank-1.dex just to keep things organized.
Extract the package
Rename and convert classes.dex to a JAR file
You should now have a
com.mybank-1-dex2jar.jar file in the current directory, which can be opened by JD.
Peeking at the code
After dragging the JAR file into JD-GUI, you should be greeted by a window similar to the following.
Here's where the fun begins. While there are some obvious packages containing parts of the token module, such as
com.mybank.varejo.token, it doesn't take too long to realize that the core functionality is implemented in a few of the default package classes, which are obfuscated. Bummer.
Deobfuscating the exception strings
Here's a snippet from
Every exception thrown by this piece of code is obfuscated, as well as many of the strings used throughout the code. That is a major roadblock, since exception messages and strings in general are a great way of figuring out what the code is doing when reverse engineering something.
Luckily, their developers decided to actually show useful text when a problem occurs and an exception gets thrown, so they wrapped those obfuscated strings with
a.a, presumably a decryption routine that returns the original text. That routine is not too straightforward, but it is possible to get a high level understanding of what it is doing. Here are some findings after analyzing the
a class and its dependencies:
pis a base64 decoder.
bis an AES implementation. Searching for its internal strings and constants on Google revealed that it is part of Paulo Barreto's JAES, a public domain crypto library.
private static byte ain class
ais an obfuscated key, which can be deobfuscated by this short C program, basically replicating a snippet of the original
This code yields the AES encryption key.
a.a is not just a wrapper for JAES's AES class. It also does some crypto of its own. Here's some pseudopython for
In a nutshell, besides AES with an obfuscated key, this class appears to implement CBC (cipher block chaining) which was not present in the original JAES library.
A simple test to make sure it works:
The message reads (in Portuguese) "it is not possible to change PIN without being logged in". Success!
Reverse engineering the code generation process
Deobfuscating the exception strings was a fun battle, but the war was not over yet. I had yet to figure out how to generate an authentication code myself. After looking around the code for a long while, I found a good entry point to the token generation process in the
br.com.othercompany.token.dispositivo.OTP class. Here's a snippet, with the exception strings deobfuscated.
This method basically generates a timestamp
i which is the number of 36-second intervals since April 1st, 2007 at midnight (expressed as the Unix timestamp
1175385600000L). Why 36 seconds? That's how long each token lasts. Why April 1st, 2007 at midnight? No idea.
It also includes a correction factor (
getAjusteTemporal(), which means temporal adjustment in Portuguese). I assume this is calculated at activation time as the difference between the server's and the device's clocks. In this snippet,
o.a is the core token generating function, and its parameters are a byte array (a key) and the current timestamp.
Finding the key
The key is obtained by calling
this.a.getConfiguracao().getChave().a(20) in the previous snippet.
this.a is a
Perfil (profile) object;
getConfiguracao() returns a
Configuracao (settings) object;
getChave() returns a
a(int) returns a byte array, which is the key itself.
z class is obfuscated but fortunately quite simple. It is just a wrapper around a byte array up to 32 bytes in length, and its
a(int) method truncates that array to the provided length. The
Perfil object, in turn, gets created by the
PersistenciaDB (persistence database) class, which contains a bunch of obfuscated strings:
Finally! Investigating the
a.a method pays off. Here are the deobfuscated strings.
A SQL statement, interesting. So that's how it stores its profiles. And there's a filename too,
token.db, probably a SQLite database. Further investigation of the
carregar (load) method in
PersistenciaDB class shows that, indeed, it is a SQLite database, accessed through the
One might think it would only be a matter of getting the key from the database, then. Not so fast. The data blob is encrypted as well, as evidenced further in the
carregar method by the use of the
aa.a method (so much for descriptive names - blame the obfuscation). That method accepts as parameters the data blob, an empty buffer, and a parameter that gets passed through the
carregar method - a key - truncated to 16 characters.
Before investigating the crypto behind
aa.a, I decided to find the key to decrypt the blob. It gets passed as a parameter to the
carregar method. After digging around for a bit, I found the class that generates the key:
PersistenciaUtils. Here it is, in its entirety:
In other words, the SHA-1 digest of the device's
android_id (a unique identifier), or a default value if that doesn't work. Notice that it hashes the hex string, not the actual bytes. So that's why Titanium Backup did not work when I tried it - I was not backing up this identifier, even though there was an option for that in Titanium Backup. It's too late to go back now though, let's keep reversing this app.
To find my
android_id, I used
adb once again.
aa.a splits the data blob in several sequential fields: a 96-byte header, a 16-byte nonce, a 16-byte tag, and the rest of the blob as cryptotext. Further inspection of the
aa class reveals some more details about the obfuscated classes:
eis an implementation of EAX, an AEAD (Authenticated Encryption with Associated Data) algorithm, from JAES.
fis an implementation of CMAC (Cipher-based Message Authentication Code), also from JAES.
his an implementation of the CTR (counter) mode, from JAES as well.
lis an unknown implementation of the SHA-1 hashing algorithm. Interestingly, it is not used by the
PersistenciaUtilclass, which uses the
mis an unknown implementation of the HMAC (keyed-Hash Message Authentication Code) algorithm.
nis a wrapper around
m, providing HMAC-SHA1.
aa.a derives a second key by computing the CMAC tag of the header and uses it to decrypt the cryptotext. In pseudopython:
If the EAX authentication succeeds,
aa.a returns the decrypted content to
PersistenciaDB, which then interprets the decrypted data.
Looking back at the
PersistenciaDB class, now at the
a method which parses the decrypted data into a
Perfil object, it consists basically of a deserialization of the decrypted data into several booleans, shorts, and byte arrays. It is possible to identify several of the fields, of which three stand out (their offsets were discovered by adding along the deserialization).
And yes, this is finally the key I was looking for. My PIN matched, which was a welcome validation that my implementation was working correctly, and my time offset was small enough to ignore.
Understanding the code generation process
The key obtained at the previous step gets truncated to 20 characters in the
OTP class, which then passes it along with the timestamp to the
o.a method. That method references several of the obfuscated classes identified in the previous steps, which is a relief. Based on that, here's some pseudopython for that method.
Basically the timestamp (a long, 8 bytes in length) gets (manually) turned into a big-endian byte array. That array gets hashed using HMAC-SHA1 employing the key as key, generating a hash. The last four bits of the hash determine an index at which an integer is read. Take that integer, modulo 1000000, and that's our code. Simple, huh? Yeah, I didn't think so either. But it works!
A while later, I found this snippet in Google Authenticator's implementation of TOTP:
Looks familiar? It's the exact same algorithm. In fact, only a couple of things prevented me from creating a Google Authenticator QR-Code from this data:
- The arbitrary timestamp epoch of April 1st, 2007 at midnight.
- The period, which is 30 seconds in Google Authenticator and 36 seconds in my bank's token. The key URI format used by Google Authenticator accepts a period parameter which could fix this, but the application currently ignores it.
Porting everything over to an Arduino (clone)
Or, rather, a Texas Instruments Stellaris LaunchPad I had lying around. They are actually code-compatible when using the Energia IDE, and I even used some Arduino-specific libraries:
A cryptographic library for Arduino (including SHA and HMAC-SHA)
A lightweight date and time library for JeeNodes and Arduinos.
A library for 2x16 LCD (like JDH162A or HD44780) written for Energia and Stellaris Launchpad (LM4F).
The RTC part needs improvement. Since the Stellaris LaunchPad does not have an onboard RTC, the internal clock needs to be set at each startup, which is cumbersome and requires a computer to get it going, and that's not very practical. For now, here's the complete code:
An interesting hack occurs on this line:
Instead of figuring out a way to set the clock at every startup, I used this hack in which the current time is filled in by the compiler just before the code gets uploaded to the board, resulting in a close-enough internal clock. RTClib generates timestamps with an epoch of Jan 1st, 2000 at midnight, so the code generator's epoch had to be converted to
228700800. A correction factor of
7200 was also required because the compiler fills in the local time instead of UTC, so it is two hours behind for me.
It is important to mention that this project's results do not allow me or anyone else to hack into bank accounts, or even replicate a client's token without access to a rooted device with an active code generator. On the other hand, a malicious third party application with root privileges would have access to all the information required to generate codes, but would still need the account details (including a password) to fully compromise an account.
I would like to thank Daniel Nascimento, Raphael Campos and Miguel Gaiowski for helping me review this article.
Finally, here's some proof that it works (a couple of seconds too fast) :)