Neoden 4 Pick and Place

Overview

This is a long post.

We replaced out Neoden TM220A with a Neoden 4, it has worked well for us but there was a couple of things I wanted to change in the software. Like the machine moves over the pulled tape with parts and sometimes the tape gets in the way. maybe some changes to the vision algorithms.

So where to begin.

The easy one; if you use Eagle and want a slightly better experience https://github.com/charlie-x/neoden4 use my ULP, if you don’t use Eagle the various formats are documented inside that ULP

The Neoden4 is a Windows XP box that is locked down a little bit, ctrl alt delete gets you to the Chinese task manager, select the  the lower middle button, then select the run menu and run CMD or explorer.

image

It has a C: Write cache protection, anything you write to the C: drive goes into a cache file that is located on the root of drive C: so if you mod the registry or such, and reboot the changes will be gone.

To get rid of it, boot XP in safe mode, then run

KYSYSProtectApp.exe

image

It’ll look better on the machine. click the lower right button down and this will remove the KYSysProtect.sys driver, using the first button will re-enable it.

[INSTALL]                     [CANCEL]

[OPEN]

[SHUTDOWN]

[UNINSTALL]

image

Then you can pop in the registry editor regedit.exe . Change shell from start.bat to explorer.exe

Reboot and you should see explorer, the D:\Neoden4\ folder contains the startup executable. Now you can add a usb to wifi or wired adapter and setup networking.

Either of these will work  and have XP drivers

https://www.amazon.com/gp/product/B00ET4KHJ2/

https://www.amazon.com/gp/product/B00762YNMG/

Notes

The run csv files are stored in D:\Neoden\proc folder

The footprints are stored inside the config folder as pkg.csv

0402,1.00,0.50,0.50,

0603,1.60,0.80,0.50,

0805,2.00,1.20,0.50,

1206,3.20,1.60,0.50,

1210,3.20,2.50,0.50,

1812,4.50,3.20,0.50,

config.ini contains all the preset positions of the feeders, etc they’re often in QT variant format but QT supports either

[Default]

bLighting=false

nRunSpeed=100

nVibFreq=100

nVibCurrent=30

fAngleCamLookUp=@Variant(\0\0\0\x87?\x99\x99\x9a)

fBrightness=@Variant(\0\0\0\x87@\0\0\0)

[Stack]

lstStack0.pos.x=410.223266601562

lstStack0.pos.y=88.2767715454102

lstStack0.nFeedId=1

lstStack0.nPeelId=1

or

[Stack]

lstStack0.pos.x=@Variant(\0\0\0\x87\x43\xcd\x1c\x94)

lstStack0.pos.y=@Variant(\0\0\0\x87\x42\xb0\x8d\xb5)

lstStack0.nFeedId=1

lstStack0.nPeelId=1

Either is valid

Setting.ini has the language and hashed serial number of your machines MAC address if this doesn’t match it’ll default to chinese

[Serial]

Language=78798069

[System]

Language=eng

If you don’t turn off ksysprotect the only changes you make that will stay after a reboot will be on the D:\ drive.

The update installed is a .cab file renamed to .neoden and the start.bat script looks for it on the USB drive and will extract it into the D:\Neoden4 folder at boot time.

@echo off

if not exist D:/NeoDen4/v4.1.3.9_1d6d860d134a44232ea1b42c0bc11e52.neoden goto run

expand D:/NeoDen4/v4.1.3.9_1d6d860d134a44232ea1b42c0bc11e52.neoden -F:* ./

del v4.1.3.9_1d6d860d134a44232ea1b42c0bc11e52.neoden

:run

start NeoDen4.exe

Exit

The shutdown button in the neoden software just shells out to the cmd processor and the shutdown command, this is pretty typical for these sorts of machines.

If you switch the XP language to English, it will likely no longer boot the needed files are missing even if you add the language pack, better to reinstall.

The ID for the Chinese/English switch is based on the first network MAC address the machine finds. If it appears in Chinese and was English, then either the config file was changed or the first MAC address did

Drivers for the cameras are on my github for 7×64 and 10 x64 , probably 8 too , Be aware since after years of people beating on Windows for the fault of bad drivers MS has stepped up the driver policy on 10 and you need to generate and resign the CAB as well the INF otherwise you’ll have to disable secure boot and/or driver enforcement to load modified drivers.

Better translations

On the whole the translations are fine, but there were some things that bugged me and used up valuable screen space. Like the Feeder column Lists Feeder 1, Feeder 2 etc, leaving less space for what is in the column that you really want to see. So just changing it in the QM file, the app uses QT 5.3.1 extract it to a C:\QM folder

The translations are stored in the Neoden.qm file which is a compiled XML/TS translation file.

QT Linguist can open QM files, you just need to change the filter in the open, but i used the command line tool instead.

C:\Qt\QT5.3.1\5.3\mingw482_32\bin\lconvert.exe Neoden4.qm -o Neoden4.ts

This will convert the binary .qm to an XML file. If you open it up you’ll see the translations like th

<message>
        <source>55å·æ–™æ ˆ</source>
       <translation>Feeder 55</translation>

</message>

<message>    <source>å☼-åo^</source>
        <translation>Outer</translation>

< /message>


The source is the Chinese, which doesn’t show up here and translation is the English version. So next changed all the relevant Feeder messages to > </ as if it were removed altogether and didn’t leave a single space the Chinese translation would appear instead

<message>     
        <source>æ-╪件</source>
        <translation>File</translation>

</message>

<message>
        <source>æ-Tæ ^</source>
        <translation> </translation>

< /message>

To convert the XML back to a .QM use the command

C:\Qt\QT5.3.1\5.3\mingw482_32\bin\lrelease.exe Neoden4.ts

The QM likely changes between versions, so best to always grab the current one.

Original

image

After, you can see the space before the numbers and you can stretch out the SMD Spec column

image

Fixing the MAC address

If you want to keep the same setup but either change the current mini PC< run it on another system or add a network card that messes up the install, you can either change the mac address so its the same as the original, or you can hard code the MAC address in QT this does mean setting up QT to compile with mingw etc.

If you haven’t grabbed QT 5.31 do so and extract it to C:\QT\QT5.3.1

The software doesn’t use any of the networking except for the MAC address

Open the file

C:\QT\QT5.3.1\qtbase\src\network\kernel\qnetworkinterface.cpp

Look for

QString QNetworkInterface::hardwareAddress() const

In the following code substitute the desired MAC address for 00:00:00:00:00:00.

/*!
        Returns the low-level hardware address for this interface. On
       Ethernet interfaces, this will be a MAC address in string
       representation, separated by colons.
       Other interface types may have other types of hardware
        addresses. Implementations should not depend on this function
        returning a valid MAC address.

*/

QString QNetworkInterface::hardwareAddress() const

{
       return QString(“00:00:00:00:00:00”);
       return d ? d->hardwareAddress : QString();

}

Now recompile QT

configure.bat -release -qt-zlib -opensource -confirm-license -platform win32-g++ -opengl desktop -prefix C:\Qt\Qt5.3.1\5.3\mingw48_32 -nomake tests -nomake examples

Then copy over the new Qt5Network.dll to the machine or your local install and test it.

Change the Logo

This is a simple one, change Logo.png in the D:\Neoden4\Res folder to a PNG of your choice that is 500×100 32 bit with an alpha background

Logo

image

Shutdown

If for some reason you don’t want the machine to shut down after you click Shut Down search and replace shutdown in the Neoden4.exe with an invalid cmd. Like xxxxxx then it won’t execute it. It is in a couple of spots. Entering the config mode incorrectly also tells the machine to shutdown, which is annoying if you do run it on a different PC

image

image

Running on a different PC

You’ll need the MAC address fix from above otherwise it’ll be in Chinese. Copy over all the files from D:\neoden4\ to the new machine , doesn’t really matter where. But you will need a serial port that is at COM2:  and the same drive letter as the USB drive is now.

If you run XP the drivers for the cameras are in the D:\Neoden4 folder, if you want x64 or newer windows we will have to make some up, which just means grabbing the right version of the Cypress FX usb drivers and adding the Cameras USB ID’s

As a side note you can use software like KernelPro to forward USB and the Serial over TCP/IP to any other machine on the network and it’ll work fine as long as you install the camera drivers and the forwarded Serial is COM2

com0com should work too, but you will need to forward the USB as well, i’ve used the Kernel Pro software a fair but and its been pretty solid and useful for sniffing, RE etc.

image

The Windows DDK also has the skeleton code for a Virtual Serial Port I’ve used that one extensively too

Another great tool for sniffing is Device Monitoring Studio, came in handy when  building the keyboard.

This of course needs a network adapter on the Neoden4 side in order to forward the Serial/USB too

The Cameras

Camera USB ID

camera1

camera1a

Second

camera2

As we can clearly see its a Cypress FX with CYUSB3.sys driver. The machine had a folder called CameraNew that had the XP32 drivers, but we may want to update those.

Just need to add this to the cyusb3.inf driver. (which i’ve linked here )

VID_52CB&PID_52CB.DeviceDesc=”Camera Neoden Tech.”

and to each relevant section (the platforms)

%VID_52CB&PID_52CB.DeviceDesc%=CyUsb3, USB\VID_52CB&PID_52CB

e.g. add it to each of these sections

;for all platforms

[Device.NT]

;for x86 platforms

[Device.NTx86]

Add the same line to each of these Device sections for the OS you want to create the driver , all of them for completeness

Cypress actually document the process here, as well as locating the drivers you might need, i used windows 7 x64 as my base

https://community.cypress.com/docs/DOC-12366

To match the device with the drivers, refer to the steps mentioned under the section “Matching Devices to the Driver ” in the attached PDF file. Adding the VID/PID is already done in the attached .inf file , so you can skip “Step A : Add the device’s Vendor ID and Product ID to the CYUSB3.INF file”.

Now you will have camera drivers for your system and can install them when you add the USB cameras.

The serial connection is hardwired on the motherboard of the mini PC which is inside the PNP, in this post I am mostly going to concentrate on the software side and to be honest if you can’t find the bits to rewire in the new PC you should probably leave it alone as it does have a few things going on. But you do just need to reroute USB (cameras) and Serial(Control) . So they’re fairly straightforward.

USB to serial is fine. since i have ran it plenty of times with a serial<>tcpip bridge

You can also use these drivers for a fresh XP install that uses the language set of your choice.

As long as you copy all the opencv dlls, and install folder on D: from the machine  that is all you need to make a fresh install on the same PC, if you change the PC you will need to do MAC fix or it might default to the Chinese language, which is fine if you speak Chinese.

I didn’t really look into how they generate the config password since its so easy to replicate a MAC , but that is what it is based on, i may take a look sometime.

Other Mods to the GUI

I wanted rid of the frameless app so it can be resized on a larger monitor, since the app uses QT it’ll use setWindowsFlags with

Qt::FramelessWindowHint

Which is 0x0000800

Looking thru the binary we can see the calls to setWindowsFlags , it is mangled so using a demangle tool we can see this is correct

QWidget::setWindowFlags(QFlags<Qt::WindowType>)


Which is right, and there is the flag being placed on the stack for the call, so just change it to 0 and  it will now run with a resizeable border

C7 04 24 00 08 00 00   mov     dword ptr [esp], 0x800

FF 15 EC 04 4F 00       call  ds:_ZN7QWidget14setWindowFlagsE6QFlagsIN2Qt10WindowTypeEE

the qt_main is where the main window is created, which itself is called from WinMain, so traverse WinMain to  the QT main routine looking for

QApplication::QApplication(int&, char**, int)



which in this case is mangled as

_ZN12QApplicationC1ERiPPci

Although I usually find QT apps a PITA to RE they do all share common flows so its easy to find the basics

We’ll know where the main window is created in that function since it’ll have

_ZN7QWidget4showEv

QWidget::show()

But we have enough info with the C7 04 24 00 08 00 00  FF 15 EC, just search and replace that with C7 04 24 00 00 00 00 FF 15 EC a global replace does change all the windows to be bordered, but you might not want to change all of them, next i want to change all the tabs to automatically expand, but i haven’t done that yet, and Neoden do actually keep doing updates.

image

image

Most of the changes they’re making seem mostly related to text fixes ,maybe since they did a Neoden USA partnership

image

Then it can run remotely on Windows 7 x64 etc with just the pnp computer being the remote host.

image

Gaining control

Lets cover getting access to the control systems if we want to change the software altogether

It is all serial to the motion controller board so it is trivial to snoop, after that there is CAN bus but it doesn’t really feel like a necessity to go that far yet, (apparentlyit now is ) maybe if people wanted to use the feeders or peelers for other projects but there are better ones. Also i wonder if its any coincidence that serial byte size is 8 bytes, which is what you send in the most common CAN frame.

Here is the boot up trace, these are annotated for the test software with a C++ wrapper for the RS232dll.dll so it can snoop, change or inject if needed

/// boot to main menu

//status msg

Write 0x45Read 0x09

Write 0x05

Read 0x14

Write 0x85

Read 0x1c

Read 0x00 0x03 0x00 0x16 0x00 0x00 0x00 0x00 0x5d

// blow/suck
    Write 0x43

Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 1

Write 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0xb2

Write 0x03
    Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 2

Write 0x01 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x30

Write 0x03
    Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 3

Write 0x01 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x51

Write 0x03
    Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 4

Write 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x15

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 1

Write 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x61

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 2

Write 0x00 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0xe3

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 3

Write 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x82

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 4

Write 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0xc6

Write 0x03

Read 0x03

Write 0x03

Read 0x47

//blow all off
    Write 0x43

Read 0x0f

Write 0xc3

Read 0x07

// all off?

Write 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

Write 0x03

Read 0x47

//rotate nozzles
    Write 0x41

Read 0x0d

Write 0xc1

Read 0x05

Write 0x00 0x00 0x96 0x00 0x00 0x00 0x00 0x00 0x45

Write 0x01

Read 0x01

Write 0x01

Read 0x45

// move nozzles, max retract
    Write 0x42

Read 0x0e

Write 0xc2

Read 0x06

Write 0x00 0x00 0x96 0x00 0x00 0x00 0x00 0x00 0x45

Write 0x02

Read 0x11

// rails set speed to 00
    Write 0x46

Read 0x0a

Write 0xc6

Read 0x02

Write 0x64 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x19

Write 0x06

Read 0x06

Write 0x06

Read 0x42

//rail set speed to 100%

Write 0x46

Read 0x0a

Write 0xc6

Read 0x02

Write 0x64 0x09 0x00 0xc8 0x00 0x00 0x00 0x00 0x8c

Write 0x06

Read 0x06

Write 0x06

Calculating the CRC Routines

So looking at the data, its a cmd, reply, cmd reply, cmd with extra data and an obvious CRC at the end. Going back to the binary lets see what CRC tables there are.

Yep sure enough there are two common CRCs

A full CRC16 table for poly 0x1021

dw 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032

             37161, 41290, 45419, 49548, 53677, 57806, 61935, 4657

Second table is the first 16 words of the same table

dw 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 161, 41290, 45419, 49548, 53677, 57806, 61935

Looking for references to this we see a normal CRC16, its next to com port write/write (which we can tell from the references to the rs232dll.dll )

For the smaller table its a different routine. Digging into it a bit we see it is taking the cmd we sent, the reply from the machine , masking and then calculating the crc from the smaller table.

push    ebx

sub     esp, 0x28

mov     ebx, [esp+0x2C+arg_4]

mov     eax, [esp+0x2C+arg_0]

mov     [esp+2Ch+var_28], 2

mov     [esp+2Ch+var_E], al

mov     eax, ebx

and     ebx, 0Fh

and     eax, 0FFFFFFF0h

mov     [esp+0x2C+var_D], al

lea     eax, [esp+-0x2C+var_E]

mov     [esp+2Ch+var_2C], eax

call    crc16_16

and     eax, 0x0F

cmp     al, bl

setz    al

add     esp, 0x28

pop     ebx

retn    8

So it is pulling in cmd( sent by software) and the reply from the machine, combining them into a 16 bit value ,masking it off to be the top nybble, then crcing those two bytes, then it masks the lower nybble of the calculated CRC and the reply from the machine then check to see if they match, converting it to C

bool  checkReply ( char cmd, char reply )

{
       // combine and mask
       int16_t v3 = ( cmd + ( int16_t ( reply  << 8 ) ) ) & 0xf0ff;
       // technically it calls the full 32bit crc, but we only need the lower nybble
        uint16_t crc = crc16_16 ( ( uint8_t * ) &v3, 2 );
       // mask off lower nybble
        crc &= 0x0f;
       reply &= 0x0f;
       // if they match, we’re good.
        return ( crc == reply  );

}

Lets test it out, i also put the CRC routine here

Launch C++

Feeding it a couple of cmds that the machine says are valid, and one bad command we get

CMD = 0xC3 reply = 0x07 = yes

CMD = 0x45 reply = 0x09 = yes

CMD = 0x45 reply = 0xAA = no

Great we know know that this is how the machine replies to commands.

Next is the CRC at the end of the larger blocks, this one was fixed at 2 bytes, so we know it is not it. We know there is a second CRC16 table, so looking at the references for that we find the standard CRC16 routine, and then referring to that we see the routine that then spits out the 9 bytes. The RS232 dll has a separate routine for sending out the single bytes.

Just to make it easy to prove out we can use an online tool to verify it is what I think it is.

Online CRC Calculator

Lets take a couple of known strings

0x64 0x09 0x00 0x00 0x00 0x00 0x00 0x00  CRC=0x19

0x64 0x09 0x00 0xc8 0x00 0x00 0x00 0x00  CRC=0x8C

Set CRC to HEX and 16

Result 1

image

Result 2

image

There we go, CRC16/XMODEM i know its polynomial 0x1021 and i know the seed is 0 and Neoden only uses the lower byte

So now we know what both the CRC routines are, how the basic command structure works and we can go from there

The next part is a little tedious since it means we snoop the serial, and select one command at a time, see what happens and try variations on it, record, test and repeat.. basic science . Luckily there aren’t that many.

// a few hours pass by

A quick C++ class to define a basic test harness with each part added as more information is discovered, creating a class for sub components like the Rails, feeders etc and then an embodied class for the machine.

Taking just one simple example, the buzzer

// beep if true, off if false
             // @return pass/fail
            bool Buzzer ( bool on ) {
                if ( bBoot == false ) { return false; }
                if ( getAck ( 0x47 ) == false ) {
                     return false;
                 }
                if ( getAck ( 0xc7 ) == false ) {
                      return false;
                }
                uint8_t cmd[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
                if ( on ) {
                      cmd[5] = 0x01;
                }
                //doesn’t expect a reply from the machine
                 if ( sendData ( cmd, 8 ) != 8 ) {
                    return false;
                }
                return getAck ( 0x07 );
             }

The flow is, (it varies on some commands)

check the boot worked (sending the byte sequence from before)

getAck sends 0x47 and checks for the shorter CRC’d reply , returning true or false if passed

repeats for 0xC7 , not sure why it follows this style yet, maybe its a class/subclass thing or another protocol

now it sends out the 8 byte packet with the message we’re sending, byte 5 = 1 to turn on the buzzer, 0 to turn off the buzzer

finally it checks to see if the command completed properly, by sending out the getAck(0x7)

The final getAck will often send back the cmd you sent rather than the CRC, I believe it is doing this as a pause method, in code the retry for the final command to 5 retries, the serial has a 500 ms timeout set. (it is also 115200,n,8,1)

The 8 byte send is done thru sendData which will tack on the 9th byte which is the computed CRC16/XMODEM/POLY1021 and it never expects a reply for that send

For the X Y coord’s they take mm and then are n*1000 to convert to an int16, for speed for the rails they are n*2 for the int16

Building a DLL to snoop

Even though snooping is trivial on serial, it is often useful to build a DLL that can intercept data. So the easiest one first is the RS232DLL.DLL.. . Their name not mine Smile

First step is just to take a quick peek at what the DLL exports, dumpbin (MSVC)  will do that.

dumpbin /exports rs232dll.dll

File Type: DLL
     Section contains the following exports for rs232.dll
       00000000 characteristics
        545B1F00 time date stamp Wed Nov  5 23:10:56 2014
           0.00 version
              1 ordinal base
              5 number of functions
              5 number of names
       ordinal hint RVA      name
             1    0 00001080 rs232_close
              2    1 00001050 rs232_open
             3    2 00001090 rs232_read
             4    3 000010B0 rs232_readSyn
              5    4 000010D0 rs232_write
     Summary
           5000 .data
             1000 .rdata
           1000 .reloc
            7000 .text

Great it is simple, open close, read , another read, and a write.

Now to make a proxy DLL, there are a bunch of tools to do this, I haven’t tried this one but it has both x64 and x32 which i’d added to the tool used here, the other one is here

https://github.com/SeanPesce/DLL_Wrapper_Generator

In short a proxy dll when loaded  loads the old dll which has been renamed, and then adds the functions from the old DLL as pointers, when the host calls your proxy dll , you do stuff then pass control to the original DLL

Example

extern “C” __declspec ( naked ) void __E__0__()

{
       __asm pushad
       __asm pushfd
       OutputDebugStringA ( “rs232_close\n” );
       __asm popfd
       __asm popad
       __asm {
            jmp procs[E_RS232_CLOSE*4];
       }

}

this is the proxy for the rs232_close function. All it does is print rs232_close\n to the windows debug log.

the DLLMain is where the proxy is setup

BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD reason, LPVOID )
    {
       OutputDebugStringA ( “DllMain called\n” );
       if ( reason == DLL_PROCESS_ATTACH ) {
            OutputDebugStringA ( “DllMain DLL_PROCESS_ATTACH\n” );
           hLThis = hInst;
           hL = LoadLibrary ( _T ( “rs232dllOLD.DLL” ) );
           if ( !hL ) { OutputDebugStringA ( “Failed to load original library\n” ); return false; }
            procs[E_RS232_CLOSE] = GetProcAddress ( hL, “rs232_close” );
           if ( procs[E_RS232_CLOSE] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_close\n” ); }
           procs[E_RS232_OPEN] = GetProcAddress ( hL, “rs232_open” );
           if ( procs[E_RS232_OPEN] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_open\n” ); }
           procs[E_RS232_READ] = GetProcAddress ( hL, “rs232_read” );
           if ( procs[E_RS232_READ] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_read\n” ); }
           procs[E_RS232_READSYN] = GetProcAddress ( hL, “rs232_readSyn” );
           if ( procs[E_RS232_READSYN] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_readSyn\n” ); }
           procs[E_RS232_WRITE] = GetProcAddress ( hL, “rs232_write” );
           if ( procs[E_RS232_WRITE] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_write\n” ); }
       }
       if ( reason == DLL_PROCESS_DETACH ) {
            OutputDebugStringA ( “DllMain DLL_PROCESS_DETACH\n” );
           FreeLibrary ( hL );
        }
       return 1;
    }

When the HOST attaches to our proxy DLL , it uses LoadLibrary to open the old DLL RS232dllOLDd.dll then it uses GetProcAddress on the handle for that DLL and gets the pointer to the real function, and stores it in an array for later.

Very basic,  and works great., this is for an X32 proxy with VC unless we want to emit we use an external asm file for X64, which would look like this.

; entirelyDifferentFunc

  __E__0__ proc
       PUSHAQ
       lea        rcx, _entirelyDifferentFunc
       call qword ptr    __imp_OutputDebugStringA
       POPAQ
       call        procs[0*8]
       ret

__E__0__ endp

_entirelyDifferentFunc    db    “entirelyDifferentFunc”,13,10,0

PUSHAQ/POPAQ are macros that save all the things.

Also use a.def file to map the new functions to the originals, as well define the exports/order

LIBRARY   rs232dll

EXPORTS

rs232_close @1

rs232_open @2

rs232_read @3

rs232_readSyn @4

rs232_write @5

The @number is the ordinal for the function in the DLL, its index. This tells the linker we’re exporting these functions, we can alias them here too. Depending on how the original DLL was built it might be mangled,. or use stdcall/fastcall etc etc so this can be dealt with here

aliasing looks like this

EXPORTS

rs232_close=__E__0__ @1

rs232_open=__E__1__ @2

rs232_read=__E__2__ @3

rs232_readSyn=__E__3__ @4

rs232_write=__E__4__ @5

this allows us to map the autogenerated names to the real names.

Once you’ve mapped all these out you can make a simple dump function to show the data being passed in and out

// name of function, ptr to data to dump and length of data

void dump ( const  char *name, unsigned char *ptr, unsigned int length )

{
       if ( ptr ) {
           if ( length ) {
                 //if ( ptr[0] != 0x64 )   { return; }
               if ( name ) {
                    OutputDebugStringA ( name );
               }
               _RPT0 ( _CRT_WARN, “Write = { ” );
               while ( length– ) {
                    _RPT1 ( _CRT_WARN, “0x%02x,”, *ptr++ );
               }
               _RPT0 ( _CRT_WARN, “} ;\n ” );
            }
        }

}

Here you can also filter out data you don’t care about, you can see an example commented out where i wasn’t interested in messages that started with 0x64 this can help increase signal to noise ratio

Forget Snoop, Just rewrite the DLL

Since the RS232 dll turned out to be really basic, it was just reimplemented as to have total control

__declspec ( dllexport ) int __cdecl rs232_read ( LPVOID lpBuffer, DWORD nNumberOfBytesToRead )

{
       DWORD bytesRead = 0;
       BOOL  fSuccess = ReadFile (
                             hCom,    //Handle
                            lpBuffer,  //Incoming data
                            nNumberOfBytesToRead,  
                             &bytesRead, //Bytes Read
                            0 // not overlapped
                         );
       dump ( “rs232_read”, ( unsigned char* ) lpBuffer, bytesRead );
       return bytesRead;
    }

__declspec( dllexport) tells VC that we’re exporting this function

// doesnt use serial timeouts, uses a GetTickCount

__declspec ( dllexport ) int __cdecl rs232_readSyn ( PVOID lpBuffer, DWORD nNumberOfBytesToRead, DWORD timeout )

{
       ULONGLONG endTime;
       DWORD bytesRead = 0;
       endTime = GetTickCount64() + timeout;
       do {
            bytesRead = 0;
           BOOL  fSuccess = ReadFile (
                                  hCom,
                                lpBuffer,
                                 nNumberOfBytesToRead,
                                 &bytesRead,
                                NULL // not overlapped
                            );
           if ( fSuccess & bytesRead ) {
               dump ( “rs232_readSyn”, ( unsigned char* ) lpBuffer, bytesRead );
               return 1;
            }
       } while ( GetTickCount64() < endTime );
       return 0;
    }

Mapping these functions out to be as close to the original as possible.

__declspec ( dllexport ) int __cdecl rs232_write ( LPVOID lpBuffer, DWORD nNumberOfBytesToWrite )
    {
       DWORD dwNumberOfBytesWritten = 0;
       dump ( “rs232_write”, ( unsigned char* ) lpBuffer, nNumberOfBytesToWrite );
       BOOL fSuccess
            = WriteFile (
                 hCom,
                 lpBuffer,
                 nNumberOfBytesToWrite,
                 &dwNumberOfBytesWritten,
                 NULL
             );
       return dwNumberOfBytesWritten;

}

__declspec ( dllexport ) int __cdecl rs232_close()

{
       OutputDebugStringA ( “rs232_close\n” );
       int ret = CloseHandle ( hCom );
       hCom = NULL;
       return ret;
    }

Comm States used and set internally

int  OurSetCommState ( HANDLE h, DWORD baud )

{
       DWORD v7;
       struct _DCB DCB;
       DCB.DCBlength = sizeof ( DCB );
        GetCommState ( h, &DCB );
       DCB.BaudRate = baud;
        DCB.ByteSize = 8;             //  data size, xmit and rcv
       DCB.Parity = NOPARITY;      //  parity bit
       DCB.StopBits = ONESTOPBIT;    //  stop bit
       if ( SetCommState ( h, &DCB ) ) {
            return 1;
       }
       v7 = GetLastError();
       return 0;

}

int  SetTimeouts ( HANDLE h )
    {
       DWORD error;
       struct _COMMTIMEOUTS CommTimeouts;
       GetCommTimeouts ( h, &CommTimeouts );
       CommTimeouts.ReadTotalTimeoutMultiplier = 0;
         CommTimeouts.ReadTotalTimeoutConstant = 0;
       CommTimeouts.ReadIntervalTimeout = -1;
        CommTimeouts.WriteTotalTimeoutMultiplier = 10;
       CommTimeouts.WriteTotalTimeoutConstant = 1000;
       if ( SetCommTimeouts ( h, &CommTimeouts ) ) {
            return 1;
       }
       error = GetLastError();
        _RPT1 ( _CRT_WARN, “SetTimeouts failed: = %d\n”, error );
       return 0;
    }

rs232_open function, which is called with the com2: 115200, ,n,8,1 settings , this also sets timeouts etc

// opens and sets up baud rate etc.

//a4/a5 = 0 (parity)

__declspec ( dllexport ) int __cdecl rs232_open ( LPCSTR lpFileName, int baudrate, int bits, int a4, int a5 )

{
       OutputDebugStringA ( “rs232_open ” );
        OutputDebugStringA ( lpFileName );
        OutputDebugStringA ( “\n” );
       // HANDLE hCom;
        int result = 0;
       //  Open a handle to the specified com port.
        hCom = CreateFileA ( lpFileName,
                            GENERIC_READ | GENERIC_WRITE,
                            0,      //  must be opened with exclusive-access
                            NULL,   //  default security attributes
                             OPEN_EXISTING, //  must use OPEN_EXISTING
                             0,      //  not overlapped I/O
                             NULL ); //  hTemplate must be NULL for comm devices
       if ( hCom == INVALID_HANDLE_VALUE ) {
            //  Handle the error.
           _RPT1 ( _CRT_WARN, “CreateFile failed with error %d.\n”, GetLastError() );
           return result;
       }
       SetCommMask ( hCom, 511u );
        SetupComm ( hCom, 512u, 32u );
       PurgeComm ( hCom, 12u );
       if ( OurSetCommState ( hCom, baudrate ) ) {
           if ( SetTimeouts ( hCom ) ) {
               EscapeCommFunction ( hCom, 5 );
               EscapeCommFunction ( hCom, 3 );
               PurgeComm ( hCom, 0xF );
               result = 1;
           }
       }
       return result;
    }

then there is a global for the HANDLE to the serial device/COM2 

static HANDLE hCom = 0;

And then the whole thing is wrapped in, this matches it to the original DLL which says it was built in VC6 ? linker 6.0

extern “C” {

}

rs232_readSyn has the timeout that reads the single ack back

So in this case it was just as easy to rewrite the DLL as proxying it and ignore the COM settings and fill in as needed

Adding USB cameras, for some reason

One of the first things to do with the software after making it run on a dev box was hook up a couple of USB cameras so the machine didn’t need to be up and running while fiddling with it. The Neoden cameras are Cypress FX devices which do basically just have a couple of routines to set exposure, flash, size etc the capture routine just returns a w*h uint8 grey buffer for the image, with the cameras id of 0/1(5) passed to the DLL

same procedure as before, examine the dll and see what it exports

ordinal hint RVA      name
         1    0 00001840 img_capture
           2    1 000017D0 img_init
          3    2 00001870 img_led
          4    3 000019F0 img_read
          5    4 00001A30 img_readAsy
          6    5 000018B0 img_reset
          7    6 000018F0 img_set_exp
          8    7 00001930 img_set_gain
          9    8 00001970 img_set_lt
         10    9 000019B0 img_set_wh

which more or less translates too

__declspec ( dllexport ) BOOL _cdecl img_capture ( int which_camera);
     __declspec ( dllexport ) int _cdecl img_init();
    __declspec ( dllexport ) BOOL _cdecl img_led ( int which_camera, int16_t mode );
    __declspec ( dllexport ) int _cdecl img_read ( int which_camera, unsigned char * pFrameBuffer, uint32_t BytesToRead, uint32 ms );
    __declspec ( dllexport ) int _cdecl img_readAsy (int which_camera, unsigned char * pFrameBuffer, uint32 BytesToRead, uint32 ms);
    __declspec (dllexport) int _cdecl img_reset(int which_camera);
    __declspec ( dllexport ) BOOL _cdecl img_set_exp ( int which_camera, int16_t exposure );
    __declspec ( dllexport ) BOOL _cdecl img_set_gain ( int which_camera, int16_t gain );
    __declspec ( dllexport ) BOOL _cdecl img_set_lt ( int which_camera, int16_t a2, int16_t a3 );
    __declspec ( dllexport ) BOOL _cdecl img_set_wh ( int which_camera, int16_t w, int16_t h );

so after wrapping videoinput/opencv into a new dll

static int16_t g_width, g_height;
    VideoCapture *stream1 = NULL, *stream2 = NULL;
       __declspec ( dllexport ) int _cdecl img_init()
         {
            static bool init = false;
           if ( init == false ) {
               stream1 = new VideoCapture ( 1 );
                 stream2 = new VideoCapture ( 0 );
                init = true;
            }
           return 1;
         }

the software only seemed to use readAsy

// a1 = 5 (1 if downlooking?)
       // a2 = 0x05580030
      // a3 = 0x00100000
      // a5 = 0x97 (changes)
      // dwMilliseconds = 1000
      int _cdecl img_readAsy ( int which_camera, unsigned char * pFrameBuffer, int a3, DWORD dwMilliseconds, char a5 )
      {
          if ( pFrameBuffer == NULL ) {
              return 0;
          }
         Mat cameraFrame;
         if ( which_camera == 5 ) {
               if ( stream1 == NULL ) {
                  return 0;
              }
             stream1->read ( cameraFrame );
         } else {
               if ( stream2 == NULL ) {
                  return 0;
              }
             stream2->read ( cameraFrame );
             // my camera was flipped
               flip ( cameraFrame, cameraFrame, 0 );
          }
        // convert to grey
          if ( cameraFrame.rows ) {
              cv::Mat greyMat;
              flip ( cameraFrame, cameraFrame, 1 );
              cv::cvtColor ( cameraFrame, greyMat, CV_BGR2GRAY );
              Size size ( g_width, g_height );
              resize ( greyMat, greyMat, size );
             memcpy ( pFrameBuffer, greyMat.ptr(), g_height * g_width );
           }
          return 1;
      }

// a2 = 1024, a3 =1024 a1 = 5

BOOL _cdecl img_set_wh ( int which, int16_t width, int16_t height )

{
       _RPT3 ( _CRT_WARN, “img_set_wh) %d %d %d\n”, which, width, height );
       g_width = width;
       g_height = height;
       return TRUE;
    }

So just a very quick and dirty interface to cameras that VideoInput supports, it is only really useful during desktop RE work but maybe there will be a time when i can swap out the cameras in the machine, either way it works and we can move on to the real Neoden cameras

Deeper Dive with the Neoden Camera

Picking apart the NeodenCamera.dll it is using CyApi / CyUsb3.sys to communicate with the cameras. I did see a post on eevblog that said it was CGUSB2.dll there might be an older rev of the hardware that uses those cameras, but these don’t. Similar hardware so could just be a rewrite to the new FX chip

Going over the IOCTLs

First process is to build up the Constructor/Destructor code for the CyUSB interface, since that shows me where the device context/structs are. Also decoding the IOCTLs which are built with

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)


It creates a 32 bit value

diagram illustrating the i/o control code layout

Online Decoder

The commonly appearing IOCTL is 0x220020 which decodes as

Device = 0x22(FILE_DEVICE_UNKNOWN) which means this device doesn’t match any of the predefined ones

Function 0x8

Access FILE_ANY_ACCESS

Method unbuffered

The others observed are

0x220008 Function 0x08 Unbuffered

0x22003C Function 0xF Buffered

0x220040  Function 0x10 Buffered

0x220010 Function 0x04 Buffered

0x220000 Function 0x0 Buffered

0x220004 Function 0x01 Buffered

Looking in cyioctl.h from the Cypress SDK the IOCTLS are defined as

#define IOCTL_ADAPT_GET_DRIVER_VERSION         CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS)

IOCTL_ADAPT_INDEX as 0x000

So lets see what matches and if it makes any sense

Taking the IOCTL 0x220000 we see that is function 0, buffered, so seems to match IOCTL_ADAPT_GET_DRIVER_VERSION  so now to check the code and see if it looks like that IOCTL is indeed looking for a driver version, this seems like a lucky place to start since a driver version is going to be obvious, Plus they are all in the Open call so filling in the basic startup stuff

void  CallDeviceIO_220000(DWORD *HANDLE)

{
      DWORD *pThis;
      DWORD *v2;

   pThis = HANDLE;
      if ( HANDLE[541] == -1 )                      // handle to device, is it not open?
      {
        HANDLE[11] = 0; // otherwise set output buffer to \0
      }
      else
      {
        outbutBuffer = HANDLE + 11;

          // handle, ioctl, ptr to outbuffer, size of buffer
        if ( !CallDeviceIO(HANDLE, 0x220000u, HANDLE + 11, 4u) || !pThis[530] )                                            

// failed
          *outputBuffer = 0;
      }

}

So it is asking for 4 bytes from the device, Lets look at the others

220000 Function 0x0 Buffered IOCTL_ADAPT_GET_DRIVER_VERSION

220004 Function 0x01 Buffered  IOCTL_ADAPT_GET_USBDI_VERSION Get the USBDI Version

220010 Function 0x04 Buffered IOCTL_ADAPT_GET_ADDRESS Get device address from driver

220008 Function 0x08 Unbuffered IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER Send a raw packet to endpoint

220040 Function 0x10 Buffered IOCTL_ADAPT_GET_FRIENDLY_NAME

22003C Function 0xF Buffered IOCTL_ADAPT_GET_DEVICE_NAME

Confirm with CyUSB3/CyAPI SDK

Seems to make sense, looking back at the code  the IOCTLs that pass strings have larger buffers, (0x100) but before we go too far down the rabbit hole, lets look back at the Cypress SDK

And there it is in the Open function

GetUSBAddress();

GetDeviceName();

GetFriendlyName();

GetDriverVer();

GetUSBDIVer();

GetSpeed();

Great everything is matching, so now have verified this is the right SDK and it is acting as expected.

bool CCyUSBDevice::Open(UCHAR dev)

Calling this function and from the SDK know that the HANDLE above is pointing to this

typedef struct _USB_DEVICE_DESCRIPTOR {
        UCHAR   bLength;
        UCHAR   bDescriptorType;
        USHORT  bcdUSB;
        UCHAR   bDeviceClass;
        UCHAR   bDeviceSubClass;
        UCHAR   bDeviceProtocol;
        UCHAR   bMaxPacketSize0;
        USHORT  idVendor;
        USHORT  idProduct;
        USHORT  bcdDevice;
        UCHAR   iManufacturer;
        UCHAR   iProduct;
        UCHAR   iSerialNumber;
        UCHAR   bNumConfigurations;

} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;

So at this point  it might be best to start rebuilding the NeodenCamera.dll with the CyUSB3 SDK, since again it is a simple enough DLL with just the specifics for chatting to the hardware.

Can we recreate the NeodenCamera.dll

Just to be clear, This doesn’t really need to be done this since how to use the existing DLL is now documented ,but for the *nix people they can recreate with CyAPi or libusb.

Loaded up VC2017,  created a DLL project, set it to MBCS added the CyUsb3 files to the project, turned off Precompiled headers for CyAPi.cpp  , added stdint.h and crtdbg.h to the stdafx.h , also need SetupAPI.lib for CyUsb3 added that to stdafx.h as well. Currently its set to be an X86 since the Neoden software is all 32 bit.

Using the information from the USB VideoCapture driver and filling out the basic functions that the DLL exports.

__declspec ( dllexport ) BOOL _cdecl img_capture ( int which_camera );

__declspec ( dllexport ) int _cdecl img_init();

__declspec ( dllexport ) BOOL _cdecl img_led ( int which_camera, int16_t mode );

__declspec ( dllexport ) int _cdecl img_read ( int which_camera, unsigned char * pFrameBuffer, int BytesToRead, int ms);

__declspec ( dllexport ) int _cdecl img_readAsy ( int which_camera, unsigned char * pFrameBuffer, int BytesToRead, int ms);

__declspec ( dllexport ) int _cdecl img_reset ( int which_camera );

__declspec ( dllexport ) BOOL _cdecl img_set_exp ( int which_camera, int16_t exposure );

__declspec ( dllexport ) BOOL _cdecl img_set_gain ( int which_camera, int16_t gain );

__declspec ( dllexport ) BOOL _cdecl img_set_lt ( int which_camera, int16_t a2, int16_t a3 );

__declspec ( dllexport ) BOOL _cdecl img_set_wh ( int which_camera, int16_t w, int16_t h );

They’re a little rough at the moment but they will be fixed up as work progresses

Forgot to add the .def so thats just (although the code is using the __declspec(dllexport)

EXPORTS

img_capture = img_capture @1

img_init @2

img_led @3

img_read @4

img_readAsy @5

img_reset @6

img_set_exp @7

img_set_gain @8

img_set_lt @9

img_set_wh @10

Next step is setting up the USB Device, so add CyUSB.h to stdafx.h and add a couple of CCyUSNDevice to init the USB

So lets enumerate the USB devices that match ours first. Also at this point I realise its easier to run this as an EXE so i change the type from DLL to EXE in the VC options, and add a WInMain

int WINAPI WinMain (
        HINSTANCE hInstance,    
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,        
        int nCmdShow      

)

{
        return 0;

}

Now i can just run as an EXE don’t need a host to test.

Enumerating the USB

Ok lets enumerate the USB devices we want

// Create the CyUSBDevice

CCyUSBDevice* USBNeodenCamera = new CCyUSBDevice ( 0, CYUSBDRV_GUID, true );

int n = USBNeodenCamera->DeviceCount();

// for all Cypress devices found

for ( int i = 0; i < n; i++ ) {

    USBNeodenCamera->Open ( i );

    // Is it the Neoden?
        if ( USBNeodenCamera->VendorID != 0x52CB ) {
            continue;
        }

     std::string model = narrow ( std::wstring ( USBNeodenCamera->Product ) );
        std::string serial = narrow ( std::wstring ( USBNeodenCamera->SerialNumber ) );

    _RPT5 ( _CRT_WARN, “DID %04x:%04x, %s, %s, %s,%s\n”,
                USBNeodenCamera->VendorID, USBNeodenCamera->ProductID,
                USBNeodenCamera->DeviceName, USBNeodenCamera->DevPath,
                model.c_str(), serial.c_str()
              );

}

delete USBNeodenCamera;

Then test.

DID 52cb:52cb, B0001 Camera Shibz, \\?\usb#vid_52cb&pid_52cb#1&2d12bed1&0&0000#{ae18aa60-7f6a-11d4-97dd-00010229b959}, B0001 Camera Shibz,

DID 52cb:52cb, H0001 Camera Shibz, \\?\usb#vid_52cb&pid_52cb#1&2d12bed1&0&0001#{ae18aa60-7f6a-11d4-97dd-00010229b959}, H0001 Camera Shibz,

Perfect, both cameras found. the 0000 and 0001 are up and down cameras . both use the same driver

Since this is a simple DLL and that it is known that both cameras exist , that are always two. That does reduce the complexity a bit, so it can be noted either by the device index or init a global to point to each of the cameras when we find them. Shibz is slang for Shibuya, Tokyo not sure if there is another clue there or not. The names of the cameras do differ slightly with a H and B

After the enumeration, I figure that the img_led function is probably easiest to implement to start off with, poking through the original dll i see another IOCTL

0x220044 which decodes as Function 0x11, referring to the Cyioctl.h 0x11 is IOCTL_ADAPT_ABORT_PIPE, which aborts any current EndPoint transactions, a purge if you will, it’s another one of the CyApis functions

RetVal = (DeviceIoControl(hDevice,IOCTL_ADAPT_ABORT_PIPE,&Address,sizeof(UCHAR),NULL,0,&dwBytes,&ov)!=0);

So now we’ll need an EndPoint, time to dig some more. Lets pop up USBDeview on the machine

Wall of text time. Figure out the EndPoints

First Camera


    =========================== USB Port1 ===========================

Connection Status        : 0x01 (Device is connected)
Port Chain               : 5-1
PortAttributes           : 0x00000002 (Shared USB2)

      ======================== USB Device ========================

        +++++++++++++++++ Device Information ++++++++++++++++++
Device Description       : Camera Neoden Tech.
Device Path              : \\?\usb#vid_52cb&pid_52cb#5&1783ac8f&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Device ID                : USB\VID_52CB&PID_52CB\5&1783AC8F&0&1
Hardware IDs             : USB\Vid_52cb&Pid_52cb&Rev_0001 USB\Vid_52cb&Pid_52cb
Driver KeyName           : {36FC9E60-C465-11CF-8056-444553540000}\0014 (GUID_DEVCLASS_USB)
Driver                   : System32\Drivers\CYUSB3.sys (Version: 1.2.3.10  Date: 2014-09-18)
Driver Inf               : C:\WINDOWS\inf\oem1.inf
Legacy BusType           : PNPBus
Class                    : USB
Class GUID               : {36FC9E60-C465-11CF-8056-444553540000} (GUID_DEVCLASS_USB)
Interface GUID           : {a5dcbf10-6530-11d2-901f-00c04fb951ed} (GUID_DEVINTERFACE_USB_DEVICE)
Service                  : CYUSB3
Enumerator               : USB
Location Info            : B0001 Camera Shibz
Manufacturer Info        : Cypress
Capabilities             : 0x84 (Removable, SurpriseRemovalOK)
Status                   : 0x0180600A (DN_DRIVER_LOADED, DN_STARTED, DN_DISABLEABLE, DN_REMOVABLE, DN_NT_ENUMERATOR, DN_NT_DRIVER)
Problem Code             : 0
Address                  : 1
Power State              : D0 (supported: D0, D3, wake from D0, wake from D3)

        +++++++++++++++++ Registry USB Flags +++++++++++++++++
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags
 GlobalDisableSerNumGen  : REG_BINARY 01
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags\52CB52CB0001
 osvc                    : REG_BINARY 00 00

        ---------------- Connection Information ---------------
Connection Index         : 0x01 (1)
Connection Status        : 0x01 (DeviceConnected)
Current Config Value     : 0x01
Device Address           : 0x01 (1)
Is Hub                   : 0x00 (no)
Number Of Open Pipes     : 0x01 (1)
Device Bus Speed         : 0x02 (High-Speed)
Pipe0ScheduleOffset      : 0x00 (0)
Data (HexDump)           : 01 00 00 00 12 01 00 01 00 00 00 40 CB 52 CB 52   ...........@.R.R
                           01 00 01 02 00 01 01 02 00 01 00 01 00 00 00 01   ................
                           00 00 00 07 05 82 02 00 02 00 00 00 00 00         ..............

    ---------------------- Device Descriptor ----------------------
bLength                  : 0x12 (18 bytes)
bDescriptorType          : 0x01 (Device Descriptor)
bcdUSB                   : 0x100 (USB Version 1.00)
bDeviceClass             : 0x00 (defined by the interface descriptors)
bDeviceSubClass          : 0x00
bDeviceProtocol          : 0x00
bMaxPacketSize0          : 0x40 (64 bytes)
idVendor                 : 0x52CB
idProduct                : 0x52CB
bcdDevice                : 0x0001
iManufacturer            : 0x01 (String Descriptor 1)
 Language 0x0409         : "Neoden HangZhou"
iProduct                 : 0x02 (String Descriptor 2)
 Language 0x0409         : "B0001 Camera Shibz"
iSerialNumber            : 0x00 (No String Descriptor)
bNumConfigurations       : 0x01 (1 Configuration)
Data (HexDump)           : 12 01 00 01 00 00 00 40 CB 52 CB 52 01 00 01 02   .......@.R.R....
                           00 01                                             ..

    ------------------ Configuration Descriptor -------------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x02 (Configuration Descriptor)
wTotalLength             : 0x0019 (25 bytes)
bNumInterfaces           : 0x01 (1 Interface)
bConfigurationValue      : 0x01 (Configuration 1)
iConfiguration           : 0x00 (No String Descriptor)
bmAttributes             : 0x80
 D7: Bus Powered         : 0x01 (yes)
 D6: Self Powered        : 0x00 (no)
 D5: Remote Wakeup       : 0x00 (no)
 D4..0: Reserved, set 0  : 0x00
MaxPower                 : 0x32 (100 mA)
Data (HexDump)           : 09 02 19 00 01 01 00 80 32 09 04 00 00 01 FF 00   ........2.......
                           00 00 07 05 82 02 00 02 00                        .........

        ---------------- Interface Descriptor -----------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x04 (Interface Descriptor)
bInterfaceNumber         : 0x00
bAlternateSetting        : 0x00
bNumEndpoints            : 0x01 (1 Endpoint)
bInterfaceClass          : 0xFF (Vendor Specific)
bInterfaceSubClass       : 0x00
bInterfaceProtocol       : 0x00
iInterface               : 0x00 (No String Descriptor)
Data (HexDump)           : 09 04 00 00 01 FF 00 00 00                        .........

        ----------------- Endpoint Descriptor -----------------
bLength                  : 0x07 (7 bytes)
bDescriptorType          : 0x05 (Endpoint Descriptor)
bEndpointAddress         : 0x82 (Direction=IN EndpointID=2)
bmAttributes             : 0x02 (TransferType=Bulk)
wMaxPacketSize           : 0x0200 (max 512 bytes)
bInterval                : 0x00 (never NAKs)
Data (HexDump)           : 07 05 82 02 00 02 00                              .......

      -------------------- String Descriptors -------------------
             ------ String Descriptor 0 ------
bLength                  : 0x04 (4 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language ID[0]           : 0x0409 (English - United States)
Data (HexDump)           : 04 03 09 04                                       ....
             ------ String Descriptor 1 ------
bLength                  : 0x20 (32 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "Neoden HangZhou"
Data (HexDump)           : 20 03 4E 00 65 00 6F 00 64 00 65 00 6E 00 20 00    .N.e.o.d.e.n. .
                           48 00 61 00 6E 00 67 00 5A 00 68 00 6F 00 75 00   H.a.n.g.Z.h.o.u.
             ------ String Descriptor 2 ------
bLength                  : 0x26 (38 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "B0001 Camera Shibz"
Data (HexDump)           : 26 03 42 00 30 00 30 00 30 00 31 00 20 00 43 00   &.B.0.0.0.1. .C.
                           61 00 6D 00 65 00 72 00 61 00 20 00 53 00 68 00   a.m.e.r.a. .S.h.
                           69 00 62 00 7A 00                                 i.b.z.

Second camera



    =========================== USB Port8 ===========================

Connection Status        : 0x01 (Device is connected)
Port Chain               : 5-8
PortAttributes           : 0x00000002 (Shared USB2)

      ======================== USB Device ========================

        +++++++++++++++++ Device Information ++++++++++++++++++
Device Description       : Camera Neoden Tech.
Device Path              : \\?\usb#vid_52cb&pid_52cb#5&1783ac8f&0&8#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Device ID                : USB\VID_52CB&PID_52CB\5&1783AC8F&0&8
Hardware IDs             : USB\Vid_52cb&Pid_52cb&Rev_0001 USB\Vid_52cb&Pid_52cb
Driver KeyName           : {36FC9E60-C465-11CF-8056-444553540000}\0015 (GUID_DEVCLASS_USB)
Driver                   : System32\Drivers\CYUSB3.sys (Version: 1.2.3.10  Date: 2014-09-18)
Driver Inf               : C:\WINDOWS\inf\oem114.inf
Legacy BusType           : PNPBus
Class                    : USB
Class GUID               : {36FC9E60-C465-11CF-8056-444553540000} (GUID_DEVCLASS_USB)
Interface GUID           : {a5dcbf10-6530-11d2-901f-00c04fb951ed} (GUID_DEVINTERFACE_USB_DEVICE)
Service                  : CYUSB3
Enumerator               : USB
Location Info            : H0001 Camera Shibz
Manufacturer Info        : Cypress
Capabilities             : 0x84 (Removable, SurpriseRemovalOK)
Status                   : 0x0180600A (DN_DRIVER_LOADED, DN_STARTED, DN_DISABLEABLE, DN_REMOVABLE, DN_NT_ENUMERATOR, DN_NT_DRIVER)
Problem Code             : 0
Address                  : 8
Power State              : D0 (supported: D0, D3, wake from D0, wake from D3)

        +++++++++++++++++ Registry USB Flags +++++++++++++++++
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags
 GlobalDisableSerNumGen  : REG_BINARY 01
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags\52CB52CB0001
 osvc                    : REG_BINARY 00 00

        ---------------- Connection Information ---------------
Connection Index         : 0x08 (8)
Connection Status        : 0x01 (DeviceConnected)
Current Config Value     : 0x01
Device Address           : 0x04 (4)
Is Hub                   : 0x00 (no)
Number Of Open Pipes     : 0x01 (1)
Device Bus Speed         : 0x02 (High-Speed)
Pipe0ScheduleOffset      : 0x00 (0)
Data (HexDump)           : 08 00 00 00 12 01 00 01 00 00 00 40 CB 52 CB 52   ...........@.R.R
                           01 00 01 02 00 01 01 02 00 04 00 01 00 00 00 01   ................
                           00 00 00 07 05 82 02 00 02 00 00 00 00 00         ..............

    ---------------------- Device Descriptor ----------------------
bLength                  : 0x12 (18 bytes)
bDescriptorType          : 0x01 (Device Descriptor)
bcdUSB                   : 0x100 (USB Version 1.00)
bDeviceClass             : 0x00 (defined by the interface descriptors)
bDeviceSubClass          : 0x00
bDeviceProtocol          : 0x00
bMaxPacketSize0          : 0x40 (64 bytes)
idVendor                 : 0x52CB
idProduct                : 0x52CB
bcdDevice                : 0x0001
iManufacturer            : 0x01 (String Descriptor 1)
 Language 0x0409         : "Neoden HangZhou"
iProduct                 : 0x02 (String Descriptor 2)
 Language 0x0409         : "H0001 Camera Shibz"
iSerialNumber            : 0x00 (No String Descriptor)
bNumConfigurations       : 0x01 (1 Configuration)
Data (HexDump)           : 12 01 00 01 00 00 00 40 CB 52 CB 52 01 00 01 02   .......@.R.R....
                           00 01                                             ..

    ------------------ Configuration Descriptor -------------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x02 (Configuration Descriptor)
wTotalLength             : 0x0019 (25 bytes)
bNumInterfaces           : 0x01 (1 Interface)
bConfigurationValue      : 0x01 (Configuration 1)
iConfiguration           : 0x00 (No String Descriptor)
bmAttributes             : 0x80
 D7: Bus Powered         : 0x01 (yes)
 D6: Self Powered        : 0x00 (no)
 D5: Remote Wakeup       : 0x00 (no)
 D4..0: Reserved, set 0  : 0x00
MaxPower                 : 0x32 (100 mA)
Data (HexDump)           : 09 02 19 00 01 01 00 80 32 09 04 00 00 01 FF 00   ........2.......
                           00 00 07 05 82 02 00 02 00                        .........

        ---------------- Interface Descriptor -----------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x04 (Interface Descriptor)
bInterfaceNumber         : 0x00
bAlternateSetting        : 0x00
bNumEndpoints            : 0x01 (1 Endpoint)
bInterfaceClass          : 0xFF (Vendor Specific)
bInterfaceSubClass       : 0x00
bInterfaceProtocol       : 0x00
iInterface               : 0x00 (No String Descriptor)
Data (HexDump)           : 09 04 00 00 01 FF 00 00 00                        .........

        ----------------- Endpoint Descriptor -----------------
bLength                  : 0x07 (7 bytes)
bDescriptorType          : 0x05 (Endpoint Descriptor)
bEndpointAddress         : 0x82 (Direction=IN EndpointID=2)
bmAttributes             : 0x02 (TransferType=Bulk)
wMaxPacketSize           : 0x0200 (max 512 bytes)
bInterval                : 0x00 (never NAKs)
Data (HexDump)           : 07 05 82 02 00 02 00                              .......

      -------------------- String Descriptors -------------------
             ------ String Descriptor 0 ------
bLength                  : 0x04 (4 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language ID[0]           : 0x0409 (English - United States)
Data (HexDump)           : 04 03 09 04                                       ....
             ------ String Descriptor 1 ------
bLength                  : 0x20 (32 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "Neoden HangZhou"
Data (HexDump)           : 20 03 4E 00 65 00 6F 00 64 00 65 00 6E 00 20 00    .N.e.o.d.e.n. .
                           48 00 61 00 6E 00 67 00 5A 00 68 00 6F 00 75 00   H.a.n.g.Z.h.o.u.
             ------ String Descriptor 2 ------
bLength                  : 0x26 (38 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "H0001 Camera Shibz"
Data (HexDump)           : 26 03 48 00 30 00 30 00 30 00 31 00 20 00 43 00   &.H.0.0.0.1. .C.
                           61 00 6D 00 65 00 72 00 61 00 20 00 53 00 68 00   a.m.e.r.a. .S.h.
                           69 00 62 00 7A 00                                 i.b.z.

Thats a lot of data, but there are the endpoints, scanning with a quick cheat with the CyUsb API just running around the endpoints til it was valid, but that doesn’t give me the data as fast as this does.

Sniffing DeviceIO using a simplified Test App

Next thing to try is sniffing the DeviceIO going to the cameras, but there will likely be a lot of noise and the Neoden test app doesn’t seem to succesfully grab any data, so lets make a quick app, new project  in VS2017 again. Making a console app

image

Added the videoinput/opencv libs and headers for video capture, and create a simple image display

#include <iostream>

#include <stdint.h>

#include <stdlib.h>

#include “opencv2/highgui/highgui.hpp”

#include “opencv2/imgproc/imgproc.hpp”

#include “opencv2/opencv.hpp”

#pragma comment(lib,”comctl32.lib”)

#pragma comment(lib,”gdi32.lib”)

#pragma comment(lib,”vfw32.lib”)

using namespace cv;

using namespace std;

int main()

{
        int g_width = 1024, g_height = 1024;

    Mat cameraFrame ( g_width, g_height, CV_8UC3, Scalar ( 10, 100, 150 ) );;
        cv::Mat greyMat;
        flip ( cameraFrame, cameraFrame, 1 );
        cv::cvtColor ( cameraFrame, greyMat, CV_BGR2GRAY );
        Size size ( g_width, g_height );
        resize ( greyMat, greyMat, size );

    imshow ( “camera”, greyMat );

    cv::waitKey ( 0 )

}

Creating a .lib and .h for a DLL

Next step is linking to the NeodenCamera.dll which we can do with LoadLibrary or create a header and .lib so lets do that.

dumpbin /exports NeodenCamera.dll

take these

   1    0 00001840 img_capture
     2    1 000017D0 img_init
     3    2 00001870 img_led
     4    3 000019F0 img_read
     5    4 00001A30 img_readAsy
     6    5 000018B0 img_reset
     7    6 000018F0 img_set_exp
     8    7 00001930 img_set_gain
     9    8 00001970 img_set_lt
    10   9 000019B0 img_set_wh

convert it to

LIBRARY NeodenCamera.dll

EXPORTS
            img_capture
            img_init
            img_led
            img_read
            img_readAsy
            img_reset
            img_set_exp
            img_set_gain
            img_set_lt
            img_set_wh

save it as NeodenCamera.def, from VC folder use  (run vcvars32.bat)

lib /def:NeodenCamera.def /out:NeodenCamera.lib /machine:x86

This makes the .lib and .exp file, we just need the .LIB, add it to your project.

Finally we need the prototypes for the function, IDA or such can help with that. Typically these sorts of DLLs  use stack based argument passing as in standard C so they’re easier to figure out. if its C++ and they’re using mangled names you can demangle for at least the basics. There are lots of tutorials on IDA so rather than focus there i’ll just kickstart with what is known so far

bool _cdecl img_capture ( int which_camera );

int _cdecl img_init();

bool _cdecl img_led ( int which_camera, int16_t mode );

int _cdecl img_read ( int which_camera, unsigned char * pFrameBuffer, int BytesToRead, int dwMilliseconds);

int _cdecl img_readAsy ( int which_camera, unsigned char * pFrameBuffer, int BytesReturned, int dwMilliseconds);

int _cdecl img_reset ( int which_camera );

bool _cdecl img_set_exp ( int which_camera, int16_t exposure );

bool _cdecl img_set_gain ( int which_camera, int16_t gain );

bool _cdecl img_set_lt ( int which_camera, int16_t a2, int16_t a3 );

bool _cdecl img_set_wh ( int which_camera, int16_t w, int16_t h );

these are C linkage, the may have to be set to extern “C”  depending on the setup

Starting here, calling img_init() and noting the results . Running the app you should get a warning about the NeodenCamera.dll missing which is expected, so add the dll to the path or to the same folder as the test exe

calling img_init()

bool ret = img_init();

printf ( “%d = img_ini\nt”, ret );

yields

DLL 1->DLL_PROCESS_ATTACH

USB╔Φ▒╕┴¼╜╙╩²[2]!
╔Φ▒╕┤·║┼[0]  = B0001 Camera Shibz
╔Φ▒╕┤·║┼[1]  = H0001 Camera Shibz

1 = img_init

so far so good, but since that  that is a function with no parameters and hard to mess up. but it means the .def and .lib worked

Lets try to read the image from camera 5 (UP)

unsigned char buffer[ 1024 * 1024  ];

int main()

{
       int g_width = 1024, g_height = 1024;
       DWORD bytesToRead= g_width * g_height;
       bool ret = img_init();
       memset ( &buffer[0], 0xaa, sizeof ( buffer ) );
       printf ( “%d = img_init\n”, ret );
       // camera indices are to be 1 and 5(looking up)
       ret = img_readAsy ( 5, &buffer[0], bytesToRead, 1000 );
       printf ( “%d = img_readAsy, %d\n”, ret, bytesToRead);
       Mat cameraFrame ( Size ( g_width, g_height ), CV_8UC1, buffer, 1024 );
       imshow ( “camera”, cameraFrame );

      cv::waitKey ( 0 );

}

image

Great there is is the UP camera, I also think we have to init the width and height first. but after a run of the neoden software first and the settings stayed.

Lets try the down camera next, switch the 5 to 1

image

Neat.

Next add the width and height set, which seems to work

int main()

{
        int g_width = 1024, g_height = 1024;
        DWORD bytesToRead = g_width * g_height;

    memset ( &buffer[0], 0xaa, sizeof ( buffer ) );

    bool ret = img_init();
      printf ( “%d = img_init\n”, ret );

    ret = img_set_wh ( 1, g_width, g_height );
      printf ( “%d = img_set_wh(1,w,h)\n”, ret );

    ret = img_set_wh ( 5, g_width, g_height );
      printf ( “%d = img_set_wh(5,w,h)\n”, ret );

    // camera indices are to be 1 and 5(looking up)
      ret = img_readAsy ( 1, &buffer[0], bytesToRead, 1000 );
      printf ( “%d = img_readAsy, %d\n”, ret, bytesToRead );

    Mat cameraFrame ( Size ( g_width, g_height ), CV_8UC1, buffer, 1024 );

    imshow ( “camera”, cameraFrame );

    cv::waitKey ( 0 );

}

Thats enough to get started, the simpler the better.

Sniffing DeviceIO

The PNP software starts up with

img_init
img_set_exp 1 25
img_set_gain) 1 8
img_set_lt 1 624 496
img_set_wh 1 32 32
img_init
img_set_exp 5 100
img_set_lt 5 128 0
img_set_wh 5 1024 1024

Previously it wasn’t noted that the software calls init twice. also odd size for camera 1

From the test app

img_set_exp) 1 25
img_set_gain) 1 8
img_set_lt) 1 128 0
img_set_wh) 1 1024 1024
img_readAsy) 1 0x30d0030 1048576 1000 ms

Which all matches

Another great tool for spying on API’s is API Monitor using the X32 version and turning off everything but DeviceIoControl then running the test appl, clearing out the trace until the image appears, then switching the camera we see the following

Use the binoculars to bring up the search after loading X32 version of API monitor

 image

Search for DeviceIOControl (case insensitive) and then check them off in the API Filter

image

Then either use monitor new process or enable the capture of newly launched processes and it’ll ask to monitor the new process

image

Now run Neoden4 software or the TestApp previously made ( add published to github link)

image

Then API Monitor shows you the filtered functions we’re interested in so press the various manual test functions to see the results, it’ll also show the buffers being passed and the parameters.

DeviceIoControl ( 0x000000b8, 0x220024, 0x00c00020, 524326, 0x00c00020, 524326, 0x0018fc34, 0x0018fc98 )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000831

DeviceIoControl ( 0x000000b8, 0x220024, 0x03840020, 524326, 0x03840020, 524326, 0x0018fc34, 0x0018fcac )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000560



DeviceIoControl ( 0x000000b8, 0x00220020 , 0x002ca348, 44, 0x002ca348, 44, 0x0018fbd0, 0x0018fbf8 )
FALSE    997 = Overlapped I/O operation is in progress.     0.0000057

for img_led(5,0)

DeviceIoControl ( 0x000000bc, 0x00220020  , 0x0310a348, 44, 0x0310a348, 44, 0x0063fc54, 0x0063fc7c )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000213
0000  40 b2 00 00 00 00 06 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00 0022  06 00 00 00 b2 01 00 00 00 00        

for img_led(5,1)

DeviceIoControl ( 0x000000bc,0x00220020    , 0x0310a348, 44, 0x0310a348, 44, 0x0063fc54, 0x0063fc7c )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000239
0000  40 b2 01 00 00 00 06 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00 0022  06 00 00 00 b2 01 01 00 00 00       

Marked the differences, i’m not sure if img_led is used, or if i’m using it right anyway these camera boards tend to have a cypress FX CY7C68013 and either an fpga (if they’re UVC or need a lot of processing) or straight hooked to the GPIOs to do triggers, LED flash etc. So that could be where they are or it could be attached from the RS232 and the motor control board.

The Flash LEDs are not controlled via the camera dll, i think they’re a throwback to another version since we know the LEDs are connected to the head motor control board and controlled via CAN bus from the primary motor control board. They are included in my test harness from for serial control.



As a note be aware of the EP0 control since that also controls how the FX is programmed and erased


For capture of up(5)

DeviceIoControl ( 0x000000bc, 0x00220024, 0x03840020, 524326, 0x03840020, 524326, 0x0063fb84, 0x0063fbe8 )


FALSE    997 = Overlapped I/O operation is in progress.     0.0000777


DeviceIoControl ( 0x000000bc, 0x00220024, 0x03960020, 524326, 0x03960020, 524326, 0x0063fb84, 0x0063fbfc )
    FALSE    997 = Overlapped I/O operation is in progress.     0.0000560


Partial Buffer


0000  00 00 00 00 00 00 00 00 00 00 00 00 00 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00
0022  00 00 08 00 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 00 01 00 01 01 01 01 01 01 01 01 01
0044  01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01
0066  01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 00 01 01 01 00 01 00 01 01 01 01 01 01 01 01 01 01 01
0088  00 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01
00aa  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
00cc  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
00ee  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 00 01 00 01 00 01

DeviceIoControl ( 0x000000bc, 0x00220020  , 0x0310a348, 44, 0x0310a348, 44, 0x0063fb20, 0x0063fb48 ) 


FALSE    997 = Overlapped I/O operation is in progress.     0.0000063


0000  40 b1 00 00 00 00 06 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00
0022  06 00 00 00 b1 fb 00 00 00 00
       

       

The image buffer looks about right, since it is mostly a black image, refreshing it would give a similar result (camera senors are a good source of noise)

0x00220024  is 0x22, IOCTL_ADAPT_SEND_NON_EP0_TRANSFER. buffered, any file


This IOCTL command is used to request Bulk, Interrupt or Isochronous data transfers across corresponding USB device endpoints.


0x00220020 is IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER. buffered, any file


This command sends a control request to the default Control endpoint, endpoint zero.

So all making sense so far, back to the driver.


Decoding the above for IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER

union {
       struct {
       UCHAR Recipient:5;
       UCHAR Type:2;
       UCHAR Direction:1;
       } bmRequest;
       UCHAR bmReq;


};


bmRequest.Recipient = 0; // Device


bmRequest.Type = 2; // Vendor


bmRequest.Direction = 1; // IN command (from Device to Host)


int iXmitBufSize = sizeof(SINGLE_TRANSFER) + bufLen; // The size of the two-part  structure


UCHAR *pXmitBuf = new UCHAR[iXmitBufSize]; // Allocate the memory


ZeroMemory(pXmitBuf, iXmitBufSize);


PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER)pXmitBuf; // The SINGLE_TRANSFER comes
    first


pTransfer->SetupPacket.bmRequest = bmReq;


pTransfer->SetupPacket.bRequest = ReqCode;


pTransfer->SetupPacket.wValue = Value;


pTransfer->SetupPacket.wIndex = Index;


pTransfer->SetupPacket.wLength = bufLen;


pTransfer->SetupPacket.ulTimeOut = TimeOut / 1000;


pTransfer->Reserved = 0;


pTransfer->ucEndpointAddress = 0x00; // Control pipe


pTransfer->IsoPacketLength = 0;


pTransfer->BufferOffset = sizeof (SINGLE_TRANSFER);


pTransfer->BufferLength = bufLen;

and IOCTL_ADAPT_SEND_NON_EP0_TRANSFER

PUCHAR CCyBulkEndPoint::BeginDataXfer(PCHAR buf, LONG bufLen, OVERLAPPED *ov)


{


if (hDevice == INVALID_HANDLE_VALUE) return NULL;


int iXmitBufSize = sizeof (SINGLE_TRANSFER) + bufLen;


PUCHAR pXmitBuf = new UCHAR[iXmitBufSize];


ZeroMemory(pXmitBuf, iXmitBufSize);


PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER)pXmitBuf;


pTransfer->Reserved = 0;


pTransfer->ucEndpointAddress = Address;


pTransfer->IsoPacketLength = 0;


pTransfer->BufferOffset = sizeof (SINGLE_TRANSFER);


pTransfer->BufferLength = bufLen;


// Copy buf  into pXmitBuf


UCHAR *ptr = (PUCHAR) pTransfer + pTransfer->BufferOffset;


memcpy(ptr, buf, bufLen);


DWORD dwReturnBytes;


DeviceIoControl(hDevice, IOCTL_ADAPT_SEND_NON_EP0_TRANSFER,
                  pXmitBuf, iXmitBufSize,
                  pXmitBuf, iXmitBufSize,
                  &dwReturnBytes, ov);


return  pXmitBuf;


}

So for the image transfer it looks like it is using

CCyUSBEndPoint::BeginBufferedXfer

So we can just follow the call chain CyApi and follow it up to

bool CCyControlEndPoint::Write(PUCHAR buf, LONG &bufLen)

Backtracking a tad


On a personal note I have used IDA for a while but sometimes will forget to use it with all the tools it has and just manually convert all the offsets. But there are useful tools like FLIRT and people have tried to put together libraries of FLIRT databases, but there are a lot of libraries to do that for.


Luckily for us Cypress publishes the PDB file for CyUsb3.sys so if you load it into IDA it’ll create all the structures that are in the PDB for you. After loading it and letting IDA figure itself all out, dump the typeinfo to an IDC, then reload the app(NeodenCamera.dll) that uses the CyUSB3.sys which is likely different and execute the IDC you just made, that will transfer the structs to the new RE project but not the offsets etc will will be wrong. Now keep a copy of the IDC somewhere you’ll forget all about for next time


As an example the _SINGLE_TRANSFER structure which has a union (also mapped)

00000000 _SINGLE_TRANSFER struc ; (sizeof=0x26, align=0x2, mappedto_45)

00000000 ___u0           $902C784530A83C47F9612DF5432F758B ?

0000000C reserved        db ?

0000000D ucEndpointAddress db ?

0000000E NtStatus        dd ?

00000012 UsbdStatus      dd ?

00000016 IsoPacketOffset dd ?

0000001A IsoPacketLength dd ?

0000001E BufferOffset    dd ?

00000022 BufferLength    dd ?

00000026 _SINGLE_TRANSFER ends

Typing  that in manually the first time, after forgetting the union… then redoing it, then thought there must be a better way, and of course there is, and this is one of those ways, but no doubt next time it’ll just get brute forced again

Now apply that struct to the code

SINGLE_TRANSFER *__thiscall CCyIsocEndPoint__BeginBufferedXfer(int this, void *inputBuffer, size_t bytesToWrite, LPOVERLAPPED lpOverlapped)


{
      int v4; // ebx
      int blocks; // esi
      unsigned int v7; // edi
      _SINGLE_TRANSFER *pTransfer; // esi
      DWORD dwReturnBytes; // [esp+Ch] [ebp-8h]
      DWORD iXmitBufSize; // [esp+10h] [ebp-4h]


  v4 = this;
      if ( *(_DWORD *)(this + 4) == -1 )            // HDEVICE
        return 0;
      blocks = (signed int)bytesToWrite / *(unsigned __int16 *)(this + 12);
      if ( (signed int)bytesToWrite % *(unsigned __int16 *)(this + 12) )
        ++blocks;
      if ( !blocks )
        return 0;
      v7 = 8 * blocks;
      iXmitBufSize = 8 * blocks + bytesToWrite + 38;
      pTransfer = (_SINGLE_TRANSFER *)operator new(iXmitBufSize);
      memset(pTransfer, 0, iXmitBufSize);
      pTransfer->reserved = 0;
      pTransfer->ucEndpointAddress = *(_BYTE *)(v4 + 10);
      pTransfer->IsoPacketLength = v7;
      v7 += 38;
      pTransfer->BufferOffset = v7;
      pTransfer->IsoPacketOffset = 38;              // SINGLE_TRANSFER
      pTransfer->BufferLength = bytesToWrite;
      memcpy(&pTransfer->SetupPacket.bmReqType._bf0 + v7, inputBuffer, bytesToWrite);
      dwReturnBytes = 0;
      DeviceIoControl(
        *(HANDLE *)(v4 + 4),
        0x220024u,
        pTransfer,
        iXmitBufSize,
        pTransfer,
        iXmitBufSize,
        &dwReturnBytes,
        lpOverlapped);                              // IOCTL_ADAPT_SEND_NON_EP0_TRANSFER
      *(_DWORD *)(v4 + 32) = GetLastError();
      return pTransfer;


}

The Cypress SDK contains the source for that function, let us see how it compares

PUCHAR CCyUSBEndPoint::BeginBufferedXfer ( PUCHAR buf, LONG bufLen, OVERLAPPED *ov )

{
        if ( hDevice == INVALID_HANDLE_VALUE ) { return NULL; }

    int iXmitBufSize = sizeof ( SINGLE_TRANSFER ) + bufLen;
        PUCHAR pXmitBuf = new UCHAR[iXmitBufSize];
        ZeroMemory ( pXmitBuf, iXmitBufSize );

    PSINGLE_TRANSFER pTransfer = ( PSINGLE_TRANSFER ) pXmitBuf;
        pTransfer->ucEndpointAddress = Address;
        pTransfer->IsoPacketLength = 0;
        pTransfer->BufferOffset = sizeof ( SINGLE_TRANSFER );
        pTransfer->BufferLength = bufLen;

    // Copy buf into pXmitBuf
        UCHAR *ptr = ( PUCHAR ) pTransfer + pTransfer->BufferOffset;
        memcpy ( ptr, buf, bufLen );

    DWORD dwReturnBytes;

    DeviceIoControl ( hDevice,
                          IOCTL_ADAPT_SEND_NON_EP0_TRANSFER,
                          pXmitBuf,
                          iXmitBufSize,
                          pXmitBuf,
                          iXmitBufSize,
                          &dwReturnBytes,
                          ov );

    UsbdStatus = pTransfer->UsbdStatus;
        NtStatus   = pTransfer->NtStatus;

    LastError = GetLastError();
        return pXmitBuf;

}

The compiler has optimised some things away and made some little tricks but it is close.




Reading an I2C EEPROM

It’s definitely been Cypress week around here, with the RGB Blinky Ball (which has a cypress psoc4) we wante d to ship a programmer, the Cypress one is $90 so not that. A bit banging version for the FX2LP ecists so we hooked that one up and it worked great. There is a a fork here PSOC

These boards are dirt cheap and are great for either reading logic at high speed or writing it.

image

It also comes with an AT24C128 eeprom for boot strapping the FX2LP.

image

Recalling that the Microchip PICCKIT can read EEPROMs with, and just happen to have one the desk for the One Key Keyboard.

It needs this software.
http://ww1.microchip.com/downloads/en/DeviceDoc/PICkit3%20Programmer%20Application%20v3.10.zip

The I2C Address of the chip is set as

A0 = 1 A1 = 0 A2 = 0 = 0xA3


image

Next you have to mod the PICKIT

> 24LC I2C bus devices:         Bus Speed-                 400kHz with Tools -> Fast Programming checked                 100kHz with Tools -> Fast Programming unchecked

        NOTE: Bus pullups are required for all
              programming operations.  400kHz requires
              2k Ohm pullups.

        NOTE: The I2C (24LC) Serial EEPROM devices require the following PICkit 3
              hardware changes to work properly:

              Remove TR3 from the PICkit 3.
              Remove R50 from the PICkit 3.

        Connections for 24LC devices
        ---------------------------------------
        PICkit 3 Pin             24LC Device Pin (DIP)
        (2) Vdd                  8 Vcc
        (3) GND                  4 Vss
        (5) PGC                  6 SCL (driven as push-pull)
        (6) PGM(LVP)             5 SDA (requires pullup)
                                 7 WP - disabled (GND)
                                 1, 2, 3 Ax pins
                                    Connect to Vdd or GND per
                                    datasheet and to set address

url

Load this “OS” into it

image

image

Hook up power, ground , sda and scl ,  Make sure before you add power you start a capture, since it only sends and clocks when transmitting you can trigger and capture or just run a few second capture that’ll give you time to switch on the power.

The PicKit 3 is meant to be modded , trying it first. remove the R50 4.7K, and the 5.0V Zener at TR3. The software continually resets the 3.3V to 5V so watch for that.  This is a clone of the Pickit 3 the lower right MELF is the diode TR3

imageT

This didn’t seem to work very well and the data just reapeated, and was very sparse i’ve seen enough code in binary to know its not right. Figured it was worth a shot.

So next to try is the Ginkgo box which is a USB to CAN/I2C/SPI/GPIO test box it is useful for all sorts of things and originally it was used to do automated testing on some SPI control systems. But also a no go I suspect it was the CAN firmware but the doc’s are less than clear about it (it was. you can reflash any firmware on to it and even though the label says I2C/SPI/CAN it has internal checks note: add to list  of next projects also there is an internal jumper if you set it you can recover the bad flash)

So lets just use a Logic Analyser, coincedentally we made a mini logic analyser that was based on the same 68013A chip at NSL called the AnnaLogic which is plenty capable to do this, but lets use\ the Logic Pro 16 instead.

image

It pops out immediatately and now it is easy to recognise the fimrmware since all the fx2lp bin’s I’ve seen to date have had a 222222 sequence before the last few bytes., next is to convert the trace from the Saleae to a bin and load it into my FX2 dev board. The text file export has the time stap and address, followed by the data byte, just extract that column after removing the setup code.

Time [s],Packet ID,Address,Data,Read/Write,ACK/NAK
0.343118400000000,,0xA1,,Read,NAK
0.343347040000000,1,0xA3,0x32,Read,NAK
0.343575520000000,2,0xA2,0x00,Write,ACK
0.343681120000000,2,0xA2,0x00,Write,ACK
0.343909600000000,3,0xA3,0xC2,Read,ACK

C2 is the start of the EEPROM

The logs show that after a tiny pause from the firmware download to the FX2LP it starts sending out on I2C address 0xBA

0.725436960000000,3,0xA3,0x00,Read,NAK

time gap

352480000000,4,0xBA,0x01,Write,ACK
0.738503360000000,4,0xBA,0x00,Write,ACK
0.738653760000000,4,0xBA,0x8C,Write,ACK

Noting that is likely a camera on the I2C addres 0xBA, so searching for I2C Camera 0xBA and the first hit is the MT9M001

Array Format (5:4): 1,280H x 1,024V
Monochrome sensor
Slave address 0xBA (SADDR=0)

It is pretty close to what we expected.  This camera is very popular for interfacing to the FX2LP. There are apparently different versions of the Neoden4 with a different camera, and that vendor uses similar sensors too. But we don’t really need the model yet.

On that MT9M001 sensor 0x8C is a reserved address, so not sure if its a good match

Next task on the list is to sniff the CAN busses, and because of the previous automotive woirk NSLLabs posses every CAN adapter known to humanity, so that should be straightforward..

…more later…