-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix of the bug with long Oracle passwords and creation of oracle2john.py #5643
base: bleeding-jumbo
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# This software is Copyright (c) 2024, k4amos <k4amos at proton.me> | ||
# and it is hereby released to the general public under the following terms: | ||
# | ||
# Redistribution and use in source and binary forms, with or without | ||
# modification, are permitted. | ||
|
||
# --- | ||
|
||
# Utility to obtain a hash of ORACLE authentication (o5logon) that can be cracked with John | ||
# Usage: ./oracle2john.py -f <pcap files> | ||
# | ||
# This script depends on Scapy (https://scapy.net) | ||
# To install: pip install --user scapy | ||
|
||
import sys | ||
import argparse | ||
import re | ||
|
||
try: | ||
import scapy.all as scapy | ||
except ImportError: | ||
print( | ||
"\033[91m[Error] Scapy seems to be missing, run 'pip install --user scapy' to install it\033[0m" | ||
) | ||
sys.exit(1) | ||
|
||
|
||
def read_file(args, filename): | ||
""" | ||
Reads a PCAP file and extracts relevant Oracle authentication data (o5logon). | ||
""" | ||
auth_data = { | ||
"server_auth_sesskey": None, | ||
"auth_vfr_data": None, | ||
"auth_password": None, | ||
"client_auth_sesskey": None, | ||
} | ||
|
||
packets = scapy.rdpcap(filename) | ||
for packet in packets: | ||
auth_data = process_packet(args, packet, auth_data) | ||
|
||
if auth_data["server_auth_sesskey"] is None or auth_data["auth_vfr_data"] is None: | ||
print( | ||
f"\033[91m[Error] The PCAP file {filename} does not contain the required fields of o5logon authentification\033[0m" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should also go to stderr to avoid having this in the hash output file when redirection is used. I would also consider this being a warning (or omit it altogether) because it might be a valid use case to throw this script on a bunch of pcap files containing arbitrary network traffic. |
||
) | ||
elif None not in list(auth_data.values()): | ||
# Format of the hash : $o5logon$ <server's AUTH_SESSKEY> * <AUTH_VFR_DATA> * <AUTH_PASSWORD> * <client's AUTH_SESSKEY> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not familiar with the o5logon protocol and don't know why the client values can be missing. Would it make sense to add a comment explaining the two cases? |
||
print( | ||
f'$o5logon${auth_data["server_auth_sesskey"]}*{auth_data["auth_vfr_data"]}*{auth_data["auth_password"]}*{auth_data["client_auth_sesskey"]}' | ||
) | ||
|
||
else: | ||
# Format of the hash : $o5logon$ <server's AUTH_SESSKEY> * <AUTH_VFR_DATA> | ||
print( | ||
f'$o5logon${auth_data["server_auth_sesskey"]}*{auth_data["auth_vfr_data"]}' | ||
) | ||
|
||
|
||
def select_hexa(raw_string): | ||
""" | ||
Extracts the first valid hexadecimal string from the raw data. | ||
""" | ||
match_hexa = re.search( | ||
"([A-Fa-f0-9]+)", raw_string.decode("ascii", errors="ignore").replace(" ", "") | ||
) | ||
if match_hexa: | ||
return match_hexa.group(1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explicitly |
||
|
||
|
||
def process_packet(args, packet, auth_data): | ||
""" | ||
Processes a packet and updates the auth_data dictionary with the extracted values. | ||
""" | ||
raw_data = bytes(packet) | ||
|
||
pbkdf2_match = re.search(rb"PBKDF2", raw_data) | ||
if pbkdf2_match: | ||
print( | ||
"\033[91m[Error] Sorry, PBKDF2 is not (yet) supported by this utility (only o5logon is)\033[0m" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stderr again. But I'm wondering, it is that fatal that the program should exit? What if another connection is contained within the pcap? And the script seems to support multiple files as well. If the first cannot be processed, the latter will never be tried? |
||
) | ||
sys.exit(1) | ||
|
||
server_auth_sesskey_match = re.search( | ||
rb"AUTH_SESSKEY([\s\S]+?)AUTH_VFR_DATA", raw_data | ||
) | ||
if server_auth_sesskey_match: | ||
auth_data["server_auth_sesskey"] = select_hexa( | ||
server_auth_sesskey_match.group(1) | ||
) | ||
|
||
auth_vfr_data_match = re.search( | ||
rb"AUTH_VFR_DATA([\s\S]+?)(AUTH_GLOBALLY_UNIQUE_DBID|$)", raw_data | ||
) | ||
if auth_vfr_data_match: | ||
auth_data["auth_vfr_data"] = select_hexa(auth_vfr_data_match.group(1)) | ||
|
||
auth_password_match = re.search(rb"AUTH_PASSWORD([\s\S]+?)AUTH_RTT", raw_data) | ||
if auth_password_match: | ||
auth_data["auth_password"] = select_hexa(auth_password_match.group(1)) | ||
|
||
client_auth_sesskey_match = re.search( | ||
rb"AUTH_SESSKEY([\s\S]+?)AUTH_PASSWORD", raw_data | ||
) | ||
if client_auth_sesskey_match: | ||
auth_data["client_auth_sesskey"] = select_hexa( | ||
client_auth_sesskey_match.group(1) | ||
) | ||
|
||
return auth_data | ||
|
||
|
||
if __name__ == "__main__": | ||
|
||
parser = argparse.ArgumentParser( | ||
formatter_class=argparse.RawDescriptionHelpFormatter, | ||
epilog=""" | ||
### Utility to obtain a hash of ORACLE authentication (o5logon) that can be cracked with John | ||
Written by k4amos | ||
|
||
Usage: ./oracle2john.py -f <pcap files> | ||
""", | ||
) | ||
|
||
parser.add_argument("-f", "--file", type=str, required=True, nargs="+") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since files are a mandatory and the only parameter the script expects, I would personally omit the flag-style option and just make it a positional argument instead. Most other *2john scripts also do it this way (but not all), so this is a matter of preferences. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, the
Which do not? We should open an issue for that! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to my quick search, at least There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be terrific 👍 |
||
|
||
parsed_args = parser.parse_args() | ||
args = vars(parsed_args) | ||
|
||
for filename in args["file"]: | ||
auth_data = read_file(args, filename) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,7 @@ static struct fmt_tests o5logon_tests[] = { | |
{"$o5logon$A10D52C1A432B61834F4B0D9592F55BD0DA2B440AEEE1858515A646683240D24A61F0C9366C63E93D629292B7891F44A*878C0B92D61A594F2680", "m3ow00"}, | ||
{"$o5logon$52696131746C356643796B6D716F46474444787745543263764B725A6D756A69E46DE32AFBB33E385C6D9C7031F4F2B9*3131316D557239736A65", "123456"}, | ||
{"$o5logon$4336396C304B684638634450576B30397867704F54766D71494F676F5A5A386F09F4A10B5908B3ED5B1D6878A6C78751*573167557661774E7271", ""}, | ||
{"$o5logon$4D04DBD23D103F05D9B57EB6EC14D83A0A468AB906EAC907D3A8C796573E5F34BC15F0ECBC9EAC0350A38A663A368233*442192E518F6F43D7CF7*D3963B6AAED39C231BD5C92A10C0F146CA4784D1503A9B97598B31D33406390B7CA4F8B3EE5406A54C1842E4E63D1220*192AB7C9BA21C883824CF3D5BA073AC1129FD841E0AF6DF522C7EBDC52783CB8B97B792BFB6D9D743C7F4376FF0E7F93", "password1234567890"}, | ||
{NULL} | ||
}; | ||
|
||
|
@@ -131,7 +132,7 @@ static int valid(char *ciphertext, struct fmt_main *self) | |
if ((p = strtokm(NULL, "*"))) { /* client's encrypted password */ | ||
int len = hexlenu(p, &extra); | ||
|
||
if (extra || len < 64 || len % 32 || len > 2 * PLAINTEXT_LENGTH + 16) | ||
if (extra || len < 64 || len % 32 || len > 3 * PLAINTEXT_LENGTH) | ||
Comment on lines
-134
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't pay attention at first but I now realized I wrote the original line, lol. I'm pretty sure the
diff --git a/src/o5logon_fmt_plug.c b/src/o5logon_fmt_plug.c
index 498f2d6b1..b5af3e887 100644
--- a/src/o5logon_fmt_plug.c
+++ b/src/o5logon_fmt_plug.c
@@ -83,7 +83,7 @@ static struct custom_salt {
unsigned char salt[SALT_LENGTH]; /* AUTH_VFR_DATA */
unsigned char ct[CIPHERTEXT_LENGTH]; /* Server's AUTH_SESSKEY */
unsigned char csk[CIPHERTEXT_LENGTH]; /* Client's AUTH_SESSKEY */
- unsigned char pw[16 + PLAINTEXT_LENGTH]; /* Client's AUTH_PASSWORD */
+ unsigned char pw[2 * (PLAINTEXT_LENGTH + 16)]; /* Client's AUTH_PASSWORD */
int pw_len; /* AUTH_PASSWORD length (blocks) */
} *cur_salt;
EDIT: Scrapped the last comment, that part is correct in current code (sizes are after hex decode). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We could make it |
||
goto err; | ||
if ((p = strtokm(NULL, "*")) == NULL) /* client's sesskey */ | ||
goto err; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go to stderr, i.e. add the parameter
file=sys.stderr
toprint
.