From bf068a2780b899b354225d914ed13488ad5b76b9 Mon Sep 17 00:00:00 2001 From: maride Date: Sun, 30 May 2021 21:13:17 +0200 Subject: [PATCH] Init commit --- barf.py | 243 +++++++++++++++++++++++++++++++++++++++++ barf.sh | 80 ++++++++++++++ examples/single-char | Bin 0 -> 16704 bytes examples/single-char.c | 54 +++++++++ 4 files changed, 377 insertions(+) create mode 100644 barf.py create mode 100755 barf.sh create mode 100755 examples/single-char create mode 100644 examples/single-char.c diff --git a/barf.py b/barf.py new file mode 100644 index 0000000..c18b24d --- /dev/null +++ b/barf.py @@ -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() + diff --git a/barf.sh b/barf.sh new file mode 100755 index 0000000..897be44 --- /dev/null +++ b/barf.sh @@ -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 + diff --git a/examples/single-char b/examples/single-char new file mode 100755 index 0000000000000000000000000000000000000000..dcb0b67904b7817f41f3903570bcda089cc93bd6 GIT binary patch literal 16704 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 literal 0 HcmV?d00001 diff --git a/examples/single-char.c b/examples/single-char.c new file mode 100644 index 0000000..b05a41f --- /dev/null +++ b/examples/single-char.c @@ -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 +#include + +#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! :("); + } +} +