SB Freedom is the mobile banking app of SBI. It is bad and begs to be remade. After months of frustration, I finally decided to take a look at it and improve it a bit. I went a little off track tho.
I will have you know I had never worked on any of the languages used below. If you have, please ignore my bad code.
Let's begin.
First thing I noticed when I started using the app was the text sent in the SMS mode.
This didn't look secure. I captured the traffic in GPRS mode. It was the same query sent over HTTP. SBI already has SSL enabled banking website. The use of HTTP is plain wrong and stupid.
I decompiled and disassembled the apk. Two methods to tinker with apks:
apk -> dex2jar -> JAD -> decompiled java code.
apk -> apkstudio -> disassembled smali -> apkstdio -> modified apk
I had a look at the smali assembly.
Nope.jpg
The decompiled java code is much easier to read.
After some hours of reverse engineering the variables and functions and refactoring work, I had a basic idea about the internals of the application.
The application uses a database.
All the same ones are interesting.
15th is definitely the current mode to contact bank servers. GPRS or SMS. There is a registration process which includes a SMS based request to initialize the GPRS mode. This must generate a token of some kind to be used in future requests.
20th is the sync sign. The app can get out of sync with the server sometimes. There is an option to correct that. This is either time, a counter or a cryptographic function to validate requests.
The base64 decoded values are garbage. These are encrypted somehow.
After some more general searching and code refactoring, I struck gold soon enough:
SBIRMS1 is the name of the db. The a() method of j class seems to be generating a key.
Let's take a look.
Digging deeper.
dh class stores a byte array! dh is initialized by a() of class k. Which is in-turn initialized by the two strings "cizer" and "010101".
The class k was particularly bad. The decompiled code was hard to comprehend.
Don't panic. Here is the refactored version.
So, k.a() generates a 128bit key from two 6 character strings.
I had found the following numbers initialized to an array in a file which must be the AES class.
So, I assumed that the generated value is used as the AES key to encrypt and decrypt the database. You know what is even more classy? AES is used in ECB mode!
To test my refactored code, I compared the results from it to results from the original. I modified the apk and injected logging code to find out the return value of the function k.a().
This is the modified smali code that I ran on my phone to grab the actual result of the k.a() call.
The return value of k.a() is written to the log.
Values from the test confirmed that my code was correct. The value is:
319031602090F3B4C032A3ADA1538092
Let's decrypt the database!
1 is the userid. The same ones are all blank. The one next to GPRS is a 10 character string of numbers. This is the session ID.
The userid and mpin are used to encrypt the requests. For the user abc123 whose mpin is 123456, this key is always 23742020201233004137140201102316.
The query "MBSCM abc123,9EVxTudpAAbsZIUo345rXCKdJpmaqMOQtYEYDne4e+8="
is "MBSCM abc123,RANDOMsvc=654321"
So, change the mpin of user abc123 to 654321.
The RANDOM part is a 7 bytes value that is sent with each request. It is the current time in a tight format.
time_data_str() returns the current time in the format YYYMMDDHHMMSS . HH is 24h.
abyte1 is a array of half length of s.
Since the values can only be between 0-9, we can represent each character in just 4 bits!
The "hacking" may have become obvious now. Since there are only 1000000 possible mpins and the userid is sent with every request, we can easily bruteforce the mpin. How do we know which one is the correct one? The encrypted data contains a prefix that is the size of the string payload. This is stored as a 32bit integer in 4 bytes. If this length is calculated incorrectly, there is high chance of it being a larger number than the usual size of a request <30 chars.
Here is the refactored code for the prefix.
This woks wonderfully in practice. I was able to use this method to crack mpins from captured traffic in seconds.
Also sent with every GPRS request is the session ID. So, by only capturing one request of any mbanking user, we can get all the credential we need to access the account. All the functionality of the app including transfers can be used.
This can be done by various MITM attacks (on L2 or L3), such as setting up a malicious public hotspot, hacking CPEs, DNS hijack etc.
With the credentials and the complete format of requests, everything can be automated.
Flaws in the system:
1. userid of the customer is not encrypted in transport (sent in plain text) on both channels(SMS and GPRS).
3.
The AES encryption mode used is ECB (Electronic codebook) which is known
to be inadequate for use in any kind cryptographic protocol meant for
serious purposes.
2nd week January: Discovery and testing.
13th Jan: After finding and testing, I tried to contact SBI. There is no email to report such security issues. Sent a email on phising@sbi.co.in. No response.
19-21 Jan: Contacted a local officer of SBI. Some emails exchanges after I filed a written complaint, of course! Basic details shared.
22 Jan: Sent a detailed report to SBI. Prepared a very detailed blog post.
25 Feb: No response or action from SBI even after multiple followup emails.
26 Feb: A officer asked the technical department to respond.
Around the same time, I got a call from a officer who thanked us for the report. He didn't acknowledge the vulnerability.
May 26: Requested for response multiple times. No patch or even an acknowledgement.
June 1: I call the officer, he says that their technical department said that it is a non-issue. Advised me to contact the RBI.
I had tried to contact the banking lokpal, but they said that they only handle consumer cases.
I tried to contact the RBI but the concerned officer was on some tour and there was a general lack of interest there too.
I sent a final warning that I will make the exploit public on the 5th June.
Then I lost a very detailed blog post about it due to a bug in blogger and had no backup.
This is my second draft with bits and pieces from the lost one.
Please disable mobile banking if you are an SBI customer and ask them to fix this.
I will have you know I had never worked on any of the languages used below. If you have, please ignore my bad code.
Let's begin.
First thing I noticed when I started using the app was the text sent in the SMS mode.
MBSCM abc123,9EVxTudpAAbsZIUo345rXCKdJpmaqMOQtYEYDne4e+8=Note that the last part is base64 encoded. And that the username is in plain text.
This didn't look secure. I captured the traffic in GPRS mode. It was the same query sent over HTTP. SBI already has SSL enabled banking website. The use of HTTP is plain wrong and stupid.
I decompiled and disassembled the apk. Two methods to tinker with apks:
apk -> dex2jar -> JAD -> decompiled java code.
apk -> apkstudio -> disassembled smali -> apkstdio -> modified apk
I had a look at the smali assembly.
Nope.jpg
The decompiled java code is much easier to read.
After some hours of reverse engineering the variables and functions and refactoring work, I had a basic idea about the internals of the application.
The application uses a database.
All the same ones are interesting.
15th is definitely the current mode to contact bank servers. GPRS or SMS. There is a registration process which includes a SMS based request to initialize the GPRS mode. This must generate a token of some kind to be used in future requests.
20th is the sync sign. The app can get out of sync with the server sometimes. There is an option to correct that. This is either time, a counter or a cryptographic function to validate requests.
The base64 decoded values are garbage. These are encrypted somehow.
After some more general searching and code refactoring, I struck gold soon enough:
SBIRMS1 is the name of the db. The a() method of j class seems to be generating a key.
Let's take a look.
dh class stores a byte array! dh is initialized by a() of class k. Which is in-turn initialized by the two strings "cizer" and "010101".
The class k was particularly bad. The decompiled code was hard to comprehend.
So, k.a() generates a 128bit key from two 6 character strings.
I had found the following numbers initialized to an array in a file which must be the AES class.
1, 2, 4, 8, 16, 32, 64, 128, 27, 54, 108, 216, 171, 77, 154, 47, 94, 188, 99, 198, 151, 53, 106, 212, 179, 125, 250, 239, 197, 145
So, I assumed that the generated value is used as the AES key to encrypt and decrypt the database. You know what is even more classy? AES is used in ECB mode!
To test my refactored code, I compared the results from it to results from the original. I modified the apk and injected logging code to find out the return value of the function k.a().
This is the modified smali code that I ran on my phone to grab the actual result of the k.a() call.
The return value of k.a() is written to the log.
Values from the test confirmed that my code was correct. The value is:
319031602090F3B4C032A3ADA1538092
Let's decrypt the database!
1 is the userid. The same ones are all blank. The one next to GPRS is a 10 character string of numbers. This is the session ID.
The userid and mpin are used to encrypt the requests. For the user abc123 whose mpin is 123456, this key is always 23742020201233004137140201102316.
The query "MBSCM abc123,9EVxTudpAAbsZIUo345rXCKdJpmaqMOQtYEYDne4e+8="
is "MBSCM abc123,RANDOMsvc=654321"
So, change the mpin of user abc123 to 654321.
The RANDOM part is a 7 bytes value that is sent with each request. It is the current time in a tight format.
time_data_str() returns the current time in the format YYYMMDDHHMMSS . HH is 24h.
abyte1 is a array of half length of s.
Since the values can only be between 0-9, we can represent each character in just 4 bits!
The "hacking" may have become obvious now. Since there are only 1000000 possible mpins and the userid is sent with every request, we can easily bruteforce the mpin. How do we know which one is the correct one? The encrypted data contains a prefix that is the size of the string payload. This is stored as a 32bit integer in 4 bytes. If this length is calculated incorrectly, there is high chance of it being a larger number than the usual size of a request <30 chars.
Here is the refactored code for the prefix.
This woks wonderfully in practice. I was able to use this method to crack mpins from captured traffic in seconds.
Also sent with every GPRS request is the session ID. So, by only capturing one request of any mbanking user, we can get all the credential we need to access the account. All the functionality of the app including transfers can be used.
This can be done by various MITM attacks (on L2 or L3), such as setting up a malicious public hotspot, hacking CPEs, DNS hijack etc.
With the credentials and the complete format of requests, everything can be automated.
Flaws in the system:
1. userid of the customer is not encrypted in transport (sent in plain text) on both channels(SMS and GPRS).
2. 6 numerical digits password are not secure. The compatibility argument is not enough to jeopardize customers interests.
4. The 128bit AES key is
generated by a self made, non standard, untested, unproven to be fit for
the purpose key generation algorithm which involves simple bit
manipulations, value substitutions and arithmetic operations. This is
completely against any standards and recommendation in cryptography.
Also, the keys are derived from two 6 characters strings one of which is
known for every request viz. userid and mpin.
5. The
Internet channel(GPRS) doesn't use HTTPS for transport. There is no sane
reason for this except incompetence. The gprs session id is also sent
in plain text waiting to be captured and exploited.Time line of events:
2nd week January: Discovery and testing.
13th Jan: After finding and testing, I tried to contact SBI. There is no email to report such security issues. Sent a email on phising@sbi.co.in. No response.
19-21 Jan: Contacted a local officer of SBI. Some emails exchanges after I filed a written complaint, of course! Basic details shared.
22 Jan: Sent a detailed report to SBI. Prepared a very detailed blog post.
25 Feb: No response or action from SBI even after multiple followup emails.
26 Feb: A officer asked the technical department to respond.
Around the same time, I got a call from a officer who thanked us for the report. He didn't acknowledge the vulnerability.
May 26: Requested for response multiple times. No patch or even an acknowledgement.
June 1: I call the officer, he says that their technical department said that it is a non-issue. Advised me to contact the RBI.
I had tried to contact the banking lokpal, but they said that they only handle consumer cases.
I tried to contact the RBI but the concerned officer was on some tour and there was a general lack of interest there too.
I sent a final warning that I will make the exploit public on the 5th June.
Then I lost a very detailed blog post about it due to a bug in blogger and had no backup.
This is my second draft with bits and pieces from the lost one.
Please disable mobile banking if you are an SBI customer and ask them to fix this.
The app has since been taken offline since launch of new apps hence publishing now.








No comments:
Post a Comment