Sunday, January 19, 2014

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

So instead of going after communicating with a real dongle, it's probably better that we first have a decent emulator library because any values we get are virtually useless without having a robust way to manage responses and handle all the requests we need.

[Design]

There are a few items that we want to store on disk (basically things that aren't state related).

The Dongle Serial
The Manufacturer Serial
The Vendor ID
The Dongle Password
The 200 byte internal flash memory (of which the last 4 is the algorithm)

In addition, if we're going the rainbow table route, we need a way to store keys for different
algorithms selected. We *MIGHT* want to support changing the algorithm on the fly in a program (I know that if I was using this dongle to protect a program, that's DEFINITELY one thing I would think of ;) ).

As a result, we need a structure to quickly read the keys from a file to memory... a num_keys value is probably in order for that.

Then, some kind of structure that would be like:

Dongle Response
Algorithm used (basically copied from the last 4 bytes of the internal flash memory)
DogBytes - number of bytes in the buffer
DogRequest - The unencrypted payload (1-63/4 bytes)

So that will be our format for... let's call it doge.key -a quick little C program can init a file like this for us:


***WOW MUCH SECURITY... VERY MICRODOGE... SUCH HACK***

Anyway, I basically set everything to null - yes, you could just create a 224 byte null file.

[Extending The API]

So, I think that I would just be pleased as punch if the API we wrote (are writing) would support adding rainbow table entries on the fly, in addition to writing the vendor_id, mfg_serial, dog_serial, and maybe printing out this lovely data for us to see.

 [MD_RTADD]
 opcode: 0x539  -  1337 ;)
 unsigned long RTAdd()
 Reads: DogData, DogBytes, DogPassword
 Writes: None
 Return Value - 0 if successful, 13371337 if not.
 Desc: Basically, this is like DogConvert except we're going to pass what we want the dog response to be as DogPassword. In the driver, we will read the last 4 bytes of the flash memory to determine the algorithm, create a new rainbow table entry, and store it in our dog.key file.

 We'll also add 1338:
 [MD_SETDOGSERIAL]
 opcode: 0x53A
 unsigned long SetDogSerial()
 Reads: DogData
 Writes: None
 Return Value: 0 if successful, 13381338 if not.
 Desc: Sets the Dog_Serial value to the int stored in DogData.

1339:
 opcode: 0x53B
[MD_SETMFGSERIAL]
 unsigned long SetMgfSerial()
 Reads: DogData
 Writes: None
 Return Value: 0 if successful, 13391339 if not.
 Desc: Sets the Mfg_Serial value to the int stored in DogData.

1340:
opcode: 0x53C
[MD_SETVID ]
 unsigned long  SetVendorID()
 Reads: DogData
 Writes: None
 Return Value: 0 if successful, 13401340 if not.
 Desc: Sets the Dongle VendorID value to the 8 byte buffer stored in DogData.

aaaand 1341 to be safe:
opcode: 0x53D
[MD_RLRT ]
 unsigned long  ReloadRainbowTable()
 Reads: None
 Writes: None
 Return Value: 0 if successful, 13411341 if not.
 Desc: Sends a signal to the emu to reload the rainbow table from disk.


Of course, this means we'll have to also create our own request packet... no big deal :D

In the end, our additions look like this (notice that I'm calling LinuxUsbDriverEntry directly ^^)


Next, we'll focus on refactoring the emulator:




So, there's quite a bit going on here... I'm going to paraphrase what's going on.

[WTF]
Basically, we're still using the hook methodology, but we're made things a little more sophisticated - we're reading in a binary file (doge.key) on initialization to populate a data structure (Dog) in memory with key metadata what would be accessible during any kind of transaction. We then go on to read the binary file - pulling keys out of each subsequent 76 byte sequence containing the algorithm the key was generated under (so we can maintain algorithm switching functionality of the dongle), the response, the size of the response, and the actual request data itself.

The control flow is pretty basic - we hook our dongle call, decrypt the packet, find out what it wants, and construct a response in kind, encrypt it, and write it back to the program.

The switch statement in the function controls every call to the imaginary driver. Running a test program with a simple convert shows that our added keys are indeed working in the new version:


The test application itself is nothing more than an original C program where we test the dongle's functionality (compiled against our extended API, of course )



In the end, it's probably easier to make a python program that can seed  an initial dongle and generate the appropriate rainbow table values for you - for the sake of brevity, I'm going to leave that as an exercise to the reader..

Actually... nahhh


It reads something that looks like this:

Basically, all the character prefixes for each line tell the script what data is being loaded.
Next time, we'll interface with a real dongle to pull keys, and perhaps introduce MD4.0 as well :)


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.



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


[FOREWARD]
Ok, this one is probably going to be a bit longer than normal due to having several goals, seeing as how it's been a while, let's recap what we've done so far with the Microdog 3.4 library:


  •  Identified that Microdog version 3.4 is split into two pieces - a kernel module (usbdog.o/ko/.sys) and a client library (mhlinuxc.o / mhwin32.lib) which, when compiled with a program, offers an interface to the dongle functionality. We've also identified that both parts are unstripped.



  •  We can extract/repack the dongle's vendor ID and serial number - two values that determine if a program compiled with the dongle's client library will talk to a specific dongle.


So now, what do we want to do?

  • We want to learn about the client library before we start debugging it.
  • We want to optionally run a client lib modified with a real microdog dongle (if we have one) and 

collect some data on how it's actually supposed to work (and maybe some keys).

  •  We want to find any kind of documentation that would help us better understand our reversed functionality.
  •  We want to emulate the driver itself - the endpoint which the client lib performs all transactions.
  •  We, of course, also want to rebuild pseudo dongle functionality in the driver to act as if it's actually talking to the real hardware. This is going to be the biggest part.



[The Client Library]

So let's dig into the client library - what any developer would use in their application.


 Function names and globals still retain their original names, this is going to make it quite a bit easier.

Now, there are a couple of ways to attack this, we can either:
A. Check out the exports table, map the interfaces and come up with a header ourselves.
B. Hunt down the developer documentation that's all over online.

To save the trouble, this is what we're looking for (super paraphrased):

[Globals]
1.unsigned short DogAddr
The address of the 200 byte internal flash memory a read/write operation should start on.
2.unsigned short DogBytes
The number of bytes in DogData - if reading/writing, can't exceed 200-DogAddr. Normally
between 0-200, but DogConvert() operations only allow 1-63.
3.unsigned long DogPassword
Some functions such as setting a new password, reading, or writing flash memory require
a password to work. Password is 0x00000000 from the factory.
4.unsigned long NewPassword
A buffer only used to set a new password with SetPassword()
5.unsigned long DogResult
A buffer where the results of DogConvert() are stored.
6.unsigned char DogCascade
For LPT Microdogs, they can be daisy chained with a max of 16. This value (0-15) keeps an index
number so the lib knows which one you're talking about. This is always set to 0x00 for USB.
7.void * DogData
A buffer that can be used to convert data or read/write data to/from the flash memory.

[Exported Functions]
1.DogCheck()
Reads: DogCascade
Writes: None
Returns: 0 on success - errcode otherwise.
Desc: Basically just asks the driver if it can talk to the dongle. Useful for a simple isDonglePresent() check, although not very secure.
2.ReadDog()
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.
3.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.
4.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.
5.DisableShare()
Reads: DogCascade
Writes: None
Returns: 0 on success - errcode otherwise.
Desc: Disables the ability for a dongle to be shared across parallel port sharing solutions... only used for parallel port dongles.
6.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.
7.SetPassword()
Reads: DogCascade,DogPassword,NewPassword
Writes: None
Returns: 0 on success - errcode otherwise.
Desc: Sets a new dongle password.
8.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.

[Internal Functions for Client Library]
The following functions are present in the library, but are only used internally.
1. EnableShare()
Basically called before any DogConvert transaction to ensure the dongle attached isn't being used
and can be shared with our program.
2. ProcessInput()
This is what determines the opcode that the driver needs so the dongle itself knows what to do, it also does checks to make sure that we arent trying to send more than 200 bytes to the dongle (client side input validation), and basically that the global variables are sanity checked and make sense before continuing.

3. ProcessOutput()
A stub that returns 0 - useless function.
4. ProtectEntry()
Determines which order each step in obfuscating our globals into a packet execute.
Also contains all the auditing code for our response to determine if the dongle responded properly
or at all and then populates the globals based on valid data. This one will be very important to look at later.
5. PickupDogID()
From the previous blog post, this decrypts the 8 byte ID from the client lib and keeps it in memory, you'll see why later.
6. PickupSerialNo()
From the previous blog post, this decrypts the 4 byte DogSerial from the client lib and keeps it for sending to the driver. The idea is that the serial should match the one that our driver finds when it calls the USB device descriptor and requests it, but I'm getting ahead of myself.
7. PlatformEntry0-9()
Ten steps (functions) of hell - basically just a maze to fuck with people. In the end, NOTHING is changed that isn't reversed. They just did this to piss people off. Platform entry 9 is the most interesting because it's actually the point that calls the data to be encrypted and sent in a packet form to the dongle driver... it's really the only function in this series that ACTUALLY does anything.
8. ResetDriverData()
Once a response comes back from the dongle's driver, it has to be decrypted... this is what does that.
9. InitializeDriverInData()
Starts a chain of events that pushes all of our global variables through a maze of code design to irritate the hell out of reverse engineers. It also creates a random number, adds 0x646C6F47 to that number, and XORs all the data in the packet after the first 8 bytes with it as a rolling key.
10.GetMaskKey()
Get the seed mask key and add 0x646C6F47 (or 'dloG) to it.
11.GenerateRand()
Get the time of day and return that as a seed mask key.
12.LinuxDriverEntry()
Open a file descriptor to /dev/mhdog, do a 0x6B00 ioctl call with an 8 byte arg (memory offsets to a request packet buffer and a response packet buffer).
12.LinuxUSBDriverEntry()
Open a file descriptor to /dev/usbdog, do a 0x6B00 ioctl call with an 8 byte arg (memory offsets to a request packet buffer and a response packet buffer).

[DISCLAIMER]
Now, I didn't type all of that because I expect anyone to remember it. Use it as a reference later on so we keep in mind the rules of the game. As with breaking anything, keeping the rules of the game in mind will let you know when it's appropriate to break them.

For right now, let's just leave the driver itself as a black box (one step at a time).

So from 30,000 feet, we have this device with the following data somewhere in it:

  • 200 byte flash memory
  • 4 byte VendorID (DogSerial)
  • 8 byte DogID
  • 4 byte Manufacturer ID
  • 4 byte DogPassword
  • 1 byte DogCascade
  • 1 byte DogShare


With just this information, we can start to build our emulator by abstracting the internal memory requirements of the dongle.

//Even though the last two values are byte, I'm super lazy and don't want to deal
//with alignment issues later.
struct MicroDog_Memory{
unsigned int vendor_id;
unsigned char dog_id[8];
unsigned int mfg_serial;
unsigned int dog_password;
unsigned int dog_cascade;
unsigned int dog_share;
};

//We also know that every DogConvert request is going to deal with a 4 byte response and 1-63 bytes of
//input data based on DogBytes.
struct MicroDog_Convert_Tx{
unsigned int DogResponse;
unsigned int DogBytes; //yes, this should be a short.
unsigned char DogRequest[64];
};

With client library in hand, our goal now shifts to making the client library talk to our own code. Let's start with each exported transaction, look at how the dongle client library handles it, and then what gets sent to the driver.

[DogCheck()]

A good place to start here is to create a sample program and link it with our client lib.

#include<stdio.h>
#include "gsmh.h"

int main(){
int errcode;
errcode = DogCheck()
if(!errcode){
  printf("Success!\n");
}else{
  printf("Fail! Errcode :%d \n", errcode);
}
}

Ok, pretty basic - we make a call to the dongle and it returns zero if it's there and 1 if it's not. What does it do in the client library?

return ProtectEntry(1, (int)ProcessInput, (int)ProcessOutput);

So ProtectEntry takes 3 params - a number which correlates to the opcode (1 being DogCheck), the result of ProcessInput (an errcode), and the result of ProcessOutput which is always 0 because that's all it does.

Reading the notes above will tell you what each function does, but the stack trace will give you an idea that DogCheck's path is like this:

DogCheck->ProcessInput()->ProcessOutput()->ProtectEntry()-> PlatformEntry0-8 (in calculated pseudorandom order) -> PlatformEntry9->InitializeDriverInData()->LinuxDriverEntry/LinuxUsbDriverEntry()->PlatformEntry9->ProtectEntry()->ResetDriverData()

Skipping all the platformEntry0-8 crap of just xoring and shifting bytes around, we come to PlatformEntry9 which constructs our request packet from the globals to send to the driver.  InitializeDriverInData is responsible for creating the request packet that gets sent to the driver.

Here's what it looks like somewhat reconstructed:

Allow me to explain:

Basically, this is constructing our packet from another data structure that was created with ProcessInput().

IDA's struct feature is nice, so I'll just copy what I wrote there:

struct_request_buffer
{
_WORD magic; // 0x484D
_WORD opcode; // Depends on what we're doing - for DogCheck, this would be 0x01
_DWORD VendorID; // Our vendor ID from the library.
_DWORD MaskKey; // Pseudo-random number used as an IV to encrypt the packet.
_WORD DogAddr; // Our dog address (used for read/write)
_WORD DogBytes; // Number of bytes to read/write
_BYTE DogData[256]; // Our payload... why 256? Why anything.
_DWORD DogPassword; // Because this is where we put that.
_BYTE DogCascade; // The cascade value.
};

The part we care most about from a driver standpoint is:


This guy is where the magic happens. We send two pointers via ioctl to our kernel module. The first pointer goes to 277(aligned to 280) bytes as our request 'packet'. The second pointer goes to a block of 268 bytes as where the program expects the driver to write its response 'packet'.

We'll make a simple hook of ioctl in linux to dump packets and inspect them - basically preventing the program from letting data leave userspace.

/* The Hook */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

#define MICRODOG_XACT 0x6B00

//Globals
static void *io;
static void (*realIOCTL)(int fd, int request, unsigned long data);

typedef struct request_packet{
  unsigned short magic;
  unsigned short opcode;
  unsigned int dog_serial;
  unsigned int mask_key;
  unsigned short dog_addr;
  unsigned short dog_bytes;
  unsigned char payload[256];
  unsigned int dog_password;
  unsigned char dog_cascade;
};

 void __attribute__((constructor)) initialize(void)
{
  //Setting our real ioctl function.
    io = dlopen("libc.so.6", RTLD_NOW);
    realIOCTL = dlsym(io, "ioctl");
  }  

int ioctl(int fd, int request, unsigned long* data){

  if(request == MICRODOG_XACT){
/* DO SOMETHING ALREADY */
return 0;
   }

  //Any other ioctl we don't care about.
  realIOCTL(fd,request,data);  
}

Running our target application hooked against this lib and:


Oooooook... this particular error code isn't even documented anywhere, but it's relatively simple to figure out if you remember that this is expecting to endpoint a kernel module that has special ioctl's set. If the kernel comes back and says 'really, /dev/usbdog is just a file, isn't a chardev file, and has no module loaded that will talk to you', then this error gets thrown.

Making a quick kernel module will result in the RetCode changing to 43210 - another undocumented code, but we can't expect much to happen right now. After all, we aren't even DOING anything.

The next step is to dump a request packet and see what's up. Dumping a DogCheck() packet gets us this:


Well then... this isn't right... something's missing. Oh yeah! To reverse what InitializeDriverInData did... what did it do???

Basically, we take each value starting with DogAddr to the end of the packet and XOR each value against our key (mask_key + 0x646C6F47) which gives us this:




All the values are messed up, the opcode is 0x14 which... doesn't even exist in our lib... how can this be!? wtf is going on?! :)

A few things, actually, but it all boils down to the fact that we can continue to bang our heads off of the client lib forever and figure out the proper request/response that way, OR we could speed things up by looking at the actual driver it talks to. Let's put our transaction on hold here until we look at the driver.

[The Driver Part 1 - Request Decryption]

Basically, like any kmod that uses a chardev, we can have extended operations other than file ops if we use ioctl. Our trace for the driver (if we could run it as it's for 2.4 red hat) would look like this:

ioctl_usbdog0->DecodeInParam()->UsbDendOutputData->UsbRecvInputData->USBAKernelEntry()->EncodeOutParam()

Basically, we pull in our request buffer, decrypt it, validate the data, use parts of it to initially log into the dongle and do some stuff, set some things, convert things, whatever. Then, we get the response from the dongle, make a response packet, encode that with our key (the same one as the request packet), and then copy the response to a buffer in our program's memory... done!

Before we can inspect the packet, it must be decrypted. Although we already know how the packet was encrypted (InitializeDriverInData), we can also look into our driver (usbdog.o) to determine how the 'right' way.




As you can see, the packet basically takes our mask_key that was time seeded, adds a static to it, xors our values against that, and that's basically it.

[Back to The Client Lib]

We now know that our packet is decryption successfully, so then wtf is this 0x14 opcode and what does it want? The current undocumented errcode we're getting is:

Let's see if we can find this in the client lib.



Well, THIS is interesting... This is right after the packet comes back and gets decrypted... so it has to do with the payload that we're returning. Basically, it's a failure condition if something that's in a loop of 8 bytes doesn't match another buffer of 8 bytes with the global name IdBuffer_0... that name sounds familiar...

****MAYBE BECAUSE IT'S THE VENDOR_ID!!!! ****

Ok, so throwing a theory out there (more than a theory I guess, haha). You'd want to make sure that the driver is talking to the right dongle, yes? You could poll the device descriptor for the dongle itself and get both the vendor_id (this thing) and the dog serial which we send in the request buffer. This opcode (0x14) is then some kind of "login" functionality... basically it requests a login, the dongle sends back its 8 byte vendor ID, and then it's checked in the client library if it matches... no match, no go. Makes sense they don't want to document this errorcode. We'll cover late where it processes this command in the driver. For now, just accept that after quite a few passes with a debugger, this is ACTUALLY what's happening.


This means we need an additional handler for opcode 0x14 in our emulator to send back the dongle vendor ID as its payload. to review, all opcodes we have to support so far are:

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

Ok, so now we have to figure out the response packet... The response packet is initialized like this:

typedef struct response_packet{
  unsigned int dog_serial;
  unsigned int return_code;
  unsigned char payload[256];
};

It actually can't have anything else due to always being RIGHT above the request packet memory address.

The return_code value must always be zero... if not, the client lib will return whatever number was in there... it's basically the same error handling as the client lib itself, but uniform in the driver.

After constructing a packet with our 8 byte vendor_id value in the response, encrypting the payload (DogData), setting return_code to 0, setting our dog_serial, and writing it to memory, our program gives us another packet.





Ok! Now we're getting somewhere!!! This one is opcode 0x1 which was DogCheck... now our description says that dog_check just returns 0 if the dongle is there as the return value, doesnt use the data or anything... maybe we can just construct a response with a 0 return value and the client lib will accept it. We'll do something like this to test:




awwwwwwwyeaaaaaaaaaahhhhhh!!!!

Ok, so we've completed our first transaction.


For the sake of not making this post any longer, I'm going to pick this up in another post coming up shortly.