Init commit

This commit is contained in:
maride 2021-05-30 21:13:17 +02:00
commit bf068a2780
4 changed files with 377 additions and 0 deletions

243
barf.py Normal file
View 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
View 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

Binary file not shown.

54
examples/single-char.c Normal file
View 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! :(");
}
}