Writing an Alexa Port Scanner for Couch Hacking

As I got one for Prime Day, I figured that I would write an Alexa Port Scanner.

Introduction

I do not think that Alexa will replace many (or even any) of my day-to-day tools. That said, I still figured it would be fun to control some of my automation by voice.

My first idea was to develop a simple port scanner, turn it into an Alexa skill, and somehow load it onto my device. Unfortunately, I had no idea how Alexa actually worked outside of using my friend’s.

Tutorial

Developing skills for Alexa isn’t too difficult, and I followed the official Python tutorial. That said, I think it took me more than 5 minutes, as I did not have any of the prerequisite accounts created.

The tutorial has you build a simple application to set and retrieve your favorite color.

The code can also be found at this gist.

I ended up not even completing or testing the tutorial application, but modifying it as I went along.

Intent Modifications

For my port scanner, I knew that I would first need to change the Intents.

As the system needed to perform voice recognition, I setup a list of sites that the skill could scan. First, I modified the tutorial’s intent schema. I also added the StopIntent so that I could stop the skill using, “Alexa stop”.

{
  "intents": [
    {
      "intent": "PortScanIntent",
      "slots": [
        {
          "name": "Site",
          "type": "LIST_OF_SITES"
        }
      ]
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    }
  ]
}

With my schema modified, I also configured LIST_OF_SITES. For now, I only added sites under my control.

doyler.net
r4y.pw

As I was the only one using this skill for now, I also only setup a few sample utterances.

PortScanIntent port scan {Site}
PortScanIntent what ports are open on {Site}
PortScanIntent scan the open ports on {Site}

Lambda Function Modifications

Once the simple modifications to the intents were complete, it was time to change the actual application functionality.

The final code is in the next section, I just want to talk about what I changed.

First, I modified all the text and speech output to make sense for my application.

Then, I removed all the color saving and retrieving methods and added a scan_site() method of my own.

The basis for the port scanner was one that I shared on this site a bit back.

Other than that, I added some simple error checking, debug statements, and a way to handle the StopIntent.

You can find the final application below, and the GitHub link is at bottom of this post.

from __future__ import print_function
import socket
import sys

# --------------- Helpers that build all of the responses ----------------------

def build_speechlet_response(title, output, reprompt_text, should_end_session):
    return {
        'outputSpeech': {
            'type': 'PlainText',
            'text': output
        },
        'card': {
            'type': 'Simple',
            'title': "SessionSpeechlet - " + title,
            'content': "SessionSpeechlet - " + output
        },
        'reprompt': {
            'outputSpeech': {
                'type': 'PlainText',
                'text': reprompt_text
            }
        },
        'shouldEndSession': should_end_session
    }


def build_response(session_attributes, speechlet_response):
    return {
        'version': '1.0',
        'sessionAttributes': session_attributes,
        'response': speechlet_response
    }


# --------------- Functions that control the skill's behavior ------------------

def get_welcome_response():
    """ If we wanted to initialize the session to have some attributes we could
    add those here
    """

    session_attributes = {}
    card_title = "Welcome"
    speech_output = "Welcome to the Alexa Port Scanner. "\
                    "Please scan a site by saying, "\
                    "Port Scan doyler.net"
    # If the user either does not reply to the welcome message or says something
    # that is not understood, they will be prompted again with this text.
    reprompt_text = "Please tell me a site you'd like to scan by saying, " \
                    "Port Scan doyler.net"
    should_end_session = False
    return build_response(session_attributes, build_speechlet_response(
        card_title, speech_output, reprompt_text, should_end_session))


def handle_session_end_request():
    card_title = "Session Ended"
    speech_output = "Thank you for trying the Alexa Port Scanner. " \
                    "Have a nice day! "
    # Setting this to true ends the session and exits the skill.
    should_end_session = True
    return build_response({}, build_speechlet_response(
        card_title, speech_output, None, should_end_session))


def scan_site(intent, session):
    card_title = intent['name']
    session_attributes = {}
    open_ports = []
    should_end_session = False

    if 'Site' in intent['slots']:
        host = intent['slots']['Site']['value']
        host = host.replace(" ", "")

        ports = [22, 23, 80, 443, 445, 3389]

        for port in ports:
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.settimeout(5)
                result = s.connect_ex((host, port))
                if result == 0:
                    session_attributes[port] = 'open'
                    open_ports.append(port)
                    print(str(open_ports))
                s.close()
            except:
                print("Unexpected error:" + str(sys.exc_info()[0]))
                pass
        
        speech_output = "The following ports are open on doyler.net " + \
                        str(open_ports) + \
                        "."
        reprompt_text = "You can ask me to scan a different site by saying, " \
                        "Port Scan r4y.pw"
    else:
        speech_output = "I'm not sure what site you want to scan. " \
                        "Please try again."
        reprompt_text = "I'm not sure what site you want to scan. " \
                        "You can tell me your favorite color by saying, " \
                        "Port Scan doyler.net"
    return build_response(session_attributes, build_speechlet_response(
        card_title, speech_output, reprompt_text, should_end_session))



# --------------- Events ------------------

def on_session_started(session_started_request, session):
    """ Called when the session starts """

    print("on_session_started requestId=" + session_started_request['requestId']
          + ", sessionId=" + session['sessionId'])


def on_launch(launch_request, session):
    """ Called when the user launches the skill without specifying what they
    want
    """

    print("on_launch requestId=" + launch_request['requestId'] +
          ", sessionId=" + session['sessionId'])
    # Dispatch to your skill's launch
    return get_welcome_response()


def on_intent(intent_request, session):
    """ Called when the user specifies an intent for this skill """

    print("on_intent requestId=" + intent_request['requestId'] +
          ", sessionId=" + session['sessionId'])

    intent = intent_request['intent']
    intent_name = intent_request['intent']['name']

    # Dispatch to your skill's intent handlers
    if intent_name == "PortScanIntent":
        return scan_site(intent, session)
    elif intent_name == "AMAZON.HelpIntent":
        return get_welcome_response()
    elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":
        return handle_session_end_request()
    else:
        raise ValueError("Invalid intent")


def on_session_ended(session_ended_request, session):
    """ Called when the user ends the session.

    Is not called when the skill returns should_end_session=true
    """
    print("on_session_ended requestId=" + session_ended_request['requestId'] +
          ", sessionId=" + session['sessionId'])
    # add cleanup logic here


# --------------- Main handler ------------------

def lambda_handler(event, context):
    """ Route the incoming request based on type (LaunchRequest, IntentRequest,
    etc.) The JSON body of the request is provided in the event parameter.
    """
    print("event.session.application.applicationId=" +
          event['session']['application']['applicationId'])

    """
    Uncomment this if statement and populate with your skill's application ID to
    prevent someone else from configuring a skill that sends requests to this
    function.
    """
    # if (event['session']['application']['applicationId'] !=
    #         "amzn1.echo-sdk-ams.app.[unique-value-here]"):
    #     raise ValueError("Invalid Application ID")

    if event['session']['new']:
        on_session_started({'requestId': event['request']['requestId']},
                           event['session'])

    if event['request']['type'] == "LaunchRequest":
        return on_launch(event['request'], event['session'])
    elif event['request']['type'] == "IntentRequest":
        return on_intent(event['request'], event['session'])
    elif event['request']['type'] == "IntentRequest":
        return on_session_ended(event['request'], event['session'])
    elif event['request']['type'] == "SessionEndedRequest":
        return on_session_ended(event['request'], event['session'])

Debugging

While there was plenty of testing and debugging going on during the entire process, I ran into a few issues towards the end.

First, utilizing print statements can still help, and you can find the output in your Lambda logs.

Additionally, be prepared to see plenty of screens that look like this in your Alexa app.

Alexa Port Scanner - Errors

That said, while it took me about an hour, I figured out what my biggest issue with the scanner was. While I was telling Alexa, “Port scan doyler.net”. Alexa was hearing “Port scan doyler .net”. Unfortunately, TCP connections don’t work very well with spaces in the middle, and my scanner code was throwing errors. This is the reasoning for the .strip() before actually scanning the provided site.

Finally in Action

After all of the debugging was complete, I was able to successfully port scan this site using Alexa!

Alexa Port Scanner - Success

I even recorded a short video so that you can hear her as well.

Alexa Port Scanner – Conclusion

While this isn’t the most useful security tool, it is still fun to do some enumeration from my couch.

Unfortunately, due to the way the voice recognition works, I am not sure how to scan a site without first adding it to my intents.

Other than that, I’m sure that I could have some more useful output or reporting with this.

Additionally, if you have any ideas what else I could/should automate with Alexa, then let me know!

Finally, you can find the code and updates in my GitHub repository.

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, eWPTX, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for SecureWorks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (OSCE?!) or side project to work on, he enjoys playing video games, traveling, and watching sports.

2 Comments

Filed under Security Not Included

2 Responses to Writing an Alexa Port Scanner for Couch Hacking

  1. Josh Holmes

    Dude that was awesome!

Leave a Reply

Your email address will not be published. Required fields are marked *

*