Greetings my fellow hackers,
As we continue with our series, the AMUNET app becomes complicated with new functionalities and structures to understand. We’ll sail right through. As stated earlier in previous tutorials, the app doesn’t fully exists because I build them before I share so forgive me if it takes sometime before a tutorial comes out. I need to make sure everything works well first.
PREVIOUS TUTORIALS
Below are the tutorials covered so far.
TODAY’S TUTORIAL
In today’s tutorial, we are going to send data to our server using Volley Google. Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. In order to use volley in our android app, we first need to import it into our android studio project.
Go to build.gradle
( Module: app ) under Grade Scripts and add the dependency implementation 'com.android.volley:volley:1.1.0'
. Make sure you sync the project. You should something similar to this.
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.2' implementation 'com.android.support:design:27.1.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.android.volley:volley:1.1.0'
}
Now, we can go on to use the volley library in our project. Before we continue, create a new Basic Activity
called “Dashboard
”. We wouldn’t use it now ( later in this tutorial ). The reason why we are creating the Dashboard
activity is because we are going to use the MainActivity.java
for phone registration.
SETTING UP AN APP ICON
No one likes using the default icon for created projects. You can use any icon of your choice. I’m using an eye image. Make sure your image is big enough. I’ll upload it here incase anyone wants to use it.
Now, head over to Roman Urik’s Github. I love using this tool because of the ease it allows for the customization of icons. Select Image
( Under Foreground ) and upload your preferred image. Customize the icon according to your taste. I’m using a white background.
After you are done, click on the Download button
on the upper right section of the website under the Source on Github
link. The image should be downloaded onto your computer.
Extract the package and you should notice the icons are grouped into resolutions. Don’t mess them up. They are grouped intentionally. Go back to the Android Studio and under res
, right click on mipmap
. On Mac, choose “Reveal in Finder” and on Windows, something similar maybe “Open in Explorer” or “Explore” should open the mipmap directory in your explorer. Now copy the files accordingly from the extracted mipmap folder to the opened android studio mipmap directory. Should everything be done correctly, the new icons copied will show in the android studio. If you still have trouble, search online or watch this video Change The App Icon in Android Studio - YouTube
Open AndroidManifest
( under app->manifests ) and change the value for android:roundIcon
and android:icon
in the application
tag to the filename of the app icon imported. This will allow the app use the icon.
Still in AndroidManifest
file, add the READ_PHONE_STATE
, INTERNET
AND ACCESS_NETWORK_STATE
permissions. Above the application tag opening, add these lines.
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
CREATING THE UI FOR MAIN ACTIVITY
Two files are created under the layout
folder when a Basic Activity is chosen. In our case, it’s activity_main.xml
and content_main.xml
unless you named your activity otherwise.
In the activity_main.xml, clear the toolbar
and the appbarlayout
code. Our code should look like this.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <include layout="@layout/content_main" /> </android.support.design.widget.CoordinatorLayout>
We will design our registration interface in the content_main
. The code for the UI
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:id="@+id/textinputlayout1"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:hint="Username"
android:id="@+id/username"
android:layout_height="wrap_content" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:id="@+id/textinputlayout2"
android:layout_below="@id/textinputlayout1"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:hint="Name"
android:id="@+id/full_name"
android:layout_height="wrap_content" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:id="@+id/textinputlayout3"
android:layout_below="@id/textinputlayout2"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:hint="Password"
android:inputType="textWebPassword"
android:id="@+id/password"
android:layout_height="wrap_content" />
</android.support.design.widget.TextInputLayout>
<ImageView
android:layout_width="150dp"
android:src="@drawable/eye2"
android:layout_marginBottom="10dp"
android:layout_above="@id/textinputlayout1"
android:layout_centerHorizontal="true"
android:id="@+id/logo_imageview"
android:layout_height="150dp" />
<Button
android:layout_width="match_parent"
android:text="Sign up"
android:id="@+id/create_account_button"
android:textColor="@android:color/white"
android:layout_marginTop="10dp"
android:background="@color/colorPrimary"
android:layout_below="@id/textinputlayout3"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_height="wrap_content" />
</RelativeLayout>
I have already uploaded the image ( eye2 ) for the ImageView ( located in the drawable folder ) in the setting up app icon section of this tutorial. You can change the source of the ImageView or rename my image to eye2 in the drawable folder.
This is my screen right now.
CONFIGURATION.JAVA
Create a new Java class and name it Configuration
. Inside Configuration
class, write this code.
private static final String app_host = "xx.xx.xx.xx";
private static final String domain_path = "https://" + app_host + "/";
private static final String app_auth = domain_path + "/receiver.php";
public static String getApp_host() {
return app_host;
}
public static String getDomain_path() {
return domain_path;
}
public static String getApp_auth() {
return app_auth;
}
The Configuration
java class we just created will allow us to reference the server address from this file instead of manually typing it across the entire project which is like not cool.
xx.xx.xx.xx
is the server address ( localhost, network address ) eg 0x00sec.org or 104.18.48.48domain_path
is the qualified domain server path including the appropriate protocols. eg https://0x00sec.org/. Don’t modify this line since it’s just a reference.app_auth
is a file on the server that receives the request. Takes the server path and the receiving file path.
The remaining methods are getters
( Right click
→ Generate
→ Getter
).
WRITING THE CODES FOR MAINACTIVITY.JAVA
The final part of this tutorial will be a bit tricky so try and read with understanding as I also try to explain in simple terms.
Normally, above the onCreate
method and below public class MainActivity extends ...
code, lets declare our UI objects.
EditText username, password, name;
Button create_account_button;
SharedPreferences sharedPreferences;
private static final int READ_PHONE_STATE_REQUEST_CODE = 10001;
ProgressDialog progressDialog;
Basically
- We declare the EditText for our username, password and name fields ( content_main.xml ).
- We declare the Button for our sign up button ( content_main.xml )
- SharedPreferences allows us to save information. We create an instance of it.
READ_PHONE_STATE_REQUEST_CODE
will allow us determine if our permission request was granted or denied ( discussed later ).- ProgressDialog will allow us to display a progress bar with a message.
onCreate Method
This will be the code for our onCreate method.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferences = getSharedPreferences("Auth", Context.MODE_PRIVATE);
final String auth_key = sharedPreferences.getString("auth_key", null);
if(auth_key != null) {
startActivity(new Intent(MainActivity.this, Dashboard.class));
finish();
}
username = findViewById(R.id.username);
password = findViewById(R.id.password);
name = findViewById(R.id.full_name);
create_account_button = findViewById(R.id.create_account_button);
create_account_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
show_permission_alert("Allow the app to read the phone's information", "read_phone_state");
} else {
if(username.getText().toString().length() < 5) {
show_alert("Username must be more than 5 characters");
return;
}
if(password.getText().toString().length() < 5) {
show_alert("Password must be more than 5 characters");
return;
}
if(name.getText().toString().length() < 3) {
show_alert("Enter a valid name");
return;
}
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setMessage("Creating account ...");
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.show();
create_phone_account();
}
}
});
}
sharedPreferences
- get a file “Auth
” ( key-value file ).MODE_PRIVATE
makes the file private to our app.String auth_key
- gets the value for the key (auth_key
) in ourAuth
file. Returnsnull
if the keydoesn't exist
.- If the key exists ( phone registered ), redirect us to the
Dashboard
activity we created earlier. - Set reference to our respective EditTexts (
username
,password
,name
) - Set reference to the signup button ( create_account_button)
- When the button is clicked, we check if the
READ_PHONE_STATE
permission has been granted. If the permission hasn’t been granted, we callshow_permission_alert
method ( takes two arguments:message
andpermission_id
). Will create the method in a bit. - If the permission is granted, then we check and make sure our
username
,name
andpasswords
fields have valid data before we send them to our server. There is also a new method calledshow_alert
which displays messages to the user. - If the data is valid, we call the
create_phone_account
method to send our registration details to the server.
Hope I got someone to understand it.
The permission READ_PHONE_STATE
allows us to read the phone’s International Mobile Equipment Identity ( IMEI ). With the IMEI, we can later identify the phone and user accounts registered with it.
From #6
( above ), should the permission be denied, we call the show_permission_alert
. Lets create the method.
SHOW_PERMISSION_ALERT
Outside the onCreate
and inside the MainActivity
class, create this method.
private void show_permission_alert(String message, final String permission) {
AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
dialog.setMessage(message);
dialog.setCancelable(false);
dialog.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(permission.toLowerCase().equals("read_phone_state")) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[] {Manifest.permission.READ_PHONE_STATE},
READ_PHONE_STATE_REQUEST_CODE);
}
}
});
dialog.show();
}
It simply displays any message you pass to it. After you read the message displayed and you click “Ok”, the method checks if the permission_id
passed during the method call matches the ones specified in the method. If they match, the appropriate code executes. The reason for this approach is that we will be asking for a lot of future permissions and instead of creating different methods every time, we can instead group them into one method along with their respective codes.
In this method, we request the READ_PHONE_STATE
permission when the “read_phone_state
” is passed. The READ_PHONE_STATE_REQUEST_CODE
will allow us to check if the permission was granted or not. Before we move on to check the whether our permission was granted or not, lets create the show_alert
method. Similar to the show_permission_alert
method but just displays messages and does not accept permissions_id
.
SHOW_ALERT
There is not much explanation to be done here as I’ve already explained.
protected void show_alert(String msg) {
AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
dialog.setMessage(msg);
dialog.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
dialog.show();
}
Just displays messages.
ON REQUEST PERMISSIONS RESULT
This method allows us to check if the permission request was granted or not. In order to determine the state of the permission request, we’ll need the identifier ( request code - READ_PHONE_STATE_REQUEST_CODE
). Hope you now understand the importance of the request code integer value.
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case READ_PHONE_STATE_REQUEST_CODE: {
if (grantResults.length > 0
&& grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(getApplicationContext(), "Without this permission, the desired action cannot be performed", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Permission granted", Toast.LENGTH_LONG).show();
}
return;
}
}
}
We print the appropriate response based on the user’s action. Since we need the READ_PHONE_STATE
permission in order to get the app working, we won’t allow registration unless the permission is allowed.
GET DEVICE IMEI
IMEI’s are unique to every phone meaning we can track a phone based on its IMEI. We use the IMEI to identify a phone and not mix the data. This method will retrieve the IMEI of the phone and return it to its caller.
protected String getDeviceIMEI() {
String deviceUniqueIdentifier = null;
TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
if (null != tm) {
try {
deviceUniqueIdentifier = tm.getDeviceId();
} catch (SecurityException e) {
return null;
}
}
if (null == deviceUniqueIdentifier || 0 == deviceUniqueIdentifier.length()) {
deviceUniqueIdentifier = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
}
return deviceUniqueIdentifier;
}
Makes use of the Telephony Manager
which requires the READ_PHONE_STATE
permission. Now in the onClickListener
function of the create_account_button
( onCreate method ), there is one last method we have not tackled and that is the create_phone_account
.
CREATE PHONE ACCOUNT
Before we move on, in the previous tutorial Building a God’s Eye Android App: Part 1 - Collecting Installed Android Apps, we included a thread calling the collect_installed_apps
in the onCreate
method. Kindly delete that thread. The onCreate
method should now look like this.
Moving on to the create_phone_acccount
method
private void create_phone_account() {
final String phone_imei = getDeviceIMEI();
final String phone_serial = Build.SERIAL;
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
StringRequest serverRequest = new StringRequest(Request.Method.POST, Configuration.getApp_auth(), new Response.Listener<String>() {
@Override
public void onResponse(String req) {
progressDialog.dismiss();
try {
final JSONObject response = new JSONObject(req);
if(response.getBoolean("success")) {
final String server_response = response.getString("response");
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("auth_key", response.getString("api_key"));
editor.apply();
new Thread(new Runnable() {
@Override
public void run() {
collect_phone_details();
collect_installed_apps();
}
}).start();
new CountDownTimer(5000,1000) {
@Override
public void onTick(long l) {
}
@Override
public void onFinish() {
show_alert(server_response);
}
}.start();
username.setText("");
password.setText("");
name.setText("");
} else {
show_alert(response.getString("response"));
}
} catch (Exception e) {
show_alert("Authentication error: " + e.getMessage());
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
progressDialog.dismiss();
show_alert("Internet disconnected");
}
}) {
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("imei", phone_imei);
params.put("serial", phone_serial);
params.put("user", username.getText().toString());
params.put("name", name.getText().toString());
params.put("pass", password.getText().toString());
return params;
}
};
requestQueue.add(serverRequest);
}
This method makes use of the RequestQueue function in the volley library to send requests. In this case we are sending a POST request. Notice the parameters
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("imei", phone_imei);
params.put("serial", phone_serial);
params.put("user", username.getText().toString());
params.put("name", name.getText().toString());
params.put("pass", password.getText().toString());
return params;
}
The POST parameters being sent here are optional and not mandatory. I have set up a web server which accepts these specific parameters so incase you don’t have a server to test and want to use my server, send these particular parameters otherwise the data won’t be logged.
The codes in the onResponse
method are also optional and not mandatory. My web server gives each phone an API key to access the platform after phone registration. Incase you want to use my server, all data sent will have to be posted with the API key otherwise the incoming data will be discarded.
The thread we earlier deleted in the onCreate
method will now be called here should the response have a success field set to true. Remember the response is converted to a JSON Object. Volley allows us to directly receive the data in JSONObject but for debugging purposes, I used the StringRequest to see what was returned before converting the strings to JSON ( Was having issues with my server back then ).
After the phone information has been logged on your web server or mine. You can begin to send data to the server. A thread is run in the onResponse
method calling two methods collect_phone_details
and collect_installed_apps
. After a count down of 5 seconds, the server response is shown. The delay allows the two methods collect_phone_details
and collect_installed_apps
to finish execution before the user gets the chance to perform any further action which can interrupt the upload.
COLLECT PHONE DETAILS
In this method, we gather information about our device using Build
( android.os ) and TelephonyManager ( SIM Operations ).
private void collect_phone_details() {
upload_detail("VERSION.RELEASE", Build.VERSION.RELEASE);
upload_detail("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL);
upload_detail("VERSION.SDK.NUMBER", String.valueOf(Build.VERSION.SDK_INT));
upload_detail("BOARD", Build.BOARD);
upload_detail("BOOTLOADER", Build.BOOTLOADER);
upload_detail("BRAND", Build.BRAND);
upload_detail("CPUABI", Build.CPU_ABI);
upload_detail("CPUABI2", Build.CPU_ABI2);
upload_detail("DISPLAY", Build.DISPLAY);
upload_detail("FINGERPRINT", Build.FINGERPRINT);
upload_detail("HARDWARE", Build.HARDWARE);
upload_detail("HOST", Build.HOST);
upload_detail("ID", Build.ID);
upload_detail("MANUFACTURER", Build.MANUFACTURER);
upload_detail("MODEL",Build.MODEL);
upload_detail("PRODUCT", Build.PRODUCT);
upload_detail("SERIAL", Build.SERIAL);
upload_detail("TAGS", Build.TAGS);
upload_detail("TIME", String.valueOf(Build.TIME));
upload_detail("TYPE", Build.TYPE);
upload_detail("UNKNOWN",Build.UNKNOWN);
upload_detail("USER", Build.USER);
upload_detail("DEVICE", Build.DEVICE);
TelephonyManager telephonyManager = ((TelephonyManager)getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE));
String simOperatorName = telephonyManager.getSimOperatorName();
String simNumber = "";
try {
simNumber = telephonyManager.getLine1Number();
} catch (SecurityException e) {
}
upload_detail("SIM1.OPERATOR", simOperatorName);
upload_detail("SIM1.PHONE", simNumber);
}
The method makes an extensive use of another method upload_detail
to send data to our server. If you intend to use my server, leave the values intact otherwise the information won’t show. If you don’t intend to use my server, then feel free to modify the parameters and requests as you wish.
UPLOAD DETAILS
This method sends data to the server using the api_key obtained during the registration process. The key is stored on the device using sharedPreferences. The method won’t upload if there is no api key.
private void upload_detail(final String key, final String value) {
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
final String auth_key = sharedPreferences.getString("auth_key", null);
if(auth_key == null) { return; }
StringRequest serverRequest = new StringRequest(Request.Method.POST, Configuration.getApp_auth(), new Response.Listener<String>() {
@Override
public void onResponse(String req) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}) {
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("auth", auth_key);
params.put("k", key);
params.put("v", value);
return params;
}
};
requestQueue.add(serverRequest);
}
Should you want to use my server, leave the POST parameters intact. Any slight modifications will cause the data to be rejected. I mean rejected. Otherwise, you are free to use and name your POST parameters as you please.
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("auth", auth_key);
params.put("k", key);
params.put("v", value);
return params;
}
Be sure to keep the data intact. I can’t stress on it enough if you are to use my server.
COLLECT INSTALLED APPS
Last but one of the method calls for today’s tutorial. This method enumerates through the Package Manager and gets information about apps installed on the device. In our previous tutorial, we had only two parameters: app name and package name. Now we have included three more parameters.
private void collect_installed_apps() {
final PackageManager pm = getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo packageInfo : packages) {
if(pm.getLaunchIntentForPackage(packageInfo.packageName) != null)
{
try {
String app_name = packageInfo.loadLabel(getPackageManager()).toString();
String app_package = packageInfo.processName;
String app_uid = Integer.toString(packageInfo.uid);
String app_versionName = pm.getPackageInfo(app_package, 0).versionName.toString();
String app_versionCode = String.valueOf(pm.getPackageInfo(app_package, 0).versionCode);
upload_app(app_name, app_package, app_uid, app_versionName, app_versionCode);
} catch (Exception e) {
}
}
}
}
There is not much to explain as I have already done that in the previous tutorial. The new parameters are app_uid
, app_versionName
and app_versionCode
.
UPLOAD APP
This function uploads data about the installed apps to the server. You are free to modify the parameters if you are not using my server as a backend.
private void upload_app(final String app_name, final String app_package, final String app_uid, final String app_vName, final String app_vCode) {
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
final String auth_key = sharedPreferences.getString("auth_key", null);
if(auth_key == null) { return; }
StringRequest serverRequest = new StringRequest(Request.Method.POST, Configuration.getApp_auth(), new Response.Listener<String>() {
@Override
public void onResponse(String req) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}) {
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("auth", auth_key);
params.put("app_name", app_name);
params.put("app_package", app_package);
params.put("app_uid", app_uid);
params.put("app_vname", app_vName);
params.put("app_vcode", app_vCode);
return params;
}
};
requestQueue.add(serverRequest);
}
That’s it for this tutorial.
CONFIGURING YOUR ANDROID APP TO COMMUNICATE WITH MY SERVER ( AMUNETCLOUD )
In communicating with AMUNETCLOUD, you need to make sure the following settings are in place.
Change the address in the Configuration to
private static final String app_host = "play.cardfinder.co";
private static final String domain_path = "https://" + app_host + "/";
private static final String app_auth = domain_path + "/auth.php";
Secondly, make sure that you do not modify the original codes posted here as the server will reject modified request or data.
ACCESSING UPLOADED PHONE INFORMATION
To access your data, simply log onto AMUNETCLOUD using the credentials entered during registration. You should be redirected to your phone data.
DISCLAIMER
The server is a test or demonstration server and therefore I will not be held accountable for whatever data is sent to it. It’s purely for education purpose.
I would love your contributions, suggestions, feedbacks, critics, etc. Anything to help the series.
You can directly import the project into your android studio if you are having trouble.
Checkout the github repo: https://github.com/sergeantexploiter/Amunet
Until we meet again. I’m out.
#Sergeant