class PartitionDisplay(Parameterized, AutoScript): _name = "Partition Comparison" def __init__(self, parent): AutoScript.__init__(self) self._autoscript_init = False self.parent = parent self.poi = POI(self) self.poiDock = CWMainGUI.getInstance().addDock( self.poi, "Partition Comparison POI Table", area=Qt.TopDockWidgetArea) self.poiDock.hide() self.defineName() self._traces = None self.api = CWCoreAPI.getInstance() self.graph = GraphWidget() self.bselection = QToolBar() self.graph.addWidget(self.bselection) self.graphDock = CWMainGUI.getInstance().addDock( self.graph, "Partition Comparison Graph", area=Qt.TopDockWidgetArea) self.graphDock.hide() def defineName(self): self.partObject = Partition() partModeList = {} for a in self.partObject.supportedMethods: partModeList[a.partitionType] = a self.diffObject = DifferenceMode() diffModeList = {} for a in self.diffObject.supportedMethods: diffModeList[a.differenceType] = a self.addGroup("generatePartitions") self.addGroup("generatePartitionStats") self.addGroup("generatePartitionDiffs") self.addGroup("displayPartitionDiffs") self.getParams().addChildren([ { 'name': 'Comparison Mode', 'key': 'diffmode', 'type': 'list', 'values': diffModeList, 'value': self.diffObject.diffMethodClass, 'action': lambda _: self.updateScript() }, { 'name': 'Partition Mode', 'key': 'partmode', 'type': 'list', 'values': partModeList, 'value': self.partObject.partMethodClass, 'action': lambda _: self.updateScript() }, { 'name': 'Display', 'type': 'action', 'action': lambda _: self.runAction() }, { 'name': 'Auto-Save Data to Project', 'key': 'part-saveints', 'type': 'bool', 'value': False, 'action': lambda _: self.updateScript() }, { 'name': 'Auto-Load Data from Project', 'key': 'part-loadints', 'type': 'bool', 'value': False, 'action': lambda _: self.updateScript() }, { 'name': 'Points of Interest', 'key': 'poi', 'type': 'group', 'children': [ { 'name': 'Selection Mode', 'type': 'list', 'values': { 'Max N Points/Subkey': 'maxn' }, 'value': 'maxn' }, { 'name': 'Point Range', 'key': 'poi-pointrng', 'type': 'range', 'limits': (0, 0), 'default': (0, 0), 'value': (0, 0), 'action': lambda _: self.updatePOI() }, { 'name': 'Num POI/Subkey', 'key': 'poi-nummax', 'type': 'int', 'limits': (1, 200), 'value': 1, 'action': lambda _: self.updatePOI() }, { 'name': 'Min Spacing between POI', 'key': 'poi-minspace', 'type': 'int', 'limits': (1, 100E6), 'value': 1, 'step': 100, 'action': lambda _: self.updatePOI() }, { 'name': 'Hill detection', 'key': 'poi-hilldet', 'type': 'bool', 'value': True, 'tip': "Extend the bounds downhill for each peak", 'action': lambda _: self.updatePOI() }, # {'name':'Threshold', 'key':'threshold', 'type':'int', 'visible':False}, { 'name': 'Open POI Table', 'type': 'action', 'action': lambda _: self.poiDock.show() }, ] }, ]) def updatePOI(self, _=None): self.updateScript() if self._autoscript_init == False: return # Some sort of race condition - applying Therac-25 type engineering and just # randomly hope this is enough delay QTimer.singleShot( 500, lambda: self.runScriptFunction.emit( "TraceExplorerDialog_PartitionDisplay_findPOI")) def setBytePlot(self, num, sel): self.enabledbytes[num] = sel if self.doRedraw: self.redrawPlot() def setByteAll(self, status): self.doRedraw = False for i, t in enumerate(self.byteNumAct): t.defaultWidget().setChecked(status) self.setBytePlot(i, status) self.doRedraw = True self.redrawPlot() def redrawPlot(self): self.graph.clearPushed() for bnum in range(0, self.numKeys): if self.enabledbytes[bnum]: self.graph.setColorInt(bnum, self.numKeys) self.graph.passTrace(self.SADList[bnum], pen=pg.mkPen(pg.intColor(bnum, 16)), idString=str(bnum)) def updateScript(self, ignored=None): ##Partitioning & Differences try: diffMethodStr = self.findParam('diffmode').getValue().__name__ partMethodStr = self.findParam('partmode').getValue().__name__ except AttributeError as e: return self.importsAppend( 'from chipwhisperer.analyzer.utils.Partition import PartitionRandDebug, PartitionRandvsFixed, PartitionEncKey, PartitionHWIntermediate, PartitionHDLastRound' ) self.importsAppend( 'from chipwhisperer.analyzer.utils.TraceExplorerScripts.PartitionDisplay import DifferenceModeTTest, DifferenceModeSAD' ) self.importsAppend( 'from chipwhisperer.analyzer.ui.CWAnalyzerGUI import CWAnalyzerGUI' ) self.addGroup("displayPartitionStats") self.addVariable('displayPartitionStats', 'ted', 'self.') self.addFunction('displayPartitionStats', 'setTraceSource', 'UserScript.traces', obj='ted') self.addFunction('displayPartitionStats', 'parent.getProgressIndicator', '', 'progressBar', obj='ted') self.addFunction('displayPartitionStats', 'partObject.setPartMethod', partMethodStr, obj='ted') self.addFunction('displayPartitionStats', 'partObject.generatePartitions', 'saveFile=True, loadFile=False', 'partData', obj='ted') self.addFunction( 'displayPartitionStats', 'generatePartitionStats', 'partitionData={"partclass":%s, "partdata":partData}, saveFile=True, progressBar=progressBar' % partMethodStr, 'partStats', obj='ted') self.addFunction( 'displayPartitionStats', 'generatePartitionDiffs', '%s, statsInfo={"partclass":%s, "stats":partStats}, saveFile=True, loadFile=False, progressBar=progressBar' % (diffMethodStr, partMethodStr), 'partDiffs', obj='ted') self.addFunction('displayPartitionStats', 'displayPartitions', 'differences={"partclass":%s, "diffs":partDiffs}' % partMethodStr, obj='ted') self.addFunction('displayPartitionStats', 'poi.setDifferences', 'partDiffs', obj='ted') ##Points of Interest ptrng = self.findParam(["Points of Interest", 'poi-pointrng']).getValue() self.addGroup("findPOI") self.addVariable('findPOI', 'ted', 'self.') self.addFunction( 'findPOI', 'poi.calcPOI', 'numMax=%d, pointRange=(%d, %d), minSpace=%d' % (self.findParam(["Points of Interest", 'poi-nummax' ]).getValue(), ptrng[0], ptrng[1], self.findParam(["Points of Interest", 'poi-minspace' ]).getValue()), obj='ted') #Check if this updateScript was called as a result of showing the TraceExplorer window if ignored == "traceexplorer_show": self._autoscript_init = True def generatePartitionStats(self, partitionData={ "partclass": None, "partdata": None }, saveFile=False, loadFile=False, tRange=(0, -1), progressBar=None): traces = self._traces if tRange[1] < 0: tRange = (tRange[0], traces.numTraces() + 1 + tRange[1]) self.partObject.setPartMethod(partitionData["partclass"]) self.numKeys = len( self.partObject.partMethod.getPartitionNum(traces, 0)) numPoints = traces.numPoints() if loadFile: cfgsecs = self.api.project().getDataConfig( sectionName="Trace Statistics", subsectionName="Total Trace Statistics") foundsecs = [] for cfg in cfgsecs: desiredsettings = {} desiredsettings["tracestart"] = tRange[0] desiredsettings["traceend"] = tRange[1] desiredsettings[ "partitiontype"] = self.partObject.partMethod.__class__.__name__ if self.api.project().checkDataConfig(cfg, desiredsettings): foundsecs.append(cfg) else: foundsecs = [] if len(foundsecs) > 1: IOError("Too many sections!!!") elif len(foundsecs) == 1: fname = self.api.project().convertDataFilepathAbs( foundsecs[0]["filename"]) stats = np.load(fname) else: # Array to hold average + stddev of all traces/partitions A_k = [] A_j = [] Q_k = [] dataArrays = [A_k, A_j, Q_k] ACnt = [] for bnum in range(0, self.numKeys): for d in dataArrays: d.append([]) ACnt.append([]) for i in range(0, self.partObject.partMethod.getNumPartitions()): for d in dataArrays: d[bnum].append(np.zeros(numPoints)) ACnt[bnum].append(0) # Get segment list segList = traces.getSegmentList() if progressBar: progressBar.setWindowTitle("Phase 1: Trace Statistics") progressBar.setMaximum( bnum) # len(segList['offsetList']) * self.numKeys) progressBar.show() # TODO: Double-check this fix # for tsegn, segtrace in enumerate(segList['offsetList']): tsegn = 0 if progressBar: progressBar.setText("Segment %d" % tsegn) # Average data needs to be calculated # Require partition list partData = partitionData["partdata"] # print "Calculating Average + Std-Dev" # Std-Dev calculation: # A[0] = 0 # A[k] = A[k-1] + (x[k] - A[k-1]) / k # Q[0] = 0 # Q[k] = Q[k-1] + (x[k] - A[k-1])(x[k] - A[k]) for bnum in range(0, self.numKeys): progressBar.updateStatus(tsegn * self.numKeys + bnum) if progressBar.wasAborted(): break for i in range(0, self.partObject.partMethod.getNumPartitions()): util.updateUI() tlist = partData[bnum][i] if len(tlist) > 0: for tnum in tlist: if tnum > tRange[0] and tnum < tRange[1]: t = traces.getTrace(tnum) ACnt[bnum][i] += 1 A_k[bnum][i] = A_k[bnum][i] + ( t - A_j[bnum][i]) / ACnt[bnum][i] Q_k[bnum][i] = Q_k[bnum][i] + ( (t - A_j[bnum][i]) * (t - A_k[bnum][i])) A_j[bnum][i] = A_k[bnum][i] if progressBar.wasAborted(): progressBar.hide() return # Finally get variance for bnum in range(0, self.numKeys): for i in range(0, self.partObject.partMethod.getNumPartitions()): # TODO: Should be using population variance or sample variance (e.g. /n or /n-1)? # Since this is taken over very large sample sizes I imagine it won't matter # ultimately. Q_k[bnum][i] = Q_k[bnum][i] / max(ACnt[bnum][i] - 1, 1) # Average is in A_k stats = {"mean": A_k, "variance": Q_k, "number": ACnt} # Wasn't cancelled - save this to project file for future use if requested if saveFile: progressBar.setText("Saving Mean/Variance Partitions") cfgsec = self.api.project().addDataConfig( sectionName="Trace Statistics", subsectionName="Total Trace Statistics") cfgsec["tracestart"] = tRange[0] cfgsec["traceend"] = tRange[1] cfgsec[ "partitiontype"] = self.partObject.partMethod.__class__.__name__ fname = self.api.project().getDataFilepath( 'tracestats-%s-%d-%s.npz' % (cfgsec["partitiontype"], tRange[0], tRange[1]), 'analysis') # Save mean/variance for trace np.savez(fname["abs"], mean=A_k, variance=Q_k, number=ACnt) cfgsec["filename"] = fname["rel"] progressBar.hide() return stats def generatePartitionDiffs(self, diffModule, statsInfo={ "partclass": None, "stats": None }, saveFile=False, loadFile=False, tRange=(0, -1), progressBar=None): traces = self._traces if tRange[1] < 0: tRange = (tRange[0], traces.numTraces() + 1 + tRange[1]) self.partObject.setPartMethod(statsInfo["partclass"]) self.diffObject.setDiffMethod(diffModule) self.numKeys = len( self.partObject.partMethod.getPartitionNum(traces, 0)) numPoints = traces.numPoints() foundsecs = [] if loadFile: cfgsecs = self.api.project().getDataConfig( sectionName="Trace Statistics", subsectionName="Partition Differences") for cfg in cfgsecs: desiredsettings = {} desiredsettings["tracestart"] = tRange[0] desiredsettings["traceend"] = tRange[1] desiredsettings[ "partitiontype"] = self.partObject.partMethod.__class__.__name__ desiredsettings[ "comparetype"] = self.diffObject.mode.moduleName if self.api.project().checkDataConfig(cfg, desiredsettings): foundsecs.append(cfg) else: cfgsecs = [] if len(foundsecs) > 1: IOError("Too many sections!!!") elif len(foundsecs) == 1: fname = self.api.project().convertDataFilepathAbs( foundsecs[0]["filename"]) SADList = np.load(fname) else: progressBar.setWindowTitle( "Phase 2: Calculating Partition Differences") progressBar.setText("Calculating all Differences based on " + self.diffObject.mode.differenceType) SADList = self.diffObject.difference( self.numKeys, self.partObject.partMethod.getNumPartitions(), None, numPoints, statsInfo["stats"], progressBar) if saveFile: cfgsec = self.api.project().addDataConfig( sectionName="Trace Statistics", subsectionName="Partition Differences") cfgsec["tracestart"] = tRange[0] cfgsec["traceend"] = tRange[1] cfgsec[ "partitiontype"] = self.partObject.partMethod.__class__.__name__ cfgsec["comparetype"] = self.diffObject.mode.moduleName fname = self.api.project().getDataFilepath( 'partdiffs-%s-%s-%d-%s.npy' % (cfgsec["partitiontype"], cfgsec["comparetype"], tRange[0], tRange[1]), 'analysis') np.save(fname["abs"], SADList) cfgsec["filename"] = fname["rel"] progressBar.updateStatus(progressBar.maximum) progressBar.setWindowTitle('Debug Fail') if progressBar.wasAborted(): return self.SADList = SADList return SADList def displayPartitions(self, differences={ "partclass": None, "diffs": None }, tRange=(0, -1)): self.graphDock.show() traces = self._traces if tRange[1] < 0: tRange = (tRange[0], traces.numTraces() + 1 + tRange[1]) self.partObject.setPartMethod(differences["partclass"]) self.numKeys = len( self.partObject.partMethod.getPartitionNum(traces, 0)) self.SADList = differences["diffs"] # Place byte selection option on graph if hasattr(self, 'enabledbytes') and len( self.enabledbytes) == self.numKeys: pass else: self.enabledbytes = [False] * self.numKeys self.doRedraw = True self.byteNumAct = [] for i in range(0, self.numKeys): ql = QToolButton() ql.setText('%d' % i) color = pg.intColor(i, self.numKeys) ql.setStyleSheet("color: rgb(%d, %d, %d)" % (color.red(), color.green(), color.blue())) qa = QWidgetAction(self.graph) qa.setDefaultWidget(ql) qa.setStatusTip('%d' % i) ql.setCheckable(True) ql.setChecked(self.enabledbytes[i]) ql.clicked[bool].connect(partial(self.setBytePlot, i)) self.byteNumAct.append(qa) byteNumAllOn = QAction('All On', self.graph) byteNumAllOff = QAction('All Off', self.graph) byteNumAllOn.triggered.connect(partial(self.setByteAll, True)) byteNumAllOff.triggered.connect(partial(self.setByteAll, False)) self.bselection.clear() for i in range(0, self.numKeys): self.bselection.addAction(self.byteNumAct[i]) self.bselection.addAction(byteNumAllOn) self.bselection.addAction(byteNumAllOff) self.graph.setPersistance(True) self.poi.setDifferences(self.SADList) self.findParam(["Points of Interest", 'poi-pointrng']).setLimits( (0, len(self.SADList[0]))) self.findParam(["Points of Interest", 'poi-pointrng']).setValue( (0, len(self.SADList[0]))) self.redrawPlot() def runAction(self): self.runScriptFunction.emit( 'TraceExplorerDialog_PartitionDisplay_displayPartitionStats') def setTraceSource(self, traces): self._traces = traces self.partObject.setTraceSource(self._traces)
def addTraces(self, tracedata, tracerange, progressBar=None, pointRange=None, attack=None): # NOTE: this is for calling substuff, not for legacy code here! if pointRange is None: pointRange = (0,-1) #--- roundwidth = 0 if attack is not None: round528 = attack.keeloq_round528 roundwidth = attack.keeloq_roundwidth # print "Using KEELOQ timing values from attack: %d %d" % (round528, roundwidth) if self.config['roundtiming'] != True: roundwidth = 0 #--- prepare environment partClass = keeloqPartition_CiphertextMSB partObject = Partition() partObject.setPartMethod(partClass) partObject.setTraceSource(tracedata) partDisplay = PartitionDisplay(None) partDisplay._traces = tracedata if progressBar: progressBar.setMaximum(64 + 1) #--- loop for all unknown keybits keystream = "" for bit in range(0, 64): #--- Analize all possible values (0 and 1) guessNum = 2 guessDiffs = [] * guessNum #--- Partition and find differences using T-Test if bit==48: print "\nTODO: Sorry for becoming slower, the cipher implementation has not been optimized yet.\n" for guessBit in range (0, guessNum): # Probe known keystream + guessbit + dummybit # # The dummybit is necessary because of a property of Keeloq. The last bit of the stream is # mixed into the attacked data bit with XOR. This results in exactly complementary partitioning # for values 0 vs 1, making it impossible to pick a "winner". Therefore we append the dummybit # at this position, so that guessbit is the last input that actually makes a measureable difference. partData = partObject.generatePartitions(partitionClass=None, saveFile=False, loadFile=False, tRange=tracerange, partitionConfig={"keystream":"%s %d 0" % (keystream, guessBit)}) partStats = partDisplay.generatePartitionStats(partitionData={"partclass":partClass, "partdata":partData}, saveFile=False, loadFile=False, tRange=tracerange, pointRange=pointRange) partDiffs = partDisplay.generatePartitionDiffs(DifferenceModeTTest, statsInfo={"partclass":partClass, "stats":partStats}, saveFile=False, loadFile=False, tRange=tracerange) guessDiffs.append(partDiffs[0]) # TODO: since we calculate 0 and 1 anyway we should call the partition mode just once and use a mode that produces 2 keys (0,1) #--- Determine range in which high correlation is expected # TODO: for strict round timing, it is faster to do partStats only for the desired pointRange if roundwidth <= 0: # look in whole pointRange lookStart = 0 lookStop = len(guessDiffs[0]) else: # look only at expected position (enforce round timing) round = 528 - 32 - (bit+1) roundStart = round528 - ((528-round) * roundwidth) roundStop = roundStart + roundwidth lookStart = roundStart - pointRange[0] lookStop = roundStop - pointRange[0] # print "Round=%d Pos=%d-%d look=%d-%d" % (round, roundStart, roundStop, lookStart, lookStop) if (lookStart < 0) or (lookStop > len(guessDiffs[0])): print "Enforced round timing range (%d,%d) is outside of analyzed pointRange(%d,%d). "\ "Can't detect key bit %d. Result so far: %s" %\ (roundStart, roundStop, pointRange[0], pointRange[1], bit, keystream) return #--- Detect highest correlation # print "Looking at range %d-%d of %d" % (lookStart, lookStop, len(guessDiffs[0])) # if bit == 0: # print guessDiffs[0][lookStart:lookStop] # print guessDiffs[1][lookStart:lookStop] winBit = 0 if (np.nanmax(guessDiffs[0][lookStart:lookStop]) > np.nanmax(guessDiffs[1][lookStart:lookStop])) else 1 badBit = winBit ^ 1 winOffset = np.argmax(guessDiffs[winBit][lookStart:lookStop]) + lookStart badOffset = np.argmax(guessDiffs[badBit][lookStart:lookStop]) + lookStart winDiff = guessDiffs[winBit][winOffset] badDiff = guessDiffs[badBit][badOffset] winRatioSpot = winDiff / guessDiffs[badBit][winOffset] winRatioTrace = winDiff / badDiff keystream = "%s%d" % (keystream, winBit) #--- report print "Analysis of keybit #%02d (of 64): %d (diff=%f spot=%f trace=%f pos=%d) vs %d (diff=%f pos=%d)" %\ (bit, winBit, winDiff, winRatioSpot, winRatioTrace, winOffset + pointRange[0], badBit, badDiff, badOffset + pointRange[0]) if progressBar: progressBar.setText("Attacking key bits (%d of 64)" % bit) progressBar.setStatusMask("key=%s\nkeystream=%s" % (keeloqFormatKeystream(keystream), keystream)) progressBar.updateStatus(bit) if progressBar.wasAborted(): print "Aborting. Result so far: %s" % keystream return #--- keyStr = "%s%s" % (keystream[16:64], keystream[0:16]) keyInt = int(keyStr, 2) print "Keystream = %s" % keystream print "Key: %016x" % keyInt if progressBar: progressBar.setText("Attack finished") progressBar.setStatusMask("Key: %016x" % keyInt) progressBar.updateStatus(64) #--- Decrypt the data associated with all traces progress = 64 if True: start = tracerange[0] end = tracerange[1] if end == -1: end = tracedata.numTraces() tnum = start while tnum < end: t = tracedata.getSegment(tnum) # Discover where this trace starts & ends tmapstart = t.mappedRange[0] tmapend = t.mappedRange[1] if progressBar: increment = tmapend + 1 - tmapstart progressBar.setMaximum(progress + increment + 1) progressBar.setText("Decrypting traces") for tnum in range(tmapstart, tmapend + 1): if progressBar: progressBar.updateStatus(progress) progress += 1 if progressBar.wasAborted(): return textout = t.getTextout(tnum - tmapstart) if (textout is not None) and (len(textout) >= 4): data = (textout[0] << 24) | (textout[1] << 16) | (textout[2] << 8) | (textout[3] << 0) decrypt = keeloqDecrypt(data, keyInt) print "Trace %0d: %08x -> %08x" % (tnum - tmapstart, data, decrypt) tnum = tmapend + 1 print "Key: %016x" % keyInt #--- cleanup if progressBar: progressBar.setMaximum(100) progressBar.updateStatus(100)
class PartitionDisplay(Parameterized, AutoScript): _name = "Partition Comparison" def __init__(self, parent): AutoScript.__init__(self) self._autoscript_init = False self.parent = parent self.poi = POI(self) self.poiDock = CWMainGUI.getInstance().addDock(self.poi, "Partition Comparison POI Table", area=Qt.TopDockWidgetArea) self.poiDock.hide() self.defineName() self._traces = None self.api = CWCoreAPI.getInstance() self.graph = GraphWidget() self.bselection = QToolBar() self.graph.addWidget(self.bselection) self.graphDock = CWMainGUI.getInstance().addDock(self.graph, "Partition Comparison Graph", area=Qt.TopDockWidgetArea) self.graphDock.hide() def defineName(self): self.partObject = Partition(self) partModeList = {} for a in self.partObject.supportedMethods: partModeList[a.partitionType] = a self.diffObject = DifferenceMode() diffModeList = {} for a in self.diffObject.supportedMethods: diffModeList[a.differenceType] = a self.addGroup("generatePartitions") self.addGroup("generatePartitionStats") self.addGroup("generatePartitionDiffs") self.addGroup("displayPartitionDiffs") self.getParams().addChildren([ {'name':'Comparison Mode', 'key':'diffmode', 'type':'list', 'values':diffModeList, 'value':self.diffObject.diffMethodClass, 'action':lambda _: self.updateScript()}, {'name':'Partition Mode', 'key':'partmode', 'type':'list', 'values':partModeList, 'value':self.partObject.partMethodClass, 'action':lambda _: self.updateScript()}, {'name':'Display', 'type':'action', 'action':lambda _:self.runAction()}, {'name':'Auto-Save Data to Project', 'key':'part-saveints', 'type':'bool', 'value':False, 'action':lambda _: self.updateScript()}, {'name':'Auto-Load Data from Project', 'key':'part-loadints', 'type':'bool', 'value':False, 'action':lambda _: self.updateScript()}, {'name':'Points of Interest', 'key':'poi', 'type':'group', 'children':[ {'name':'Selection Mode', 'type':'list', 'values':{'Max N Points/Subkey':'maxn'}, 'value':'maxn'}, {'name':'Point Range', 'key':'poi-pointrng', 'type':'range', 'limits':(0, 0), 'default':(0, 0), 'value':(0, 0), 'action':lambda _: self.updatePOI()}, {'name':'Num POI/Subkey', 'key':'poi-nummax', 'type':'int', 'limits':(1, 200), 'value':1, 'action':lambda _: self.updatePOI()}, {'name':'Min Spacing between POI', 'key':'poi-minspace', 'type':'int', 'limits':(1, 100E6), 'value':1, 'step':100, 'action':lambda _: self.updatePOI()}, {'name':'Hill detection', 'key':'poi-hilldet', 'type':'bool', 'value':True, 'tip':"Extend the bounds downhill for each peak", 'action':lambda _: self.updatePOI()}, # {'name':'Threshold', 'key':'threshold', 'type':'int', 'visible':False}, {'name':'Open POI Table', 'type':'action', 'action':lambda _: self.poiDock.show()}, ]}, ]) def updatePOI(self, ignored=None): self.updateScript() if self._autoscript_init == False: return # Some sort of race condition - applying Therac-25 type engineering and just # randomly hope this is enough delay QTimer.singleShot(500, lambda:self.runScriptFunction.emit("TraceExplorerDialog_PartitionDisplay_findPOI")) def setBytePlot(self, num, sel): self.enabledbytes[num] = sel if self.doRedraw: self.redrawPlot() def setByteAll(self, status): self.doRedraw = False for i, t in enumerate(self.byteNumAct): t.defaultWidget().setChecked(status) self.setBytePlot(i, status) self.doRedraw = True self.redrawPlot() def redrawPlot(self): self.graph.clearPushed() for bnum in range(0, self.numKeys): if self.enabledbytes[bnum]: self.graph.setColorInt(bnum, self.numKeys) self.graph.passTrace(self.SADList[bnum], pen=pg.mkPen(pg.intColor(bnum, 16)), idString = str(bnum)) def updateScript(self, ignored=None): ##Partitioning & Differences try: diffMethodStr = self.findParam('diffmode').getValue().__name__ partMethodStr = self.findParam('partmode').getValue().__name__ except AttributeError as e: return self.importsAppend('from chipwhisperer.analyzer.utils.Partition import PartitionRandDebug, PartitionRandvsFixed, PartitionEncKey, PartitionHWIntermediate, PartitionHDLastRound') self.importsAppend('from chipwhisperer.analyzer.utils.TraceExplorerScripts.PartitionDisplay import DifferenceModeTTest, DifferenceModeSAD') self.importsAppend('from chipwhisperer.analyzer.ui.CWAnalyzerGUI import CWAnalyzerGUI') self.addGroup("displayPartitionStats") self.addVariable('displayPartitionStats', 'ted', 'self.') self.addFunction('displayPartitionStats', 'setTraceSource', 'UserScript.traces', obj='ted') self.addFunction('displayPartitionStats', 'parent.getProgressIndicator', '', 'progressBar', obj='ted') self.addFunction('displayPartitionStats', 'partObject.setPartMethod', partMethodStr, obj='ted') self.addFunction('displayPartitionStats', 'partObject.generatePartitions', 'saveFile=True, loadFile=False', 'partData', obj='ted') self.addFunction('displayPartitionStats', 'generatePartitionStats', 'partitionData={"partclass":%s, "partdata":partData}, saveFile=True, progressBar=progressBar' % partMethodStr, 'partStats', obj='ted') self.addFunction('displayPartitionStats', 'generatePartitionDiffs', '%s, statsInfo={"partclass":%s, "stats":partStats}, saveFile=True, loadFile=False, progressBar=progressBar'% (diffMethodStr, partMethodStr), 'partDiffs', obj='ted') self.addFunction('displayPartitionStats', 'displayPartitions', 'differences={"partclass":%s, "diffs":partDiffs}' % partMethodStr, obj='ted') self.addFunction('displayPartitionStats', 'poi.setDifferences', 'partDiffs', obj='ted') ##Points of Interest ptrng = self.findParam(["Points of Interest",'poi-pointrng']).getValue() self.addGroup("findPOI") self.addVariable('findPOI', 'ted', 'self.') self.addFunction('findPOI', 'poi.calcPOI', 'numMax=%d, pointRange=(%d, %d), minSpace=%d' % ( self.findParam(["Points of Interest",'poi-nummax']).getValue(), ptrng[0], ptrng[1], self.findParam(["Points of Interest",'poi-minspace']).getValue()), obj='ted') #Check if this updateScript was called as a result of showing the TraceExplorer window if ignored == "traceexplorer_show": self._autoscript_init = True def generatePartitionStats(self, partitionData={"partclass":None, "partdata":None}, saveFile=False, loadFile=False, tRange=(0, -1), progressBar=None): traces = self._traces if tRange[1] < 0: tRange = (tRange[0], traces.numTraces() + 1 + tRange[1]) self.partObject.setPartMethod(partitionData["partclass"]) self.numKeys = len(self.partObject.partMethod.getPartitionNum(traces, 0)) numPoints = traces.numPoints() if loadFile: cfgsecs = self.api.project().getDataConfig(sectionName="Trace Statistics", subsectionName="Total Trace Statistics") foundsecs = [] for cfg in cfgsecs: desiredsettings = {} desiredsettings["tracestart"] = tRange[0] desiredsettings["traceend"] = tRange[1] desiredsettings["partitiontype"] = self.partObject.partMethod.__class__.__name__ if self.api.project().checkDataConfig(cfg, desiredsettings): foundsecs.append(cfg) else: foundsecs = [] if len(foundsecs) > 1: IOError("Too many sections!!!") elif len(foundsecs) == 1: fname = self.api.project().convertDataFilepathAbs(foundsecs[0]["filename"]) stats = np.load(fname) else: # Array to hold average + stddev of all traces/partitions A_k = [] A_j = [] Q_k = [] dataArrays = [A_k, A_j, Q_k] ACnt = [] for bnum in range(0, self.numKeys): for d in dataArrays: d.append([]) ACnt.append([]) for i in range(0, self.partObject.partMethod.getNumPartitions()): for d in dataArrays: d[bnum].append(np.zeros(numPoints)) ACnt[bnum].append(0) # Get segment list segList = traces.getSegmentList() if progressBar: progressBar.setWindowTitle("Phase 1: Trace Statistics") progressBar.setMaximum(bnum) # len(segList['offsetList']) * self.numKeys) progressBar.show() # TODO: Double-check this fix # for tsegn, segtrace in enumerate(segList['offsetList']): tsegn = 0 if progressBar: progressBar.setText("Segment %d" % tsegn) # Average data needs to be calculated # Require partition list partData = partitionData["partdata"] # print "Calculating Average + Std-Dev" # Std-Dev calculation: # A[0] = 0 # A[k] = A[k-1] + (x[k] - A[k-1]) / k # Q[0] = 0 # Q[k] = Q[k-1] + (x[k] - A[k-1])(x[k] - A[k]) for bnum in range(0, self.numKeys): progressBar.updateStatus(tsegn * self.numKeys + bnum) if progressBar.wasAborted(): break for i in range(0, self.partObject.partMethod.getNumPartitions()): util.updateUI() tlist = partData[bnum][i] if len(tlist) > 0: for tnum in tlist: if tnum > tRange[0] and tnum < tRange[1]: t = traces.getTrace(tnum) ACnt[bnum][i] += 1 A_k[bnum][i] = A_k[bnum][i] + (t - A_j[bnum][i]) / ACnt[bnum][i] Q_k[bnum][i] = Q_k[bnum][i] + ((t - A_j[bnum][i]) * (t - A_k[bnum][i])) A_j[bnum][i] = A_k[bnum][i] if progressBar.wasAborted(): progressBar.hide() return # Finally get variance for bnum in range(0, self.numKeys): for i in range(0, self.partObject.partMethod.getNumPartitions()): # TODO: Should be using population variance or sample variance (e.g. /n or /n-1)? # Since this is taken over very large sample sizes I imagine it won't matter # ultimately. Q_k[bnum][i] = Q_k[bnum][i] / max(ACnt[bnum][i] - 1, 1) # Average is in A_k stats = {"mean":A_k, "variance":Q_k, "number":ACnt} # Wasn't cancelled - save this to project file for future use if requested if saveFile: progressBar.setText("Saving Mean/Variance Partitions") cfgsec = self.api.project().addDataConfig(sectionName="Trace Statistics", subsectionName="Total Trace Statistics") cfgsec["tracestart"] = tRange[0] cfgsec["traceend"] = tRange[1] cfgsec["partitiontype"] = self.partObject.partMethod.__class__.__name__ fname = self.api.project().getDataFilepath('tracestats-%s-%d-%s.npz' % (cfgsec["partitiontype"], tRange[0], tRange[1]), 'analysis') # Save mean/variance for trace np.savez(fname["abs"], mean=A_k, variance=Q_k, number=ACnt) cfgsec["filename"] = fname["rel"] progressBar.hide() return stats def generatePartitionDiffs(self, diffModule, statsInfo={"partclass":None, "stats":None}, saveFile=False, loadFile=False, tRange=(0, -1), progressBar=None): traces = self._traces if tRange[1] < 0: tRange = (tRange[0], traces.numTraces() + 1 + tRange[1]) self.partObject.setPartMethod(statsInfo["partclass"]) self.diffObject.setDiffMethod(diffModule) self.numKeys = len(self.partObject.partMethod.getPartitionNum(traces, 0)) numPoints = traces.numPoints() foundsecs = [] if loadFile: cfgsecs = self.api.project().getDataConfig(sectionName="Trace Statistics", subsectionName="Partition Differences") for cfg in cfgsecs: desiredsettings = {} desiredsettings["tracestart"] = tRange[0] desiredsettings["traceend"] = tRange[1] desiredsettings["partitiontype"] = self.partObject.partMethod.__class__.__name__ desiredsettings["comparetype"] = self.diffObject.mode.moduleName if self.api.project().checkDataConfig(cfg, desiredsettings): foundsecs.append(cfg) else: cfgsecs = [] if len(foundsecs) > 1: IOError("Too many sections!!!") elif len(foundsecs) == 1: fname = self.api.project().convertDataFilepathAbs(foundsecs[0]["filename"]) SADList = np.load(fname) else: progressBar.setWindowTitle("Phase 2: Calculating Partition Differences") progressBar.setText("Calculating all Differences based on " + self.diffObject.mode.differenceType) SADList = self.diffObject.difference(self.numKeys, self.partObject.partMethod.getNumPartitions(), None, numPoints, statsInfo["stats"], progressBar) if saveFile: cfgsec = self.api.project().addDataConfig(sectionName="Trace Statistics", subsectionName="Partition Differences") cfgsec["tracestart"] = tRange[0] cfgsec["traceend"] = tRange[1] cfgsec["partitiontype"] = self.partObject.partMethod.__class__.__name__ cfgsec["comparetype"] = self.diffObject.mode.moduleName fname = self.api.project().getDataFilepath('partdiffs-%s-%s-%d-%s.npy' % (cfgsec["partitiontype"], cfgsec["comparetype"], tRange[0], tRange[1]), 'analysis') np.save(fname["abs"], SADList) cfgsec["filename"] = fname["rel"] progressBar.updateStatus(progressBar.maximum) progressBar.setWindowTitle('Debug Fail') if progressBar.wasAborted(): return self.SADList = SADList return SADList def displayPartitions(self, differences={"partclass":None, "diffs":None}, tRange=(0, -1)): self.graphDock.show() traces = self._traces if tRange[1] < 0: tRange = (tRange[0], traces.numTraces() + 1 + tRange[1]) self.partObject.setPartMethod(differences["partclass"]) self.numKeys = len(self.partObject.partMethod.getPartitionNum(traces, 0)) self.SADList = differences["diffs"] # Place byte selection option on graph if hasattr(self, 'enabledbytes') and len(self.enabledbytes) == self.numKeys: pass else: self.enabledbytes = [False] * self.numKeys self.doRedraw = True self.byteNumAct = [] for i in range(0, self.numKeys): ql = QToolButton() ql.setText('%d' % i) color = pg.intColor(i, self.numKeys) ql.setStyleSheet("color: rgb(%d, %d, %d)" % (color.red(), color.green(), color.blue())) qa = QWidgetAction(self.graph) qa.setDefaultWidget(ql) qa.setStatusTip('%d' % i) ql.setCheckable(True) ql.setChecked(self.enabledbytes[i]) ql.clicked[bool].connect(partial(self.setBytePlot, i)) self.byteNumAct.append(qa) byteNumAllOn = QAction('All On', self.graph) byteNumAllOff = QAction('All Off', self.graph) byteNumAllOn.triggered.connect(partial(self.setByteAll, True)) byteNumAllOff.triggered.connect(partial(self.setByteAll, False)) self.bselection.clear() for i in range(0, self.numKeys): self.bselection.addAction(self.byteNumAct[i]) self.bselection.addAction(byteNumAllOn) self.bselection.addAction(byteNumAllOff) self.graph.setPersistance(True) self.poi.setDifferences(self.SADList) self.findParam(["Points of Interest",'poi-pointrng']).setLimits((0, len(self.SADList[0]))) self.findParam(["Points of Interest",'poi-pointrng']).setValue((0, len(self.SADList[0]))) self.redrawPlot() def runAction(self): self.runScriptFunction.emit('TraceExplorerDialog_PartitionDisplay_displayPartitionStats') def setTraceSource(self, traces): self._traces = traces self.partObject.setTraceSource(self._traces)