Code Archaeology with ElanLM
Reviving functions from the past

Jan 2001
by pilgrim
Courtesy of Fravia's page of reverse engineering
slightly edited
by +Tsehp
There is a crack, a crack in everything That's how the light gets in
( )Beginner (x)Intermediate ( )Advanced ( )Expert

Using historic Unix object code to reveal function names within Intel-based ElanLM code.
Analysing the ElanLM licensing code, applying to SPSS products to give full non-expiry keys.

Code Archaeology with ElanLM
Reviving functions from the past
Written by pilgrim

This started as a natural progression from the FlexLm and SentinelLM analyses.
Both securities were based, back in the dark old days of 1995, on the ElanLm from Elan Computing Inc.
But 6 years is a long time on the web, and the project turned more into an archaelogical dig.

Tools required
SoftICE 4.05
Icedump 6.021
Ida 4.0.4
Hiew 6.40
Quine's IDA IDC to convert IDB to PAT

Target's URL/FTP
Various SPSS products, most notibly SPSS 10.1 base, all available from

Program History
Hmm, the grandfather of both FlexLM and SentinelLM, dates back to 1995 and further.

Initial research
First thing to do with a non-trivial crack is get a thorough grounding in the targets licensing 
mechanism. Search the web to the best of your ability.
Unfortunately, can reveal no clues since the take-over by sentinel,
but many people have used ElanLM in the past.
There are many traces of ElanLM scattered over the web - install instructions, tech notes, 
marketing docs.
I came up with a good amount of data: some function prototypes; 
a simplistic explanation of how to incorporate the licensing code into an application;
some UNIX object code containing ElanLM code;
and many, many license codes for various SPSS products - most, but not all, of which had expired.

Initial install attempt
Let's start with the SPSS10.1base demo, available from the SPSS ftp server.
It's a single compressed installshield EXE, so clean out your windows/temp folder, start the 
installation process and watch the files appear in your temp folder.
Copy these and scrap the original EXE, we can use the decompressed setup.exe to re-install.

Have a look in the setupntr.inf and we see:

PrestampedName=Evaluation Version
PrestampedLicense=47142 99766 32600 66848 08170 26408 52332 722

Here we see the SPSS version, 10.1, and default install data.
We can comment all of these out and the installer will ask us for the info.
The installer will then check the key validity and let us know what, if anything, we've licensed.
This is useful for key-testing later.. 
The installer shows us that the license key shown expires 1/1/2001 

Once installed have a look around the files and we see traces of the ELanLM code.
Dissassemble, and we don't get much - no Elan function names and not a lot to help us.
We do see some *.lic files which is where the ElanLM stores it's licensing info.
Initial research shows that the filename is something like 20.lic where 20 is a feature name.
Host-locking data and time data is stored encrypted in the lic files to prevent tampering and time 
manipulation expiry avoidance techniques.

The initial research revealed that a program supplied by SPSS called licrenew.exe
can be used to renew licenses - this sounds just what we want, 
and it can be found on the SPSS ftp server.

Pass our prestamped license key into licrenew.exe and we get the same license file - 
so it seems to work.
Pass garbage in and it complains.
Dissassemble and we still don't see much. 
How to get some useful Elan function names? ...

UNIX object code analysis

In my initial research I found some UNIX object code containing ElanLM code.
It looks like some old graphics library called CAVE used Elan and so required
the user to link in the Elan library to their final release code.

If we dissassemble the code with IDA we don't get Intel assembler, 
but we do get lots of Elan function names, constant strings and data.
We also see some filenames, embedded in the code.
If we get IDA to generate a MAP file, we can see all the functions, for example, the first few:

 0002:00029F80       elm_message
 0002:0002A594       elm_valid
 0002:0002AF20       elm_numkeyhostid
 0002:0002B010       elm_shorthostid
 0002:0002B0E0       elm_hostxtod
Now, have a look at the object code with your favourite hex editor.
Way past the function names for elm_message etc we see file headers, for example:

@(#)message.c	1.24 5/2/95 Copyright (c) 1995 Elan Computer Group, Inc
$Id: valid.c 1.15 1995/12/01 22:04:30 von Exp $ ELAN
@(#)nkhost.c	1.5 5/2/95 Copyright (c) 1995 Elan Computer Group, Inc.
@(#)shostid.c	1.5 5/2/95 Copyright (c) 1995 Elan Computer Group, Inc.
@(#)hostxtod.c	1.11 5/2/95 Copyright (c) 1995 Elan Computer Group, Inc.

Now it seems a fair assumption to me that the elm_* functions are in the same order
as the order of the files compiled into the library.

So now what you can do is take your MAP file and guess which files the functions came from.
At the same time I recorded the function sizes.

What for? Well..

Apply the UINX file information to the Intel EXE

Looking at licrenew.exe with our favorite hex editor, we don't see function names, 
but we DO see the same file headers, constant string and data as in the UNIX object code.

So now we can start taking intelligent guesses to name the function in licrenew.exe
Approaches I took were: 
 functions are grouped together in the same files;
 function sizes will be in similar proportions in the Intel and Unix code;
 debug printfs can indicate function names, or variable names;
 error messages are grouped in numeric order;

This is where the Interactive part of IDA comes into it's own - 
functions, variables, constants can be renamed as we attempt to find their true identity.

For example, we see

		push	offset aElm_hostidGeth ; "elm_hostid: gethostname failed"

So it's a fair assumption that the function this code is in is elm_hostid().
This is in hostid.c, surrounded by elm_ether() and elm_pick_hostcode().

Note that there are some differences - Unix sockets are replaced by Winsock for example.

Perservere, dear cracker, and you'll end up with most functions identified.

ElanLM FLIRT Signatures

Quine has an IDA IDC to convert IDB to PAT files which then be used to generate a SIG file.
This needs modifying for IDA 4.04:
for 4.04+ find/change this line to this (AskFile() has changed a bit):
pat_name = AskFile(1,"*.pat", "Choose where to save the .pat file:");
( Thanks to the owl, Fravias message board )

So take our licrenew.exe in IDA and run the IDC script - we get a sig file.
Apply to another EXE containing elm code and we see the functions - the sig works well.

Softice Symbols

Get IDA to generate a MAP file
Use softices util16\msym.exe to convert this into a sym file
Use symbol loader to get it into softice.
( file open module, open our SYM file, then module load ) 
Ctrl-D into softice and do a sym to see all our lovely function names.


The analysis below makes use of IceDump a lot.
First ensure that softice is loading the kernel32 symbols,
then run the version of IceDump to match your Softice version.
Get your softice screen as required and 

/SCREENDUMP [filename]

to dump your screen to a file.
An excellent tool - thanks guys.

ElanLM code analysis

Finally! time for some code analysis. Let's start with licrenew.exe.

Enter our eval license for SPSS10.1 base and watch the program roll..

In Softice, bpx _main

:00401013 689AA10000              push 0000A19A <- "Enter license code:"
:00401018 E877040000              call 00401494
017F:00401040  CALL      _fgets <- this gets the licence string entered

We see the licence string being cleaned up - white space removed
And here we see that the first number must be 3 or 4 - I use 4 for subsequent work:

:00401B34 8A02                    mov al, byte ptr [edx]
:00401B36 3C33                    cmp al, 33 <- '3'
:00401B38 7408                    je 00401B42
:00401B3A 3C34                    cmp al, 34 <- '4'
:00401B3C 0F85AA000000            jne 00401BEC

We then see elm_key() being called, which in turn calls elm_unbuildkey():

:00406D71 50                      push eax <- 0
:00406D72 51                      push ecx <- points to empty space
:00406D73 52                      push edx <- license string without the first '4'
:00406D74 E827530000              call 0040C0A0 <- elm_unbuildkey
:00406D79 83C40C                  add esp, 0000000C <- eax = ffffff2 = Code or license key corrupt Error
:00406D7C C3                      ret

so elm_unbuildkey analyses and separates the big key entered.

Let's look at this:

First clear some structure entries, then:
:0040C10D 50                      push eax <- license string without the initial '4'
:0040C10E 6A01                    push 00000001
:0040C110 E89BFAFFFF              call 0040BBB0 <- elm_kiko

This returns eax pointing to zeros if we entered a random key, 
or a DECRYPTED key for the eval license.

For our eval key we see: 3009810396900134041123456101160

Key encryption

Above we see elm_kiko() decrypts the key. 
If we see where else this is called we find within the big elm_genmultikey() function:

loc_4078D4:				; CODE XREF: elm_genmultikey+B18
		lea	eax, [esp+468h+var_200] <- string to encrypt
		push	eax 
		push	0 <- encrypt, not decrypt
		call	elm_kiko
		add	esp, 8
		test	eax, eax <- did we encrypt OK?
		jz	short loc_40790D ; No: Error: unable to encrypt - invalid characters

Our helpful error message shows this was an attempt to encrypt - 
passing 0 into elm_kiko is encrypt, a 1 is decrypt.

So if we enter our eval key into licrenew, bpx on elm_unbuildkey() as before,
then just before the call to elm_kiko() modify the [eax] license key to our decrypted key, 
and mod the push 1 to push 0, call elm_kiko(), and we get our original encrypted eval key.
So our assumptino is correct, that we can encrypt as well as decrypt with elm_kiko().

How about we modify licrenew.exe to just pass a string through kiko for us?
Take a copy of licrenew.exe and call it something else, I used kiko.exe.
Starting at the _main() function, we modify the code as shown
by the lines starting with >

:00401000 55                      push ebp
:00401001 8BEC                    mov ebp, esp
:00401003 81ECA00D0000            sub esp, 00000DA0
:00401009 53                      push ebx
:0040100A 56                      push esi
:0040100B 57                      push edi
:0040100C 8D859CFEFFFF            lea eax, dword ptr [ebp+FFFFFE9C]
:00401012 50                      push eax
:00401013 689AA10000              push 0000A19A <- "Enter license code:"
:00401018 E877040000              call 00401494
:0040101D 83C408                  add esp, 00000008
:00401020 8D859CFEFFFF            lea eax, dword ptr [ebp+FFFFFE9C]
:00401026 50                      push eax
:00401027 E8C4F10000              call 004101F0 <- printf
:0040102C 83C404                  add esp, 00000004
:0040102F 68501F4200              push 00421F50
:00401034 6803010000              push 00000103

:00401039 8D8574F6FFFF            lea eax, dword ptr [ebp+FFFFF674]
:0040103F 50                      push eax <- storage space for the string to be entered
:00401040 E85BF30000              call 004103A0 <- gets()
:00401045 83C40C                  add esp, 0000000C
; This code parses the input string for the 0x0A we get at the end of a string 
; when we enter a string from DOS
; elm_kiko() wants a null-terminated string without the 0x0A
; so we search the entered string for 0x0A, replace it with a null-terminator
; then move onto elm_kiko.
; Note that elm_kiko can't cope with white-space, or non-numeric ASCII chars,
; so we must be careful when entering our key to encrypt, or we could do
; some extra string manipulation here
>00401048 8BD8                    mov ebx, eax
>0040104A 8A0B                    mov cl, byte ptr [ebx]
>0040104C 80F900                  cmp cl, 00
>0040104F 7E14                    jle 00401065
>00401051 80F90A                  cmp cl, 0A
>00401054 7509                    jne 0040105F
>00401056 B100                    mov cl, 00
>00401058 880B                    mov byte ptr [ebx], cl
>0040105A E906000000              jmp 00401065
>0040105F 43                      inc ebx
>00401060 E9E5FFFFFF              jmp 0040104A

>00401065 50                      push eax <- our null-terminated key
>00401066 6A00                    push 00000000 <- encrypt
>00401068 E843AB0000              call 0040BBB0 <- elm_kiko()
>0040106D 83C408                  add esp, 00000008
>00401070 50                      push eax <- the encrypted string
>00401071 6880F04100              push 0041F080 <- "%s"
>00401076 E875F10000              call 004101F0 <- printf
>0040107B 83C408                  add esp, 00000008
>0040107E E905040000              jmp 00401488 <- exit licrenew gracefully

We could apply a similar technique to get licrenew to printf the decrypted key for us -
easier than using softice all the time.

Key analysis

We have 3 0 0981 0396 9001 340 41 123456 1011 60 from our eval key.
I decrypted all the other example keys just to aid analysis.
We can keep entering our keys into the SPSS setup, hit update and see what's been licensed.

We immediately see that 41 123456 1011 60 is vendordata - it's used by SPSS, not ElanLM
We always have 41 to start with.
123456 is the serial number
1011 is version major/minor = 10.1.x
The last 60 is something to do with license type: beta/expiry etc. 
Even with a non-expiry Elan license, with 60 SPSS demo still expires, 
setting this to 00 ensures SPSS doesn't expire.

Playing around, we find that the 9001 is installation date. 
If we set this to 0000 the feature is not installed.
We can leave this at 9001 which is fine.

Don't know what the 0981 is - maybe another date? Leave it alone and all works fine.

The 0396 is expiry date - set this to 0000 and we don't expire.

340 is the feature number. 
If we change this to 341, encrypt using kiko,
and enter into the license check during setup we see SPSS tables is now enabled, 
and we get 341.lic created.
We can repeat this for features 340 to 349 and get 10 keys which will enable all features.

An example, for SPSS base:

kiko() gives

We can modify the setupntr.inf to 
and we get a non-expiry install for SPSS base.
Remember to add the initial 4 to the encrypted key.

Multikey generation, variable feature length 

We want to enable ALL features in one license key - not one at a time, 
and some of the example licenses show us how.

For example, a decrypted SPSS8 license:

5 7 1 0981 4503 0999 20 0999 0999 0999 0999 0999 0999 0999 0999 41 123456 0891 00

Here we see 9 install dates, max/min of 8.9.x, a feature code of 20.

Let's analyse elm_unbuildkey to see what the first few bytes of the key show.
I stepped through this function a few times with different example licenses until 
it became clear what was happening:

elm_unbuildkey	proc near		; CODE XREF: elm_key+34p
var_129		= byte ptr -129h
kiko[0]		= dword	ptr -128h
var_124		= dword	ptr -124h
var_120		= dword	ptr -120h
num_features	= dword	ptr -11Ch
lic_num_size    = dword	ptr -118h
loc_40C4AB:				; CODE XREF: elm_unbuildkey+402j
		mov	eax, [kiko[0]]
		cmp	byte ptr [eax],	35h <- First key byte = '5'
		jnz	short loc_40C4C4
		movsx	eax, byte ptr [eax+1] <- get next key byte
		add	[kiko[0]], 2 <- skip past the 1st 2 bytes
		sub	eax, 2Eh 
		mov	[num_features], eax <- store number of features

The initial key byte value of 8 through to 5 are optional pre-processed options. 
5 sets the number of features in the license.
Then we process the 'basic' key bytes 4 through to 1.

loc_40C4C4:				; CODE XREF: elm_unbuildkey+412j
		mov	eax, [kiko[0]] <- get the next key byte
		movsx	eax, byte ptr [eax]
		sub	eax, 31h	; switch 4 cases 
		cmp	eax, 3
		ja	short loc_40C4DA ; default - returns an error
		jmp	ds:off_40C8C0[eax*4] ; switch jump

loc_40C4EA:				; CODE XREF: elm_unbuildkey+433j
					;	case 0x31 
		mov	[lic_num_size], 2 ; <- store the number of digits in the lic feat
		mov	esi, 1
		add	[kiko[0]], esi <- skip to the next key byte
		jmp	short loc_40C567

loc_40C522:				; CODE XREF: elm_unbuildkey+433j
					; DATA XREF: .text:0040C8C0o
		mov	eax, [kiko[0]]	; case 0x33
		mov	esi, 1
		movsx	eax, byte ptr [eax+1] <- get the byte after the '3'
		add	[kiko[0]], 2 <- skip 2 key bytes
		sub	eax, 2Dh <- value 0  = 3, value 9 = 6
		mov	[lic_num_size], eax <- store the number of digits in the lic feat

So here we see the first 5 specifies a pre-processor option to set the number of features
in the licencse.
The 2nd '7' specifies there are to be 9 licences in total
The 3rd '3' specifies we want to set the number of bytes in the feature number
The 4th '0' equates to 3 bytes in the feature number

If the 3rd number is '1' then we use a 2-digit feature number

So we can now generate a new SPSS10 license:

5 = set number of features
8 = 10 features in total ( we'll need 10 start dates, one before the base feature,
the rest after ) 
3 = specify number of digits in the feature
0 = 3 digits in the feature

5 8 3 0 0981 0000 9001 340 9001 9001 9001 9001 9001 9001 9001 9001 9001 41123456101100
gives 222282608671903167996825549038212929714167861036383605982050880007377471204
or, in the setupntr.inf,

Other SPSS product examples, finding the necessary feature information

For a valid license we need to specify the correct: serial number, feature names, max/min version.
In most cases these are easy to find as a default demo license is stored somewhere.
In some cases they're nowhere to be seen, but we can still find them.
I've included a few more SPSS products as examples:


In setup.inf we have PrestampedLicense=49485 54156 08930 52050 84840 79032 15097 638
Decrypts to 3 1 0981 0396 9001 9820 41 000001 0501 60                    

Feature is 9820, max/min versions are 05/01
So we want to modify the last 60 to 00 so we're not a demo, and mod the 0396 so we don't expire

3 1 0981 0000 9001 9820 41000001050100                    

SPSS smart viewer 8.01

No obvious keys found. Run setup and it asks for a license key.
Try the SPSS 8.0 License: 468049156968732379626154602660600787424952101346701081234801981928987

Filemon shows us:

So Setupsup.dll ( which contains elan code ) makes 20.lit, which I think is really 20.lic,
as the SPSS 8.0 license we used had features 20 to 29.

Later we see:

So the base feature is 9620

Looking in setup.inf we see:
RegistryRoot=SPSS Viewer\8.0

So the revision in the vendor ID is 0801

5 8 3 1 0981 0000 9001 9620 9001 9001 9001 9001 9001 9001 9001 9001 9001 41123456080100
kiko gives 046191825875167947935245019779276161192875682000862007970152571542109479130

Sys Stat 10

It installs OK, with a license, feature number 59880.lic
If we have a look in windows\temp whilst installing, we see in value.shl:
PRESEEDED_LICENSE=40878 77246 26891 47740 93000 62535 09778 59948

licrenew gives: 3 2 0981 0396 9001 59880 41 314159 1001 60
OK, we see an expiry date, 0396, demo key at the end = 60 and feature is 59880

modify to 3 2 0981 0000 9001 59880 41314159100100
kiko gives 251251566587714581702049453290517566975
So try 4251251566587714581702049453290517566975

To finish the crack, we'll need to modify value.shl in
Setup is Install shield
So let's get busy with i5comp: decompress the cab, modify value.shl, recompress the cab file.
And we're done - no demo, no expiry.

SPSS10 off the internet

Just found this on an ftp server somewhere. No file_id.diz, no NFO.

PrestampedLicense=30074 30750 07417 16797 48104 5071
Decrypts to 5 2 1 0981 0000 9001 00 0000 9001 9001 41 657180 100120

2nd feature, 01.lic, won't be installed as the install date is 0000
And this only licenses 3 options. Hmmm, not all there?

OK, let's try with our previous SPSS 8 license with a modified major/minor version,
feature, and number of features:

5 8 1 0981 0000 0999 00 0999 0999 0999 0999 0999 0999 0999 0999 0999 41 123456 1001 20
kiko gives: 5311719179070095658993395656839187074789471947090477410971955008341900857
And that's everything licensed now.

Final Notes
A surprisingly simple licensing mechanism to crack, once we've got the function names sorted out.
Seems strange to include the encryption as well as decryption code in release code, but who's complaining? Again, thanks to the good guys out there in internet land who continue to share the knowledge. ... still looking for the light ...


Ob Duh
I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell, don't come back.

You are deep inside Fravia's page of reverse engineering, choose your way out:

redhomepage redlinks redsearch_forms red+ORC redhow to protect redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_Fravia+
redIs reverse engineering legal?