SURFCAM 2000 B117

http://www.surfware.com

I first ran into Surfcam back in my old SiEGE days in 1997, the core protection remains the same today, a Sentinel SuperPro dongle. I discuss here only how you can totally reverse engineer the protection scheme and NOT how to make a ready-made crack. It is also worth noting that all of this work was carried out without any access to the dongle so there are many assumptions. Our efforts will be focused on surfcam.exe which I suggest you load into IDA, please also note that this process involved lots of tedious pouring over code which I spare you most of the details of, I assume you can bpm for yourselves, once loaded into IDA apply your favourite Sentinel SuperPro signature and lets start, its going to be a long session.

IDA recognises the core 3 protection functions, sproFindFirstUnit(sub_648D30), sproRead(sub_648F00) & sproQuery(sub_6492F0). sproFindFirstUnit() has 3 xrefs, in SoftICE one xref will locate you the developer ID (0xFA4B), browsing in IDA another finds you another (ID 0x25CF), this implies immediately that either Surfcam supports 2 of its own dongles or 1 of these is from a 3rd party. Whichever it is, having sproFindFirstUnit() fail doesn't seem to checkout any other developer ID other than 0xFA4B, so developer ID 0x25CF is not our Surfcam dongle since it makes no sense not to check it out at launch time.

My first thought was that developer ID 0x25CF was Predator Virtual CNC's dongle which ships as Surfcam's editor, there is a tight integration between the 2 products that allows Predator to support Surfcam's dongle but NOT vice-a-versa, so the Predator Virtual CNC dongle alone is NOT capable of driving Surfcam. This theory was scuppered however when I investigated Predator itself and saw it checked out another developer ID (0xEE8E), by sheer co-incidence I saw another reference to the mysterious 0x25CF developer ID in another totally unrelated target (ASAP v6.6), so I'm mystified who this dongle belongs too.

The reason the 0x25CF reference is important is that there are 4 sproQuery() references which could only be executed assuming the dongle 0x25CF was checked out, this leaves us with only 2 references for our Surfcam dongle (5B2759 & 65258E), after some tedious backtracing I found the reason why the 0x25CF dongle is never checked out, a missing registry key named 'Footprint', this fact will prove very important when we come to emulating sproQuery(). I ran into Nolan Blender on IRC and he suggested one theory this dongle exists is due to SentinelLM (sub_569826), or a forthcoming implementation of, as there are traces of SentinelLM in the disassembly, one wonders though just how 2 other product vendors also had these references.

I patched sproFindFirstUnit() trivially and emulated sproRead() as per usual (code shown below).

:00648D4B CMP WORD PTR [ESI], 7242h <-- Packet record valid.
:00648D50 JZ SHORT LOC_648D60 <-- Change this to JZ 648D52 or NOP it.
:00648D52 MOV AX, 2 <-- Change this to MOV AX, 0.
:00648D56 POP ESI
:00648D57 POP EBX
:00648D58 RETN 8

:00648F20 JZ SHORT LOC_648F30 <-- Change this to JZ 648F22 or NOP it.
:00648F22 PUSH EBP <-- Save EBP.
:00648F23 MOV EAX, [ESP+14h] <-- Get word to read (from stack).
:00648F27 SHL EAX, 1 <-- Multiply by 2.
:00648F29 CALL 648F2E <-- Prepare to set up delta offset.
:00648F2E POP EBP <-- Delta.
:00648F2F LEA EDI, [EBP+17h] <-- Point at start of simulated dongle memory.
:00648F32 MOVZX EAX, WORD PTR [EAX+EDI] <-- Read a simulated word.
:00648F36 MOV EDI, [ESP+18h] <-- Get return address (from the stack).
:00648F3A MOV [EDI], AX <-- Place the return word.
:00648F3D XOR EAX, EAX <-- Clear EAX (SP_SUCCESS).
:00648F3F POP EBP <-- Restore EBP.
.....
:00648F45 <-- Start of simulated dongle memory.

This routine is obvious and is used by most scene crackers with minor variation, if you are anxious not to be accused of stealing this code, might I suggest you add an AND EAX, 0FFFFh just after 648F23 to deal with the possibility your stack isn't DWORD aligned (it happens rarely), and also swap the references to EDI for ESI, these are the only 2 registers along with EAX you can safely play with as they are pushed on the stack at the start of the function. As a rule I never patch sproQuery() before live debugging, I recommend you place a breakpoint on it, in Surfcam's case it is called before sproRead(). Here is the code :-

:005B2742 PUSH DWORD PTR [EBP+18] <-- Algorithm descriptor.
:005B2745 LEA EAX,[EDI+02]
:005B2748 MOV [ESI],EAX
:005B274A PUSH ESI
:005B274B PUSH DWORD PTR [EBP+10] <-- Return address.
:005B274E PUSH DWORD PTR [EBP+0C] <-- Query string (Testing).
:005B2751 PUSH DWORD PTR [EBP+08] <-- Number of bytes.
:005B2754 PUSH 0071F460 <-- Packet record.
:005B2759 CALL sproQuery()
:005B275E MOVZX EAX,AX <-- Extend status.
:005B2761 TEST EAX,EAX
:005B2763 JNZ 005B2777 <-- No jump, query SP_SUCCESS.

The first call uses an algorithm descriptor of 0xA with a query length of 0xC, note the pointer to the return address, meaning the dongle returns only the last 32-bits of the query response. All of Surfcam's reads and queries are performed twice (for data integrity perhaps, and also to drive anyone cracking it insane, it does believe me), in this instance the query response 0x0D88426D is recoverable (it is checked against 5 return strings, only the former is valid). Lets handle this first query :-

:00649312 JZ SHORT LOC_649314 <-- Patched (you could also NOP it).
:00649314 MOVZX ESI, WORD PTR [ESP+24h] <-- Algorithm descriptor (from the stack).
:00649319 CMP ESI, 0Ah <-- Was it 0Ah.
:0064931C JNZ SHORT LOC_64933D <-- No, lets handle it elsewhere.
:0064931E MOV EDI, [ESP+18h] <-- Get query string.
:00649322 CMP DWORD PTR [EDI], 74736554 <-- Was it 'Test'.
:00649328 JNZ SHORT LOC_64933D <-- No, lets handle it elsewhere.
:0064932A MOV EAX, 0D88426D <-- EAX = good response.
:0064932F MOV EDI, [ESP+20h] <-- Response address.
:00649333 MOV [EDI], EAX <-- Place response.
:00649335 XOR EAX, EAX <-- Clear status.
:00649337 POP EDI
:00649338 POP ESI
:00649339 POP EBX
:0064933A RETN 18h
:0064933D <-- More code will be added here as we deal with the query.

Setting breakpoints on read and query means we can log the checks. After several calls to the query (handled by our initial code above), Surfcam starts to read words from the dongle, the first phase reads words 0x1C-0x23 into an array and then words 0, 1 & 0x1B individually, recall that word 0 is the dongle ID (you should fix it) and word 1 is the developer ID (recovered from sproFindFirstUnit() ). The array 0x1C-0x23 is passed to sub_5C6390 (along with a key which is generated by XOR'ing word 0x1B with the developer ID & dongle ID). This function is a fairly simple encryptor :-

:005C6393 MOV ECX, [EBP+arg_4] ; Counter.
:005C6396 TEST ECX, ECX
:005C6398 JLE SHORT LOC_5C63B8
:005C639A MOV EAX, [EBP+arg_0] ; Pointer to bytes to decrypt.
:005C639D MOV EDX, [EBP+arg_8] ; ^ key.
:005C63A0 IMUL EDX, 345h
:005C63A6 ADD EDX, 269Dh
:005C63AC MOV [EBP+arg_8], EDX ; Save key for next round.
:005C63AF SHR EDX, 5
:005C63B2 XOR [EAX], DL ; ^ with dongle array.
:005C63B4 INC EAX ; Next byte.
:005C63B5 DEC ECX ; Loop counter.
:005C63B6 JNZ SHORT LOC_5C639D

At the end of this our newly encrypted array will be passed to sub_5C62B6 along with another ^ key (word 0x1B ^ developer ID) and a word checksum will be calculated, the result must be 0. Take some advice from me here, it isn't worth pouring over the decryption / checksumming functions just yet, settle for noting down the results and manually patching the checksum to 0, at the end of this function a DWORD, (the encrypted result from dongle words 0x1F1F1E1E) will be AND'd with 0x070002FF and TEST'd against 0x40000000, the result determines the value of flag [711554], you would like it to be 2, so make sure TEST 0x1F1F1E1E, 0x40000000 jumps.

Surfcam now switches back to the query and this time we get algorithm descriptor 0xB which is not handled by our current emulator. This is one of the trickiest parts to Surfcam and there isn't any one way of solving it, the code starts at 5C63F7, sub_5C65B9 is used to generate a random byte which is used to index into an array of query strings. This wouldn't be so bad if all the query results were actually the correct ones, they aren't, here is the decision making code :-

:005C6422 MOVZX EDX,BYTE PTR [EDX+EDI+006D7E60] <-- Get result byte.
:005C642A XOR EAX,EDX <-- ^ it with random.
:005C642C SUB [ESI+006D7F90],EAX <-- Sub result from array.

Surfcam uses an internal array of 32 bytes (8 DWORD's) each set to the good value 0xA. If any of these entries finds there way to 0xB at the critical checking code 5C645E then you get a fatal error and Surfcam will terminate, the code is shown below :-

:005C645E CMP [EDX*4+EDI],ECX <-- Check query response.
:005C6461 SETZ BL <-- Flag BL.
:005C6464 ADD [ESI+006D7F90],EBX <-- Maintain array integrity.

So, at 5C6422 Surfcam determines whether a given query response for a randomly generated byte is correct and will then either subtract 1 from 0xA to leave 9 or subtract 0 (leaving 0xA). At 5C645E the query response is checked and accordingly EBX is flagged to 0 or 1, if the query response was genuine the array integrity is maintained by adding 1, if the query response was false the array integrity is maintained by adding 0. This is a very clever mechanism since it guarantees that a cracker cannot simply patch the SETZ BL instruction, we have to know whether or not the query used was genuine.

I thought for a while what would be the best way to defeat this, I had some elegant ideas involving patching the Surfcam internal table so that I would always set BL to true, this would involve establishing true/false criteria for 255 values, too much work I judged, see the integrity checking part of this tutorial. One might also consider hooking Surfcam's internal table directly and working out whether the query was genuine that way, in the end I settled for a rudimentary but effective solution :-

:00649349 PUSH EBP <-- Save EBP.
:0064934A CALL 64934F
:0064934F POP EBP <-- Delta again.
:00649350 MOV EAX, 8 <-- 8 DWORD's.
:00649355 MOV EDI, 6D7F90 <-- Start of integrity array.
:0064935A LEA ESI, [EBP+1Fh] <-- Start of good array.
:0064935D XCHG EAX, ECX <-- Swap (preserving ECX).
:0064935E REPZ MOVSD <-- Maintain array.
:00649360 XCHG EAX, ECX <-- Swap back.
:00649361 MOV EDI, [ESP+24h] <-- Query response.
:00649365 MOV [EDI], EAX <-- Place 0.
:00649367 POP EBP <-- Restore EBP.
:0064936E Array of 8 DWORDS (0xA) each.

Effectively all I do here is maintain Surfcam's array and send back a query response of 0, thus the SETZ BL always fails as there is no query sent to the dongle that requires 0 as a good response. Back to reading the dongle we go. Next Surfcam reads words 0xE to 0x1B inclusive, then words 0 & 1, the resulting array of 28 bytes is passed to sub_5C6390 with a key and encrypted, once again sub_5C62B6 generates a checksum word (0 required) and finally sub_5C6311 calls sub_5C635A which uses entries 0xF-0x1A (24 bytes) to decrypt the username (you can accept this is what happens and settle for garbage for a user name at this stage), 4 bytes generate 5 letters in a simple table driven algorithm. The decrypted word 0xE is the sim_id.

The above read loop is repeated as I recall and another call to the query looms (the other reference we found in IDA). This time the algorithm descriptor is 0xE. The query is verified at this code :-

:004684F2 PUSH EAX <-- Query response.
:004684F3 CALL 005C6734 <-- Use it in a loop.
:004684F8 POP ECX
:004684F9 MOV ESI,EAX <-- Save result of loop in ESI.
:004684FB CALL 005C718C <-- Effectively MOV EAX, 2A2C8A57.
:00468500 SUB ESI,EAX <-- Hope we get 0.
:00468502 NEG ESI
:00468504 SBB ESI,ESI
:00468506 INC ESI
:00468507 CMP ESI,01
:0046850A JZ 00468515 <-- Good jump.

This code is definitely integrity checked by the program so don't even think of tampering with the JZ (not unless you fancy opening another can of worms). Unfortunately, sub_5C6734 is a truly horrid function, which loops 0x2F1h times (this makes a brute force attack considerably longer), the function code is also integrity checked too so don't tamper with the loop value ..... eventually I bit the bullet and ripped the code from IDA writing a crude brute forcer. Don't bother running it on a slow PC either, (thats sadly what I have), who knows, maybe Surfcam's developers will be kind enough to give me a lucrative job protecting their software :-), my friend on a P700 kindly oblidged and 7hrs later the answer 0x77C08222 arrived.

Now that you've built up a picture of exactly what Surfcam does (reading/querying), we'll investigate a little more what each decrypted word actually does. This will centre around the 16 decrypted bytes we get from the array 0x1C-0x23. With bpr work lets see what we can establish.

i). Word 0x1C = 0x352, explicit check (5C67A0).
ii). Word 0x1E (low byte) = options and user type.
- AND with 0x5F = 0x5F (5-axis, 5C5CB3), other options force this to 0x7F.
- (high byte) = extras (SAT, 5-axis Trim, CATIA, Parasolid) = 0xFFh (5C5BBD).
iii). Word 0x1F (low nibble) = user type (0xF).
iv). Word 0x20 = 0xFFFF, no time limit (468656).

Surfcam also reads words 0x8-0xA inclusive and subjects them to the same encryption / checksum word generation as our previous blocks. The results don't seem to lead anywhere, I have some suspicion they are related to the time expiry, assume 0x8 & 0x9 need to be 0 in the absence of any better information. At this stage we know now what our dongle words should decrypt too and we also have to ensure that the checksum word is 0 for each block. This is the part that gave me the most headache since word 0x1B is used to generate the key for the checksum function. Your reference for this (it would take too long for me to explain it all) is the supplied source code, this generates the 112 bytes of dongle memory needed for your sproRead() for any given user name.

Within Surfcam there are a further 11 protected files (cat2dsn.dll, dsn2igs.dll, Dsnfix.dll, PEditorSimLibrary.dll, prsd2dsn.dll, pscp_300.dll, sat2dsn.dll, Spostg.exe, Spostl.exe, Spostm.exe & Sx32w.dll). Earlier in this tutorial I talked about integrity checking in Surfcam so I'll just discuss it briefly here with a small code snippet, 11 portions of code are integrity checked in this manner :-

:00445E4C XOR EAX,EBX <-- ^ check.
:00445E4E POP ECX
:00445E4F JZ 00445E58 <-- Jump check passed.

In conclusion then we can see that Surfcam is a fairly strong dongle protection with plenty of tricks and traps, it certainly kept me entertained for some considerable hours. The full emulation routines can be found with the source code to the dongle memory generator here (3k). Finally, I should point out that you shouldn't use any of the information here to crack Surfcam and use it illegally, I don't support that in any way.


Return to Dongles Return to Main Index


© 1998,1999, 2000, 2001 CrackZ. 8th February 2001.