Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add macos support #8

Merged
merged 1 commit into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
- [Description](#description)
- [Installation](#installation)
- [Profile method for macOS host](#profile-method-for-macos-host)
* [Howto](#howto)
- [Profile method for non-jailbroken devices](#profile-method-for-non-jailbroken-devices)
* [Howto](#howto-1)
- [Secret preference method for jailbroken devices](#secret-preference-method-for-jailbroken-devices)
* [Howto](#howto-2)
- [Enable HTTP instrumentation method](#enable-http-instrumentation-method)

# Description

Simple pure python utility for sniffing HTTP/HTTPS decrypted traffic recorded by one of Apple's not-so-well documented
Expand All @@ -6,9 +16,26 @@ APIs.
# Installation

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

# Profile method for macOS host

This method applies to Apple's CFNetwork profile. This profile is meant for debugging processes using the CFNetwork
framework. **This method doesn't include the request/response body.**

## Howto

- Download Apple's CFNetwork profile which can be found here:
https://developer.apple.com/services-account/download?path=/iOS/iOS_Logs/NetworkDiagnostic.mobileconfig

- Install it using double-click

- That's it! :) You can now just start sniffing out everything using:
```shell
python3 -m harlogger profile
```

# Profile method for non-jailbroken devices

This method applies to Apple's CFNetwork profile. This profile is meant for debugging processes using the CFNetwork
Expand All @@ -23,15 +50,15 @@ framework. **This method doesn't include the request/response body.**

```shell
# if you don't already have it
python3 -m pip install -U --user pymobiledevice3
python3 -m pip install -U pymobiledevice3

# install the profile
pymobiledevice3 profile install CFNetworkDiagnostics.mobileconfig
```

- That's it! :) You can now just start sniffing out everything using:
```shell
python3 -m harlogger profile
python3 -m harlogger mobile profile
```

Output should look like:
Expand Down Expand Up @@ -78,7 +105,7 @@ for `com.apple.CFNetwork` and trigger the `com.apple.CFNetwork.har-capture-updat
Output should look like:

```
➜ harlogger git:(master) ✗ python3 -m harlogger preference
➜ harlogger git:(master) ✗ python3 -m harlogger mobile preference
➡️ CFNetwork(1140) POST https://www.bing.com/fd/ls/lsp.aspx
POST /fd/ls/lsp.aspx HTTP/2.0
Accept: */*
Expand Down
44 changes: 35 additions & 9 deletions harlogger/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
from pymobiledevice3.cli.cli_common import Command
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider

from harlogger.sniffers import Filters, SnifferPreference, SnifferProfile
from harlogger.sniffers import Filters, HostSnifferProfile, MobileSnifferProfile, SnifferPreference


@click.group()
def cli():
pass


@cli.command('profile', cls=Command)
@cli.group()
def mobile():
""" Mobile sniffing options """
pass


@mobile.command('profile', cls=Command)
@click.option('pids', '-p', '--pid', type=click.INT, multiple=True, help='filter pid list')
@click.option('--color/--no-color', default=True)
@click.option('process_names', '-pn', '--process-name', multiple=True, help='filter process name list')
Expand All @@ -19,19 +25,19 @@ def cli():
@click.option('--response/--no-response', is_flag=True, default=True, help='show responses')
@click.option('-u', '--unique', is_flag=True, help='show only unique requests per image/pid/method/uri combination')
@click.option('--black-list/--white-list', default=True, is_flag=True)
def cli_profile(service_provider: LockdownServiceProvider, pids, process_names, color, request, response, images,
unique, black_list):
def mobile_profile(service_provider: LockdownServiceProvider, pids, process_names, color, request, response, images,
unique, black_list):
"""
Sniff using CFNetworkDiagnostics.mobileconfig profile.

This requires the specific Apple profile to be installed for the sniff to work.
"""
filters = Filters(pids, process_names, images, black_list)
SnifferProfile(service_provider, filters=filters, request=request, response=response, color=color,
unique=unique).sniff()
MobileSnifferProfile(service_provider, filters=filters, request=request, response=response, color=color,
unique=unique).sniff()


@cli.command('preference', cls=Command)
@mobile.command('preference', cls=Command)
@click.option('-o', '--out', type=click.File('w'), help='file to store the har entries into upon exit (ctrl+c)')
@click.option('pids', '-p', '--pid', type=click.INT, multiple=True, help='filter pid list')
@click.option('--color/--no-color', default=True)
Expand All @@ -41,8 +47,8 @@ def cli_profile(service_provider: LockdownServiceProvider, pids, process_names,
@click.option('--response/--no-response', is_flag=True, default=True, help='show responses')
@click.option('-u', '--unique', is_flag=True, help='show only unique requests per image/pid/method/uri combination')
@click.option('--black-list/--white-list', default=True, is_flag=True)
def cli_preference(service_provider: LockdownServiceProvider, out, pids, process_names, images, request, response,
color, unique, black_list):
def mobile_preference(service_provider: LockdownServiceProvider, out, pids, process_names, images, request, response,
color, unique, black_list):
"""
Sniff using the secret com.apple.CFNetwork.plist configuration.

Expand All @@ -54,5 +60,25 @@ def cli_preference(service_provider: LockdownServiceProvider, out, pids, process
unique=unique).sniff()


@cli.command('profile')
@click.option('pids', '-p', '--pid', type=click.INT, multiple=True, help='filter pid list')
@click.option('--color/--no-color', default=True)
@click.option('process_names', '-pn', '--process-name', multiple=True, help='filter process name list')
@click.option('images', '-i', '--image', multiple=True, help='filter image list')
@click.option('--request/--no-request', is_flag=True, default=True, help='show requests')
@click.option('--response/--no-response', is_flag=True, default=True, help='show responses')
@click.option('-u', '--unique', is_flag=True, help='show only unique requests per image/pid/method/uri combination')
@click.option('--black-list/--white-list', default=True, is_flag=True)
def host_profile(pids, process_names, color, request, response, images, unique, black_list):
"""
Sniff using CFNetworkDiagnostics.mobileconfig profile.

This requires the specific Apple profile to be installed for the sniff to work.
"""
filters = Filters(pids, process_names, images, black_list)
HostSnifferProfile(filters=filters, request=request, response=response, color=color,
unique=unique).sniff()


if __name__ == '__main__':
cli()
7 changes: 6 additions & 1 deletion harlogger/http_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ def parse_transaction(message: str) -> 'HTTPTransaction':
parsed_transaction = HTTPTransaction._parse_fields(message=message)

if 'Protocol Enqueue' in parsed_transaction:
method, url, http_version = parsed_transaction.pop('Protocol Enqueue').split()[1:]
info = parsed_transaction.pop('Protocol Enqueue').split()[1:]
if len(info) == 2:
method, url = info
http_version = 'unknown'
else:
method, url, http_version = info
parsed_transaction.pop('Message')
parsed_transaction.pop('Request')
res = HTTPRequest(url, method, http_version, parsed_transaction)
Expand Down
96 changes: 65 additions & 31 deletions harlogger/sniffers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import posixpath
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import IO, Tuple
from typing import IO, Optional, Tuple

from haralyzer import HarEntry
from maclog.log import get_logger
from pygments import highlight
from pygments.formatters.terminal256 import TerminalTrueColorFormatter
from pygments.lexers.textfmts import HttpLexer
Expand Down Expand Up @@ -36,18 +37,16 @@ class Filters:

def should_keep(self, entry_hash: EntryHash) -> bool:
""" Filter out entry if one of the criteria specified (pid,image,process_name) """
in_filters = self.pids is not None and entry_hash.pid in self.pids or \
self.process_names is not None and entry_hash.process_name in self.process_names or \
self.images is not None and entry_hash.image in self.images
in_filters = (self.pids is not None and entry_hash.pid in self.pids or
self.process_names is not None and entry_hash.process_name in self.process_names or
self.images is not None and entry_hash.image in self.images)

return self.black_list and not in_filters or not self.black_list and in_filters


class SnifferBase(ABC):
def __init__(self, lockdown: LockdownClient, filters: Filters = None, unique: bool = False, request: bool = True,
def __init__(self, filters: Filters = None, unique: bool = False, request: bool = True,
response: bool = True, color: bool = True, style: str = 'autumn'):
self._lockdown = lockdown
self._os_trace_service = OsTraceService(self._lockdown)
self._filters = filters
self._request = request
self._response = response
Expand All @@ -74,6 +73,14 @@ def sniff(self) -> None:
pass


class MobileSnifferBase(SnifferBase, ABC):
def __init__(self, lockdown: LockdownClient, filters: Filters = None, unique: bool = False, request: bool = True,
response: bool = True, color: bool = True, style: str = 'autumn'):
super().__init__(filters, unique, request, response, color, style)
self._lockdown = lockdown
self._os_trace_service = OsTraceService(self._lockdown)


class SnifferPreference(SnifferBase):
"""
Sniff using the secret com.apple.CFNetwork.plist configuration.
Expand Down Expand Up @@ -140,7 +147,37 @@ def _sniff(self) -> None:
continue


class SnifferProfile(SnifferBase):
class SnifferProfileBase(SnifferBase, ABC):
def _handle_entry(self, pid: int, message: str, filename: str, image_name: str, subsystem: Optional[str] = None,
category: Optional[str] = None):
if subsystem != 'com.apple.CFNetwork' or category != 'Diagnostics':
return

if 'Protocol Received' not in message and 'Protocol Enqueue' not in message:
return

lines = message.split('\n')
if len(lines) < 2:
return

http_transaction = HTTPTransaction.parse_transaction(message)
if not http_transaction:
raise HTTPParseError()
entry_hash = EntryHash(pid,
posixpath.basename(filename),
os.path.basename(image_name),
http_transaction.url)

if not self._filters.should_keep(entry_hash):
return

if self._request and isinstance(http_transaction, HTTPRequest):
self.show(entry_hash, http_transaction.formatted, '➡️')
if self._response and isinstance(http_transaction, HTTPResponse):
self.show(entry_hash, http_transaction.formatted, '⬅️', f'({http_transaction.url})')


class MobileSnifferProfile(MobileSnifferBase, SnifferProfileBase):
"""
Sniff using CFNetworkDiagnostics.mobileconfig profile.

Expand All @@ -151,32 +188,29 @@ def __init__(self, lockdown: LockdownClient, filters: Filters = None, unique: bo
response: bool = True, color: bool = True, style: str = 'autumn'):
super().__init__(lockdown, filters, unique, request, response, color, style)

def sniff(self):
def sniff(self) -> None:
for entry in self._os_trace_service.syslog():
if entry.label is None or entry.label.subsystem != 'com.apple.CFNetwork' or \
entry.label.category != 'Diagnostics':
continue
subsystem = None
category = None
if entry.label is not None:
subsystem = entry.label.subsystem
category = entry.label.category
self._handle_entry(entry.pid, entry.message, entry.filename, entry.image_name, subsystem=subsystem,
category=category)

if 'Protocol Received' not in entry.message and \
'Protocol Enqueue' not in entry.message:
continue

lines = entry.message.split('\n')
if len(lines) < 2:
continue
class HostSnifferProfile(SnifferProfileBase):
"""
Sniff using CFNetworkDiagnostics.mobileconfig profile.

http_transaction = HTTPTransaction.parse_transaction(entry.message)
if not http_transaction:
raise HTTPParseError()
entry_hash = EntryHash(entry.pid,
posixpath.basename(entry.filename),
os.path.basename(entry.image_name),
http_transaction.url)
This requires the specific Apple profile to be installed for the sniff to work.
"""

if not self._filters.should_keep(entry_hash):
continue
def __init__(self, filters: Filters = None, unique: bool = False, request: bool = True,
response: bool = True, color: bool = True, style: str = 'autumn'):
super().__init__(filters, unique, request, response, color, style)

if self._request and isinstance(http_transaction, HTTPRequest):
self.show(entry_hash, http_transaction.formatted, '➡️')
if self._response and isinstance(http_transaction, HTTPResponse):
self.show(entry_hash, http_transaction.formatted, '⬅️', f'({http_transaction.url})')
def sniff(self) -> None:
for entry in get_logger():
self._handle_entry(entry.process_id, entry.event_message, entry.process_image_path, entry.sender_image_path,
subsystem=entry.subsystem, category=entry.category)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = "Simple utlity for sniffing decrypted HTTP/HTTPS traffic on an iOS
readme = "README.md"
requires-python = ">=3.8"
license = { file = "LICENSE" }
keywords = ["ios", "http", "https", "har", "sniffer", "jailbroken"]
keywords = ["ios", "osx", "mac", "macos", "http", "https", "har", "sniffer", "jailbroken"]
authors = [
{ name = "doronz88", email = "[email protected]" },
{ name = "netanelc305", email = "[email protected]" }
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
click
pymobiledevice3>=2.0.2
pygments
haralyzer>=2.4.0
haralyzer>=2.4.0
maclog