#!/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 [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$"? $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 [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=; $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 = ) { $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( ); }