Exemplo n.º 1
0
    def list(self, pattern=""):
        '''
		Display the list of module, filtered by the string provided as ``pattern``.

		:param pattern: filter
		:type pattern: str
		'''
        displayDict = {}

        for module in self.modulesList:
            info = self.modulesList[module]().info()
            technology = (info["technology"][:1]).upper() + (
                info["technology"][1:]).lower()
            if (pattern in info["description"] or pattern in info["name"]
                    or pattern in info["technology"]
                    or pattern in info["type"]):
                if not technology in displayDict:
                    displayDict[technology] = []
                displayDict[technology].append(
                    [info["name"], info["type"], info["description"]])

        for module in sorted(displayDict):
            if displayDict[module]:
                io.chart(["Name", "Type", "Description"],
                         sorted(displayDict[module]),
                         "{} Modules".format(module))
Exemplo n.º 2
0
    def pairing(
        self,
        active: ["active", "passive"] = "active",
        parameters:
        "!method:_autocompletePairingParameters" = "inputOutput=yesno|authentication=bonding|ltk=112233445566778899aabbccddeeff|rand=1122334455667788|ediv=12"
    ):
        self.receiver.removeCallbacks()
        self.initializeCallbacks()
        parameters = {
            param.split("=")[0]: param.split("=")[1]
            for param in parameters.split("|")
        }
        pairModule = utils.loadModule("ble_pair")
        pairModule["INTERFACE"] = self.args["INTERFACE"]
        pairModule["MODE"] = "slave"
        pairModule["ACTIVE"] = "yes" if active == "active" else "no"
        pairModule["KEYBOARD"] = "yes" if (
            "inputOutput" in parameters
            and "keyboard" in parameters["inputOutput"]) else "no"
        pairModule["YESNO"] = "yes" if ("inputOutput" in parameters and "yesno"
                                        in parameters["inputOutput"]) else "no"
        pairModule["DISPLAY"] = "yes" if (
            "inputOutput" in parameters
            and "display" in parameters["inputOutput"]) else "no"
        pairModule["CT2"] = "yes" if (
            "authentication" in parameters
            and "ct2" in parameters["authentication"]) else "no"
        pairModule["MITM"] = "yes" if (
            "authentication" in parameters
            and "mitm" in parameters["authentication"]) else "no"
        pairModule["BONDING"] = "yes" if (
            "authentication" in parameters
            and "bonding" in parameters["authentication"]) else "no"
        pairModule["SECURE_CONNECTIONS"] = "yes" if (
            "authentication" in parameters
            and "secureConnections" in parameters["authentication"]) else "no"
        pairModule["KEYPRESS"] = "yes" if (
            "authentication" in parameters
            and "keypress" in parameters["authentication"]) else "no"
        pairModule["LTK"] = parameters["ltk"] if "ltk" in parameters else ""
        pairModule["EDIV"] = parameters["ediv"] if "ediv" in parameters else ""
        pairModule["RAND"] = parameters["rand"] if "rand" in parameters else ""
        pairModule["IRK"] = parameters["irk"] if "irk" in parameters else ""
        pairModule["ADDR"] = parameters["addr"] if "addr" in parameters else ""
        pairModule["ADDR_TYPE"] = parameters[
            "addrType"] if "addrType" in parameters else ""
        pairModule["CSRK"] = parameters["csrk"] if "csrk" in parameters else ""
        pairModule["PIN"] = parameters["pin"] if "pin" in parameters else ""

        io.chart(["Name", "Value"],
                 [[k, v] for k, v in pairModule.args.items()],
                 "Input parameters")
        output = pairModule.execute()
        if output["success"]:
            if active == "active":
                io.success("Active pairing enabled !")
            else:
                io.success("Passive pairing enabled !")
        else:
            io.fail("An error occured during pairing !")
Exemplo n.º 3
0
	def displayDevices(self):
		displayMode = utils.listArg(self.args["DISPLAY"])
		devices = [] 
		for address,device in self.devices.items():
			currentLine = []
			adv_data=device["ADV_IND_data"]+" (ADV_IND)" if device["ADV_IND_data"]!="" else ""
			if device["ADV_IND_data"]!="" and device["SCAN_RSP_data"]!="":
				adv_data+="\n"
			adv_data+=device["SCAN_RSP_data"]+" (SCAN_RSP)" if device["SCAN_RSP_data"]!="" else ""

			if "address" in displayMode:
				currentLine.append(address)
			if "name" in displayMode:
				currentLine.append(device["name"])
			if "company" in displayMode:
				currentLine.append(device["company"])
			if "flags" in displayMode:
				currentLine.append(",".join(device["flags"]))
			if "data" in displayMode:
				currentLine.append(adv_data)
			devices.append(currentLine)
		
		headLine = []
		if "address" in displayMode:
			headLine.append("BD Address")
		if "name" in displayMode:
			headLine.append("Name")
		if "company" in displayMode:
			headLine.append("Company")
		if "flags" in displayMode:
			headLine.append("Flags")
		if "data" in displayMode:
			headLine.append("Advertising data")

		io.chart(headLine, devices, "Devices found")
Exemplo n.º 4
0
	def run(self):
		self.emitter = self.getEmitter(interface=self.args["INTERFACE"])

		if utils.booleanArg(self.args["SHOW_CAPABILITIES"]):		
			self.displayCapabilities()

		if "rfstorm" in self.args["INTERFACE"]:
			interface = self.args["INTERFACE"]
			mode = self.emitter.getMode()
			index = str(self.emitter.getDeviceIndex())
			io.chart(["Interface","Device Index","Mode"],[[interface,"#"+index,mode]])

			return self.ok({"INTERFACE":interface,
					"INDEX":index,
					"MODE":mode
					})

		elif ".pcap" in self.args["INTERFACE"]:
			interface = self.args["INTERFACE"]
			mode = self.emitter.getMode()
			io.chart(["Interface","Mode"],[[interface,mode]])
			return self.ok({"INTERFACE":interface,
					"MODE":mode
					})
		return self.nok()
					
Exemplo n.º 5
0
	def displayConnection(self,index=0):
		aa = "0x{:8x}".format(self.receivers[index].getAccessAddress())
		crcInit = "0x{:6x}".format(self.receivers[index].getCrcInit())
		channelMap = "0x{:10x}".format(self.receivers[index].getChannelMap())
		hopInterval = int(self.receivers[index].getHopInterval())
		hopIncrement = int(self.receivers[index].getHopIncrement())
		io.chart(["Access Address", "CRCInit", "Channel Map", "Hop Interval", "Hop Increment"],[[aa,crcInit,channelMap,hopInterval,hopIncrement]],"Sniffed Connection")
Exemplo n.º 6
0
	def showCharacteristics(self,startHandle,endHandle,title="Characteristics"):
		'''
		This method displays the GATT characteristics described as attributes included in the Database and provide a mechanism to only select the characteristics between two handles (it is mainly used in order to print the characteristics included in a Service).

		:param startHandle: first ATT handle
		:type startHandle: int
		:param endHandle: last ATT handle
		:type endHandle: int
	 	:param title: Title of the chart
		:type title: str
		'''
		formattedCharacteristics = []
		for i in range(startHandle,endHandle):
			if i < len(self.attributes):
				att = self.attributes[i]
				if att.type == UUID(name="Characteristic Declaration"):
					declarationHandle = "0x{:04x}".format(att.handle)
					characteristic = CharacteristicDeclaration(data=att.value[::-1])
					uuid16 = ("0x{:04x}".format(characteristic.UUID.UUID16) 
							if characteristic.UUID.UUID16 is not None
							else ""
						 )
					uuid128 = (characteristic.UUID.UUID128.hex() 
							if characteristic.UUID.UUID128 is not None
							else ""
						)
					name = (characteristic.UUID.name
							if characteristic.UUID.name is not None
							else ""
						)
					valueHandle = "0x{:04x}".format(characteristic.valueHandle)
					value = self.attributes[characteristic.valueHandle].value
					value = (value.replace(b"\x00",b"").decode("ascii") 
						if utils.isPrintable(value)
						else value.hex()
					)
					permissions = ",".join(characteristic.permissionsFlag.permissions)
					startDescriptor = characteristic.valueHandle + 1
					descriptors = ""
					while (startDescriptor < len(self.attributes) and 
						self.attributes[startDescriptor] is not None and
					       (self.attributes[startDescriptor].type != UUID(name="Characteristic Declaration") and
						self.attributes[startDescriptor].type != UUID(name="Primary Service") and
						self.attributes[startDescriptor].type != UUID(name="Secondary Service"))):
						descriptor = self.attributes[startDescriptor]
						
						namedesc = CharacteristicDescriptor(UUID=descriptor.type).UUID.name
						valuedesc = (descriptor.value.replace(b"\x00",b"").decode("ascii") 
								if utils.isPrintable(descriptor.value)
								else descriptor.value.hex()
							)
						startSymbol = "" if descriptors == "" else "\n"
						descriptors += startSymbol + namedesc +" : "+ valuedesc
						startDescriptor += 1
					formattedCharacteristics.append([declarationHandle, valueHandle, uuid16, uuid128, name,permissions,value,descriptors])
		io.chart(["Declaration Handle","Value Handle","UUID16","UUID128","Name","Permissions", "Value","Descriptors"]
			,formattedCharacteristics,
			io.colorize(title, "yellow")
			)
Exemplo n.º 7
0
	def displayAccessPoints(self):
		devices = []
		for ap in self.accessPoints:
			address = ap
			SSID = self.accessPoints[ap]["ssid"]
			channels = ",".join([str(c) for c in self.accessPoints[ap]["channels"]])
			devices.append([address,SSID,channels])
		io.chart(["MAC Address","SSID","Channel"],devices,"Access Point Found") 
Exemplo n.º 8
0
	def tasks(self,pattern=""):
		'''
		This method allows to display the existing background tasks. A string pattern can be provided as a filter.
		
		:param pattern: Filter
		:type pattern: str
		'''
		io.chart(["PID","Name","State","Output"], self.taskManager.getTasksList(pattern),"Background Tasks")
Exemplo n.º 9
0
	def info(self):
		'''
		This method displays informations about the loaded module, such as the name, technology used, etc.
		'''
		for module in self.modules:
			if module["module"] is not None:
				infos = module["module"].info()
				content = [infos["name"],infos["technology"], infos["type"], infos["description"]]
				io.chart(["Name", "Technology", "Type","Description"], [content], module["name"])
Exemplo n.º 10
0
	def displayStations(self):
		devices = []
		for sta in self.stations:
			if sta != "FF:FF:FF:FF:FF:FF":
				address = sta
				channels = ",".join([str(c) for c in self.stations[sta]["channels"]])
				devices.append([address,channels])
		if devices!=[]:
			io.chart(["MAC Address","Channel"],devices,"Stations Found") 
Exemplo n.º 11
0
 def displayCapabilities(self):
     capabilitiesList = []
     for capability in self.capabilities:
         capabilitiesList.append([
             capability,
             (io.colorize("yes", "green")
              if self.emitter.hasCapabilities(capability) else io.colorize(
                  "no", "red"))
         ])
     io.chart(["Capability", "Available"], capabilitiesList)
Exemplo n.º 12
0
 def displayDevices(self):
     sys.stdout.write(" " * 100 + "\r")
     if self.changes != 0:
         devices = []
         for k, v in self.devices.items():
             devices.append([
                 str(k), ",".join(str(i) for i in v["channels"]), v["type"]
             ])
         io.chart(["Address", "Channels", "Device type"], devices)
         self.changes = 0
Exemplo n.º 13
0
	def connections(self):
		counter = 1
		connectionsList = []
		for connection in self.emitter.getConnections():
			connectionsList.append([str(counter),connection["address"], connection["handle"]])
			counter += 1
		if connectionsList == []:
			io.fail("No active connections !")
		else:
			io.chart(["Identifier", "Address", "Handle"],connectionsList,io.colorize("Active connections","yellow"))
Exemplo n.º 14
0
	def showargs(self):
		'''
		This method displays a chart describing the available input parameters for the loaded module.
		'''
		for module in self.modules:
			currentArgs = []
			for argument in module["module"].args:
				argName = (module["name"]+"."+argument) if len(self.modules)>1 else argument
				argValue = module["module"].args[argument]
				currentArgs.append([argName, argValue])
			io.chart(["Name","Value"],currentArgs,io.colorize(module["name"],"yellow"))
Exemplo n.º 15
0
 def displayDevices(self):
     devices = []
     for address in self.devices:
         devices.append([
             address,
             hex(self.devices[address]['classOfDevice']),
             self.devices[address]['rssi'],
             self.devices[address]['data'][:26].hex() + "[...]"
         ])
     io.chart(["Address", "Class Of Device", "RSSI", "Data"], devices,
              "Devices found")
Exemplo n.º 16
0
	def info(self):
		'''
		This method displays informations about the loaded module, such as the name, technology used, etc.
		'''
		for module in self.modules:
			if "module" in module and module["module"] is not None:
				infos = module["module"].info()
				content = [infos["name"],infos["technology"], infos["type"], infos["description"]]
				io.chart(["Name", "Technology", "Type","Description"], [content], module["name"])
			elif "shortcut" in module and module["shortcut"] is not None:
				name = module["name"].strip("0123456789")
				description = self.loadedShortcuts[name]["description"]
				modules = self.loadedShortcuts[name]["modules"]
				io.chart(["Name","Modules","Description"],[[name,modules,description]],module["name"]+" (shortcut)")
Exemplo n.º 17
0
 def printServices(self, services, title="Services"):
     formattedServices = []
     for service in services:
         startHandle = "0x{:04x}".format(service["startHandle"])
         endHandle = "0x{:04x}".format(service["endHandle"])
         uuid16 = (hex(service["uuid"].UUID16)
                   if service["uuid"].UUID16 is not None else "")
         uuid128 = (service["uuid"].UUID128.hex()
                    if service["uuid"].UUID128 is not None else "")
         name = (service["uuid"].name
                 if service["uuid"].name is not None else "")
         formattedServices.append(
             [startHandle, endHandle, uuid16, uuid128, name])
     io.chart(["Start Handle", "End Handle", "UUID16", "UUID128", "Name"],
              formattedServices, io.colorize(title, "yellow"))
Exemplo n.º 18
0
    def displayDetails(self):
        '''
		This method displays a chart indicating the session keys' diversifier, the initializations vectors and the session key. If some values have not been provided or calculated, it displays an error message.
		'''
        if self.ready:
            io.chart(["Name", "Value"],
                     [["Master SKD", self.masterSkd.hex()],
                      ["Master IV", self.masterIv.hex()],
                      ["Slave SKD", self.masterSkd.hex()],
                      ["Slave IV", self.masterIv.hex()],
                      ["SKD", self.skd.hex()], ["IV", self.iv.hex()],
                      ["Session Key", self.sessionKey.hex()]],
                     "Encryption information")
        else:
            io.fail("Missing informations, encryption disabled")
Exemplo n.º 19
0
 def run(self):
     self.emitter = self.getEmitter(interface=self.args["INTERFACE"])
     if utils.booleanArg(self.args["SHOW_CAPABILITIES"]):
         self.displayCapabilities()
     interface = self.args["INTERFACE"]
     address = self.emitter.getAddress()
     mode = self.emitter.getMode()
     channel = self.emitter.getChannel()
     io.chart(["Interface", "MAC Address", "Mode", "Channel"],
              [[interface, address, mode, channel]])
     return self.ok({
         "INTERFACE": interface,
         "ADDRESS": address,
         "MODE": mode,
         "CHANNEL": channel
     })
Exemplo n.º 20
0
	def run(self):
		self.emitter = bt.BluetoothEmitter(interface=self.args["INTERFACE"])
		interface = self.args["INTERFACE"]
		address = self.emitter.getAddress()
		localName = self.emitter.getLocalName()
		manufacturer = self.emitter.getManufacturer()
		changeableAddress = "yes" if self.emitter.isAddressChangeable() else "no"
		io.chart(["Interface","BD Address","Local Name","Manufacturer","Changeable Address"],
			[[interface, address,localName,manufacturer,changeableAddress]])
		return self.ok({
				"INTERFACE":interface,
				"ADDRESS":address,
				"LOCAL_NAME":localName,
				"MANUFACTURER":manufacturer,
				"CHANGEABLE_ADDRESS":changeableAddress
				})
Exemplo n.º 21
0
	def list(self,pattern=""):
		'''
		Display the list of module, filtered by the string provided as ``pattern``.

		:param pattern: filter
		:type pattern: str
		'''
		displayList = []
		for module in self.modulesList:
			info = self.modulesList[module]().info()
			if (
				pattern in info["description"]	or
				pattern in info["name"] 	or
				pattern in info["technology"]	or 
				pattern in info["type"]
			):
				displayList.append([info["name"], info["type"], info["description"]])
		io.chart(["Name", "Type","Description"], displayList, "Modules")
Exemplo n.º 22
0
 def display(self, data):
     devices = self.devices
     if len(devices):
         deviceHdr = list(next(iter(
             devices.values())).keys()) if len(devices) else []
         deviceContent = [
             list(device.values()) for device in devices.values()
         ]
         io.chart(deviceHdr, deviceContent, "Devices found")
     connections = self.connections
     if len(connections):
         connHdr = list(next(iter(
             connections.values())).keys()) if len(connections) else []
         connContent = [
             list(connection.values())
             for connection in connections.values()
         ]
         io.chart(connHdr, connContent, "Connections found")
Exemplo n.º 23
0
 def displayDevices(self):
     if utils.integerArg(self.args["START_CHANNEL"]) != utils.integerArg(
             self.args["END_CHANNEL"]):
         sys.stdout.write(" " * 100 + "\r")
     columnsNames = ["Pan ID", "Channel", "Association permitted", "Nodes"]
     networks = []
     nodes = ""
     for panID, network in self.devices.items():
         for node, role in network["nodes"].items():
             nodes += zigbee.addressToString(node) + "(" + role + ")" + "\n"
         networks.append([
             hex(panID),
             str(network["channel"]),
             "yes" if network["associationPermitted"] else
             ("unknown" if network["associationPermitted"] is None else
              "no"), nodes[:-1]
         ])
     io.chart(columnsNames, networks)
Exemplo n.º 24
0
    def printAttributes(self, attributes):
        formattedAttributes = []
        for attribute in attributes:
            aType = ble.UUID(data=attribute["type"])
            if aType.name is not None:
                attributeType = aType.name
            elif aType.UUID16 is not None:
                attributeType = hex(aType.UUID16)
            else:
                attributeType = aType.UUID128.hex()

            attributeValue = attribute["value"].replace(
                b"\x00", b"").decode("ascii") if utils.isPrintable(
                    attribute["value"]) else attribute["value"].hex()
            attributeHandle = "0x{:04x}".format(attribute["handle"])
            formattedAttributes.append(
                [attributeHandle, attributeType, attributeValue])
        io.chart(["Attribute Handle", "Attribute Type", "Attribute Value"],
                 formattedAttributes, io.colorize("Attributes", "yellow"))
Exemplo n.º 25
0
	def shortcuts(self,pattern=""):
		'''
		This method allows to list the different shortcuts available in the framework. A string pattern can be provided
		as a filter.

		:param pattern: Filter
		:type pattern: str
		'''
		shortcuts = []
		for shortcutName,shortcut in self.loadedShortcuts.items():
			if (pattern == "" or
			    pattern in shortcutName or
			    pattern in shortcut["description"] or
			    pattern in shortcut["modules"]):
				shortcuts.append([shortcutName,shortcut["modules"],shortcut["description"]])
		if shortcuts != []:
			io.chart(["Name","Modules","Description"],shortcuts,"Shortcuts")
		else:
			io.fail("No shortcut found !")
Exemplo n.º 26
0
	def showServices(self):
		'''
		This method displays the GATT services described as attributes included in the Database.
		'''
		formattedServices = []
		for att in self.attributes:
			if att is not None and (att.type == UUID(name="Primary Service") or att.type == UUID(name="Secondary Service")):
				startHandle = "0x{:04x}".format(att.handle)
				service = Service(data=att.value[::-1])
				serviceName = service.UUID.name if service.UUID.name is not None else ""
				serviceUUID16 = "0x{:04x}".format(service.UUID.UUID16) if service.UUID.UUID16 is not None else ""
				serviceUUID128 = service.UUID.UUID128.hex() if service.UUID.UUID128 is not None else ""
				if len(formattedServices) > 0:
					formattedServices[-1][1] = "0x{:04x}".format(att.handle - 1)
				formattedServices.append([startHandle,"0x{:04x}".format(0xFFFF),serviceUUID16, serviceUUID128,serviceName])
		io.chart(["Start Handle","End Handle", "UUID16", "UUID128", "Name"],
			 formattedServices,
			 io.colorize("Services", "yellow")
			)
		return formattedServices
Exemplo n.º 27
0
	def showargs(self):
		'''
		This method displays a chart describing the available input parameters for the loaded module.
		'''
		for module in self.modules:
			currentArgs = []
			if "shortcut" not in module:
				for argument in module["module"].args:
					argName = (module["name"]+"."+argument) if len(self.modules)>1 else argument
					argValue = module["module"].args[argument]
					currentArgs.append([argName, argValue])
				io.chart(["Name","Value"],currentArgs,io.colorize(module["name"],"yellow"))
			else:
				for argument in module["mapping"]:
					argName = (module["name"]+"."+argument) if len(self.modules)>1 else argument
					if module["mapping"][argument]["value"] is not None:
						argValue =  module["mapping"][argument]["value"]
					else:
						argValue = "<auto>"
					currentArgs.append([argName, argValue])
				io.chart(["Name", "Value"], currentArgs,io.colorize(module["name"],"green"))
Exemplo n.º 28
0
    def run(self):
        self.emitter = self.getEmitter(interface=self.args["INTERFACE"])

        if utils.booleanArg(self.args["SHOW_CAPABILITIES"]):
            self.displayCapabilities()

        if "irma" in self.args["INTERFACE"]:
            interface = self.args["INTERFACE"]
            index = str(self.emitter.getDeviceIndex())
            port = self.emitter.getSerialPort()
            frequency = str(self.emitter.getFrequency())
            io.chart(["Interface", "Device Index", "Serial Port", "Frequency"],
                     [[interface, "#" + index, port, frequency + " kHz"]])

            return self.ok({
                "INTERFACE": interface,
                "INDEX": index,
                "PORT": port,
                "FREQUENCY": frequency
            })

        return self.nok()
Exemplo n.º 29
0
	def show(self):
		'''
		This method displays a chart to present the ATT level vision of the attributes included in the Database.
		'''
		formattedAttributes = []
		for att in self.attributes:
			if att is not None:
				aType = att.type
				if aType.name is not None:
					attributeType = aType.name
				elif aType.UUID16 is not None:
					attributeType = hex(aType.UUID16)
				else:
					attributeType = aType.UUID128.hex()
				
				attributeValue = att.value.replace(b"\x00",b"").decode("ascii") if utils.isPrintable(att.value) else att.value.hex()
				attributeHandle = "0x{:04x}".format(att.handle)
				formattedAttributes.append([attributeHandle, attributeType,attributeValue])
		io.chart(["Attribute Handle", "Attribute Type", "Attribute Value"],
			 formattedAttributes,
			 io.colorize("Attributes","yellow")
			)
Exemplo n.º 30
0
 def printCharacteristics(self, characteristics, title="Characteristics"):
     formattedCharacteristics = []
     for characteristic in characteristics:
         declarationHandle = "0x{:04x}".format(
             characteristic["declarationHandle"])
         valueHandle = "0x{:04x}".format(characteristic["valueHandle"])
         permissionsFlag = ",".join(characteristic["permissionsFlag"])
         uuid16 = (hex(characteristic["uuid"].UUID16)
                   if characteristic["uuid"].UUID16 is not None else "")
         uuid128 = (characteristic["uuid"].UUID128.hex()
                    if characteristic["uuid"].UUID128 is not None else "")
         name = (characteristic["uuid"].name
                 if characteristic["uuid"].name is not None else "")
         value = (characteristic["value"].replace(b"\x00",
                                                  b"").decode("ascii")
                  if utils.isPrintable(characteristic["value"]) else
                  characteristic["value"].hex())
         descriptors = ""
         if "descriptors" in characteristic:
             for desc in characteristic["descriptors"]:
                 namedesc = ble.CharacteristicDescriptor(
                     data=desc["type"]).UUID.name
                 valuedesc = (
                     desc["value"].replace(b"\x00", b"").decode("ascii")
                     if utils.isPrintable(desc["value"])
                     and len(desc["value"]) > 0 else desc["value"].hex())
                 endSymbol = "\n" if characteristic["descriptors"][
                     -1] != desc else ""
                 descriptors += namedesc + " : " + valuedesc + endSymbol
         formattedCharacteristics.append([
             declarationHandle, valueHandle, uuid16, uuid128, name,
             permissionsFlag, value, descriptors
         ])
     io.chart([
         "Declaration Handle", "Value Handle", "UUID16", "UUID128", "Name",
         "Permissions", "Value", "Descriptors"
     ], formattedCharacteristics, io.colorize(title, "yellow"))