mirror of
https://github.com/maride/barf.git
synced 2024-12-22 14:37: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