Kats
Kats

Reputation: 143

Python socket.accept() not receiving any connection requests

I recently bought the book Black Hat Python by Justin Seitz because I've been interested in getting into Python and playing around with network security.

I'm working in Kali Linux and one of the examples in the book is a simple TCP Proxy. I've already written a few scripts that are connected through ports, but now I'm having trouble trying to create a connection to a remote server, such as Google.

Now, I admit that I'm just about a complete novice with Python. I mostly program in C++. As a disclosure, I did move the main() function to the top of the code as to be organized in order of execution. In the actual source, it's located at the very bottom.

import sys
import socket
import threading
import time
import os

def main():
    if len(sys.argv[1:]) != 5:
        print "Usage: nproxy.py [localhost] [localport] [remotehost] [remoteport] [receive_first]"
        print "Example: nproxy.py 127.0.0.1 5555 ftp.example.com 5555 True"
        sys.exit(0)

    #setup listening parameters
    local_host = sys.argv[1]
    local_port = sys.argv[2]
    #setup remote target
    remote_host = sys.argv[3]
    remote_port = sys.argv[4]
    #this tells our proxy to connect and receive data before sending to the remote host
    receive_first = sys.argv[5]

    if "True" in receive_first:
        receive_first = True
    else:
        receive_first = False

Everything from here up is just the main() function getting all of the parameters and variables parsed and setup.

    #spin listening socket
    server_loop(local_host, local_port, remote_host, remote_port, receive_first)

#status class for my timeout function to stop the socket.accept()
class status:
    connected = False

The server_loop() function is the source function for my issue. It contains all of the code for connecting clients to the servers.

The first section is just setting up my server socket as well as creating a few execution shortcuts for myself.

def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
    #setup server socket
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    if "@" in local_host:
        local_host = "127.0.0.1"

    if "@" in local_port:
        local_port = remote_port

    local_addr = (local_host, int(local_port))
    remote_addr = (remote_host, int(remote_port))

Binding the server to my local address works just fine. It's during the while loop that the problem starts, I would think.

    try:
        server.bind(local_addr)
    except:
        print "[!!] Failed to listen on %s:%d" % local_addr
        print "[!!] Check for other listening sockets or correct permissions."
        sys.exit(0)

    print "[*] Listening on %s:%d" % local_addr
    server.listen(5)

Here starts the while loop to keep the program open to handle incoming connections. The tout thread contains the code for timing out and closing the server socket.

    while True:
        tout = threading.Thread(target=timeout, args=(local_host, local_port))
        tout.start()
        print "[*] Connecting..."

This seems to be where my program hangs. You see, I'm not 100% sure how this is supposed to be handled. In the book, the code works just fine for him and will successfully return from server.accept() and connect the client_socket to the correct address.

In my execution, however, the program stops at the accept() function and never returns any socket or address data. Unless my understanding of the accept() function is wrong and the code wasn't designed with remote servers in mind.

I'm not entirely sure how the accept() function even works. Does it send a SYN packet to the host to initiate a connection request or is the function just sitting there waiting, thinking that the host is going to send a SYN-ACK return when no SYN was even sent in the first place?

        client_socket, addr = server.accept()

        #The timeout function is designed to close the server by feeding     
        #it the localhost address. If this happens, the statement below catches the 
        #program and calls that the server request has timed out.

        if "127.0.0.1" in addr[0]:
            print "[!!] Server connection has timed out."
            sys.exit(0)
        #print out the local connection information
        status.connected = True
        print "[==>] Received incoming connection from %s:%d" % (addr[0], addr[1])

        #start a thread to talk to the remote host

If the server.accept() were to return normally, the client_socket would be connected to the host and initialize the rest of the proxy as it sends and receives data normally. The rest of the code is for completion in case I missed something crucial that's actually the cause of the failure.

        proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket, remote_addr, receive_first))
        proxy_thread.start()

def timeout(local_host, local_port):
    t = 5
    while True:
        if status.connected is True:
            break
        if t <= 0:
            wake = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            wake.connect((local_host, int(local_port)))
            break
        time.sleep(0.1)
        t-=0.1


def proxy_handler(client_socket, remote_host, remote_port, receive_first):
    #connect to the remote host
    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote_socket.connect(remote_addr)

    if receive_first:
        remote_buffer = receive_from(remote_socket)
        hexdump(remote_buffer)

        #send it to our response handler
        remote_buffer = response_handler(remote_buffer)

        #if we have data to send to our local client, send it
        if len(remote_buffer):
            print "[<==] Sending %d bytes to localhost." % len(remote_buffer)
            client_socket.send(remote_buffer)

    #now let's loop and read from local
    #send to remote, send to local
    #rinse and repeat
    while True:
        #read from localhost
        local_buffer = receive_from(client_socket)
        if len(local_buffer):
            print "[==>] Received %d bytes from localhost." % len(local_buffer)
            hexdump(local_buffer)

            #send it to our request handler
            local_buffer = request_handler(local_buffer)

            #send off the data to the remote host
            remote_socket.send(local_buffer)
            print "[==>] Sent to remote."

        #receive back a response
        remote_buffer = receive_from(remote_socket)

        if len(remote_buffer):
            print "[<==] Received %d bytes from remote" % len(remote_buffer)
            hexdump(remote_buffer)

            #send to our response handler
            remote_buffer = response_handler(remote_buffer)

            #send the response to the local socket
            client_socket.send(remote_buffer)
            print "[<==] Sent to localhost."

        #if no more data on either side, close the connection
        if not len(local_buffer) or not len(remote_buffer):
            client_socket.close()
            remote_socket.close()
            print "[*] No more data. Closing connections..."
            break

def hexdump(src, length=16):
    result = []
    digits = 4 if isinstance(src, unicode) else 2
    for i in xrange(0, len(src), length):
        s = src[i:i+length]
        hexa = b' '.join(["%0*X" % (digits, ord(x)) for x in s])
        text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])
        result.append(b"%04X   %-*s   %s" % (i, length*digits + 1), hexa, text)
    print b'\n'.join(result)

def receive_from(connection):
    buffer = ""

    #we set a 2 second timeout; depending on your target, this may need to be adjusted
    connection.settimeout(2)

    try:
        #keep reading into the buffer until there's no more data or we time out
        while True:
            data = connection.recv(4096)
            if not data:
                break
            buffer += data
    except:
        pass
    return buffer

#modify any requests destined for remote host
def request_handler(buffer):
    #perform packet modifications
    return buffer

#modify any responses destined for the localhost
def response_handler(buffer):
    #perform packet modification
    return buffer

main()

When I run the program, I use: sudo python ./nproxy.py @ @ www.google.com 21 True

In order, it calls my program, the first two parameters are shortcuts to automatically connect to the localhost address as well as the port of the host that we're trying to connect to. In this case, it's 21, but I've tried on port 80 as well with the same results. I've also tried about a dozen different websites as well and none seem to be returning my connection requests.

Thank you so much in advance for any advice or help you can give.

Upvotes: 2

Views: 3742

Answers (1)

kalhartt
kalhartt

Reputation: 4129

Your program is working as expected, although the formatting string in hexdump is invalid and proxy_handler should be proxy_handler(client_socket, remote_addr, receive_first). I also disabled the timeout, since I'm using the proxy locally I dont' want requests to shut it down. Using it in the following manner works for me:

# Start proxy on local port 12345 and route to google
$ python @ 12345 www.google.com 80 True

# In another terminal, request from the server
# This should print the same as `curl www.google.com`
$ curl 127.0.0.1:1235

I think you are misinterpreting what this is supposed to do. This quote being the main reason why.

In my execution, however, the program stops at the accept() function and never returns any socket or address data. Unless my understanding of the accept() function is wrong and the code wasn't designed with remote servers in mind.

I'll start by saying methods on a socket are basically thin wrappers around the corresponding C functions, use man 2 socket or man 2 accept for more details than the python docs might offer.

To answer your question though, accept() is blocking because there is no client. It is waiting for another program to send a SYN packet to its open socket, which it will respond to with SYN|ACK. This part is all related to the client connecting to the proxy, you seem to think it involves the remote host somehow.

Upvotes: 1

Related Questions