impacket icon indicating copy to clipboard operation
impacket copied to clipboard

ntlmrelayx.py SOCKS option TypeError with socks5 on relaying

Open yesiamdollar opened this issue 2 years ago • 6 comments
trafficstars

Configuration

impacket version: Impacket v0.10.0 Python version: 3.11 Target OS: Kali Linux

Debug Output With Command String

ps : I've installed the latest release from https://github.com/fortra/impacket/releases/download/impacket_0_10_0/impacket-0.10.0.tar.gz When using socks option from ntlmrelayx.py, there is an error when trying to use crackmapexec or any other tool with proxychains.

pc crackmapexec smb -u 'A.FOOR' -p '' -d DOMAIN 10.13.15.2

I got this error and I can't relay

----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 55444)
Traceback (most recent call last):
  File "/usr/lib/python3.11/socketserver.py", line 691, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.11/socketserver.py", line 361, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 286, in __init__
    socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
  File "/usr/lib/python3.11/socketserver.py", line 755, in __init__
    self.handle()
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 322, in handle
    hostLength = unpack('!B',request['PAYLOAD'][0])[0]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: a bytes-like object is required, not 'int'
----------------------------------------
----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 55460)
Traceback (most recent call last):
  File "/usr/lib/python3.11/socketserver.py", line 691, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.11/socketserver.py", line 361, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 286, in __init__
    socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
  File "/usr/lib/python3.11/socketserver.py", line 755, in __init__
    self.handle()
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 322, in handle
    hostLength = unpack('!B',request['PAYLOAD'][0])[0]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: a bytes-like object is required, not 'int'
----------------------------------------

yesiamdollar avatar Jun 21 '23 15:06 yesiamdollar

I tried with the master branch version and I couldn't reproduce it. Can you provide more context about your configuration? Can you try the master branch version also ? thanks

anadrianmanrique avatar Jul 01 '23 00:07 anadrianmanrique

I have the same issue, here are the details

NTLMRelayx Host

  • Hostname: ATTACKER.test.local
  • IP: 192.168.86.59
  • OS: Kali 6.6.9-1kali1 (2024-01-08)
  • Python version: 3.11.7
  • Impacket version: v0.11.0 installed using pipx
  • Relay Command: ntlmrelayx.py -t ws01.test.local -socks -smb2support -debug

Authentication Sender (WS02.test.local)

  • OS: Windows 10 Pro 22H2
  • Build: 19045.3930
  • Coercion Method: Enter \\192.168.86.59\test into file explorer

Authentication Target (WS01.test.local)

  • OS: Windows 10 Pro 22H2
  • Build: 19045.4170
  1. Relay setup image
  2. Socks connection established image
  3. Validate proxychains config image
  4. Attempt smbclient connection using the socks proxy image image
  5. Attempt secretsdump using the socks proxy image image
  6. Validate a successful relay is possible by specifying the attack directly with ntlmrelayx (no socks) image

gjhami avatar Mar 26 '24 13:03 gjhami

hello @gjhami , did you try socks4 on proxychains ?

yesiamdollar avatar Mar 26 '24 13:03 yesiamdollar

@yesiamdollar sadly that didn't work either, but I think it is the expected behavior since ntlmrelayx states it will create a socks5 listener.

Setup NTLMRelayx Listener image

Receive DA connection image

Attempt to secretsdump using SOCKS proxy connection configured for SOCKS4 image

Output in NTLMRelayx Window image

Note: I also tried specifying the target for secretsdump by IP and just hostname. I also tried specifying the full domain before the username instead of the NETBIOS name. I could never get NTLMRelayx to associate the incoming connection to the SOCKS proxy with the existing relay.

gjhami avatar Apr 04 '24 02:04 gjhami

I tried downgrading to python 3.10, since I realized 3.11 isn't actually supported, via pyenv, but that did not resolve the error. The following changes did. I'm still not sure why others are not experiencing the same error or the cause of the underylying type mismatches.

  1. Selecting an element of the byte string in request['PAYLOAD'] unexpectedly resulted in an int instead of a buffer of 1 byte on line 326 of impacket/exampless/ntlmrelayx/servers/socksserver.py. Skipping the unpacking step since the data was already an int as follows allowed NTLMRelayx to successfully perform this part of parsing the intended target and it no longer threw an exception.
hostLength = request['PAYLOAD'][0]
  1. NTLMRelayx still wasn't working because hostnames of targets passed via proxychains were interpreted as bytestrings instead of strings and therefore didn't match hosts for any existing socks connections, since those were stored as strings. Decoding the hostname parsed from the socks proxy connection on line 327 of impacket/exampless/ntlmrelayx/servers/socksserver.py into a string as follows resolved this issue.
self.targetHost = request['PAYLOAD'][1:hostLength+1].decode()

Successful screenshots:

  • An exception is still generated because 'no such domain exists' that warrants further troubleshooting, but this occurs during the NTDS dump portion of secretsdump which doesn't apply since I was targeting a workstation. The following screenshots show success with python3.10, but the same changes also allowed me to successfully perform secretsdump using a SOCKS connection for authentication with python3.11.

image image image


Failure with python3.10 without the changes described above: image image image

gjhami avatar Apr 04 '24 04:04 gjhami

Update: I just had the same issue in a client environment. If anyone has any advice or is getting the socks proxy feature to work correctly and could share OS/python/impacket versions that would be much appreciated! Thanks in advance!

gjhami avatar Jul 13 '24 16:07 gjhami

Update: I've attached screenshots and pcaps.zip for the loopback and ethernet interfaces that validate the issue persists in Impacktv0.12.0 using Python 3.12.0.

WORKAROUND: While the problem persists I did finally figure out how to make this work until the comprehensive solution described below is merged. If you use the hostname or FQDN of the target instead of the IP then it will result in the error posted here even though the attacker machine successfully resolves the FQDN to an IP as shown in eth0-capture-target-fqdn.pcap due to parsing issues with extracting the target from the command being proxied. However, if you just specify the IP of the target instead of the FQDN in both ntlmrelayx and the proxied command, then everything works as-is! This is still a bug and should be fixed, but at least there's a workaround for now and a solution below with a PR coming :)

Environment Details:

NTLMRelayx Host (ATTACKER.test.local)

  • IP: 192.168.86.59
  • OS: Kali 6.11.2-kali1 (2024-10-15)
  • Python version: 3.12.0
  • Impacket version: v0.12.0 installed using pyenv
  • Relay Commands:
    • FQDN Based: ntlmrelayx.py -t ws01.test.local -socks -smb2support -debug
    • IP Based: ntlmrelayx.py -t 192.168.86.58 -socks -smb2support -debug
  • Secretsdump Commands:

Authentication Sender (WS02.test.local)

  • IP: 192.168.86.60
  • OS: Windows 10 Pro 22H2
  • Build: 19045.5011
  • Coercion Method: Enter ls \\192.168.86.59\test123 into PowerShell

Authentication Target (WS01.test.local)

  • IP: 192.168.86.58
  • OS: Windows 10 Pro 22H2
  • Build: 19045.5011

Domain Controller / DNS Server (DC01.test.local)

  • IP: 192.168.86.57
  • OS: Windows Server 2022 Standard 21H2
  • Build: 20348.2340

FQDN Based Screenshots

Relay image image image

Secretsdump image

IP Based Screenshots

Relay

  • Notice there was a timeout error but this did not affect the success of secretsdump and I think it was just network related. It does not appear when debug mode is disabled.

image image image

Secretsdump image image


Root Cause Analysis & Proposed Solution

The bug is triggered only when targeting by hostname or FQDN because of this codeblock from Lines 321-332 of socksserver.py

        # Let's process the request to extract the target to connect.
        # SOCKS5
        if self.__socksVersion == 5:
            if request['ATYP'] == ATYP.IPv4.value:
                self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4])
                self.targetPort = unpack('>H',request['PAYLOAD'][4:])[0]
            elif request['ATYP'] == ATYP.DOMAINNAME.value:
                hostLength = unpack('!B',request['PAYLOAD'][0])[0]
                self.targetHost = request['PAYLOAD'][1:hostLength+1]
                self.targetPort = unpack('>H',request['PAYLOAD'][hostLength+1:])[0]
            else:
                LOG.error('No support for IPv6 yet!')

Setting up some print statements for debugging showed the following:

request: 050100030f777330312e746573742e6c6f63616c01bd
request["ATYP"]: 3
request["PAYLOAD"]:  b'\x0fws01.test.local\x01\xbd'
request["PAYLOAD"][0]: 15

A little testing showed accessing the value at an index of a bytes object yielded an int, rather than another bytes object. Thanks for the surprise type conversion Python :). image

Removing the type conversion allowed execution to progress, but it subsequently interpreted the target passed by the proxied command as a bytes object instead of a string: image

Decoding the bytes object for target name resolved this issue as well, so my original solution was correct and I was just thrown off by the erroneous error shown in debug mode. I will make a PR for this. Checking git blame, it appears this feature has worked this way since the addition of the socks proxy functionality, so I'm not sure why more people aren't running into this.

A review of the git blame and PyPi history showed the code was introduced when python2.7 was still in use, and I confirmed the code would have been valid in Python2.7. It looks like it just never got updated to work with python3. image

Fixed Code

You could make the fix look nicer by just grabbing the 0th byte of the payload and expecting an int, but I chose to preserve the unpack behavior for the hostname length since byte order remains explicit this way.

        # Let's process the request to extract the target to connect.
        # SOCKS5
        if self.__socksVersion == 5:
            if request['ATYP'] == ATYP.IPv4.value:
                self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4])
                self.targetPort = unpack('>H',request['PAYLOAD'][4:])[0]
            elif request['ATYP'] == ATYP.DOMAINNAME.value:
                hostLength = unpack('!B',request['PAYLOAD'][:1])[0]
                self.targetHost = request['PAYLOAD'][1:hostLength+1].decode(encoding='utf-8')
                self.targetPort = unpack('>H',request['PAYLOAD'][hostLength+1:])[0]
            else:
                LOG.error('No support for IPv6 yet!')

gjhami avatar Oct 31 '24 16:10 gjhami