@@ -967,6 +967,237 @@ def test_gcm(self):
967967 self .assertEqual (successful_faults , 0 )
968968 self .assertEqual (started , True )
969969
970+ def test_cmac (self ):
971+ print ("Starting the cmac test" )
972+ data_len = 32
973+ # We prepare two data inputs and check for collisions between them
974+ data1 = [i for i in range (data_len )]
975+ data2 = [data_len - i for i in range (data_len )]
976+ data = [data1 , data2 ]
977+ key_len = 16
978+ key = [i for i in range (key_len )]
979+ iv = [i for i in range (16 )]
980+ cfg = 0
981+ trigger = 0
982+
983+ # Directory for the trace log files
984+ pc_trace_file = os .path .join (log_dir , "cmac_pc_trace.log" )
985+ # Directory for the log of the campaign
986+ campaign_file = os .path .join (log_dir , "cmac_test_campaign.log" )
987+
988+ successful_faults = 0
989+ total_attacks = 0
990+
991+ gdb = None
992+ started = False
993+ with open (campaign_file , "w" ) as campaign :
994+ print (f"Switching terminal output to { campaign_file } " , flush = True )
995+ sys .stdout = campaign
996+ try :
997+ # Program the bitstream, flash the target, and set up OpenOCD
998+ target .initialize_target ()
999+
1000+ # Initialize the testOS
1001+ trigger_testos_init ()
1002+
1003+ # Connect to GDB
1004+ gdb = GDBController (
1005+ gdb_path = GDB_PATH , gdb_port = GDB_PORT , elf_file = elf_path
1006+ )
1007+
1008+ # We provide the name of the unique marker in the pentest framework
1009+ function_name = "PENTEST_MARKER_CMAC"
1010+ # Gives back an array of hits where the function is called
1011+ trace_address = parser .get_marker_addresses (function_name )
1012+ print (
1013+ "Start and stop addresses of " , function_name , ": " , trace_address
1014+ )
1015+
1016+ crash_observation_address = parser .get_function_start_address (
1017+ "ottf_exception_handler"
1018+ )
1019+
1020+ # Start the tracing
1021+ # We set a short timeout to detect whether GDB has connected properly
1022+ # and a long timeout for the entire tracing
1023+ initial_timeout = 10
1024+ total_timeout = 60 * 60 * 5
1025+
1026+ gdb .setup_pc_trace (pc_trace_file , trace_address [0 ], trace_address [1 ])
1027+ gdb .send_command ("c" , check_response = False )
1028+
1029+ # Trigger the cmac from the testOS (we do not read its output)
1030+ symfi .handle_cmac (data [0 ], data_len , key , key_len , iv , cfg , trigger )
1031+
1032+ start_time = time .time ()
1033+ initial_timeout_stopped = False
1034+ total_timeout_stopped = False
1035+
1036+ # Run the tracing to get the trace log
1037+ # Sometimes the tracing fails due to race conditions,
1038+ # we have a quick initial timeout to catch this
1039+ while time .time () - start_time < initial_timeout :
1040+ output = gdb .read_output ()
1041+ if "breakpoint 1, " in output :
1042+ initial_timeout_stopped = True
1043+ break
1044+ if not initial_timeout_stopped :
1045+ print ("No initial break point found, can be a misfire, try again" )
1046+ sys .exit (1 )
1047+ while time .time () - start_time < total_timeout :
1048+ output = gdb .read_output ()
1049+ if "PC trace complete" in output :
1050+ print ("\n Trace complete" )
1051+ total_timeout_stopped = True
1052+ break
1053+ if not total_timeout_stopped :
1054+ print ("Final tracing timeout reached" )
1055+ sys .exit (1 )
1056+
1057+ # Parse and truncate the trace log to get all PCs in a list
1058+ pc_list = gdb .parse_pc_trace_file (pc_trace_file )
1059+ # Get the unique PCs and annotate their occurrence count
1060+ pc_count_dict = Counter (pc_list )
1061+ if len (pc_count_dict ) <= 0 :
1062+ print ("Found no tracing, stopping" )
1063+ sys .exit (1 )
1064+ print ("Trace data is logged in " , pc_trace_file )
1065+ print (
1066+ "Tracing has a total of" ,
1067+ len (pc_count_dict ),
1068+ "unique PCs" ,
1069+ flush = True ,
1070+ )
1071+
1072+ # Reset the target, flush the output, and close gdb
1073+ gdb = reset_target_and_gdb (gdb )
1074+
1075+ data_out = [None , None ]
1076+
1077+ started = True
1078+ for pc , count in pc_count_dict .items ():
1079+ for i_count in range (min (MAX_SKIPS_PER_LOOP , count )):
1080+ # Search for collisions in outputs between the cmac instances
1081+ for i in range (2 ):
1082+ print ("-" * 80 )
1083+ print (
1084+ "Applying instruction skip in " ,
1085+ pc ,
1086+ "occurrence" ,
1087+ i_count ,
1088+ "data" ,
1089+ i ,
1090+ )
1091+ print ("-" * 80 )
1092+
1093+ crash_observation = "crash detected"
1094+
1095+ try :
1096+ # The observation points
1097+ observations = {
1098+ # Crash check
1099+ crash_observation_address : f"{ crash_observation } " ,
1100+ }
1101+ gdb .add_observation (observations )
1102+
1103+ gdb .apply_instruction_skip (
1104+ pc , parser .parse_next_instruction (pc ), i_count
1105+ )
1106+ gdb .send_command ("c" , check_response = False )
1107+
1108+ # The instruction skip loop
1109+ symfi .handle_cmac (
1110+ data [i ],
1111+ data_len ,
1112+ key ,
1113+ key_len ,
1114+ iv ,
1115+ cfg ,
1116+ trigger ,
1117+ )
1118+ testos_response = read_testos_output ()
1119+
1120+ gdb_response = gdb .read_output ()
1121+ data_out [i ] = None
1122+ if "instruction skip applied" in gdb_response :
1123+ total_attacks += 1
1124+
1125+ if crash_observation in gdb_response :
1126+ print ("Crash detected, resetting" , flush = True )
1127+ gdb = reset_target_and_gdb (gdb )
1128+ else :
1129+ testos_response_json = json .loads (
1130+ testos_response
1131+ )
1132+ print (
1133+ "Output:" , testos_response_json , flush = True
1134+ )
1135+ if testos_response_json ["status" ] == 0 :
1136+ data_out [i ] = tuple (
1137+ testos_response_json ["data" ]
1138+ )
1139+
1140+ if (
1141+ utils .is_partial_collision (
1142+ data_out [0 ],
1143+ data_out [1 ],
1144+ match_threshold_ratio = 0.75 ,
1145+ )
1146+ ) or utils .is_majority_zeros (
1147+ data_out [i ], total_length = 16
1148+ ):
1149+ successful_faults += 1
1150+ print ("-" * 80 )
1151+ print ("Successful FI attack!" )
1152+ print (
1153+ "Location:" ,
1154+ pc ,
1155+ "iteration" ,
1156+ i_count ,
1157+ )
1158+ print (gdb_response )
1159+ print ("Response:" , testos_response_json )
1160+ print ("-" * 80 )
1161+ # Reset GDB by closing and opening again
1162+ gdb = reset_gdb (gdb )
1163+ else :
1164+ print (
1165+ "No break point found, something went wrong" ,
1166+ flush = True ,
1167+ )
1168+ gdb = reset_target_and_gdb (gdb )
1169+
1170+ except json .JSONDecodeError :
1171+ print (
1172+ "Error: JSON decoding failed. Invalid response format" ,
1173+ flush = True ,
1174+ )
1175+ try :
1176+ gdb = reset_target_and_gdb (gdb )
1177+ except TimeoutError :
1178+ gdb = re_initialize (gdb )
1179+
1180+ except TimeoutError as e :
1181+ print ("Timeout error, retrying" , flush = True )
1182+ print (e , flush = True )
1183+ try :
1184+ gdb = reset_target_and_gdb (gdb )
1185+ except TimeoutError :
1186+ gdb = re_initialize (gdb )
1187+
1188+ finally :
1189+ print ("-" * 80 )
1190+ print (
1191+ f"Total attacks { total_attacks } , successful attacks { successful_faults } "
1192+ )
1193+ # Close the OpenOCD and GDB connection at the end
1194+ if gdb :
1195+ gdb .close_gdb ()
1196+ target .close_openocd ()
1197+ sys .stdout = original_stdout
1198+ self .assertEqual (successful_faults , 0 )
1199+ self .assertEqual (started , True )
1200+
9701201
9711202if __name__ == "__main__" :
9721203 r = Runfiles .Create ()
0 commit comments