This commit is contained in:
Neo Anderson committed 2020-04-11 22:05:53 -07:00
1 parent 1eba2934d3
commit a2e0206604
15 files changed
+1311 -2

No files matched your search

+29
View File
@@ -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
+46
View File
@@ -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.
-2
View File
@@ -1,2 +0,0 @@
# sco-breaker
Generates keys for an aptly named Operating System
+274
View File
@@ -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.
BIN
View File
Binary file not shown.
+160
View File
@@ -0,0 +1,160 @@
.Dd January 27, 2018
.Dt SB 1
.Os
.
.Sh NAME
.Nm sb
.Nd generate serial numbers, activation keys and registration keys
.
.Sh SYNOPSIS
.Nm
.Ar product_id
.Ar major_ver
.Ar minor_ver
.Op Ar license_data
.Nm
.Fl r
.Ar serial_number
.Ar host_id
.Nm
.Fl r
.Ar registration_lock
.
.Sh DESCRIPTION
.Nm
generates serial numbers, activation keys and registration keys for SCO
products.
SCO products from 1992 and earlier may not be compatible with the licensing
information generated by
.Nm .
.Pp
During installation of an SCO product, you will be prompted for at least a
serial number and an activation key.
This information can be generated by calling
.Nm
.Em without
.Fl r .
In order to generate a serial number and activation key
.Dq ( SNAK ) ,
you need the correct
product ID, major version and minor version.
The product ID is an SCO-internal integer that denotes the licensed product.
The major and minor versions denote the version of the licensed product.
.Sy This may not be the same as the version the product is marketed as .
For example, UnixWare 7.1.4 has major version 71 and minor version 4.
The distinction between 7.1.4, 7.1.4+ and 7.1.4 Definitive is made through the
product ID, rather than the version fields.
.Pp
On an SCO UNIX installation, you can use
.Cm /etc/brand -d
to list all known product IDs for that installation.
Usually, there is also a pre-installation and post-installation script bundled
with the SCO product that shows the specific expectations for product ID and
version values;
search those scripts for
.Dq Cm brand -Q .
.Pp
Optionally, you can supply
.Em license data .
License data is a semicolon-delimited list of fields that specify various
aspects about the license in question.
Each field consists of a character and then a value, e.g.
.Dq c4;u100
denotes a license for four CPUs and 100 users.
License data may also only consist of a single field.
The following characters are known:
.Bl -tag -width Ds
.It Cm c
Number of CPUs.
.It Cm d
Expiry time of the license in days.
This is used for evaluation licenses.
.It Cm 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.
.It Cm u
Number of users.
.It Cm m
Checksum over the other fields.
.Sy You must not supply this value .
.El
.Pp
The characters g, q and x have also been observed, but their effect is
unknown.
.Sy This program does not check license data is not checked for validity .
.Sy You must order the license data alphabetically .
.Pp
After installation, you may also be asked for a
.Em registration key
for continued operation.
.Nm
will generate a registration key if the
.Fl r
flag is passed.
You need to supply either the serial number of the product and the host ID, or
a valid registration lock.
The host ID differs for every installation, even for the same product and
serial number.
If you have forgotten the serial number of the product, it can be found with
.Cm /etc/brand -L .
The host ID can be found with
.Pa /etc/brand Fl I .
If you supply a registration lock, please be aware that it contains semicolons
(;).
You will have to quote it to prevent the shell from parsing the semicolons as
command delimiters.
.
.Sh EXIT STATUS
.Ex -std
.
.Sh EXAMPLES
Generate a new serial number and activation key for UnixWare 7.1.4 Definitive
2018 (product ID 203, version major 71, version minor 4) with no license data:
.Bd -literal -offset indent
$ sb 203 71 4
.Ed
.Pp
Register a product for your host:
.Bd -literal -offset indent
$ /etc/brand -I
orxrrwjwxz
$ sb -r SCO539702 orxrrwjwxz
.Ed
.
.Sh DIAGNOSTICS
.Bl -diag
.It "%s not a number"
The supplied product ID, major version or minor version is not actually a
number.
.It "%s out of range (max %u)"
The supplied product ID, major version or minor version is out of range.
If you are
.Em absolutely certain
that you have the correct parameters, please contact the author to fix the
limit.
The limits are theoretical maximum limits, not ones observed in use by SCO.
.It "malloc"
Memory allocation has failed.
If you had enough memory to display this manual page, you can probably just
try again.
You may need to check
.Pa /etc/malloc.conf
if the issue persists.
.It "internal: limit > 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.
BIN
View File
Binary file not shown.
+24
View File
@@ -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
+246
View File
@@ -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 <sys/types.h>
#include <string.h>
#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<<s | 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;
}
+36
View File
@@ -0,0 +1,36 @@
#include <stdint.h>
/* $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_ */
+72
View File
@@ -0,0 +1,72 @@
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#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';
}
+147
View File
@@ -0,0 +1,147 @@
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
+71
View File
@@ -0,0 +1,71 @@
#include <libgen.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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;
}
+183
View File
@@ -0,0 +1,183 @@
#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#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;
}
+23
View File
@@ -0,0 +1,23 @@
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}