Code Archaeology with ElanLM
Reviving functions from the past

Jan 2001

by pilgrim


( )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.


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 SPSS 10.1 base 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, 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 :-

_main(): ...
:00401013 push 0000A19A <- "Enter license code:"
:00401018 call 00401494 ...
: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 mov al, byte ptr [edx]
:00401B36 cmp al, 33 <- '3'
:00401B38 je 00401B42
:00401B3A cmp al, 34 <- '4'
:00401B3C jne 00401BEC

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

elm_key(): ...
:00406D71 push eax <- 0
:00406D72 push ecx <- points to empty space
:00406D73 push edx <- license string without the first '4'
:00406D74 call 0040C0A0 <- elm_unbuildkey
:00406D79 add esp, 0C <- eax = ffffff2 = Code or license key Corrupt Error.
:00406D7C ret

So elm_unbuildkey analyses and separates the big key entered. Let's look at this: elm_unbuildkey() : First clear some structure entries, then :-

:0040C10D push eax <- license string without the initial '4'
:0040C10E push 1
:0040C110 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 assumption 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 push ebp
:00401001 mov ebp, esp
:00401003 sub esp, 00000DA0
:00401009 push ebx
:0040100A push esi
:0040100B push edi
:0040100C lea eax, dword ptr [ebp+FFFFFE9C]
:00401012 push eax
:00401013 push 0000A19A <- "Enter license code:"
:00401018 call 00401494
:0040101D add esp, 8
:00401020 lea eax, dword ptr [ebp+FFFFFE9C]
:00401026 push eax
:00401027 call 004101F0 <- printf
:0040102C add esp, 4
:0040102F push 00421F50
:00401034 push 00000103
:00401039 lea eax, dword ptr [ebp+FFFFF674]
:0040103F push eax <- storage space for the string to be entered
:00401040 call 004103A0 <- gets()
:00401045 add esp, 0C

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 mov ebx, eax
:0040104A mov cl, byte ptr [ebx]
:0040104C cmp cl, 00
:0040104F jle 00401065
:00401051 cmp cl, 0A
:00401054 jne 0040105F
:00401056 mov cl, 00
:00401058 mov byte ptr [ebx], cl
:0040105A jmp 00401065
:0040105F inc ebx
:00401060 jmp 0040104A
:00401065 push eax <- our null-terminated key
:00401066 push 0 <- encrypt
:00401068 call 0040BBB0 <- elm_kiko()
:0040106D add esp, 8
:00401070 push eax <- the encrypted string
:00401071 push 0041F080 <- "%s"
:00401076 call 004101F0 <- printf
:0040107B add esp, 8
:0040107E 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 :-

3009810000900134041123456101100 kiko() gives 7088280247423058332415325997637894495

We can modify the setupntr.inf to PrestampedLicense=47088280247423058332415325997637894495 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:0040C8C0
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 license. 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
22282608671903167996825549038212929714167861036383605982050880007377471204 or, in the setupntr.inf, PrestampedLicense=4222282608671903167996825549038212929714167861036383605982050880007377471204

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 :
Filemon shows us: _ins0432 Read C:\WINDOWS\TEMP\_ISTMP2.DIR\SETUPSUP.DLL _ins0432 Open C:\WINDOWS\TEMP\_ISTMP2.DIR\LICENSES\20.LIT 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: _ins0432 Open C:\WINDOWS\TEMP\_ISTMP2.DIR\LICENSES\9620.LIC NOTFOUND So the base feature is 9620.

Looking in setup.inf we see: RegistryRoot=SPSS Viewer\8.0 VersionMajor=8 VersionMinor=0 VersionPatch=1 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 5831098100009001962090019001900190019001900190019001900141123456080100 kiko gives 046191825875167947935245019779276161192875682000862007970152571542109479130 PrestampedLicense=4046191825875167947935245019779276161192875682000862007970152571542109479130

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 InstallShield 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. PrestampedSerialNo=657180 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 ... pilgrim.