mirror of
https://github.com/maride/barf.git
synced 2024-12-22 22:47:30 +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
|
# include project files
|
||||||
from BreakpointManager import BreakpointManager
|
from BreakpointManager import BreakpointManager
|
||||||
|
from TargetManager import TargetManager
|
||||||
from Helper import *
|
from Helper import *
|
||||||
from Bruteforce import *
|
from Bruteforce import *
|
||||||
|
|
||||||
@ -39,11 +40,14 @@ def main():
|
|||||||
# Create our breakpoints, managed by the BreakpointManager
|
# Create our breakpoints, managed by the BreakpointManager
|
||||||
bm = BreakpointManager(args["positiveAddr"], args["negativeAddr"], args["winAddr"])
|
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 ;)
|
# start the bruteforcing madness ;)
|
||||||
# DisableLogging()
|
Bruteforce(bm, tm, args["knownPrefix"], args["knownSuffix"], args["chunksize"])
|
||||||
Bruteforce(bm, args["knownPrefix"], args["knownSuffix"], args["chunksize"])
|
|
||||||
|
|
||||||
# g'night, gdb
|
# g'night, gdb
|
||||||
|
gdb.execute("set confirm off")
|
||||||
gdb.execute("quit")
|
gdb.execute("quit")
|
||||||
|
|
||||||
|
|
||||||
@ -53,9 +57,13 @@ def getArguments():
|
|||||||
a["positiveAddr"] = barf_positive_addr
|
a["positiveAddr"] = barf_positive_addr
|
||||||
a["negativeAddr"] = barf_negative_addr
|
a["negativeAddr"] = barf_negative_addr
|
||||||
a["winAddr"] = barf_win_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["knownPrefix"] = barf_known_prefix
|
||||||
a["knownSuffix"] = barf_known_suffix
|
a["knownSuffix"] = barf_known_suffix
|
||||||
a["chunksize"] = barf_chunksize
|
a["chunksize"] = barf_chunksize
|
||||||
|
a["persistent"] = barf_persistent
|
||||||
return a
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
37
barf.sh
37
barf.sh
@ -9,10 +9,14 @@
|
|||||||
POSITIVEADDR=""
|
POSITIVEADDR=""
|
||||||
NEGATIVEADDR=""
|
NEGATIVEADDR=""
|
||||||
WINADDR=""
|
WINADDR=""
|
||||||
|
STARTADDR=""
|
||||||
|
ENDADDR=""
|
||||||
|
BUFFADDR=""
|
||||||
KNOWNPREFIX=""
|
KNOWNPREFIX=""
|
||||||
KNOWNSUFFIX=""
|
KNOWNSUFFIX=""
|
||||||
BARFPATH="$(dirname $(realpath $0))/src"
|
BARFPATH="$(dirname $(realpath $0))/src"
|
||||||
CHUNKSIZE=1
|
CHUNKSIZE=1
|
||||||
|
PERSISTENT="False"
|
||||||
|
|
||||||
# getopt is kind-of unstable across distributions and versions, so we implement it on our own
|
# 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
|
# 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"
|
WINADDR="$2"
|
||||||
shift; shift
|
shift; shift
|
||||||
;;
|
;;
|
||||||
|
-s|--start-addr)
|
||||||
|
STARTADDR="$2"
|
||||||
|
shift; shift
|
||||||
|
;;
|
||||||
|
-e|--end-addr)
|
||||||
|
ENDADDR="$2"
|
||||||
|
shift; shift
|
||||||
|
;;
|
||||||
|
--buff-addr)
|
||||||
|
BUFFADDR="$2"
|
||||||
|
shift; shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
SHOWHELP=1
|
SHOWHELP=1
|
||||||
shift
|
shift
|
||||||
@ -48,6 +64,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
CHUNKSIZE="$2"
|
CHUNKSIZE="$2"
|
||||||
shift; shift
|
shift; shift
|
||||||
;;
|
;;
|
||||||
|
-x|--persistent)
|
||||||
|
PERSISTENT="1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
*) # unknown option - we assume it is the target literal
|
*) # unknown option - we assume it is the target literal
|
||||||
TARGETFILE="$key"
|
TARGETFILE="$key"
|
||||||
shift
|
shift
|
||||||
@ -67,15 +87,30 @@ if [ ! -e "$TARGETFILE" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
# see if the user needs our help
|
||||||
if [ "$SHOWHELP" == 1 ]; then
|
if [ "$SHOWHELP" == 1 ]; then
|
||||||
echo "Usage: ./barf.sh"
|
echo "Usage: ./barf.sh"
|
||||||
echo " -p | --positive-addr 0x123456 a location to be counted as good hit"
|
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 " -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 " -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 " -< | --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 " -> | --suffix } a known suffix, e.g. the suffix of your flag"
|
||||||
echo " -c | --chunksize 1 amount of characters to try at once"
|
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 " -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 " ./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."
|
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
|
fi
|
||||||
|
|
||||||
# ready for take-off
|
# 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
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from Helper import *
|
from Helper import *
|
||||||
|
from TargetManager import TargetManager
|
||||||
|
|
||||||
# The charset to try, sorted by the likelihood of a character class
|
# The charset to try, sorted by the likelihood of a character class
|
||||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!?'#%+/ ;[`@-\".<,*|&$(]=)^>\\:~"
|
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!?'#%+/ ;[`@-\".<,*|&$(]=)^>\\:~"
|
||||||
|
|
||||||
# bruteforces a single character, sandwiched between the known parts.
|
# bruteforces a single character, sandwiched between the known parts.
|
||||||
# Returns the most promising string.
|
# 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 is the variable were we store our found-to-be-correct chars
|
||||||
keyFragment = ""
|
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.
|
# 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
|
# 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()
|
bm.ResetBreakpoints()
|
||||||
TryInput(knownPrefix + keyFragment + "^" * chunksize + knownSuffix)
|
tm.Run(knownPrefix + keyFragment + "^" * chunksize + knownSuffix)
|
||||||
refScore = bm.PopScore()
|
refScore = bm.PopScore()
|
||||||
|
|
||||||
# iterate over every character in the charset
|
# iterate over every character in the charset
|
||||||
@ -29,7 +30,7 @@ def BruteforceChar(bm, knownPrefix, knownSuffix, chunksize):
|
|||||||
|
|
||||||
# and try it
|
# and try it
|
||||||
bm.ResetBreakpoints()
|
bm.ResetBreakpoints()
|
||||||
TryInput(inp)
|
tm.Run(inp)
|
||||||
score = bm.PopScore()
|
score = bm.PopScore()
|
||||||
|
|
||||||
# yay, that's a hit
|
# yay, that's a hit
|
||||||
@ -45,9 +46,9 @@ def BruteforceChar(bm, knownPrefix, knownSuffix, chunksize):
|
|||||||
# Bruteforce calls BruteforceChar until:
|
# Bruteforce calls BruteforceChar until:
|
||||||
# - BruteforceChar was unable to increase the score using any character in the charset, OR
|
# - BruteforceChar was unable to increase the score using any character in the charset, OR
|
||||||
# - the "win" breakpoint is hit :)
|
# - the "win" breakpoint is hit :)
|
||||||
def Bruteforce(bm, knownPrefix, knownSuffix, chunksize):
|
def Bruteforce(bm, tm, knownPrefix, knownSuffix, chunksize):
|
||||||
while True:
|
while True:
|
||||||
res = BruteforceChar(bm, knownPrefix, knownSuffix, chunksize)
|
res = BruteforceChar(bm, tm, knownPrefix, knownSuffix, chunksize)
|
||||||
if res is False:
|
if res is False:
|
||||||
# no character from the given charset matched. :(
|
# no character from the given charset matched. :(
|
||||||
EnableLogging()
|
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
|
# Disables the typical GDB spam
|
||||||
def DisableLogging():
|
def DisableLogging():
|
||||||
|
return
|
||||||
gdb.execute("set logging file /dev/null")
|
gdb.execute("set logging file /dev/null")
|
||||||
gdb.execute("set logging redirect on")
|
gdb.execute("set logging redirect on")
|
||||||
gdb.execute("set logging 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
|
# Prints a small MOTD, hence the name of the function
|
||||||
def MOTD():
|
def MOTD():
|
||||||
print("+--------------------------------------------+")
|
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