Flexlm v6.1
lc_new_job()
flexlm
flexlm
26 September 1999
by dan
Courtesy of Fravia's page of reverse engineering
fra_00xx
98xxxx
handle
1100
NA
PC
A great essay. A good reverser throws some light in a very 'obfuscated' prortection scheme, and explains his reversing work in a fascinating style. A must for all those that want to learn how you should tackle a complicated reversing job.
There is a crack, a crack in everything That's how the light gets in
Rating
( )Beginner (X)Intermediate ( )Advanced ( )Expert

"lc_init() should only be used with license generators, and should not normally be used in applications shipped to clients. Use lc_new_job() instead, as it offers enhanced security." -- flexlm reference manual - v6.1
Flexlm v6.1
lc_new_job()
Written by dan


Introduction

There are now quite a few essays written on flexlm.  These essays should be read before

reading this one.  This essay might help you if you are trying to get encryption seeds for

a target that uses flex v6.1 and you can't get them using the methods in previous essays.

That was where I was before doing the things in this essay.  If I would have read this

essay back then, it would have helped me.

I have tried to show not only what I found, but how I found it.  The how is maybe

less relavent to this subject.  The how is nothing special - these are common techniques.

You probably could have found this stuff out on your own without having to know my methods.

But maybe you will find some technique I used helpful for your cracking in general.

I just tried to write something that I would have enjoyed reading.





Tools required
tools I used:
softice
wdasm
lib (Micro$oft - sorry!)
dumpbin (Micro$oft - sorry!)
egrep (pc users - search cigwin)
perl (cigwin again)


Target's URL/FTP
Its hard to say what the target is - is it the "target" or is it flexlm?
Let's just say:
A program that integrates flexlm v6.1 and calls lc_new_job()
Refer to past essays - those targets may have been updated.


Program History
Flexlm has been broken before. They added enhanced security.

Essay

**************************************************************

Part 1 learning curve and confusion:

**************************************************************

Initial interest in flexlm for me started a week or so ago.  I read some essays, found

some targets and applied the essays techniques to these targets. - nothing special.

Then I ran into a problem:

I had gotten the necessary "keys and seeds" needed to generate a license for a

target (target A). There was a new version of target A (target B).

I could not get the "keys and seeds" for target B using the same techniques for target A. 



(note: target A used flexlm 6.0.  Target B used flexlm 6.1)



So this sucked!  I queried for answers and got some hints but no one said

"dude you need to xor your answer with 0xff".  I guessed I would have to either wait

for an answer or investigate myself ...



About this time I replaced the target B seeds and keys with target A's.

There was no logic in doing this - I was just flailing for answers.

I generated a license for target B using target A's seeds and keys.

This worked!! (after upgrading the "version" on the license entry)



At this point, if you understand what I wrote you might be asking -

"You got the right license so time to move on right?"



Maybe thats what I should of done.



This was confusing to me.  Target A and B definitly had different vendorcode

structures - so how could the old target A info work on target B?



As another experiment, I took the "encryption_seeds" from target A

and the VENDORKEYS from target B and generated a valid license for target B. 

(again no logic in doing this)



This was confusing also.  All through this process I was thinking that

I am not really understanding the license generation process and what

the SEEDS and VENDORKEYS are really used for.



**************************************************************

Part 2 a little less confusion but still - confusion:

**************************************************************



After reading more essays and some discussions with Nolan Blender, I started

to realize that if you got the right license i.e. no error -8 that means

you have the right "ENCRYPTION_SEEDS".  The VENDORKEYS have nothing to do

with this.



I now understand that the encryption seeds are used as "keys" to

a function that takes information on the license entry and makes an

authenticator.  That authenticator is compared to the one on the

license entry line.  It literally comes down to a character compare.

If they don't match you get -8 error.  This prevents someone from

for example changing the expiration date in an otherwise valid

license file.  Another essay should explore this authenticator function.



I still don't quite understand the importance of the VENDORKEYS as far

as a cracker generating a valid license.  I think that as long as they

follow certain checks, the actual values are not used to create the 

authenticator that you see in the license file.



I did start to understand the importance of the ENCRYPTION_SEEDS in license generation

though.  So now I knew why my target A info worked.  It was because target A

was giving me the right ENCRYPTION_SEEDS. Note that there are no other right

ENCRYPTION_SEEDS - these were the ones.  Therefore, target B was doing something

funny with its vendorcode.data and hiding the ENCRYPTION_SEEDS somehow.



So what this means is that it is perfectly valid to have the same ENCRYPTION_SEEDS

with different VENDORKEYS - which is what target A and target B had.



The problem then is one of two things:

1.  The vendorcode structure that I got from lc_init

    did not have the same encoding for the encryption seeds.  That is 

    es1 != vc.data[0]^vk5 and es2 != vc.data[1]^vk5.

2.  The old encoding was true but the "real" vc.data was hiding somewhere

    else.



Why? Because I KNEW the encryption seeds and KNEW vk5 and KNEW vc.data and

this was TRUE:  es1 != vc.data[0]^vk5 and es2 != vc.data[1]^vk5.



These things would have to be tested out.



It should be noted at this point that having the right ENCRYPTION_SEEDS was

a pure gift.  This is rare that you have the answer before knowing where

to find it.  Another style of essay might not mention this fact that I 

had the right answer from the start.  This makes this kind of attack 100%

easier and should be considered less of a crack than someone who didn't

have the answer.  It is an important point.



Another implication that the seeds worked is that flexlm itself was

being configured correctly when I generated the license.  This gave

me confidence that my target was not using some of the flex-able features

of flexlm.  I admit I don't quite understand this - it has something

to do with lc_set_attr calls though.



You on the ohter hand will probably not have the right encryption

seeds (which might be why you're reading this junk).  That's ok -

follow the steps of the previous essays, which is what I did in the

first place.  You should have generated a license file with your features,

however when ran you will get a -8 error from flexlm (bad seeds).  You

need this to be able to have the right code execute.



You should note that flexlm is staticly linked to this target,

and that the flexlm sdk contains the libraries that my target used.

This is unusual for most of the cracking I have done.  Having

full documentation and source code, examples, libraries etc. is

pretty overwhelming.



**************************************************************

Part 3 test some things out: Program flow

**************************************************************



I wanted to see the flow of the program.  Which routines were called

and in what order.  I put breaks on lc_init, lc_checkout, and l_sg (described below),

then ran the program, watching the order things were called.  Also observing return

values.  The API documents helped to interpret what to expect for return values.



The way I originally found these routines in my target was partly by comparing

the "signature" of the .dll code to my target.



The flow went something like this:



call lc_init {

  ...

:00440008 50                      push eax                           pram

:00440009 56                      push esi                           pram

:0044000A E8BD1E0000              call 00441ECC                      call l_sg(job,code,??)

:0044000F 83C40C                  add esp, 0000000C                  restore

:00440012 817DCC21436587          cmp dword ptr [ebp-34], 87654321   compare seed to default

:00440019 7409                    je 00440024

:0044001B 817DD078563412          cmp dword ptr [ebp-30], 12345678   compare other seed

:00440022 7527                    jne 0044004B

  ...

}

...

call lc_checkout [

  ...

  ...

  call l_sg

  ...

  ...

  if(bad seeds) return -8

  else return 0

}



Note that from reading other essays, I knew that the check for default values was

a good place to look for seeds.  I knew lc_checkout was another important location

for getting "features".



At this point, I decided to trace through the l_sg call inside lc_init.  I printed

out a copy of it and traced through it, writing notes on the copy.  I should note

at this point I was comparing the wdasm output of the program to my "annotated

object dissassembly" (see appendix).  The first part of l_sg was:

l_sg(???,name,vendorcode):

:00441ECC 55                      push ebp                     - after this push, 8 extra bytes on stack before params

:00441ECD 8B4C2408                mov ecx, dword ptr [esp+08]  - this is first param to l_sg

:00441ED1 8BEC                    mov ebp, esp

:00441ED3 83EC04                  sub esp, 00000004            - we're going to use 4 bytes in this routine

:00441ED6 8B4150                  mov eax, dword ptr [ecx+50]  - ok first param is a ptr.

...

:00441EDC F6804101000080          test byte ptr [eax+00000141], 80  - ok another derefrence, confusing!!!

:00441EE3 741E                    je 00441F03                       - test failed here on my trace

:00441EE5 833D74024F0000          cmp dword ptr [004F0274], 00000000 - this is a call vector

:00441EEC 7415                    je 00441F03                        - vector zero so don't call it

:00441EEE FF7510                  push [ebp+10]                      - set up params

:00441EF1 FF750C                  push [ebp+0C]

:00441EF4 51                      push ecx

:00441EF5 FF1574024F00            call dword ptr [004F0274]          - vector call !!!

:00441EFB 83C40C                  add esp, 0000000C

:00441EFE E9A5000000              jmp 00441FA8



:00441F03 8B7D10                  mov edi, dword ptr [ebp+10]  - jump here edi = vendorcode

:00441F06 8B750C                  mov esi, dword ptr [ebp+0C]  - this is vendor name

:00441F09 8D470C                  lea eax, dword ptr [edi+0C]  - = vendorcode+0c=vendorkeys

:00441F0C 50                      push eax

:00441F0D 56                      push esi

:00441F0E E83FAD0000              call 0044CC52  - this is l_key(name, keys)

...

...



Now, look at the first tests.  It is testing a flag (which I failed), testing

a vector for 0 and if not 0 will call the vector stored there.  To me a call to

a vector is suspect.  Especially after that convoluted test!  I was glad that the

program did not go to that vector.  It looked like something scary was going to be

there.  I decided to ignore this part of l_sg for a while.



I wanted to get more comfortable with l_sg.  I traced through the remaining part

and made notes on my printout.  First I noted the parameters passed to it.

I wasn't sure what the first one was but the other two were obvious.

Then I wanted to know what the outputs were.  This code was generated from

C so one output could be eax, but the calling routines would trash eax.  It

basically came down to:



:00441FA2 895708                  mov dword ptr [edi+08], edx  - store something in vc.data[1] (seed 2)

:00441FA5 894704                  mov dword ptr [edi+04], eax  - store something in vc.data[0] (seed 1)

restore and return



There are calls by value (value on stack) and calls by reference (pointer).

This was a call by reference that modified the vendorcode that was

passed to it.  However, the values placed here were the same "wrong"

seeds that I had gotten on my initial attempt to crack this target.

After some more re-reading of essays, I realized that this is the "old style"

of recovering the seeds - by xoring the vc.data with vendorcode5.  By

doing this I learned a lot about how the older versions of flexlm worked.

But if you recall, I already knew these were the wrong seeds.



I pretty much new that this is what l_sg would return because I had originally

bpx'ed right after that call.  This was because of previous essays.  The only

thing that I learned was that there was some funny flag/vector stuff going

on in l_sg.



**************************************************************

Part 4 test some things out: Data Flow

**************************************************************



I was tired of tracing the code and staring at disassembly.

Time for a different approach:



Maybe the seeds were hidden somewhere else, or maybe the vendorcode.data

still had something to do with the seeds.  I knew one way to

find out if vendorcode.data was still relavent: Using a valid license,

poke bad values into vendorcode.data and see if the program fails or not.

If vendorcode.data was not really used, the program would not fail.

If vendorcode.data was used the program would fail.

It failed.



Ok, so vendorcode.data sill has some relavence to the authenticator.  And

probably is still containing the seeds with some other function on top

of it.  This made me feel a little better.  At least the seeds were still

where I thought they were.



I did a bpm on the vendorcode.data passed to lc_init.  I followed the

data around.  It was copied several times so I bpm'ed all the copies.

I ran the program several times.  Trying to get a feel for what was going

on.  I could see that something wierd was happening - at one point the

vendorcode.data appeared to get "randomized".  That is, there were non-

deterministic values placed there on sucessive runs - interesting and scary!!!



**************************************************************

Part 5 Program Flow again: Good Guy/Bad Guy

**************************************************************



At this point I decided to target certain routines and observe execution of

the program under two conditions:  One condition is with a good license file.

The other is with a bad licence file.  The way I targeted the routines was

to do a search in the library for references to l_sg (see appendix).  I could

have also searched wdasm for the l_sg address but I was starting to like this

new strategy.

I can't remember the order of things now, but I also traced down through lc_checkout

as part of deciding which routines to single out.

One of the routines that used l_sg was good_lic_key.  By searching for references

to good_lic_key and so on up the heiarachy I deducted that good_lic_key was

called in lc_checkout.  Ok this is getting somewhere because lc_checkout is the

routine that returns -8 (bad seeds).



By tracing "good" and "bad" execution of good_lic_key, I could see the following:



good_lic_key {

...

  l_sg()

  extract_date()

  l_ckout_crypt()

  test eax from l_ckout_crypt()

  if eax zero BAD GUY!!! jump to -8 !!!

  good guy code

...

}



Now if you have been following this,  I already knew l_sg had some suspect code.

But the bad guy test came from l_ckout_crypt().  What should I persue next - l_sg?

- l_ckout_crypt ?  I decided to trace l_ckout crypt.



Wrong decision.



What I found out from good guy/bad guy execution of l_ckout_crypt is that it forms

a string of data based in part on a license file entry.  Then, it forms an authenticator

based on that string of data.  Then it compares this authenticator to the one in

the license file.  Bad guy will fail this compare.  So I could continue into this

function to find the seeds - they have to be there somewhere.  I decided to abandon

this.  It might have been around the corner but I felt that the l_sg should be followed

some more.



***************************************************************

Part 6 Seeds are uncovered - the answer

***************************************************************



Ok, back to l_sg.  I bpx'ed lc_init, lc_checkout, and l_sg.  I knew that it would be

called in both routines, but I wanted to see it when it got into lc_checkout.



This time l_sg took the jump to the vector!!!  I should have known.

It would have saved me a lot of time to just break on this jump in the first place!!!



The first part of the vector code went like this:



store strlen(vendor name)

if parameter zero go somewhere else

job+offseta=gronk(time())

job+offsetb=gronk(time())

job+offsetc=gronk(time())



The "job" structure gets passed by reference to almost all flexlm

functions.  This is one way they communicate with each other.



If you think about this, what function would gronk time() a bunch

of times?  It will not give you the same results any time ran.  It would give

you RANDOM values.



At this point things were making sense.  The explanation for seeing random data in

vc.data now was clear - the time() function was randomizing vc.data.



As I traced through this function, I could see there were three main sections.

1. create random data in dword offsets +4 +8 +c +10 of job structure.

2. crunch vendor name into a 4 byte value.

3. process vendorcode.data using vendorkey1 vendorkey2,

            a constant, random data, and value from 2



At some point I stopped tracing and reset the program, breaking after generation of

the random data.  I zeroed out the random data and set a break at the end of the

routine.  Pressed ctrl+d and there they were - the unencrypted correct seeds that

I had gotten originally from target A!!



The code after the random number generation basically does:

mask = vendorkey1 ^ vendorkey2 ^ dword const ^ vname hash ^ random

seed 1 = mask ^ vendorcode.data[0]

seed 2 = mask ^ vendorcode.data[1]

where:

dword const = constant in the code

random = a 32-bit value composed of bytes at offsets +0xb +0x8 +0x13 and +0x9 of job

vname hash = a function of a vendor name checksum



Ok.  Now you can see that es1 != vc.d[0]^vk5.  This was the answer.  The seeds

had been hidden by a different function.



Further study of this routine shows that if the first parameter passed

to it is 0 instead of a pointer to job, it will use 0 for random data and

therefore will return the correct seeds.



Since the random bytes are stored in the job, the real seeds can be

used without ever seeing their true values in the cpu or memory as long

as you have the vendorcode and job structures to work with.



***************************************************************

Part 7 Tracing flags  

***************************************************************



This was pretty much a done deal.  I could get the seeds out of this target.

A question was lingering.  The l_sg checks a flag to see if it should

jump to the "alternate" seed decrypter.  Called in lc_init, this flag is clear.

Called in good_lic_key, this flag was set.  So somewhere this flag was

being set.  Time to bpm.



I set a bpm on this byte.  I found what was setting it.  The code was something

like:

...

:00444694 E8A7B3FFFF              call 0043FA40                  <-call lc_init

:00444699 83C410                  add esp, 00000010

:0044469C 8B0E                    mov ecx, dword ptr [esi]      <- ecx = *job

:0044469E 8B5150                  mov edx, dword ptr [ecx+50]   <- ok this is familiar (see l_sg)

:004446A1 5E                      pop esi

:004446A2 808A4101000080          or byte ptr [edx+00000141], 80 <-set flag!!!

:004446A9 8BE5                    mov esp, ebp

:004446AB 5D                      pop ebp

:004446AC C3                      ret



Now let me digress.  At this point I knew that this flag was going to be at

the same address every time I ran the program.  This makes the bpm that much

easier.  This implies that this variable is not dynamically declared.  Or maybe

it is dynamically declared but the process is deterministic.  I don't know enough

to say.  But I do know that having this flag at the same location for each run

is a great help.  anyway...



Well there it was, the same screwey dereferencing and the setting of the flag.

Right after the call to lc_init. That made sense.  Thats why l_sg behaved one

way in lc_init and another way in lc_checkout.  This was a gift having the flag

set right after the call to lc_init.  It implied that whatever this routine was,

when called it set in motion the alternate seed decryption routine.  This routine

was pretty small.  I searched for it in the library (by searching for references

to lc_init, then examining these object files - see appendix).  This routine turned out

to be lc_new_job().  I then found out who was calling lc_new_job.  The calls at that

level looked like:

lc_new_job()

lc_set_attr(0x38,"license file")  <- set up where licence file is

lc_checkout(feature a...)

...

lc_checkout(feature b...)

...



This code must have been part of what flexlm would call the "client code".

I was at the top of the API chain.  This code has vendor specific stuff in

it whereas previous code was flexlm generic.  This was good to know.



After finding this I bpx'ed at lc_new_job, lc_init, l_sg, lc_checkout, the "real"

l_sg and ran the program.  I verified the program flow was how I thought it should

be.



All along I had no idea where the calls were coming from.  I thought lc_init

was the "init" routine called by the client.  Now I knew that there was a level

up.  And there was a high probability this was the top level.



***************************************************************

Part 8 the feature lc_new_job  

***************************************************************



This brings me to the title of this essay - the feature lc_new_job.  Its pretty

late in the essay to be finally discussing the title subject.  But that's how

I found it.  There was no magic revelation that I should look for lc_new_job.

I had to trace the program/data flow from an arbitrary starting point.



I had rememberd reading about lc_new_job in the documents.  It was strange

because they talked about something I felt was inapropriate to discuss in

an API document.  It stood out.  I re-read the description.



They made a point that you have to link with lm_new.obj.  If you don't you

will get a linker error for l_36_buf.  (I already knew l_36_buf was the name

of the l_sg "alternate seed" vector by the way).  Time to look at lm_new.obj.



Anyway, a disassembly of lm_new.obj revealed the alternate seed decrypter

that was calling time() to generate random data and xoring it with the real

seed.  This made sense.  I knew that lc_new_job set that "alternate seed" flag.

I knew that the documents said you needed lm_new.obj.  These two facts

confirmed that the "alternate seed" decrypter should be in lm_new.obj.



***************************************************************

Part 9 lm_new.c

***************************************************************



When I discussed the information about lc_new_job and lm_new.obj,

VoxQuietis informed me that he couldn't find lm_new.obj in his

flexlm distribution.  At this point, pilgrim noted this build rule

in the flexlm makefile:

lm_new.obj : ..\machind\lsvendor.c lmrand2.obj ..\machind\lm_code.h

        lmrand1 -i ..\machind\lsvendor.c

        $(CC) $(CFLAGS) $(INCS) /c lmcode.c

        $(LD) $(LFLAGS) /out:lmrand2.exe lmcode.obj lmrand2.obj \

                $(LIBS) $(CLIBS)

        del  lm_new.c  

        lmrand2  -o lm_new.c

        $(CC) $(CFLAGS) $(INCS) /c lm_new.c



What this is saying is that lm_new.obj doesn't come with the distribution -

you make it from lm_new.c.  Another thing this is saying is that lm_new.c

doesn't come with the distribution - you GENERATE it.  Time to look at

lm_new.c.



This module has two functions.  Both functions are related to lc_new_job.

One function is called directly by lc_new_job.  This function "unpackages"

the vendorcode and vendor name. The other function is the "alternate seed"

decrypter and undoes the pre-encryption done on the seeds to form vc.data.

The decrypter also conditionally masks the decrypted seeds with a random

value and stores that mask in the job structure so the seeds can be recovered

later.



The "unpackager" was one of the remaining questions I had.  That is, how

was the original vendorcode stored in the executable?  Now I could see

that it was obfuscated inside a function.



So a recap of the relavant backtraces reveals:



lc_new_job {

  call lm_new.unpackage(vname,&vc)   (unpackage vendorcode,vendor name)

  call lm_new.unpackage(0,0)          (not sure why this call 0,0 )

  call lc_init {

    ...

    call l_sg (alt seed flag not set - return old style vc.d^vk5)

    compare seeds to default 12345678 and 87654321

    ...

  }

  set alt seed flag

}

...

lc_checkout {

  ...

  good_lic_key {

    ...

    l_sg  {

      alt seed flag set so,

      lm_new.decrypt(job,name,vc)    (decrypt and RANDOMIZE!!)

    }

    extract_date

    l_ckout_crypt / -8 test

    ...

  }

  ...

}



Now back to lm_new.c.  Another thing I noticed was that the "dword const" used

in its decryption routine was not the same as the dword const used

by my target.  Refer back to part 6 for what the dword const is.

This was maybe the most important observation made about lm_new.c.  I

decided to re-make lm_new.c.  When I did, I got yet another different

dword const.  Not only that, other things were different about lm_new.c

each time I generated it.



What this is saying is that lm_new.c is a "personalized" version for each

target.  This means it is unique for each target.  In the past, the encoding

of the seeds was generic:  They were encrypted with vendorkey5 and a generic

library routine could be used to get vendorkey5.  Now, a custom routine

was used for each target.



Personalization is nothing new but is usually used to single out and verify

a specific entity.  For example, a private key is personalized.  If a person signs

something with a private key, you know it came from them.  If private keys were

not personalized, anyone could sign for anyone else.  But then they wouldn't be

"private" keys now would they?  They would be the same value for everyone.



This method of personalization is very practical for globetrotter.  They don't

have to interact with their customers to give them a custom routine.  It

is all handled through automatic generation of lm_new.c.



***************************************************************

Part 10 The source code generator

***************************************************************



I would like to illustrate what I think the source code generator does.

The source code generator is everything used to make lm_new.c.  The

final source code generator executable is lmrand2.  (I'm not sure how

this ASCII art will look in your browser!!)



                ______________                     ______________________________

 clear seeds -->| encryption |--->encrypted seeds->|vendorcode.data[]=enc. seeds,|

                | routine    |                     |other vendorcode,vendorname  |

                -----^----^---                     -|-----------------------------

                     |    |                         |

                     |    |            _____________V__________

                     |    |            |Unpackager source code|--> unpackager source code

 _________________   |    |            |generator             |    in lm_new.c

 |PERSONALIZATION |  |    |            ------------------------

 |INFO = rand();  |--|    |

 ------------------  |    |-VENDORKEYS,VENDORNAME

                     |

                _____V____________________

                | Seed decryption source |-----> seed decryption source code in lm_new.c

                | code generator         |

                --------------------------



This basically says that the generator:

Creates some personalization info from rand()

Encrypts the seeds using personalization info, vendorkeys, and vendorname.

Forms the vendorcode structure from the encrypted seeds and the rest

of what vendorcode is.

Feeds vendorcode and vendor name to the unpackager source code generator

to generate the unpackager source code in lm_new.c

Feeds the personalization info to the seed decryption source code generator

to generate the seed decryption source code in lm_new.c



Things to observe:



The encryption process and the obfuscation done by the unpackager are

separate processes.



It is unimportant what the seed decryption algorithm really is.  The important

thing is that it undoes what the encryption routine did.  This is easy to do since

the thing that encrypted the seeds and the thing that generates the decryption source

code lie in the same entity.



The encryption/decryption of the seeds is a personalized process.



***************************************************************

Part 11 Final analysis

***************************************************************



We now have enough information to make a final analysis on what the "enhanced

security" of lc_new_job is.  (This might not be the "final" analysis because

there still might be some things I missed.)



First is the obfuscation of the vendorcode and vendorname.



The second is more complicated.  Let me define some terms first:

The two previous methods of breaking flexlm seeds are what I would

call "pre-seed" attack and "post-seed" attack.



The pre-seed attack was possible because the methods of encrypting

the seeds were generic.  All you needed was the vendorcode structure

to get the seeds.  Calling l_svk with the vendorcode gives you vendorkey5.

Xoring vendorkey5 with vendorcode.data gives you the clear seeds.



The post-seed attack was possible because the seeds were in the clear after

decrypting them.  All you needed was to break at the compare to default

values and write down the seeds.



Both of these methods were un-obtrusive in the sense that the cracker only

needed to set the right breakpoint and write down some values.  No modification

of program execution was needed.



So now we can see what the second part of the "enhanced security" is.  It is

trying to deal these two forms of attack.  The personalization makes the current

pre-seed attack invalid.  So the cracker is forced to use the post-seed attack.

But the randomization on the clear seeds makes the current post-seed attack

invalid.  So the cracker is forced to a new form of attack.



Remember that I was foiled by the post-seed countermeasure early on when I was

following the seeds around and saw the random data.  I didn't mention it but

I was foiled by the pre-seed countermeasure early on because I tried

the pre-seed attack also.



You can not deal with one form of attack without dealing with the other.  You

need both countermeasures in place.  For example if the encryption was only

personalized the cracker could still do the post-seed attack.  If the decryption

output was only randomized the cracker could still do the pre-seed attack.



Part of what decreased the effectiveness of these countermeasures is that the

target did not change its seeds when switching to a new version of flexlm.

This might have been done for practical reasons.  The ironic thing is that

this not only compromized the target's security, it compromized the security

of all targets using the flexlm feature lc_new_job.



**************************************************************

Appendix - exploiting object code/libraries 

***************************************************************



As Nolan Blender pointed out in an essay, flexlm is composed of a ton

of object files located in a object libraray.  This is cool because

not only are API calls visible, but internal calls between object

libraries are visible too.  Also, you can find out all the object files

that use a particular function.  It is nice to have the symbol info from

the object files to annotate the disassembly.  Even c standard functions

are annotated!!  Have you ever traced through printf by mistake not knowing

thats what it was?  If I understand pilgrim right, IDA can read object files

and find and annotate the code in a built application.  I didn't know this

at the time.  However I did understand that I wanted that symbol info.

I tried to use wdasm to disassemble the object file but that didn't work.

It is highly probable I just didn't know how to use it right.



I guess the way some people handle this is to build their own flexlm client

app with debug switches on.  Then they can reverse engineer this app.  With

all the symbol info.  This of course is a sane way to do things.



I wasn't interested in this approach.  I wanted to stick with my target.

The basic method went like this:



Target a function

Search the library for references to the function using dumpbin.exe

If I wanted the actual function - the reference would not be "undef"

If I wanted calls to the function - the reference would be a "undef"

Extract the relavant object file using lib.exe

Extract the reloc/header info into one file using dumpbin.exe

Extract the object disassembly into another file using dumpbin.exe

Combine info from the reloc and disassembly files

  into one "annotated object disassembly" file using a perl script file



Once I found some object code that I wanted to see in the target, I would

manually search for the "signature" of it in the target.



There maybe tools that can do what I wanted to do.  I would like to know.

But it didn't matter in the end.  This approach worked very well.



Eventually, I had three batch files, one was wheresym.bat: (find symbols in obj or lib)

dumpbin /archivemembers /symbols %1 | egrep "member|%2" > result.txt



Second was extract.bat: (extract .obj from .lib)

lib /extract:%2 %1



The third was gronk.bat:  (extract an obj and create annotated disassembly)

call extract lmgr.lib SINTEL_REL\%1.obj

dumpbin /relocations /headers %1.obj > %1.sym

dumpbin /disasm %1.obj > %1.dis

perl sym.pl %1.sym %1.dis > %1.txt



Note that all objects in lmgr.lib had SINTEL_REL\ in front of their name.

The perl script sym.pl is included later.  The tools dumpbin and lib have

equivalents such as objdump and ar.



Gronk.bat creates 3 files *.sym *.dis and *.txt.  I am only interested in

*.txt - the final output of the process.



Here's an example,

say I was interested in l_sg.  I would type:

>wheresym lmgr.lib l_sg



The result.txt file would contain:

...

Archive member name at 1AB84: /176            SINTEL_REL\l_cksum.obj

013 00000000 UNDEF  notype ()    External     | _l_sg

...

Archive member name at 4621A: /1729           SINTEL_REL\lm_ckout.obj

092 00000000 SECT1D notype ()    External     | _l_sg

...

Archive member name at 5E9F2: /2249           SINTEL_REL\lm_init.obj

015 00000000 UNDEF  notype ()    External     | _l_sg



This tells me that l_sg is in lm_ckout.obj and is referenced in lm_init.obj

and l_cksum.obj.  Note that l_sg might be called by a function in lm_ckout.obj

or not.  I would have to gronk lm_ckout.obj in order to find out.



So it depends on what I wanted to do.

Say I type:

gronk lm_ckout.obj



Searching the resultant lm_ckout.txt for l_sg, I find some calls and then the

actual function (I only show code for illustration of this method):



New proc searching...

Found proc 164

_l_sg:

  00000000: 55                 push        ebp

...

  00000015: A1 00 00 00 00     mov         eax,[00000000]

Found 00000016 _l_n36_buff

...

  00000039: 57                 push        edi

  0000003A: E8 00 00 00 00     call        0000003F

Found 0000003B _l_key

  0000003F: 8B F0              mov         esi,eax



The "Found" lines and "New proc" line are from the perl script.  The code is from

the .dis file.  The found lines state an address of a reference.  For example,

this code is saying the l_n36_buff reference is at address 0x16.  You should realize

this object code will be located at a different address in linked code and all reloc

info will be resolved.  The "call 0x3f"  is not really calling address 0x3f.  The

four zero's are just placeholders for the reloc info.  So this call is a call to l_key.



My point is I can take this file and compare against the target to find function names.

Another powerful feature is finding all the calls by just searching on the names.

Now if I wanted to go down the heiarchy, I would find l_key and study its references.

If I wanted to go up, I would look for other references to l_sg.



For example, also in lm_ckout.txt is good_lic_key:

New proc searching...

Found proc 79

_good_lic_key:

...

  00000016: E8 00 00 00 00     call        0000001B

Found 00000017 _memcpy

...

  0000002D: E8 00 00 00 00     call        00000032

Found 0000002E _l_sg

...

  00000041: E8 00 00 00 00     call        00000046

Found 00000042 _l_extract_date

...

  0000004B: E8 00 00 00 00     call        00000050

Found 0000004C _l_ckout_crypt

...



Next, I could query good_lic_key to see who calls it and so on.

So I could work in the space of the object files or the space of my target.

It was a minor inconvenience to switch between them.



The perl script first processes the symbol info in the .sym file.  It

creates an array of what function,address,and reloc name are in an entry.

It assumes the entries' addresses are in increasing order.

Then it processes the disassembly file and uses the array it built up

previously.  I was in a wierd mood when I wrote it so the file processing is

in a state machine.  I don't think the split stuff is necessary but whatever.

I have that habit from using a2p (awk to perl) which does a similar thing.

Note that this script is highly dependant on the output syntax of my dumpbin.exe

which was from msvc++ 5.0.

Here is the perl script sym.pl

#***************************************************************



$arg1 = shift;

$arg2 = shift;

@proca = ();

@addra = ();

@syma  = ();



open (IN,$arg1);



$state = 0;

while () {



  if($state==2) {

    $proc_name = "";

  }

  if(($state==2)&&(/Communal/)) {

    chop;

    @Fld=split(' ',$_,999);

    $proc_name=$Fld[$#Fld];

#    print $_;

    $state=3;

  }

  if($state==5) {

    chop;

    @Fld=split(' ',$_,999);

    if($#Fld!=-1) {

      push(@proca,$proc_name);

      push(@addra,$Fld[0]);

      push(@syma,$Fld[$#Fld]);

#      print "$proc_name $Fld[0] $Fld[$#Fld]\n";

    }

#    print $_;

  }



  if($state==1) {

    if (/text/){$state=2;}

    else {$state=0;}

  }

  $state=1 if(/^SECTION HEADER/);

  if(($state==3)&&(/RELOCATIONS/)){$state=4;}

  if(($state==4)&&(/-------/)){$state=5;}

  if(($state==5)&&(/^$/)){$state=0;}

#  print "$state $_";

}



close IN;



#for ($i=0;$i<=$#proca;$i++) {

#  print "$proca[$i] $addra[$i] $syma[$i]\n";

#}



open (IN,$arg2);



$state = 0;

while () {

  chop;

  $symfound=0;



  if($state==3) {

    @Fld=split(' ',$_,999);

    if($Fld[0] =~ /[0123456789ABCDEF]*:/) {

      $addr=$Fld[0];

      $addr=~s/://;

      $j = hex $addra[$searchi];

      $j;

      $k=hex $addr;

      if($searchi<=$#addra) {

        if(($j-$k<=0)&&

           ($proca[$searchi] eq $proc)) {

          print "Found $addra[$searchi] $syma[$searchi]\n";

          $symfound=1;

          $searchi++;

        } else {

#          print "Looking for $addr == $addra[$searchi] $syma[$searchi]\n";

        }

      }

#      print "$addr\n";

    }

  }



  if(/^$/){$state=0;}

  if(/^_/){$state=1;}



  if($state==1) {

    $proc = $_;

    $proc =~ s/://;

    print "New proc searching...\n";

    $searchi=0;

    while(($searchi<=$#proca)&&($proc ne $proca[$searchi])) {

      $searchi++;

    }

    if($searchi>$#proca) {

      print "Not found proc\n";

      $state=2;

    } else {

      print "Found proc $searchi\n";

      $state=3;

    }

#    print "$proc\n";

  }



  { print "$_\n";}



}



close IN;



#***************************************************************







Final Notes

I would like to thank Nolan Blender, VoxQuietis, and pilgrim for their

correspondance.  Also, 1000 thanks to them and others such as Siul+Hacky,

Acme, and CrackZ who wrote the informative essays on flexlm.



If you analyze my method of cracking this target, you will see that

I could have found quick soloutions early on by just following through

on things.  However, at the time it was not that obvious what I should

be doing.  Sometimes you need to experiment before following through

with a certain approach.



If you want the textbook version of this - read the sections in reverse

order!!!



<EOF>







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?