From d6708b9931ed399fdec9fa5092d97629c08b70f3 Mon Sep 17 00:00:00 2001 From: "Abdulrahman Alhajri (alhajria9)" Date: Sat, 3 Apr 2021 18:21:55 +0300 Subject: [PATCH] Initial commit --- .gitignore | 8 ++++ Makefile | 30 ++++++++++++++ README.md | 13 ++++++ dictionaries/base.txt | 32 ++++++++++++++ download_targets.sh | 12 ++++++ requirements.txt | 3 ++ src/brute_advanced.py | 70 +++++++++++++++++++++++++++++++ src/brute_basic.py | 79 +++++++++++++++++++++++++++++++++++ src/brute_intermediate.py | 85 ++++++++++++++++++++++++++++++++++++++ src/brutus.py | 78 ++++++++++++++++++++++++++++++++++ targets/.placeholder | 0 test/test_basic.py | 13 ++++++ test/test_brutus.py | 24 +++++++++++ test/test_maxPos.py | 14 +++++++ test/testbin | Bin 0 -> 8576 bytes test/testbin.c | 22 ++++++++++ 16 files changed, 483 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 dictionaries/base.txt create mode 100755 download_targets.sh create mode 100644 requirements.txt create mode 100755 src/brute_advanced.py create mode 100755 src/brute_basic.py create mode 100755 src/brute_intermediate.py create mode 100644 src/brutus.py create mode 100644 targets/.placeholder create mode 100644 test/test_basic.py create mode 100644 test/test_brutus.py create mode 100644 test/test_maxPos.py create mode 100755 test/testbin create mode 100644 test/testbin.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c632d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*~ +/dictionaries/reductor.py +/brute_venv/ +/html/ +/src/__pycache__/ +/test/__pycache__/ +#*# +/targets/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dc4a981 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +VENV=brute_venv +INVENV = $(shell pip3 -V | grep $(VENV)) +current_dir = $(shell pwd) + +prereqs: venvcheck FORCE + pip install -r requirements.txt + +venv: FORCE + python3 -m venv $(VENV) + +docs: + pdoc --html ./src/brutus.py --force + +venvcheck: +ifeq ($(INVENV),) + $(error You should only run this from within the venv. Use '. ./$(VENV)/bin/activate') +else + @echo "venv check passed\n" +endif + +test/testbin: test/testbin.c + $(CC) -o test/testbin test/testbin.c + +test: FORCE venvcheck test/testbin + py.test -v test/ + + + + +FORCE: diff --git a/README.md b/README.md new file mode 100644 index 0000000..cebb981 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Brute Campbell + +This document should be edited to include the points below and any +other you think appropriate. You can use this as the report part of +your submission if you convert it to PDF and make sure to include +source code, etc. If you don't want to do this, you can write the +report in Word, LaTeX, etc. + +## User documentation + +## Unit Tests + +## Algorithms diff --git a/dictionaries/base.txt b/dictionaries/base.txt new file mode 100644 index 0000000..d18e5e9 --- /dev/null +++ b/dictionaries/base.txt @@ -0,0 +1,32 @@ +abroad +afford +airport +anchor +andorra +another +coated +romance +rotary +royalty +salmon +season +senator +shadow +social +solaris +station +stomach +storage +tattoo +tobago +tomato +toolbar +totally +totals +tractor +upload +various +vocals +voltage +warrior + diff --git a/download_targets.sh b/download_targets.sh new file mode 100755 index 0000000..8af1d6a --- /dev/null +++ b/download_targets.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +bins=(basic1 basic2 basic3 intermediate1 intermediate2 intermediate3 advanced1 advanced2 advanced3) + +rm -f targets/* + +for b in "${bins[@]}" +do + curl -o targets/$b "https://github.coventry.ac.uk/pages/csx239/naturan_demanto/bins/$b" +done + +chmod u+x targets/* diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e67a70d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pdoc3 +pytest +pexpect diff --git a/src/brute_advanced.py b/src/brute_advanced.py new file mode 100755 index 0000000..962555b --- /dev/null +++ b/src/brute_advanced.py @@ -0,0 +1,70 @@ +#!python3 +from brutus import Binary + + +def maxPos(seq): + """ Given a list of numbers, return the **position** of the largest + Args: + seq: the list to be searched + Returns: + an integer position of the largest number in `seq` + """ + maxNum=seq[0] + maxPos=0 + for i in range(len(seq)): + if seq[i]>maxNum: + maxNum=seq[i] + maxPos=i + return maxPos + + +# def averageTry(target, promptText, failText, guess, repeats=2): +# # Provided to assist. If you use it, document it properly... :) +# # Runs multiple attempts at cracking the binary, returning the +# # success AND the average length of time each try took +# results=[] +# success=False +# for i in range(repeats): +# b=Binary(target) +# b.run() +# result=b.timedAttempt(promptText,guess, failText) +# success=result[0] +# results.append(result[1]) +# return (success,sum(results)/len(results)) + +def breakBinary(target, promptText, failText): + + + #Your code here + # Suggested algorithm: + + #1. Use an accumulator for the current guess + #2. in a loop, try the current guess plus each letter of the alphabest and see which one takes longest + #3. If it is the correct password, end + #4. If not, add the current best letter to the guess and repeat... + pass + + + +if __name__=="__main__": + + + # Create a simple menu system to pick the binary we want to force + targets=[] + targets.append(["targets/advanced1","Password:", "Password Incorrect"]) + targets.append(["targets/advanced2","Password:", "Password Incorrect"]) + targets.append(["targets/advanced3","Password:", "Password Incorrect"]) + + print("Intermediate Binary Breaker") + print("Which binary do you want to brute force?") + + for c in range(len(targets)): + print(f"{c}: {targets[c][0]}") + + selection=int(input("Enter the number of the binary to be forced: ")) + + if 0 <= selection < len(targets): + target=targets[selection] + breakBinary(target[0],target[1],target[2]) + else: + print("Invalid selection") diff --git a/src/brute_basic.py b/src/brute_basic.py new file mode 100755 index 0000000..4a815dd --- /dev/null +++ b/src/brute_basic.py @@ -0,0 +1,79 @@ +#!python3 +from brutus import Binary + +# This is a starting-point for your project +# Most of the work is done for the basic task +# Intermediate will require reading from a dictionary file and creating guesses based on the contents +# For the advanced task, see the supporting module "brute_time" + + + +def generateGuesses(): + """ Creates a list of PIN guesses. Needs finishing, testing and documenting... """ + #Here, the guesses are listed explicitly. We *could* write out + #1000 guesses, but the point is to use the computer to do the + #brute force work, not our fingers + + #For the basic task, the only functionality you need is to replace + #the line below with code that can generate all the potential + #guesses + + return ["000","001","002","003","004"] + + + +def breakBinary(target, promptText, failText): + """" Break into the given target binary. + Assumes "basic" level binary, with PIN codes of 000-999 + Args: + target: path to the binary. e.g. "./bins/basic1" + promptText: text to look for in the output that signals a password is required. e.g. "Password:" + failText: text that indicates an attempt failed. e.g. "Password Incorrect" + Returns: + None: if no successful attempt was made + string: a successful password""" + + + guesses=generateGuesses() + + + + for g in guesses: + + #The actual attempt + b=Binary(target) + b.run() + success=b.attempt(promptText,g, failText) + + + if success: + print(f"The Guess '{g}' appears to be correct") + return g #Return the answer. No need to "break" because the return exits the function + else: + print(f"guess: {g} - Password incorrect!") + return None #If we get here, it means we didn't return earlier in the loop with a successful guess + + +if __name__=="__main__": + + + # Create a simple menu system to pick the binary we want to force + targets=[] + targets.append(["targets/basic1","Password:", "Password Incorrect"]) + targets.append(["targets/basic2","Enter the secret code:", "ACCESS DENIED"]) + targets.append(["targets/basic3","Got the PIN?", "NO"]) + + + print("Basic Binary Breaker") + print("Which binary do you want to brute force?") + + for c in range(len(targets)): + print(f"{c}: {targets[c][0]}") + + selection=int(input("Enter the number of the binary to be forced: ")) + + if 0 <= selection < len(targets): + target=targets[selection] + breakBinary(target[0],target[1],target[2]) + else: + print("Invalid selection") diff --git a/src/brute_intermediate.py b/src/brute_intermediate.py new file mode 100755 index 0000000..a72e125 --- /dev/null +++ b/src/brute_intermediate.py @@ -0,0 +1,85 @@ +#!python3 +from brutus import Binary + +def wordsFromFile(filePath): + """ Read lines from a file containing one word per line and return a list of the words + Args: + filePath: the absolute or relative path of the file to be read + Returns: + a list of the words from the file, stripped of whitespace + """ + f=open(filePath,"r") + out=[] + for l in f.readlines(): + w=l.strip() + if len(w)>0: + out.append(w.lower()) + f.close() + return out + +def breakBinary(target, promptText, failText, guesses): + """" Break into the given target binary. + Assumes "intermeduate level binary, with dictionary words + Args: + target: path to the binary. e.g. "./bins/basic1" + promptText: text to look for in the output that signals a password is required. e.g. "Password:" + failText: text that indicates an attempt failed. e.g. "Password Incorrect" + guesses: list of words to try as passwords + Returns: + None: if no successful attempt was made + string: a successful password""" + + for g in guesses: + + #The actual attempt + b=Binary(target) + b.run() + success=b.attempt(promptText,g, failText) + + + if success: + print(f"The Guess '{g}' appears to be correct") + return g #Return the answer. No need to "break" because the return exits the function + else: + print(f"guess: {g} - Password incorrect!") + return None #If we get here, it means we didn't return earlier in the loop with a successful guess + + +if __name__=="__main__": + + #Load the dictionary + words=wordsFromFile("dictionaries/base.txt") + + + ### YOUR CODE HERE + ### Currently it passes in the plain words + ### Change the line "words2=words" so that the list "words2" contains your guesses + ### You need to create a word list that has the dictionary words in PLUS + ### 1. Each word with all 0-9 digits appended (so 'swordfish' would be 'swordfish0', 'swordfish1' etc. + ### 2. Each word turned into "l33t-5p34k" + ### Each o becomes 0, each i becomes 1, each a becomes 4, each s becomes 5, each e becomes 3 + ### 'swordfish' becomes '5w0rdf15h', for example + ### You can assume case (upper/lower) will not need to be changed + + words2=words + + + # Create a simple menu system to pick the binary we want to force + targets=[] + targets.append(["targets/intermediate1","Password:", "Password Incorrect"]) + targets.append(["targets/intermediate2","Secret code:", "Auth Failure"]) + targets.append(["targets/intermediate3","Enter Credentials:", "Invalid Credentials"]) + + print("Intermediate Binary Breaker") + print("Which binary do you want to brute force?") + + for c in range(len(targets)): + print(f"{c}: {targets[c][0]}") + + selection=int(input("Enter the number of the binary to be forced: ")) + + if 0 <= selection < len(targets): + target=targets[selection] + breakBinary(target[0],target[1],target[2], words2) + else: + print("Invalid selection") diff --git a/src/brutus.py b/src/brutus.py new file mode 100644 index 0000000..d68d46a --- /dev/null +++ b/src/brutus.py @@ -0,0 +1,78 @@ +""" Brutus: A simple password-protected password brute-force support tool + +Brutus provides a simple interface for you to run a binary or script and test a password, simplifying the task of breaking passwords with brute force. + +""" +import pexpect +import time + +class Binary: + """ Represents a binary file for cracking and allows it to be run with given input and a test for correct/incorrect password """ + def __init__(self,path,options=[]): + """ Initialises the binary + + Args: + path: the absolute or relative path of the binary + options: an option list of options that are to be given to the binary on execution + """ + self.path=path + self.options=options + self.proc=None + self.queue=[] + def run(self): + """ Begins executing the binary. Returns as soon as it has been launched. """ + argv=[self.path]+self.options + self.proc=child = pexpect.spawn(" ".join(argv)) + def read(self): + """ Reads data from the standard output of the binary. + Returns: + None: if there is no data + A String: containing the next line of output if available.""" + + self.proc.expect(prompt) + self.proc.send(data+"\n") + try: + self.proc.expect(fail, timeout=2) + return False + except pexpect.exceptions.EOF: + return True + + + def attempt(self,prompt,data, fail): + """ Make a guess at a password, when the expected prompt is found + Args: + prompt: a string to idenitfy in the output that signifies WHEN to make the attempt + data: a string to send as the password guess + fail: the text expected on a failed attempt + Returns: + True: If the failure text *IS NOT* found in the output after the attempt + False: If the failure text *IS* found in the output after the attempt + """ + self.proc.expect(prompt) + self.proc.send(data+"\n") + try: + self.proc.expect(fail, timeout=2) + return False + except pexpect.exceptions.EOF: + return True + + + def timedAttempt(self,prompt,data,fail): + """ Make a guess at a password, when the expected prompt is found + Args: + prompt: a string to idenitfy in the output that signifies WHEN to make the attempt + data: a string to send as the password guess + fail: the text expected on a failed attempt + Returns: + a tuple in which the first item is: + + - True: If the failure text *IS NOT* found in the output after the attempt + + - False: If the failure text *IS* found in the output after the attempt + + and the second item is the number of fractional seconds the guess took to make + """ + t1=time.perf_counter() + result=self.attempt(prompt,data,fail) + t2=time.perf_counter() + return (result,t2-t1) diff --git a/targets/.placeholder b/targets/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/test/test_basic.py b/test/test_basic.py new file mode 100644 index 0000000..1303c8f --- /dev/null +++ b/test/test_basic.py @@ -0,0 +1,13 @@ +import pytest +import sys +sys.path.append("./src/") + + +#You will need to write tests for your own functions, or change tests for ones you modify + +import brute_basic + + +def test_guessGenerator(): + #This definitely needs changing and expanding + assert len(brute_basic.generateGuesses())==5 diff --git a/test/test_brutus.py b/test/test_brutus.py new file mode 100644 index 0000000..f240510 --- /dev/null +++ b/test/test_brutus.py @@ -0,0 +1,24 @@ +import pytest +import sys +sys.path.append("./src/") + +#Here the tests are all around the "brutus" module that assists in brute-forcing +#You will need to write tests for your own functions + +import brutus + + +def test_incorrect(): + bad_guesses=["","NO","123","aejfhskdjfhkjsdfh","-1","Swordfish", " swordfish", "swordfish "] + for guess in bad_guesses: + b=brutus.Binary("./test/testbin") + b.run() + result=b.attempt("password:",guess,"Password Incorrect") + assert result==False, f"'{guess}' worked as a guess, but shouldn't have" + + +def test_correct(): + b=brutus.Binary("./test/testbin") + b.run() + result=b.attempt("password:","swordfish","Password Incorrect") + assert result==True, "The correct password was used, but brutus reported it incorrect" diff --git a/test/test_maxPos.py b/test/test_maxPos.py new file mode 100644 index 0000000..34d588e --- /dev/null +++ b/test/test_maxPos.py @@ -0,0 +1,14 @@ +import pytest +import sys +sys.path.append("./src/") + + +#You will need to write tests for your own functions, or change tests for ones you modify + +from brute_advanced import maxPos + + +def test_maxPos(): + assert maxPos([1,2,3])==2 + assert maxPos([1])==0 + assert maxPos([1,-10])==0 diff --git a/test/testbin b/test/testbin new file mode 100755 index 0000000000000000000000000000000000000000..3a73082d4dfe876a61814a8cd0f0c0f7f573b369 GIT binary patch literal 8576 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 VbxP^XU0Z(#UtO<>AXC5ozW^5y=pz6C literal 0 HcmV?d00001 diff --git a/test/testbin.c b/test/testbin.c new file mode 100644 index 0000000..f5b931c --- /dev/null +++ b/test/testbin.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +int main(void) +{ + char* actualPw = "swordfish"; //Of course + char guess[20]; + printf ("Enter the password: "); + fgets ( guess, 80, stdin ); + + //Strip the \n, replace with 0 + guess[strcspn(guess, "\n")] = 0; + printf("You entered [%s]\n",guess); + if(strcmp(guess,actualPw)==0){ + printf("Password Correct\n"); + }else{ + printf("Password Incorrect\n"); + } +}