Last active
September 27, 2020 11:54
-
-
Save svagionitis/ef9519c1938ef1e60300788ded41a412 to your computer and use it in GitHub Desktop.
SSHA encoding and decoding (bruteforce)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
#include <stdint.h> | |
#include <ctype.h> | |
#include <openssl/rand.h> | |
#include <openssl/sha.h> | |
#define NUM_SALT_BYTES 8 | |
#define SHA1_LENGTH 20 | |
static const unsigned char indexBase64Tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
/* http://en.wikipedia.org/wiki/ASCII#cite_note-48 */ | |
static const char printableAlphabet[] = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; | |
/** | |
* \brief Compute the string length | |
* See [libowfat](http://www.fefe.de/libowfat/) for more info. | |
* | |
* \param in The string to compute length | |
* \return the length of string | |
*/ | |
static size_t | |
str_len (const char *in) | |
{ | |
const char *t = in; | |
for (;;) { | |
if (!*t) break; ++t; | |
if (!*t) break; ++t; | |
if (!*t) break; ++t; | |
if (!*t) break; ++t; | |
} | |
return (size_t)(t - in); | |
} | |
/** | |
* \brief Compare two strings and check if they are equal. | |
* When the strings are different, str_diff does not read bytes past the | |
* first difference. See [libowfat](http://www.fefe.de/libowfat/) for more info. | |
* | |
* \param a The first string. | |
* \param b The second string. | |
* \return negative, 0, or positive, depending on whether the | |
* string a[0], a[1], ..., a[n]=='\0' is lexicographically smaller than, | |
* equal to, or greater than the string b[0], b[1], ..., b[m-1]=='\0'. | |
*/ | |
static ssize_t | |
str_diff (const char *a, const char *b) | |
{ | |
const unsigned char *s = (const unsigned char *) a; | |
const unsigned char *t = (const unsigned char *) b; | |
ssize_t j = 0; | |
for (;;) { | |
if ((j = (*s - *t))) break; if (!*t) break; ++s; ++t; | |
if ((j = (*s - *t))) break; if (!*t) break; ++s; ++t; | |
if ((j = (*s - *t))) break; if (!*t) break; ++s; ++t; | |
if ((j = (*s - *t))) break; if (!*t) break; ++s; ++t; | |
} | |
return j; | |
} | |
/** | |
* \brief find character in ASCIIZ string | |
* See [libowfat](http://www.fefe.de/libowfat/) for more info. | |
* | |
* \param in The string to search for. | |
* \param needle The character to search in the string | |
* \return the index of the first occurrance of needle or \0 in string | |
*/ | |
static ssize_t | |
str_chr (const char *in, char needle) | |
{ | |
const char *t = in; | |
const char c = needle; | |
for (;;) { | |
if (!*t || *t == c) break; ++t; | |
if (!*t || *t == c) break; ++t; | |
if (!*t || *t == c) break; ++t; | |
if (!*t || *t == c) break; ++t; | |
} | |
return (ssize_t)(t - in); | |
} | |
static int | |
nibble_to_char (int v) | |
{ | |
return (v >= 10 ? 'a' + v - 10 : '0' + v); | |
} | |
int | |
hexify_buffer (const unsigned char *input, | |
size_t input_sz, | |
char *buffer, | |
size_t buffer_sz) | |
{ | |
size_t i; | |
if (!input || !input_sz || (input_sz >= (SIZE_MAX / 2))) | |
/* catch overflow (wrap-around) of output_sz */ | |
return -1; | |
if (!buffer || (buffer_sz < (2 * input_sz + 1))) { | |
fprintf (stderr, "%s: output size %zu needs to match input\n", | |
__func__, input_sz); | |
return -1; | |
} | |
for (i = 0; i < input_sz; i++) { | |
buffer[2*i] = (char) nibble_to_char (input[i] >> 4); | |
buffer[2*i + 1] = (char) nibble_to_char (input[i] & 0x0f); | |
} | |
buffer[2 * i] = '\0'; | |
return 0; | |
} | |
char * | |
hexify (const unsigned char *input, | |
size_t input_sz) | |
{ | |
size_t output_sz; | |
char *output; | |
output_sz = 2 * input_sz + 1; | |
output = malloc (output_sz); | |
if (hexify_buffer (input, input_sz, output, output_sz)) { | |
free (output); | |
output = NULL; | |
} | |
return output; | |
} | |
static int | |
nibble_from_char (int c) | |
{ | |
if (c >= '0' && c <= '9') | |
return (c - '0'); | |
if (c >= 'a' && c <= 'f') | |
return (c - 'a' + 10); | |
if (c >= 'A' && c <= 'F') | |
return (c - 'A' + 10); | |
return -1; | |
} | |
int | |
unhexify_buffer (const char *input, | |
size_t input_sz, | |
unsigned char *buffer, | |
size_t *buffer_sz) | |
{ | |
size_t i; | |
if (!buffer_sz) { | |
fprintf (stderr, "%s: no output size?!\n", __func__); | |
return -1; | |
} | |
if (!input || !input_sz || (input_sz % 2 != 0)) { | |
fprintf (stderr, "%s: input size %zu needs to be even\n", | |
__func__, input_sz); | |
*buffer_sz = 0; | |
return -1; | |
} | |
if (!buffer || (*buffer_sz < input_sz / 2)) { | |
fprintf (stderr, "%s: output size %zu needs to match input\n", | |
__func__, input_sz); | |
*buffer_sz = 0; | |
return -1; | |
} | |
for (i = 0; i < input_sz; i += 2) { | |
int n1, n2; | |
n1 = nibble_from_char (input[i]); | |
if (n1 < 0 || n1 > 0xf) | |
goto out; | |
n2 = nibble_from_char (input[i+1]); | |
if (n2 < 0 || n2 > 0xf) | |
goto out; | |
buffer[i/2] = (unsigned char) ((n1 << 4) | n2); | |
} | |
*buffer_sz = i / 2; | |
return 0; | |
out: | |
fprintf (stderr, "%s: input is invalid @ %zu: 0x%.2x%.2x (%c%c)\n", | |
__func__, i, input[i], input[i+1], input[i], input[i+1]); | |
*buffer_sz = 0; | |
return -1; | |
} | |
unsigned char * | |
unhexify (const char *input, size_t input_sz) | |
{ | |
size_t output_sz; | |
unsigned char *output; | |
output_sz = input_sz / 2; | |
output = malloc (output_sz); | |
if (unhexify_buffer (input, input_sz, output, &output_sz)) { | |
free (output); | |
output = NULL; | |
} | |
return output; | |
} | |
char * | |
base64_enc (const unsigned char *input, size_t input_sz, size_t *output_sz) | |
{ | |
char *output; | |
size_t i, j, out_sz; | |
if (!input || !input_sz) | |
return NULL; | |
out_sz = (input_sz * 4) / 3; | |
if (output_sz) | |
*output_sz = out_sz; | |
output = calloc (out_sz + 1, sizeof (char)); | |
if (!output) | |
return NULL; | |
output[out_sz] = '\0'; | |
for (i = 0, j = 0;i < input_sz;i += 3, j += 4) { | |
unsigned int threeOctets = 0x0U; | |
unsigned int remainLength = input_sz - i; | |
unsigned char encChars[4]; | |
/* | |
Put the three characters in the unsigned int buffer. If the three | |
characters are "Man" then we will see the following in the buffer: | |
String: M a n | |
Decimal: 77 97 110 | |
Hexadecimal: 0x4D 0x61 0x6E | |
Binary: 01001101 01100001 01101110 | |
So the buffer will have the value 0x004D616E (in decimal 5,071,214). | |
*/ | |
threeOctets |= input[i] << 16; | |
if (!(remainLength == 1)) { | |
threeOctets |= input[i+1] << 8; | |
threeOctets |= input[i+2]; | |
} | |
/* | |
Now we want to take the 6 bits from these three characters. | |
77 97 110 | |
| | | |
010011010110000101101110 | |
| | | | |
19 22 5 46 | |
Mask 0x0000003F = 00000000000000000000000000111111 | |
Mask 0x00000FC0 = 00000000000000000000111111000000 | |
Mask 0x0003F000 = 00000000000000111111000000000000 | |
Mask 0x00FC0000 = 00000000111111000000000000000000 | |
00000000010011010110000101101110 | |
& 00000000000000000000000000111111 (0x0000003F) | |
---------------------------------- | |
00000000000000000000000000101110 (0x2E, 46) | |
00000000010011010110000101101110 | |
& 00000000000000111111000000000000 (0x0003F000) | |
---------------------------------- | |
12 | |
00000000000000010110000000000000 >> 00000000000000000000000000010110 (0x16, 22) | |
*/ | |
encChars[3] = (unsigned char) (threeOctets & 0x0000003F); | |
encChars[2] = (unsigned char)((threeOctets & 0x00000FC0) >> 6); | |
encChars[1] = (unsigned char)((threeOctets & 0x0003F000) >> 12); | |
encChars[0] = (unsigned char)((threeOctets & 0x00FC0000) >> 18); | |
output[j] = indexBase64Tbl[encChars[0]]; | |
output[j+1] = indexBase64Tbl[encChars[1]]; | |
output[j+2] = ((remainLength > 1) | |
? indexBase64Tbl[encChars[2]] : '='); | |
output[j+3] = ((remainLength > 2) | |
? indexBase64Tbl[encChars[3]] : '='); | |
} | |
return output; | |
} | |
unsigned char * | |
base64_dec (const char *input, size_t input_sz, size_t *output_sz) | |
{ | |
unsigned char *output; | |
size_t i, j, out_sz, pad_sz; | |
if (!input || !input_sz) | |
return NULL; | |
if (input_sz % 4 != 0) { | |
fprintf (stderr, "%s: invalid base64 buffer\n", __func__); | |
return NULL; | |
} | |
pad_sz = strchr (input, '=') | |
? input_sz - (strchr (input, '=') - input) | |
: 0; | |
out_sz = ((input_sz * 3) / 4) - pad_sz; | |
if (output_sz) | |
*output_sz = out_sz; | |
output = calloc (out_sz + 1, sizeof (unsigned char)); | |
if (!output) | |
return NULL; | |
output[out_sz] = '\0'; | |
for (i = 0, j = 0;i < input_sz; i += 4, j += 3) { | |
unsigned char k = 0, indexTable[4] = {0}; | |
unsigned int threeOctets = 0x0U; | |
for (k = 0;k < 4; k++) { | |
if (isupper (input[i+k])) | |
/* The first 26 characters in the index Base64 | |
* table are the Uppercase alphabet. 65 is the | |
* decimal value for 'A'. | |
*/ | |
indexTable[k] = input[i+k] - 65; | |
else if (islower (input[i+k])) | |
/* The following 26 characters are the lowercase | |
* alphabet. 97 is the decimal value for 'a'. | |
*/ | |
indexTable[k] = (input[i+k] - 97) + 26; | |
else if (isdigit(input[i+k])) | |
/* The following 10 characters are the ten digits. | |
* 48 is the decimal value for '0'. | |
*/ | |
indexTable[k] = (input[i+k] - 48) + 52; | |
else if (input[i+k] == '+') | |
indexTable[k] = 62; | |
else if (input[i+k] == '/') | |
indexTable[k] = 63; | |
} | |
/* Store the index values in the unsigned int buffer in order | |
* to make the appropriate bitwise calculation for decoding. | |
*/ | |
threeOctets |= (indexTable[3] & 0x3F); | |
threeOctets |= (indexTable[2] & 0x3F) << 6; | |
threeOctets |= (indexTable[1] & 0x3F) << 12; | |
threeOctets |= (indexTable[0] & 0x3F) << 18; | |
output[j] = (unsigned char)((threeOctets & 0x00FF0000) >> 16); | |
output[j+1] = (unsigned char)((threeOctets & 0x0000FF00) >> 8); | |
output[j+2] = (unsigned char) (threeOctets & 0x000000FF); | |
} | |
return output; | |
} | |
char * | |
ssha_enc_salt (const char *plaintext, const unsigned char *salt, size_t salt_sz) | |
{ | |
unsigned char *plainPlusSalt; | |
unsigned char digestBytes[SHA1_LENGTH]; | |
unsigned char *hashPlusSalt; | |
char *ciphertext; | |
int ret; | |
if (!plaintext) | |
return NULL; | |
if (!salt || !salt_sz) | |
return NULL; | |
size_t plainBytesLength = strlen (plaintext); | |
/* Get memory for the plain+salt buffer */ | |
plainPlusSalt = malloc (plainBytesLength + salt_sz); | |
if (!plainPlusSalt) | |
return NULL; | |
/* Copy the plaintext to the plain+salt buffer */ | |
memcpy (&plainPlusSalt[0], plaintext, plainBytesLength); | |
/* Append the random salt to the plain+salt buffer */ | |
memcpy (&plainPlusSalt[plainBytesLength], salt, salt_sz); | |
/* Get the SHA1 of the plain+salt buffer */ | |
SHA1 (plainPlusSalt, plainBytesLength + salt_sz, digestBytes); | |
free (plainPlusSalt); | |
hashPlusSalt = malloc (SHA1_LENGTH + salt_sz); | |
if (!hashPlusSalt) | |
return NULL; | |
/* Copy the hashed value to the hash+salt buffer */ | |
memcpy (&hashPlusSalt[0], digestBytes, SHA1_LENGTH); | |
/* Append the salt to the hash+salt buffer */ | |
memcpy (&hashPlusSalt[SHA1_LENGTH], salt, salt_sz); | |
ciphertext = base64_enc (hashPlusSalt, SHA1_LENGTH + salt_sz, NULL); | |
free (hashPlusSalt); | |
if (!ciphertext) | |
return NULL; | |
return ciphertext; | |
} | |
char * | |
ssha_enc (const char *plaintext) | |
{ | |
unsigned char saltBytes[NUM_SALT_BYTES]; | |
int ret; | |
ret = RAND_bytes (saltBytes, sizeof (saltBytes)); | |
if (ret != 1) | |
return NULL; | |
return ssha_enc_salt (plaintext, saltBytes, NUM_SALT_BYTES); | |
} | |
/** | |
* \brief Get the next permutation of a given string and alphabet. | |
* For example, if the alphabet is "abc" and if the string is | |
* - "aaab", the next permutation will be "aaac" | |
* - "aaac", the next permutation will be "aaba" | |
* - "cccb", the next permutation will be "cccc" | |
* | |
* \param buff The string to change to the next permutation. | |
* \param buff_sz The size of the string. | |
* \param alphabet The alphabet to be used to calculate the next permutation. | |
* \param alpahabet_sz The size of the alphabet. | |
* \return 1 if everything is ok, 0 otherwise. | |
*/ | |
int | |
get_next (char *buff, size_t buff_sz, const char *alphabet, size_t alphabet_sz) | |
{ | |
size_t i; | |
if (!buff) { | |
fprintf (stderr, "%s:%d buff is null\n", __func__, __LINE__); | |
return 0; | |
} | |
if (!alphabet) { | |
fprintf (stderr, "%s:%d alphabet is null\n", __func__, __LINE__); | |
return 0; | |
} | |
for (i = buff_sz - 1; i != -1; i--) { | |
// Get the position of the character in the | |
// alphabet string. | |
size_t pos = str_chr (alphabet, buff[i]); | |
// If the character, is the last character | |
// of the alphabet, change to the first character | |
// of the alphabet and continue to the next character | |
// on buffer. | |
if (buff[i] == alphabet[alphabet_sz - 1]) { | |
buff[i] = alphabet[(pos+1) % alphabet_sz]; | |
continue; | |
} else { | |
buff[i] = alphabet[(pos+1) % alphabet_sz]; | |
break; | |
} | |
} | |
return 1; | |
} | |
/** | |
* \brief Tries to decode an ssha encoded buffer using amodified odometer algorithm, | |
* it's a permutation of N members of an alphabet, which each member of the alphabet | |
* can take MAX_VAL values. For example, if N = 5 and the alphabet "abc" (MAX_VAL = 3), | |
* we get the following permutations | |
* | |
* a a a a a | |
* b a a a a | |
* c a a a a | |
* a b a a a | |
* b b a a a | |
* c b a a a | |
* a c a a a | |
* b c a a a | |
* c c a a a | |
* a a b a a | |
* b a b a a | |
* c a b a a | |
* ... | |
* ... | |
* c c c c c | |
* | |
* The total number of permutations is MAX_VAL^N, in the above example, | |
* the total permutations will be 3^5 = 243. | |
* | |
* \param ciphertext The ssha encoded buffer. | |
* \param plaintext_sz It's the N members to permut (actually it's the target plaintext buffer size). | |
* \param alphabet The alphabet that will be used. | |
* \param alphabet_sz It's the MAX_VAL of each member (actually it's the size of the alphabet). | |
* \return 1 if everything is ok, 0 otherwise. | |
*/ | |
char * | |
ssha_dec (const char *ciphertext, size_t plaintext_sz, const char *alphabet, size_t alphabet_sz) | |
{ | |
char *output; | |
unsigned char saltBytes[NUM_SALT_BYTES] = {0}; | |
unsigned char *hashPlusSalt; | |
size_t hashPlusSalt_sz; | |
size_t cipherLength = strlen (ciphertext); | |
char *start, *end; | |
size_t i, count = 0; | |
hashPlusSalt = base64_dec (ciphertext, cipherLength, &hashPlusSalt_sz); | |
if (hashPlusSalt_sz != SHA1_LENGTH + NUM_SALT_BYTES) { | |
fprintf (stderr, "%s:%d not correct cipher algorithm (%zd!=%d)\n", | |
__func__, __LINE__, hashPlusSalt_sz, SHA1_LENGTH + NUM_SALT_BYTES); | |
goto out_base64_dec; | |
} | |
memcpy (saltBytes, &hashPlusSalt[SHA1_LENGTH], NUM_SALT_BYTES); | |
// The buffer that holds the changes | |
start = calloc (plaintext_sz, sizeof (char)); | |
if (!start) { | |
fprintf(stderr, "%s:%d Mem Alloc failed\n", __func__, __LINE__); | |
goto out_base64_dec; | |
} | |
// Initialize with the first member of alphabet | |
for (i = 0;i < plaintext_sz;i++) | |
start[i] = alphabet[0]; | |
// The end result of the buffer | |
end = calloc (plaintext_sz, sizeof (char)); | |
if (!end) { | |
fprintf(stderr, "%s:%d Mem Alloc failed\n", __func__, __LINE__); | |
goto out_end; | |
} | |
// Initialize with the last member of alphabet | |
for (i = 0;i < plaintext_sz;i++) | |
end[i] = alphabet[alphabet_sz - 1]; | |
while (strcmp (start, end)) { | |
char *current_ciphertext = ssha_enc_salt (start, saltBytes, sizeof (saltBytes)); | |
if (!current_ciphertext) | |
break; | |
fprintf (stdout, "%s:%d %ju: Testing cleartext: %s (%s) with ciphertext: %s\n", | |
__func__, __LINE__, count, start, current_ciphertext, ciphertext); | |
if (!strcmp (current_ciphertext, ciphertext)) { | |
output = strdup (start); | |
free (current_ciphertext); | |
break; | |
} | |
get_next (start, plaintext_sz, alphabet, alphabet_sz); | |
count++; | |
free (current_ciphertext); | |
} | |
free (end); | |
out_end: | |
free (start); | |
out_base64_dec: | |
free (hashPlusSalt); | |
return output; | |
} | |
int main (int argc, char *argv[]) | |
{ | |
const char password[] = "p@55w0rd"; | |
char *ssha_encoded_string = ssha_enc (password); | |
printf ("Password: %s SSHA encoded: %s\n", password, ssha_encoded_string); | |
ssha_dec (ssha_encoded_string, 8, printableAlphabet, sizeof (printableAlphabet)); | |
free (ssha_encoded_string); | |
return 0; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment