diff --git a/src/rpcclient/rpcclient/darwin/client.py b/src/rpcclient/rpcclient/darwin/client.py
index 731a2ab8..41398413 100644
--- a/src/rpcclient/rpcclient/darwin/client.py
+++ b/src/rpcclient/rpcclient/darwin/client.py
@@ -15,6 +15,7 @@
from rpcclient.darwin.fs import DarwinFs
from rpcclient.darwin.hid import Hid
from rpcclient.darwin.ioregistry import IORegistry
+from rpcclient.darwin.keychain import Keychain
from rpcclient.darwin.location import Location
from rpcclient.darwin.media import DarwinMedia
from rpcclient.darwin.network import DarwinNetwork
@@ -69,6 +70,7 @@ def _init_process_specific(self):
self.lief = DarwinLief(self)
self.bluetooth = Bluetooth(self)
self.core_graphics = CoreGraphics(self)
+ self.keychain = Keychain(self)
self.type_decoders = {
self.symbols.CFNullGetTypeID(): self._decode_cfnull,
self.symbols.CFStringGetTypeID(): self._decode_cfstr,
diff --git a/src/rpcclient/rpcclient/darwin/keychain.py b/src/rpcclient/rpcclient/darwin/keychain.py
new file mode 100644
index 00000000..05fd2de8
--- /dev/null
+++ b/src/rpcclient/rpcclient/darwin/keychain.py
@@ -0,0 +1,65 @@
+import logging
+from typing import Mapping, List
+
+from rpcclient.exceptions import BadReturnValueError
+from rpcclient.symbol import Symbol
+
+logger = logging.getLogger(__name__)
+
+
+class Keychain:
+ """ keychain utils """
+
+ def __init__(self, client):
+ self._client = client
+
+ def add_internet_password(self, account: str, server: str, password: str):
+ attributes = self._client.symbols.objc_getClass('NSMutableDictionary').objc_call('new')
+ attributes.objc_call('setObject:forKey:', self._client.symbols.kSecClassInternetPassword[0],
+ self._client.symbols.kSecClass[0])
+ attributes.objc_call('setObject:forKey:', self._client.cf(account), self._client.symbols.kSecAttrAccount[0])
+ attributes.objc_call('setObject:forKey:', self._client.cf(server), self._client.symbols.kSecAttrServer[0])
+ attributes.objc_call('setObject:forKey:', self._client.cf(password), self._client.symbols.kSecValueData[0])
+ err = self._client.symbols.SecItemAdd(attributes, 0).c_int32
+ if err != 0:
+ raise BadReturnValueError(f'SecItemAdd() returned: {err}')
+
+ def query_apple_share_passwords(self) -> List[Mapping]:
+ return self._query(self._client.symbols.kSecClassAppleSharePassword)
+
+ def query_internet_passwords(self) -> List[Mapping]:
+ return self._query(self._client.symbols.kSecClassInternetPassword)
+
+ def query_generic_passwords(self) -> List[Mapping]:
+ return self._query(self._client.symbols.kSecClassGenericPassword)
+
+ def query_identities(self) -> List[Mapping]:
+ return self._query(self._client.symbols.kSecClassIdentity)
+
+ def query_certificates(self) -> List[Mapping]:
+ return self._query(self._client.symbols.kSecClassCertificate)
+
+ def query_keys(self) -> List[Mapping]:
+ return self._query(self._client.symbols.kSecClassKey)
+
+ def _query(self, class_type: Symbol) -> List[Mapping]:
+ with self._client.safe_malloc(8) as p_result:
+ p_result[0] = 0
+
+ query = self._client.symbols.objc_getClass('NSMutableDictionary').objc_call('new')
+ query.objc_call('setObject:forKey:', class_type[0],
+ self._client.symbols.kSecClass[0])
+ query.objc_call('setObject:forKey:', self._client.symbols.kSecMatchLimitAll[0],
+ self._client.symbols.kSecMatchLimit[0])
+ query.objc_call('setObject:forKey:', self._client.symbols.kCFBooleanTrue[0],
+ self._client.symbols.kSecReturnAttributes[0])
+ query.objc_call('setObject:forKey:', self._client.symbols.kCFBooleanTrue[0],
+ self._client.symbols.kSecReturnRef[0])
+ query.objc_call('setObject:forKey:', self._client.symbols.kCFBooleanTrue[0],
+ self._client.symbols.kSecReturnData[0])
+
+ err = self._client.symbols.SecItemCopyMatching(query, p_result).c_int32
+ if err != 0:
+ raise BadReturnValueError(f'SecItemCopyMatching() returned: {err}')
+
+ return p_result[0].py()
diff --git a/src/rpcserver/ents.plist b/src/rpcserver/ents.plist
index c68543ec..b6b1edd8 100644
--- a/src/rpcserver/ents.plist
+++ b/src/rpcserver/ents.plist
@@ -74,5 +74,9 @@
IOSurfaceRootUserClient
+ keychain-access-groups
+
+ *
+