86890704fd
todo: add documentation & wireshark dissector
182 lines
5.5 KiB
Python
Executable file
182 lines
5.5 KiB
Python
Executable file
# This file is part of Scapy
|
|
# See http://www.secdev.org/projects/scapy for more information
|
|
# Copyright (C) Philippe Biondi <phil@secdev.org>
|
|
# This program is published under a GPLv2 license
|
|
|
|
# scapy.contrib.description = Socket Secure (SOCKS)
|
|
# scapy.contrib.status = loads
|
|
|
|
"""SOCKS4/5 Protocol
|
|
|
|
You can change the server ports that are used in the SOCKS layer by editing.
|
|
conf.contribs['socks']['serverports']
|
|
"""
|
|
|
|
from scapy.config import conf
|
|
from scapy.error import warning
|
|
from scapy.layers.dns import DNSStrField
|
|
from scapy.layers.inet import TCP, UDP
|
|
from scapy.layers.inet6 import IP6Field
|
|
from scapy.fields import ByteField, ByteEnumField, ShortField, IPField, \
|
|
StrField, MultipleTypeField
|
|
from scapy.packet import Packet, bind_layers, bind_bottom_up
|
|
|
|
# TODO: support the 3 different authentication exchange procedures for SOCKS5 # noqa: E501
|
|
# 1 - Plain (https://tools.ietf.org/html/rfc1928 - 3.Procedure for TCP-based clients) # noqa: E501
|
|
# 2 - Username/password (https://tools.ietf.org/html/rfc1929)
|
|
# 3 - GSS-API (https://tools.ietf.org/html/rfc1961)
|
|
|
|
conf.contribs.setdefault('socks', {})
|
|
conf.contribs['socks'].setdefault('serverports', [1080])
|
|
|
|
|
|
class SOCKS(Packet):
|
|
fields_desc = [
|
|
ByteEnumField("vn", 0x5,
|
|
{0x4: "v4 - Request", 0x0: "v4 - Reply", 0x5: "v5"}),
|
|
]
|
|
|
|
def guess_payload_class(self, pkt):
|
|
d_port = s_port = True
|
|
if self.underlayer and isinstance(self.underlayer, TCP):
|
|
ports = conf.contribs['socks']['serverports']
|
|
d_port = self.underlayer.dport in ports
|
|
s_port = self.underlayer.sport in ports
|
|
if self.vn == 0x5:
|
|
if d_port:
|
|
return SOCKS5Request
|
|
elif s_port:
|
|
return SOCKS5Reply
|
|
elif self.vn == 0x4:
|
|
if d_port:
|
|
return SOCKS4Request
|
|
elif self.vn == 0x0:
|
|
if s_port:
|
|
return SOCKS4Reply
|
|
warning("No TCP underlayer, or dport/sport not in "
|
|
"conf.contribs['socks']['serverports']. "
|
|
"Assuming a SOCKS v5 request layer")
|
|
return SOCKS5Request
|
|
|
|
def add_payload(self, payload):
|
|
if self.underlayer and isinstance(self.underlayer, TCP):
|
|
if isinstance(payload, (SOCKS5Request, SOCKS4Request)):
|
|
self.underlayer.dport = 1080
|
|
self.underlayer.sport = 1081
|
|
elif isinstance(payload, (SOCKS5Reply, SOCKS4Reply)):
|
|
self.underlayer.sport = 1080
|
|
self.underlayer.dport = 1081
|
|
Packet.add_payload(self, payload)
|
|
|
|
|
|
bind_bottom_up(TCP, SOCKS, sport=1080)
|
|
bind_bottom_up(TCP, SOCKS, dport=1080)
|
|
|
|
# SOCKS v4
|
|
|
|
_socks4_cd_request = {
|
|
1: "Connect",
|
|
2: "Bind"
|
|
}
|
|
|
|
|
|
class SOCKS4Request(Packet):
|
|
name = "SOCKS 4 - Request"
|
|
overload_fields = {SOCKS: {"vn": 0x4}}
|
|
fields_desc = [
|
|
ByteEnumField("cd", 1, _socks4_cd_request),
|
|
ShortField("dstport", 80),
|
|
IPField("dst", "0.0.0.0"),
|
|
StrField("userid", ""),
|
|
ByteField("null", 0),
|
|
]
|
|
|
|
|
|
_socks4_cd_reply = {
|
|
90: "Request granted",
|
|
91: "Request rejected",
|
|
92: "Request rejected - SOCKS server cannot connect to identd",
|
|
93: "Request rejected - user-ids mismatch"
|
|
}
|
|
|
|
|
|
class SOCKS4Reply(Packet):
|
|
name = "SOCKS 4 - Reply"
|
|
overload_fields = {SOCKS: {"vn": 0x0}}
|
|
fields_desc = [
|
|
ByteEnumField("cd", 90, _socks4_cd_reply),
|
|
] + SOCKS4Request.fields_desc[1:-2] # Re-use dstport, dst and userid
|
|
|
|
# SOCKS v5 - TCP
|
|
|
|
|
|
_socks5_cdtypes = {
|
|
1: "Connect",
|
|
2: "Bind",
|
|
3: "UDP associate",
|
|
}
|
|
|
|
|
|
class SOCKS5Request(Packet):
|
|
name = "SOCKS 5 - Request"
|
|
overload_fields = {SOCKS: {"vn": 0x5}}
|
|
fields_desc = [
|
|
ByteEnumField("cd", 0x0, _socks5_cdtypes),
|
|
ByteField("res", 0),
|
|
ByteEnumField("atyp", 0x1,
|
|
{0x1: "IPv4", 0x3: "DomainName", 0x4: "IPv6"}),
|
|
MultipleTypeField(
|
|
[
|
|
# IPv4
|
|
(IPField("addr", "0.0.0.0"), lambda pkt: pkt.atyp == 0x1),
|
|
# DNS
|
|
(DNSStrField("addr", ""), lambda pkt: pkt.atyp == 0x3),
|
|
# IPv6
|
|
(IP6Field("addr", "::"), lambda pkt: pkt.atyp == 0x4),
|
|
],
|
|
StrField("addr", "")
|
|
),
|
|
ShortField("port", 80),
|
|
]
|
|
|
|
|
|
_socks5_rep = {
|
|
0: "succeeded",
|
|
1: "general server failure",
|
|
2: "connection not allowed by ruleset",
|
|
3: "network unreachable",
|
|
4: "host unreachable",
|
|
5: "connection refused",
|
|
6: "TTL expired",
|
|
7: "command not supported",
|
|
8: "address type not supported",
|
|
}
|
|
|
|
|
|
class SOCKS5Reply(Packet):
|
|
name = "SOCKS 5 - Reply"
|
|
overload_fields = {SOCKS: {"vn": 0x5}}
|
|
# All fields are the same except the first one
|
|
fields_desc = [
|
|
ByteEnumField("rep", 0x0, _socks5_rep),
|
|
] + SOCKS5Request.fields_desc[1:]
|
|
|
|
|
|
# SOCKS v5 - UDP
|
|
|
|
class SOCKS5UDP(Packet):
|
|
name = "SOCKS 5 - UDP Header"
|
|
fields_desc = [
|
|
ShortField("res", 0),
|
|
ByteField("frag", 0),
|
|
] + SOCKS5Request.fields_desc[2:] # Re-use the atyp, addr and port fields
|
|
|
|
def guess_payload_class(self, s):
|
|
if self.port == 0:
|
|
return conf.raw_layer
|
|
return UDP(sport=self.port, dport=self.port).guess_payload_class(None)
|
|
|
|
|
|
bind_bottom_up(UDP, SOCKS5UDP, sport=1080)
|
|
bind_bottom_up(UDP, SOCKS5UDP, sport=1080)
|
|
bind_layers(UDP, SOCKS5UDP, sport=1080, dport=1080)
|