Skip to content

Commit

Permalink
rpcclient: add darwin_media
Browse files Browse the repository at this point in the history
  • Loading branch information
doronz88 committed Feb 10, 2022
1 parent c581c86 commit d0a18a0
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 39 deletions.
50 changes: 47 additions & 3 deletions src/rpcclient/rpcclient/client/darwin_client.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import struct
import typing

from cached_property import cached_property

from rpcclient.client.client import Client
from rpcclient.darwin_fs import DarwinFs
from rpcclient.darwin_media import DarwinMedia
from rpcclient.darwin_processes import DarwinProcesses
from rpcclient.exceptions import RpcClientException
from rpcclient.preferences import Preferences
from rpcclient.structs.darwin import utsname
from rpcclient.structs.darwin_consts import kCFNumberSInt64Type, kCFNumberDoubleType
from rpcclient.symbol import DarwinSymbol


Expand All @@ -16,7 +20,9 @@ def __init__(self, sock, sysname: str, hostname: str, port: int = None):
super().__init__(sock, sysname, hostname, port)
self._dlsym_global_handle = -2 # RTLD_GLOBAL

self.dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", 2)
if 0 == self.dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", 2):
raise RpcClientException('failed to load CoreFoundation')

self._cf_types = {
self.symbols.CFDateGetTypeID(): 'date',
self.symbols.CFDataGetTypeID(): 'data',
Expand All @@ -33,6 +39,7 @@ def __init__(self, sock, sysname: str, hostname: str, port: int = None):
self.fs = DarwinFs(self)
self.prefs = Preferences(self)
self.processes = DarwinProcesses(self)
self.media = DarwinMedia(self)

@property
def modules(self) -> typing.List[str]:
Expand All @@ -55,5 +62,42 @@ def symbol(self, symbol: int):
""" at a symbol object from a given address """
return DarwinSymbol.create(symbol, self)

def cfstr(self, s: str) -> DarwinSymbol:
return self.symbols.CFStringCreateWithCString(0, s, 0)
def cf(self, o: object):
if isinstance(o, DarwinSymbol):
# assuming it's already a cfobject
return o
elif isinstance(o, str):
return self.symbols.CFStringCreateWithCString(0, o, 0)
elif isinstance(o, bytes):
return self.symbols.CFDataCreate(0, o, len(o))
elif isinstance(o, bool):
if o:
return self.symbols.kCFBooleanTrue[0]
else:
return self.symbols.kCFBooleanFalse[0]
elif isinstance(o, int):
with self.safe_malloc(8) as buf:
buf[0] = o
return self.symbols.CFNumberCreate(0, kCFNumberSInt64Type, buf)
elif isinstance(o, float):
with self.safe_malloc(8) as buf:
buf.poke(struct.pack('<d', o))
return self.symbols.CFNumberCreate(0, kCFNumberDoubleType, buf)
elif isinstance(o, list) or isinstance(o, tuple):
cfvalues = [self.cf(i) for i in o]
with self.safe_malloc(8 * len(cfvalues)) as buf:
for i in range(len(cfvalues)):
buf[i] = cfvalues[i]
return self.symbols.CFArrayCreate(0, buf, len(cfvalues), 0)
elif isinstance(o, dict):
cfkeys = [self.cf(i) for i in o.keys()]
cfvalues = [self.cf(i) for i in o.values()]
with self.safe_malloc(8 * len(cfkeys)) as keys_buf:
with self.safe_malloc(8 * len(cfvalues)) as values_buf:
for i in range(len(cfkeys)):
keys_buf[i] = cfkeys[i]
for i in range(len(cfvalues)):
values_buf[i] = cfvalues[i]
return self.symbols.CFDictionaryCreate(0, keys_buf, values_buf, len(cfvalues), 0, 0, 0)
else:
raise NotImplementedError()
124 changes: 124 additions & 0 deletions src/rpcclient/rpcclient/darwin_media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import struct

from rpcclient.common import path_to_str
from rpcclient.exceptions import RpcClientException, BadReturnValueError
from rpcclient.symbol import DarwinSymbol


class Recorder:
def __init__(self, client, recorder: DarwinSymbol):
self._client = client
self._recorder = recorder

def release(self):
self._recorder.objc_call('release')

def record(self):
self._recorder.objc_call('record')

def pause(self):
self._recorder.objc_call('pause')

def stop(self):
self._recorder.objc_call('stop')

def delete_recording(self):
if not self._recorder.objc_call('deleteRecording'):
raise BadReturnValueError('deleteRecording failed')

@property
def recording(self) -> bool:
return bool(self._recorder.objc_call('isRecording'))

def __del__(self):
try:
self.release()
except Exception: # noqa: E722
# Best effort.
pass


class Player:
def __init__(self, client, player):
self._client = client
self._player = player

def release(self):
self._player.objc_call('release')

def play(self):
self._player.objc_call('play')

def pause(self):
self._player.objc_call('pause')

def stop(self):
self._player.objc_call('stop')

def set_volume(self, value: float):
self._player.objc_call('setVolume:', struct.pack('<f', value))

@property
def playing(self) -> bool:
return bool(self._player.objc_call('isPlaying'))

@property
def loops(self) -> int:
return self._player.objc_call('numberOfLoops')

@loops.setter
def loops(self, value: int):
self._player.objc_call('setNumberOfLoops:', value)

def __del__(self):
try:
self.release()
except Exception: # noqa: E722
# Best effort.
pass


class DarwinMedia:
def __init__(self, client):
"""
:param rpcclient.client.darwin_client.DarwinClient client:
"""
self._client = client

if 0 == client.dlopen('/System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio', 2):
raise RpcClientException('failed to load AVFAudio')

def set_audio_session(self):
AVAudioSession = self._client.symbols.objc_getClass('AVAudioSession')
audio_session = AVAudioSession.objc_call('sharedInstance')
audio_session.objc_call('setCategory:error:', self._client.symbols.AVAudioSessionCategoryPlayAndRecord[0], 0)

@path_to_str('filename')
def get_recorder(self, filename: str) -> Recorder:
NSURL = self._client.symbols.objc_getClass('NSURL')
url = NSURL.objc_call('fileURLWithPath:', self._client.cf(filename))
settings = self._client.cf({
self._client.symbols.AVEncoderAudioQualityKey: 100,
self._client.symbols.AVEncoderBitRateKey: 16,
self._client.symbols.AVNumberOfChannelsKey: 1,
self._client.symbols.AVSampleRateKey: 8000.0,
})

self.set_audio_session()

AVAudioRecorder = self._client.symbols.objc_getClass('AVAudioRecorder')
recorder = AVAudioRecorder.objc_call('alloc').objc_call('initWithURL:settings:error:', url, settings, 0)

return Recorder(self._client, recorder)

@path_to_str('filename')
def get_player(self, filename: str) -> Player:
NSURL = self._client.symbols.objc_getClass('NSURL')
url = NSURL.objc_call('fileURLWithPath:', self._client.cf(filename))

self.set_audio_session()

AVAudioPlayer = self._client.symbols.objc_getClass('AVAudioPlayer')
player = AVAudioPlayer.objc_call('alloc').objc_call('initWithContentsOfURL:error:', url, 0)

return Player(self._client, player)
24 changes: 12 additions & 12 deletions src/rpcclient/rpcclient/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ def __init__(self, client):

def copy_key_list(self, application_id: str, username: str = kCFPreferencesCurrentUser,
hostname: str = kCFPreferencesCurrentHost) -> typing.Optional[typing.List[str]]:
application_id = self._client.cfstr(application_id)
username = self._client.cfstr(username)
hostname = self._client.cfstr(hostname)
application_id = self._client.cf(application_id)
username = self._client.cf(username)
hostname = self._client.cf(hostname)
return self._client.symbols.CFPreferencesCopyKeyList(application_id, username, hostname).py

def copy_value(self, key: str, application_id: str, username: str = kCFPreferencesCurrentUser,
hostname: str = kCFPreferencesCurrentHost) -> typing.Optional[str]:
key = self._client.cfstr(key)
application_id = self._client.cfstr(application_id)
username = self._client.cfstr(username)
hostname = self._client.cfstr(hostname)
key = self._client.cf(key)
application_id = self._client.cf(application_id)
username = self._client.cf(username)
hostname = self._client.cf(hostname)
return self._client.symbols.CFPreferencesCopyValue(key, application_id, username, hostname).py

def copy_all_values(self, application_id: str, username: str = kCFPreferencesCurrentUser,
Expand All @@ -37,9 +37,9 @@ def copy_all_values(self, application_id: str, username: str = kCFPreferencesCur

def set_value(self, key: str, value: str, application_id: str, username: str = kCFPreferencesCurrentUser,
hostname: str = kCFPreferencesCurrentHost):
key = self._client.cfstr(key)
value = self._client.cfstr(value)
application_id = self._client.cfstr(application_id)
username = self._client.cfstr(username)
hostname = self._client.cfstr(hostname)
key = self._client.cf(key)
value = self._client.cf(value)
application_id = self._client.cf(application_id)
username = self._client.cf(username)
hostname = self._client.cf(hostname)
self._client.symbols.CFPreferencesSetValue(key, value, application_id, username, hostname)
20 changes: 20 additions & 0 deletions src/rpcclient/rpcclient/structs/darwin_consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Types from MacTypes.h

# Basic C types
kCFNumberSInt8Type = 1
kCFNumberSInt16Type = 2
kCFNumberSInt32Type = 3
kCFNumberSInt64Type = 4
kCFNumberFloat32Type = 5
kCFNumberFloat64Type = 6 # 64-bit IEEE 754

# Other
kCFNumberCharType = 7
kCFNumberShortType = 8
kCFNumberIntType = 9
kCFNumberLongType = 10
kCFNumberLongLongType = 11
kCFNumberFloatType = 12
kCFNumberDoubleType = 13
kCFNumberCFIndexType = 14
kCFNumberMaxType = 14
29 changes: 5 additions & 24 deletions src/rpcclient/rpcclient/symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from construct import FormatField

from rpcclient.exceptions import CfSerializationError
from rpcclient.structs.darwin_consts import kCFNumberSInt64Type, kCFNumberDoubleType

ADDRESS_SIZE_TO_STRUCT_FORMAT = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}
RETVAL_BIT_COUNT = 64
Expand Down Expand Up @@ -112,17 +113,17 @@ def tell(self):
return self + self._offset

@property
def c_int64(self):
def c_int64(self) -> int:
""" cast to c_int64 """
return ctypes.c_int64(self).value

@property
def c_int32(self):
def c_int32(self) -> int:
""" cast to c_int32 """
return ctypes.c_int32(self).value

@property
def c_int16(self):
def c_int16(self) -> int:
""" cast to c_int16 """
return ctypes.c_int16(self).value

Expand Down Expand Up @@ -195,26 +196,6 @@ def __call__(self, *args, **kwargs):
return self._client.call(self, args)


# Types from MacTypes.h
kCFNumberSInt8Type = 1
kCFNumberSInt16Type = 2
kCFNumberSInt32Type = 3
kCFNumberSInt64Type = 4
kCFNumberFloat32Type = 5
kCFNumberFloat64Type = 6 # 64-bit IEEE 754
# Basic C types
kCFNumberCharType = 7
kCFNumberShortType = 8
kCFNumberIntType = 9
kCFNumberLongType = 10
kCFNumberLongLongType = 11
kCFNumberFloatType = 12
kCFNumberDoubleType = 13
# Other
kCFNumberCFIndexType = 14
kCFNumberMaxType = 14


class DarwinSymbol(Symbol):
def objc_call(self, selector, *params):
""" call an objc method on a given object """
Expand All @@ -239,7 +220,7 @@ def py(self):
if t == 'str':
return self._client.symbols.CFStringGetCStringPtr(self, 0).peek_str()
if t == 'bool':
return bool(self._client.symbols.CFCFBooleanGetValueGetValue(self, 0))
return bool(self._client.symbols.CFBooleanGetValue(self, 0))
if t == 'number':
with self._client.safe_malloc(200) as buf:
if self._client.symbols.CFNumberIsFloatType(self):
Expand Down

0 comments on commit d0a18a0

Please sign in to comment.