Skip to main content

Reversing the USB update process of a device

This post is a work in progress, so if you find it incomplete and not readable probably it's not finished yet. I prefer to publish a little before than leave a post to rust in my drafts.

I'm again at it: I have a device that I want to know how it works and I started to reverse it, this time without any particular reason if not curiosity.

What I couldn't know was that I was entering a rabbit hole of biblic proportion and this post is only the tip of the iceberg.

In this post I want to describe without any particular order, how to reverse a C++ application and the USB protocol that it uses to update the firmware on the device. I don't think this will be useful to anyone, let me know in case it has changed your life :)

I will use Ghidra and I will try to show how to do some specific steps and how I approach reversing in general.

Context

The device is the EZCast bought on Aliexpress for like ten euro, I never used much since I have a Chromecast but out of curiosity I have opened it and soldered the serial on the two exposed pads and accessed the (root) shell.

It has an update process via USB by a Windows application that you can download from their site. The md5 of this application is 8cad508dddcbb11f67297e79609c2561 (I have seen two different versions during my experiments).

I never run it since I'm a Linux guy; some time ago I started playing with it and with a spell of mine I found out an interesting URL:

$ strings MiraScreen/EZCastToolDriver/EZUpdate.exe  | grep http
 ...
https://www.iezvu.com/upgrade/ota_rx.php
 ...

right now I don't remember how (probably I stumbled upon some json strings), but I found the right payload for a request

$ curl -X POST -H "Content-type: application/json; charset=utf-8" -i https://www.iezvu.com/upgrade/ota_rx.php -d'{
        "version":      1,
        "vendor":       "ezcast",
        "mac_address":  "authorshxj",
        "softap_ssid":  "000000-00000000",
        "firmware_version":     "0"
}'
HTTP/2 200
server: nginx
date: Wed, 16 Aug 2017 16:03:02 GMT
content-type: text/html; charset=utf-8
vary: Accept-Encoding
x-powered-by: PHP/5.5.9-1ubuntu4.21

{"ota_conf_file":"http://cdn.iezvu.com/upgrade/ezcast/ezcast-16224000.conf","ota_fw_file":"http://cdn.iezvu.com/upgrade/ezcast/ezcast-16224000.gz","ota_enforce":true}
$ curl -X POST -H "Content-type: application/json; charset=utf-8" -i https://www.iezvu.com/upgrade/ota_rx.php -d'{
        "version":      1,
        "vendor":       "mirawire_8252n",
        "mac_address":  "authorshxj",
        "softap_ssid":  "000000-00000000",
        "firmware_version":     "0"
}'
HTTP/2 200
server: nginx
date: Wed, 16 Aug 2017 16:14:38 GMT
content-type: text/html; charset=utf-8
vary: Accept-Encoding
x-powered-by: PHP/5.5.9-1ubuntu4.21

{"ota_conf_file":"http://cdn.iezvu.com/upgrade/mirawire_8252n_8M/mirawire_8252n_8M-16285000.conf","ota_fw_file":"http://cdn.iezvu.com/upgrade/mirawire_8252n_8M/mirawire_8252n_8M-16285000.gz","ota_enforce":false}

and downloaded the firmware update at the url indicated in the field ota_fw_file.

Analyzing the firmware opened a can of worms, since now I wanted to know how the upgrade process works.

MFC C++

First of all, the application is using VC6 (the magic in the FuncInfo struct is 0x19930520).

I opened ghidra and imported the binary: obviously I had no idea of what I was doing so the initial phase was to start jumping around the calls tree: my first thing that I do when reversing something is to rename functions, variables etc... with labels that indicate something about them, if not just to remember that is something I have already seen. It can seem something boring and useless but I assure you that the brain is very good at spotting patterns (maybe too much) and this helps a lot during the reversing process.

However, at some point, walking up to the call tree you arrive to a function that is not called from no one and you are like "uhm, how is this possible?"; for example the function at 0x0040a050 (that by the way is the function at the end calls the one the uses the url described above).

Maybe the address is indicated somewhere, or the code is not disassembled yet; using Ghidra you can look for direct reference using the menu Search > For Direct References

and found that at 0x004e02a4 there is 4 bytes value corresponding at that address.

Right clicking on the address in the listing window and choosing Data > pointer from the menu you can inform Ghidra that the address stores a pointer to a function (you can also define a shortcut for data type you use frequently, I suggest you one for dword).

Continuing to define as dword the values next to it you can see there is a pattern

As I said previously, it is all about recognizing patterns, reversing patterns, and in this case if you search for something related to reversing MFC you can find an old post of Quequero where he describes the structure of this kind of application, in particular this struct

struct AFX_MSGMAP_ENTRY
{
        UINT nMessage;   // windows message
        UINT nCode;      // control code or WM_NOTIFY code
        UINT nID;        // control ID (or 0 for windows messages)
        UINT nLastID;    // used for entries specifying a range of control id's
        UINT_PTR nSig;       // signature type (action) or pointer to message #
        AFX_PMSG pfn;    // routine to call (or special value)
};

that corresponds perfectly with what we are looking for. This struct is used to describe "callback" from element of the GUi of the program represented by the MFC class.

At the end the layout in memory of a MFC class is the following

+ CRuntimeClass      <------------------------------.
+ Message Map data   <----------------------------------------.
 + ptr to MFC42.DLL::<super class>::messageMap()     |        |
 + ptr to AFX_MSGMAP_ENTRY array -.                  |        |
 + AFX_MSGMAP_ENTRY array  <------'                  |        |
  + 0th element                                      |        |
  + 1th element                                      |        |
   ...                                               |        |
  + last element (all elements are NULL)             |        |
+ MFC class' vtable                                  |        |
 + GetRuntimeClass() (returns a pointer to) ---------'        |
 + Destructor()                                               |
 + null()                                                     |
 + null()                                                     |
 + null()                                                     |
 + OnCmdMsg()                                                 |
  ...                                                         |
 + GetTypeLib()                                               |
 + GetMessageMap() (returns a pointer to) --------------------'
  ...

and in particular GetRuntimeClass() gives us the class this vtable belongs to; instead if you want the constructor you need to look at the function referencing the vtable:

All the destructors have a structure like

CDialog * __thiscall FUN_0040a4d0(void *this,byte param_1)

{
  FUN_0040a4f0((CDialog *)this);
  if ((param_1 & 1) != 0) {
    operator_delete(this);
  }
  return (CDialog *)this;
}

Some of these tables have as the first function something that ghidra doesn't recognize as a GetRuntimeClass():

                     **************************************************************
                     *                          FUNCTION                          *
                     **************************************************************
                     undefined * * __stdcall FUN_004192b0(void)
     undefined * *     EAX:4          <RETURN>
                     FUN_004192b0                                    XREF[1]:     004e0d40(*)  
004192b0   0      b8 90 0c        MOV        EAX=>PTR_s_CPage_ECDkey_004e0c90,PTR_s_CPage_E   = 0055fcf4
                  4e 00
004192b5   0      c3              RET

but in reality this is a custom object so the function returns the CRuntimeStructure for that object:

char *  m_lpszClassName 
dword   m_nObjectSize   
dword * m_pBaseClass    
dword * m_pfnCreateObject   
dword * m_pfnGetBaseClass   
dword   m_wSchema   

Remember: you can follow the GetRuntimeClass() into the original library and find out the size of the class so to have an idea of how much space a class is going to occupy in memory (and in particular it's very useful for local variables in the stack).

                     PTR_s_CPage_ECDkey_004e0c90                     XREF[2]:     FUN_004192b0:004192b0(*), 
                                                                                  FUN_004192b0:004192b0(*)  
004e0c90          f4 fc 55 00     addr       s_CPage_ECDkey_0055fcf4                          = "CPage_ECDkey"
004e0c94          6c 41 00 00     ddw        416Ch
004e0c98          ff ff 00 00     ddw        FFFFh
004e0c9c          50 92 41 00     addr       FUN_00419250
004e0ca0          b0 3f 42 00     addr       FUN_00423fb0
004e0ca4          00 00 00 00     ddw        0h

obviously also the getMessageMap() method is custom.

You can read on the official documentation

or an article about CString internals in VC6

bad enough this application uses mfc42.dll that is a library with API not completly "forward" compatible with the one existing today.

You can organize smartly the functon into class moving the function in the classes with the mouse once that you put the function into the right hierarchy, __thiscall set the ecx register to the right type (be aware that the struct connected to the class must have the same name, it's obvious you know, until you name one Whatever and the other WhateverClass).

Subclasses

The classes that are used in the application are

Name Description
CPageUpdateClass
CDialogUpdateClass
explicit CDialog(
    UINT nIDTemplate,
    CWnd* pParentWnd = NULL);

UINT GetDlgItemTextA(
  HWND  hDlg,
  int   nIDDlgItem,
  LPSTR lpString,
  int   cchMax
);

BOOL SetDlgItemTextA(
  HWND   hDlg,
  int    nIDDlgItem,
  LPCSTR lpString
);

after doModal() there is initDialog()

The general organization of the vtable is the following

GetRuntimeClass
???
nullsub
nullsub
nullsub
OnCmdMsg
OnFinalRelease
IsInvokeAllowed
GetDispatchIID
GetTypeInfoCount
GetTypeLibCache
GetTypeLib
GetMessageMap
GetCommandMap
GetDispatchMap
GetConnectionMap
GetInterfaceMap
GetEventSinkMap
OnCreateAggregates
GetInterfaceHook
GetExtraConnectionPoints
GetConnectionHook
PreSubclassWindow
Create
DestroyWindow
PreCreateWindow
CalcWindowRect
OnToolHitTest
GetScrollBarCtrl
WinHelpA
ContinueModal
EndModalLoop
OnCommand
OnNotify
GetSuperWndProcAddr
???
_function_shared
CPage_ECDkey::FUN_00401fa0
PreTranslateMessage
OnAmbientProperty
WindowProc
OnWndMsg
DefWindowProcA
PostNcDestroy
OnChildNotify
CheckAutoCenter
IsFrameWnd
SetOccDialogInfo
DoModal
OnInitDialog
OnSetFont
OnOK
OnCancel
PreInitDialog

Windows quirks

For someone like me, coming from linux, some aspects of the runtime of Windows are very puzzling

Libraries resolution

This is the most WTF of all: the external call to a function placed in a library is identified by an id so you cannot know the name of the function called if you don't have the library and in my case I have some API that are mismatched from the one recognnized by ghidra (it's obvious since in every place where those functions are used the stack goes banana).

Maybe I'm missing something here but how in the world someone could think that using an id was a good idea?

Calling conventions

Internally this application uses all the possible families of calling convention that I list here

Name Arguments Stack cleaning Return value
cdecl passed on the stack in reverse order by the caller eax
stdcall passed on the stack in reverse order callee eax
fastcall passed via registers by the caller eax
thiscall used with class's methods, ecx contains the this pointer and the arguments are on the stack on the reverse order eax

If ghidra mis-recognize the calling convention of a function you will see something strange happening to the local variables.

Resources

Inside a Windows executable is possible to use a particular section (named .rsrc) for storing resources, like icon, images etc... and load it during the execution with appropriate calls, like FindResourceA(hModule,(LPCSTR)((uint)rsrc_id & 0xffff),type).

This can be useful to "connect the dots" to particular functionality: in the application exists the function loadRsrc() that takes a parameter identifying the resource and set a particular field of the class to the resource: you can see that is possible to directly see what icon corresponds to what resource from ghidra

Strange that from the symbol tree pane I see the PNGs and them have the xref to the function but the call to loadRsrc() doesn't have the xref back (if not a = <PNG-Image> comment in the listing window that doesn't jump to the resource).

It's possible also to reconstruct some custom dialog using the resource id passed as argument to the constructor of CDialog()

Internal state

There is a global variable used to handle the internal state of the GUI at 0x00594e78 and it's processed mainly at 0x00409a20 by a function named by me setState(); this function is pretty interesting since allows to know what state corresponds to what number via the messages that presents to the user and so you can create a wonderful enum :)

Also there are pieces of the interfaces that are set, like the PNGs etc...

Firmware downloading and parsing

int __cdecl download(CDialogUpdateClass *this)
{
  int iVar1;

  setState(this,DOWNLOADING_CONF);
  iVar1 = download_conf(this);
  if (iVar1 == 0) {
    setState(this,RETRY_SERVER);
    return 1;
  }
  setState(this,UPGRADE);
  return 1;
}
/* Here get the response from the server with FW and CONF urls,
   but also downloads the conf. */

int __fastcall download_conf(CDialogUpdateClass *this)

{
  char *response;
  int iVar1;

  response = (char *)client_do_request(s_https://www.iezvu.com/upgrade/ot_0055e0ec,
                                       (char *)&this->json);
  if (response == NULL) {
    return 0;
  }
                    /* here seems that the function takes like three args
                       but internally doesn't use the last two */
  parse_response(response,(char *)&this->ota_conf_file,(char *)&this->ota_fw_file);
  free(response);
  iVar1 = downloading_at((char *)&this->ota_conf_file,(char *)&this->Upgrade.con_path);
  if (iVar1 != 0) {
    return 0;
  }
  getServerVersionFromConfFile(this);
  return 1;
}

The core of what interest us is at 0x00408d50:

int __cdecl update(CDialogUpdateClass *this)

{
  int downloadStatus;

  downloadStatus = download_firmware(this);
  if (downloadStatus != 0) {
    setState(this,DOWNLOAD_FIRMWARE_FAILED);
    return 0;
  }
  firmware_open_and_parse(this);
  return 1;
}

00408d80 download_firmware()

void __fastcall download_firmware(CDialogUpdateClass *this)

{
  setState(this,DOWNLOADING_FIRMWARE);
  downloading_at((char *)&this->ota_fw_file,(char *)&this->Upgrade.tmp_path);
  return;
}

After downloading the file from the remote server and saving it in Upgrade.tmp (this path is set by the routine that starts at 0x0040882c) the application parses it.

If we take a look at the first 200 bytes of the firmware we have some hints that is structured into different sections:

00000000: 4163 7469 6f6e 7346 6972 6d77 6172 6500  ActionsFirmware.
00000010: 5570 6461 7465 5665 723a 7631 2e33 3100  UpdateVer:v1.31.
00000020: 4348 4543 4b53 554d 0000 0000 0000 0000  CHECKSUM........
00000030: 4000 0000 c0fd b801 0000 0000 b59b c093  @...............
00000040: 434f 4d50 5245 5353 0000 0000 0000 0000  COMPRESS........
00000050: 0000 0000 0000 0000 00b2 4100 00de 9d00  ..........A.....
00000060: 4144 4543 6164 6675 7300 0000 0000 0000  ADECadfus.......
00000070: 0004 0000 0020 0000 0000 04b4 0000 0000  ..... ..........
00000080: 4144 4655 6164 6675 7300 0000 0000 0000  ADFUadfus.......
00000090: 0024 0000 1826 0000 0000 00a0 0000 0000  .$...&..........
000000a0: 4857 5343 6877 7363 0000 0000 0000 0000  HWSChwsc........
000000b0: 004c 0000 102a 0000 0000 01a0 0000 0000  .L...*..........
000000c0: 4636 3438 6677 7363 0000 0000 0000 0000  F648fwsc........
000000d0: 0078 0000 2091 0100 0080 01a0 0000 0000  .x.. ...........
000000e0: 4636 3438 6d62 7265 6300 0000 0000 0000  F648mbrec.......
000000f0: 000a 0200 001a 0000 0000 01a0 0040 04b4  .............@..
00000100: 4636 3438 6272 6563 0000 0000 0000 0000  F648brec........
00000110: 0024 0200 0000 0200 0000 0000 1000 0000  .$..............
00000120: 4649 524d 0000 0000 9879 f68a e33c 4541  FIRM.....y...<EA
00000130: 0024 0400 0000 9e00 00de 9d00 c304 2400  .$............$.
00000140: 4c49 4e55 5800 0000 0000 0000 0000 0000  LINUX...........
00000150: 0100 0000 0000 e000 0300 0000 0000 0000  ................
00000160: 726f 6f74 6673 0000 0000 0000 0000 0000  rootfs..........
00000170: 002a 2800 0000 0004 0000 0004 8300 0100  .*(.............
00000180: 7573 6572 3100 0000 0000 0000 0000 0000  user1...........
00000190: 00f0 8b01 0000 8000 0000 8000 8300 0100  ................
000001a0: 7672 616d 0000 0000 0000 0000 0000 0000  vram............
000001b0: 00a4 b801 005a 0000 0000 1000 0b00 0000  .....Z..........
000001c0: 7265 7365 7276 6500 0000 0000 0000 0000  reserve.........
000001d0: 0000 0000 0000 0000 0000 0002 0000 0000  ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

it seems that the header is composed of header sections of 32 bytes each, let's see if we are able to understand the structure of them.

The function firmware_open_and_parse() located at 0x00408b00 then opens the downloaded firmware using a global CFile instance located at 0x00593858 that will be used from other parts of the application to act on the firmware itself or on other kind of files.

The next step parses finally the firmware at 0x0040e7a0: first of all the first 16 bytes must contain the header of the firmware with the string ActionsFirmware

A interesting part is the handling of the CHECKSUM section: if there isn't such section there is some code to decrypt, however if there is a section with that name then read some values and calculate the checksum

00000020 43 48 45 43 4b  char[16]  "CHECKSUM"              checksum
         53 55 4d 00 00 
         00 00 00 00 00
00000030 40 00 00 00     ddw       40h                     start
00000034 c0 ed 66 00     ddw       66EDC0h                 size
00000038 00 00 00 00     ddw       0h                      encriptedFlag
0000003c 5a fa ad 12     ddw       12ADFA5Ah               checksum

the function is at 0x00407510

int __cdecl checksum(void *buffer,uint size)

{
  int ctr;
  byte bVar1;
  uint nDword;
  dword *ptrBuffer;
  int iVar2;
  dword tmp;

  ctr = 0;
  nDword = size >> 2;
  ptrBuffer = (dword *)buffer;
  while (nDword != 0) {
    tmp = *ptrBuffer;
    ptrBuffer = ptrBuffer + 1;
    ctr = ctr + tmp;
    nDword = nDword - 1;
  }
  if (ptrBuffer < (dword *)(size + (int)buffer)) {
    iVar2 = 0;
    do {
      bVar1 = (byte)iVar2;
      iVar2 = iVar2 + 8;
      ctr = ctr + ((uint)*(byte *)ptrBuffer << (bVar1 & 0x1f));
      ptrBuffer = (dword *)((int)ptrBuffer + 1);
    } while (ptrBuffer < (dword *)(size + (int)buffer));
  }
  return ctr;
}

After that check for the sections named LINUX and FIRM, with the last one mandatory.

00000120        46 49 52 4d         section_t
                00 00 00 00 
                9a 0b f0 dc 
   00000120 46 49 52 4d 00  char[16]  "FIRM"                  name
            00 00 00 9a 0b 
            f0 dc a1 74 bf
   00000130 00 de 01 00     ddw       1DE00h                  start_address
   00000134 00 00 17 00     ddw       170000h                 ???
   00000138 00 ce 16 00     ddw       16CE00h                 size
   0000013c df c6 16 00     ddw       16C6DFh                 unk1
00000160        72 6f 6f 74         section_t
                66 73 00 00 
                00 00 00 00 
   00000160 72 6f 6f 74 66  char[16]  "rootfs"                name
            73 00 00 00 00 
            00 00 00 00 00
   00000170 00 a6 18 00     ddw       18A600h                 start_address
   00000174 00 50 4e 00     ddw       4E5000h                 length
   00000178 00 50 4e 00     ddw       4E5000h                 unk0
   0000017c 83 00 01 00     ddw       10083h                  unk1
00000180        76 72 61 6d         section_t
                00 00 00 00 
                00 00 00 00 
   00000180 76 72 61 6d 00  char[16]  "vram"                  name
            00 00 00 00 00 
            00 00 00 00 00
   00000190 00 a0 66 00     ddw       66A000h                 start_address
   00000194 00 4e 00 00     ddw       4E00h                   length
   00000198 00 00 08 00     ddw       80000h                  unk0
   0000019c 0b 00 00 00     ddw       Bh                      unk1

The LINUX parsing is done at 0x00417ae0 and it's the more puzzling piece because some values don't make sense: this is an example

00000140        4c 49 4e 55         section_t
                58 00 00 00 
                00 00 00 00 
   00000140 4c 49 4e 55 58  char[16]  "LINUX"                 name
            00 00 00 00 00 
            00 00 00 00 00
   00000150 01 00 00 00     ddw       1h                      ?????
   00000154 00 00 20 00     ddw       200000h                 ?????
   00000158 02 00 00 00     ddw       2h                      n_subsections
   0000015c 00 00 00 00     ddw       0h                      ????

at 0x14 and 0x1c there is something used elsewhere. The interesting fact is that is possible to have a reserve section that doesn't seem to indicate actual data in the OTA but some metadata.

The dword at offset 0x18 indicates the number of subsections to read: the name of the subsections doesn't imply anything.

The FIRM instead at 0x0040ef10; at 0x00406e80 is manipulating the first 0x80 bytes copied in memory

At this point the smart reader could ask about the other sections, like ADECadfus, ADFUadfus etc... that part will be investigated in the next post where I'll take a look more in detail in the internal working(?) of this family of chips.

Intermezzo: stack_adjust

This is particular function that I encountered during my trip in the assembly land

        004dbf50   0    51                  PUSH                         ECX             <-- it's going to use ECX
        004dbf51 004    3d 00 10 00 00      CMP                          EAX,0x1000      <-- EAX must be passed as argument
        004dbf56 004    8d 4c 24 08         LEA                          ECX,[ESP + 0x8] <-- ECX = addr of 1st arg
 .----- 004dbf5a 004    72 14               JC                           LAB_004dbf70
 |                           LAB_004dbf5c
 | .--> 004dbf5c 004    81 e9 00 10         SUB                          ECX,0x1000
 | |                    00 00
 | |    004dbf62 004    2d 00 10 00 00      SUB                          EAX,0x1000
 | |    004dbf67 004    85 01               TEST                         dword ptr [ECX],EAX  <--- here it's make an AND between
 | |    004dbf69 004    3d 00 10 00 00      CMP                          EAX,0x1000
 | '--- 004dbf6e 004    73 ec               JNC                          LAB_004dbf5c
 |                           LAB_004dbf70
 '----> 004dbf70 004    2b c8               SUB                          ECX,EAX              <--- ECX points to addr 1st arg - EAX
        004dbf72 004    8b c4               MOV                          EAX,ESP              <--- EAX now points to the stack frame
        004dbf74 004    85 01               TEST                         dword ptr [ECX],EAX  <--- USELESS???
        004dbf76 004    8b e1               MOV                          ESP,ECX              <--- now use ECX as stack pointer (ghidra goes banana)
        004dbf78 - ? -  8b 08               MOV                          ECX,dword ptr [EAX]  <--- restore ECX
        004dbf7a - ? -  8b 40 04            MOV                          EAX,dword ptr [EAX + 0x4] <---. 
        004dbf7d - ? -  50                  PUSH                         EAX   <-----------------------'-- restore the return address so that
        004dbf7e - ? -  c3                  RET <------------------------------------------------------'   we jump back to the caller

probably is a "dynamic" allocation routine that uses the stack: it moves the stack pointer EAX bytes below: indeed at the end of each function that uses this method there is a ADD ESP, <offset> that restore the correct frame for the caller.

Since this function messup the stack, every function that uses it makes ghidra lose tracking of the local variables after the call.

The best way to deal with it is to set stack depth change to minus the offset plus four (I don't know why... probably there is a disalignment between the listing and decompilation windows)

seems that ghidra can handle this with alloca_probe

GZIP

An interesting part is where the code gunzip the FIRM section at 0x00414d50: it doesn't seem to be depending from parameters as I thought initially (I have firmwares from other Actions' devices that don't compress that part).

It's the first time I recognize the format following the specification.

AWK

Function at 0x004111d0 does some magic with awk to parse

%s -v BS=\ "{if($NF==BS){$NF=NULL;line=line $0;}else{print line $0;line=NULL;}}" %s |%s -F# -v SP=" " "$1{gsub(/\t/,SP,$1);gsub(/rd_size=__FIX_ME_ON_PACK__/,\"rd_size=0x%08x\",$1);print $1}" > %s

Partitions

At 0x00416660 the application builds what seems to be the Native MBR using the information extracted during the parsing of the LINUX portion of the firmware

USB communication protocol

The mechanism that the application uses to update the firmware is by a custom USB protocol on top of the mass storage; the core of it is the Command Block Wrapper (CBW) a packet of 31 bytes (yeah, 31) having the following organization:

  .----.----.----.----.
  | U    S    B    C  |
  |----|----|----|----|
  |        tag        |
  |----|----|----|----|
  |   transferLength  |
  |----'----'----'----'
  | Fl | LU | CL |    |
  |----|----|----|....|
  |                   |
  |....|....|....|....|
  |                   |
  |....|....|....|....|
  |         |         |
  |....|....|....|....'
  |              |
  '....|....|....'

the "custom" part is inside CBWCB.

struct cmd_block_t {
    byte cmd;
    dword arg0;
    dword arg1;
    short subCmd;
    short subCmd2;
};

struct CBW_t {
    byte[4] signature;
    dword tag;
    dword transferLength;
    byte flags;
    byte LUN;
    byte cmdLength;
    struct cmd_block_t cmdBlock;
    byte padding[3];
};

The typical code is the following

  CBW_t localCBW;

  /* first it copies the 31 bytes of the pre-filled packet */
  int counter = 7;
  dword* cbw = (dword *)&GLOBAL_CBW_PACKET;
  dword* ref2localCBW = (dword *)&localCBW;
  while (counter != 0) {
    counter = counter + -1;
    *ref2localCBW = *cbw;
    cbw = cbw + 1;
    ref2localCBW = ref2localCBW + 1;
  }
  /* this strange assignment exists because there are the three remaining bytes*/
  *(undefined2 *)ref2localCBW = *(undefined2 *)cbw;
  *(undefined *)((int)ref2localCBW + 2) = *(undefined *)((int)cbw + 2);

  /* then it fills the values needed for the wanted function */
  localCBW.transferLength = transferLength;
  localCBW.cmdBlock.arg0 = arg0;
  localCBW.cmdBlock.arg1 = arg1;

  /* it then sends the packet to the device */
  retValue = USB_Write(&localCBW,0x1f,usbIndex);

  /*
   * here can happen some data upload/download from the device
   * depending on the command
   */

  /* the device sends the response of the command */
  retValueResponse = USB_ReadFile(responseBuffer,0xd,index);

Comments

Comments powered by Disqus