--- /dev/null
+// Extracts NTMLSSP challenge/authentication pairs from a pcap file,
+// into a format crackable by the “jumbo” edition of John the Ripper.
+// Heavily inspired by https://github.com/psychomario/ntlmsspparse/blob/master/ntlmssp.py,
+// but infinitely faster.
+//
+// The struct definitions were lifted from ChromeOS, who probably
+// lifted them from some specification. They have been adapted a bit.
+
+#include <string.h>
+#include <pcap.h>
+#include <stdlib.h>
+#include <netinet/ip.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <map>
+#include <string>
+#include <algorithm>
+
+using namespace std;
+
+enum ntlmssp_message_type
+{
+ NTLMSSP_NEGOTIATE = 1,
+ NTLMSSP_CHALLENGE = 2,
+ NTLMSSP_AUTH = 3,
+ NTLMSSP_UNKNOWN = 4,
+};
+
+#define NTLMSSP_SIGNATURE "NTLMSSP"
+#define CIFS_CRYPTO_KEY_SIZE (8)
+
+typedef struct _SECURITY_BUFFER {
+ uint16_t Length;
+ uint16_t MaximumLength;
+ uint32_t BufferOffset; /* offset to buffer */
+} __attribute__((packed)) SECURITY_BUFFER;
+
+struct _NTLM_MESSAGE {
+ uint8_t Signature[sizeof(NTLMSSP_SIGNATURE)];
+ uint32_t MessageType;
+};
+
+typedef struct _NEGOTIATE_MESSAGE {
+ uint8_t Signature[sizeof(NTLMSSP_SIGNATURE)];
+ uint32_t MessageType; /* NtLmNegotiate = 1 */
+ uint32_t NegotiateFlags;
+ SECURITY_BUFFER DomainName; /* RFC 1001 style and ASCII */
+ SECURITY_BUFFER WorkstationName; /* RFC 1001 and ASCII */
+ /* SECURITY_BUFFER for version info not present since we
+ do not set the version is present flag */
+ char DomainString[0];
+ /* followed by WorkstationString */
+} __attribute__((packed)) NEGOTIATE_MESSAGE, *PNEGOTIATE_MESSAGE;
+
+typedef struct _CHALLENGE_MESSAGE {
+ uint8_t Signature[sizeof(NTLMSSP_SIGNATURE)];
+ uint32_t MessageType; /* NtLmChallenge = 2 */
+ SECURITY_BUFFER TargetName;
+ uint32_t NegotiateFlags;
+ uint8_t Challenge[CIFS_CRYPTO_KEY_SIZE];
+ uint8_t Reserved[8];
+ SECURITY_BUFFER TargetInfoArray;
+ /* SECURITY_BUFFER for version info not present since we
+ do not set the version is present flag */
+} __attribute__((packed)) CHALLENGE_MESSAGE, *PCHALLENGE_MESSAGE;
+
+typedef struct _AUTHENTICATE_MESSAGE {
+ uint8_t Signature[sizeof(NTLMSSP_SIGNATURE)];
+ uint32_t MessageType; /* NtLmsAuthenticate = 3 */
+ SECURITY_BUFFER LmChallengeResponse;
+ SECURITY_BUFFER NtChallengeResponse;
+ SECURITY_BUFFER DomainName;
+ SECURITY_BUFFER UserName;
+ SECURITY_BUFFER WorkstationName;
+ SECURITY_BUFFER SessionKey;
+ uint32_t NegotiateFlags;
+ /* SECURITY_BUFFER for version info not present since we
+ do not set the version is present flag */
+ char UserString[0];
+} __attribute__((packed)) AUTHENTICATE_MESSAGE, *PAUTHENTICATE_MESSAGE;
+
+#define NTLMSSP_FEATURE_SESSION_KEY 0x00000001
+#define NTLMSSP_FEATURE_SIGN 0x00000002
+#define NTLMSSP_FEATURE_SEAL 0x00000004
+#define NTLMSSP_FEATURE_CCACHE 0x00000008
+
+struct quintuple {
+ uint32_t saddr, daddr;
+ uint16_t sport, dport;
+
+ bool operator< (const quintuple &o) const {
+ if (saddr != o.saddr)
+ return saddr < o.saddr;
+ if (daddr != o.daddr)
+ return daddr < o.daddr;
+ if (sport != o.sport)
+ return sport < o.sport;
+ return dport < o.dport;
+ }
+};
+
+map<quintuple, string> prev_packets;
+
+string to_ascii(const SECURITY_BUFFER &buf, const void *base) {
+ const char *str = (char *)base + buf.BufferOffset;
+
+ string ret;
+ for (int i = 0; i < buf.Length; ++i) {
+ if (str[i] == 0) {
+ // poor man's UTF-16 conversion :-)
+ continue;
+ }
+ ret.push_back(str[i]);
+ }
+ return ret;
+}
+
+string to_hex(const SECURITY_BUFFER &buf, const void *base) {
+ const uint8_t *str = (uint8_t *)base + buf.BufferOffset;
+
+ string ret;
+ char tmp[256];
+ for (int i = 0; i < buf.Length; ++i) {
+ sprintf(tmp, "%02x", str[i]);
+ ret += tmp;
+ }
+ return ret;
+}
+
+string to_hex(const uint8_t *start, const uint8_t *end) {
+ string ret;
+ char tmp[256];
+ for (const uint8_t *ptr = start; ptr != end; ++ptr) {
+ sprintf(tmp, "%02x", *ptr);
+ ret += tmp;
+ }
+ return ret;
+}
+
+void my_callback(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
+{
+ int len = h->caplen;
+ if (len < 40) {
+ //printf("skipped short packet\n");
+ return;
+ }
+ if (bytes[12] != 0x08 || bytes[13] != 0x00) {
+ //printf("skipped non-IPv4 packet\n");
+ return;
+ }
+ const struct iphdr *ip = (const struct iphdr *)(bytes + 14);
+ const struct tcphdr *tcp = (const struct tcphdr *)(bytes + 14 + sizeof(*ip));
+ const u_char *data = bytes + 14 + sizeof(*ip) + sizeof(*tcp);
+ if (data > bytes + len) {
+ return;
+ }
+ const u_char *ntlmssp = (const u_char *)memmem(data, (bytes + len) - data, "NTLMSSP", 7);
+ if (ntlmssp == NULL) {
+ return;
+ }
+
+ quintuple invq;
+ invq.saddr = ip->daddr;
+ invq.daddr = ip->saddr;
+ invq.sport = tcp->dest;
+ invq.dport = tcp->source;
+
+ auto it = prev_packets.find(invq);
+ if (it == prev_packets.end()) {
+ const _CHALLENGE_MESSAGE *challenge = reinterpret_cast<const _CHALLENGE_MESSAGE *>(ntlmssp);
+ if (challenge->MessageType != NTLMSSP_CHALLENGE || ntlmssp + sizeof(_CHALLENGE_MESSAGE) > bytes + len) {
+ return;
+ }
+ quintuple q;
+ q.saddr = ip->saddr;
+ q.daddr = ip->daddr;
+ q.sport = tcp->source;
+ q.dport = tcp->dest;
+ prev_packets.insert(make_pair(q, string(ntlmssp, bytes + len)));
+ return;
+ }
+
+ const _CHALLENGE_MESSAGE *challenge = reinterpret_cast<const _CHALLENGE_MESSAGE *>(it->second.data());
+ const _AUTHENTICATE_MESSAGE *auth = reinterpret_cast<const _AUTHENTICATE_MESSAGE *>(ntlmssp);
+
+ if (auth->MessageType != NTLMSSP_AUTH || ntlmssp + sizeof(_AUTHENTICATE_MESSAGE) > bytes + len) {
+ return;
+ }
+
+ printf("%s::%s:%s:%s:%s\n",
+ to_ascii(auth->UserName, auth).c_str(),
+ to_ascii(auth->DomainName, auth).c_str(),
+ to_hex(auth->LmChallengeResponse, auth).c_str(),
+ to_hex(auth->NtChallengeResponse, auth).c_str(),
+ to_hex(challenge->Challenge, challenge->Challenge + CIFS_CRYPTO_KEY_SIZE).c_str());
+
+ prev_packets.erase(it);
+}
+
+int main(int argc, char **argv)
+{
+ pcap_t *pcap = pcap_open_offline(argv[1], NULL);
+ pcap_activate(pcap);
+ pcap_loop(pcap, -1, my_callback, NULL);
+ return 0;
+}
+