mirror of
https://github.com/maride/barf.git
synced 2024-12-22 06:27:29 +00:00
Init commit
This commit is contained in:
commit
bf068a2780
243
barf.py
Normal file
243
barf.py
Normal file
@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# (c) 2021 Martin "maride" Dessauer
|
||||
#
|
||||
# BARF, or the Breakpoint-Assisted Rough Fuzzer, is a tool to do intelligent bruteforcing.
|
||||
# The "intelligent" part comes from watching breakpoints and counting how often they were hit.
|
||||
# Input is fed into the target program, character-wise, and the character with the best score wins. ;)
|
||||
# This is done as long as there is a better score to get, and/or until a "win breakpoint" is hit.
|
||||
# If that's hard to understand on the first read, see some of the examples. ;)
|
||||
#
|
||||
# This script is not designed to be directly called. Instead, it gets imported by gdb, via the -x argument.
|
||||
# Because passing arguments into gdb-python scripts is not trivial, the script _should_ be called by the barf.sh wrapper.
|
||||
# If you have any reasons to avoid the wrapper script, ... uh well. Your choice. You can call the barf.py script via gdb like this:
|
||||
# gdb -nx -ex "py barf_positive_addr=False;barf_negative_addr='0x5555555551c0';barf_win_addr='0x5555555551ec';barf_known_prefix='';barf_known_suffix=''" -x barf.py ./beispiel1
|
||||
# -nx avoids loading .gdbinit
|
||||
# -ex throws your arguments into gdb-python (must be specified _before_ handing in the script
|
||||
# -x specifies the location of the script
|
||||
# after that comes your executable (./beispiel1 in this case)
|
||||
#
|
||||
# In doubt, see https://github.com/maride/barf
|
||||
# Have fun with the script! :)
|
||||
|
||||
|
||||
# The charset to try, sorted by the likelihood of a character class
|
||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!?"
|
||||
|
||||
|
||||
# Breakpoint wrapper class
|
||||
# A breakpoint class with patented, award-winning score functionality.
|
||||
class CounterBreakpoint(gdb.Breakpoint):
|
||||
# addr is the address of the breakpoint
|
||||
# isGood is a boolean, determing if the breakpoint is good-to-hit or bad-to-hit
|
||||
# (means a negative breakpoint has isGood = false, and vice versa)
|
||||
def __init__(self, addr, isGood):
|
||||
self.isGood = isGood
|
||||
self.currentScore = 0
|
||||
|
||||
# gdb requires address literals to start with a star
|
||||
if addr[0] != "*":
|
||||
addr = "*" + addr
|
||||
|
||||
super().__init__(addr)
|
||||
|
||||
# returns the score of this breakpoint
|
||||
def GetScore(self):
|
||||
return self.currentScore
|
||||
|
||||
# resets the score to 0
|
||||
def ResetScore(self):
|
||||
self.currentScore = 0
|
||||
|
||||
# returns the score and resets it to 0 afterwards
|
||||
def PopScore(self):
|
||||
i = self.GetScore()
|
||||
self.ResetScore()
|
||||
return i
|
||||
|
||||
# the function called by GDB if the breakpoint is hit
|
||||
def stop(self):
|
||||
if self.isGood:
|
||||
self.currentScore += 1
|
||||
else:
|
||||
self.currentScore -= 1
|
||||
|
||||
# don't break into gdb GUI
|
||||
return False
|
||||
|
||||
|
||||
# Abstracts the breakpoints into a single class:
|
||||
# - returns the score of the positive and negative breakpoint
|
||||
# - checks if the win function was hit
|
||||
# - takes care of resetting all breakpoints with a single call
|
||||
class BreakpointManager:
|
||||
posB = None
|
||||
negB = None
|
||||
winB = None
|
||||
|
||||
def __init__(self, pAddr, nAddr, wAddr):
|
||||
if pAddr:
|
||||
self.posB = CounterBreakpoint(pAddr, True)
|
||||
if nAddr:
|
||||
self.negB = CounterBreakpoint(nAddr, False)
|
||||
if wAddr:
|
||||
self.winB = CounterBreakpoint(wAddr, True)
|
||||
|
||||
def GetScore(self):
|
||||
score = 0
|
||||
if self.posB:
|
||||
score += self.posB.GetScore()
|
||||
if self.negB:
|
||||
score += self.negB.GetScore()
|
||||
return score
|
||||
|
||||
def ResetBreakpoints(self):
|
||||
if self.posB:
|
||||
self.posB.ResetScore()
|
||||
if self.negB:
|
||||
self.negB.ResetScore()
|
||||
if self.winB:
|
||||
self.winB.ResetScore()
|
||||
|
||||
def PopScore(self):
|
||||
score = 0
|
||||
if self.posB:
|
||||
score += self.posB.PopScore()
|
||||
if self.negB:
|
||||
score += self.negB.PopScore()
|
||||
return score
|
||||
|
||||
def HitWin(self):
|
||||
return self.winB.GetScore() != 0
|
||||
|
||||
|
||||
# Enables the typical GDB spam
|
||||
def EnableLogging():
|
||||
gdb.execute("set logging off")
|
||||
|
||||
|
||||
# Disables the typical GDB spam
|
||||
def DisableLogging():
|
||||
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("+--------------------------------------------+")
|
||||
print("| 🥩 BARF - Breakpoint-Assisted Rough Fuzzer |")
|
||||
print("| (c) 2021 Martin 'maride' Dessauer |")
|
||||
print("| github.com/maride/barf |")
|
||||
print("+--------------------------------------------+")
|
||||
|
||||
|
||||
# bruteforces a single character, sandwiched between the known parts.
|
||||
# Returns the most promising string.
|
||||
def BruteforceChar(bm, knownPrefix, knownSuffix):
|
||||
# keyFragment is the variable were we store our found-to-be-correct chars
|
||||
keyFragment = ""
|
||||
|
||||
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()
|
||||
TryInput(knownPrefix + keyFragment + "^" + knownSuffix)
|
||||
refScore = bm.PopScore()
|
||||
|
||||
# iterate over every character in the charset
|
||||
for c in charset:
|
||||
# generate full input string
|
||||
inp = knownPrefix + keyFragment + c + knownSuffix
|
||||
|
||||
# and try it
|
||||
bm.ResetBreakpoints()
|
||||
TryInput(inp)
|
||||
score = bm.PopScore()
|
||||
|
||||
# yay, that's a hit
|
||||
if score > refScore:
|
||||
keyFragment += c
|
||||
found = True
|
||||
break
|
||||
|
||||
# check if we found something this round
|
||||
return keyFragment if found else False
|
||||
|
||||
|
||||
# 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):
|
||||
while True:
|
||||
res = BruteforceChar(bm, knownPrefix, knownSuffix)
|
||||
if res is False:
|
||||
# no character from the given charset matched. :(
|
||||
EnableLogging()
|
||||
print("BARF is done with the charset and was unable to increase the score further. Issues may be:")
|
||||
print(" - Your charset is too small")
|
||||
print(" - Your chunk size is too small")
|
||||
print(" - Your breakpoints are off")
|
||||
print(" - The specified binary doesn't operate round-wise, so it's impossible to calculate a proper score")
|
||||
if len(knownPrefix) > 0:
|
||||
print(f"Anyway, I stopped with the key '{knownPrefix}[...mystery!...]{knownSuffix}'")
|
||||
print("Maybe that helps you. Have a good night!")
|
||||
DisableLogging()
|
||||
return knownPrefix + knownSuffix
|
||||
else:
|
||||
# good input, we stepped further
|
||||
knownPrefix += res
|
||||
EnableLogging()
|
||||
print(f"Found new scorer, we're now at '{knownPrefix}[...]{knownSuffix}'")
|
||||
DisableLogging()
|
||||
|
||||
# let's examine it further - check if we hit the win breakpoint :)
|
||||
if bm.HitWin():
|
||||
EnableLogging()
|
||||
print("BARF found the flag - or at least managed to hit the 'win' breakpoint!")
|
||||
print(f"Winning guess for the flag is '{knownPrefix + knownSuffix}'")
|
||||
DisableLogging()
|
||||
return knownPrefix + knownSuffix
|
||||
|
||||
|
||||
# getArguments grabs the arguments from pre-defined variables and returns it as a dict
|
||||
def getArguments():
|
||||
a = dict()
|
||||
a["positiveAddr"] = barf_positive_addr
|
||||
a["negativeAddr"] = barf_negative_addr
|
||||
a["winAddr"] = barf_win_addr
|
||||
a["knownPrefix"] = barf_known_prefix
|
||||
a["knownSuffix"] = barf_known_suffix
|
||||
return a
|
||||
|
||||
# main func
|
||||
def main():
|
||||
MOTD()
|
||||
gdb.execute("set pagination off")
|
||||
|
||||
# check our args :)
|
||||
args = getArguments()
|
||||
|
||||
# Create our breakpoints, managed by the BreakpointManager
|
||||
bm = BreakpointManager(args["positiveAddr"], args["negativeAddr"], args["winAddr"])
|
||||
|
||||
# start the bruteforcing madness ;)
|
||||
DisableLogging()
|
||||
Bruteforce(bm, args["knownPrefix"], args["knownSuffix"])
|
||||
|
||||
# g'night, gdb
|
||||
gdb.execute("quit")
|
||||
|
||||
|
||||
# actually execute main function
|
||||
main()
|
||||
|
80
barf.sh
Executable file
80
barf.sh
Executable file
@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# wrapper script around barf.py
|
||||
#
|
||||
# In doubt, see https://github.com/maride/barf
|
||||
# Have fun with the script! :)
|
||||
|
||||
# setting defaults for the arguments
|
||||
POSITIVEADDR=""
|
||||
NEGATIVEADDR=""
|
||||
WINADDR=""
|
||||
KNOWNPREFIX=""
|
||||
KNOWNSUFFIX=""
|
||||
|
||||
# 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
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
|
||||
case $key in
|
||||
-p|--positive-addr)
|
||||
POSITIVEADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-n|--negative-addr)
|
||||
NEGATIVEADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-w|--win-addr)
|
||||
WINADDR="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-h|--help)
|
||||
SHOWHELP=1
|
||||
shift
|
||||
;;
|
||||
-b|--prefix)
|
||||
KNOWNPREFIX="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-a|--suffix)
|
||||
KNOWNSUFFIX="$2"
|
||||
shift; shift
|
||||
;;
|
||||
*) # unknown option - we assume it is the target literal
|
||||
TARGETFILE="$key"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# check if we have all arguments we need
|
||||
if [ "$POSITIVEADDR" == "" ] && [ "$NEGATIVEADDR" == "" ] || [ "$TARGETFILE" == "" ] ; then
|
||||
# nope, missing some args
|
||||
SHOWHELP=1
|
||||
fi
|
||||
|
||||
# check if the arguments are valid
|
||||
if [ ! -e "$TARGETFILE" ]; then
|
||||
echo "The file $TARGETFILE does not exist."
|
||||
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 " -< | --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 " -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."
|
||||
exit 1
|
||||
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'" --command barf.py $TARGETFILE
|
||||
|
BIN
examples/single-char
Executable file
BIN
examples/single-char
Executable file
Binary file not shown.
54
examples/single-char.c
Normal file
54
examples/single-char.c
Normal file
@ -0,0 +1,54 @@
|
||||
// single-char.c
|
||||
// -------------
|
||||
//
|
||||
// The binary reads some chars from stdin and checks it against a hard-coded flag.
|
||||
// If the entered flag is correct, a corresponding message will be printed out.
|
||||
//
|
||||
// Compile with
|
||||
// gcc -o single-char single-char.c
|
||||
//
|
||||
// Quick binary analysis
|
||||
// - load into gdb
|
||||
// - start, so the binary is mapped to the final position
|
||||
// - execute "disas main"
|
||||
// Look at 0x00005555555551c7 <+130>. It moves 0 to rbp-0x4, that's the foundFlag = 0 below.
|
||||
// This is the perfect address for --negative-addr
|
||||
// Finding the win function is even easier. We just need to search for the point where puts("yay, ...") is called.
|
||||
// And that is at 0x00005555555551ec. It is not important if you choose the instruction moving the string into
|
||||
// memory, or the instruction calling puts(), as long as it is inside the correct part of the if() block ;)
|
||||
//
|
||||
// With the addresses identified above, we call barf with:
|
||||
// ./barf.sh --negative-addr 0x5555555551c7 --win-addr 0x5555555551ec ./single-char
|
||||
//
|
||||
// Please note that your addresses will likely differ, e.g. if you edit the source file below.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define BUFSIZE 32
|
||||
|
||||
int main(int argc ,char* argv[]) {
|
||||
char buf[BUFSIZE];
|
||||
char flag[BUFSIZE] = "CTF{F00_b4R_B4z_fL4g!}\n";
|
||||
int foundFlag = 1;
|
||||
|
||||
// read flag
|
||||
fgets(buf, BUFSIZE, stdin);
|
||||
|
||||
// walk flag
|
||||
int i = 0;
|
||||
while(buf[i] != '\0' && i < BUFSIZE) {
|
||||
if(buf[i] != flag[i]) {
|
||||
foundFlag = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// check flag
|
||||
if(foundFlag) {
|
||||
puts("yay, that's the flag! :)");
|
||||
} else {
|
||||
puts("nay, that's not the flag! :(");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user