mirror of
https://github.com/maride/barf.git
synced 2024-12-22 06:27:29 +00:00
Add persistence mode
This commit is contained in:
parent
0f7b756c93
commit
11de20c5f3
12
barf.py
12
barf.py
@ -25,6 +25,7 @@ sys.path.insert(1, barf_path)
|
||||
|
||||
# include project files
|
||||
from BreakpointManager import BreakpointManager
|
||||
from TargetManager import TargetManager
|
||||
from Helper import *
|
||||
from Bruteforce import *
|
||||
|
||||
@ -39,11 +40,14 @@ def main():
|
||||
# Create our breakpoints, managed by the BreakpointManager
|
||||
bm = BreakpointManager(args["positiveAddr"], args["negativeAddr"], args["winAddr"])
|
||||
|
||||
# Manage the target with the TargetManager
|
||||
tm = TargetManager(args["persistent"], args["startAddr"], args["endAddr"], args["buffAddr"])
|
||||
|
||||
# start the bruteforcing madness ;)
|
||||
# DisableLogging()
|
||||
Bruteforce(bm, args["knownPrefix"], args["knownSuffix"], args["chunksize"])
|
||||
Bruteforce(bm, tm, args["knownPrefix"], args["knownSuffix"], args["chunksize"])
|
||||
|
||||
# g'night, gdb
|
||||
gdb.execute("set confirm off")
|
||||
gdb.execute("quit")
|
||||
|
||||
|
||||
@ -53,9 +57,13 @@ def getArguments():
|
||||
a["positiveAddr"] = barf_positive_addr
|
||||
a["negativeAddr"] = barf_negative_addr
|
||||
a["winAddr"] = barf_win_addr
|
||||
a["startAddr"] = barf_start_addr
|
||||
a["endAddr"] = barf_end_addr
|
||||
a["buffAddr"] = barf_buff_addr
|
||||
a["knownPrefix"] = barf_known_prefix
|
||||
a["knownSuffix"] = barf_known_suffix
|
||||
a["chunksize"] = barf_chunksize
|
||||
a["persistent"] = barf_persistent
|
||||
return a
|
||||
|
||||
|
||||
|
37
barf.sh
37
barf.sh
@ -9,10 +9,14 @@
|
||||
POSITIVEADDR=""
|
||||
NEGATIVEADDR=""
|
||||
WINADDR=""
|
||||
STARTADDR=""
|
||||
ENDADDR=""
|
||||
BUFFADDR=""
|
||||
KNOWNPREFIX=""
|
||||
KNOWNSUFFIX=""
|
||||
BARFPATH="$(dirname $(realpath $0))/src"
|
||||
CHUNKSIZE=1
|
||||
PERSISTENT="False"
|
||||
|
||||
# getopt is kind-of unstable across distributions and versions, so we implement it on our own
|
||||
# hat-tip to https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
|
||||
@ -32,6 +36,18 @@ while [[ $# -gt 0 ]]; do
|
||||
WINADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-s|--start-addr)
|
||||
STARTADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-e|--end-addr)
|
||||
ENDADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
--buff-addr)
|
||||
BUFFADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-h|--help)
|
||||
SHOWHELP=1
|
||||
shift
|
||||
@ -48,6 +64,10 @@ while [[ $# -gt 0 ]]; do
|
||||
CHUNKSIZE="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-x|--persistent)
|
||||
PERSISTENT="1"
|
||||
shift
|
||||
;;
|
||||
*) # unknown option - we assume it is the target literal
|
||||
TARGETFILE="$key"
|
||||
shift
|
||||
@ -67,15 +87,30 @@ if [ ! -e "$TARGETFILE" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if the persistent mode can be used
|
||||
if [[ "$PERSISTENT" == "1" && ("$STARTADDR" == "" || "$ENDADDR" == "" || "$BUFFADDR" == "" ) ]]; then
|
||||
# missing the end address for persistent mode
|
||||
echo "You need to specify --start-addr, --end-addr and --buff-addr if you want to use persistent mode."
|
||||
echo "Set --start-addr to an address before your input reaches the program (e.g. before fgets())"
|
||||
echo "Set --end-addr to an address after the program has checked if the input is good or not (e.g. somewhere after gets('Yay!') and gets('Nay!'))"
|
||||
echo "Set --buffer-addr to the address where user input is stored (e.g. the address of b in case of fgets(b, 16, stdin)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# see if the user needs our help
|
||||
if [ "$SHOWHELP" == 1 ]; then
|
||||
echo "Usage: ./barf.sh"
|
||||
echo " -p | --positive-addr 0x123456 a location to be counted as good hit"
|
||||
echo " -n | --negative-addr 0x789ABC a location to be counted as bad hit"
|
||||
echo " -w | --win-addr 0xDEF042 a location reached if your input is correct"
|
||||
echo " -s | --start-addr 0xF0000D a location directly after your input is fed into the target (for persistent mode)"
|
||||
echo " -e | --end-addr 0x133337 a location where the to-be-fuzzed logic is done (for persistent mode)"
|
||||
echo " --buff-addr 0x424242 the location where user input is stored (for persistent mode)"
|
||||
echo " -< | --prefix CTF{ a known prefix, e.g. the prefix of your flag"
|
||||
echo " -> | --suffix } a known suffix, e.g. the suffix of your flag"
|
||||
echo " -c | --chunksize 1 amount of characters to try at once"
|
||||
echo " -x | --persistent enable the experimental (!) persistent mode"
|
||||
echo " -h | --help a great and useful help message, you should try it!"
|
||||
echo " ./path/to/your/crackme the path to the target to be fuzzed"
|
||||
echo "Note that you need to either specify --positive-addr or --negative-addr and your target of course."
|
||||
@ -83,5 +118,5 @@ if [ "$SHOWHELP" == 1 ]; then
|
||||
fi
|
||||
|
||||
# ready for take-off
|
||||
gdb --quiet -nx --eval-command "py barf_positive_addr='$POSITIVEADDR';barf_negative_addr='$NEGATIVEADDR';barf_win_addr='$WINADDR';barf_known_prefix='$KNOWNPREFIX';barf_known_suffix='$KNOWNSUFFIX';barf_path='$BARFPATH';barf_chunksize=$CHUNKSIZE" --command barf.py $TARGETFILE
|
||||
gdb --quiet -nx --eval-command "py barf_positive_addr='$POSITIVEADDR';barf_negative_addr='$NEGATIVEADDR';barf_win_addr='$WINADDR';barf_start_addr='$STARTADDR';barf_end_addr='$ENDADDR';barf_buff_addr='$BUFFADDR';barf_known_prefix='$KNOWNPREFIX';barf_known_suffix='$KNOWNSUFFIX';barf_path='$BARFPATH';barf_chunksize=$CHUNKSIZE;barf_persistent=$PERSISTENT" --command barf.py $TARGETFILE
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user