Skip to content

Instantly share code, notes, and snippets.

@svagionitis
Last active September 27, 2020 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save svagionitis/ef9519c1938ef1e60300788ded41a412 to your computer and use it in GitHub Desktop.
Save svagionitis/ef9519c1938ef1e60300788ded41a412 to your computer and use it in GitHub Desktop.
SSHA encoding and decoding (bruteforce)
#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