Reverse engineering NSmart API
APK online downloader - NSmart (latest)
strings Nsmart_4.6.apk > nsmart-strings.txt
Since the APK is (obviously) a Java archive (essentially a ZIP file), we can extract it and take a look primarly at these three files:
classes.dex
,classes2.dex
andresources.arsc
.
Running strings
on those three files enables us to easily extract some REST API endpoints and classpaths. They are available here.
Tools:
Converting APK to standard Java Archive:
.\dex2jar-2.1\dex-tools-2.1\d2j-dex2jar.bat -f -o nsmart.jar Nsmart_4.6.apk
Open the nsmart.jar
file in the Java decompiler. Sample REST API calls are:
- in
buslogic.nsmartapp.api.CitiesExtendedApi.class
... public CitiesExtendedApi(String paramString1, String paramString2) { this.companyApiKey = paramString1; this.companyUrl = paramString2; } public void getCitiesExtended() { OkHttpClient okHttpClient = new OkHttpClient(); HttpUrl.Builder builder = HttpUrl.get(this.companyUrl + "/publicapi/v1/networkextended.php").newBuilder(); builder.addQueryParameter("action", "get_cities_extended"); Request request = (new Request.Builder()).url(builder.build()).addHeader("X-Api-Authentication", this.companyApiKey).build(); okHttpClient.newBuilder().connectTimeout(7L, TimeUnit.SECONDS); okHttpClient.newBuilder().readTimeout(20L, TimeUnit.SECONDS); okHttpClient.newCall(request).enqueue(new Callback() { ... }); } ...
- in
buslogic.nsmartapp.api.AnnouncementApi.class
... public AnnouncementApi(String paramString1, String paramString2, int paramInt) { this.companyApiKey = paramString1; this.stationId = paramInt; this.companyUrl = paramString2; } ... public void getAnnouncements() { OkHttpClient okHttpClient = new OkHttpClient(); HttpUrl.Builder builder = HttpUrl.get(this.companyUrl + "/publicapi/v1/announcement/announcement.php").newBuilder(); builder.addQueryParameter("ibfm", "TM00000"); builder.addQueryParameter("station_uid", "" + this.stationId); Request request = (new Request.Builder()).url(builder.build()).addHeader("X-Api-Authentication", this.companyApiKey).build(); okHttpClient.newBuilder().connectTimeout(4L, TimeUnit.SECONDS); okHttpClient.newBuilder().readTimeout(10L, TimeUnit.SECONDS); okHttpClient.newCall(request).enqueue(new Callback() { ... }); } ...
All the API call require an Authentication key, so we search for the code where its value is set. The relevant code is in buslogic.nsmartapp.ui.SplashActivity.class
:
...
import android.app.Activity;
...
public class SplashActivity extends Activity {
...
private String companyApiKey;
private String companyUrl;
...
protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2131427366);
this.sharedPrefVersion = getSharedPreferences("com.example.testjedan.version", 0);
this.mDatabase = ((BasicApp)getApplication()).getDatabase();
this.companyApiKey = getString(2131820660);
this.companyUrl = getString(2131820664);
this.screenSplashDuration = Integer.parseInt(getString(2131820967));
hideSystemUI();
VersionApi versionApi = new VersionApi(this.companyApiKey, this.companyUrl);
versionApi.setCallBack(new SplashActivity$$ExternalSyntheticLambda1(this));
versionApi.getVersion();
}
...
}
The line this.companyApiKey = getString(2131820660);
represents an superclass (android.app.Activity
) call to the application Context, where the parameter 2131820660
(hex 7F110074
) is the resource ID of the API key located in the Application resources.
Moreover, we can see that the same number is declared as a constant in the buslogic.nsmartapp.R.class
:
public final class R {
...
public static final class string {
...
public static final int company_api_key = 2131820660;
...
public static final int company_url = 2131820664;
...
}
...
}
These constant names can be a clue to the actual resource names of the sought after values (and, as it turns out, they are the same).
Tools:
.\apktool_2.6.1.jar d '.\Nsmart_4.6.apk'
We position ourselves to the res
(resources) folder of the generated directory, and recursively search for the hex value of the company api key resource ID:
grep -r 7f110074
And the result is:
values/public.xml: <public type="string" name="company_api_key" id="0x7f110074" />
As we can see, the resource name is the same as the constant name in the previously show piece of code from the R.class
.
Now we again search for the api key value by its name company_api_key
:
grep -r company_api_key
And the result is:
values/public.xml: <public type="string" name="company_api_key" id="0x7f110074" />
values/strings.xml: <string name="company_api_key">4670f468049bbee2260</string>
The API key is 4670f468049bbee2260.
Now we create a few test calls in accordance with the decompiled java code shown previously, from buslogic.nsmartapp.api.CitiesExtendedApi.class
and buslogic.nsmartapp.ui.SplashActivity.class
.
curl --location --request GET 'https://online.nsmart.rs/publicapi/v1/networkextended.php?action=get_cities_extended' --header 'X-Api-Authentication: 4670f468049bbee2260'
From the response JSON we extract the station information. For instance, the ID for the station "Narodnog Fronta - Šekspirova" is 6532
.
Now we can form the Announcment API call for this station:
curl --location --request GET 'https://online.nsmart.rs/publicapi/v1/announcement/announcement.php?ibfm=TM00000&station_uid=6532' --header 'X-Api-Authentication: 4670f468049bbee2260'
The JSON response, among other things, gives us the exact current position for all busses that stop at the given bus station.
- Nemanja Popović (Android Dev)
- Anja Ekres (the idea)
- Dejan Bogosavljev (Generalissimo)