def gather_permissions_labels(): # FIXME: would probably put in global db? cmd = '{cli} shell getprop ro.product.model' model = catch_err(run_command(cmd, outf=MAP)).strip().replace(' ', '_') cmd = '{cli} shell pm list permissions -g -f > {outf}' #perms = catch_err(run_command(cmd, outf=model+'.permissions')) perms = catch_err( run_command(cmd, outf='static_data/android_permissions.txt'))
def setup(self): p = run_command('{cli} kill-server; {cli} start-server') if p != 0: print(">> Setup failed with returncode={}. ~~ ex={!r}".format( p.returncode, p.stderr.read() + p.stdout.read()), file=sys.stderr)
def isrooted(self, serial): ''' Doesn't return all reasons by default. First match will return. TODO: make consistent with iOS isrooted, which returns all reasons discovered. ''' cmd = "{cli} -s {serial} shell 'command -v su'" s = catch_err(run_command(cmd, serial=shlex.quote(serial))) if not s or s == -1 or 'not found' in s or len(s) == 0 or ( s == "[android]: Error running ''. Error (1):"): print(config.error()) reason = "couldn't find 'su' tool on the phone." return (False, reason) else: reason = "found '{}' tool on the phone. Verify whether this is a su binary.".format( s.strip()) return (True, reason) installed_apps = self.installed_apps if not installed_apps: installed_apps = self.get_apps(serial) # FIXME: load these from a private database instead. from OWASP, # https://sushi2k.gitbooks.io/the-owasp-mobile-security-testing-guide/content/0x05j-Testing-Resiliency-Against-Reverse-Engineering.html root_pkgs = ['com.noshufou.android.su','com.thirdparty.superuser',\ 'eu.chainfire.supersu', 'com.koushikdutta.superuser',\ 'com.zachspong.temprootremovejb' ,'com.ramdroid.appquarantine'] root_pkgs_check = list(set(root_pkgs) & set(installed_apps)) if root_pkgs_check: reason = "found the following app(s) on the phone: '{}'."\ .format(str(root_pkgs_check)) return (True, reason)
def _dump_phone(self, serial): print('DUMPING iOS INFO...') # FIXME: pathlib migration at some point hmac_serial = config.hmac_serial(serial) cmd = "'{}/ios_dump.sh' {} {Apps} {Info} {Jailbroken-FS} {Jailbroken-SSH}"\ .format(config.SCRIPT_DIR, hmac_serial, **config.IOS_DUMPFILES) print(cmd) path = self.dump_path(serial, fkind='Dir') # dumped = catch_err(run_command(cmd)).strip() dumpf = os.path.join(path, config.IOS_DUMPFILES['Apps']) dumpfinfo = os.path.join(path, config.IOS_DUMPFILES['Info']) #dumped = catch_err(run_command(cmd)).strip() dumped = catch_err(run_command(cmd)).strip() print('iOS DUMP RESULTS for {}:'.format(hmac_serial)) print(dumped) if dumped == serial or True: print("Dumped the data into: {}".format(dumpf)) self.parse_dump = parse_dump.IosDump(dumpf, finfo=dumpfinfo) return True else: print( "Couldn't connect to the device. Trying to reconnect. This way." ) #connected, connected_reason = self.setup() #if not connected: # print(connected_reason) return False
def uninstall(self, serial, appid): cmd = '{cli} -s {serial} uninstall {appid!r}' s = catch_err(run_command(cmd, serial=shlex.quote(serial), appid=shlex.quote(appid)), cmd=cmd, msg="Could not uninstall") return s != -1
def uninstall(self, serial, appid): #cmd = '{cli} -i {serial} --uninstall_only --bundle_id {appid!r}' #cmd = 'ideviceinstaller --udid {} --uninstall {appid!r}'.format(serial, appid) cmd = '{}ideviceinstaller --uninstall {appid!r}'.format(self.cli) s = catch_err(run_command(cmd, appid=appid), cmd=cmd, msg="Could not uninstall") return s != -1
def device_info(self, serial): m = {} cmd = '{cli} -s {serial} shell getprop ro.product.brand' m['brand'] = run_command( cmd, serial=serial).stdout.read().decode('utf-8').title() cmd = '{cli} -s {serial} shell getprop ro.product.model' m['model'] = run_command(cmd, serial=serial).stdout.read().decode('utf-8') cmd = '{cli} -s {serial} shell getprop ro.build.version.release' m['version'] = run_command( cmd, serial=serial).stdout.read().decode('utf-8').strip() cmd = '{cli} -s {serial} shell dumpsys batterystats | grep -i "Start clock time:" | head -n1' runcmd = catch_err(run_command(cmd, serial=serial), cmd=cmd) #m['last_full_charge'] = datetime.strptime(runcmd.split(':')[1].strip(), '%Y-%m-%d-%H-%M-%S') m['last_full_charge'] = datetime.now() return "{brand} {model} (running Android {version})".format(**m), m
def _get_apps_(self, serialno, flag): cmd = "{cli} -s {serial} shell pm list packages {flag} | sed 's/^package://g' | sort" s = catch_err(run_command(cmd, serial=serialno, flag=flag), msg="App search failed", cmd=cmd) if not s: self.setup() return [] else: installed_apps = [x for x in s.splitlines() if x] return installed_apps
def get_apps(self, serialno): installed_apps = self._get_apps_(serialno, '-u') hmac_serial = config.hmac_serial(serialno) if installed_apps: q = run_command( 'bash scripts/android_scan.sh scan {ser} {hmac_serial}', ser=serialno, hmac_serial=hmac_serial, nowait=True) self.installed_apps = installed_apps return installed_apps
def setup(self, attempt_remount=False): ''' FIXME: iOS setup. ''' if config.PLATFORM == 'linux' and attempt_remount: # should show GUI prompt for password. sudo apt install policykit-1 if not there. cmd = "pkexec '" + config.SCRIPT_DIR + "/ios_mount_linux.sh' mount" #mountmsg = run_command(cmd).stderr.read().decode('utf-8') if catch_err(run_command(cmd)) == -1: return (False, "Couldn't detect device. See {}/ios_mount_linux.sh."\ .format(config.SCRIPT_DIR)) cmd = '{}idevicepair pair'.format(self.cli) pairmsg = run_command(cmd).stdout.read().decode('utf-8') if "No device found, is it plugged in?" in pairmsg: return (False, pairmsg) elif "Please enter the passcode on the device and retry." in pairmsg: return (False, "Please unlock your device and follow the trust dialog"\ " (you will need to enter your passcode). Then try to scan again.") elif "SUCCESS: Paired with device" in pairmsg: return (True, "Device successfully paired. Setup complete.") elif "said that the user denied the trust dialog." in pairmsg: return (False, "The trust dialog was denied. Please unplug the device"\ ", reconnect it, and scan again -- accept the trust dialog to proceed.") return (True, "Follow trust dialog on iOS device to continue.")
def devices(self): def _is_device(x): """Is it looks like a serial number""" return re.match(r'[a-f0-9]+', x) is not None #cmd = '{cli} --detect -t1 | tail -n 1' cmd = '{}idevice_id -l | tail -n 1'.format(self.cli) self.serialno = None s = catch_err(run_command(cmd), cmd=cmd, msg="") d = [ l.strip() for l in s.split('\n') if l.strip() and _is_device(l.strip()) ] print("Devices found:", d) return d
def devices(self): # FIXME: check for errors related to err in runcmd.py. #cmd = '{cli} devices | tail -n +2 | cut -f2' #runcmd = catch_err(run_command(cmd), cmd=cmd).strip() #cmd = '{cli} kill-server; {cli} start-server' #s = catch_err(run_command(cmd), time=30, msg="ADB connection failed", cmd=cmd) cmd = '{cli} devices | tail -n +2' runcmd = catch_err(run_command(cmd), cmd=cmd).strip().split('\n') conn_devices = [] for rc in runcmd: d = rc.split() if len(d) != 2: continue device, state = rc.split() device = device.strip() if state.strip() == 'device': conn_devices.append(device) return conn_devices
def recent_permissions_used(appid): df = pd.DataFrame( columns=[ 'appId', 'op', 'mode', 'timestamp', 'time_ago', 'duration']) cmd = '{cli} shell appops get {app}' recently_used = catch_err(run_command(cmd, app=appid)) if 'No operations.' in recently_used: return df record = {'appId': appid} now = datetime.datetime.now() print(recently_used) for permission in recently_used.split('\n')[:-1]: permission_attrs = permission.split(';') record['op'] = permission_attrs[0].split(':')[0] record['mode'] = permission_attrs[0].split(':')[1].strip() if len(permission_attrs) == 2: record['timestamp'] = ( now - _parse_time( permission_attrs[1].split('=')[1].strip())).strftime( config.DATE_STR) # TODO: keep time_ago? that leaks when the consultation was. record['time_ago'] = permission_attrs[1].split('=')[1].strip() else: record['timestamp'] = 'unknown (op)' record['time_ago'] = 'unknown (op)' record['duration'] = 'unknown (op)' df.loc[df.shape[0]] = record continue # NOTE: can convert this with timestamp + _parse_time('duration') if len(permission_attrs) == 3: record['duration'] = permission_attrs[2].split('=')[1].strip() else: record['duration'] = 'unspecified' df.loc[df.shape[0]] = record return df.sort_values(by=['time_ago']).reset_index(drop=True)
def recent_permissions_used(appid): cols = ['appId', 'op', 'mode', 'timestamp', 'time_ago', 'duration'] df = pd.DataFrame([], columns=cols) cmd = '{cli} shell appops get {app}' recently_used = catch_err(run_command(cmd, app=appid)) if 'No operations.' in recently_used: return df record = {'appId': appid} now = datetime.datetime.now() print(recently_used) for permission in recently_used.split('\n')[:-1]: permission_attrs = permission.split(';') t = permission_attrs[0].split(':') if len(t) != 2: # Could not parse continue record = {c: '' for c in cols} record['op'] = t[0].strip() record['mode'] = t[1].strip() if len(permission_attrs) == 2: tt = permission_attrs[1].split('=') if len(tt) != 2: continue record['timestamp'] = (now - _parse_time(tt[1].strip()))\ .strftime(config.DATE_STR) # TODO: keep time_ago? that leaks when the consultation was. record['time_ago'] = tt[1].strip() if len(permission_attrs) == 3: tt = permission_attrs[2].split('=') if len(tt) == 2: record['duration'] = tt[1].strip() df.loc[df.shape[0]] = record return df.sort_values(by=['time_ago']).reset_index(drop=True)
DEVICE_PRIMARY_USER = { 'me': 'Me', 'child': 'A child of mine', 'partner': 'My current partner/spouse', 'family_other': 'Another family member', 'other': 'Someone else' } ANDROID_PERMISSIONS_CSV = 'static_data/android_permissions.csv' IOS_DUMPFILES = {'Jailbroken': 'ios_jailbroken.log', 'Apps': 'ios_apps.plist', 'Info': 'ios_info.xml'} TEST_APP_LIST = 'static_data/android.test.apps_list' #TITLE = "Anti-IPS: Stop Intimate Partner Surveillance" VERSION_STABLE = catch_err(run_command( 'git describe --abbrev=0 --tags')).strip() VERSION_CURRENT = catch_err(run_command('git describe --tags')).strip() TITLE = {'title': "Mobile Device Privacy Scanner{}".format(" (test)" if TEST else ''), 'version_current': '', 'version_stable': VERSION_STABLE} APP_FLAGS_FILE = 'static_data/app-flags.csv' APP_INFO_FILE = 'static_data/app-info.csv' APP_INFO_SQLITE_FILE = 'sqlite:///static_data/app-info.db' + \ ("~test" if TEST else "") SQL_DB_PATH = 'sqlite:///data/fieldstudy.db' + ("~test" if TEST else "") def set_test_mode(test): global TEST, APP_FLAGS_FILE, SQL_DB_PATH TEST = test
def package_info(dumpf, appid): # FIXME: add check on all permissions, too. # need to get # requested permissions: # install permissions: # runtime permissions: cmd = "sed -n -e '/Package \[{appid}\]/,/Package \[/p' {dumpf}"\ .format(appid=appid, dumpf=dumpf.replace('.json', '.txt')) print(cmd) # TODO: Need to udpate it once the catch_err function is fixed. package_dump = run_command(cmd).stdout.read().decode() # cmd = '{cli} shell dumpsys usagestats {app} | grep "App Standby States:" -A 1'\ # .format(cli=config.ADB_PATH, app=appid) # now = datetime.datetime.now() #usage_stats = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)\ # .stdout.read().decode('utf-8')#.strip() ''' App Standby States: package=net.cybrook.trackview u=0 bucket=10 reason=u-mb used=+4m41s645ms usedScr=+2m19s198ms lastPred=+19d0h27m2s920ms activeLeft=+55m18s355ms wsLeft=-30d6h13m15s667ms lastJob=-24855d3h14m7s432ms idle=n totalElapsedTime=+305d6h7m59s376ms totalScreenOnTime=+67d8h56m19s585ms ''' ''' # switch to top method after tests package_dump = open(DUMPPKG, 'r').read() #print(package_dump) ''' try: sp = simpleparse(package_dump) except AttributeError as e: print(package_dump) return [] try: # FIXME: TypeError: list indices must be integers or slices, not str # FIXME: don't rely on rsonlite to parse correctly? Seems to miss the # Packages:. for now, using sed to filter out potential hazards in # parsing output. if isinstance(sp, list): sp = sp[0] _, pkg = sp.popitem() if isinstance(pkg, list): pkg = pkg[0] except IndexError as e: print(e) print('Didn\'t parse correctly. Not sure why.') return [], {} print("pkg={}".format(json.dumps(pkg, indent=2))) install_perms = [k.split(':')[0] for k, v in pkg.get('install permissions:', {}).items()] requested_perms = pkg.get('requested permissions:', []) #usage_stats = filter(None, usage_stats.split('\n')[1].split(' ')) #usage_stats = dict(item.split('=') for item in usage_stats) # print(usage_stats) pkg_info = {} pkg_info['firstInstallTime'] = pkg.get('firstInstallTime', '') pkg_info['lastUpdateTime'] = pkg.get('lastUpdateTime', '') pkg_info['versionCode'] = pkg.get('versionCode', '') pkg_info['versionName'] = pkg.get('versionName', '') #pkg_info['used'] = now - _parse_time(usage_stats['used']) #pkg_info['usedScr'] = now - _parse_time(usage_stats['usedScr']) #('User 0: installed', 'true hidden=false stopped=false notLaunched=false enabled=0\nlastDisabledCaller: com.android.vending\ngids=[3003]\nruntime permissions:') #inst_det_key = [v for k,v in pkg.items() if 'User 0:' in k][0] #install_details = dict(item.split('=') for item in inst_det_key.strip().split(' ')[1:]) #install_details = {k:bool(strtobool(install_details[k])) for k in install_details} # print(install_details) all_perms = list(set(requested_perms) | set(install_perms)) return all_perms, pkg_info
def devices_info(self): cmd = '{cli} devices -l' return run_command(cmd).stdout.read().decode('utf-8')