How to make V5.x license files with the V6.x SDK
Dear Reverser, Even after having read, that there are more reasons not to publish an essay / write an email (info.htm) to You, I still would like the following essay have published by You (I wanna submit my essay, dammit). It deals with some questions that are in my opinion not explained in several essays studying Flexlm protection scheme. Hence I think, this essay is not the tell-You- how-I-did-the-crack essay You are about to remove from Your site. I used Your formatting muster, but I needed to add a section at the end, to house some hopefully interesting code snippets. Moreover there are a few hyperlinks to documents on the crazyboy mirror, since the fortress is down. Another point of doubt is the introduction: it is meant fully frank, but maybe it sounds quite pathetic, and maybe it is not the right location for such words. Hope You will give me a hint, if You think I'd better change this. I had the reversing content itself checked by an independent reverser, so I am sure about meeting the quality standards of the students essays. Finally I would like to explain the learners point of view to the change of your site. I have been visiting reverser.org for somewhat more than one year. I was fascinated by the esprit that glows between the lines that can be seen on Your site. This is _very_ fascinating to me, since nowadays only few people are able to understand the sublime patterns of that (I am really sorry, that my English is as poor as it is) ... and Sine Ira et Studio isn't it a pity that the classical humanistic education seems to be dying out? I also gratefully appretiate the lack of irritating XXX-stuff that seems to be unavoidable within all the cracking related web pages. I am not a puritan (our pope says: Wachset und Mehret Euch - maybe even a polish pope has a certain idea how this must be accomplished), but when concentrating on some assembly stuff, I feel even disturbed by silly couloured comic strips on top of the monitor. There Your site guarantees the pure delight: The joy of following so many diffents little threads and hints into this labyrinth of a web site and all the wealth of information on how the computer (which is my most common erveryday's tool) really works. Hence from my point of view You should go on to publish both the expert stuff for the avantgarde of the S/W reversing art and the stuff for the learning people. With respect to the expert stuff things are clear, since they explain techniques previously not known at all. I suppose the development of a good publication stategy for the learner/intermediate level as much more problematic. In my opinion quite a lot of diffent topics need to be treated: Using the tools at a selection of different targets, Application Interfaces e.g. all the message box and system time stuff, typical protection schemes e.g. serial numbers, timelock, installshield, .., OS related stuff (I realise that You try hard to promote work on Linux). If I understood the discussion right, these points were more or less made by some people allready shortly after the publication of Your intentions. I would like to add, that the essays are also a kind of a forum for the reverse engineers (In their transactions IEEE also publishes letters, which in this sense correspond to the postings of Your forum, and articles, which would correspond to the students essays). The idea of beeing a forum should also become of some importance on the decision on whether to publish or not. I.e. if one can see, that there are a couple of people working on a certain field (in my case on Flexlm), then the academy database would be the most suited place to publish the results of this work (even if there is no dedicated project). I don't know, whether this comment was necessary, or even welcome, but to me Your exact publishing policy is not evident by now. With best regards VoxQuietis
Flexlm software protection scheme has been treated by several essays before. Nevertheless there are still quite some open questions (I think I also won't be able to treat them all). In the following I will focus on these points:
1.) How to collect the required key data fast and efficiently
2.) Solving the Bad Key Data problem
3.) Compiling a license key generator (aka lmcrypt.exe)
I expect you having read the essay of Pilgrim. And I expect you to have the Flexlm HTML manuals available. It's worth the time studying them in order to understand the concepts of Flexlm operation.
A disassembler (WDASM is good enough)
A debugger (SoftIce is the best one)
A hex editor (I like Hiew)
Your favourite C-Compiler (I use good old Borland C/C++ V4.5)
Flexlm software development - you might beg for a password, or you might crack the encryption yourself, e.g. after having read pilgrim's beautiful new essay.
I do not intend to treat a specific target. But You might consider Aitor's target as a working example. Nevertheless I have to state that the implementation of the Flexlm protection in this case is rather poor. Try for example this target, a buggy and overbloated collection of CAE tools. It suffers from crippled features a lot. But it is incorporating a implementation of Flexlm, that is much more sophisticated (if this expression could be used on Flexlm at all ;-) than the one mentioned before. It is not affected by the simple DLL-patching approach described in the preceding essays, since the programmers made use of such nice, ugly features as vendor defined checkout filters (i.e. the checkout is shifted from the DLL into the target itself) and of dongle protected host restrictions. Nevertheless a proper license file will the perfect remedy against these shortcomings, if you aren't too lazy to write a little dongle emulator in addition to the license generator (maybe I treat Sentinel C Plus dongle in another essay provided I see the need for that from your response ;-).
No specific target - no specific history ;-)
1.) Gathering the required information
Although the retrieval of the needed information is treated by SiuL+Hacky and Pilgrim, I would like to summarize this topic in order to show You a fast and efficient way of catching the required pieces of information. The first step deals with Your target and with the lmgr-dll that came with it. Start disassembling the DLL, You'll need the disassembly later. Most probably it is named lmgr32xx.dll, where xx is the version of the Flexlm. I my case the DLL is lmgr325c.dll, which means I have to deal with Flexlm version 5.12. Look for the location of the exported function lc_init. We're going to have a breakpoint placed on it. Load the target into the debugger. Put Your breakpoint on lc_init. Be aware of a possible relocation of the DLL. Run your target, and the debugger will break in. You should have the description of the lc_init function prototype available. It looks like that:
status = lc_init(job,VENDOR_NAME, &code, &job);
Look at the stack. The second argument points to the vendor string (i.e. the vendor name) - write it down! The next argument points to the code struct. It should be known to You that the corresponding structure would look like that: int type; /* compatibility with previous versions */ int (seed1)^(vendorkey5); /* seed 1 for Flexlm checksum */ int (seed2)^(vendorkey5); /* seed 2 for Flexlm checksum */ int vendorkey1; /* vendor key 1 */ int vendorkey2; /* vendor key 2 */ int vendorkey3; /* vendor key 3 */ int vendorkey4; /* vendor key 4 */ short flexlm_version; /* to some extent the different versions */ short flexlm_revision; /* are the root of this essay */ Write them down. From pilgrim's essay You should already know what to do with them.
Now we have vendor keys 1 to 4, plus the XORed seeds 1 and 2. But we still have to fish for vendor key 5 - it had been the intention of the Globetrotter guys to hide it somewhere within the memory, so we'll need our brain (Isn't this the only essential tool?). Pilgrim did find it. But he didn't tell us how. Pas de problem. We're going to find them, too. (And I will tell You, how to find them ;-) Lets start examining the disassembly of the flexlm-DLL. Look at the exported functions. Something fishy (or maybe too much of that)? Look again ... and remember we're looking for cryptographic stuff ... Ah ... l_checksum ... all the checksum stuff should start from the correct seeds, as it is obvious, that the scrambled encryption seeds need to be corrected just before checksum generation takes place. So jump to the address of the l_checksum function and trace the code. After a couple of lines You'll find two XOR instructions. Put a breakpoint ... and ... nothing happens. Don't worry, scan the disassembly for similar code. Or maybe you examine the call of l_extract_date, which is following the XORs. Doing so You'll find two or three locations showing the same XOR instructions. Indeed there are several copies of the l_checksum function. Put breakpoints on all the instances of the l_checksum function. Run your target, and ... bingo! ... we now know vendor key 5. Write it down, and insert the gathered information into lm_code.h as indicated by Pilgrim. Caveat: Do not put breakpoints on the XOR instructions itself - doing so would lead to a bad data error, and the breakpoint wouldn't hit. Maybe there is a anti-Softice feature dwelling deep into the lmgr-DLL, I don't know (Maybe this is a challenge for reversing experts. Maybe some interesting points are hiding there). Nevertheless in order to follow this essay put the breakpoint some lines above, and it will work.
2.) The bad vendor key problem
Now the target told us the secrets, we need to know (I hope You already fished out the feature names - if not, read the description of lc_checkout and catch them. But be warned, lc_checkout might not be sufficient). Let's prepare the arena for a first license generation by the means of the Flexlm guy's demo license generator.
Create a working directory, copy all the header filed from the Flexlm \machind directory plus the lmcrypt source code (lmcrypt.c). Moreover we need the demo license generator genlic32.exe and the lmgr-DLL from the \i86_n3 directory (i.e. lmgr326a.dll for the v6.0 kit). Put the information found by You in lm_code.h and run genlic32.exe.
An error message comes up, complaining on bad vendor key data. It gives you also the error code number , -44. See the lmclient.h header for a summary of the error codes. From the Flexlm documentation it should be clear, that this error code is returned within the eax register at the completion e.g. of lm_init. Hence we have to scan the disassembly of lmgr326a.dll (or the corresponding dll of your Flexlm SDK) for 'ffffffd4' in order to find out, why lmgr thinks the codes were bad (They aren't, are they? We typed them carefully to lm_code.h).
You can put breakpoints on all locations, where 'ffffffd4' is assigned to a register or a memory location - there are only a few occurrences of this. But look at the disassembly: it's obvious - there is one push 'ffffffd4' directly preceding a call to the l_set_error routine. Following the code upwards leads directly to the 'bad guy-good boy' decisions. You might be tempted (as I were so,too) to invert the jumps, but this leads You into dozens of other error messages - and we want to attack the mechanism itself, not only the flow of the program. Hence follow the code upwards: Directly over the bad guy-good boy test there is weird looking bit juggling that is used to decide the validity of the codes. One the other hand the data that are processed by this strange code are prepared within a subroutine a few lines above
Put a breakpoint just before the call preceding the suspicious code snippet and examine the arguments that are passed into the subroutine: the code struct and the vendor name. Sure enough we're on the right track. Step into the subroutine. There are two types of calls inside. The first one takes the vendor name as input (and _only_ the vendor name!), while the second routine is fed by the vendor keys. The second call proves to host some encryption algorithm, while the first one is ... (you should be able to guess it) ... a checksum over the vendor name:
33F6 xor esi, esi ;count variable 0 .. 3 B835C90415 mov eax, 1504C935 ;put seed in eax ... 8B54240C mov edx, dword ptr [esp+0C] ;this one is the vendor name ... :loop 0FBE3A movsx edi, byte ptr [edx] ;read one character from vendor name 8D0CF500000000 lea ecx, dword ptr [8*esi+0x0] 42 inc edx ;increase edx for the next character D3E7 shl edi, cl ;move char 0..3 bytes higher 33C7 xor eax, edi ;in eax the output accumulates 46 inc esi 83FE04 cmp esi, 0x4 7C02 jl still_the_same_dword: 33F6 xor esi, esi ;esi should count to 3 only :still_the_same_dword 803A00 cmp byte ptr [edx], 00 ;loop until whole name processed 75E4 jne loop: ...
Watch this code snippet working in the debugger. Then its function becomes clear instantly: starting from the seed 1504c935 the vendor name is XORed character by character. The first character of the vendor name string is XORed with the lowest byte of eax, the second one with the next byte of eax, and so on. Now we should examine this procedure as it takes place during the initialization process of your target. Start again from the assignment of the error code -44 (You might fake the code struct that is handed to lc_init in order to force the error, or -better- try the dead listing approach). After a short search you'll land into the same routine - with slight modifications most probable due to the new compilation of the code.
The next step is obvious: Patch the lmgr326a.dll (or your equivalent, if using a different Flexlm SDK). Replace the original seed by the one used within the lmgr-DLL from Your target. Run genlic32.exe again,
.. et voilą .. no more bad vendor keys
3.) Compiling a license key generator
But still we don't have enough control over the license generation. There are a couple of open points: First of all, older license schemes require longer security(?) strings, which can not be generated with genlic32. Moreover there are implementations with dongles, that require the incorporation of a so called vendor defined hostid. All this can be achieved by a specialized license generator program. Globetrotter guys are nice enough to supply us with the source code of this. It is called lmcrypt.c. The following paragraph shall describe, how to get this working. So prepare your favorite C compiler. Include the lmgr-DLL that came with the Flexlm SDK. Read the manuals on how to use lmcrypt. You might also check the vendor defined hostid section, when dealing with these kind of things. You should prepare a prototype of the license file, i.e. the desired one with the exception of the right security string. Maybe you can get a time limited demo license (I admit, that this is quite lame, one the other hand the Flexlm manual describe everything You need to know to write a prototype by your own).
Now lets prepare the compilation of lmcrypt. Actually lmcrypt uses five imports from the dll. The following is the example on how to bind the lmgr-dll to lmcrypt in Borland C/C++ compiler.
NAME LMCRYPT DESCRIPTION 'binding lmgr326a.dll to lmcrypt' HEAPSIZE 32768 STACKSIZE 32768 IMPORTS _lc_init=LMGR326A.lc_init _lc_perror=LMGR326A.lc_perror _lc_set_attr=LMGR326A.lc_set_attr _lc_cryptstr=LMGR326A.lc_cryptstr _lc_free_mem=LMGR326A.lc_free_mem
Be sure to spell correctly, there are also uppercase export functions of the lmgr-dll that do not work with lmcrypt.
Compile lmcrypt.exe and run it ... verdammt! same lame error again ...
One needs a lot of patience with Flexlm. Again it complains on bad vendor codes. But don't worry. We're near the end of our adventure. Look on the error message - Flexlm always produces very sounding error messages (no 1800000c general protection fault). Apparently something with lc_init went wrong. Set a breakpoint on lc_init and look on the arguments that are handed over to lc_init. It should look like that: short type; /* there are a couple of different code structs */ int (seed1)^(vendorkey5); /* seed 1 Flexlm checksum */ int (seed2)^(vendorkey5); /* seed 2 Flexlm checksum */ int vendorkey1; /* vendor key 1 */ int vendorkey2; /* vendor key 2 */ int vendorkey3; /* vendor key 3 */ int vendorkey4; /* vendor key 4 */ short flexlm_version; /* to some extent the different versions */ short flexlm_revision; /* were the root of this essay */ Compare this to the parameters that were handed to lc_init by your target at the top of the essay: The struct type was declared as int, and not as short. You can check this at the vendor code check. Set your breakpoint and look how the single vendor codes are processed: They are misaligned by one word. (I was not able to find out, whether this was a real bug within the Flexlm SDK, or whether that was intended by the Globetrotter guys as a little additional obstacle on the way to a license generator)
Now the solution is simple: locate the declaration of the code structs (they are within lmclient.h) and correct them. Recompile lmcrypt ... ... and generate your own licenses ...
A little tip at the end: I use something like lmcrypt -i licproto.dat -o license.dat -f -longkey -verfmt 5.1 on DOS command prompt. It gives You control over all the little options we need :-)
In my opinion Flexlm is far from being dead. There are targets incorporating vendor defined checkout filters, i.e. the checkout routine is incorporated into the target. There might be further restrictions for a successful checkout, e.g. only a certain range of host IDs is allowed. Or, even worse, checksum (or other cryptographic) protections of the target and the checkout mechanism might be incorporated. The protectionists could use anti-debugging tricks, encryption of the subroutines they don't want us to use ... Hence I believe the race with the protectionists will go on. And we should sharing our knowlegde in order to stay on the winning side.
The following code snippets should familiarize you with the 'look and feel' of Flexlm.
First code snippet shows the location of the good boy/bad guy decision. :10006A78 8B450C mov eax, dword ptr [ebp+0C] :10006A7B 83C00C add eax, 0000000C :10006A7E 50 push eax :10006A7F FF7510 push [ebp+10] :10006A82 E8C8190000 call 1000844F <- prepares the vendor code check :10006A87 83C408 add esp, 00000008 follow this one :10006A8A 8BF8 mov edi, eax :10006A8C 85FF test edi, edi :10006A8E 742B je 10006ABB :10006A90 8B470C mov eax, dword ptr [edi+0C] :10006A93 B9FFFF0000 mov ecx, 0000FFFF <- certain properties of valid :10006A98 8BD0 mov edx, eax vendor codes are checked :10006A9A 23C1 and eax, ecx in these lines :10006A9C C1EA10 shr edx, 10 :10006A9F 81F2EFA3FFFF xor edx, FFFFA3EF :10006AA5 23D1 and edx, ecx :10006AA7 2BD0 sub edx, eax :10006AA9 8B4704 mov eax, dword ptr [edi+04] :10006AAC 8BC8 mov ecx, eax :10006AAE 2480 and al, 80 :10006AB0 83E17F and ecx, 0000007F :10006AB3 894704 mov dword ptr [edi+04], eax :10006AB6 894DFC mov dword ptr [ebp-04], ecx :10006AB9 EB03 jmp 10006ABE * Referenced by a Jump at Address 10006A8E(C): :10006ABB 8B55FC mov edx, dword ptr [ebp-04] * Referenced by a Jump at Address 10006AB9(U) :10006ABE 85FF test edi, edi :10006AC0 0F8480020000 je 10006D46 <- bad guy jumps here :10006AC6 85D2 test edx, edx :10006AC8 0F8578020000 jne 10006D46 <- bad guy jumps here Second code snippet shows the body of the vendor code check. Note that at this stage there is no date check, i.e. bad key data error doesn't indicate that keys are expired. :1000844F 56 push esi :10008450 57 push edi :10008451 FF74240C push [esp+0C] :10008455 E8C4FFFFFF call 1000841E <- checksum over vendor string :1000845A 8B742414 mov esi, dword ptr [esp+14] :1000845E 83C404 add esp, 00000004 :10008461 8BF8 mov edi, eax :10008463 FF36 push dword ptr [esi] <- vendor key 1 :10008465 E85C000000 call 100084C6 <- run encryption with key 1 :1000846A 83C404 add esp, 00000004 :1000846D 33C7 xor eax, edi :1000846F A320E80310 mov dword ptr [1003E820], eax :10008474 FF7604 push [esi+04] <- vendor key 2 :10008477 E84A000000 call 100084C6 <- run encryption with key 2 :1000847C 83C404 add esp, 00000004 :1000847F 3306 xor eax, dword ptr [esi] <- XOR with key 1 :10008481 330520E80310 xor eax, dword ptr [1003E820] :10008487 A324E80310 mov dword ptr [1003E824], eax :1000848C FF7608 push [esi+08] <- vendor key 3 :1000848F E832000000 call 100084C6 <- run encryption with key 3 :10008494 83C404 add esp, 00000004 :10008497 334604 xor eax, dword ptr [esi+04] <- XOR with key 2 :1000849A 330524E80310 xor eax, dword ptr [1003E824] :100084A0 A328E80310 mov dword ptr [1003E828], eax :100084A5 FF760C push [esi+0C] <- vendor key 4 :100084A8 E819000000 call 100084C6 <- run encryption with key 4 :100084AD 83C404 add esp, 00000004 :100084B0 334608 xor eax, dword ptr [esi+08] <- XOR with key 3 :100084B3 330528E80310 xor eax, dword ptr [1003E828] :100084B9 5F pop edi :100084BA 5E pop esi :100084BB A32CE80310 mov dword ptr [1003E82C], eax :100084C0 B820E80310 mov eax, 1003E820 :100084C5 C3 ret The third code snippet shows the checksum algorithm, which is describe above. :1000841F B835C90415 mov eax, 1504C935 <- save seed of checksum in eax :10008424 57 push edi :10008425 33F6 xor esi, esi :10008427 8B54240C mov edx, dword ptr [esp+0C] <- vendor name :1000842B 803A00 cmp byte ptr [edx], 00 :1000842E 741C je 1000844C <- step out if ready * Referenced by a Jump at Address 1000844A(C) <- head of loop :10008430 0FBE3A movsx edi, byte ptr [edx] <- read one character :10008433 8D0CF500000000 lea ecx, dword ptr [8*esi+00000000] :1000843A 42 inc edx :1000843B D3E7 shl edi, cl <- no shift for first character :1000843D 33C7 xor eax, edi shift second character one byte higher :1000843F 46 inc esi third character two bytes :10008440 83FE04 cmp esi, 00000004 fourth character three bytes :10008443 7C02 jl 10008447 :10008445 33F6 xor esi, esi <- fifth byte: no shift * Referenced by a Jump at Address 10008443(C) and so on until the end of the :10008447 803A00 cmp byte ptr [edx], 00 vendor string :1000844A 75E4 jne 10008430 <- process next character ...............
Snippets from the LMGR325C.dll DisassemblyAgain the first snippet shows the location of the good boy/bad guy decision. :10009730 8B450C mov eax, dword ptr [ebp+0C] :10009733 83C00C add eax, 0000000C :10009736 50 push eax :10009737 8B4510 mov eax, dword ptr [ebp+10] :1000973A 50 push eax :1000973B E8EDBA0000 call 1001522D <- step in here, it's the vendor code check :10009740 83C408 add esp, 00000008 :10009743 8945F4 mov dword ptr [ebp-0C], eax :10009746 837DF400 cmp dword ptr [ebp-0C], 00000000 :1000974A 0F8441000000 je 10009791 :10009750 8B45F4 mov eax, dword ptr [ebp-0C] :10009753 8B400C mov eax, dword ptr [eax+0C] :10009756 350000EFA3 xor eax, A3EF0000 :1000975B 8945FC mov dword ptr [ebp-04], eax :1000975E C16DFC10 shr dword ptr [ebp-04], 10 <- look at the characteristic bit-juggling :10009762 8165FCFFFF0000 and dword ptr [ebp-04], 0000FFFF :10009769 33C0 xor eax, eax :1000976B 8B4DF4 mov ecx, dword ptr [ebp-0C] :1000976E 8B490C mov ecx, dword ptr [ecx+0C] :10009771 81E1FFFF0000 and ecx, 0000FFFF :10009777 2BC1 sub eax, ecx :10009779 F7D8 neg eax :1000977B 2945FC sub dword ptr [ebp-04], eax :1000977E 8B45F4 mov eax, dword ptr [ebp-0C] :10009781 8B4004 mov eax, dword ptr [eax+04] :10009784 83E07F and eax, 0000007F :10009787 8945F8 mov dword ptr [ebp-08], eax :1000978A 8B45F4 mov eax, dword ptr [ebp-0C] :1000978D 83600480 and dword ptr [eax+04], FFFFFF80 * Referenced by a Jump at Address 1000974A(C) :10009791 837DF400 cmp dword ptr [ebp-0C], 00000000 :10009795 0F841F000000 je 100097BA <- bad guy jumps here, dead listing reverser :1000979B 837DFC00 cmp dword ptr [ebp-04], 00000000 comes from here! :1000979F 0F8515000000 jne 100097BA <- bad guy jumps here The last snippet shows the vendor name checksum generation. You should carefully look at identical structure of the corresponding snippet from lmgr326a.dll :100151D4 53 push ebx :100151D5 56 push esi :100151D6 57 push edi :100151D7 C745F8F240A358 mov [ebp-08], 58A340F2 <- save seed of checksum in [ebp-08] :100151DE C745FC00000000 mov [ebp-04], 00000000 * Referenced by a Jump at Address 1001521B(U) :100151E5 8B4508 mov eax, dword ptr [ebp+08] <- head of loop :100151E8 0FBE00 movsx eax, byte ptr [eax] :100151EB 85C0 test eax, eax :100151ED 0F842D000000 je 10015220 <- step out if ready :100151F3 8B4508 mov eax, dword ptr [ebp+08] :100151F6 0FBE00 movsx eax, byte ptr [eax] :100151F9 8B4DFC mov ecx, dword ptr [ebp-04] :100151FC C1E103 shl ecx, 03 :100151FF D3E0 shl eax, cl :10015201 3145F8 xor dword ptr [ebp-08], eax :10015204 FF4508 inc [ebp+08] :10015207 FF45FC inc [ebp-04] :1001520A 837DFC04 cmp dword ptr [ebp-04], 0x4 <- inner counter counts until 3 :1001520E 0F8C07000000 jl 1001521B :10015214 C745FC00000000 mov [ebp-04], 0x0 * Referenced by a (U)nconditional or (C)onditional Jump at Address 1001520E(C) :1001521B E9C5FFFFFF jmp 100151E5 ..............