I back out of the JS directory, and find the directory where the .so libraries are stored. A quick ls of the directory shows me a file called xxlicense.so. (Name has been changed to protect the innocent). I toss that into IDA Pro, and see that there are some functions here, but that they call to some centralized function not present in this file. The list of libraries that this thing loaded was impressive! The normal ones for glibc, and the like, but also a TON of other libraries. Not sure if they used them all, or if they were just trying to obfuscate where the protection bits were. So, I start loading the libs, one at a time, looking for where the functions were that it was calling. I FINALLY find them in a very large library. A little digging around shows me that in typical Linux style, they didn't strip the symbols from the library, so not only do I have the names for the EXPORTED functions, but I also have the names of the utility functions used by the exported functions. A little digging reveals verifyKey. A cursory glance through here is initially intimidating. OpenSSL, SHA1, big nums, etc. All the makings for a genuine nightmare. But, since you're reading about it here, you know where this is going.
Some further study reveals that the unlock code that you enter is 20 characters long. They have a list of allowable characters. (No 0's, O's, 1's, l's, or anything that could be construed as being something else).
char validCharacters[]= {"BCDFGHJKMPQRTVWXY2346789\0"};
These characters are processed one at a time, and a pseudo-summation is performed to generate a key. The key contains some data, and a checksum of the key. My mock up of their code looks like this:
counter = 0; do { BN_mul_word(pOutput, 24); keyCharacter = pInput[counter]; compareCharacter = 'B'; innerLoopCounter = 0; while ( keyCharacter != compareCharacter ) { ++innerLoopCounter; if ( innerLoopCounter == 24 ) { innerLoopCounter = -1; break; } compareCharacter = validCharacters[innerLoopCounter]; } ++counter; BN_add_word(pOutput, innerLoopCounter); } while ( counter != length );
There are 24 characters in their "safe list". That's why there's a comparison of innerLoopCounter and 24. The 1st character of their list is 'B'. That's why they "pre-load" B into compareCharacter.Now, as you see, they don't add the CHARACTER to the sum, they add the INDEX of the character in their "safe list". And then, multiply the whole thing by 24. (Except for the LAST character). Being that the code is 20 characters long, this yields a valid number in the 46-91 bit range. That's why they use the BN (Big Number) functionality from OpenSSL. Once this is done, they then slice, and dice the results. The upper 45 bits are a SHA1 hash of the lower 46 bits. And the lower 46 bits contains 31 bits of serial number, and 4 bits of license count, and 11 unused bits. Since we understand all of this, we simply need to do things in the REVERSE order to make our keygen. So, we take a serial number, and number of licenses, and shift those into place. Generate a SHA1 of that half, truncate it to 45 bits, and slap it into the TOP half. THEN comes the interesting part. Reversing the function you see above. It took me a while to figure out exactly how to do it, since brute forcing 24^19th didn't seem like something that I would like to do on a regular basis. The way that I came to understand what would be the final technique was to start with a code of 00000000000000000001. I looked at the output. Then, I moved the 1 to the left, like this: 00000000000000000010. Looked at THAT value. I did that all the way across, and examined the values. It came out as 1 * 24 ^nth. (Where nth represents the position of the 1 character in the string).
This triggered a thought. What if we do the LEFT most character first. And by DO, I mean divide it by 24^19th, use the result as the index into the character array, and then work on the remainder.
So, I whipped up code to do that. It looks like this:
// Set the divisor to 24^19 BN_set_word(result, 24); BN_set_word(remainder, 19); BN_exp(rollingDivisor, result, remainder, ctx); // Process each digit (codes are 20 digits long) while ( count < 20 ) { // If we make it to 0, just spit out B's for the rest (or hot fire). if ( BN_is_zero(code) ) { outputCode[count++] = 'B'; continue; } // Divide the code by the divisor BN_div(result, remainder, code, rollingDivisor, ctx); // The remainder becomes the new code BN_copy(code, remainder); // Adjust the divisor BN_div_word(rollingDivisor, 24); // Value should be in the realm of 0-23 if ( BN_num_bits(result) > 5) { printf("Something is broken, too many bits\n"); goto Exit; } // Horrible hack because BN_bn2bin didn't appear to want to work. index = atoi(BN_bn2dec(result)); // Save the letter of the code outputCode[count++] = validCharacters[index]; }
No comments:
Post a Comment