From a2e020660404fa86e5a8d2bd114496fe5da5868d Mon Sep 17 00:00:00 2001 From: Neo Anderson Date: Sat, 11 Apr 2020 22:05:53 -0700 Subject: [PATCH] sb 1-2 --- Makefile | 29 ++++++ README | 46 +++++++++ README.md | 2 - doc/drm.ms | 274 +++++++++++++++++++++++++++++++++++++++++++++++++ doc/drm.ms.pdf | Bin 0 -> 19194 bytes doc/sb.1 | 160 +++++++++++++++++++++++++++++ doc/sb.1.pdf | Bin 0 -> 20192 bytes h.h | 24 +++++ md5.c | 246 ++++++++++++++++++++++++++++++++++++++++++++ md5.h | 36 +++++++ md_common.c | 72 +++++++++++++ reg.c | 147 ++++++++++++++++++++++++++ sb.c | 71 +++++++++++++ snak.c | 183 +++++++++++++++++++++++++++++++++ snakext.c | 23 +++++ 15 files changed, 1311 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 README delete mode 100644 README.md create mode 100644 doc/drm.ms create mode 100644 doc/drm.ms.pdf create mode 100644 doc/sb.1 create mode 100644 doc/sb.1.pdf create mode 100644 h.h create mode 100644 md5.c create mode 100644 md5.h create mode 100644 md_common.c create mode 100644 reg.c create mode 100644 sb.c create mode 100644 snak.c create mode 100644 snakext.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..198fd17 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.PHONY: clean + +OBJS = md5.o md_common.c reg.o sb.o snak.o snakext.o +CFLAGS += -std=c11 -D_POSIX_C_SOURCE=2 + +all: sb + +md5.o: md5.c md5.h +md_common.o: md_common.c md5.h +reg.o: h.h md5.h +sb.o: h.h +snak.o: snak.c h.h +snakext.o: snakext.c h.h md5.h + +sb: $(OBJS) + $(CC) -o sb $(OBJS) + +clean: + rm -f sb *.o + +dist: + mkdir sb-1.2 + cp *.c *.h README Makefile sb-1.2 + cp -r doc/ sb-1.2 + groff -Tpdf -mdoc doc/sb.1 > sb-1.2/doc/sb.1.pdf + groff -Tpdf -ms doc/drm.ms > sb-1.2/doc/drm.ms.pdf + tar czf sb-1.2.tar.gz --owner=root --group=root --format=ustar sb-1.2 + rm -rf sb-1.2 + diff --git a/README b/README new file mode 100644 index 0000000..5090d86 --- /dev/null +++ b/README @@ -0,0 +1,46 @@ +Overview +-------- + +sb, the SCO Breaker, generates serial numbers, activation keys and +registration keys for SCO products, such as UnixWare 7.1.4. + +It still works for versions released after SCO was acquired by Xinuos. +Products made by Xinuos themselves (namely OpenServer 10) do not employ any +kind of DRM. + +License +------- + +MIT + +Installation +------------ + + $ make + $ install -s sb /usr/local/bin + $ install -m 0644 doc/sb.1 /usr/local/man/man1 + +The install step is optional; sb does not depend on any files and can thus be +run from any directory. + +Usage and Documentation +----------------------- + +See doc/sb.1 for using sb and doc/drm.ms for a description of the DRM +mechanisms employed by SCO. + +For those with no access to troff implementation, a PDF version of the +documents have been included. + +Motivation +---------- + +UNIX wants to be free. +It's an integral part of the UNIX experience to be able to read and understand +the source code of the system. +If you haven't done so yet, use something from the V6 through 4.4BSD era. +You'll see what I mean. + +So this is a very elaborate "Fuck you" to SCO. +I hope it's to your liking. + diff --git a/README.md b/README.md deleted file mode 100644 index 5eb2a13..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# sco-breaker -Generates keys for an aptly named Operating System diff --git a/doc/drm.ms b/doc/drm.ms new file mode 100644 index 0000000..1ae741d --- /dev/null +++ b/doc/drm.ms @@ -0,0 +1,274 @@ +.de UX +\s-1UNIX\s0\\$1 +.. +.RP +.TL +Digital Rights Management Mechanisms in the SCO Product Line +.AU +Anonymous +.ND +.AB +This document attempts to explain the primary mechanisms with which SCO +has attempted to protect their intellectual property as well as how they fell +short of doing so. +.PP +In particular, the serial number and activation key (SNAK) combination, the +license data and the registration key shall be covered. +.AE +.SH +Overview +.LP +The product line of SCO Group, Inc. (SCO) consists of the OpenServer and +UnixWare operating systems as well as associated add-on software, such as user +licenses or development kits. +After SCO had gone bankrupt, UnXis, Inc. bought their +.UX +business and continued to offer it. +UnXis, Inc. later renamed to Xinuos, Inc. +The new OpenServer 10 operating system offered by Xinuos, Inc. is +.I not +covered by this document; +OpenServer 10 eschews digital rights management entirely. +Because of this, ``SCO products'' is still used, even though the products are +now maintained and sold by a different entity. +.PP +SCO's digital rights management consists of a number of different parts: +.IP 1. +the serial number (henceforth +.B serno ) +and activation key, +.B actkey ), ( +forming the +.B SNAK '', `` +.IP 2. +the license data indicating additional information about a product, and +.IP 3. +the registration procedure. +.LP +The purchase of an SCO product provides the buyer with a SNAK and license data +if required. +For continued use, a product usually also needs to be +.I registered . +When the system prompts for registration, a registration lock +.B reglock ) ( +is displayed. +The reglock is then supposed to be entered together with personally +identifying information on a Xinuos, Inc. web portal. +.SH +Serial Number and Activation Key +.LP +The pair of serial number and activation key, internally also called +``SNAK'', consists of a nine-character serno and an eight-character actkey. +The actkey encodes the product ID, product version and whether license data is +required as well as a checksum over the serno and the data contained in the +actkey. +The first three characters of the actkey denote the product ID. +The second triplet denotes the product version and whether license data is +required. +The final two characters are the checksum. +While the serno is effectively an arbitrary string, the actkey is an +all-lowercase string. +For example, a valid SNAK would be the serno ``SCO524572'' and the actkey +``mnridxjo''. +There are two layers of protection: obscurity and a checksum. +.PP +The +.B actkey +is encrypted. +Presumably because of cryptography export restrictions in the United States of +America, the encryption algorithm is weak. +The ciphertext (i.e. the actkey) is processed in reverse, starting with the +NUL. +Each character is +.I rotated +in the alphabet (a-z) by the previous character's offset in the alphabet. +The first character to be decrypted, which is the last character in the +string, will always be unchanged by this algorithm. +See +.I incfrp () +(decryption) and +.I decfrp () +(encryption) +functions for the implementation. +.PP +The second layer of protection is a simple checksum over the serno and +the first six characters of the decrypted actkey. +See the +.I mnsnc () +function for the implementation.\(dg +.FS +\(dg ``incfrp'' and ``mnsnc'' are the names used by SCO. +It is unclear what they stand for. +``decfrp'' has been named that way because the opposite of increment is +decrement, assuming ``inc'' in ``incfrp'' stands for ``increment''. +.FE +.PP +After decrypting the actkey and validating the checksum, +the decrypted actkey is parsed. +Both of the data triplets in the actkey are encoded in base 26 with alphabet +a-z; the most significant digit is written first. +For the example above, the decrypted actkey is ``ahvneoco''. +``ahv'' decodes to 0xCB (203), which is the product ID. +``neo'' decodes to 0x22CA (8906), which contains the flag if license data is +required and the product version. +The version is contained in the lower three nibbles: +0x22CA & 0xFFF equals 714. +The last decimal digit (4) denotes the minor version, the other ones denote +the major version (71). +``co'' is the checksum value. +.SH +License Data +.LP +License data is required if bits 11\(en15 of the second triplet in the actkey +equal 3. +In the given example, this wasn't the case; the relevant bits equal 2 instead. +Values other than 2 and 3 are invalid. +License data is used to supply additional information about a license and +enforce restrictions. +If license data is required, it is printed alongside the SNAK on Certificates +of License and Authenticity. +For example, license data could look like this: ``c4;k0;mjqs8r7''. +.PP +The core of the algorithm is an MD5 hash over a secret value, the serno, the +actkey and the rest of the license data. +Then, this hash is translated to a custom base 32 alphabet. +It is important to note that the license data is ordered by the flag +characters, except for the m flag always being at the end. +See the +.Pa snakext.c +file for the implementation. +.PP +This algorithm is actually susceptible to length-extension attacks because the +secret is merely prepended and MD5 has no mitigations for length-extension +attacks. +However, because license data is fairly strict in validating the input, it is +not actually practical. +.PP +The following fields are known: +.IP c +number of CPUs; +.IP d +expiration of license in days; +this is used for evaluation licenses; +.IP k +if no argument or a non-zero argument is given, registration is required, but +more than one user can access the system; +if the argument is zero, no registration is required, but only one user can +access the system and possibly the network stack is gimped; +.IP u +number of users that may access the system at the same time; +.IP m +MD5 hash over the license data secret, the serno, the actkey and a +canonicalized version of the string until this flag. +.LP +More flags exist. +At least b, g, q have been observed but their format and effects are unknown. +.SH +Registration Key +.LP +Some products must be registered, else they expire after a set amount of time. +This involves a per-installation identifier, the host ID (also referred to as +node ID). +The host ID and serial number are then combined to generate the registration +key. +Because the host ID is different for each installation of a product, storing +registration keys is meaningless. +Most likely, SCO realized the deficiencies of the simple SNAK scheme and added +another layer of protection. +Customers are meant to use an online portal to register their products, +which also checks for double registrations of the same product, allowing the +identification of serial numbers that have been shared. +.I "/etc/brand -k serno" +or +.I scoadmin +can be used to generate the registration lock. +Some of the data there is superfluous for registration code generation; +it is surmised that they are collected for statistical purposes. +This also implies that SCO has customers, which may be a stretch in the first +place, especially considering the USD 2,500 price tag. +.PP +The core of the algorithm is an MD5 hash over a secret value, the host ID and +the serno. +The secret value differs from the one used for the license data. +After that, the first four bytes of the hash are swapped and then encoded in +base16 with a custom alphabet and a two-character checksum. +Because this scheme is symmetric, no actual interaction with SCO is required. +Furthermore, no measures were taken to obscure the secret \(en it is stored in +the +.I /etc/brand +binary with no obfuscation whatsoever. +The secret value used to sign a registration key is the same as the one used +to create the m flag for the registration lock. +There is something truly, profoundly wrong with this; I believe I need not +spell it out. +See the +.I reg.c +file for the implementation. +.PP +A registration lock may look like this: +``d180120;e380106;i203/71.4;oSCO310807;uorxrrwjwxz;mg7fuxu''. +Like the license data, it is a flag string. +The following flags are known: +.IP i +the product ID and version; +the product ID is separated by a slash, the product major and minor versions +are separated by a dot; +.IP o +the serno; +.IP u +the host ID, encoded in some variation of base16 with a custom alphabet +(kbwtacorhzgsejqx) with an appended checksum; +.IP m +MD5 hash over the registration secret, and a canonicalized version of the +string until this flag; +the canonicalization algorithm is the same as for license data. +.LP +Other flags exist, namely d and e, but their purpose is unknown. +.SH +Conclusions +.LP +SCO has done everything wrong that could possibly be done wrong, while also +making matters much more complicated for themselves than necessary. +There are a total of four checksums: +one in the SNAK, a different one in the flag string for the license data, a +different one in the flag string for the registration lock and a different one +for the registration key. +Furthermore, there are two secrets: +the one for the flag string in the license data and the one used for both +sides of the registration process. +And then there are three different encoding schemes: +the encryption of the activation key, the encoding of the activation key (base +26), the base 32 encoding in the m flag for license data and registration +lock and the base 16 encoding for the registration key. +It would not have been necessary to keep this many separate encodings and +algorithms around. +.PP +Due to poor operational security, a mostly complete code dump of SCO UnixWare +leaked on the Internet. +They realized that keeping the +.I /etc/brand +utility in the main tree would be dangerous, so it was checked in only as a +binary file. +However, the binary was neither optimized nor stripped, making reverse +engineering effectively trivial. +.PP +Because all secrets in this DRM mechanism are known to both SCO and +.I /etc/brand , +reverse engineering is all that is required to break every layer of +protection; +there is no cryptographic layer of protection, such as asymmetric signatures +over the registration key. +Elliptic curve signatures in a base64 encoding would likely have been +tolerable for users. +Alternatively, a truncated RSA signature could have been used \(en a full +signature would be too long for users to type into the terminal. +The short Schnorr signatures would have been another option, used by Microsoft +in the Windows XP era. +.PP +None of this has a real-world impact and the estimated amount of lost sales +tends towards zero. +The only reasons to buy an SCO product are either legacy applications or the +support contract that comes with it. +Legacy applications generally do not generate new sales. +Breaking the DRM mechanisms does not cause a support contract to come into +existence out of thin air. diff --git a/doc/drm.ms.pdf b/doc/drm.ms.pdf new file mode 100644 index 0000000000000000000000000000000000000000..09278c331623ebb51edaec07b6fe58d74e82a8a9 GIT binary patch literal 19194 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 UINT16_MAX" +You should never see this. +If you do, your platform is almost certainly clinically insane. +Please stop trying to run +.Nm +on a literal Game Boy. +.El +. +.Sh CAVEATS +While this code appears to work, it may be possible that the product +nonetheless phones home over IP. +No efforts have been made to try and observe such activity. +It may prove advantageous to first strictly firewall an SCO UNIX installation. +If the outgoing connections look okay for approximately 24 hours, it may be +safe to let loose on the Internet. +More cautious users may wish to wait up to 31 days. diff --git a/doc/sb.1.pdf b/doc/sb.1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a26078ac4def7e5947b810ff0db6d30ba12c5305 GIT binary patch literal 20192 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 ORFN<#DLpfN*#83_t<$*x literal 0 HcmV?d00001 diff --git a/h.h b/h.h new file mode 100644 index 0000000..0d9f413 --- /dev/null +++ b/h.h @@ -0,0 +1,24 @@ +#ifndef SB_H_H +#define SB_H_H + +/* sb.c */ +void usage(bool fail); +_Noreturn void die(const char *msgfmt, ...); + +/* md_common.c */ +char *BSCanon(const char *s); +void extmd(char md[static 7], unsigned int nstr, ...); + +/* reg.c */ +int gen_regcode(int argc, char *argv[]); + +/* snak.c */ +int gen_snak(int argc, char *argv[]); + +/* snakext.c */ +void mdsnakext(const char *serno, const char *actkey, const char *snakext, + char snakextmd[static 7]); + + +#endif + diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..caf3c41 --- /dev/null +++ b/md5.c @@ -0,0 +1,246 @@ +/* $OpenBSD: md5.c,v 1.11 2015/09/11 09:18:27 guenther Exp $ */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include +#include +#include "md5.h" + +#define PUT_64BIT_LE(cp, value) do { \ + (cp)[7] = (value) >> 56; \ + (cp)[6] = (value) >> 48; \ + (cp)[5] = (value) >> 40; \ + (cp)[4] = (value) >> 32; \ + (cp)[3] = (value) >> 24; \ + (cp)[2] = (value) >> 16; \ + (cp)[1] = (value) >> 8; \ + (cp)[0] = (value); } while (0) + +#define PUT_32BIT_LE(cp, value) do { \ + (cp)[3] = (value) >> 24; \ + (cp)[2] = (value) >> 16; \ + (cp)[1] = (value) >> 8; \ + (cp)[0] = (value); } while (0) + +static uint8_t PADDING[MD5_BLOCK_LENGTH] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5_CTX *ctx) +{ + ctx->count = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len) +{ + size_t have, need; + + /* Check how many bytes we already have and how many more we need. */ + have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + need = MD5_BLOCK_LENGTH - have; + + /* Update bitcount */ + ctx->count += (uint64_t)len << 3; + + if (len >= need) { + if (have != 0) { + memcpy(ctx->buffer + have, input, need); + MD5Transform(ctx->state, ctx->buffer); + input += need; + len -= need; + have = 0; + } + + /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ + while (len >= MD5_BLOCK_LENGTH) { + MD5Transform(ctx->state, input); + input += MD5_BLOCK_LENGTH; + len -= MD5_BLOCK_LENGTH; + } + } + + /* Handle any remaining bytes of data. */ + if (len != 0) + memcpy(ctx->buffer + have, input, len); +} + +/* + * Pad pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Pad(MD5_CTX *ctx) +{ + uint8_t count[8]; + size_t padlen; + + /* Convert count to 8 bytes in little endian order. */ + PUT_64BIT_LE(count, ctx->count); + + /* Pad out to 56 mod 64. */ + padlen = MD5_BLOCK_LENGTH - + ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + if (padlen < 1 + 8) + padlen += MD5_BLOCK_LENGTH; + MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ + MD5Update(ctx, count, 8); +} + +/* + * Final wrapup--call MD5Pad, fill in digest and zero out ctx. + */ +void +MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx) +{ + int i; + + MD5Pad(ctx); + for (i = 0; i < 4; i++) + PUT_32BIT_LE(digest + i * 4, ctx->state[i]); +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH]) +{ + uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; + +#if BYTE_ORDER == LITTLE_ENDIAN + memcpy(in, block, sizeof(in)); +#else + for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { + in[a] = (uint32_t)( + (uint32_t)(block[a * 4 + 0]) | + (uint32_t)(block[a * 4 + 1]) << 8 | + (uint32_t)(block[a * 4 + 2]) << 16 | + (uint32_t)(block[a * 4 + 3]) << 24); + } +#endif + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + diff --git a/md5.h b/md5.h new file mode 100644 index 0000000..9f6cf37 --- /dev/null +++ b/md5.h @@ -0,0 +1,36 @@ +#include + +/* $OpenBSD: md5.h,v 1.17 2012/12/05 23:19:57 deraadt Exp $ */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + */ + +#ifndef _MD5_H_ +#define _MD5_H_ + +#define MD5_BLOCK_LENGTH 64 +#define MD5_DIGEST_LENGTH 16 +#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1) + +typedef struct MD5Context { + uint32_t state[4]; /* state */ + uint64_t count; /* number of bits, mod 2^64 */ + uint8_t buffer[MD5_BLOCK_LENGTH]; /* input buffer */ +} MD5_CTX; + +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, const uint8_t *, size_t); +void MD5Pad(MD5_CTX *); +void MD5Final(uint8_t [MD5_DIGEST_LENGTH], MD5_CTX *); +void MD5Transform(uint32_t [4], const uint8_t [MD5_BLOCK_LENGTH]); + +#endif /* _MD5_H_ */ diff --git a/md_common.c b/md_common.c new file mode 100644 index 0000000..cab1871 --- /dev/null +++ b/md_common.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include + +#include "md5.h" +#include "h.h" + +const char *fromHextet = "0123456789abcdefghjkmnpqrstuwxyz"; + +static int +asciiCode(int c) +{ + static const int fromAlpha[] = { + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 1, 0x12, 0x13, 1, + 0x14, 0x15, 0, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f + }; + + if (c >= '0' && c <= '9') + return c - '0'; + if (isupper(c)) + c = tolower(c); + if (islower(c)) + return fromAlpha[c - 'a']; + return -1; +} + +char * +BSCanon(const char *s) +{ + char *ret; + int c; + + ret = malloc(strlen(s) + 1); + if (ret == NULL) + die("malloc"); + strcpy(ret, s); + + for (char *p = ret; *p; ++p) { + c = asciiCode(*p); + if (c != -1) + *p = fromHextet[c]; + } + + return ret; +} + +void +extmd(char md[static 7], unsigned int nstr, ...) +{ + char *str; + MD5_CTX ctx; + char digest[MD5_DIGEST_LENGTH]; + va_list ap; + + MD5Init(&ctx); + va_start(ap, nstr); + + for (unsigned int i = 0; i < nstr; ++i) { + str = va_arg(ap, char *); + MD5Update(&ctx, (const uint8_t *)str, strlen(str)); + } + + MD5Final(digest, &ctx); + for (size_t i = 0; i < 6; ++i) + md[i] = fromHextet[digest[i] & 0x1F]; + md[6] = '\0'; +} + diff --git a/reg.c b/reg.c new file mode 100644 index 0000000..5321447 --- /dev/null +++ b/reg.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include + +#include "h.h" +#include "md5.h" + +static const char *regSecret = "\x75\xf8\xe8\x5e\x83\xc4\x5e\x4c\xff\x75\x5e\x48\xe8\x65\x5e\x46\x59\x8b\x45"; + +static char * +toAsciiBase16(uint32_t in, unsigned char ckbase) +{ + static const char *charmap = "\005k\001b\002w\003t\004a\005c\001o\002r\003h\004z\005g\001s\002e\003j\004q\005x\001"; + static char buf[11]; + unsigned int a, i; + unsigned char ck; + + ck = ckbase; + for (i = 8; i > 0; --i) { + a = 15 - (in & 0xF); + buf[i - 1] = charmap[2 * a + 1]; + ck += a * (i); + in >>= 4; + } + + /* checksum */ + for (i = 9; i >= 8; --i) { + buf[i] = charmap[2 * (ck & 0xF) + 1]; + ck >>= 4; + } + + buf[10] = '\0'; + return buf; +} + +static uint32_t +l32be(uint8_t i[4]) +{ + return (uint32_t)i[3] + | ((uint32_t)i[2] << 8) + | ((uint32_t)i[1] << 16) + | ((uint32_t)i[0] << 24); +} + +static char * +generateRegistrationID(const char *serno, const char *szHostid) +{ + static char buf[11]; + uint32_t regkey; + MD5_CTX ctx; + uint8_t digest[MD5_DIGEST_LENGTH]; + + MD5Init(&ctx); + MD5Update(&ctx, (const uint8_t *)regSecret, strlen(regSecret)); + MD5Update(&ctx, (const uint8_t *)szHostid, strlen(szHostid)); + MD5Update(&ctx, (const uint8_t *)serno, strlen(serno)); + MD5Final(digest, &ctx); + + regkey = l32be(digest); + snprintf(buf, sizeof(buf), "%s", toAsciiBase16(regkey, 3)); + return buf; +} + +static bool +valid_reglock(char *reglock) +{ + char *canon, *theirs, *p; + char mine[7]; + + if ((p = strstr(reglock, ";m")) == NULL) + return false; + if (*(p + 2) == '\0') + return false; + + *p = '\0'; + theirs = p + 2; + + canon = BSCanon(reglock); + extmd(mine, 2, regSecret, canon); + free(canon); + + return (strcmp(mine, theirs) == 0); +} + +static void +parse_reglock(char *reglock, char **serno, char **hostid) +{ + char *last, *p; + + if (!valid_reglock(reglock)) + die("registration lock %s invalid; check for typos", reglock); + + /* Assumption: Nobody generates bogus reglocks, so they're well-behaved + * after the MD5 checks out. + */ + for (p = strtok_r(reglock, ";", &last); + p != NULL; + p = strtok_r(NULL, ";", &last)) { + switch (*p) { + case 'o': + *serno = p + 1; + break; + case 'u': + *hostid = p + 1; + break; + default: + break; + } + } +} + +int +gen_regcode(int argc, char *argv[]) +{ + char *serno, *hostid, *regcode; + size_t serno_len, hostid_len; + + if (argc < 1) { + usage(true); + return EXIT_FAILURE; + } + + if (argc == 2) { + serno = argv[0]; + hostid = argv[1]; + serno_len = strlen(serno); + hostid_len = strlen(hostid); + if (serno_len == 10 && hostid_len == 9) { + /* Arguments are probably swapped. */ + serno = argv[1]; + hostid = argv[0]; + } else if (serno_len != 9 || hostid_len != 10) { + die("invalid length for serno or hostid"); + } + } else { + parse_reglock(argv[0], &serno, &hostid); + } + + regcode = generateRegistrationID(serno, hostid); + printf("Registration Key: %s\n", regcode); + + return EXIT_SUCCESS; +} + + diff --git a/sb.c b/sb.c new file mode 100644 index 0000000..b85da7c --- /dev/null +++ b/sb.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include + +#include "h.h" + +static char *progname; + +void +usage(bool fail) +{ + fprintf(fail ? stderr : stdout, + "usage: %s product_id major_ver minor_ver " + "[license_data]\n" + " %s -r serial_number host_id\n" + " %s -r registration_lock\n", + progname, progname, progname); +} + +_Noreturn void +die(const char *msgfmt, ...) +{ + va_list ap; + + fprintf(stderr, "%s: ", progname); + + va_start(ap, msgfmt); + vfprintf(stderr, msgfmt, ap); + va_end(ap); + + putchar('\n'); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + int c; + int ret = EXIT_SUCCESS; + bool want_regcode = false; + + progname = basename((argv[0] != NULL) ? argv[0] : "sb"); + + while ((c = getopt(argc, argv, "hr")) != -1) { + switch (c) { + case 'r': + want_regcode = true; + break; + + default: + ret = EXIT_FAILURE; + case 'h': + usage(ret == EXIT_FAILURE); + return ret; + } + } + + argc -= optind; + argv += optind; + + if (want_regcode) + ret = gen_regcode(argc, argv); + else + ret = gen_snak(argc, argv); + + return ret; +} + diff --git a/snak.c b/snak.c new file mode 100644 index 0000000..57c1a7f --- /dev/null +++ b/snak.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "h.h" + +static bool +overflow15(uint32_t a, uint32_t b) +{ + return (a + b > 0x7FFF); +} + +static char * +mnsnc(const char *s) +{ + static char buf[3]; + uint16_t a; + uint16_t flag; + int c = s[8] % 16; + + for (a = 0; *s != '\0'; ++s) { + if (overflow15(a, *s)) + flag = 1; + else + flag = 0; + a = flag | (2 * (a + *s)); + } + + for (; c > 0; --c) + a = ((a & 0x8000U) >> 15) | (uint16_t)(2 * a); + + buf[2] = 0; + buf[1] = a % 26 + 'a'; + a /= 26; + buf[0] = a % 26 + 'a'; + + return buf; +} + +static void +strbn(char *out, unsigned int in) +{ + static const char *alphabet = "abcdefghijklmnopqrstuvwxyz"; + size_t i; + + for (i = 3; i > 0; --i) { + out[i - 1] = alphabet[in % 26]; + in /= 26; + } +} + +static void +decfrp(char *s) +{ + char *p; + unsigned char a, b; + + a = b = 0; + + for (p = s + strlen(s) - 1; p >= s; --p) { + a = (*p - 'a' + b) % 26; + a = (a + 'a') & 0xFF; + b += a; + b %= 26; + *p = a; + } +} + +/* Implementation of xorshift* without retaining seed state. */ +static uint64_t +rnd(uint64_t seed) +{ + seed ^= seed >> 12; + seed ^= seed << 25; + seed ^= seed >> 27; + return seed * 0x2545F4914F6CDD1D; +} + +static unsigned int +mkver(unsigned int lictype, unsigned int major, unsigned int minor) +{ + return ((lictype << 12) | (major * 10 + minor)); +} + +static void +mksnak(bool has_snakext, uint16_t product_id, uint16_t major, uint16_t minor, + char *serno, char *actkey) +{ + const char *cksum; + uint64_t serial; + unsigned int version = mkver((has_snakext ? 3 : 2), major, minor); + char merged[18]; + + memset(serno, 0, 10); + memset(actkey, 0, 9); +#ifdef DBG + printf("has_snakext: %d, major: %u, minor: %u, version: %u\n", + !!has_snakext, major, minor, version); +#endif + + /* bitmask to ensure at most six digits */ + serial = rnd((uintptr_t)serno * time(NULL)) & 0xEFFFF; + snprintf(serno, 10, "SCO%06" PRIu64, serial); + + strbn(actkey, product_id); + strbn(actkey + 3, version); + snprintf(merged, sizeof(merged), "%s%s", serno, actkey); + + cksum = mnsnc(merged); + actkey[6] = cksum[0]; + actkey[7] = cksum[1]; + + decfrp(actkey); +} + +static uint16_t +strtou16lim(const char *in, unsigned long limit) +{ + char *end; + unsigned long i; + + if (limit > UINT16_MAX) + die("internal: limit > UINT16_MAX"); + + i = strtoul(in, &end, 10); + if (*in == '\0' || *end != '\0') + die("%s not a number", in); + if (i > limit) + die("%s out of range (max %"PRIu16")", in, limit); + + return i; +} + +int +gen_snak(int argc, char *argv[]) +{ + char *snakext = NULL; + uint16_t product_id, major, minor; + char serno[10], actkey[9], snakextmd[7]; + + if (argc < 3) { + usage(true); + return EXIT_FAILURE; + } + + /* "zzz" is the maximum possible product ID encoded value. + * This decodes to a value of 17575. + */ + product_id = strtou16lim(argv[0], 17575); + + /* License type (whether snakext is to be read), version major and + * version minor share an integer, max encoded as "zzz". + * The license type is shifted up by 12, leaving 0xFFF (4095) for the + * version major and minor. + * Of that, the version major is all the upper digits, and the minor is + * the bottom digit (i.e. version/10 => major, version%10 => minor). + * + * Without doing too much checking, the maximum major version is 409 and + * the maximum minor version is 9. + */ + major = strtou16lim(argv[1], 409); + minor = strtou16lim(argv[2], 9); + + if (argc >= 4) + snakext = argv[3]; + + mksnak(snakext != NULL, product_id, major, minor, serno, actkey); + printf("Serial number: %s\n" + "Activation key: %s\n", serno, actkey); + + if (snakext != NULL) { + mdsnakext(serno, actkey, snakext, snakextmd); + printf("License data: %s;m%s\n", snakext, snakextmd); + } + + return EXIT_SUCCESS; +} + diff --git a/snakext.c b/snakext.c new file mode 100644 index 0000000..9ffc353 --- /dev/null +++ b/snakext.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include + +#include "md5.h" +#include "h.h" + +static const char *extSecret = "\x5e\x4f\xbe\x45\x5e\x4c\x8d\x40\x9f\xeb\x26\x5e\x4f\xbe\x45\x5e\x4c\x3d\x30\x7c"; + +void +mdsnakext(const char *serno, const char *actkey, const char *snakext, + char snakextmd[static 7]) +{ + char *canon; + + canon = BSCanon(snakext); + extmd(snakextmd, 4, extSecret, serno, actkey, canon); + free(canon); +} +