Skip to content

Commit

Permalink
Merge pull request #3 from doronz88/feature/macos
Browse files Browse the repository at this point in the history
add macos support
  • Loading branch information
doronz88 authored Sep 10, 2023
2 parents 9c70e2b + 4a1be91 commit dd16592
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
python-version: [3.8, 3.9, "3.10", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
# Description

Simple utility to search for interesting preferences in iDevices.
Simple utility to search for interesting preferences in macOS and connected iDevices.

# Installation

```shell
python3 -m pip install -U --user cfprefsmon
python3 -m pip install -U cfprefsmon
```

# Usage

```
Usage: cfprefsmon [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
host Sniff on macOS host
mobile Sniff on connected iOS device
```

# Example
Expand All @@ -14,7 +27,7 @@ In this example, where the value for each preference is `None`, this is probably
on a jailbroken device.

```
➜ cfprefmon git:(master) ✗ cfprefsmon
➜ cfprefmon git:(master) ✗ cfprefsmon mobile
CFPreference[com.apple.springboard][kCFPreferencesAnyUser][SBDisableHomeButton] = 0 # Process: /System/Library/CoreServices/SpringBoard.app/SpringBoard
CFPreference[com.apple.springboard][kCFPreferencesAnyUser][SBStoreDemoAppLock] = 0 # Process: /System/Library/CoreServices/SpringBoard.app/SpringBoard
CFPreference[com.apple.springboard][kCFPreferencesAnyUser][ThermalLockoutEnabledBrickMode] = 0 # Process: /System/Library/CoreServices/SpringBoard.app/SpringBoard
Expand Down
119 changes: 71 additions & 48 deletions cfprefsmon/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from typing import Optional

import click
from pymobiledevice3.lockdown import create_using_usbmux
from maclog.log import get_logger
from pymobiledevice3.cli.cli_common import LockdownCommand
from pymobiledevice3.lockdown import LockdownClient
from pymobiledevice3.services.os_trace import OsTraceService

FORMAT = 'CFPreference[{domain}][{user}][{key}] = {value} # Process: {procname}'
Expand All @@ -13,71 +17,90 @@

DEFAULT_USER = 'kCFPreferencesAnyUser'

PREFS = {}

@click.command()
@click.option('--udid')
@click.option('--unique', is_flag=True, help='output only unique entries')
@click.option('--color/--no-color', default=True, help='make colored output')
@click.option('--undefined', is_flag=True, help='filter only non-existing keys')
def cli(udid, unique, color, undefined):
lockdown = create_using_usbmux(serial=udid)
prefs = {}
for entry in OsTraceService(lockdown).syslog():
if entry.label is None:
continue

if entry.label.subsystem != 'com.apple.defaults' or entry.label.category != 'User Defaults':
continue
def print_entry(message: str, filename: str, subsystem: Optional[str] = None, category: Optional[str] = None,
unique: bool = False, color: bool = False, undefined: bool = False) -> None:
if subsystem != 'com.apple.defaults' or category != 'User Defaults':
return

message = entry.message
if 'cfprefs' not in message.lower():
return

if 'cfprefs' not in message.lower():
continue
if not message.startswith(HAS_VALUE_PREFIX) and not message.startswith(NO_VALUE_PREFIX):
return

if not message.startswith(HAS_VALUE_PREFIX) and not message.startswith(NO_VALUE_PREFIX):
continue
# print(message)
user = DEFAULT_USER
value = None

# print(message)
key = message.split(FOR_KEY_PREFIX, 1)[1].split(FOR_KEY_SUFFIX, 1)[0].strip()
domain = message.rsplit(DOMAIN_PREFIX, 1)[1].split(',', 1)[0].strip()
procname = filename
has_value = False

if USER_PREFIX in message:
user = message.rsplit(USER_PREFIX, 1)[1].split(',', 1)[0].strip()

if not user:
user = DEFAULT_USER
value = None

key = message.split(FOR_KEY_PREFIX, 1)[1].split(FOR_KEY_SUFFIX, 1)[0].strip()
domain = message.rsplit(DOMAIN_PREFIX, 1)[1].split(',', 1)[0].strip()
procname = entry.filename
has_value = False
if message.startswith(HAS_VALUE_PREFIX):
value = message.split(HAS_VALUE_PREFIX, 1)[1].split(FOR_KEY_PREFIX, 1)[0]
has_value = True

if USER_PREFIX in message:
user = message.rsplit(USER_PREFIX, 1)[1].split(',', 1)[0].strip()
if domain not in PREFS:
PREFS[domain] = {}

if not user:
user = DEFAULT_USER
if user not in PREFS[domain]:
PREFS[domain][user] = []

if message.startswith(HAS_VALUE_PREFIX):
value = message.split(HAS_VALUE_PREFIX, 1)[1].split(FOR_KEY_PREFIX, 1)[0]
has_value = True
if unique and key in PREFS[domain][user]:
return

if domain not in prefs:
prefs[domain] = {}
PREFS[domain][user].append(key)

if user not in prefs[domain]:
prefs[domain][user] = []
if color:
domain = click.style(domain, fg='yellow')
user = click.style(user, fg='bright_green')
key = click.style(key, fg='green')
procname = click.style(procname, fg='magenta')

if unique and key in prefs[domain][user]:
continue
if not has_value:
value = click.style(value, fg='red')

prefs[domain][user].append(key)
if (not undefined) or (undefined and not has_value):
print(FORMAT.format(domain=domain, user=user, key=key, value=value, procname=procname))

if color:
domain = click.style(domain, fg='yellow')
user = click.style(user, fg='bright_green')
key = click.style(key, fg='green')
procname = click.style(procname, fg='magenta')

if not has_value:
value = click.style(value, fg='red')
@click.group()
def cli():
pass

if (not undefined) or (undefined and not has_value):
print(FORMAT.format(domain=domain, user=user, key=key, value=value, procname=procname))

@cli.command()
@click.option('--unique', is_flag=True, help='output only unique entries')
@click.option('--color/--no-color', default=True, help='make colored output')
@click.option('--undefined', is_flag=True, help='filter only non-existing keys')
def host(unique, color, undefined):
""" Sniff on macOS host """
for entry in get_logger():
print_entry(entry.event_message, entry.process_image_path, entry.subsystem, entry.category, unique=unique,
color=color, undefined=undefined)


@cli.command(cls=LockdownCommand)
@click.option('--unique', is_flag=True, help='output only unique entries')
@click.option('--color/--no-color', default=True, help='make colored output')
@click.option('--undefined', is_flag=True, help='filter only non-existing keys')
def mobile(service_provider: LockdownClient, unique, color, undefined):
""" Sniff on connected iOS device """
for entry in OsTraceService(service_provider).syslog():
if entry.label is None:
continue
print_entry(entry.message, entry.filename, entry.label.subsystem, entry.label.category, unique=unique,
color=color, undefined=undefined)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "cfprefsmon"
version = "0.0.4"
description = "Search for interesting internal preferences inside a connected iDevice"
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.8"
license = { file = "LICENSE" }
keywords = ["ios", "cli", "preferences", "cfprefsd", "monitor"]
authors = [
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pymobiledevice3>=2.0.0
maclog
click

0 comments on commit dd16592

Please sign in to comment.