Great Scott Gadgets

open source tools for innovative people


Reverse-engineering a VNA ECal Interface With Cynthion

Recently I’ve been working on a little reverse-engineering project, hoping to make some of my electronics test equipment more convenient to use.

Often when doing reverse-engineering, a general strategy that I follow is to make (informed) guesses about how something might work and then I go looking for ways to prove that right or wrong. In this project, Cynthion was really useful for that process as I could use it to emulate part of the target system, so that I could quickly and easily test out theories about the protocol.

This write-up goes over some of the progress I’ve made so far and hopefully it will provide some helpful techniques you can use in your own projects!

VNA with a Cynthion plugged in

Background

In my work and hobby projects, I’m often using a Vector Network Analyzer (VNA) to measure RF components. Ideally, on each power-up and whenever the measurement parameters are changed, the VNA should be calibrated by connecting and measuring a set of four standards in turn on each of the two measurement ports. This can get a little tedious if you need to re-calibrate often, so an alternative option is to use an Electronic Calibration module (ECal) which only requires one connection per port and then has internal switches to select between the different standards automatically. ECal modules are available for my VNA (an Agilent E8803A), but they’re rare and expensive, so I’d like to figure out how the VNA communicates with them so that I can implement it myself in an open-source ECal.

Reverse-engineering options

Of course, the easiest way to do this would be to connect an ECal module and capture the USB traffic (with Packetry!), but an actual ECal is too elusive.

The next option is to disassemble and analyze the software running on the VNA, which I spent a bit of time doing, but it was slow going as I’m not too familiar with the APIs on Windows and how it uses them for USB. However, there are some quick things to learn by looking at the software. It’s split into many DLLs, so it’s easy to see the imports and exports of each and get an idea of the functionality we might expect from the USB ECal:

$ winedump -j export ecalusb.dll 
Contents of ecalusb.dll: 33280 bytes

[...]

  Entry Pt  Ordn  Name
  00001F50     1 ReadModule
  00002160     2 SetState
  00001F90     3 ReadModule1K
  000016D0     4 Reset
  00001FD0     5 ReadModuleData
  00002010     6 WriteModuleData
  00002210     7 EraseSector

Done dumping ecalusb.dll

So, it should have ways to read and write data on the module, which makes sense as it needs to store S-parameter data describing the characteristics of the different standards to be used for calibration. It also has SetState, which probably sets the switch positions to select different standards on each port.

Device Emulation

I decided to go down the route of emulating the ECal device, then seeing what the VNA tried to request of it and iterate from there. Using Cynthion with the Facedancer library you can easily emulate a USB device by writing a Python script, and quickly make changes to try out different things.

I started with the template.py example from the Facedancer project. Usually, a USB host will identify a particular device by looking at the vendor ID & product ID and/or the manufacturer/product/serial strings. From searching on forums and other test equipment groups, I found the expected values for the target device and I modified the template with these:

#!/usr/bin/env python3

from facedancer         import main
from facedancer         import *

@use_inner_classes_automatically
class ECalDevice(USBDevice):

    vendor_id                : int  = 0x0957
    product_id               : int  = 0x0001

    manufacturer_string      : str  = "Agilent Technologies"
    product_string           : str  = "USB ECal Module"
    serial_number_string     : str  = "S/N 12346"
    device_speed             : DeviceSpeed = DeviceSpeed.FULL

    class ECalConfiguration(USBConfiguration):

        class ECalInterface(USBInterface):

            class ECalInEndpoint(USBEndpoint):
                number               : int                    = 1
                direction            : USBDirection           = USBDirection.IN
                transfer_type        : USBTransferType        = USBTransferType.BULK
                max_packet_size      : int = 64
                
                def handle_data_requested(self):
                    self.send(b"Hello!")

            class ECalOutEndpoint(USBEndpoint):
                number               : int                    = 1
                direction            : USBDirection           = USBDirection.OUT

                def handle_data_received(self, data):
                    print(f"Received data: {data}")

main(ECalDevice)

Running this script and then clicking “Detect Connected ECals” on the VNA gives the following output:

$ python ecal-emulate.py --suggest
INFO    | __init__       | Starting emulation, press 'Control-C' to disconnect and exit.
INFO    | moondancer     | Using the Moondancer backend.
INFO    | moondancer     | Connected FULL speed device '__main__.ECalDevice' to target host.
INFO    | device         | Host issued a bus reset; resetting our connection.
INFO    | moondancer     | Target host configuration complete.
WARNING | device         | Stalling unhandled OUT VENDOR request 0x04 to DEVICE [value=0x0000, index=0x0000, length=0].

This shows that the script got a vendor-specific request from the VNA, but doesn’t yet have the code to handle it so it returns a STALL response (which is the terminology for how USB devices respond to unhandled requests). Since I ran it with the --suggest argument, stopping the script gives me a suggestion for code that I can add to handle the request!

^CINFO    | moondancer     | Disconnecting from target host.

Automatic Suggestions
These suggestions are based on simple observed behavior;
not all of these suggestions may be useful / desirable.


Request handler code:

  @vendor_request_handler(number=4, direction=USBDirection.OUT)
  @to_device
  def handle_control_request_4(self, request):
      # Most recent request data: bytearray(b'').
      # Replace me with your handler.
      request.stall()

I add the suggested handler to my script, but change the response from stall to ack and also print out the request:

--- a/ecal-emulate.py
+++ b/ecal-emulate.py
@@ -34,4 +34,11 @@ class ECalDevice(USBDevice):
                def handle_data_received(self, data):
                    print(f"Received data: {data}")

+    @vendor_request_handler(number=4, direction=USBDirection.OUT)
+    @to_device
+    def handle_control_request_4(self, request):
+        print(request)
+        request.ack()
+
+
main(ECalDevice)

I ran the script and got a new message saying that there was also a vendor request number 2 to be handled, so I went through the same process to handle that too. After doing that, I got a lot more output - now it sent many #2 vendor requests with values counting down from 0x400 (1024) by 6 each time:

OUT VENDOR request 0x04 to DEVICE [value=0x0000, index=0x0000, length=0]
OUT VENDOR request 0x02 to DEVICE [value=0x0400, index=0x0000, length=0]
OUT VENDOR request 0x02 to DEVICE [value=0x03fa, index=0x0000, length=0]
...
OUT VENDOR request 0x02 to DEVICE [value=0x0010, index=0x0000, length=0]
OUT VENDOR request 0x02 to DEVICE [value=0x000a, index=0x0000, length=0]
OUT VENDOR request 0x02 to DEVICE [value=0x0004, index=0x0000, length=0]

This looked very promising, as I was expecting it to read out 1 kB of memory from the hints in some of the DLL exports mentioned earlier. However, I was a bit confused at this point because, while it seemed to be addressing the memory, I couldn’t see any way that the device could actually return the data. Due to the way USB control transfers work, there isn’t a way for an OUT request to return any data - it can only ACK or NAK.

I wanted to see if there might be something else happening on the bus that I might be missing. Fortunately, I have a few Cynthions about so I hooked up a second one in-line to do a packet capture and see if I could learn more:

Screenshot of Packetry doing a packet capture. After each OUT vendor request, there’s a BULK IN transfer of “Hello!”

Doh! Of course, the template example I was working from was also setting up a BULK IN endpoint to return “Hello!”. After each address vendor request, the VNA would request data on the bulk endpoint and receive those 6 bytes, then adjust the address accordingly and repeat. If I had realised, I could have just added a print to the handler to see that happening rather than setting up the packet capture.

Now that I had a pretty good idea of how the data was being read out I could go ahead and implement it properly, but I needed some appropriate data to return. Fortunately I was able to find some memory dumps that another user had shared online for the 8506x series ECal modules. They had shared them as ASCII hex dumps, so I converted them to binaries with xxd:

$ head HP85062-60006.txt 
=~=~=~=~=~=~=~=~=~=~=~= PuTTY log 2021.09.29 21:47:04 =~=~=~=~=~=~=~=~=~=~=~=
dump 0 040000

000000: 48 50 38 35 30 36 30 43 20 45 43 41 4C 00 E8 D1  | HP85060C ECAL... | 
000010: 31 83 C4 02 64 00 4E 6F 76 20 32 38 20 31 39 39  | 1...d.Nov 28 199 | 
000020: 34 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | 4............... | 
000030: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | ................ | 
000040: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | ................ | 
000050: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | ................ | 
000060: FF FF FF FF 30 30 33 36 37 00 31 C0 50 E8 88 08  | ....00367.1.P... | 

$ xxd -r HP85062-60006.txt HP85062-60006.bin

$ hexdump -C HP85062-60006.bin | head
00000000  48 50 38 35 30 36 30 43  20 45 43 41 4c 00 e8 d1  |HP85060C ECAL...|
00000010  31 83 c4 02 64 00 4e 6f  76 20 32 38 20 31 39 39  |1...d.Nov 28 199|
00000020  34 00 ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |4...............|
00000030  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000060  ff ff ff ff 30 30 33 36  37 00 31 c0 50 e8 88 08  |....00367.1.P...|
00000070  33 35 46 33 35 46 20 4d  57 31 00 c4 04 56 e8 fb  |35F35F MW1...V..|
00000080  08 83 c4 02 38 20 41 75  67 20 32 30 30 31 20 00  |....8 Aug 2001 .|
00000090  41 47 49 4c 45 4e 54 2f  4d 54 41 00 b8 f8 6a eb  |AGILENT/MTA...j.|
000000a0  93 83 3e 94 06 00 74 1e  a1 82 06 83 c0 41 a2 98  |..>...t......A..|

Then I modified my Python script to load that file and return the data from the bulk endpoint, and tried again to detect the device from the VNA:

--- a/ecal-emulate.py
+++ b/ecal-emulate.py
@@ -14,6 +14,9 @@ class ECalDevice(USBDevice):
     serial_number_string     : str  = "S/N 12346"
     device_speed             : DeviceSpeed = DeviceSpeed.FULL
 
+    address = 0
+    data = open('EEPROM/HP85062-60006.bin', 'rb').read()
+
     class ECalConfiguration(USBConfiguration):
 
         class ECalInterface(USBInterface):
@@ -25,7 +28,10 @@ class ECalDevice(USBDevice):
                 max_packet_size      : int = 64
                 
                 def handle_data_requested(self):
-                    self.send(b"Hello!")
+                    # Respond with 32 bytes of EEPROM data
+                    dev = self.get_device()
+                    addr = dev.address
+                    self.send(dev.data[addr:addr+32])
 
             class ECalOutEndpoint(USBEndpoint):
                 number               : int                    = 1

Screenshot of the VNA software detecting an ECal, but showing garbled data

Success! …sort of. It detected something, which is great progress, but the output is a mess. After staring at it for a while, I realised my silly mistake - I’d forgotten to update the address when receiving vendor request 2:

--- a/ecal-emulate.py
+++ b/ecal-emulate.py
@@ -44,6 +44,7 @@ class ECalDevice(USBDevice):
     @to_device
     def handle_control_request_2(self, request):
         print(request)
+        self.address = request.value
         request.ack()

I added that, re-ran everything, and… nothing! It didn’t detect anything anymore. Something about that mistake actually made it work better.

Something that’s very common in file formats is to start with a header that includes some magic value and the parser will check that value before doing anything else. With the mistake in place, the script was returning the first 32 bytes of data to every request, so that was probably enough to pass the header check and show a detected device. However, that suggests that upon implementing the addressing, now the script wasn’t returning the header whenever the VNA was expecting it.

I had a look back at the pattern of #2 vendor requests:

OUT VENDOR request 0x02 to DEVICE [value=0x0400, index=0x0000, length=0]
OUT VENDOR request 0x02 to DEVICE [value=0x03e0, index=0x0000, length=0]
...
OUT VENDOR request 0x02 to DEVICE [value=0x0040, index=0x0000, length=0]
OUT VENDOR request 0x02 to DEVICE [value=0x0020, index=0x0000, length=0]
^CINFO    | moondancer     | Disconnecting from target host.

Two things stood out to me about those:

  1. The VNA starts by requesting with value=0x400 and then counts down - that’s a bit odd, I’d expect it to start at address 0 and count up.
  2. The VNA never actually sends a request with value=0x0, so the script never sends the header at all with the addressing in place!

This got me thinking that maybe there’s some quirk in the addressing and it should actually be reversed (so that a request with value=0x400 goes to address 0x0). I made that change and re-ran the test:

--- a/ecal-emulate.py
+++ b/ecal-emulate.py
@@ -44,7 +44,7 @@ class ECalDevice(USBDevice):
     @to_device
     def handle_control_request_2(self, request):
         print(request)
-        self.address = request.value
+        self.address = 0x400 - request.value
         request.ack()

Screenshot of the VNA software detecting an ECal showing correct data

Bingo! The device is detected and all the information about it looks correct now.

While there are some other details still to figure out, I’ll wrap it up there as I think it’s demonstrated the process pretty well. Hopefully it provides some inspiration, please let us know if you use these techniques and tools in your own projects!

For anyone interested, the full code and any further research is available here: https://github.com/miek/ecal-reversing


Free Stuff - February 2024

The belated February 2024 Free Stuff recipient for the Great Scott Gadgets Free Stuff Program is Adam Drake! Adam, a teacher in Canada, sponsors three clubs at his high school - a competitive robotics club, a model railway club, and a D&D club. All of these clubs are fully funded from either internal school funds, the school PAC (Parental Advisory Council), or the NSHSS. This summer, Adam ran an RF Comms summer school where 18 students gained their amateur radio certification!

Following the success of the RF Comms summer school, Adam is now starting another after-school club: “RF Communications.” This club will teach students all about wireless communications, such as Bluetooth, Wi-Fi, cellular, and radio (HF, VHF, UHF, etc). Students will learn the theory of RF (radio frequency) communications, but the focus will be on practical uses of the technology. Students who do not yet have their radio licenses will have more chances to study and gain Canadian Amateur Radio licenses through this club.

We will be sending Adam a HackRF One to support these clubs and the students they impact. Thank you, Adam, for all you do in your community!


Cynthion is Here!

We’re pleased to announce that Cynthion is now available for purchase from our authorized resellers! This FPGA-based hardware platform from Great Scott Gadgets powered by LUNA gateware is your new go-to tool for discovering and exploring the world of USB at a fraction of the cost of existing High Speed USB analyzers. Whether you’re experienced with USB or you’re new and learning about it, Cynthion is a great multipurpose addition to your hardware experimentation toolbox! We’ve also developed custom open-source software tools that work with Cynthion:

  • Packetry is a fast and intuitive open-source software that allows you to capture and analyze traffic between a host and Low-, Full-, or High-Speed USB devices.

  • Moondancer is a Facedancer backend that enables you to reverse engineer USB devices and even create your own!

Cynthion comes in a beautiful and protective milled aluminum enclosure so that despite its small size (it fits in the palm of your hand) it has a solid feel and a nice weight. It is also currently available for sale without an enclosure at a lower cost. We’ve offered this option to make Cynthion as financially accessible as possible for hobbyists, students, and small teams.

You can learn more about Cynthion, including where to purchase it, by visiting our Cynthion webpage.


Free Stuff - January 2024

The January 2024 recipient for the Great Scott Gadgets Free Stuff Program is Marc, the author of PySDR.org. Marc is interested in learning more about HackRF One and potentially adding a chapter on HackRF One to the PySDR.org documentation. We look forward to working with Marc on this update to PySDR.org and helping even more folks with finding new ways to interact with their HackRF One.


Cynthion is at Mouser

Note: This is a crosspost of a Cynthion update on Crowd Supply: https://www.crowdsupply.com/great-scott-gadgets/cynthion/updates/cynthion-is-at-mouser

An 885 lb / 401.43 kg shipment of thousands of Cynthions has been received by Mouser (who will fulfill Cynthion orders for Crowd Supply)! We expect the Cynthions to start shipping to backers around the 5th of July. You will receive a notice when your Cynthion has shipped.

While you wait for your shipping notice and for your device to arrive, take a moment to read the Cynthion documentation. Cynthion does not ship with USB cables, so take a look at the connection diagrams in this documentation for different use cases and make sure you have all the cables you will need.

As you receive your Cynthions we would love to see your first pictures with your device! Please tag Great Scott Gadgets on X, Mastodon, Instagram, or post in the #show-your-gsg channel in our Discord.


Cynthion Shipping Soon!

Note: This is a crosspost of a Cynthion update on Crowd Supply: https://www.crowdsupply.com/great-scott-gadgets/cynthion/updates/cynthion-shipping-soon

The first few Cynthions have come off of the manufacturing line! Once the first full batch of Cynthions is completed we will send them to Mouser who will ship them to you, our backers. Shipping will happen soon, so please make sure your address on Crowd Supply is up-to-date. If you need assistance with an address change please contact Crowd Supply. We will post another update once the first orders leave the Mouser warehouse. Until then, please enjoy these photos from our contract manufacturer.

Batch of Cynthions before last USB component was soldered on.


Enclosed Cynthion ready to ship


Batch of enclosed Cynthions ready to ship



Free Stuff - December 2023

The December 2023 recipient for the Great Scott Gadgets Free Stuff Program is a STEM Camp where students will have the opportunity to use the requested HackRF One to do a Quantum Physics experiment with laser light and modulated RF. We are excited to see how the experiment goes and to see pictures from the camp!


Free Stuff - November 2023

The November 2023 recipient for the Great Scott Gadgets Free Stuff Program is Ryan. Ryan works as a wireless systems administrator for a public school. He coaches robotics and programming teams after school. Ryan has asked for a HackRF One to show the students in his clubs how to interact with the wirelss devices around them and to inspire them to explore RF as future career options.


Cynthion Manufacturing Progress

Note: This is a crosspost of a Cynthion update on Crowd Supply: https://www.crowdsupply.com/great-scott-gadgets/cynthion/updates/cynthion-manufacturing-progress

Cynthion update time!

Final samples have been manufactured, some of them have been sent for RoHS testing (and each of them has passed), and manufacturing is under way. Everything’s going great so far, and we’re on track for June shipping!

Here is your first glimpse of finished Cynthions in their enclosures and in the boxes we will be shipping them in.

The significance of RoHS testing is that Cynthion had to pass that test in order for us to earn our CE marking, which allows us to sell Cynthion in the EU. RoHS stands for “Restriction of Hazardous Substances in Electrical and Electronic Equipment” and this directive requires that electronic and electrical equipment does not exceed certain thresholds for lead, cadmium, mercury, and other hazardous substances. RoHS testing is completed by a third-party lab and the lab we chose used “destructive testing methods” which meant that we had to send a few completed Cynthions, including enclosure and packaging, for them to destroy.


Cynthion Has Passed EMC and Tycho Has Shipped to Our Manufacturer

Note: This is a crosspost of a Cynthion update on Crowd Supply: https://www.crowdsupply.com/great-scott-gadgets/cynthion/updates/cynthion-has-passed-emc-and-tycho-has-shipped-to-our-manufacturer

The title says it all! Cynthion has passed EMC testing which means it is ready for manufacturing and Tycho, our Cynthion-testing quality assurance rig, has been shipped to our manufacturer. Our next steps are to work closely with our manufacturer to create final product samples, ensure the QA process is achieving the results we expect at factory level, and receive the final product samples and test them in-house here at Great Scott Gadgets. We’ll have more for you soon!


subscribe to GSG feed