Files
twnhi-smartcard-agent/pysoxy.py
2020-05-20 00:12:35 +08:00

358 lines
9.8 KiB
Python

# This file is part of twnhi-smartcard-agent.
#
# twnhi-smartcard-agent 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 3 of the License, or
# (at your option) any later version.
#
# twnhi-smartcard-agent 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.
#
# You should have received a copy of the GNU General Public License
# along with twnhi-smartcard-agent.
# If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
"""
Small Socks5 Proxy Server in Python
from https://github.com/MisterDaneel/
"""
# Network
import socket
import select
from struct import pack, unpack
# System
import traceback
from threading import Thread, activeCount
from signal import signal, SIGINT, SIGTERM
from time import sleep
import sys
hijacker = None
hijacked_host = None
#
# Configuration
#
MAX_THREADS = 200
BUFSIZE = 2048
TIMEOUT_SOCKET = 5
LOCAL_ADDR = '127.0.0.1'
LOCAL_PORT = 17777
# Parameter to bind a socket to a device, using SO_BINDTODEVICE
# Only root can set this option
# If the name is an empty string or None, the interface is chosen when
# a routing decision is made
# OUTGOING_INTERFACE = "eth0"
OUTGOING_INTERFACE = ""
#
# Constants
#
'''Version of the protocol'''
# PROTOCOL VERSION 5
VER = b'\x05'
'''Method constants'''
# '00' NO AUTHENTICATION REQUIRED
M_NOAUTH = b'\x00'
# 'FF' NO ACCEPTABLE METHODS
M_NOTAVAILABLE = b'\xff'
'''Command constants'''
# CONNECT '01'
CMD_CONNECT = b'\x01'
'''Address type constants'''
# IP V4 address '01'
ATYP_IPV4 = b'\x01'
# DOMAINNAME '03'
ATYP_DOMAINNAME = b'\x03'
class ExitStatus:
""" Manage exit status """
def __init__(self):
self.exit = False
def set_status(self, status):
""" set exist status """
self.exit = status
def get_status(self):
""" get exit status """
return self.exit
def error(msg="", err=None):
""" Print exception stack trace python """
if msg:
traceback.print_exc()
print("[-] {} - Code: {}, Message: {}".format(msg, str(err[0]), err[1]))
else:
traceback.print_exc()
def proxy_loop(socket_src, socket_dst):
""" Wait for network activity """
while not EXIT.get_status():
try:
reader, _, _ = select.select([socket_src, socket_dst], [], [], 1)
except select.error as err:
error("Select failed", err)
return
if not reader:
continue
try:
for sock in reader:
data = sock.recv(BUFSIZE)
if not data:
return
if sock is socket_dst:
socket_src.send(data)
else:
socket_dst.send(data)
except socket.error as err:
error("Loop failed", err)
return
def connect_to_dst(dst_addr, dst_port):
""" Connect to desired destination """
sock = create_socket()
if OUTGOING_INTERFACE:
try:
sock.setsockopt(
socket.SOL_SOCKET,
socket.SO_BINDTODEVICE,
OUTGOING_INTERFACE.encode(),
)
except PermissionError as err:
print("[-] Only root can set OUTGOING_INTERFACE parameter")
EXIT.set_status(True)
try:
sock.connect((dst_addr, dst_port))
print('[+] Connect to %s:%d' % (dst_addr, dst_port))
return sock
except socket.error as err:
error("Failed to connect to DST", err)
return 0
def request_client(wrapper):
""" Client request details """
# +----+-----+-------+------+----------+----------+
# |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
# +----+-----+-------+------+----------+----------+
try:
s5_request = wrapper.recv(BUFSIZE)
except ConnectionResetError:
if wrapper != 0:
wrapper.close()
error()
return False
# Check VER, CMD and RSV
if (
s5_request[0:1] != VER or
s5_request[1:2] != CMD_CONNECT or
s5_request[2:3] != b'\x00'
):
return False
# IPV4
if s5_request[3:4] == ATYP_IPV4:
dst_addr = socket.inet_ntoa(s5_request[4:-2])
dst_port = unpack('>H', s5_request[8:len(s5_request)])[0]
# DOMAIN NAME
elif s5_request[3:4] == ATYP_DOMAINNAME:
sz_domain_name = s5_request[4]
dst_addr = s5_request[5: 5 + sz_domain_name - len(s5_request)]
port_to_unpack = s5_request[5 + sz_domain_name:len(s5_request)]
dst_port = unpack('>H', port_to_unpack)[0]
else:
return False
return (dst_addr, dst_port)
def request(wrapper):
"""
The SOCKS request information is sent by the client as soon as it has
established a connection to the SOCKS server, and completed the
authentication negotiations. The server evaluates the request, and
returns a reply
"""
dst = request_client(wrapper)
# Server Reply
# +----+-----+-------+------+----------+----------+
# |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
# +----+-----+-------+------+----------+----------+
rep = b'\x07'
bnd = b'\x00' + b'\x00' + b'\x00' + b'\x00' + b'\x00' + b'\x00'
hijacked = False
if dst:
if dst[0] == hijacked_host.encode():
print('[*] Hijack %s to local server' % hijacked_host)
hijacked = True
socket_dst = True
else:
socket_dst = connect_to_dst(dst[0], dst[1])
if not dst or socket_dst == 0:
rep = b'\x01'
else:
rep = b'\x00'
if hijacked:
bnd = b'\x01\x01\x01\x01\x01\x01'
else:
bnd = socket.inet_aton(socket_dst.getsockname()[0])
bnd += pack(">H", socket_dst.getsockname()[1])
reply = VER + rep + b'\x00' + ATYP_IPV4 + bnd
try:
wrapper.sendall(reply)
except socket.error:
if wrapper != 0:
wrapper.close()
return
# start proxy
if rep == b'\x00':
if hijacked:
hijacker(wrapper)
else:
proxy_loop(wrapper, socket_dst)
if wrapper != 0:
wrapper.close()
if socket_dst != 0 and socket_dst != True:
socket_dst.close()
def subnegotiation_client(wrapper):
"""
The client connects to the server, and sends a version
identifier/method selection message
"""
# Client Version identifier/method selection message
# +----+----------+----------+
# |VER | NMETHODS | METHODS |
# +----+----------+----------+
try:
identification_packet = wrapper.recv(BUFSIZE)
except socket.error:
error()
return M_NOTAVAILABLE
# VER field
if VER != identification_packet[0:1]:
return M_NOTAVAILABLE
# METHODS fields
nmethods = identification_packet[1]
methods = identification_packet[2:]
if len(methods) != nmethods:
return M_NOTAVAILABLE
for method in methods:
if method == ord(M_NOAUTH):
return M_NOAUTH
return M_NOTAVAILABLE
def subnegotiation(wrapper):
"""
The client connects to the server, and sends a version
identifier/method selection message
The server selects from one of the methods given in METHODS, and
sends a METHOD selection message
"""
method = subnegotiation_client(wrapper)
# Server Method selection message
# +----+--------+
# |VER | METHOD |
# +----+--------+
if method != M_NOAUTH:
return False
reply = VER + method
try:
wrapper.sendall(reply)
except socket.error:
error()
return False
return True
def connection(wrapper):
""" Function run by a thread """
if subnegotiation(wrapper):
request(wrapper)
def create_socket():
""" Create an INET, STREAMing socket """
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(TIMEOUT_SOCKET)
except socket.error as err:
error("Failed to create socket", err)
sys.exit(0)
return sock
def bind_port(sock):
"""
Bind the socket to address and
listen for connections made to the socket
"""
try:
print('[+] Socks5 proxy bind on {}:{}'.format(LOCAL_ADDR, str(LOCAL_PORT)))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((LOCAL_ADDR, LOCAL_PORT))
except socket.error as err:
error("Bind failed", err)
sock.close()
sys.exit(0)
# Listen
try:
sock.listen(10)
except socket.error as err:
error("Listen failed", err)
sock.close()
sys.exit(0)
return sock
def exit_handler(signum, frame):
""" Signal handler called with signal, exit script """
print('[*] Signal handler called with signal', signum)
EXIT.set_status(True)
def main(hijack, host):
""" Main function """
global hijacker
global hijacked_host
hijacker = hijack
hijacked_host = host
new_socket = create_socket()
bind_port(new_socket)
#signal(SIGINT, exit_handler)
#signal(SIGTERM, exit_handler)
while not EXIT.get_status():
if activeCount() > MAX_THREADS:
sleep(3)
continue
try:
wrapper, _ = new_socket.accept()
wrapper.setblocking(1)
except socket.timeout:
continue
except socket.error:
error()
continue
except TypeError:
error()
sys.exit(0)
recv_thread = Thread(target=connection, args=(wrapper, ))
recv_thread.start()
new_socket.close()
EXIT = ExitStatus()
if __name__ == '__main__':
main()