Skip to main content

Analysis of the UTS app

Introduction

The UTS (Unreserved Ticketing System) is an initiative by the CRIS division of the Ministry of Railways allowing people to book paperless unreserved (a seat/berth is not guaranteed) train tickets in advance. The goal of the app is to reduce long queues at railway stations for physical tickets. The UTS project boasts of handling approximately 2,34,000 tickets daily, with 6333 stations as the originating points.

The number of people travelling ticketless in Indian Railways has consistently been increasing over the years, with the number touching 3.6 crore as of 2022-23. One of the several reasons people travel ticketless besides not wanting to pay is the inconvenience of standing behind long queues at the stations just to get the ticket. The introduction of the UTS app in 2018 (across all zones) was a welcome move to tackle this problem.

Flow

The ticket booking and login flow has been extensively analysed and critiqued at this and this blog.

Problems

  1. Location Restrictions: To combat the issue of people only booking tickets on the spot in the event th ey are asked for one, the UTS app does not allow ticket booking inside a fixed radius from the centre (?) of the railway station. This has resulted in a lot of problems which are widely reported on social media.

  2. Tamper Detection: In an age where vendor ROMs make android phones barely usable, detecting root or custom ROMs is an unnecessary mechanism with the exception of maybe banking applications.

The Architecture

The android application for UTS has the package name com.cris.utsmobile. The version being analysed in this post is 15.1.50. The application logic is mostly handled by the Java layer with some security specific stuff and endpoints being fetched from the native layer, which is very common to see in mobile applications.

It is always a good idea to look at the APK contents before dropping it into a disassembly framework. In UTS’ case, there is an interesting observation to be found.

Hidden deep under a path in the assets/fonts folder is a database file named UTS1124.db


/images/7zFM_wl2pnbZ788.png

There are some references to SQLite (more on that later) in the java layer, but opening the database with an SQLite viewer leads to an error, indicating that the DB is either using a custom format or is somehow encrypted.

In the lib folder where the native binaries are found, we encounter two shared libraries named libnative-lib.so and libsqlcipher.so , confirming our suspicion that the DB file is actually encrypted.

/images/javaw_3Gnknt2qr6.png

The main java code for the program lies in the package com.cris

com.cris.uts deals with utility stuff such as database handling and encryption while the other package contains most of the actual app code.

The Database

Performing a string lookup for “UTS1124” returns a reference to this location in the package com.cris.uts.database


/images/javaw_ynyJoqbSpn.png

We find several functions in the class responsible for loading elements such as Station Names, latitudes and longitudes. Each function calls the method getWritableDatabase which in turn calls down the line calls sqlciphers openDatabase.

The second parameter to this function is the passphrase that is used to derive a key and decrypt the database.


/images/javaw_ha0o3WO5ot.png

getWritableDatabase takes only one argument which is the passphrase.

The “key” is retrieved from the native lib by calling the function Java_decode_code_stringForDB


/images/javaw_uaSSyCg62R.png

Opening up the library "libnative-lib.so" in IDA, and navigating to the function Java_assetdbhelperlib_in_org_cris_com_assetdbhelperlib_Utils_Java_1decode_1code_1stringForDB, we see from the decompilation that the function logic is somewhat obfuscated and seems to be building a string by combining a bunch of binary values.


/images/ida_BrlrClMi6S.png

However, we need not attempt to understand or even deobfuscate this function. We can simply retrieve the static value by hooking the java function on app startup and capturing the return value. It is ensured that the function is called since the app has to read the database at least once after startup.

/images/DB_Browser_for_SQLite_JR2SRdUTRc.png

Doing exactly that, the decryption passphrase was captured and the decrypted database was dumped through the sqlcipher utility.

The dumped SQLite database can be viewed normally with a SQLite browser.

There are several tables, most of them are self explanatory. However, one notable table is the MUSER_STATION table, which contains exact latitudes and longitudes of (almost) all the railway stations in India, along with their station code. These are the exact coordinates which are evaluated in the geofencing procedure when one tries to book a ticket.

The contents of this table are available here in a .csv format and can be analysed. Most of these coordinates lie exactly in the centre of the station when viewed through Google Maps imagery, however some are outliers and might lie at extreme ends. Having these coordinates handy can be helpful in urgent booking situations since the app does not inform the user directly of the coordinates.

Besides holding these values, the database is also responsible for acting as a central repository for all the booked tickets that get saved to the mobile phone and can be viewed even when offline. Updates/patches to the database can also be pushed dynamically by the UTS server whenever needed.

Station QRs

The UTS app offers a QR booking mode as an alternative to the journey mode, with the difference that the app enforces a maximum radius of distance from the station coordinates in contrast to the minimum radius restriction while booking through the journey mode. Therefore, a ticket through this mode can only be booked while inside the train station.

These QR codes are pasted at various locations at all railway stations and are encrypted. To save time, people have exploited the static nature of QR codes and maintained a repository of crowdsourced QR codes from railway stations across the country and hosted them across various websites. However, this is overkill since the decryption logic for the QR codes is present in the UTS app itself.

The function generateQRKeyIn located in the class assetdbhelperlib.in.org.cris.com.assetdbhelperlib.VersionComparator contains the key building logic for the QR decryption. Base64 Decoding the contents of a QR code and decrypting it using AES-ECB with the aforementioned key churns out the plaintext contents which are of the following format:


ASANGAON : ASO : 19.4393835000000 : 73.3077903000000

Station Name : Station Code : Latitude : Longitude


These values are consistent with the details already stored in the in-app database, therefore, it remains a daunting question why the redundant coordinates in the QR codes exist in the first place.

A PoC is available at poc.

It turns out that only the station name and code are verified during the ticket booking procedure, therefore it is possible that one could simply change the coordinates to their current location, re-encrypt the contents (AES is symmetric) and base64 encode the same to generate a QR code, which can be scanned using the app and ultimately allows anyone to book a ticket for any station in the country (which is already possible anyway through the non-QR workflow).