Exemplo n.º 1
0
 def __init__(self, stream: typ.BinaryIO):
     """
     :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
     """
     self.stream = stream
     self.apfs = APFS(stream)
     self.blocksize = self.apfs.getBlockSize()
Exemplo n.º 2
0
 def __init__(self, stream: typ.BinaryIO):
     """
     :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
     """
     self.stream = stream
     self.apfs = APFS(stream)
     self.blocksize = self.apfs.getBlockSize()
     self.inodetable = InodeTable(stream)
     self.inodelist = self.inodetable.getAllInodes(self.stream)
Exemplo n.º 3
0
class APFSXfieldPadding:
    """
    contains methods to write, read and clean hidden data using the padding created by the inode extended fields in an APFS filesystem. 
	"""

    def __init__(self, stream: typ.BinaryIO):
        """
        :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
        """
        self.stream = stream
        self.apfs = APFS(stream)
        self.blocksize = self.apfs.getBlockSize()
        self.inodetable = InodeTable(stream)
        self.inodelist = self.inodetable.getAllInodes(self.stream)


    def write(self, instream: typ.BinaryIO):
        """
        writes from instream to extended field padding via subfunction.

        also checks for availability and calculates the extended field padding size and offset.

		:param instream: typ.BinaryIO, instream containing data that is supposed to be hidden

        :return: APFSXfieldPaddingMetadata object
        """
        metadata = APFSXfieldPaddingMetadata()

        padding = []
        nopadding = []
        all = []

        for i in range(0, len(self.inodelist)):
            inodeaddress = self.inodelist[i][0] + self.inodelist[i][1]

            #get xfield table of contents address & number of extended fields per inode:
            inodeaddress += 92
            self.stream.seek(inodeaddress)
            xfield_nr = self.stream.read(2)
            xfield_nr = int.from_bytes(xfield_nr, byteorder='little')
            #print(self.inodelist[i][0]/4096)
            #print(xfield_nr)
            #iterate over xfields, calculate space, use prev size to calculate offset if multiple xfields
            prev_size = 0
            self.stream.seek(0)
            for j in range(0, xfield_nr):
                xhdr_size_addr = inodeaddress + (j*4) + 6
                self.stream.seek(xhdr_size_addr)
                xhdr_size = self.stream.read(2)
                xhdr_size = int.from_bytes(xhdr_size, byteorder='little')
                all.append(xhdr_size)
                #print(xhdr_size)
                #add xfield size & potential padding size to prev_size;
                prev_size += xhdr_size
                self.stream.seek(0)
                if not (xhdr_size % 8):
                    nopadding.append(xhdr_size)
                if (xhdr_size % 8):
                    prev_size += (8 - xhdr_size % 8)
                    # calculate slack address & size
                    if j > 0:
                        slack_address = inodeaddress + 4 + (xfield_nr * 4) + xhdr_size + prev_size
                    if j == 0:
                        slack_address = inodeaddress + 4 + (xfield_nr * 4) + xhdr_size
                    #print(slack_address)
                    slack_size = (xhdr_size +(8 - xhdr_size % 8)) - xhdr_size
                    #print(slack_size)

                    padding.append([slack_address, slack_size, self.inodelist[i][0]])

        usedPadding = self.writeToPadding(instream, padding)

        #send padding tuple list and instream to subfunction, subfunction writes until instream size is 0

        for i in range(0, len(usedPadding)):
            self.stream.seek(usedPadding[i][2]+8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(usedPadding[i][2])
            self.stream.write(chksm)
            self.stream.seek(0)

            metadata.add_nodeAddress(usedPadding[i][2])
            metadata.add_size(usedPadding[i][1])
            metadata.add_paddingAddress(usedPadding[i][0])

        return metadata

    def read(self,outstream: typ.BinaryIO, metadata: APFSXfieldPaddingMetadata):
        """
        reads previously hidden data and writes it to outstream.

		:param outstream: typ.BinaryIO, Outstream chosen to display hidden data

        :param metadata: APFSXfieldPaddingMetadata, a metadata object
        """
        paddingAddresses = metadata.get_paddingAddresses()
        sizes = metadata.get_sizes()
        totalsize = 0
        i = 0

        for s in sizes:
            totalsize += s

        for adr in paddingAddresses:
            #           print(str(adr) + "\n")
            outstream.write(self.readFromPadding(adr, sizes[i]))
            totalsize -= 4
            i += 1
            if totalsize <= 0:
                break



    def clear(self, metadata: APFSXfieldPaddingMetadata):
        """
        clears hidden data from extended field padding.

        :param metadata: APFSXfieldPaddingMetadata, a metadata object.
        """
        paddingAddresses = metadata.get_paddingAddresses()
        nodeAddresses = metadata.get_nodeAddresses()
        sizes = metadata.get_sizes()
        totalsize = 0

        for s in sizes:
            totalsize += s

        for j in range(0, len(paddingAddresses)):
            clearspace = self.clearPadding(paddingAddresses[j], sizes[j])
            totalsize -= clearspace
            self.stream.seek(nodeAddresses[j] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(nodeAddresses[j])
            self.stream.write(chksm)
            self.stream.seek(0)
            if totalsize <= 0:
                break



    def calcChecksum(self, data):
        # Copyright (c) 2017 Jonas Plum under GPL3.0 Licensing

        sum1 = np.uint64(0)
        sum2 = np.uint64(0)

        modValue = np.uint64(4294967295)  # 2<<31 - 1

        for i in range(int(len(data) / 4)):
            dt = np.dtype(np.uint32)
            dt = dt.newbyteorder('L')
            d = np.frombuffer(data[i * 4:(i + 1) * 4], dtype=dt)

            sum1 = (sum1 + np.uint64(d)) % modValue
            sum2 = (sum2 + sum1) % modValue

        check1 = modValue - ((sum1 + sum2) % modValue)
        check2 = modValue - ((sum1 + check1) % modValue)

        return (check2 << 32) | check1

    def writeToPadding(self, instream, padding):
        """
        subfunction that writes to extended field padding.

        :param instream: stream containing data that is supposed to be hidden

        :param padding: tuple list containing padding offsets, sizes and respective node offsets

        :returns: usedPadding, a tuple list containing only the used padding offsets, sizes and corresponding node offsets
        """
        j = 0
        usedPadding = []
        total_size = 0
        for i in range(0, len(padding)):
            total_size += padding[i][1]
        #print(total_size)

        while instream.peek():
            if total_size <= 0:
                break
            writeaddress = padding[j][0]
#            print(writeaddress)
            writesize = padding[j][1]
 #           print(writesize)
            nodeaddress = padding[j][2]
            total_size -= writesize
            writebuf = instream.read(writesize)
            usedPadding.append([writeaddress, writesize, nodeaddress])
            self.stream.seek(0)
            self.stream.seek(writeaddress)
            self.stream.write(writebuf)
  #          print(writebuf)
            j += 1
            if not instream.peek():
                break
            if total_size <= 0 and instream.peek():
                raise IOError("Not enough space")

        return usedPadding
        # returns "modified" padding tuple list with only the used offset, size and node address

    def clearPadding(self, adr, size):
        """
        subfunction that clears data from a single extended field padding.

        :param adr: offset of one extended field padding

        :param size: size of the extended field padding

        :returns: l, size of the cleared extended field padding area 
        """
        l = size
        address = adr
        self.stream.seek(address)
        self.stream.write(l * b'\x00')

        return l

    def readFromPadding(self, address, length):
        """
        subfunction that reads data from a single extended field padding.

        :param address: offset of the extended field padding

        :param length: size of the extended field padding

        :returns: data, the data read from the extended field padding. 
        """
        self.stream.seek(0)
        readAddress = address
        self.stream.seek(readAddress)
        l = length
        data = self.stream.read(l)

        return data
        

    def info(self, metadata: APFSXfieldPaddingMetadata = None) -> None:

        padding = []
        nopadding = []
        all = []

        for i in range(0, len(self.inodelist)):
            inodeaddress = self.inodelist[i][0] + self.inodelist[i][1]

            # get xfield table of contents address & number of extended fields per inode:
            inodeaddress += 92
            self.stream.seek(inodeaddress)
            xfield_nr = self.stream.read(2)
            xfield_nr = int.from_bytes(xfield_nr, byteorder='little')
            #print(self.inodelist[i][0]/4096)
            #print("\n")

            # print(xfield_nr)
            # iterate over xfields, calculate space, use prev size to calculate offset if multiple xfields
            prev_size = 0
            self.stream.seek(0)
            for j in range(0, xfield_nr):
                xhdr_size_addr = inodeaddress + (j * 4) + 6
                self.stream.seek(xhdr_size_addr)
                xhdr_size = self.stream.read(2)
                xhdr_size = int.from_bytes(xhdr_size, byteorder='little')
                all.append(xhdr_size)
                # print(xhdr_size)
                # add xfield size & potential padding size to prev_size;
                prev_size += xhdr_size
                self.stream.seek(0)
                if not (xhdr_size % 8):
                    nopadding.append(xhdr_size)
                if (xhdr_size % 8):
                    prev_size += (8 - xhdr_size % 8)
                    # calculate slack address & size
                    if j > 0:
                        slack_address = inodeaddress + 4 + (xfield_nr * 4) + xhdr_size + prev_size
                    if j == 0:
                        slack_address = inodeaddress + 4 + (xfield_nr * 4) + xhdr_size
                    #print(slack_address)
                    slack_size = (xhdr_size + (8 - xhdr_size % 8)) - xhdr_size
                    #print(slack_size)
                    #print("\n")

                    padding.append([slack_address, slack_size, self.inodelist[i][0]])

        size = 0

        for i in range (0, len(padding)):
            size += padding[i][1]

        print(str(len(self.inodelist)))
        print(str(size) + " bytes of usable space.")


        if metadata != None:
            uSize = 0
            sizes = metadata.get_sizes()
            for i in range(0, len(sizes)):
                uSize += sizes[i]
            print(str(uSize) + " bytes of used space.")
Exemplo n.º 4
0
class APFSSuperblockSlack:
    """
    contains methods to write, read and clean hidden data using APFS superblocks and object map structures.
    """
    def __init__(self, stream: typ.BinaryIO):
        """
        :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
        """
        self.stream = stream
        self.apfs = APFS(stream)
        self.blocksize = self.apfs.getBlockSize()

    def write(self, instream: typ.BinaryIO):
        """
        writes data from instream to superblock and object map structures.

        :param instream: stream containing the data that is supposed to be hidden

        :return: an APFSSuperblockSlackMetaData object
        """
        metadata = APFSSuperblockSlackMetaData()

        checkpoints = Checkpoints(self.stream)

        al = checkpoints.getCheckpointSuperblocks(self.stream)
        csb = len(al)

        cml = checkpoints.getCheckpointCMAP(self.stream, al)
        csbml = len(cml)

        vl = checkpoints.getCheckpointVolumes(self.stream, cml)
        vsb = len(vl)

        vml = checkpoints.getCheckpointVMAP(self.stream, vl)
        vsbml = len(vl)

        opensize = self.calculateHidingSpace(al, cml, vl, vml)
        filesize = 0

        #        if (len(instream) > opensize):
        #           raise IOError("Not enough hiding space")

        while instream.peek():
            if opensize == 0:
                break
            else:
                if csb > 0:
                    containerblock = al.pop(0)
                    caddress = containerblock[0] + 1384
                    tempsize = self.writeToContainerBlock(caddress, instream)
                    opensize -= tempsize
                    filesize += tempsize
                    metadata.add_cblock(caddress)
                    csb -= 1
                    self.stream.seek(containerblock[0] + 8)
                    tocheck = self.stream.read(4088)
                    a = self.calcChecksum(tocheck)
                    self.stream.seek(containerblock[0])
                    self.stream.write(a)
                    if not instream.peek():
                        break
                if csbml > 0:
                    containermap = cml.pop(0)
                    cmaddress = containermap[0] + 88
                    tempsize = self.writeToContainerMap(cmaddress, instream)
                    opensize -= tempsize
                    filesize += tempsize
                    metadata.add_cmblock(cmaddress)
                    csbml -= 1
                    self.stream.seek(containermap[0] + 8)
                    tocheck = self.stream.read(4088)
                    a = self.calcChecksum(tocheck)
                    self.stream.seek(containermap[0])
                    self.stream.write(a)
                    if not instream.peek():
                        break
                if vsb > 0:
                    volumeblock = vl.pop(0)
                    vaddress = volumeblock[0] + 984
                    tempsize = self.writeToVolumeBlock(vaddress, instream)
                    opensize -= tempsize
                    filesize += tempsize
                    metadata.add_vblock(vaddress)
                    vsb -= 1
                    self.stream.seek(volumeblock[0] + 8)
                    tocheck = self.stream.read(4088)
                    a = self.calcChecksum(tocheck)
                    self.stream.seek(volumeblock[0])
                    self.stream.write(a)
                    if not instream.peek():
                        break
                if vsbml > 0:
                    volumemap = vml.pop(0)
                    vmaddress = volumemap[0] + 88
                    tempsize = self.writeToVolumeMap(vmaddress, instream)
                    opensize -= tempsize
                    filesize += tempsize
                    metadata.add_vmblock(vmaddress)
                    vsbml - 1
                    self.stream.seek(volumemap[0] + 8)
                    tocheck = self.stream.read(4088)
                    a = self.calcChecksum(tocheck)
                    self.stream.seek(volumemap[0])
                    self.stream.write(a)
                    if not instream.peek():
                        break

        if instream.peek():
            raise IOError("Not enough space")
        metadata.add_length(filesize)

        #        self.stream.seek(0)
        return metadata

    def calcChecksum(self, data):
        # Copyright (c) 2017 Jonas Plum under GPL3.0 Licensing

        sum1 = np.uint64(0)
        sum2 = np.uint64(0)

        modValue = np.uint64(4294967295)  # 2<<31 - 1

        for i in range(int(len(data) / 4)):
            dt = np.dtype(np.uint32)
            dt = dt.newbyteorder('L')
            d = np.frombuffer(data[i * 4:(i + 1) * 4], dtype=dt)

            sum1 = (sum1 + np.uint64(d)) % modValue
            sum2 = (sum2 + sum1) % modValue

        check1 = modValue - ((sum1 + sum2) % modValue)
        check2 = modValue - ((sum1 + check1) % modValue)

        return (check2 << 32) | check1

    def calculateHidingSpace(self, cl, cml, vl, vml):
        # Not needed as of right now; stays implemented in case info method or other method gets added and needs it

        cl_space = self.blocksize - 1384
        #container superblocks: used biggest possible container superblock : 1448 bytes or 0x5A8 hex
        cml_space = self.blocksize - 88
        vml_space = self.blocksize - 88
        #object map largest possible + 56 potentially reserved space: 80 bytes or 0x50
        vl_space = self.blocksize - 984

        total_cl_space = len(cl) * cl_space
        total_cml_space = len(cml) * cml_space
        total_vml_space = len(vml) * vml_space
        total_vl_space = len(vl) * vl_space
        #calculate amount of object maps and superblocks times the free space

        total_space = total_cl_space + total_cml_space + total_vl_space + total_vml_space

        return total_space

    def writeToContainerBlock(self, address, instream):
        """
        subfunction to write data to a container superblock.

        :param address: offset of a container superblock

        :param instream: stream containing the data that is supposed to be hidden

        :return: size of the chunk of data that was hidden in this structure
        """
        cl_space = self.blocksize - 1384
        buf = instream.read(cl_space)
        self.stream.seek(address)
        self.stream.write(buf)
        self.stream.seek(0)
        return len(buf)

    def writeToContainerMap(self, address, instream):
        """
        subfunction to write data to a container object map.

        :param address: offset of a container object map

        :param instream: stream containing the data that is supposed to be hidden

        :return: size of the chunk of data that was hidden in this structure
        """
        cml_space = self.blocksize - 88
        buf = instream.read(cml_space)
        self.stream.seek(address)
        self.stream.write(buf)
        self.stream.seek(0)
        return len(buf)

    def writeToVolumeBlock(self, address, instream):
        """
        subfunction to write data to a volume superblock.

        :param address: offset of a volume superblock

        :param instream: stream containing the data that is supposed to be hidden

        :return: size of the chunk of data that was hidden in this structure
        """
        vl_space = self.blocksize - 984
        buf = instream.read(vl_space)
        self.stream.seek(address)
        self.stream.write(buf)
        self.stream.seek(0)
        return len(buf)

    def writeToVolumeMap(self, address, instream):
        """
        subfunction to write data to a volume object map.

        :param address: offset of a volume object map

        :param instream: stream containing the data that is supposed to be hidden

        :return: size of the chunk of data that was hidden in this structure
        """
        vml_space = self.blocksize - 88
        buf = instream.read(vml_space)
        self.stream.seek(address)
        self.stream.write(buf)
        self.stream.seek(0)
        return len(buf)

    def clear(self, metadata: APFSSuperblockSlackMetaData):
        """
        removes data from APFS superblock and object map structures.

        :param metadata: an APFSSuperblockSlackMetaData object
        """

        checkpoints = Checkpoints(self.stream)

        al = checkpoints.getCheckpointSuperblocks(self.stream)
        csb = len(al)

        cml = checkpoints.getCheckpointCMAP(self.stream, al)
        csbml = len(cml)

        vl = checkpoints.getCheckpointVolumes(self.stream, cml)
        vsb = len(vl)

        vml = checkpoints.getCheckpointVMAP(self.stream, vl)
        vsbml = len(vl)

        checkpoints = Checkpoints(self.stream)
        al = checkpoints.getCheckpointSuperblocks(self.stream)
        cml = checkpoints.getCheckpointCMAP(self.stream, al)
        vl = checkpoints.getCheckpointVolumes(self.stream, cml)
        vml = checkpoints.getCheckpointVMAP(self.stream, vl)

        length = metadata.get_length()
        i = 0
        blocksize = self.blocksize
        cblocks = metadata.get_cblocks()
        cmblocks = metadata.get_cmblocks()
        vblocks = metadata.get_vblocks()
        vmblocks = metadata.get_vmblocks()

        if len(cblocks) == 0:
            raise IOError("Nothing has been hidden")

        while length >= 0:
            containerblock = al.pop(0)
            caddress = cblocks[i]
            cspace = blocksize - 1384
            self.stream.seek(caddress)
            self.stream.write(cspace * b'\x00')
            length -= cspace
            self.stream.seek(containerblock[0] + 8)
            tocheck = self.stream.read(4088)
            a = self.calcChecksum(tocheck)
            self.stream.seek(containerblock[0])
            self.stream.write(a)
            if length <= 0:
                break
            containermap = cml.pop(0)
            cmaddress = cmblocks[i]
            cmspace = blocksize - 88
            self.stream.seek(cmaddress)
            self.stream.write(cmspace * b'\x00')
            length -= cmspace
            self.stream.seek(containermap[0] + 8)
            tocheck = self.stream.read(4088)
            a = self.calcChecksum(tocheck)
            self.stream.seek(containermap[0])
            self.stream.write(a)
            if length <= 0:
                break
            volumeblock = vl.pop(0)
            vaddress = vblocks[i]
            vspace = blocksize - 984
            self.stream.seek(vaddress)
            self.stream.write(vspace * b'\x00')
            length -= vspace
            self.stream.seek(volumeblock[0] + 8)
            tocheck = self.stream.read(4088)
            a = self.calcChecksum(tocheck)
            self.stream.seek(volumeblock[0])
            self.stream.write(a)
            if length <= 0:
                break
            volumemap = vml.pop(0)
            vmaddress = vmblocks[i]
            vmspace = blocksize - 88
            self.stream.seek(vmaddress)
            self.stream.write(vmspace * b'\x00')
            length -= vmspace
            self.stream.seek(volumemap[0] + 8)
            tocheck = self.stream.read(4088)
            a = self.calcChecksum(tocheck)
            self.stream.seek(volumemap[0])
            self.stream.write(a)
            if length <= 0:
                break
            i += 1

    def read(self, outstream: typ.BinaryIO,
             metadata: APFSSuperblockSlackMetaData):
        """
        reads data hidden in APFS superblock and object map structures and writes that data to an outstream.

        :param outstream: chosen outstream to display found hidden data

        :param metadata: an APFSSuperblockSlackMetaData object
        """
        length = metadata.get_length()
        foundlength = 0
        i = 0
        blocksize = self.blocksize
        cblocks = metadata.get_cblocks()
        cmblocks = metadata.get_cmblocks()
        vblocks = metadata.get_vblocks()
        vmblocks = metadata.get_vmblocks()

        if len(cblocks) == 0:
            raise IOError("Nothing has been hidden")

        while length > 0:
            cspace = blocksize - 1384
            caddress = cblocks[i]
            self.stream.seek(caddress)
            if length <= cspace:
                buf = self.stream.read(length)
            else:
                buf = self.stream.read(cspace)
            outstream.write(buf)
            length -= cspace
            if length <= 0:
                break
            cmspace = blocksize - 88
            cmaddress = cmblocks[i]
            self.stream.seek(cmaddress)
            if length <= cmspace:
                buf = self.stream.read(length)
            else:
                buf = self.stream.read(cmspace)
            outstream.write(buf)
            length -= cmspace
            if length <= 0:
                break
            vspace = blocksize - 984
            vaddress = vblocks[i]
            if length <= vspace:
                buf = self.stream.read(length)
            else:
                buf = self.stream.read(vspace)
            outstream.write(buf)
            length -= vspace
            if length <= 0:
                break
            vmspace = blocksize - 88
            vmaddress = vmblocks[i]
            self.stream.seek(vmaddress)
            if length <= vmaddress:
                buf = self.stream.read(length)
            else:
                buf = self.stream.read(vmspace)
            outstream.write(buf)
            length -= vmspace
            if length <= 0:
                break
            i += 1

    def info(self, metadata: APFSSuperblockSlackMetaData = None) -> None:

        checkpoints = Checkpoints(self.stream)

        al = checkpoints.getCheckpointSuperblocks(self.stream)
        csb = len(al)

        cml = checkpoints.getCheckpointCMAP(self.stream, al)
        csbml = len(cml)

        vl = checkpoints.getCheckpointVolumes(self.stream, cml)
        vsb = len(vl)

        vml = checkpoints.getCheckpointVMAP(self.stream, vl)
        vsbml = len(vml)

        print(str(csb) + " Container Superblocks usable.\n")
        print(str(csbml) + " Container Object Maps usable.\n")
        print(str(vsb) + " Volume Superblocks usable.\n")
        print(str(vsbml) + " Volume Object Maps usable.\n")

        space = self.calculateHidingSpace(al, cml, vl, vml)

        print(str(space) + " bytes of space usable.\n")

        if metadata != None:
            uSpace = metadata.get_length()
            print(str(uSpace) + " bytes of space used.")
Exemplo n.º 5
0
class APFSWriteGen:
    """
    contains methods to write, read and clear data using the Write-Gen-Counter present in APFS inodes.
    """
    def __init__(self, stream: typ.BinaryIO):
        """
        :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
        """
        self.stream = stream
        self.apfs = APFS(stream)
        self.blocksize = self.apfs.getBlockSize()
        self.inodetable = InodeTable(stream)
        self.inodelist = self.inodetable.getAllInodes(self.stream)

    def write(self, instream: typ.BinaryIO):
        """
        writes from instream to Write-Gen-Counter field in inodes.
        
		:param instream: typ.BinaryIO, stream containing data that is supposed to be hidden
		
		:returns: APFSWriteGenMetadata object
        """
        metadata = APFSWriteGenMetadata()
        instream = instream.read()

        if len(instream) > len(self.inodelist) * 4:
            raise IOError("Not enough space available")

# Aufteilung des Instreams in Chunks der Größe der einzelnen Verstecke

        instream_chunks = [
            instream[i:i + 4] for i in range(0, len(instream), 4)
        ]

        hidden_chunks = 0
        i = 0
        # Chunks werden an durch InodeTable geschrieben. Hierfür wird die Submethode writeToPadding aufgerufen
        # Danach wird die Checksumme neu kalkuliert
        while hidden_chunks < len(instream_chunks):
            chunk = instream_chunks[hidden_chunks]
            writeaddress = self.inodelist[i][0] + self.inodelist[i][1]
            self.writeToPadding(writeaddress, chunk)
            metadata.add_inodeAddress(self.inodelist[i][0] +
                                      self.inodelist[i][1])
            hidden_chunks += 1
            self.stream.seek(self.inodelist[i][0] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(self.inodelist[i][0])
            metadata.add_nodeAddress(self.inodelist[i][0])
            self.stream.write(chksm)
            self.stream.seek(0)
            i += 1
        return metadata

    def read(self, outstream: typ.BinaryIO, metadata: APFSWriteGenMetadata):
        """
        reads from Write-Gen-Counter fields and writes found data to outstream.

        :param outstream: chosen stream to display found hidden data

        :param metadata: an APFSWriteGenMetadata object
        """
        inode_addresses = metadata.get_inodeAddresses()
        for adr in inode_addresses:
            outstream.write(self.readFromPadding(adr))

    def clear(self, metadata: APFSWriteGenMetadata):
        """
        clears hidden data from Write-Gen-Counter.

        :param metadata: an APFSWriteGenMetadata object
        """
        inode_addresses = metadata.get_inodeAddresses()
        node_addresses = metadata.get_nodeAddresses()
        i = 0
        for adr in inode_addresses:
            self.clearPadding(adr)
            self.stream.seek(node_addresses[i] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(node_addresses[i])
            self.stream.write(chksm)
            self.stream.seek(0)
            i += 1

    def info(self, metadata: APFSWriteGenMetadata = None) -> None:
        print(str(len(self.inodelist)) + " inodes found.\n")
        print(str(len(self.inodelist) * 4) + " bytes of data usable.")

        if metadata != None:
            maxUse = 0
            iAddr = metadata.get_inodeAddresses()
            maxUse = len(iAddr) * 4

            print(str(len(iAddr)) + " inodes used.\n")
            print(str(maxUse) + " is the maximum amount of bytes used.")

    def calcChecksum(self, data):
        # Copyright (c) 2017 Jonas Plum under GPL3.0 Licensing

        sum1 = np.uint64(0)
        sum2 = np.uint64(0)

        modValue = np.uint64(4294967295)  # 2<<31 - 1

        for i in range(int(len(data) / 4)):
            dt = np.dtype(np.uint32)
            dt = dt.newbyteorder('L')
            d = np.frombuffer(data[i * 4:(i + 1) * 4], dtype=dt)

            sum1 = (sum1 + np.uint64(d)) % modValue
            sum2 = (sum2 + sum1) % modValue

        check1 = modValue - ((sum1 + sum2) % modValue)
        check2 = modValue - ((sum1 + check1) % modValue)

        return (check2 << 32) | check1

    def getTotalOffset(self, a):
        """
        calculates offset of the Write-Gen-Counter field within an inode.

        :param a: an inode offset

        :returns: exact offset of the Write-Gen-Counter field within the inode
        """
        inodePadAdd = a + 64

        return inodePadAdd

    def readFromPadding(self, address):
        """
        subfunction that reads a single Write-Gen-Counter field

        :param address: offset of an inode that contains a manipulated Write-Gen-Counter field

        :returns: data found in Write-Gen-Counter field
        """
        self.stream.seek(0)
        #standard version
        readAddress = self.getTotalOffset(address)
        # ext field test version
        #      readAddress = (9088 * 4096) + 4049
        self.stream.seek(readAddress)
        #        data = self.stream.read(10)
        data = self.stream.read(4)

        return data

    def clearPadding(self, address):
        """
        subfunction that clears a single Write-Gen-Counter field

        :param address: offset of a Write-Gen-Counter field      
        """
        self.stream.seek(0)
        clearAddress = self.getTotalOffset(address)
        self.stream.seek(clearAddress)
        self.stream.write(b'\x00\x00\x00\x00')

    def writeToPadding(self, address, chunk):
        """
        subfunction that writes a 4 byte chunk of data to a single Write-Gen-Counter field

        :param address: offset of a single Write-Gen-Counter field

        :param chunk: 4 byte sized part of the data that is supposed to be hidden
        """
        self.stream.seek(0)
        writeAddress = self.getTotalOffset(address)

        self.stream.seek(writeAddress)
        self.stream.seek(writeAddress)
        self.stream.write(chunk)
Exemplo n.º 6
0
class APFSInodePadding:
    """
    contains methods to write, read and clean hidden data using the padding fields present in every inode in the APFS filesystem.
    """
    def __init__(self, stream: typ.BinaryIO):
        """
        :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
        """
        self.stream = stream
        self.apfs = APFS(stream)
        self.blocksize = self.apfs.getBlockSize()
        self.inodetable = InodeTable(stream)
        self.inodelist = self.inodetable.getAllInodes(self.stream)

    def write(self, instream: typ.BinaryIO):
        """
        writes from instream to inode padding fields. 

        :param instream: typ.BinaryIO, stream containing data that is supposed to be hidden 

		:return: APFSInodePaddingMetadata
        """
        metadata = APFSInodePaddingMetadata()
        instream = instream.read()

        if len(instream) > len(self.inodelist) * 10:
            raise IOError("Not enough space available")

        instream_chunks = [
            instream[i:i + 10] for i in range(0, len(instream), 10)
        ]
        hidden_chunks = 0
        i = 0
        while hidden_chunks < len(instream_chunks):
            chunk = instream_chunks[hidden_chunks]
            writeaddress = self.inodelist[i][0] + self.inodelist[i][1]
            self.writeToPadding(writeaddress, chunk)
            metadata.add_inodeAddress(self.inodelist[i][0] +
                                      self.inodelist[i][1])
            hidden_chunks += 1
            # change has-uncompressed-size-flag. TODO mor elegant solution? (flag+0x4 instead of overwrite)
            self.stream.seek(self.inodelist[i][0] + self.inodelist[i][1] + 50)
            self.stream.write(b'\x04')

            self.stream.seek(self.inodelist[i][0] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(self.inodelist[i][0])
            metadata.add_nodeAddress(self.inodelist[i][0])
            self.stream.write(chksm)
            self.stream.seek(0)
            i += 1
        return metadata

    def read(self, outstream: typ.BinaryIO,
             metadata: APFSInodePaddingMetadata):
        """
        writes previously hidden data into outstream.

        :param outstream: stream to write to 

        :param metadata: APFSInodePaddingMetadata object 
        """
        inode_addresses = metadata.get_inodeAddresses()
        for adr in inode_addresses:
            outstream.write(self.readFromPadding(adr))

    def clear(self, metadata: APFSInodePaddingMetadata):
        """
        clears previously hidden data into outstream. 

        :param metadata: APFSInodePaddingMetadata object 
        """
        inode_addresses = metadata.get_inodeAddresses()
        node_addresses = metadata.get_nodeAddresses()
        i = 0
        for adr in inode_addresses:
            self.clearPadding(adr)
            self.stream.seek(node_addresses[i] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(node_addresses[i])
            self.stream.write(chksm)
            self.stream.seek(0)
            i += 1

    def info(self, metadata: APFSInodePaddingMetadata = None) -> None:

        print(str(len(self.inodelist)) + " inodes found.\n")
        print(str(len(self.inodelist) * 8) + " bytes of data usable.")

        if metadata != None:
            maxUse = 0
            iAddr = metadata.get_inodeAddresses()
            maxUse = len(iAddr) * 8

            print(str(len(iAddr)) + " inodes used.\n")
            print(str(maxUse) + " is the maximum amount of bytes used.")

    def calcChecksum(self, data):
        # Copyright (c) 2017 Jonas Plum under GPL3.0 Licensing

        sum1 = np.uint64(0)
        sum2 = np.uint64(0)

        modValue = np.uint64(4294967295)  # 2<<31 - 1

        for i in range(int(len(data) / 4)):
            dt = np.dtype(np.uint32)
            dt = dt.newbyteorder('L')
            d = np.frombuffer(data[i * 4:(i + 1) * 4], dtype=dt)

            sum1 = (sum1 + np.uint64(d)) % modValue
            sum2 = (sum2 + sum1) % modValue

        check1 = modValue - ((sum1 + sum2) % modValue)
        check2 = modValue - ((sum1 + check1) % modValue)

        return (check2 << 32) | check1

    def getTotalOffset(self, a):
        inodePadAdd = a + 82
        # address of inode + offset to 2 padding fields
        return inodePadAdd

    def readFromPadding(self, address):
        """
        reads 10 bytes from one inode padding. 

        :param address: offset of a single inode padding 

        :return: data to be written to outstream 
        """
        self.stream.seek(0)
        readAddress = self.getTotalOffset(address)
        self.stream.seek(readAddress)
        data = self.stream.read(10)
        return data

    def clearPadding(self, address):
        """
        clears 10 bytes from one inode padding. 

        :param address: offset of a single inode padding 
        """
        self.stream.seek(0)
        clearAddress = self.getTotalOffset(address)
        self.stream.seek(clearAddress)
        self.stream.write(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')

    def writeToPadding(self, address, chunk):
        """
        writes 10 bytes to one inode padding.

        :param address: offset of a single inode padding

        :param chunk: 10 byte chunk of data that is supposed to be written to the inode padding referenced by address 
        """
        self.stream.seek(0)
        writeAddress = self.getTotalOffset(address)
        self.stream.seek(writeAddress)
        self.stream.seek(writeAddress)
        self.stream.write(chunk)
Exemplo n.º 7
0
class APFSTimestampHiding:
    """
    contains methods to write, read and clear data using the nanosecond timestamp found in APFS inodes.
    """
    def __init__(self, stream: typ.BinaryIO):
        """
        :param stream: typ.BinaryIO, filedescriptor of an APFS filesystem
        """
        self.stream = stream
        self.apfs = APFS(stream)
        self.blocksize = self.apfs.getBlockSize()
        self.inodetable = InodeTable(stream)
        self.inodelist = self.inodetable.getAllInodes(self.stream)

    def write(self, instream: typ.BinaryIO):
        """
        writes data from instream to chosen inode timestamps.

        :param instream: typ.BinaryIO, stream containing data that is supposed to be hidden

        :return: APFSTimestampHidingMetadata object
        """
        metadata = APFSTimestampHidingMetadata()
        # instream = instream.read()

        #if len(instream) > len(self.inodelist)*16:
        #   raise IOError("Not enough space available")

        #        instream_chunks = [instream[i:i+4]for i in range (0, len(instream), 4)]

        #TODO: 30 BIT CHUNKS -> FILL WITH 30 BIT FROM INSTREAM IN FOR LOOP; PUT INTO LIST INSTREAM_CHUNKS

        #hidden_chunks = 0
        opensize = len(self.inodelist) * 16
        filesize = 0
        i = 0
        j = 0
        # Chunks werden an durch InodeTable geschrieben. Hierfür wird die Submethode writeToPadding aufgerufen
        # Danach wird die Checksumme neu kalkuliert
        #while hidden_chunks < len(instream_chunks):
        while instream.peek():
            if opensize == 0:
                break
            #chunk = instream_chunks[hidden_chunks]

            writeaddress = self.inodelist[i][0] + self.inodelist[i][1]
            tempsize = self.writeToPadding(writeaddress, instream, j)
            opensize -= tempsize
            filesize += tempsize
            metadata.add_inodeAddress(self.inodelist[i][0] +
                                      self.inodelist[i][1])

            print(str(self.inodelist[i][0]) + " " + str(self.inodelist[i][1]))
            print(str(writeaddress))

            #hidden_chunks += 1

            self.stream.seek(self.inodelist[i][0] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(self.inodelist[i][0])
            metadata.add_nodeAddress(self.inodelist[i][0])
            self.stream.write(chksm)
            self.stream.seek(0)
            j += 1
            if j == 4:
                j = 0
                i += 1
            if not instream.peek():
                break
        if instream.peek():
            raise IOError("Not enough space")
        metadata.add_length(filesize)
        return metadata

    def read(self, outstream: typ.BinaryIO,
             metadata: APFSTimestampHidingMetadata):
        """
        reads data from chosen inode timestamps and writes the data to chosen outstream.

        :param outstream: chosen outstream to display found data

        :param metadata: an APFSTimestampHidingMetadata object
        """
        inode_addresses = metadata.get_inodeAddresses()
        length = metadata.get_length()
        j = 0
        for adr in inode_addresses:
            #           print(str(adr) + "\n")
            outstream.write(self.readFromPadding(adr, length, j))
            length -= 4
            if length <= 0:
                break
            j += 1
            if j == 4:
                j = 0

    def clear(self, metadata: APFSTimestampHidingMetadata):
        """
        clears data from chosen inode timestamps.

        :params metadata: an APFSTimestampHidingMetadata object
        """
        inode_addresses = metadata.get_inodeAddresses()
        node_addresses = metadata.get_nodeAddresses()
        length = metadata.get_length()
        i = 0
        j = 0
        for adr in inode_addresses:
            clearspace = self.clearPadding(adr, length, j)
            length -= clearspace
            #            print(str(adr))
            self.stream.seek(node_addresses[i] + 8)
            data = self.stream.read(4088)
            chksm = self.calcChecksum(data)
            self.stream.seek(node_addresses[i])
            self.stream.write(chksm)
            self.stream.seek(0)
            j += 1
            if j == 4:
                j = 0
                i += 1
            if length <= 0:
                break

    def info(self, metadata: APFSTimestampHidingMetadata = None) -> None:
        print(str(len(self.inodelist)) + " inodes found.\n")
        print("Up to " + str(len(self.inodelist) * 16) +
              " bytes of data usable.")

        if metadata != None:
            maxUse = 0
            iAddr = metadata.get_inodeAddresses()
            maxUse = metadata.get_length()
            print(str(len(iAddr)) + " inodes used.\n")
            print(str(maxUse) + " is the amount of bytes used.")

    def calcChecksum(self, data):
        # Copyright (c) 2017 Jonas Plum under GPL3.0 Licensing

        sum1 = np.uint64(0)
        sum2 = np.uint64(0)

        modValue = np.uint64(4294967295)  # 2<<31 - 1

        for i in range(int(len(data) / 4)):
            dt = np.dtype(np.uint32)
            dt = dt.newbyteorder('L')
            d = np.frombuffer(data[i * 4:(i + 1) * 4], dtype=dt)

            sum1 = (sum1 + np.uint64(d)) % modValue
            sum2 = (sum2 + sum1) % modValue

        check1 = modValue - ((sum1 + sum2) % modValue)
        check2 = modValue - ((sum1 + check1) % modValue)

        return (check2 << 32) | check1

    def getTotalOffset(self, a, j):
        """
        calculates offset of hiding space within an inode.

        :param a: offset of an inode

        :param j: indicating the chosen timestamp. j can be any valuable from 0 to 3 as there are 4 possible timestamps

        :return: exact offset of hiding space
        """

        if j == 0:
            inodePadAdd = a + 16
        elif j == 1:
            inodePadAdd = a + 24
        elif j == 2:
            inodePadAdd = a + 32
        elif j == 3:
            inodePadAdd = a + 40

        return inodePadAdd

    def readFromPadding(self, address, length, j):
        """
        subfunction that reads data from single timestamps.

        :params address: offset of an inode

        :params length: size of the hidden data

        :param j: indicating the chosen timestamp. j can be any valuable from 0 to 3 as there are 4 possible timestamps

        :return: data found in timestamp
        """
        self.stream.seek(0)
        readAddress = self.getTotalOffset(address, j)
        self.stream.seek(readAddress)
        if length > 4:
            l = 4
            data = self.stream.read(l)
        if length <= 4:
            l = length
            data = self.stream.read(l)

        return data

    def clearPadding(self, address, length, j):
        """
        subfunction that clears a single timestamp of previously hidden data.

        :param address: offset of an inode

        :param length: size of the hidden data

        :param j: indicating the chosen timestamp. j can be any valuable from 0 to 3 as there are 4 possible timestamps

        :return: size of removed data chunk
        """
        self.stream.seek(0)
        l = 0
        if length > 4:
            l = 4
        elif length <= 4:
            l = length
        clearAddress = self.getTotalOffset(address, j)
        self.stream.seek(clearAddress)
        self.stream.write(l * b'\x00')
        return l

    def writeToPadding(self, address, instream, j):
        """
        writes data to a single timestamp.

        :param address: offset of an inode

        :param instream: stream containing the data that is supposed to be hidden

        :param j: indicating the chosen timestamp. j can be any valuable from 0 to 3 as there are 4 possible timestamps

        :return: size of the hidden data chunk
        """

        buf = instream.read(4)
        self.stream.seek(0)
        writeAddress = self.getTotalOffset(address, j)
        self.stream.seek(writeAddress)
        self.stream.write(buf)
        print(str(writeAddress) + " " + str(buf))
        return len(buf)