Working 9 till 5

By Daniel Lee on 17 November, 2016

Daniel Lee

17 November, 2016

Daniel Lee was a summer intern in MWR's New York office. Below he writes about his experience with MWR and some of the cool stuff he got to work on.

What was it like to intern at MWR?

Working as an Intern at MWR was a great experience. During my time, I was able to explore different areas of security that I did not know before. I am glad that I was able to work with passionate and talented security experts who loved what they do.

What did you learn and what did you work on?

For the first week or so, I played around with “The Bazaare”, a purposely vulnerable web application that was developed for training. This was intended to introduce some of the vulnerabilities that can be found on various web applications. To help point out the vulnerabilities in “The Bazaare”, I also did various internal web application training modules.

Once I was done with “The Bazaare”, I spent two weeks or so tackling a vulnerable Windows Server, which was supposed to model some infrastructure vulnerabilities. It was running many different vulnerable services and my goal was to break into the server and get system level privileges in various ways. In the process, I got to learn a lot about possible uses of the Metasploit framework. 

For the remainder of my internship, I worked on my own research project, which was to hack an Android game. I picked out one of the game applications from the Google Play Store and was able to find several vulnerabilities. In the process, I learned how to use Drozer, an Android testing framework, and I also learned how to write Xposed modules to assist in exploiting the vulnerabilities.

Hacking an Android Game

Background

There are many games on the Google Play Store but it is questionable whether all of these apps are actually secured. For my research project, I chose a random game from the Google Play store to test its security. I ended up choosing a mobile game called “Legion Hunters”, which was created by the developer MainGames. The main goal of the game is to defeat the monsters. It contains in-app purchases, which will help the player progress further into the game. The game is written in Java, Lua and Cocos2dx (this was discovered through de-compiling the application, more information on this is below).

What is Lua?

Lua is a powerful and lightweight-scripting language that many games on the Google Play store use. It is very common to write a game engine in C/C++ and the game itself in Lua because it will avoid any issues with separation and provide more flexibility. For example, the Lua scripts can be edited to alter the game without the need to recompile the entire game engine. More importantly, Lua is already supported in many existing game engines.

What is Cocos2dx?

Cocos2dx is a cross platform game engine that provides common functionality in the game. It has a simplified API and rich features that developers can use without spending too much time. It does support Lua, which is most likely the reason that the developers for this game chose Cocos2dx for the engine.

Initial Recon

As a first step, I played around and noted any particularly interesting features in the game. This helped me take note of entry points for possible security vulnerabilities. The following are some of the features I noted:

Main Screen

Server (tap to select)

  • Feature to choose various game channels. This does not do anything other than loading the server name until the player taps on “Play.”
  • When the player taps on “Play”, the game client sends the server name to the game server to locate the correct game channels

Play

  • Gateway to the game server
  • If the player was not logged in, this would bring up the list of login services

Account

  • It shows the list of available login services that I am going to explain next

Account Types

There are two available login services: 

  • Facebook Login service through Facebook. This works by retrieving the user information from Facebook and stores it on the game server
  • MainGames - Login service created by the developers of this game. This works by retrieving the user information from MainGames’ server and stores it on the game server.

Once the user logs in, it is not necessary to login again next time since the application remembers all the previously logged in accounts. To pull up the list of those accounts, the user can simply tap on “PlayNow” as shown below:

If the player does not want to create an account, the player can choose
‘Create New Account’ to create a temporary account.

In Game

  Once in the game itself, the following features caught my eye:

  • Chat - Chat system where the player can talk to the other players in the game
  • Userinfo - Shows the general information about the player’s character

Attack Phase

Out of all these interactive features, I set my target as the login process since that is the first step the new user has to go through. Specifically, I wanted to find out what the game login process looks like when the player logs in through MainGames.

Hoping to find any API calls to MainGames, I decompiled the app and traced all the Java classes and methods responsible for making the request to their server. Eventually, I found a Java class called “MgSdkRequestFactory.”

This class defines all URLs for MainGames’ API and handles all main requests such as payment and authorization. When I saw these URLs, I immediately noticed two things:

  • None of these are using SSL
  • These three URLs are used for login:
    • http://api.maingames.co.id/v1/member/auth
    • http://api.maingames.co.id/v1/member
    • http://api.maingames.co.id/v1/game/server/login

To see exactly what data is sent over to MainGames’ API server, I did a Man-in-the-Middle (MITM) attack on the game and found that the login process goes through the following steps.

First, it sends a POST request to “/v1/member/auth” with the following data:

username=your_user_name&password=your_password&app_id=LEGIONHUNTERSMG&timestamp=current_unix_epoch_time&sign=md5hash

In this payload, the “sign” parameter is used to check validity of the request. It is calculated by taking the MD5 hash of username + password + timestamp + app_secret. Since the app secret was initially unknown, I wrote an Xposed module that logs this information. Xposed is a modular framework that can be used to hook and alter methods and variables in Android applications and the Android OS itself, this requires root access.

In order to verify I had correctly reversed the sign parameter, I checked it against multiple test accounts and it worked for all of them. More importantly, I found that the ”app_secret” parameter did not change across users. This indicated that this is a key specific to the application.

If the user’s login was successful, it would return following JSON data:

{“error”:0,”message”:””,”member”:{“id”:user_id,”username”:your_userame,”is_staff”:0,”fullname”:your_full_name,”email”:your_email,”vip”:0,”token”:mg_token_specific_for_each_user,”picture_url”:img_if_you_have_uploaded}}

If the user’s login was not successful however, it would return following JSON data:

{“error”:-21,”message”:””}

Once the user logs in, the application sends a second POST request to “/v1/game/server/login” with the following data:

server_id=7&product_id=1401&token=mg_token&app_id=LEGIONHUNTERSMG&timestamp=current_unix_epoch_time&sign=md5hash

The “sign” parameter is still used as a way to validate the request but this time it was calculated by taking the MD5 hash of server_id + product_id + user_token + timestamp + app_secret.

If the login was successful, the server would return following JSON data:

{“error”:0,”message”:””,”data”:null}

To put this all together, I wrote a simple Python script to login to the game:

Shared Account

The developers of this game have stated on the Google Play Store that I need to grant the application read/write permissions to the device’s external storage in order to “save credentials & images to support multiple accounts login”. This indicates that there is a file on the device that contains the list of logged in accounts.

Starting from Android 6.0, apps no longer have an access to the device’s external storage by default but instead they request the permission when they need it at runtime. This means I have to look for this file somewhere under “/mnt/runtime/read” and “/mnt/runtime/write”. To make the process easier, I wrote an Xposed module that hooks onto the method “loadSharedAccount” and logged the parameters.

This resulted in discovering the following files:

  • /storage/emulated/0/MainGames_SDK/shared_acc.json
  • /mnt/runtime/write/emulated/0/MainGame_SDK/shared_acc.json
  • /mnt/runtime/read/emulated/0/MainGame_SDK/shared_acc.json
  • /mnt/runtime/default/emulated/0/MainGame_SDK/shared_acc.json

I extracted the file and realized that the content was based64 encoded. I decoded the content but it was all useless data as seen below:

I searched through the decompiled source code for mentions of this file and it turns out AES encryption was used to encrypt the content before it was written to this file. Fortunately, the developers were nice enough to hard code the key into the application and I was able to write a simple Python script that decrypts this file.

#Decrypt
def decrypt_account(self):
#decrypting creds from loaded file
f = open("shared_acc.json","r")
payload = f.read()
f.close()
payload = payload.encode('utf-8')
payload = payload.decode('base64')
account = payload[0:len(payload)-16]
aes = AES.new(self.key,AES.MODE_CBC,"0"*16)
f = open("shared_acc_decrypt.json","wb")
f.write(aes.decrypt(account))
f.close()

This script decrypts the content and saves the result into a new file called “shared_acc_decrypt.json” which contains the full list of previously logged in accounts as shown below:

This is how the app keeps track of previously logged in accounts. To login as one of the users in the list, it goes through following steps:

First, the application sends a POST request to “/v1/member” with the following payload:

token=mg_token&app_id=LEGIONHUNTERSMG&timestamp=current_unix_epoch_time&sign=md5hash

Similar to the previous “sign” parameter, it is used as a way to validate the request and this is calculated by taking the MD5 hash of user_token + timestamp + app_secret.

If the login was successful, the server would return the user data in JSON format.

However, if the login was not successful, the server would return an error code “-21”.

Temporary Account

If the user does not have an account or accidentally deletes the list of
previous accounts, the user can create a temporary user for logging in. 

First, a username and password combination are generated based on the following formulas:

Username

  • The application first takes a signature called "MG" (MainGames) as the first two characters of the username
  • Next, it takes the last four numbers from the calculated current epoch time and adds them to the current signature. (Current epoch time is defined as the number of seconds that have elapsed since Thursday of January 1 1970). For instance, if the current epoch time was "1423456797", it would take "6797" and add to this signature
  • Finally, it takes the last four numbers of the Android id and appends them

Example: "MG67970F62"

Password

  • The application first gets the current timestamp and uses it as a seed (initial value) for a pseudo random number generator
  • Then, the application generates a random number and takes the absolute value of it
  • This value is then divided by the length of pre-defined printable characters
  • The remainder is used as an index to select one of the characters from the array of predefined printable characters
  • Step 1-4 is repeated 10 times

Example: "{12f^4<=#!"

Once a username and password combination are generated, the application sends a POST request to “/v1/member/register” with the following data:

username=generated_username&password=generated_password&email=generated_username@generate_username.com&ref_product=product_id&app_id=LEGIONHUNTERSMG&timestamp=current_unix_epoch_time&sign=md5hash

This time, the “sign” parameter is calculated by taking the MD5 hash of username + password + email + ref_product + timestamp + app_secret. 

If the login was successful, it would return the user’s data in JSON format.

Using this information I improved my script to automatically generate new accounts and login to the game.

Authorization

When an account is created, the user receives the following data from the server:

  • Username: user’s account name
  • Fullname: user’s full name
  • User_id: id that is used to identify the user along with the token
  • User_token: base64 encoded value that is used to identify the user
  • Login_source: flag to indicate the name of the login service.
  • Is_temporaryGuest: flag to tell if this is a temporary account.
  • Is_staff: flag to tell if this account belongs to one of the MainGames’ employees (0 or 1)
  • Is_vip: flag to tell if this account is a VIP (0 or 1)
  • Wallet: Google wallet balance
  • Picture_url: URL to the user’s profile picture

The user_id and user_token are both used to authorize with the game server after the successful login through one of the available services (Facebook or MainGames). This is handled by the method called “API_AUTHORIZED” which takes the user_id, user_token and the flag for login service as the parameters.

Hoping to get some error response, I created a temporary account and through an Xposed module I changed this user id to one of the user ids from different accounts in the following list:

  • 7054733 (first test account)
  • 7084689
  • 7084832
  • 7120296
  • 7175891
  • 7175957 (most recent test account)

These are some of the user ids I extracted from my test accounts and these all have the following properties:

  • It always starts with “7”
  • There are seven numbers
  • It goes up in ascending order.

Instead of getting an error response, I was logged in as the other user to which the user id belonged to. Since the validation was done on the server side, I could not pinpoint exactly how the server authorizes the user but some possibilities include:

  • The server does not verify the user id and the token with each account. If such a check exists, it is not correctly implemented or it is just a simple check to make sure user id is not NULL
  • The server queries the user information based only on the user id after the username and password are verified

To confirm this vulnerability, I attempted to login as various test accounts from a temporary account. Since I was successful in all attempts, I can theoretically retrieve all user ids and login as any user.

Summary

To wrap up, the following summarizes the login process through MainGames’ service:

First time Players

Temporary Account

  1.  
    1. A username and password combination are generated
    2. These are sent over to “/v1/member/register” along with a generated email, reference_product_id, app_id, current_timestamp and signature
    3. The game updates “shared_acc.json” file with a new user
    4. The game client sends the user id, token and flag to the game server to authorize the user
    5. Once the authorization is successful, it sends the server_id, product_id, token, app_id, timestamp and signature to “/v1/game/server/login” to notify the successful login to the game server

New Account

  1.  
    1. A user enters a username, password and email
    2. Steps 2-5 from above are repeated

Existing MainGames Account

 Previous Accounts

  1.  
    1. The game reads the previously logged in accounts from “shared_acc.json” file and loads the list
    2. When a user selects one of the accounts from the list, the application sends the token, app_id, timestamp and signature to “/v1/member/”
    3. The game client sends the user id, token and flag to the game server to authorize the user
    4. Once the authorization is successful, the application sends the server_id, product_id, token, app_id, timestamp and signature to “/v1/game/server/login” to notify the successful login to the game server

No Previous Accounts

  1.  
    1. A user enters a username and password.
    2. These are sent over to “/v1/member/auth” along with app_id, timestamp and signature
    3. The game updates “share_acc.json” file with a new user
    4. The game client sends the user id, token and flag to the game server to authorize the user
    5. Once the authorization is successful, the application sends the server_id, product_id, token, app_id, timestamp and signature to “/v1/game/server/login” to notify the successful login to the game server

Last thoughts

In the end, this app was not as secure as it could have been. Perhaps most of the popular games are not as vulnerable as this one. However, the fact that such vulnerabilities in this game exist says a lot about security in mobile gaming. There are plenty of good security practices and guidelines online that Android developers can follow. These are my suggestions to the developers of “Legion Hunters”.

SSL and SSL pinning

  • All API calls should be made over SSL
  • Implement SSL pinning so that performing a MITM attack is much harder to do

Proguard

  • Proguard is used to shrink the size of .apk files by removing unused code and resources. Also, this optimizes the byte code and obfuscates all classes, fields and methods, which makes reverse engineering much harder
  • It did not take me too long to find the classes and methods used for login since their names and fields were not obfuscated

Hard Coded Keys

  • Any code that was used in the encryption process should be obfuscated. This includes the result as well as the keys used in the encryption
  • Since the key was in plain sight, I was able to easily decrypt the share_acc.json file and retrieve the list of previous accounts

Root detection

  • Implement root detection. This will make it harder for the user to use tools like GameGuardian and Xposed framework
  • I was able to easily retrieve the payloads used in the login process by using my Xposed module

Disclosure

MWR first contacted the vendor on August 12th 2016 and again on August 26th 2016, however no response was received.