/******************************************************************************* * Copyright (c) 2011 Adam Shanks (ChainsDD) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.noshufou.android.su.util; import com.noshufou.android.su.HomeActivity; import com.noshufou.android.su.R; import com.noshufou.android.su.UpdaterActivity; import com.noshufou.android.su.UpdaterFragment; import com.noshufou.android.su.preferences.Preferences; import com.noshufou.android.su.preferences.PreferencesActivity; import com.noshufou.android.su.service.PermissionsDbService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; import android.text.format.DateFormat; import android.util.Log; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class Util { private static final String TAG = "Su.Util"; public static final int MALICIOUS_NOT = 0; public static final int MALICIOUS_UID = 1; public static final int MALICIOUS_RESPOND = 2; public static final int MALICIOUS_PROVIDER_WRITE = 3; private static final HashMap<Integer, String> sSystemUids = new HashMap<Integer, String>(); static { sSystemUids.put(0, "root"); sSystemUids.put(1000, "system"); sSystemUids.put(1001, "radio"); sSystemUids.put(1002, "bluetooth"); sSystemUids.put(1003, "graphics"); sSystemUids.put(1004, "input"); sSystemUids.put(1005, "audio"); sSystemUids.put(1006, "camera"); sSystemUids.put(1007, "log"); sSystemUids.put(1008, "compass"); sSystemUids.put(1009, "mount"); sSystemUids.put(1010, "wifi"); sSystemUids.put(1011, "adb"); sSystemUids.put(1012, "install"); sSystemUids.put(1013, "media"); sSystemUids.put(1014, "dhcp"); sSystemUids.put(1015, "sdcard_rw"); sSystemUids.put(1016, "vpn"); sSystemUids.put(1017, "keystore"); sSystemUids.put(1018, "usb"); sSystemUids.put(1021, "gps"); sSystemUids.put(1025, "nfc"); sSystemUids.put(2000, "shell"); sSystemUids.put(2001, "cache"); sSystemUids.put(2002, "diag"); sSystemUids.put(3001, "net_bt_admin"); sSystemUids.put(3002, "net_bt"); sSystemUids.put(3003, "inet"); sSystemUids.put(3004, "net_raw"); sSystemUids.put(3005, "net_admin"); sSystemUids.put(9998, "misc"); sSystemUids.put(9999, "nobody"); } public static String getAppName(Context c, int uid, boolean withUid) { if (sSystemUids.containsKey(uid)) { return sSystemUids.get(uid); } PackageManager pm = c.getPackageManager(); String appName = "Unknown"; String[] packages = pm.getPackagesForUid(uid); if (packages != null) { try { if (packages.length == 1) { appName = pm.getApplicationLabel(pm.getApplicationInfo(packages[0], 0)) .toString(); } else if (packages.length > 1) { appName = ""; for (int i = 0; i < packages.length; i++) { appName += packages[i]; if (i < packages.length - 1) { appName += ", "; } } } } catch (NameNotFoundException e) { Log.e(TAG, "Package name not found", e); } } else { Log.e(TAG, "Package not found for uid " + uid); } if (withUid) { appName += " (" + uid + ")"; } return appName; } public static String getAppPackage(Context c, int uid) { if (sSystemUids.containsKey(uid)) { return sSystemUids.get(uid); } PackageManager pm = c.getPackageManager(); String[] packages = pm.getPackagesForUid(uid); String appPackage = "unknown"; if (packages != null) { if (packages.length == 1) { appPackage = packages[0]; } else if (packages.length > 1) { appPackage = ""; for (int i = 0; i < packages.length; i++) { appPackage += packages[i]; if (i < packages.length - 1) { appPackage += ", "; } } } } else { Log.e(TAG, "Package not found"); } return appPackage; } public static Drawable getAppIcon(Context c, int uid) { PackageManager pm = c.getPackageManager(); Drawable appIcon = c.getResources().getDrawable(R.drawable.sym_def_app_icon); String[] packages = pm.getPackagesForUid(uid); if (packages != null) { // if (packages.length == 1) { try { ApplicationInfo appInfo = pm.getApplicationInfo(packages[0], 0); appIcon = pm.getApplicationIcon(appInfo); } catch (NameNotFoundException e) { Log.e(TAG, "No package found matching with the uid " + uid); } // } } else { Log.e(TAG, "Package not found for uid " + uid); } return appIcon; } public static Drawable getStatusIconDrawable(Context context, int allow) { int[][] statusButtons = { { R.drawable.perm_deny_dot, R.drawable.perm_allow_dot }, { R.drawable.perm_deny_emo, R.drawable.perm_allow_emo } }; String iconTypeString = PreferenceManager.getDefaultSharedPreferences(context) .getString(Preferences.STATUS_ICON_TYPE, "emote"); int statusIconType = 1; if (iconTypeString.equals("dot")) { statusIconType = 0; } else if (iconTypeString.equals("emote")) { statusIconType = 1; } if (allow < 0 || allow > 1) { Log.e(TAG, "Bad value given to getStatusButtonDrawable(int). Expecting 0 or 1, got " + allow); return null; } Drawable drawable = context.getResources().getDrawable(statusButtons[statusIconType][allow]); return drawable; } public static String getUidName(Context c, int uid, boolean withUid) { PackageManager pm = c.getPackageManager(); String uidName= ""; if (uid == 0) { uidName = "root"; } else { pm.getNameForUid(uid); } if (withUid) { uidName += " (" + uid + ")"; } return uidName; } public static void goHome(Context context) { // Be clever here, otherwise the home button doesn't work as advertised. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); Intent intent; if (prefs.getBoolean(Preferences.GHOST_MODE, false)) { intent = new Intent(context, HomeActivity.class); } else { intent = new Intent(); intent.setComponent(new ComponentName("com.noshufou.android.su", "com.noshufou.android.su.Su")); } intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); context.startActivity(intent); } public static String getSuVersion() { Process process = null; String inLine = null; try { process = Runtime.getRuntime().exec("sh"); DataOutputStream os = new DataOutputStream(process.getOutputStream()); BufferedReader is = new BufferedReader(new InputStreamReader( new DataInputStream(process.getInputStream())), 64); os.writeBytes("su -v\n"); // We have to hold up the thread to make sure that we're ready to read // the stream, using increments of 5ms makes it return as quick as // possible, and limiting to 1000ms makes sure that it doesn't hang for // too long if there's a problem. for (int i = 0; i < 400; i++) { if (is.ready()) { break; } try { Thread.sleep(5); } catch (InterruptedException e) { Log.w(TAG, "Sleep timer got interrupted..."); } } if (is.ready()) { inLine = is.readLine(); if (inLine != null) { return inLine; } } else { os.writeBytes("exit\n"); } } catch (IOException e) { Log.e(TAG, "Problems reading current version.", e); return null; } finally { if (process != null) { process.destroy(); } } return null; } public static int getSuVersionCode() { Process process = null; String inLine = null; try { process = Runtime.getRuntime().exec("sh"); DataOutputStream os = new DataOutputStream(process.getOutputStream()); BufferedReader is = new BufferedReader(new InputStreamReader( new DataInputStream(process.getInputStream())), 64); os.writeBytes("su -v\n"); // We have to hold up the thread to make sure that we're ready to read // the stream, using increments of 5ms makes it return as quick as // possible, and limiting to 1000ms makes sure that it doesn't hang for // too long if there's a problem. for (int i = 0; i < 400; i++) { if (is.ready()) { break; } try { Thread.sleep(5); } catch (InterruptedException e) { Log.w(TAG, "Sleep timer got interrupted..."); } } if (is.ready()) { inLine = is.readLine(); if (inLine != null && Integer.parseInt(inLine.substring(0, 1)) > 2) { inLine = null; os.writeBytes("su -V\n"); inLine = is.readLine(); if (inLine != null) { return Integer.parseInt(inLine); } } else { return 0; } } else { os.writeBytes("exit\n"); } } catch (IOException e) { Log.e(TAG, "Problems reading current version.", e); return 0; } finally { if (process != null) { process.destroy(); } } return 0; } public static boolean isSuCurrent() { if (getSuVersionCode() < 10) { return false; } return true; } public static String formatDate(Context context, long date) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String format = prefs.getString("pref_date_format", "default"); if (format.equals("default")) { return DateFormat.getDateFormat(context).format(date); } else { return (String)DateFormat.format(format, date); } } public static String formatTime(Context context, long time) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean hour24 = prefs.getBoolean("pref_24_hour_format", true); boolean showSeconds = prefs.getBoolean("pref_show_seconds", false); String hour = "kk"; String min = "mm"; String sec = ":ss"; String post = ""; if (hour24) { hour = "kk"; } else { hour = "hh"; post = "aa"; } if (showSeconds) { sec = ":ss"; } else { sec = ""; } String format = String.format("%s:%s%s%s", hour, min, sec, post); return (String)DateFormat.format(format, time); } public static String formatDateTime(Context context, long date) { return formatDate(context, date) + " " + formatTime(context, date); } public static boolean elitePresent(Context context, boolean versionCheck, int minVersion) { PackageManager pm = context.getPackageManager(); int sigs = pm.checkSignatures("com.noshufou.android.su", "com.noshufou.android.su.elite"); if (sigs != PackageManager.SIGNATURE_MATCH) { return false; } else { if (versionCheck) { PackageInfo pi; try { pi = pm.getPackageInfo("com.noshufou.android.su.elite", 0); if (pi.versionCode >= minVersion) { return true; } else { return false; } } catch (NameNotFoundException e) { return false; } } else { return true; } } } public static void launchPreferences(Context context) { // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { context.startActivity(new Intent(context, PreferencesActivity.class)); // } else { // context.startActivity(new Intent(context, PreferencesActivityHC.class)); // } } public static String getHash(String pin) { MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { Log.e("Utils", "NoSuchAlgorithm, storing in plain text...", e); return pin; } digest.reset(); try { byte[] input = digest.digest(pin.getBytes("UTF-8")); String base64 = Base64.encode(input); return base64; } catch (UnsupportedEncodingException e) { Log.e("Utils", "UnsupportedEncoding, storing in plain text...", e); return pin; } } public static boolean checkPin(Context context, String pin) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String setPin = prefs.getString("pin", ""); return getHash(pin).equals(setPin); } public static void toggleAppIcon(Context context, boolean newState) { ComponentName componentName = new ComponentName("com.noshufou.android.su", "com.noshufou.android.su.Su"); context.getPackageManager().setComponentEnabledSetting(componentName, newState ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED: PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } public static List<String> findMaliciousPackages(Context context) { List<String> maliciousApps = new ArrayList<String>(); List<PackageInfo> installedApps = context.getPackageManager() .getInstalledPackages(PackageManager.GET_PERMISSIONS); for (PackageInfo pkg : installedApps) { int result = isPackageMalicious(context, pkg); if (result != 0) { maliciousApps.add(pkg.packageName + ":" + result); } } return maliciousApps; } public static int isPackageMalicious(Context context, PackageInfo packageInfo) { // If the package being checked is this one, it's not malicious if (packageInfo.packageName.equals(context.getPackageName())) { return MALICIOUS_NOT; } // If the package being checked shares a UID with Superuser, it's // probably malicious if (packageInfo.applicationInfo.uid == context.getApplicationInfo().uid) { return MALICIOUS_UID; } // Finally we check for any permissions that other apps should not have. if (packageInfo.requestedPermissions != null) { String[] bannedPermissions = new String[] { "com.noshufou.android.su.RESPOND", "com.noshufou.android.su.provider.WRITE" }; for (String s : packageInfo.requestedPermissions) { for (int i = 0; i < 2; i++) { if (s.equals(bannedPermissions[i]) && context.getPackageManager(). checkPermission(bannedPermissions[i], packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) { return i + 2; } } } } return MALICIOUS_NOT; } public static void showOutdatedNotification(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(Preferences.OUTDATED_NOTIFICATION, true)) { NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification(R.drawable.stat_su, context.getString(R.string.notif_outdated_ticker), System.currentTimeMillis()); PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, UpdaterActivity.class), 0); notification.setLatestEventInfo(context, context.getString(R.string.notif_outdated_title), context.getString(R.string.notif_outdated_text), contentIntent); notification.flags |= Notification.FLAG_AUTO_CANCEL; nm.notify(UpdaterFragment.NOTIFICATION_ID, notification); } } public static void updatePermissionsDb(Context context) { PreferenceManager.getDefaultSharedPreferences(context).edit() .putBoolean("permissions_dirty", true).commit(); Log.d(TAG, "Start PermissionsDbService"); Intent intent = new Intent(context, PermissionsDbService.class); context.startService(intent); } }