Properly implement calibration

This commit is contained in:
maride 2021-06-17 02:32:26 +02:00
parent 50aa2e7277
commit 6619f7b966
3 changed files with 45 additions and 13 deletions

View File

@ -41,7 +41,7 @@ def main():
bm = BreakpointManager(args["positiveAddr"], args["negativeAddr"], args["winAddr"])
# Manage the target with the TargetManager
tm = TargetManager(args["persistent"], args["startAddr"], args["endAddr"], args["buffAddr"])
tm = TargetManager(bm, args["persistent"], args["startAddr"], args["endAddr"], args["buffAddr"])
# start the bruteforcing madness ;)
Bruteforce(bm, tm, args["knownPrefix"], args["knownSuffix"], args["chunksize"])

View File

@ -14,14 +14,10 @@ def BruteforceChar(bm, tm, knownPrefix, knownSuffix, chunksize):
found = False
## detect best score
# we want to get the score for "everything correct except last character".
# we do this by combining knownPrefix + keyFragment with an "impossible" character.
# the resulting score is the base for the next round of guessing, hopefully with a single solution better than the score of knownPrefix + keyFragment + impossibleChar.
# please also note that this will massively fail if the "impossible" character is part of the flag, at the very position it was tested on ... have fun detecting that
bm.ResetBreakpoints()
tm.Run(knownPrefix + keyFragment + "^" * chunksize + knownSuffix)
refScore = bm.PopScore()
# detect best score
refScore = Calibrate(bm, tm, knownPrefix + keyFragment, knownSuffix, chunksize)
if refScore is False:
return False
# iterate over every character in the charset
for c in generateCharset(chunksize):
@ -29,9 +25,7 @@ def BruteforceChar(bm, tm, knownPrefix, knownSuffix, chunksize):
inp = knownPrefix + keyFragment + c + knownSuffix
# and try it
bm.ResetBreakpoints()
tm.Run(inp)
score = bm.PopScore()
score = RunAndScore(bm, tm, inp)
# yay, that's a hit
if score > refScore or bm.HitWin():
@ -79,6 +73,38 @@ def Bruteforce(bm, tm, knownPrefix, knownSuffix, chunksize):
return knownPrefix + knownSuffix
# Finds out the base score when filling the binary with partly correct chars (e.g. the already found-to-be-correct prefix)
# It does this by combining knownPrefix + keyFragment and knownSuffix with an "impossible" character.
# We're only able to proceed if every character (except one) returns the same score - the "except one" score is the winner ;)
# Note that this function will massively fail if the "impossible" character is part of the flag, at the very position it was tested on ... have fun detecting that
def Calibrate(bm, tm, prefix, suffix, chunksize):
score1 = RunAndScore(bm, tm, prefix + '^' * chunksize + suffix)
score2 = RunAndScore(bm, tm, prefix + '`' * chunksize + suffix)
if score1 == score2:
# we found a stable score, return it
return score1
else:
# There is some kind of inconsistency in the executable, stop here.
EnableLogging()
print(score1)
print(score2)
print("BARF was unable to calibrate.")
print("While this may have multiple reasons, the most realistic are:")
print(" - The specified binary is not solvable on a round-based approach")
print(" -> reverse the binary further - is there some shuffeling mechanism in place?")
print(" - The 'no way that character is part of the flag' charset is actually part of the flag")
DisableLogging()
return False
# Runs the given input and returns its breakpoint score
def RunAndScore(bm, tm, inp):
bm.ResetBreakpoints()
tm.Run(inp)
return bm.PopScore()
# generateCharset returns an iteratable object (string or set) to be used by the bruteforce function.
# the chunksize is the amount of characters to stuff into an entry
def generateCharset(chunksize):

View File

@ -13,6 +13,7 @@ from PersistenceBreakpoint import PersistenceBreakpoint
# The TargetManager aims to be the one-size-fits-all solution for execution handling.
# That means it is designed to have a unified interface, independent of e.g. persistent mode.
class TargetManager:
breakpointManager = None
usePersistent = False
# vars used for persistent mode
@ -22,11 +23,13 @@ class TargetManager:
checkpointIndex = 1
isRunning = False
# bm is the BreakpointManager in use
# usePersistent is a boolean, determing if the experimental persistent mode should be used
# startAddr is the address to start the persistent run
# endAddr is the address to jump back to startAddr
# buffAddr is the address of the target buffer to be written in persistent mode
def __init__(self, usePersistent, startAddr, endAddr, buffAddr):
def __init__(self, bm, usePersistent, startAddr, endAddr, buffAddr):
self.breakpointManager = bm
self.usePersistent = usePersistent
if usePersistent:
@ -68,6 +71,9 @@ class TargetManager:
# Please note, as this may cast some confusion, that at this point the binary
# had a full run-thru, is equipped with checkpoints and breakpoints and is
# currently in break mode (not running, so to speak), and is at startAddr.
# But, because we likely already hit some breakpoints (depends on the executable),
# we need to reset the scores.
self.breakpointManager.ResetBreakpoints()
# the executable is already running and reset
# means we just need to feed input into the binary, then continue running it