Skip to content

Commit

Permalink
v0.3.8 (#48)
Browse files Browse the repository at this point in the history
* pseudo mask-sort

* Update license

* Add extra mask creation features

- bug fixes for mask creation complexity
- support for partial masks when doing verbose calculations
- added support to calculate the mask keyspace value as a part of -v with the mask mode

* add ic flag

add ignore case flag to convert output

* update toggle rule case

toggle rule will now just send "u" instead of toggle rules if no multi case is detected

* Add *-remove rule limits

add operation limits to the remove rules as they were generating invalid rules

* add limit to iterating rules

added index limit to rule replacer to make valid rules

* documentation
  • Loading branch information
JakeWnuk authored Nov 8, 2024
1 parent 39cba41 commit 650fda8
Show file tree
Hide file tree
Showing 8 changed files with 954 additions and 38 deletions.
696 changes: 675 additions & 21 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ git clone https://github.com/JakeWnuk/ptt && cd ptt && docker build -t ptt . &&
---
### Usage:
```
Usage of Password Transformation Tool (ptt) version (0.3.7):
Usage of Password Transformation Tool (ptt) version (0.3.8):
ptt [options] [...]
Accepts standard input and/or additonal arguments.
Expand All @@ -69,6 +69,8 @@ These modify or filter the transformation mode.
Read additional files for input.
-i value
Starting index for transformations if applicable. Accepts ranges separated by '-'.
-ic
Ignore case when processing output and converts to lowercase.
-k value
Only keep items in a file.
-l value
Expand Down
11 changes: 9 additions & 2 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Password Transformation Tool (PTT) Usage Guide
> Version 0.3.7
> Version 0.3.8
## Table of Contents
- [Introduction](#introduction)
- [Installation](#installation)
Expand Down Expand Up @@ -110,6 +110,7 @@ There are some additional notes when importing data and getting started:
- `ptt -n 50`: Show verbose statistics output with a maximum of 50 items.
- `ptt -o [FILE]`: Show output and save JSON output to a file.
- `ptt -md`: Show output as a Markdown table.
- `ptt -ic`: Ignore case when creating output.
- These options are available for all transformations.
#### Rockyou Examples:
`ptt -f rockyou.txt -t pop -l 4-5`:
Expand Down Expand Up @@ -255,7 +256,13 @@ Where `<mask_characters>` can be any of the following:
- `b`: Byte characters
- Multiple characters can be combined to create a mask.

The default value is `uldsb` for all characters. The `-v` flag is optional and, if provided, will print the length of the original string and its character complexity. The format will be `:length:complexity` appended to the end of the output.
The default value is `uldsb` for all characters. The `-v` flag is optional and, if provided, will print the length of the original string and its character complexity. The format will be `:length:complexity:mask-keyspace` appended to the end of the output. The mask keyspace is the number of possible combinations for the masked portion of the string.
```
$ echo 'HelloWorld!I<3ThePasswordTransformationToolPr0j3ct' | go run . -t mask -rm ds -v
[*] All input loaded.
[*] Task complete with 1 unique results.
1 HelloWorld?sI?s?dThePasswordTransformationToolPr?dj?dct:50:4:94
```
### Mask Matching
Masks can be matched to a given string to determine if the string matches the mask. The syntax to match a mask is as follows:
```
Expand Down
15 changes: 13 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/jakewnuk/ptt/pkg/utils"
)

var version = "0.3.7"
var version = "0.3.8"
var wg sync.WaitGroup
var mutex = &sync.Mutex{}
var retain models.FileArgumentFlag
Expand Down Expand Up @@ -96,6 +96,7 @@ func main() {
bypassMap := flag.Bool("b", false, "Bypass map creation and use stdout as primary output. Disables some options.")
debugMode := flag.Int("d", 0, "Enable debug mode with verbosity levels [0-2].")
URLParsingMode := flag.Int("p", 0, "Change parsing mode for URL input. [0 = Strict, 1 = Permissive, 2 = Maximum].")
ignoreCase := flag.Bool("ic", false, "Ignore case when processing output and converts to lowercase.")
flag.Var(&retain, "k", "Only keep items in a file.")
flag.Var(&remove, "r", "Only keep items not in a file.")
flag.Var(&readFiles, "f", "Read additional files for input.")
Expand Down Expand Up @@ -198,6 +199,16 @@ func main() {
return
}

// Print ignore case if provided
if *ignoreCase {
fmt.Fprintf(os.Stderr, "[*] Ignoring case when processing output.\n")
}

// Ignore case if provided
if *ignoreCase {
primaryMap = format.CreateIgnoreCaseMap(primaryMap)
}

// Print remove frequency if provided
if *minimum > 0 {
fmt.Fprintf(os.Stderr, "[*] Removing items with frequency less than %d.\n", *minimum)
Expand Down Expand Up @@ -232,7 +243,7 @@ func main() {
}
}

// if -n is providied, filter ALL results to only that top amount
// if -n is provided, filter ALL results to only that top amount
if *outputVerboseMax > 0 {
primaryMap = format.FilterTopN(primaryMap, *outputVerboseMax)
}
Expand Down
22 changes: 22 additions & 0 deletions pkg/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,28 @@ func FilterTopN(freq map[string]int, n int) map[string]int {
return newFreq
}

// CreateIgnoreCaseMap creates a new map of item frequencies with all keys
// with the same case-insensitive value. The new map will have the
// case-insensitive key as the key and the sum of the frequencies of all keys
//
// Args:
// freq (map[string]int): A map of item frequencies
//
// Returns:
// map[string]int: A new map of item frequencies with case-insensitive keys
func CreateIgnoreCaseMap(freq map[string]int) map[string]int {
newFreq := make(map[string]int)
for key, value := range freq {
key = strings.ToLower(key)
if _, ok := newFreq[key]; ok {
newFreq[key] += value
} else {
newFreq[key] = value
}
}
return newFreq
}

// ----------------------------------------------------------------------------
// Encoding Functions
// ----------------------------------------------------------------------------
Expand Down
161 changes: 153 additions & 8 deletions pkg/mask/mask.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func MakeMaskedMap(input map[string]int, replacementMask string, verbose bool, b
}

if verbose {
newKey = fmt.Sprintf("%s:%d:%d", newKey, len(key), TestMaskComplexity(newKey))
newKey = fmt.Sprintf("%s:%d:%d:%d", newKey, len(key), TestMaskComplexity(newKey), CalculateMaskKeyspace(newKey))
}

if debug {
Expand Down Expand Up @@ -241,7 +241,8 @@ func ConvertMultiByteMask(str string) string {
return returnStr
}

// TestMaskComplexity tests the complexity of an input mask
// TestMaskComplexity tests the complexity of an input full mask or a partial
// mask string and returns a score
//
// Args:
//
Expand All @@ -251,14 +252,62 @@ func ConvertMultiByteMask(str string) string {
//
// (int): Complexity score as an integer
func TestMaskComplexity(str string) int {
complexity := 0
charTypes := []string{"?u", "?l", "?d", "?s", "?b"}
for _, charType := range charTypes {
if strings.Contains(str, charType) {
complexity++
score := 0
lowerBool := false
upperBool := false
digitBool := false
specialBool := false
byteBool := false
for i := 0; i < len(str); i++ {
if str[i] == '?' {
if i+1 < len(str) {
switch str[i+1] {
case 'l':
lowerBool = true
case 'u':
upperBool = true
case 'd':
digitBool = true
case 's':
specialBool = true
case 'b':
byteBool = true
}
}
} else {
if unicode.IsLower(rune(str[i])) {
lowerBool = true
}
if unicode.IsUpper(rune(str[i])) {
upperBool = true
}
if unicode.IsDigit(rune(str[i])) {
digitBool = true
}
if strings.ContainsRune(" !\"#$%&\\()*+,-./:;<=>?@[\\]^_`{|}~'", rune(str[i])) {
specialBool = true
}
if str[i] > 127 {
byteBool = true
}
}
}
return complexity
if lowerBool {
score++
}
if upperBool {
score++
}
if digitBool {
score++
}
if specialBool {
score++
}
if byteBool {
score++
}
return score
}

// RemoveMaskedCharacters removes masked characters from the input map
Expand Down Expand Up @@ -521,3 +570,99 @@ func ShuffleMap(input map[string]int, replacementMask string, swapMap map[string
}
return shuffleMap
}

// CalculateMaskKeyspace accepts a mask or partial mask string and returns the
// estimated keyspace of the mask
//
// Args:
//
// input (string): Input mask or partial mask
//
// Returns:
// (int): Estimated mask complexity
func CalculateMaskKeyspace(input string) int {
if IsMaskAFullMask(input) {
return CalculateKeyspace(input)
}

keyspace := 0
// find the first "?" character in the mask
for i := 0; i < len(input); i++ {
if input[i] == '?' {
if i+1 < len(input) {
switch input[i+1] {
case 'l':
keyspace += CalculateMaskKeyspace(input[i : i+2])
case 'u':
keyspace += CalculateMaskKeyspace(input[i : i+2])
case 'd':
keyspace += CalculateMaskKeyspace(input[i : i+2])
case 's':
keyspace += CalculateMaskKeyspace(input[i : i+2])
case 'b':
keyspace += CalculateMaskKeyspace(input[i : i+2])
case 'a':
keyspace += CalculateMaskKeyspace(input[i : i+2])
}
}
}
}
return keyspace
}

// CalculateKeyspace accepts a mask or partial mask string and returns the
// estimated keyspace of the mask
//
// Args:
// input (string): Input mask or partial mask
//
// Returns:
// (int): Estimated mask complexity
func CalculateKeyspace(input string) int {
keyspace := 0
for i := 0; i < len(input); i += 2 {
if input[i] == '?' && input[i+1] == 'u' {
keyspace += 26
} else if input[i] == '?' && input[i+1] == 'l' {
keyspace += 26
} else if input[i] == '?' && input[i+1] == 'd' {
keyspace += 10
} else if input[i] == '?' && input[i+1] == 's' {
keyspace += 32
} else if input[i] == '?' && input[i+1] == 'b' {
keyspace += 256
} else if input[i] == '?' && input[i+1] == 'a' {
keyspace += 95
}
}
return keyspace
}

// IsMaskAFullMask accepts a mask string and returns the type of mask
// either a full mask or a partial mask
// A full mask is a mask that contains all mask characters in the mask and
// a partial mask is a mask that contains some mask characters in the mask
//
// Args:
// input (string): Input mask or partial mask
//
// Returns:
// (bool): True if the mask is a full mask, false if the mask is a partial mask
func IsMaskAFullMask(input string) bool {
for i := 0; i < len(input); i += 2 {
if input[i] != '?' {
return false
}

if len(input) == 1 {
return false
}

if input[i+1] != 'u' && input[i+1] != 'l' && input[i+1] != 'd' && input[i+1] != 's' && input[i+1] != 'b' && input[i+1] != 'a' {
return false
}

}

return true
}
60 changes: 58 additions & 2 deletions pkg/mask/mask_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import (
// - MakeMatchedMaskedMap()
// - BoundarySplitPopMap()
// - ShuffleMap()
// - CalculateKeySpace()
// - IsMaskAFullMask()
//
// ----------------------------------------------------------------------------
// Functions without Unit Tests
// ----------------------------------------------------------------------------
// -
// - CalculateMaskKeyspace()

// Unit Test for ConstructReplacements()
func TestConstructReplacements(t *testing.T) {
Expand Down Expand Up @@ -192,7 +194,7 @@ func TestTestMaskComplexity(t *testing.T) {

// Define test cases
tests := testCases{
{"?l?l?l123", 1},
{"?l?l?l123", 2},
{"?l?l?l?d?d?d", 2},
{"?l?l?l?d?d?d?s?s?s", 3},
{"?u?u?l?d?d?d?s?s?s", 4},
Expand Down Expand Up @@ -316,3 +318,57 @@ func TestShuffleMap(t *testing.T) {
}
}
}

// Unit Test for CalculateKeyspace()
func TestCalculateKeyspace(t *testing.T) {
// Define a test case struct
type testCase struct {
input string
output int
}

type testCases []testCase

// Define test cases
tests := testCases{
{"?l?l?l123", 78},
{"?l?l?l?d?d?d", 108},
{"?l?l?l?d?d?d?s?s?s", 204},
{"?u?u?l?d?d?d?s?s?s", 204},
}

// Run test cases
for _, test := range tests {
output := CalculateKeyspace(test.input)
if output != test.output {
t.Errorf("Test failed: %v inputted, %v expected, %v returned", test.input, test.output, output)
}
}
}

// Unit Test for IsMaskAFullMask()
func TestIsMaskAFullMask(t *testing.T) {
// Define a test case struct
type testCase struct {
input string
output bool
}

type testCases []testCase

// Define test cases
tests := testCases{
{"?l?l?l123", false},
{"?l?l?l?d?d?d", true},
{"?l?l?l?d?d?d?s?s?s", true},
{"?u?u?l?d?d?d?s?s?s", true},
}

// Run test cases
for _, test := range tests {
output := IsMaskAFullMask(test.input)
if output != test.output {
t.Errorf("Test failed: %v inputted, %v expected, %v returned", test.input, test.output, output)
}
}
}
Loading

0 comments on commit 650fda8

Please sign in to comment.