#!/usr/bin/env python
import argparse
from termcolor import colored
"""
Script to generate derivations of a string making Unicode case transformations to exploit Unicode case mapping collisions.

Sources:
    https://0xsha.io/blog/the-fall-of-mighty-django-exploiting-unicode-case-transformations
    https://www.gosecure.net/blog/2020/08/04/unicode-for-security-professionals/
    https://dev.to/jagracey/hacking-github-s-auth-with-unicode-s-turkish-dotless-i-460n
    https://github.com/jagracey/Awesome-Unicode

Dependencies:
    pip3 install termcolor
"""
# List of tuple (Output Char,Code Point): Below are defined characters that collide into the English alphabet
DERIVATIONS_UPPER_LIST = [("SS", "\u00DF"), ("I", "\u0131"), ("S", "\u017F"), ("FF", "\uFB00"),
                          ("FI", "\uFB01"), ("FL", "\uFB02"), ("FFI", "\uFB03"), ("FFL", "\uFB04"),
                          ("ST", "\uFB05"), ("FL", "\uFB02")]
DERIVATIONS_LOWER_LIST = [("k", "\u212A")]


def derivate(base_string):
    derivations = []
    base_work = base_string.upper()
    for derivation_info in DERIVATIONS_UPPER_LIST:
        output_char = derivation_info[0]
        code_point = derivation_info[1]
        derivation = base_work.replace(output_char, code_point)
        if derivation != base_work and derivation not in derivations:
            derivations.append(derivation)
    base_work = base_string.lower()
    for derivation_info in DERIVATIONS_LOWER_LIST:
        output_char = derivation_info[0]
        code_point = derivation_info[1]
        derivation = base_work.replace(output_char, code_point)
        if derivation != base_work and derivation not in derivations:
            derivations.append(derivation)
    return derivations


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Script to generate derivations of a string making Unicode case transformations to exploit Unicode case mapping collisions.")
    required_params = parser.add_argument_group("required named arguments")
    required_params.add_argument("-i", action="store", dest="base_string", help="String to use as base for the derivations.", required=True)
    parser.add_argument("-o", action="store", dest="out_file", help="Output file path in which store the derivations (ex: /tmp/derivations.txt).", required=False, default="/tmp/derivations.txt")
    args = parser.parse_args()
    base_string = args.base_string
    print(colored(f"[+] Generate derivations from '{base_string}' string...", "yellow"))
    derivations = derivate(base_string)
    print(f"{len(derivations)} derivations generated.")
    print(colored(f"[+] Save derivations to file '{args.out_file}'...", "yellow"))
    with open(args.out_file, mode="w", encoding="utf-8") as f:
        f.write("\n".join(derivations))
    print(f"Derivations saved.")
