mirror of
https://github.com/maride/barf.git
synced 2026-04-17 04:25:46 +00:00
Add persistence mode
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from Helper import *
|
||||
from TargetManager import TargetManager
|
||||
|
||||
# The charset to try, sorted by the likelihood of a character class
|
||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!?'#%+/ ;[`@-\".<,*|&$(]=)^>\\:~"
|
||||
|
||||
# bruteforces a single character, sandwiched between the known parts.
|
||||
# Returns the most promising string.
|
||||
def BruteforceChar(bm, knownPrefix, knownSuffix, chunksize):
|
||||
def BruteforceChar(bm, tm, knownPrefix, knownSuffix, chunksize):
|
||||
# keyFragment is the variable were we store our found-to-be-correct chars
|
||||
keyFragment = ""
|
||||
|
||||
@@ -19,7 +20,7 @@ def BruteforceChar(bm, knownPrefix, knownSuffix, chunksize):
|
||||
# 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()
|
||||
TryInput(knownPrefix + keyFragment + "^" * chunksize + knownSuffix)
|
||||
tm.Run(knownPrefix + keyFragment + "^" * chunksize + knownSuffix)
|
||||
refScore = bm.PopScore()
|
||||
|
||||
# iterate over every character in the charset
|
||||
@@ -29,7 +30,7 @@ def BruteforceChar(bm, knownPrefix, knownSuffix, chunksize):
|
||||
|
||||
# and try it
|
||||
bm.ResetBreakpoints()
|
||||
TryInput(inp)
|
||||
tm.Run(inp)
|
||||
score = bm.PopScore()
|
||||
|
||||
# yay, that's a hit
|
||||
@@ -45,9 +46,9 @@ def BruteforceChar(bm, knownPrefix, knownSuffix, chunksize):
|
||||
# Bruteforce calls BruteforceChar until:
|
||||
# - BruteforceChar was unable to increase the score using any character in the charset, OR
|
||||
# - the "win" breakpoint is hit :)
|
||||
def Bruteforce(bm, knownPrefix, knownSuffix, chunksize):
|
||||
def Bruteforce(bm, tm, knownPrefix, knownSuffix, chunksize):
|
||||
while True:
|
||||
res = BruteforceChar(bm, knownPrefix, knownSuffix, chunksize)
|
||||
res = BruteforceChar(bm, tm, knownPrefix, knownSuffix, chunksize)
|
||||
if res is False:
|
||||
# no character from the given charset matched. :(
|
||||
EnableLogging()
|
||||
|
||||
23
src/CheckpointBreakpoint.py
Normal file
23
src/CheckpointBreakpoint.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gdb
|
||||
|
||||
# A simple breakpoint to set a checkpoint at the given address
|
||||
# After that, it doesn't do a thing anymore
|
||||
class CheckpointBreakpoint(gdb.Breakpoint):
|
||||
# The address to break on
|
||||
isSet = False
|
||||
|
||||
def __init__(self, startAddr):
|
||||
# gdb requires address literals to start with a star
|
||||
if startAddr[0] != "*":
|
||||
startAddr = "*" + startAddr
|
||||
|
||||
super().__init__(startAddr)
|
||||
|
||||
def stop(self):
|
||||
if not self.isSet:
|
||||
gdb.execute("checkpoint")
|
||||
self.isSet = True
|
||||
return False
|
||||
|
||||
@@ -9,16 +9,12 @@ def EnableLogging():
|
||||
|
||||
# Disables the typical GDB spam
|
||||
def DisableLogging():
|
||||
return
|
||||
gdb.execute("set logging file /dev/null")
|
||||
gdb.execute("set logging redirect on")
|
||||
gdb.execute("set logging on")
|
||||
|
||||
|
||||
# Runs a given input through GDB
|
||||
def TryInput(inp):
|
||||
gdb.execute(f"run 2>/dev/null 1>&2 <<< $(echo '{inp}')")
|
||||
|
||||
|
||||
# Prints a small MOTD, hence the name of the function
|
||||
def MOTD():
|
||||
print("+--------------------------------------------+")
|
||||
|
||||
30
src/PersistenceBreakpoint.py
Normal file
30
src/PersistenceBreakpoint.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gdb
|
||||
|
||||
# A breakpoint class with reset-to-checkpoint-magic
|
||||
# "You hit it? We reset it."
|
||||
class PersistenceBreakpoint(gdb.Breakpoint):
|
||||
targetManager = None
|
||||
|
||||
# tm is apointer to TargetManager so we can Reset() the executable
|
||||
# endAddr is the address on which we want to jump back
|
||||
def __init__(self, tm, endAddr):
|
||||
self.targetManager = tm
|
||||
|
||||
# gdb requires address literals to start with a star
|
||||
if endAddr[0] != "*":
|
||||
endAddr = "*" + endAddr
|
||||
|
||||
# actually create breakpoint
|
||||
super().__init__(endAddr)
|
||||
|
||||
# avoid spamming "Breakpoint X, ..." when hit
|
||||
self.silent = True
|
||||
|
||||
def stop(self):
|
||||
# do the checkpoint thing
|
||||
self.targetManager.Reset()
|
||||
# we return true so we still break - we will 'continue' later, after we wrote memory.
|
||||
return True
|
||||
|
||||
109
src/TargetManager.py
Normal file
109
src/TargetManager.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gdb
|
||||
import base64
|
||||
|
||||
from CheckpointBreakpoint import CheckpointBreakpoint
|
||||
from PersistenceBreakpoint import PersistenceBreakpoint
|
||||
|
||||
# TargetManager is a wrapper around the target itself.
|
||||
# Tasks include:
|
||||
# - Implementing the persistent mode using gdb checkpoints
|
||||
# - Feeding input into the binary
|
||||
# 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:
|
||||
usePersistent = False
|
||||
|
||||
# vars used for persistent mode
|
||||
buffAddr = None
|
||||
startBreakpoint = None
|
||||
endBreakpoint = None
|
||||
checkpointIndex = 1
|
||||
isRunning = False
|
||||
|
||||
# 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):
|
||||
self.usePersistent = usePersistent
|
||||
|
||||
if usePersistent:
|
||||
# parse buffer address to int
|
||||
if buffAddr and isinstance(buffAddr, str):
|
||||
buffAddr = int(buffAddr, 16)
|
||||
self.buffAddr = buffAddr
|
||||
|
||||
# set a breakpoint which will set a checkpoint at its address, the beginning
|
||||
self.startBreakpoint = CheckpointBreakpoint(startAddr)
|
||||
# breakpoint to reset to the checkpoint on
|
||||
self.endBreakpoint = PersistenceBreakpoint(self, endAddr)
|
||||
|
||||
# Runs the binary.
|
||||
# If the persistent mode is used, your input will be written into memory directly.
|
||||
# If the persistent mode is not used, your input will just be thrown into the binary.
|
||||
def Run(self, inp):
|
||||
if not self.usePersistent:
|
||||
# not running in persistent mode, just feed the input into the binary
|
||||
if isinstance(inp, str):
|
||||
inp = inp.encode()
|
||||
# converting it to base64 is a cheap hack to avoid sanitizing the input ;)
|
||||
b = base64.b64encode(inp).decode()
|
||||
gdb.execute("run > /dev/null <<< $(echo %s | base64 -d)" % b, to_string=True)
|
||||
else:
|
||||
# we're running in persistent mode, let's see if we need to kickstart the executable
|
||||
if not self.isRunning:
|
||||
# the executable doesn't yet run, so we gotta get it groovin ;)
|
||||
# we want to have a single, clean run, so startBreakpoint can set its checkpoint
|
||||
# and endBreakpoint can do the first round of resetting. After that, we can
|
||||
# start filling our buffer and check if we come near the flag (see block below).
|
||||
# That's why we feed /dev/null into the binary: fgets and other stdin read
|
||||
# operations should terminate cleanly, avoiding hangs in the executable
|
||||
gdb.execute("set confirm off")
|
||||
gdb.execute(f"run > /dev/null < /dev/null", to_string=True)
|
||||
self.isRunning = True
|
||||
# Due to the /dev/null magic above, we haven't yet fed inp into the binary. We
|
||||
# don't want to skip it, so we simply continue with the "isRunning==True" block.
|
||||
# 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.
|
||||
|
||||
# the executable is already running and reset
|
||||
# means we just need to feed input into the binary, then continue running it
|
||||
i = gdb.inferiors()[0]
|
||||
i.write_memory(self.buffAddr, inp + "\n\0")
|
||||
gdb.execute("continue", to_string=True)
|
||||
# Reset() will be called by endBreakpoint, we don't need to do that here
|
||||
|
||||
# Resets the target back to the checkpoint set earlier, used for the persistent mode
|
||||
# Will be called by endBreakpoint
|
||||
def Reset(self):
|
||||
# Check if we reached endBreakpoint before hitting startBreakpoint
|
||||
if not self.startBreakpoint.isSet:
|
||||
print("[!] Reset() called without a startBreakpoint set.")
|
||||
print(" Check addresses of start and end!")
|
||||
return
|
||||
|
||||
# gdb seems to have a problem with more than 41885 checkpoints.
|
||||
# the ID is rising even if checkpoints are deleted on the way.
|
||||
# to mitigate this, we start from the beginning and let Run() re-start the executable.
|
||||
if self.checkpointIndex > 40000:
|
||||
self.checkpointIndex = 1
|
||||
self.isRunning = False
|
||||
self.startBreakpoint.isSet = False
|
||||
return
|
||||
|
||||
# jump back to the start
|
||||
gdb.execute("restart 1", to_string=True)
|
||||
|
||||
# delete old checkpoint if we ran before
|
||||
if self.checkpointIndex > 1:
|
||||
gdb.execute(f"delete checkpoint {self.checkpointIndex}", to_string=True)
|
||||
|
||||
# create a fresh copy of checkpoint 1
|
||||
gdb.execute("checkpoint", to_string=True)
|
||||
# ... and use it
|
||||
self.checkpointIndex += 1
|
||||
gdb.execute(f"restart {self.checkpointIndex}", to_string=True)
|
||||
|
||||
Reference in New Issue
Block a user