Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A series of updates for a variety of reasons. #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ SECFORCE

Lorenzo Vogelsang (@ptrac3)


You Gotta Hack That

Felix Ryan (@gotta_hack)

Description:
----
Expand All @@ -36,14 +38,20 @@ Options:

[REQUIRED] `--input-file` Path of the captured .RAW file with a valid FIX login sequence

[REQUIRED] `--csv-log` Path for the output CSV log file
[REQUIRED] `--csv` Path for the output CSV log file

`--seq-start` The sequence ID to start sending FIX messages with

`--fuzz` Path of the file containing the payloads for fuzzing

`--param` Comma separeted FIX fields to fuzz. If none were provided every field will be fuzzed

`--auto-fuzz length step` It enables the auto-fuzz mode which generates UTF-8 payloads on the fly accordingly to the length and step values that were passed

`--sequential-fuzz` Effectively a brute forcer

`--no-fuzz` Just send the original FIX messages to show that the tool has connectivity and everything is working correctly

Please also consider that --fuzz and --auto-fuzz are mutually exclusive parameters.


Expand Down
235 changes: 163 additions & 72 deletions fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import itertools
import fuzzer as fz
from itertools import groupby
from socket import error as SocketError

__author__ = "Thanos Polychronis and Lorenzo Vogelsang"
__copyright__ = "Copyright 2017, SECFORCE LTD"
Expand All @@ -49,9 +50,12 @@
host = parser.add_argument('--host', type=str, nargs='+', help='the IP of the FIX server', required=True)
port = parser.add_argument('--port', type=int, help='the listening port', required=True)
input_file = parser.add_argument('--input-file', type=str, nargs=1, help='PCAP file with FIX authentication and action(s) to fuzz', required=True)
seq_start = parser.add_argument('--seq-start', type=int, help='The start number for the sequence ID (inc initial logon), defaults to "2"', required=False, default=2)
group = parser.add_mutually_exclusive_group(required=True)
fuzz = group.add_argument('--fuzz', default=0, type=str, metavar='<Filenane>', nargs='+', help='File containing payloads')
fuzz = group.add_argument('--fuzz', default=0, type=str, metavar='<Filename>', nargs='+', help='File containing payloads')
auto_fuzz = group.add_argument('--auto-fuzz', metavar='<Length> <Step>', help='Enable the auto-fuzz mode', nargs=2)
sequential_fuzz = group.add_argument('--sequential-fuzz', action='store_true', help='Enable the sequential-fuzz mode')
no_fuzz = group.add_argument('--no-fuzz', action='store_true', help='Just send the original unfettered version from file')
csv = parser.add_argument('--csv', metavar='<Filename>', type=str, nargs='+', help='Output Log file')
param = parser.add_argument('--param', default=0, type=str, metavar='', nargs='+', help='Parameters to Fuzz')
args = parser.parse_args()
Expand All @@ -62,13 +66,12 @@
#Create header for CSV logging
if args.csv:
with open(args.csv[0], "w") as myfile:
myfile.write("TimeStamp,Message Sent,Message Received,Time Elapsed"+"\n")
myfile.write("TimeStamp,Message Sent,Send Seq,Message Received,Time Elapsed"+"\n")
csv_file = str(args.csv[0])

def getFuzzList(file):
with open(file, 'r') as fuzz:
fuzzer = [line.rstrip() for line in fuzz]
print fuzzer
return fuzzer

def timestampGen():
Expand All @@ -84,21 +87,12 @@ def update_timestamp(message):
#Whole timestamp tag+field
timestamp_tag = "52="+timestamp
ts = time.time()
newtimestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y%m%d-%H:%M:%S.%f')[:-3]
newtimestamp = datetime.datetime.utcfromtimestamp(ts).strftime('%Y%m%d-%H:%M:%S.%f')[:-3]
newtimestamp_tag = "52="+newtimestamp
message = message.replace(timestamp_tag, newtimestamp_tag)
return message,timestamp


def checksum(message):

# The checksum field is removed from FIX message
message = str(message[:-7])
# Checksum is computed
message_checksum = str(int(sum(bytearray(message)))%256).zfill(3)
return message_checksum


def update_checksum(message):
checksum_field = "10="
# The checksum field is removed from FIX message
Expand All @@ -110,7 +104,7 @@ def update_checksum(message):
return message_ok


def update_bodylength(message, checksum):
def update_bodylength(message):
#9= extraction
list_fix = []
soh = '\x01'
Expand All @@ -123,43 +117,94 @@ def update_bodylength(message, checksum):


message = message.strip(soh+"9="+str(body_value)) + soh
message = beginString+soh+"9="+str(len(message))+soh+message+"10="+checksum+soh
message = beginString+soh+"9="+str(len(message))+soh+message+"10=001"+soh

list_fix.append(message)
return message


def sendFuzzMessage(host, port, logonmsg, final):

logonmsg,time_logon = update_timestamp(logonmsg)
logonmsg = update_checksum(logonmsg)
checksum_logonmsg = checksum(logonmsg)
s = socket.socket()
s.connect((host, port))
s.send(logonmsg)

for f in final:
fix_request,time_fuzz = update_timestamp(f)
checksum_msg = checksum(f)
fix_request = update_bodylength(fix_request, checksum_msg)
fix_request = update_checksum(fix_request)
start_time = time.time()
s.send(fix_request)
print "\n[-] Sent: " + fix_request
def sendFuzzMessage(host, port, logonmsg, final, current_seq_num):
requested_seq_num = None
logonmsg,time_logon,current_seq_num = update_fix_message(logonmsg, current_seq_num)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(logonmsg)
print "\n[-] Logon Sent: " + logonmsg.replace('\x01', '^')
fix_response = s.recv(1024)
print "[-] Received:" + fix_response
elapsed_time = (time.time() - start_time)
elapsed_time_ms = int(elapsed_time * 1000)
if args.csv:
csv(time_fuzz,fix_request,fix_response,elapsed_time_ms)
s.close
return final, fix_response, elapsed_time_ms, s


def update_fix_message(message):
message = update_timestamp(message)
print "[-] Logon Received:" + fix_response.replace('\x01', '^')

# logon error detected
if re.findall("\x0135=5\x01", fix_response):
logout_error_message = re.findall("(?:\x0158=)(.*?)\x01", fix_response)[0]
print "\n[*] Server sent Logout message. Error text is: " + logout_error_message
if 'MsgSeqNum' in logout_error_message:
expected_seq_num = re.findall("(?:expecting )([0-9]+)(?: but received)", logout_error_message)[0]
print "\n[*] Sequence number expected is: " + expected_seq_num + " but we gave: " + str(current_seq_num-1) \
+ " will retry logon using expected sequence number"
# have to restart socket as server doesn't play otherwise
s.close
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
logonmsg, time_logon, current_seq_num = update_fix_message(logonmsg, int(expected_seq_num))
s.send(logonmsg)
print "\n[-] Logon Sent: " + logonmsg.replace('\x01', '^')
fix_response = s.recv(1024)
print "[-] Logon Received:" + fix_response.replace('\x01', '^')

for fix_message in final:
fix_request,time_fuzz,current_seq_num = update_fix_message(fix_message, current_seq_num)
start_time = time.time()
s.send(fix_request)
print "\n[-] Payload Sent: " + fix_request.replace('\x01', '^')

fix_response = s.recv(1024)
print "[-] Payload Received:" + fix_response.replace('\x01', '^')
if re.findall("(?:\x0135=)2\x01", fix_response):
print "\n[*] Server sent ResendRequest message, extracting sequence ID for future messages"
requested_seq_num = int(re.findall("(?:\x017=)([0-9]+)\x01", fix_response)[0])
print "\n[*] Sequence number requested is: " + str(requested_seq_num) + " but we are at: " + str(current_seq_num-1) \
+ " will use requested sequence number for future payloads. Also, this payload will not be re-fired"
if not fix_response:
print "\n[*] Server did not send a response - could indicate crash occurred"
fix_response = 'Blank response - investigate this and previous payload'

s.close
except SocketError as e:
print ('Socket error: ' + str(e))
fix_response = 'Socket error: ' + str(e)

elapsed_time = (time.time() - start_time)
elapsed_time_ms = int(elapsed_time * 1000)
if args.csv:
apend_to_csv(time_fuzz, fix_request, str(int(current_seq_num)-1), fix_response, elapsed_time_ms)
if requested_seq_num:
return requested_seq_num
else:
return current_seq_num


def update_seqnum(message, current_seq_num):
# 34= extraction
seq_fix = dict(re.findall("(?:^|\x01)(34)=(.*?)\x01", message))
# Extraction of the actual seq num
seq = seq_fix['34']
# Whole seq tag+field
seq_tag = "34=" + seq
new_seq = str(current_seq_num)
new_check_sum_tag = "34=" + new_seq
message = message.replace(seq_tag, new_check_sum_tag)
current_seq_num += 1
return message, current_seq_num


def update_fix_message(message, current_seq_num):
message,time_logon = update_timestamp(message)
message, current_seq_num = update_seqnum(message, current_seq_num)
message = update_bodylength(message)
message = update_checksum(message)
return message
return message, time_logon, current_seq_num


def fix2log(message):
message = message.replace(getSoh, "^")
Expand Down Expand Up @@ -203,23 +248,43 @@ def fuzz_it(logonmsg, fix_requests):


def test(fix_requests, dict_final, logonmsg):

for i in fix_requests:
if i in dict_final.keys():
params = ",".join(dict_final[i]).split(",")

for param in params:
if args.auto_fuzz:
print("[AUTO-FUZZ-MODE] Now fuzzing %d field:" ) %int(param)
payloads = auto_fuzz(i, param)
else:
print("[Normal-FUZZ-MODE] Now fuzzing field %d: " ) %int(param)
payloads = normal_fuzz(i, param)
current_seq_num = args.seq_start

# send an unfettered FIX message to test the thing works
print("Sending the first message from the input file as an unfettered FIX message\n")
initial = list()
initial.append(str(fix_requests[0]))
current_seq_num = sendFuzzMessage(args.host[0], args.port, logonmsg, initial, current_seq_num)

print('-=-=-=-Fuzzing start-=-=-=-\n')

try:
for message_to_fuzz in fix_requests:
if message_to_fuzz in dict_final.keys():
params = ",".join(dict_final[message_to_fuzz]).split(",")

for param in params:
if args.auto_fuzz:
print("[AUTO-FUZZ-MODE] Now fuzzing field: %d" ) %int(param)
payloads = auto_fuzz(message_to_fuzz, param)
elif args.sequential_fuzz:
print("[SEQUENTIAL-FUZZ-MODE] Now fuzzing field: %d") % int(param)
payloads = sequential_fuzz(message_to_fuzz, param)
elif args.no_fuzz:
print("No further action needed as not fuzzing, just sending unfettered")
else:
print("[NORMAL-FUZZ-MODE] Now fuzzing field: %d") %int(param)
payloads = normal_fuzz(message_to_fuzz, param)

for payload in payloads:
final = list(fix_requests)
final[final.index(message_to_fuzz)] = payload
current_seq_num = sendFuzzMessage(args.host[0], args.port, logonmsg, final, current_seq_num)
except KeyboardInterrupt:
# if user "Ctrl-C", stop processing gracefully
print('Exiting...')
exit(1)

for payload in payloads:
final = list(fix_requests)
final[final.index(i)] = payload
sendFuzzMessage(args.host[0], args.port, logonmsg, final)

def fuzz_replace(request, param, payload):
newPart=re.findall('\d+', param)
Expand All @@ -236,11 +301,26 @@ def fuzz_replace(request, param, payload):
def normal_fuzz(request, param):
normal_fuzz=[]
for payload in getFuzzList(args.fuzz[0]):
fuzzed_message = fuzz_replace(request, param, payload)
a = fuzz_replace(request,param,payload)
normal_fuzz.append(a)
return normal_fuzz


def sequential_fuzz(request, param):

sequential_fuzz=[]

payloads_list = list()
for i in xrange(10000):
a = u"\\u%04x" % i
payloads_list.append(a.decode('unicode-escape'))

for payload in payloads_list:
a = fuzz_replace(request,param,payload)
sequential_fuzz.append(a)
return sequential_fuzz


def auto_fuzz(request, param):
length = int(args.auto_fuzz[0])
step = int(args.auto_fuzz[1])
Expand All @@ -249,17 +329,28 @@ def auto_fuzz(request, param):
utf8_encoded = ""
utf_payloads_test_inc = fz.utf8_gen(i)
utf8_fixed_plain_test_inc = utf_payloads_test_inc[0][0]
print utf_payloads_test_inc
for field in utf8_fixed_plain_test_inc:
utf8_encoded = utf8_encoded+field
#print utf8_encoded
a = fuzz_replace(request,param,utf8_encoded)
auto_fuzz.append(a)
return auto_fuzz

def csv(time,request,response,elapsed):

def message_cleaner(message):
SOH = '\x01'
newline = u"\u000A"
carriagereturn = u"\u000D"
comma = u"\u002C"

clean_message = message.rstrip('\n').replace(SOH, '^').replace(newline, '[{nl}]').replace(carriagereturn, '[{cr}]').replace(comma, '[{com}]')
return clean_message


def apend_to_csv(time, request, req_seq, response, elapsed):
clean_request = message_cleaner(request)
clean_response = message_cleaner(response)
with open(args.csv[0], "a") as myfile:
myfile.write(str(time).rstrip('\n')+","+str(request).rstrip('\n')+","+str(response).rstrip('\n')+","+str(elapsed).rstrip('\n')+"\n")
myfile.write(str(time).rstrip('\n')+","+str(clean_request)+","+str(req_seq)+","+str(clean_response)+","+str(elapsed).rstrip('\n')+"\n")

def main():
FIX_id=""
Expand All @@ -284,13 +375,13 @@ def main():
logonmsg = message_ok
elif (logonmsg!="" and (re.search(r'30=0' ,message)) or (re.search(r'35=A'+getSoh ,message))):
print "\n[INFO] Message is Not Logon: " + message_ok
elif (re.search(r'58=' ,message_ok)):
print "\n[INFO] This is a response, skipping...: " + message_ok
try:
FIX_id = re.search(r'49=(.*?)'+getSoh, message).group(1)
print "\n[+] Fix server ID is: " + FIX_id
except:
print "\n[-]FIX server ID was not found"
# elif (re.search(r'58=' ,message_ok)):
# print "\n[INFO] This is a response, skipping...: " + message_ok
# try:
# FIX_id = re.search(r'49=(.*?)'+getSoh, message).group(1)
# print "\n[+] Fix server ID is: " + FIX_id
# except:
# print "\n[-]FIX server ID was not found"
else:
#Creating the list of messages to fuzz
fix_requests.append(message_ok)
Expand Down