Last time, we investigated part of a gaming DRM system from the early-2010s, looking at some of the configuration files. This time, we'll investigate how the licences for these games are stored.

Is is known that the licence data for the games is stored in a particular file, here denoted 2114455.lic.

The .lic file

Here's an example of a .lic file:

00000000: 302c 0214 6f46 d538 ceb9 1bcd b968 1e65  0,..oF.8.....h.e
00000010: 514d 733a c8dd 899c 0214 78e6 a720 ac9a  QMs:......x.. ..
00000020: 3a60 e433 2c98 e1b2 a614 ec2a 5f18 0000  :`.3,......*_...
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0073 60fb 1683 6aaf 3de4 1ba3 1a13 5e31  .s`...j.=.....^1
00000050: cf1e 0553 5209 51c3 7f39 eab8 3bec 9064  ...SR.Q..9..;..d
00000060: cfe2 a61e 21fc 32ff be96 6e49 69d2 7d26  ....!.2...nIi.}&
00000070: 8b0b 3d68 26b6 ed6e 0036 fd80 2e70 311f  ..=h&..n.6...p1.
00000080: d0fc bf46 d98f f126 4b92 8906 9cd5 bf72  ...F...&K......r
00000090: 7431 01cd 3846 a156 10f5 a777 85cb 0507  t1..8F.V...w....
000000a0: 098d 3aef a624 293e 1740 d905 8e74 d360  ..:..$)>.@...t.`
000000b0: 741d eaca aa21 f566 d4b6 e87e 9dc9 20a5  t....!.f...~.. .
000000c0: 973f 39b2 2c55 713f aff6 fd91 2e88 a91c  .?9.,Uq?........
000000d0: f21f 2d76 1430 8a79 1bd5 13d4 44ce 14b5  ..-v.0.y....D...
000000e0: ffba 949a 7862 85bb 2b3c 899b c0cb 5a57  ....xb..+<....ZW
000000f0: ab06 485f 71d7 00eb 8525 6a51 b6c4 9421  ..H_q....%jQ...!
00000100: 9e47 853a ed83 2abe cdcb 65e5 2b18 5081  .G.:..*...e.+.P.
00000110: c9b5 6bd2 47b7 6946 be0a 0d2b 3d4c 8391  ..k.G.iF...+=L..
00000120: 080c be1d 414b adc5 920f 01c1 92f4 e1d4  ....AK..........
00000130: f7a2 2674 a1b9 fa3c 3f51 381f a14b 2aa8  ..&t...<?Q8..K*.
00000140: e295 77bf af74 a84e eee1 7732 4f65 6ba8  ..w..t.N..w2Oek.
00000150: 2994 0fc9 6084 be27 58e6 b183 e60c 202c  )...`..'X..... ,
00000160: 259d a0d3 3220 2d68 636e e5c7 d910 1177  %...2 -hcn.....w
00000170: cf23 f7d4 fcce 7bd9 d36b ca8c 4e48 d0ff  .#....{..k..NH..
00000180: 0a5a c8b7 a6af ebd5 5963 6f48 b6b3 266a  .Z......YcoH..&j
00000190: dd8d ed6b 3984 95e3 4acd 9fc5 ca8c f6c7  ...k9...J.......
000001a0: 07a5 b529 3996 3df3 8849 a2e1 5948 d0d6  ...)9.=..I..YH..
000001b0: 26a0 b81f b2bb 947b 3213 80ca a463 577a  &......{2....cWz
000001c0: b7bc 9610 5214 df50 21bd 9997 ecb6 202b  ....R..P!..... +
000001d0: 9c42 040e 8093 508a b2ba 3cf7 435a 040e  .B....P...<.CZ..
000001e0: ef66 68ce 846c 0caa dfc2 4fb3 9207 be47  .fh..l....O....G
000001f0: 3de0 2923 a1fe 5b26 cf34 0b9a 5e30 2bbb  =.)#..[&.4..^0+.
00000200: 5b8d a5c5 ecc4 eda5 7c60 ed5f 512c 5b1f  [.......|`._Q,[.
00000210: 8825 3838 aeac 75fb a18b 7dac 351c 2d5f  .%88..u...}.5.-_
00000220: eb71 fc66 5c5e 64f2 34e8 3ca6 b027 e3cd  .q.f\^d.4.<..'..
00000230: ba12 7b8f 779b d1a7 1faa 730d 1aad db6b  ..{.w.....s....k
00000240: 5b27 fb89 a894 1bcb 9be3 ed7b ba42 2f2d  ['.........{.B/-
00000250: 81                                       .                                       .

Aside from a curious string of null bytes from 0x2e to 0x40 inclusive, there seems to be no meaning to this data at all. Some reverse engineering will again be required.

Unravelling the encryption

As before, we run the executable using WINEDEBUG=+file, which yields, among other things:

005d:trace:file:CreateFileW L"C:\\ProgramData\\FooBar Games\\FooBar Services\\License\\2114455.lic" GENERIC_READ FILE_SHARE_READ FILE_SHARE_WRITE  creation 3 attributes 0x1
005d:trace:file:RtlDosPathNameToNtPathName_U_WithStatus (L"C:\\ProgramData\\FooBar Games\\FooBar Services\\License\\2114455.lic",0x8df8b4,(nil),(nil))
005d:trace:file:RtlGetFullPathName_U (L"C:\\ProgramData\\FooBar Games\\FooBar Services\\License\\2114455.lic" 520 0x8df604 (nil))
005d:trace:file:wine_nt_to_unix_file_name L"\\??\\C:\\ProgramData\\FooBar Games\\FooBar Services\\License\\2114455.lic" -> "/home/runassudo/.PlayOnLinux/wineprefix/tmp/dosdevices/c:/ProgramData/FooBar Games/FooBar Services/License/2114455.lic"
005d:trace:file:CreateFileW returning 0xb8
005d:trace:file:ReadFile 0xb8 0x6ed228 4096 0x8df960 (nil)
005d:trace:file:ReadFile 0xb8 0x6d917d 65536 0x8df960 (nil)
005d:trace:file:ReadFile 0xb8 0x6d938d 61440 0x8df960 (nil)

And again as before, we can set a breakpoint in CreateFileW and see where this is being called from:

$ WINEDEBUG=-all winedbg FooBarBazX.exe
WineDbg starting on pid 003e
0x000000007b465d91: movl        0xffffff24(%ebp),%esi
0x000000007b465d91: movl        0xffffff24(%ebp),%esi
Wine-dbg>break CreateFileW
Breakpoint 1 at 0x000000007b442120 CreateFileW in kernel32
Stopped on breakpoint 1 at 0x000000007b442120 CreateFileW in kernel32
Wine-dbg>x/x $ecx
Wine-dbg>x/u 0x006e9650

Not the right file, so we keep continuing (for some time unfortunately) until hitting the right file.

Stopped on breakpoint 1 at 0x000000007b442120 CreateFileW in kernel32
Wine-dbg>x/x $ecx
Wine-dbg>x/u 0x006ed2b8
C:\ProgramData\FooBar Games\FooBar Services\License\2114455.lic
Wine-dbg>info reg
Register dump:
 CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:006b
 EIP:7b442120 ESP:008df90c EBP:008df988 EFLAGS:00000212(   - --  I   -A- - )
 EAX:7d335bb9 EBX:7d3d4000 ECX:008df9a0 EDX:00000040
 ESI:00008000 EDI:00000001

Now that we've identified some more information about this call, we can set a more specific breakpoint next time to avoid all this work:

$ WINEDEBUG=-all winedbg FooBarBazX.exe
WineDbg starting on pid 006e
0x000000007b465d91: movl        0xffffff24(%ebp),%esi
0x000000007b465d91: movl        0xffffff24(%ebp),%esi
Wine-dbg>break CreateFileW
Breakpoint 1 at 0x000000007b442120 CreateFileW in kernel32
Wine-dbg>cond 1 $ecx == 0x008df9a0
Stopped on breakpoint 1 at 0x000000007b442120 CreateFileW in kernel32
=>0 0x000000007b442120 CreateFileW() in kernel32 (0x00000000008df988)
  1 0x000000007d32811f MSVCRT__wsopen+0x4e() in msvcr100 (0x00000000008df9e8)
  2 0x000000007d32cd1f MSVCRT__wfsopen+0x7e() in msvcr100 (0x00000000008dfa48)

Similarly, as before, the backtrace generated by winedbg is not complete, so we manually reconstruct the program flow:

0x000000007d327cab MSVCRT__wsopen_dispatch+0x33b in msvcr100: movl      %eax,0xffffffc0(%ebp)
0x000000007d32811f MSVCRT__wsopen+0x4f in msvcr100: addl        $32,%esp
0x000000007d32cd1f MSVCRT__wfsopen+0x7f in msvcr100: addl       $16,%esp
0x0000000000425b35: addl        $12,%esp
0x0000000000406809: leal        0x60(%esp),%ecx
0x000000000040a4de: xorl        %ecx,%ecx

After a few small helper-looking functions, we arrive at a big subroutine:

0x0040a4de in IDA

Zooming in closer on where the call to CreateFileW is:

0x0040a4de in IDA

sub_4066D0 is where the call to CreateFileW is. We see that shortly after this is a call to sub_409950. Investigating further, this is a very large subroutine, with references to a number of interesting strings:

sub_409950 in IDA

Apparent debug strings referenced here identify this subroutine as FooBarDRMLicense_DRMSystemNameV4::GetLicParams. There are also helpful references to error strings like ‘License signature is invalid’, to help us understand this function.

Having a poke around, we see that soon after the ‘License signature is good’ block, there is a call to sub_407640:

sub_407640 in IDA

Alongside more interesting strings like ‘xml’ and ‘Unable to parse the license’, the debug strings in this subroutine identify it as FooBarDRMLicense_DRMSystemNameV4::Decrypt.

More importantly, there is a reference in this subroutine to unk_54F640 in the .rdata region:

unk_54F640 in IDA

It would be reasonable to presume that this is the decryption key. But what is the algorithm being used?

A small detour now to inspect the other strings in the DRMUI.exe binary.

OpenSSL strings

Poking around for more debug-related strings, we see some strings from OpenSSL, a cryptographic library. Specifically, from version 1.0.1c of the OpenSSL EVP (digital EnVeloPe) library. Based on the location of the references to these strings, the library seems to be loaded around the mid-0x45xxxx's.

With this in mind, we can return to sub_407640 and look around IDA's proximity browser to see where any references to these OpenSSL functions might be. After a lot of button-mashing, we find sub_407640sub_436880sub_4366D0sub_4595C0. It's easy to confirm that this is an OpenSSL function of interest based on the further reference to aBSizeofCtxBuf, which seems to be an assertion (b <= sizeof ctx->buf):

sub_407640 in IDA proximity browser

We can download the source code for OpenSSL 1.0.1c and search for this assertion to find out what this function is:

$ grep -R 'b <= sizeof'
openssl-1.0.1c/crypto/evp/evp_enc.c:    OPENSSL_assert(b <= sizeof ctx->buf);
openssl-1.0.1c/crypto/evp/evp_enc.c:    OPENSSL_assert(b <= sizeof ctx->final);
openssl-1.0.1c/crypto/evp/evp_enc.c:            OPENSSL_assert(b <= sizeof ctx->final);

Looking up crypto/evp/evp_enc.c, we find that the matching assertion is from EVP_EncryptFinal_ex. Fantastic! We can now label sub_4595C0 as EVP_EncryptFinal_ex.

Flicking through the documentation for OpenSSL EVP (1, 2), we know we now need to look for the call to EVP_CipherInit_ex to identify what cipher is in use:

int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
	     const unsigned char *key, const unsigned char *iv, int enc)

Thankfully, this function contains some assertions of its own:

	OPENSSL_assert(ctx->cipher->block_size == 1
	    || ctx->cipher->block_size == 8
	    || ctx->cipher->block_size == 16);

This corresponds with the aCtxCipherBlock string, which is referenced by sub_459310. We can safely conclude, then, that sub_459310 is EVP_CipherInit_ex.

$ WINEDEBUG=-all winedbg FooBarBazX.exe
WineDbg starting on pid 0095
0x000000007b465d91: movl        0xffffff24(%ebp),%esi
0x000000007b465d91: movl        0xffffff24(%ebp),%esi
Wine-dbg>break *0x459310
Breakpoint 1 at 0x0000000000459310
Stopped on breakpoint 1 at 0x0000000000459310
Wine-dbg>x/7x $esp
0x00000000008df8cc:  0043681a 006ed820 0055b928 00000000
0x00000000008df8dc:  00000000 00000000 00000001

In accordance with standard cdecl calling convention, 0x006ed820 will be the pointer to the new EVP_CIPHER_CTX, and 0x0055b928 points to the EVP_CIPHER being used.

From the OpenSSL sources:

struct evp_cipher_st
	int nid;
	int block_size;
	int key_len;		/* Default value for variable length ciphers */
	int iv_len;
	unsigned long flags;	/* Various flags */
	int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key,
		    const unsigned char *iv, int enc);	/* init key */
	int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out,
			 const unsigned char *in, size_t inl);/* encrypt/decrypt data */
	int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */
	int ctx_size;		/* how big ctx->cipher_data needs to be */
	int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Populate a ASN1_TYPE with parameters */
	int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Get parameters from a ASN1_TYPE */
	int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr); /* Miscellaneous operations */
	void *app_data;		/* Application data */
	} /* EVP_CIPHER */;

With this knowledge, we can inspect the EVP_CIPHER object being passed:

Wine-dbg>x/8x 0x0055b928
0x000000000055b928:  000001a3 00000010 00000010 00000010
0x000000000055b938:  00005002 004596c0 00459770 00000000

The first value, 0x1a3 (419), is the nid of the cipher. Looking this up in openssl-1.0.1c/crypto/objects/obj_mac.h:

#define SN_aes_128_cbc		"AES-128-CBC"
#define LN_aes_128_cbc		"aes-128-cbc"
#define NID_aes_128_cbc		419
#define OBJ_aes_128_cbc		OBJ_aes,2L

Aha! So the cipher being used is AES-128-CBC.

Looking again at the stack, though, both the pointers to the key and IV are NULL. OpenSSL EVP allows these to be set in future calls to EVP_CipherInit_ex, so let's keep going:

Stopped on breakpoint 1 at 0x0000000000459310
Wine-dbg>x/7x $esp
0x00000000008df8d0:  0043685d 006ed820 00000000 00000000
0x00000000008df8e0:  006ed8d0 006edd2c ffffffff
Wine-dbg>x/16b 0x006ed8d0
0x00000000006ed8d0:  12 34 56 78 90 12 34 56 78 90 12 34 45 67 90 12
Wine-dbg>x/16b 0x006edd2c
0x00000000006edd2c:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

With this, we can confirm that the encryption key is as discovered earlier, and the IV is all zeroes. (Example key shown above.)

Decrypting the data

Through some trial and error, we discover that the encrypted data in the .lic file begins immediately after the string of null bytes, and continues to the end of the file. The data at the beginning is probably a header (the code mentions a signature, which may be located here).

Decrypting the data reveals an XML payload. Adding some whitespace for readability:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<License xmlns="http://foobar/license">

This plaintext payload is followed by 0x10 bytes of 0x10, apparently as PKCS#7-style padding.

Investigating the signature

This leaves us to work out what the 0x41 bytes at the beginning of the file are for, which we suspected may be a signature.

Returning to the function identified as FooBarDRMLicense_DRMSystemNameV4::GetLicParams, the function references an error message ‘Invalid signature block’, which is based on checking the return value of sub_457900. This function in turn calls sub_462670sub_461C50. Examining this function:

sub_457900 in IDA

This is a very large and complex function with a large jump table. Most debugging information has been removed (there are many references to the string file_name_removed), but there are references to the strings , Type=, Type= and Field=. Looking for these in our OpenSSL source code:

$ grep -R '", Type="'
openssl-1.0.1c/crypto/asn1/tasn_dec.c:                                  ", Type=", it->sname);

This line is from ASN1_item_ex_d2i, which is indeed a big function with a big switch statement in it, explaining the jump tables. This function is from the OpenSSL library for decoding ASN.1 DER-encoded data. Returning to the .lic file from earlier, a byte 0x30 indicates that this encodes a SEQUENCE. We can now decode the header from our .lic file to reveal:

>>> from asn1crypto.core import Sequence
>>> asn = Sequence.load(data[:0x41])
>>> asn
<asn1crypto.core.Sequence 140345494302224 b'0,\x02\x14oF\xd58\xce\xb9\x1b\xcd\xb9h\x1eeQMs:\xc8\xdd\x89\x9c\x02\x14x\xe6\xa7 \xac\x9a:`\xe43,\x98\xe1\xb2\xa6\x14\xec*_\x18'>
>>> asn.debug()
  asn1crypto.core.Sequence Object #140345494302224
    Header: 0x302c
      constructed universal tag 16
    Data: 0x02146f46d538ceb91bcdb9681e65514d733ac8dd899c021478e6a720ac9a3a60e4332c98e1b2a614ec2a5f18
>>> asn[0]
<asn1crypto.core.Integer 140345481480176 b'\x02\x14oF\xd58\xce\xb9\x1b\xcd\xb9h\x1eeQMs:\xc8\xdd\x89\x9c'>
>>> asn[1]
<asn1crypto.core.Integer 140345481503072 b'\x02\x14x\xe6\xa7 \xac\x9a:`\xe43,\x98\xe1\xb2\xa6\x14\xec*_\x18'>

So the header of the .lic file is a DER-encoded SEQUENCE of two integers.

Returning to FooBarDRMLicense_DRMSystemNameV4::GetLicParams, the function also references an error message ‘License signature is invalid’, which is based on sub_457180:

sub_457180 in IDA

The destination for where this subroutine calls is determined dynamically, so time to fire up the debugger again:

$ WINEDEBUG=-all winedbg FooBarBazX.exe
WineDbg starting on pid 0031
0x000000007b466051: movl        0xffffff24(%ebp),%esi
0x000000007b466051: movl        0xffffff24(%ebp),%esi
Wine-dbg>break *0x409a89
Breakpoint 1 at 0x0000000000409a89
Stopped on breakpoint 1 at 0x0000000000409a89
0x0000000000457180: movl        0x10(%esp),%eax
0x0000000000457184: movl        0x3c(%eax),%ecx
0x0000000000457187: movl        %eax,0x10(%esp)
Wine-dbg>info reg
Register dump:
 CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:006b
 EIP:00457187 ESP:008df984 EBP:00000000 EFLAGS:00000202(   - --  I   - - - )
 EAX:006ed560 EBX:006d92ad ECX:00596d18 EDX:006ed7a0
 ESI:006d92ac EDI:006ed560

We can see that ecx is now 0x00596d18. This corresponds with some data from the binary:

0x00596d18 in IDA

This references a string, ‘OpenSSL DSA method’. So it looks like the signature scheme being used is DSA. This is consistent with the DER-encoded header containing two integers as the signature.

Searching for this string in the OpenSSL source:

$ grep -R '"OpenSSL DSA method"'
openssl-1.0.1c/crypto/dsa/dsa_ossl.c:"OpenSSL DSA method",
openssl-1.0.1c/crypto/dsa/dsa_ameth.c:          "OpenSSL DSA method",

Looking into openssl-1.0.1c/crypto/dsa/dsa_ossl.c, we find:

static DSA_METHOD openssl_dsa_meth = {
"OpenSSL DSA method",
NULL, /* dsa_mod_exp, */
NULL, /* dsa_bn_mod_exp, */

This corresponds perfectly with the data from the binary! We note now that sub_457180 jumps into the 4th entry in this struct, corresponding with dsa_do_verify. The declaration of this function is:

static int dsa_do_verify(const unsigned char *dgst, int dgst_len, DSA_SIG *sig, DSA *dsa);

So if we inspect the stack:

Wine-dbg>x/4x $esp
0x00000000008df988:  008dfac0 00000014 006ed7a0 006ed560

This tells us that the function is calling to check a 0x14 = 20 byte (160-bit) hash at 0x008dfac0, using the DSA object at 0x006ed560. Inspecting the hash:

Wine-dbg>x/20b 0x008dfac0
0x00000000008dfac0:  b0 1b cd 00 1e 75 02 7a 6c ac 26 6d 34 af cf b0
0x00000000008dfad0:  e7 5f 01 b7

We can quickly verify that this corresponds with the SHA1 hash of the .lic file contents (without the first 0x41 bytes, of course).

Now looking at the contents of the DSA object:

Wine-dbg>x/16x 0x006ed560
0x00000000006ed560:  00000000 00000000 00000001 006ed0a0
0x00000000006ed570:  006ed4d0 006ed650 006ed6f8 00000000
0x00000000006ed580:  00000000 00000000 00000001 00000000
0x00000000006ed590:  00000001 00000000 006d00c8 00596d18

Comparing this with the definition of the DSA struct:

struct dsa_st
	/* This first variable is used to pick up errors where
	 * a DSA is passed instead of of a EVP_PKEY */
	int pad;
	long version;
	int write_params;
	BIGNUM *q;	/* == 20 */

	BIGNUM *pub_key;  /* y public key */
	BIGNUM *priv_key; /* x private key */

	BIGNUM *kinv;	/* Signing pre-calc */
	BIGNUM *r;	/* Signing pre-calc */

	int flags;
	/* Normally used to cache montgomery values */
	BN_MONT_CTX *method_mont_p;
	int references;
	CRYPTO_EX_DATA ex_data;
	const DSA_METHOD *meth;
	/* functional reference if 'meth' is ENGINE-provided */
	ENGINE *engine;

We can see, then, that 0x006ed6f8 is a pointer to the BIGNUM representing the public key, and 0x006ed0a0, 0x006ed4d0 and 0x006ed650 the p, g and q of the group. We can now easily print these memory locations to identify the parameters used to verify the DSA-SHA1 signature used in the .lic file.

Next part

Investigating this MachineHash value