http://www.ktx.com - Webpage.
3D Studio Max R2.5 - Historical Reference
v3.1 - 3rd June 2000 (Windows NT ONLY)
After having this tutorial censored in February by the bigoted BSA I have decided to take a risk updating and republishing, if I receive a legitimate request to nuke this document from AutoDesk it will disappear without question (there are already several cracks for 3DSMax v3.1 so this document will not damage anyone, the best I have seen is from Harvest (by my ex-SiEGE colleague Kashmir), the worst is DOD's which is really best forgotten). We'll see from this discussion of v3.1's protection that AutoDesk have actually learnt quite a lot since their pretty poor attempt to protect R2.5, yet, sadly, what really lets this protection down are the intrinsic vulnerabilities of Sentinel itself although Kinetix can certainly accept some of the blame (see the CRC checks below). We'll start off by looking at the InstallShield protection, a Serial # of format 3-8 and a CD-Key.
The input boxes all accept fixed length inputs, looking in the InstallShield temporary directory a few dll's show up but only Identify.dll really stands out, (InstUtil.dll sounds like its an installation utility dll and at 4.5k its too short to house a meaningful protection, Browse.dll & Regplugs.dll are also easily eliminated). By looking closely around the exports and decompiling the script you'll find that Identify.ValidateKeyWithPrefix() is the function to analyse, you can verify that patching a 1 in EAX when it returns allows the installation to continue.
I took only a very brief look at how this function works, its quite simplistic with a table and some basic arithmetic operations and a single XOR, what is tedious about the scheme is working out which positions of the Serial Number generate the corresponding CD Key, (its easy to find the good Key as its compared at offset 0x2651). You'll find later that 3DSMax itself checks that the first 3 digits of your Serial Number are 110, the rest you can freely make up (range 00000000-99999999), so one example would be Serial Number 110-99999999, CD Key 2SERDV, you get the idea though, lets install.
Next we'll load 3dsmax.exe into IDA (if you really want to save some searching apply Killer_3K's Sentinel FLIRT signature), what you'll find is that the protection is based around sproFindFirstUnit(), sproRead() & sproQuery(), the use of the query is new. Patching sproFindFirstUnit(sub_5D8B61) is the first thing to do, this is very easy and I propose not to describe it. Next we have to find the correct Authorization code, this is pretty trivial, 5D2251 checks the length (8) and 5D2497 does the real compare, for my example above the code is B63A197B.
sproRead(_5D8D30) has 2 references, each time 0 is pushed as the address to read (i.e. the dongle ID), this is the same as for R2.5, I couldn't get this to trigger in normal 3DSMax use, I'm pretty sure its provided as an interface for plugin developers. Trivially rewrite sproRead() just in case, you should place a bpx on sproQuery(_5D9120) though, it'll take some time to trigger. As we have no way of knowing exactly what the query response should be we'll rewrite sproQuery() to return at least a string we will recognise :-
:005D9142 JZ 005D9144 <-- To our routine.
:005D9144 PUSH EBP <-- Save EBP.
:005D9145 MOV EAX, [ESP+18] <-- Get number of bytes in query string.
:005D9149 XCHG ECX, EAX <-- Into ECX.
:005D914A CALL $+5 <-- Set up delta.
:005D914F POP EBP <-- Delta.
:005D9150 LEA ESI, [EBP+12] <-- Start of our query return (max. 56 bytes).
:005D9153 MOV EDI, [ESP+20] <-- Where to store it.
:005D9157 REPZ MOVSB <-- Move.
:005D9159 XCHG ECX, EAX <-- Restore ECX and clear EAX.
:005D915A POP EBP <-- Required.
db 56 dup(0) <-- Simulated query response.
As you will see from SoftICE, only 4 bytes of the query response are verified by the strcmp() at 0046F65A, the query strings are not the same on each occasion however. In fact this is further complicated because this area of code is CRC checked from several locations, here's one such check :-
:00431058 MOV ESI, 0046F190 <-- Start from here.
:0043105D XOR EAX, EAX <-- Clear EAX.
:0043105F CMP ESI, 0046F900 <-- Check till here.
:0043106A MOV ECX, 0046F190
:00431071 XOR EDX, EDX <-- Clear EDX.
:00431073 MOV DL, [ECX] <-- Get byte @ [ECX].
:00431075 INC ECX <-- Increment ECX.
:00431076 CMP ECX, 0046F900 <-- Done all checking.
:0043107C NOT EDX <-- EDX is checksum.
:00431082 JB 00431071
:00431084 SUB EAX, [006298A4] <-- Should be 0.
If you patch the strcmp() you introduce problems with all of the CRC checks and 'thats bad, very bad' (in Rainman style, if you saw the film with Dustin Hoffman :-) ). Its just not practical to sit and wait or try to find all the checks. Instead we'll modify our sproQuery() emulation code to ensure the hard work is done for us :-
:005D9142 JZ 005D9144 <-- To our routine.
:005D9144 MOV EAX, [ESP+14] <-- Number of bytes in query string.
:005D9148 XCHG ECX, EAX <-- Place in ECX.
:005D9149 LEA ESI, [ESP+38] <-- Get good response address.
:005D914D MOV EDI, [ESP+1C] <-- Where to place it.
:005D9151 REPZ MOVSB
:005D9153 XCHG ECX, EAX <-- Restore ECX/Clear EAX.
Our strcmp() now works as we require and there is no need to get our hands dirty chasing and patching Kinetix's modification check routine. So we see here that although Kinetix have made some improvements, their protection is i). still to easy to isolate and ii). has implementation mistakes, I heard a rumour that version 4 will see a switch to a much tougher license manager protection, lets wait and see :-). See Kinetix, there is no ready-made crack here, the losers who pirate your software and deprive you of worthy support wouldn't have a clue what to do with the information here.
R2.5 - 6th October 1999
Welcome to this dongle tutorial where I'm re-examining an old foe, a Sentinel. I'll assume you have downloaded the pertinent files for this tutorial or have access to them, the first step is to run maxauth.exe to authorise your copy of 3D Studio Max. I'm not particularly concerned how you crack this part, bpx GetDlgItemTextA and trace, patch by working back from the message box (disassembly), or simply type 1039120D (Serial # 661-93842762, CD-Key V13M).
Running 3DSMax produces unsurprisingly an error, however you should easily be able to find out the address of the nag with a well-timed bpx MessageBoxA. 00450F21 looks like the offending routine, note the CALL EBP instruction, which of course is really a call to [MessageBoxA]. We can now *attempt* to examine 3dsmax.exe in W32Dasm, however something's up, try it and see, instead of disassembling, W32Dasm appears to involuntarily launch 3DSMax and if your lucky the worst you'll get is a return to SoftICE with an error.
At this point, we have several options, either figure out what tricks are being used to fool W32Dasm or use IDA which does not seem to have the same problem. I played with W32Dasm for a little while with a bpx ReadFile at the file open dialog and traced painstakingly through the memory allocation, file opening and found the offending instruction at 0045C6D2, unfortunately reversing the zero flag here still gives an error inside cw3220.dll which I couldn't see much point in trying to solve.
This leaves us with IDA, however disassembling 3dsmax.exe I fear may take a while. In the interim lets commence our probes using SoftICE, a possible line of attack maybe with a bpx CreateFileA, the reason being as follows. It is known that the program must open a driver file to communicate with the dongle (in this case sentinel.vxd) and as this file is not installed on our system the API call is certain to fail so we can impose an IF condition upon this breakpoint. Lets do the following:
bpx CreateFileA if EAX==ffffffff <-- 2 equals
* Note you can use bpio -h 378 rw also.
Sure enough, after several irrelevant returns SoftICE stops on the following:
:0055214C PUSH 00552120
:00552151 CALL KERNEL32!CreateFileA
This code is now where we want to start tracing from, its probably easier to continue probing using the SoftICE loader and the g command. Needless to say the conditional jump after this break needs to be reversed. Once back at this code we'll work out how many times we can press F12 before our message box snaps, you should find that 6 or so presses or address 00450DFD is as far as you can go.
Evidently, 00450DFD is a good entry point, recalling that we know the address of the message box (0045FD21), this is not too far away and an analysis of the code in between is certainly desirable. Just simple stepping and perhaps some Zen feeling ought to make this code suspicious (the following is taken from IDA).
:00450E5C MOV EDX, DWORD_561248
:00450E62 XOR EDX, 73ADh
:00450E68 PUSH EDX
:00450E69 LEA EDX, [ESP+678+var_464]
:00450E70 PUSH EAX
:00450E71 CALL SUB_552A50
:00450E76 TEST AX,AX <-- AX = 3 here i.e. dongle not present.
:00450E79 JZ loc_450F23
Look at this code and understand why this *must* be a check, also look at what happens when we fail the first JZ, incredible as it may seem, the program does a trite repetition of the code above, 3DSMax obviously attempts to access the dongle 3 times before displaying the message box. We can using Sentinel's own API guide attempt to match this code to a specific function, it looks to me like sproFindFirstUnit() or something equivalent, A0A3 I think is Kinetix's developer ID. Naturally we'll use this '7242' as a search string to identify any other Sentinel checks.
As I've seen a few Sentinel's over the years I would expect to find 15 or so other references to '7242', the proximity of the addresses should be clear because this I am certain is a library which every developer is using regardless of whether they use all of its functions. Using IDA you'll be able to see that most of these functions are never reached, that's the great thing about IDA, its following all the execution paths so unless you receive pages of errors you can be pretty confident of its results.
As an aside, there is also a check inside util.dll, one of which is provided as an interface for use by plugin developers, unmangled its exported as HardwareLockID, and it seems to call sproFindFirstUnit (CALL 2802BEF0) & sproRead() (CALL 2802C0C0) to retrieve the serial number, you'll need to patch this as you see fit, at the end of this function EAX must hold your dongle serial number :-), else XOR EAX, EAX no dongle.