282 lines
8.8 KiB
Raku
282 lines
8.8 KiB
Raku
#!/usr/bin/perl -w
|
|
#
|
|
# Original code believed to be "(c) x7d8 sap loverz, public domain" (as noted in
|
|
# sapB_fmt_plug.c). Also Copyright (c) 2011, 2012 magnum, and hereby released to
|
|
# the general public under the following terms: Redistribution and use in
|
|
# source and binary forms, with or without modification, are permitted.
|
|
#
|
|
# This perl script converts password hashes downloaded from SAP systems
|
|
# into a format suitable for John the Ripper (written to stdout).
|
|
#
|
|
# Usage: ./sap2john.pl <input-file> [A|B|D|E|F|H]
|
|
#
|
|
# To read from stdin instead, use: ./sap2john.pl - [A|B|D|E|F|H]
|
|
#
|
|
# If you omit the optional parameter, the script generates output
|
|
# for all codvn F and codvn H hashes, as well as for all the
|
|
# CODVN B hashes (including hashes where the script assumes CODVN B
|
|
# because the CODVN column is missing or empty).
|
|
# That means, the default implementation will mix up to 3 different
|
|
# hash formats into the output file, but since these formats
|
|
# are not ambiguous, that is not a problem.
|
|
# CODVN A, D, and E hashes will be skipped, because these hashes
|
|
# are currently not supported, and because these hashes would
|
|
# be considered as valid CODVN B hashes by the current sapB_fmt_plug.c.
|
|
# (And, by the way, CODVN A and CODVN D are obsolete.)
|
|
#
|
|
# By specifying the optional parameter, you can decide which
|
|
# SAP hash format will be written to stdout instead of the default
|
|
# hash formats.
|
|
#
|
|
# To generate a suitable input file for this script, download
|
|
# the SAP hashes from one of the database tables USR02, USH02,
|
|
# or USRPWDHISTORY.
|
|
# Download the data as a spreadsheet using SAP transaction code SE16.
|
|
# Make sure to check the user settings: pick field names instead of
|
|
# field descriptions as column headings.
|
|
# If the SAP user names (which work as salts for CODVN A, B, D, E, F)
|
|
# contain non-ascii characters, please download the data using a
|
|
# single byte code page if you want to crack CODVN A or CODVN B hashes.
|
|
# Download the data using utf-8 (SAP code page 4110), if you want
|
|
# to crack CODVN F, CODVN E, or CODVN D hashes (or if you want to crack
|
|
# CODVN H hashes. (For CODVN H, the user name ist't used as a salt
|
|
# anymore.)
|
|
#
|
|
# CODVN G just means, the system computes and stores CODVN B and
|
|
# CODVN F hashes.
|
|
# (In this case, the script will create two lines of output.)
|
|
# CODVN I means, the system computes and stores CODVN B, CODVN F, and
|
|
# CODVN H hashes.
|
|
# (In this case, the script will create three lines of output.)
|
|
#
|
|
# If CODVN is empty (or the column is missing), but BCODE is filled,
|
|
# the script assumes that the corresponding hash is a CODVN B hash,
|
|
# but for very old USH02 records (created before around 1996)
|
|
# probably CODVN was in use.
|
|
#
|
|
# Please note that currently John the Ripper (jumbo) only supports
|
|
# SAP CODVN B (--format=sapb) and CODVN F (--format=sapg) hashes.
|
|
#
|
|
# FIXME: should the script generate different lines of output
|
|
# for the current password (e.g. uid=0, gid=$mandt[$i])
|
|
# and for older passwords (ocod1-ocod5, or USH02
|
|
# (column MODDA or MODTI exists) or USRPWDHISTORY
|
|
# (column TIMESTAMP exists)
|
|
#
|
|
sub fill_field
|
|
{
|
|
if ($_[0] == -1 || $_[0] > $#tmp) {
|
|
$_[1] = "";
|
|
}
|
|
else {
|
|
$_[1] = $tmp[$_[0]];
|
|
$_[1] =~ s/\s*$//;
|
|
}
|
|
}
|
|
|
|
sub write_pwdsaltedhash
|
|
{
|
|
if ($hashtypes =~ /H/ && $pwdsaltedhash[$i] ne "") {
|
|
print "$bname[$i]:$pwdsaltedhash[$i]\n";
|
|
}
|
|
}
|
|
|
|
sub write_passcode
|
|
{
|
|
# FIXME: prefix hash with "sapF$", to avoid ambiguity with other hash formats?
|
|
if ($hashtypes =~ /F/ && $passcode[$i] ne "0000000000000000000000000000000000000000") {
|
|
print "$bname[$i]:$bname[$i]\$$passcode[$i]\n";
|
|
}
|
|
}
|
|
|
|
sub write_bcode
|
|
{
|
|
# FIXME: prefix CODVN A/D/E(/B) hashes with "sap<CODVN>$"?
|
|
$vn = $_[1];
|
|
$bc = $_[0];
|
|
if ($vn eq "" || $vn eq "G" or $vn eq "I") { $vn = "B" }
|
|
if ($hashtypes =~ /$vn/) {
|
|
if ($bc ne "" && $bc ne "0000000000000000") {
|
|
print "$bname[$i]:$bname[$i]\$$bc\n";
|
|
}
|
|
}
|
|
}
|
|
if ($#ARGV < 0 || $#ARGV > 1) {
|
|
die "Usage: $0 <input-file> [A|B|D|E|F|H]\n";
|
|
}
|
|
|
|
open INPUT_FILE, "$ARGV[0]" or die "Can't open input-file ($ARGV[0])\n";
|
|
|
|
if ($#ARGV == 1) {
|
|
$hashtypes = $ARGV[1];
|
|
if ($hashtypes =~ /^[^ABDEFH]$/) {
|
|
die "invalid optional parameter: \"$hashtypes\"\n";
|
|
}
|
|
}
|
|
else {
|
|
$hashtypes = "";
|
|
}
|
|
|
|
$line = "";
|
|
$count = 0;
|
|
|
|
# USR01, USH02, USRPWDHISTORY
|
|
#$pos_mandt = -1;
|
|
$pos_bname = -1;
|
|
$pos_bcode = -1;
|
|
$pos_passcode = -1;
|
|
$pos_pwdsaltedhash = -1;
|
|
|
|
# USR02
|
|
$pos_codvn = -1;
|
|
$pos_ocod1 = -1;
|
|
$pos_codv1 = -1;
|
|
$pos_ocod2 = -1;
|
|
$pos_codv2 = -1;
|
|
$pos_ocod3 = -1;
|
|
$pos_codv3 = -1;
|
|
$pos_ocod4 = -1;
|
|
$pos_codv4 = -1;
|
|
$pos_ocod5 = -1;
|
|
$pos_codv5 = -1;
|
|
|
|
# USH02
|
|
#$pos_modda = -1;
|
|
#$pos_modti = -1;
|
|
|
|
# USRPWDHISTORY
|
|
#$pos_timestamp = -1;
|
|
|
|
until ($line =~ /\t/) {
|
|
$line=<INPUT_FILE>;
|
|
$count++;
|
|
}
|
|
|
|
chomp($line);
|
|
$line =~ s/\r//;
|
|
|
|
# column names can be either left-justified or right-justified,
|
|
# so let's remove spaces as well:
|
|
@tmp = split(/\s*\t\s*/, $line);
|
|
|
|
$columns = $#tmp;
|
|
|
|
for($i = 0; $i <= $columns; $i++) {
|
|
if ($tmp[$i] =~ /BNAME/) { $pos_bname = $i }
|
|
# elsif ($tmp[$i] =~ /MANDT/) { $pos_mandt = $i }
|
|
elsif ($tmp[$i] =~ /BCODE/) { $pos_bcode = $i }
|
|
elsif ($tmp[$i] =~ /CODVN/) { $pos_codvn = $i }
|
|
elsif ($tmp[$i] =~ /PASSCODE/) { $pos_passcode = $i }
|
|
elsif ($tmp[$i] =~ /PWDSALTEDHASH/) { $pos_pwdsaltedhash = $i }
|
|
elsif ($tmp[$i] =~ /OCOD1/) { $pos_ocod1 = $i }
|
|
elsif ($tmp[$i] =~ /CODV1/) { $pos_codv1 = $i }
|
|
elsif ($tmp[$i] =~ /OCOD2/) { $pos_ocod2 = $i }
|
|
elsif ($tmp[$i] =~ /CODV2/) { $pos_codv2 = $i }
|
|
elsif ($tmp[$i] =~ /OCOD3/) { $pos_ocod3 = $i }
|
|
elsif ($tmp[$i] =~ /CODV3/) { $pos_codv3 = $i }
|
|
elsif ($tmp[$i] =~ /OCOD4/) { $pos_ocod4 = $i }
|
|
elsif ($tmp[$i] =~ /CODV4/) { $pos_codv4 = $i }
|
|
elsif ($tmp[$i] =~ /OCOD5/) { $pos_ocod5 = $i }
|
|
elsif ($tmp[$i] =~ /CODV5/) { $pos_codv5 = $i }
|
|
# elsif ($tmp[$i] =~ /MODDA/) { $pos_modda = $i }
|
|
# elsif ($tmp[$i] =~ /MODTI/) { $pos_modti = $i }
|
|
# elsif ($tmp[$i] =~ /TIMESTAMP/) { $pos_timestamp = $i }
|
|
}
|
|
|
|
if (-1 == $pos_bcode &&
|
|
-1 == $pos_ocod1 && -1 == $pos_ocod2 && -1 == $pos_ocod3 &&
|
|
-1 == $pos_ocod4 && -1 == $pos_ocod5) {
|
|
if (-1 == $pos_passcode && -1 == $pos_pwdsaltedhash) {
|
|
die "no password hash columns found\n";
|
|
}
|
|
elsif ($hashtypes eq "B" || $hashtypes eq "E" ||
|
|
$hashtypes eq "D" || $hashtypes eq "A") {
|
|
die "CODVN B, E, D, or A requested, but column BCODE/OCODV[1-5] not found\n";
|
|
}
|
|
}
|
|
|
|
if (-1 == $pos_bname && -1 == $pos_pwdsaltedhash) {
|
|
die "no BNAME column found, but required as salt for BCOCE and PASSCODE\n";
|
|
}
|
|
|
|
if (-1 == $pos_passcode && $hashtypes =~ /F/) {
|
|
die "CODVN F requested, but column PASSCODE not found\n";
|
|
}
|
|
|
|
if (-1 == $pos_pwdsaltedhash && $hashtypes =~ /H/) {
|
|
die "CODVN H requested, but column PWDSALTEDHASH not found\n";
|
|
}
|
|
|
|
if ($hashtypes eq "") {
|
|
# FIXME: Should I use ABDEFH as a default, and prefix
|
|
# the hashes with sapA$, sapD$, sapD$ for codvn A/D/E?
|
|
# OTOH, if user names contain non-ascii characters,
|
|
# the JtR user might want to split formats requiring utf-8 input
|
|
# and formats requiring iso-8859* input into different files
|
|
# anyway.
|
|
$hashtypes = "BFH";
|
|
}
|
|
if (-1 == $pos_bname) { $hashtypes =~ s/[ABDEF]//g }
|
|
if (-1 == $pos_pwdsaltedhash) { $hashtypes =~ s/H// }
|
|
|
|
if ($hashtypes eq "") {
|
|
die "not all required columns for requested hash types found\n";
|
|
}
|
|
|
|
$rows = -1;
|
|
while ($line = <INPUT_FILE>) {
|
|
$count++;
|
|
chomp($line);
|
|
$line =~ s/\r//;
|
|
|
|
@tmp = split(/\t/, $line);
|
|
if ($#tmp >= 0) {
|
|
$rows++;
|
|
# fill_field( $pos_mandt, $mandt[$rows]);
|
|
fill_field( $pos_bname, $bname[$rows]);
|
|
fill_field( $pos_codvn, $codvn[$rows]);
|
|
fill_field( $pos_bcode, $bcode[$rows]);
|
|
fill_field( $pos_passcode, $passcode[$rows]);
|
|
fill_field( $pos_pwdsaltedhash, $pwdsaltedhash[$rows]);
|
|
fill_field( $pos_ocod1, $ocod1[$rows]);
|
|
fill_field( $pos_codv1, $codv1[$rows]);
|
|
fill_field( $pos_ocod2, $ocod2[$rows]);
|
|
fill_field( $pos_codv2, $codv2[$rows]);
|
|
fill_field( $pos_ocod3, $ocod3[$rows]);
|
|
fill_field( $pos_codv3, $codv3[$rows]);
|
|
fill_field( $pos_ocod4, $ocod4[$rows]);
|
|
fill_field( $pos_codv4, $codv4[$rows]);
|
|
fill_field( $pos_ocod5, $ocod5[$rows]);
|
|
fill_field( $pos_codv5, $codv5[$rows]);
|
|
# fill_field( $pos_modda, $modda[$rows]);
|
|
# fill_field( $pos_modti, $modti[$rows]);
|
|
# fill_field( $pos_timestamp, $timestamp[$rows]);
|
|
}
|
|
}
|
|
|
|
# Should the script count the number of valid hashes found/written to stdout,
|
|
# to write summary information to stderr?
|
|
#
|
|
#$codvn_a = 0;
|
|
#$codvn_b = 0;
|
|
#$codvn_d = 0;
|
|
#$codvn_e = 0;
|
|
#$codvn_f = 0;
|
|
#$codvn_h = 0;
|
|
|
|
for ($i=0; $i<=$rows; $i++) {
|
|
# write BCODE first, so that hopefully codvn B (the easiest to crack
|
|
# hash algorithm) will be detected first...
|
|
write_bcode( $bcode[$i], $codvn[$i] );
|
|
write_bcode( $ocod1[$i], $codv1[$i] );
|
|
write_bcode( $ocod2[$i], $codv2[$i] );
|
|
write_bcode( $ocod3[$i], $codv3[$i] );
|
|
write_bcode( $ocod4[$i], $codv4[$i] );
|
|
write_bcode( $ocod5[$i], $codv5[$i] );
|
|
|
|
write_passcode( );
|
|
# even if this format is currently not supported by JtR,
|
|
# it might be in future:
|
|
write_pwdsaltedhash( );
|
|
}
|