def onClipboardData(self, pdu: PlayerPDU): parser = ClipboardParser() pdu = parser.parse(pdu.payload) if not isinstance(pdu, FormatDataResponsePDU): return clipboardData = decodeUTF16LE(pdu.requestedFormatData) self.writeSeparator() self.writeText(f"CLIPBOARD DATA: {clipboardData}") self.writeSeparator()
def parse(self, data: bytes) -> ClientInfoPDU: """ Decode a Client Info PDU from bytes. :param data: the Client Info PDU bytes. """ stream = BytesIO(data) codePage = Uint32LE.unpack(stream) flags = Uint32LE.unpack(stream) isUnicode = flags & ClientInfoFlags.INFO_UNICODE != 0 hasNullBytes = codePage == 1252 or isUnicode nullByteCount = 1 if hasNullBytes else 0 unicodeMultiplier = 2 if isUnicode else 0 domainLength = Uint16LE.unpack( stream) + nullByteCount * unicodeMultiplier usernameLength = Uint16LE.unpack( stream) + nullByteCount * unicodeMultiplier passwordLength = Uint16LE.unpack( stream) + nullByteCount * unicodeMultiplier alternateShellLength = Uint16LE.unpack( stream) + nullByteCount * unicodeMultiplier workingDirLength = Uint16LE.unpack( stream) + nullByteCount * unicodeMultiplier domain = decodeUTF16LE(stream.read(domainLength)) username = decodeUTF16LE(stream.read(usernameLength)) password = decodeUTF16LE(stream.read(passwordLength)) alternateShell = decodeUTF16LE(stream.read(alternateShellLength)) workingDir = decodeUTF16LE(stream.read(workingDirLength)) extraInfoBytes = stream.read() if extraInfoBytes != b"": extraInfo = self.parseExtraInfo(extraInfoBytes) else: extraInfo = None return ClientInfoPDU(codePage, flags, domain, username, password, alternateShell, workingDir, extraInfo)
def parseFileBothDirectoryInformation( self, data: bytes) -> List[FileBothDirectoryInformation]: stream = BytesIO(data) information: [FileBothDirectoryInformation] = [] while stream.tell() < len(data): nextEntryOffset = Uint32LE.unpack(stream) fileIndex = Uint32LE.unpack(stream) creationTime = Uint64LE.unpack(stream) lastAccessTime = Uint64LE.unpack(stream) lastWriteTime = Uint64LE.unpack(stream) lastChangeTime = Uint64LE.unpack(stream) endOfFilePosition = Uint64LE.unpack(stream) allocationSize = Uint64LE.unpack(stream) fileAttributes = FileAttributes(Uint32LE.unpack(stream)) fileNameLength = Uint32LE.unpack(stream) eaSize = Uint32LE.unpack(stream) shortNameLength = Uint8.unpack(stream) # stream.read(1) # reserved (not actually used, WTF Microsoft ????) shortName = stream.read(24)[:min(24, shortNameLength)] fileName = stream.read(fileNameLength) if nextEntryOffset != 0: stream.read(8 - stream.tell() % 8) # alignment break shortName = decodeUTF16LE(shortName) fileName = decodeUTF16LE(fileName) info = FileBothDirectoryInformation(fileIndex, creationTime, lastAccessTime, lastWriteTime, lastChangeTime, endOfFilePosition, allocationSize, fileAttributes, eaSize, shortName, fileName) information.append(info) return information
def dealWithCreateResponse(self, pdu: DeviceIOResponsePDU, requestPDU: DeviceCreateRequestPDU): """ If its been created for reading, add the file to the list of opened files. """ createResponse = self.deviceRedirectionParser.parseDeviceCreateResponse( pdu) self.pduToSend = createResponse if requestPDU.desiredAccess & (FileAccess.GENERIC_READ | FileAccess.FILE_READ_DATA) and \ requestPDU.createOptions & CreateOption.FILE_NON_DIRECTORY_FILE != 0: self.mitm_log.info( "Opening file %(path)s as number %(number)d", { "path": decodeUTF16LE(requestPDU.path), "number": createResponse.fileId }) self.openedFiles[createResponse.fileId] = requestPDU.path
def parseDirectoryControlRequest(self, deviceID: int, fileID: int, completionID: int, minorFunction: int, stream: BytesIO) -> DeviceIORequestPDU: self.minorFunctionsForParsingResponse[completionID] = MinorFunction(minorFunction) if minorFunction == MinorFunction.IRP_MN_NOTIFY_CHANGE_DIRECTORY: return DeviceIORequestPDU(deviceID, fileID, completionID, MajorFunction.IRP_MJ_DIRECTORY_CONTROL, minorFunction, stream.read()) else: informationClass = FileSystemInformationClass(Uint32LE.unpack(stream)) initialQuery = Uint8.unpack(stream) pathLength = Uint32LE.unpack(stream) stream.read(23) path = stream.read(pathLength) path = decodeUTF16LE(path)[: -1] self.informationClassForParsingResponse[completionID] = informationClass return DeviceQueryDirectoryRequestPDU(deviceID, fileID, completionID, informationClass, initialQuery, path)
def onClipboardData(self, pdu: PlayerPDU): parser = ClipboardParser() pdu = parser.parse(pdu.payload) if not isinstance(pdu, FormatDataResponsePDU): # TODO: Handle file PDUs. return data = decodeUTF16LE(pdu.requestedFormatData) self.json[JSON_KEY_EVENTS].append({ "timestamp": self.timestamp, "type": "clipboard", "data": { "mime": "text/plain", "content": data }, })
def parseDeviceCreateRequest(self, deviceID: int, fileID: int, completionID: int, minorFunction: int, stream: BytesIO) -> DeviceCreateRequestPDU: desiredAccess = Uint32LE.unpack(stream) allocationSize = Uint64LE.unpack(stream) fileAttributes = FileAttributes(Uint32LE.unpack(stream)) sharedAccess = FileShareAccess(Uint32LE.unpack(stream)) createDisposition = FileCreateDisposition(Uint32LE.unpack(stream)) createOptions = FileCreateOptions(Uint32LE.unpack(stream)) pathLength = Uint32LE.unpack(stream) path = stream.read(pathLength) path = decodeUTF16LE(path)[:-1] return DeviceCreateRequestPDU(deviceID, fileID, completionID, minorFunction, desiredAccess, allocationSize, fileAttributes, sharedAccess, createDisposition, createOptions, path)
def parseFileNamesInformation(self, data: bytes) -> List[FileNamesInformation]: stream = BytesIO(data) information: [FileNamesInformation] = [] while stream.tell() < len(data): nextEntryOffset = Uint32LE.unpack(stream) fileIndex = Uint32LE.unpack(stream) fileNameLength = Uint32LE.unpack(stream) fileName = stream.read(fileNameLength) if nextEntryOffset != 0: stream.read(8 - stream.tell() % 8) # alignment break fileName = decodeUTF16LE(fileName) info = FileNamesInformation(fileIndex, fileName) information.append(info) return information
def parseFileFullDirectoryInformation(self, data: bytes) -> List[FileFullDirectoryInformation]: stream = BytesIO(data) information: [FileFullDirectoryInformation] = [] while stream.tell() < len(data): nextEntryOffset = Uint32LE.unpack(stream) fileIndex = Uint32LE.unpack(stream) creationTime = Uint64LE.unpack(stream) lastAccessTime = Uint64LE.unpack(stream) lastWriteTime = Uint64LE.unpack(stream) lastChangeTime = Uint64LE.unpack(stream) endOfFilePosition = Uint64LE.unpack(stream) allocationSize = Uint64LE.unpack(stream) fileAttributes = FileAttributes(Uint32LE.unpack(stream)) fileNameLength = Uint32LE.unpack(stream) eaSize = Uint32LE.unpack(stream) fileName = stream.read(fileNameLength) if nextEntryOffset != 0: stream.read(8 - stream.tell() % 8) # alignment break fileName = decodeUTF16LE(fileName) info = FileFullDirectoryInformation( fileIndex, creationTime, lastAccessTime, lastWriteTime, lastChangeTime, endOfFilePosition, allocationSize, fileAttributes, eaSize, fileName ) information.append(info) return information
def onClientInfo(self, data: bytes): """ Log the client connection information and replace the username and password if applicable. :param data: the client info data """ pdu = ClientInfoParser().parse(data) clientAddress = None if pdu.extraInfo: clientAddress = decodeUTF16LE(pdu.extraInfo.clientAddress) self.log.info( "Client Info: username = %(username)r, password = %(password)r, domain = %(domain)r, clientAddress = %(clientAddress)r", { "username": pdu.username, "password": pdu.password, "domain": pdu.domain, "clientAddress": clientAddress }) self.recorder.record(pdu, PlayerPDUType.CLIENT_INFO) # If set, replace the provided username and password to connect the user regardless of # the credentials they entered. if self.config.replacementUsername is not None: pdu.username = self.config.replacementUsername if self.config.replacementPassword is not None: pdu.password = self.config.replacementPassword if self.config.replacementUsername is not None and self.config.replacementPassword is not None: pdu.flags |= ClientInfoFlags.INFO_AUTOLOGON # Tell the server we don't want compression (unsure of the effectiveness of these flags) pdu.flags &= ~ClientInfoFlags.INFO_COMPRESSION pdu.flags &= ~ClientInfoFlags.INFO_CompressionTypeMask self.log.debug("Sending %(pdu)s", {"pdu": pdu}) self.server.sendClientInfo(pdu)
def handleCreateResponse(self, request: DeviceCreateRequestPDU, response: DeviceIOResponsePDU): """ Prepare to intercept a file: create a FileProxy object, which will only create the file when we actually write to it. When listing a directory, Windows sends a lot of create requests without actually reading the files. We use a FileProxy object to avoid creating a lot of empty files whenever a directory is listed. :param request: the device create request :param response: the device IO response to the request """ response = DeviceRedirectionParser().parseDeviceCreateResponse( response) isFileRead = request.desiredAccess & (FileAccess.GENERIC_READ | FileAccess.FILE_READ_DATA) != 0 isNotDirectory = request.createOptions & CreateOption.FILE_NON_DIRECTORY_FILE != 0 if isFileRead and isNotDirectory: remotePath = Path(decodeUTF16LE(request.path).rstrip("\x00")) mapping = FileMapping.generate(remotePath, self.config.fileDir) localPath = mapping.localPath self.openedFiles[response.fileID] = FileProxy( localPath, "wb", mapping, self.log)
def decodeClipboardData(self, data: bytes) -> str: """ Decode clipboard bytes to a string. :param data: clipboard content bytes """ return decodeUTF16LE(data)
def bytesToPath(self, pathAsBytes: bytes): """ Converts a windows-encoded path to a beautiful, python-ready path. """ return decodeUTF16LE(pathAsBytes).strip("\x00")