Saturday, January 18, 2014

Pwning a SafeNet Microdog Part 4 - Emulating the 3.4 (Part 2)

Picking up right after the last part (because it was only like 30 seconds ago), we can now emulate DogCheck(), which is fine, but plenty of other payloads need to be inspected... let's gun them down one at a time:

0x01 - DogCheck() - DONE!
0x02 - ReadDog()
0x03 - WriteDog()
0x04 - DogConvert()
0x0B - GetCurrentNo()
0x14 - Login *NEW*
0x15 - SetDogCascade
0x64 - DisableShare()
0x65 - EnableShare()

[ReadDog()]
Ok, so ReadDog()... pasting from my last post:
  Reads: DogCascade,DogAddr, DogBytes, DogData,DogPassword
  Writes: DogData
  Returns: 0 on success - errcode otherwise.
  Desc: Reads n bytes from the 200 byte flash memory area starting at y where n is DogBytes and y is   DogAddr. Dongle password is required for it to work properly.


By simply calling this and dumping the packet, we get:



Hrmm... DogCascade might not be the right value - we'll have to come back to that later ;)

But as you can see, everything looks as we've expected on our end. For now, let's make a buffer of 200 bytes in our ioctl wrapper and grab DogBytes of it starting at DogAddr and sending that back as the payload... like this:



Ok, so reading is working (yes, I realize I made the memory static which is silly if I'm going to be writing in the next step... I'm gonna fix it, haha.)

Now, because we're somewhat shooting for completion... how about checking our password against a value... The errorcode that gets thrown back from the dongle if the password is wrong happens to be 10053... as a result, let's set our password for this dongle to be:

unsigned int dog_pass = 0xBADF00D;

and then return 10053 if it doesn't match instead of copying the buffer.



[WriteDog()]
 Reads: DogCascade,DogAddr, DogBytes, DogData,DogPassword
 Writes: None
 Returns: 0 on success - errcode otherwise.
 Desc: Writes n bytes to the 200 byte flash memory area starting at y where n is DogBytes and
 y is DogAddr. Dongle password is required for it to work properly.

Ok, so write is basically the same thing as read... I don't know if we really want to go into that one... it's basically the same password check but with updating the buffer or passing back 10053 if the password is wrong.

[DogConvert()]
 Reads:   DogCascade,DogBytes,DogData
 Writes:  DogResult
 Returns: 0 on success - errcode otherwise.

 Desc: Take the buffer of 1-63 bytes in DogData and send it to the dongle where it returns a 4 byte hash of the data based upon the current algorithm selected. The last 4 bytes of the 200 byte internal flash memory determines the algorithm; byte 196 decides the algorithm and bytes 197,198, and 199 decide the algorithm descriptor. As a result, 16,777,216 possible algorithms exist.

Dumping the packet and we get.



Wait a minute!? Opcode 0x08?!? This shouldn't exist... wtf is this?!

Looks like we have another undocumented opcode. Basically, there's a global in the driver that sets the share bit to enabled or disabled... this one lets you set the state to whatever you want as a 1 byte value... I call it MD_SETSHARE. Its payload data is one byte that sets the internal share state of the dongle... just set the return code to zero and leave it alone :)

After handling that one, we get :



Now we're cookin'!!!

This one is pretty easy  - you can emulate this one simply by hashing whatever data you get. Your goals in this one might be twofold:

1. Create your own hashing algorithm based on the last 4 bytes of the internal memory (much like how the hardware cryptoprocessor does it).

2. Talk to an actual dongle, record the responses into a rainbow table, and save those for later when a convert request is made and play the entry lookup game in a hash table.


Pardon the hacked together code - I vowed to spend less than a few minutes on this, haha... basically, it's a rainbow table lookup from a binary file loaded when my hook gets initialized.

The request is 4 bytes consisting of 0xBB 0xBB 0xBB 0xCD which I set in my rainbow table to a response of 0x10101010

The result?


Granted, I return a null if it can't find the value... this really isn't so much error handling as laziness really, but it will let us exploit a program later by packing custom files with no response key at all (like a 'null repack' attack).

This was never really an error handling case considering the cryptoprocessor would never NOT be able to give you an int... Anyway, DogConvert() is done. For the first method (your own hashing), just hash the data and kick an int back into the payload, which is funny because the next one is:

[GetCurrentNo()]

 Reads: DogCascade, DogData
 Writes: DogData
 Returns: 0 on success - errcode otherwise.

 Desc: Read a unique manufacturer serial number from the dongle. Unlike the vendor ID, this one is always unique to ONE specific dongle. Useful to identify customers, etc. and normally 4 bytes.

This one is very simple, think of it like DogConvert() but with passing back a static 4 byte int all the time... that's really all there is to it.

unsigned static int DogMfg = 0xBADDEAD;





[RECAP]

Ok... so let's see how we're doing:

0x01 - DogCheck() - DONE!
0x02 - ReadDog()  - DONE!
0x03 - WriteDog() - DONE!
0x04 - DogConvert() - DONE!
0x0B - GetCurrentNo() - DONE!
0x14 - Login *NEW*  - Technically DONE!
0x15 - SetDogCascade()
0x64 - DisableShare() -  - DONE! (Only handled in the Parallel port driver, also Set Share())
0x65 - EnableShare()  - DONE! (it's actually hidden SetShare())

[Login()]

Ahhh yes... that bastard hidden function from before... so basically, this one is pretty straightforward now that you've seen the rest of the functions:



Basically, you just copy the 8 byte vendor_id from the device descriptor or from where you dumped it from the client lib (two blog posts ago *wink* *wink*) to the response payload, set return code to 0, miller time - simple as all hell.

[SetDogCascade()]
 Reads: DogCascade, DogPassword, DogData
 Writes: None
 Returns: 0 on success - errcode otherwise.
 Desc: Sets the dongle cascade to 0-15, determined by the byte in DogData.

Does basically the same thing as MD_SETSHARE... we take the only byte in the request payload and change our internal cascade state of the dongle to that... can only be 0-16. We don't even have to do error handling because the lib will do that for us.


[SetPassword()]
Reads: DogCascade,DogPassword,NewPassword
 Writes: None
 Returns: 0 on success - errcode otherwise.
 Desc: Sets a new dongle password.

No... I didn't forget about this guy. It's just that he actually didn't show up until the 4.0 library (along with a few other codes as well). VERY few of the last 3.4 libraries for windows support this guy (opcode 0x7)...

The implementation here is very simple:



What's next? Well.. we have all the opcodes mapped and handled... Basically, any program we run this against will interact with our lib as if a driver and dongle were on the other end.

Next time, we'll grab an actual dongle, get some keys out, and make somewhat of a structured object in memory to handle all of our dongle data. For now, please enjoy this video where I already did that and am playing a game that used about 1200 dongle transactions for decrypting all their data files, various presence checks, etc.



1 comment:

  1. Sir can u please upload all header files... in SDK i got onli gsmh.h header file not dlfcn file... please upload all files
    thank u....

    ReplyDelete