mirror of
https://github.com/Inndy/twnhi-smartcard-agent.git
synced 2025-07-17 20:53:22 +00:00
358 lines
9.8 KiB
Python
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()
|