Updates from January, 2019 Toggle Comment Threads | Keyboard Shortcuts

  • charliex 11:35 pm on January 31, 2019 Permalink | Reply
    Tags: neoden, neoden 4,   

    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…

     
    • nope 7:33 pm on March 7, 2019 Permalink | Reply

      password to change language
      #python
      code=””
      mac=”aa:bb:cc:dd:ee:ff”
      macInt = [int(t,16) for t in mac.split(“:”)]
      for i in range(3):
      t = macInt[i*2] + macInt[i*2+1]
      code+=”{0:02x}”.format(t)
      print(code)
      code=”neoden2015 {0} Language”.format(code)
      password=binascii.crc32(code.encode(‘utf-8’))% (1<<32)
      print(password)

    • charliex 9:21 pm on March 7, 2019 Permalink | Reply

      Nice thanks

    • Louis 4:24 pm on March 22, 2019 Permalink | Reply

      Amazing work my friend! Last night I started to tinker with my NeoDen4 and googled KYSYSProtect and came across your post. Tons of good info in this post, and great job on the Eagle ULP additions, as well! If you continue to dive into the NeoDen4, please continue to share you findings! Thank you from a fellow owner 🙂

    • Louis 11:37 pm on March 26, 2019 Permalink | Reply

      Something I have found that may help others – when running the software on a clean Windows install (tried 10, 7 and XP while I was having the issue) the software spits out an error, written in Chinese, for imgdll.dll. This error, which translates to the inability to load imgdll.dll, seems to impact at least the upward flash, even though the DLL functions don’t seem indicate a correlation. Using a dependency walker and Process Explorer, I could not find the root cause. Long story short, you must install the Microsoft Visual C++ 2008 Redistributable package to eliminate the issue.

    • Beb 8:29 am on June 11, 2019 Permalink | Reply

      Hello,

      Looking forward to you getting the can bus data, do want to try and run a feeder via the can. Thanks in advance if you get this data

  • charliex 8:05 pm on August 14, 2016 Permalink | Reply  

    ChipCon SmartRF04 EB firmware reflashing 

    A while ago I picked up a CC1110 from eBay for cheap as we were looking at doing an IM-ME clone, Eventually got around back to the C11xx work.

    ChipCon were bought by TI in about 2006, for the CC family, see the clue? the devboard I have is actually from the pre TI days, so I was amazed when in 2016 I downloaded the TI Smart RF software it connected and saw the board. The two dev boards also actually worked so lucked out on eBay again…

    On plugging in it asked to update the firmware, which I did. . After that I could use Smart RF to control the board, but not from the board itself, no LCD display. So I went off and looked for old versions of Smart RF and older HEX files, no luck , OEMs always want to get rid of that stuff, they should make it all available but it is a support headache from the help vampyres, and TI’s forums had a few similar questions that mostly dead ended.

    So I looked into the host chip, it’s a SI 8051F32, so needs a yet another debugger interface, I looked it up and see its USB Debug Adapter for about $30, not bad. It has an unusual shape and I recall having one, so I went and dug around and found it in a box, I actually had took it out of a box, looking through various devboards, looked at my hand and there it was, great…

    So downloaded SI’s production programmer, still works, doesn’t read back only verify and program.. (is the verify download and compare, checksum or compare on chip) saw eclipse mentioned/java and went back to look for a different programming tool, SI have a utility dll from programming, read docs same deal only a memory downloader, check the memory map 0x0 – 0x3FFF with after 0x3DFF reserved.  Once I connect the USB Debug Adapter to the new software it want’s to update the firmware, here we go again, flashes and its OK after a USB reset.

    Connected to the board, downloaded it to a “log file” which is literally a dump of the memory in either dec(with leading zeros)  or hex, and a cr\lf… quick sed script add commas to the dec, load it into an array, oh yeah, leading 0’s so it thinks its base 8 octal. redo it in hex output, add 0x, and end with , read it into an array write it out.

    convert the newer TI supplied hex that didn’t work files to bin from the new 40 version, compare the two against the one I just read. they’re similar at the start, but it is shifted..  I realise the bootloader is there as as a separate hex, it’s loaded from 0x0 – 0x0800, so I chop that off and wrote out the file. they are now only slight differences, 0xFF where 0x00 is in the hex, this is likely a skip in the hex (unlikely since the hex file showed a single section) or the default erase byte .

    Had I been watching the hex2bin output I’d have seen the start address of the firmware as 0x800, so at least independently verified, had too many windows open..

    Lowest address  = 00000800
    Highest address = 00003BDB
    Pad Byte        = 0
    8-bit Checksum = BE

    So now all have to do is reflash the board with the code from the second board that I did not upgrade, so convert the .bin to .hex, either without the bootloader and reflash it with the TI tools, or clone one to the other and reflash with SI adapter.

    Reflashed the whole thing in the end, which reverted it to 28 build.. Still no display when the EM is connected….so either flash on the EM module (which still works in Smart RF) mode or  theres something wrong with the EM module…

     

    and yes reflashed the cc1110 and its back to life!  Probably should have tried that first Smile

     

    SI USB chip docs

    http://www.keil.com/dd/docs/datashts/silabs/c8051f32x.pdf

    SI flash util

    http://www.silabs.com/Support%20Documents/TechnicalDocs/an117.pdf

     

    SmartRF flash programmer to redo the CC1110

    http://www.ti.com/tool/flash-programmer

     
    • Ferdinand 9:35 pm on August 17, 2016 Permalink | Reply

      If you are looking for an IM-ME, shoot me an email. I still have one I don’t use.

      • charliex 9:37 pm on August 17, 2016 Permalink | Reply

        ahh where were you a few months ago, had to ebay one for redonkuoulus amounts. cheers though.

  • charliex 11:41 pm on January 27, 2016 Permalink | Reply  

    Spindle controlling, and GUI hacks, part #1 

    Probably going to be a longish entry, at least video wise.

    One of the things that’ll improve usage, bit life, finish quality etc is having the computer control the speed of the motor. As i mentioned in the last log the flashcut can’t do it without an upgrade, given a crappy HID numpad with a cover is about $500. I didn’t want to ask. I started to look at Mach3, discovered Mach4 pushed off backlash compensation to the drivers boards I thought I’d try another way. Before I go on, CNC people are the religious types, like car people. Backlash is bad, can do terrible things, but being able to correct small amounts of it for certain things is useful to me, it is a tool and like any tool it can be used incorrectly, but I still want the option to do it.

    All I had as an output on the FlashCut box was 1/0 low voltage digital on the controller side, and 0-24v or measuring resistance on the VFD wasn’t a whole lot to go on, sure I could buffer the signals but that is still on/off , can’t make a DAC since not enough control of the lines.. Really basic stuff.

    I’d picked up the Automation Direct RS485 to USB adapter that allows me to connect to the VFD to program it. The software doesn’t control the speed just the programming. I took a look around and didn’t see much available, it is modbus which is fairly common in SCADA etc. Never used it before, I believe the internals of the FlashCut might have some modbus going on. I knew other people had used the modbus support in Mach3 so it can be done, but how to the flashcut gcode controller software to the modbus of the VFD.

    I poked around and switched on the 0-10V display of the RPM in flashcut this pops up a slider and a text input box to allow you to either type in the RPM or move it up and down, so i figured all i have to do is read that out and we’ve got the RPM value.

    This is what the loopymind HAD DXF logo looks like in flashcut

    So at the bottom in the middle is the RPM edit box. This is a generic windows GUI element we can read it from somewhere else, consider it like a file system. It stores named objects that contain data we interpret, so we don’t need to know the location of the RPM variable in FlashCut’s memory space, we just need the GUI’s data which means we don’t need to hook or mess with FlashCut at all, which is desirable for something like CNC..

    I’m using Microsoft Visual Studio C++ 2016 here, but it is mostly the same procedure for the last dozen or so versions.

    In the development tool-set there is something called Spy++ that allows us to watch windows messages and interrogate the GUI, very useful tool. It’s usually on the Tools menu of Visual Studio or you can just run it from the start menu.

    Run it and you’ll get something like this :-

    We can even see this post i’m writing now listed as a window. These are a list of the Windows in the GUI, Windows (the OS) treats a lot of things like Windows(the GUI) so you can see tool tips (the little popups that show when you hover with the mouse),, there are some hidden apps/windows, Mostly visual studio windows here.

    We’re going to use the Window Search feature to find the FlashCut window handle, so run the application you want to take a look at and then in the Search menu of Spy++ use the Windows Search popup.

    Apparently I also some allergies going on.

    OK, so now we know what we’re looking for there is a Window class called "Edit" which is the name for a standard windows edit box.

    We’ll also need a library to chat to the modbus, I found libmodbus and made some windows style changes for it and added a 64 bit version of it, that is on my GitHub https://github.com/charlie-x/libmodbus it does have some specific changes for window, i changed the f/printf’s to switch to the debug message system windows uses and started to remove the errno to their version since i don’t like the idea of one variable for all errors, and a few changes for 64 bit and some of the newer API’s. It is forked from the original.

    Next we will fire up Visual Studio and start creating the application GUI, probably better to watch this one full screen,

     

    So the next steps are to track down the values from FlashCut and reflect them in our GUI, for that we’ll go back to Visual Studio and start adding code.

    This video goes through finding the window, capturing the values and reflecting them in our UI.

    We’ve pulled out all the information we need, and no need of reversing or disassembling at all. We’re not even really looking inside FlashCut, just querying the Windows GUI. This technique works for most MFC/Windows apps.If we’ve learnt anything so far, it is SUCCESS has two Cs !

    In part 2 I’ll connect up libmodbus and start talking to the drive itself.

     
  • charliex 7:45 pm on June 21, 2014 Permalink | Reply
    Tags: avr. attiny2313, , ebay, k40, laser, m415b, , stepper   

    LightObject Z Table for the K40/EBAY Chinese lasers. 

    Editors Note: This ztable doesn’t actually fit in my particular Chinese laser, it is too tall… I’m seeing if they have shorter threaded rods available. my lasers gantry is about 93mm to clear, ztable is 105mm. le sigh….

    Note 2: see bottom for updates.

     

    I Picked up a motorised Z table from light objects for my Chinese cheapo laser cutter, it’d make it a lot more useful.

    http://www.lightobject.com/Power-table-bed-kit-for-K40-small-laser-machine-P722.aspx

    Of course first time I saw it, I didn’t pick It up and it was out of stock the next day, so a few months later I saw them back again and ordered one, arrived a few days later. First thing I noticed no instructions, but simple enough. A week or so later I got an email from them saying there might be a cable missing, I wasn’t sure it was meant to come with one but either way, they sent it out and got it a few days later, so good service there.

    Of course I thought I had stepper drivers around, but didn’t so I went back to LO and bought the M415 driver. So put it together and waited for them to arrive.

    http://www.lightobject.com/Mini-2-Phase-15A-1-axis-Stepping-Motor-Driver-P650.aspx

    After hooking up the motors  and stepper to my frequency generator, I got nada, the motor wasn’t holding , stepping , buzzing or anything..

    I probed the motor wires with an ohm meter and they were open circuit, so I prodded the pins on the motor and found the two coils, the cable was wired incorrectly.

    I fed the info back to lightobject, who are a super nice place to deal with, just had to move two wires on the connector from position 1, to 2 and from 6 to 5.

     

    Correct wiring.

    I used the M415 driver, tried out a few speeds from 1khz to 16khz to see what gave the best results, 6khz seemed fine (16 steps) I also set the current of off/on/off or 1.05A measuring the power draw on my PSU it was about 0.4A which the datasheet for the motors its mean to be claim, the driver manual notes that the current on the coils might be less than you set, and it reduces the current by about 60% when idle. Though these might be different motors, they are not getting hot.

    http://www.leadshine.com/UploadFile/Down/M415Bd.pdf

    http://softsolder.com/2013/02/16/stepper-driver-specs-2m415/

     

     

    The Electronic Goldmine had a sale on miniswitches, I want to type mini microswitches, but that seems wrong, Stock # 61690B so I picked up a 100 or so they’re right angled pcb thru hole mount lever style, I wanted them for various things like cnc limit switches, so I’ll add them to this and see what happens

    http://www.goldmine-elec-products.com/prodinfo.asp?number=G16909B

    So then I needed something to drive it , poked through my dev board boxes and found a Cypress CY8CKIT-042 (as well as a stepper motor driver! ). It’s the PSOC4 Pioneer board

     

    http://www.cypress.com/?docID=47035

     

    I did a quick test app

     

    These are the pins I used.

    If either of the switches are pressed, the table goes up and down, the limit switches will stop the current direction, but allow it to go the other way.

    I set the PWM to period 32/16 count , that gave me just about 6khz

    The pins I choose make the RGB LED on the board change colour when up/.down is selected

    Pulse is wired to PUL on the m415 and Dir to DIR, common ground.

     

    I could use the capsense to do a slider for the speed it moves at, but I think a fast/slow button set is easier.

     

    Now all I have to do is mount the limit switches, and thenfit it into the laser

     

    Remove the old bed is just four screws

    and then remove these posts (and all the gunk)

     

    One of the four posts on the z table gets in the way of the smoke extractor. so I removed the post, we’ll see what the effect of that is.

     

    I haven’t decided how best to install it yet. I’ll update when I do.

     

    The CY8CKIT is about $25, so instead I decided to make it for about $2

     

    Decided to try to redo one of the old cylon boards to see if I can make a mini controller. The ATTIN2313A is a pretty neat chip. I modded my cylon to look like this, its got extra ports too. its about 2.6cm x 2.6cm

    and a short while later (This is actually a rougher version I recut it)

     

    With some rubout (potatocam)

     

    Wired up.

     

    Momentary pushes ( all my black ones were not working correctly, so I used two red) one easy way to wire up the limits is to use a microswitch with NC connections, normally closed  vs NO normally open. Wire the limit in series with the switch, so when its engaged the control button won’t be able to close the circuit, so you can only use the down one.

     

    The downside to doing that way, rather than using the microprocessors inputs is that its harder to override if for some reason you need too, but if you’ve got limited IO space, and makes the wiring simpler, plus don’t really have to worry about noise/debounce.

     

    Mounted onto the driver. since it’s a double sided board and I only cut one side, the blue kynar is doing ground, I couldn’t route it single but I might drop one of the io’s and add a ground on the rear pins. The cap is 1uF 0805 it won’t work properly without this

     

    And there we have it, now to mount it in the laser, the tedious part!

     

    Z table

     

    The wiring is really simple.

    Two phases to the motor, 24Vish to the motor driver, common ground, 5V to the opto and the controller board. The switches are common ground, then to the controller board, pins 2 and 3, pulse and dir to the controller, 8 and 9.

     

    with notes.

     

     

    Making the table shorter

    The threaded rod is M6 x 1.0 pitch. So now I need a lathe, any excuse to buy tools right? So I did !

     

    I figured I’d need some better measuring tools as I go along, so thread per inch measuring tool (doesn’t do metric aye aye aye ) mitutoyo gauges with SPC , very nice.

     

     

    Since I also had gift cards lying around on amazon from bitcoin, I thought what the hell and bought one of those teen tiny lathes, its exactly what you’d expect it to be. I’m still deciding which real lathe to actually buy.

     

     

    took the table apart

     

    Marked the aluminium posts and cut them with a hacksaw, about 1 cm,  to fit my laser.

     

    Then I marked the area to remove from the threaded rods, hacksawed them down.

     

    On one of these the brass gear was loose, so I popped it off and filled it with thread locker then put it back on , that held it.

    Next measured how much to remove for the bearings, I used the brass parts as a marker.

     

    Then I squared off the aluminium posts and drilled holes in the middle.

     

    Test fit

     

    Now to see if it clears the gantry

     

     

    I found it easier to remove the side bars while it was in the machine to get the threaded rods into the bearings

     

    Poking around the laser for power, we need 18-24V, and 5V for the CPU.

     

    This switch on the right has 5V, but if it’s a moshi board, there is an easier place.

    This white connector with the four green, and red/black wires coming off the power supply, its marked 24V G 5V L , so that’s all we need.

     

    The other side plugs into the moshi driver board.

     

    Moshi marked it so bottom is gnd, 24v, 5v, L, I tapped into those by removing the pins and soldering to them. just pressing a pointed thing into the teeth and gently removing it. Solder the wires and then push the tooth out a little and snap it back in.

     

    Next I drilled a couple of holes for the up/down switches.

     

    And that’s more or less it, plugged in the stepper and tested it, all good.

     

    I don’t have any tap/die sets so  I didn’t tap the posts , and I wanted to keep the original setup, plus again more excuses to buy more tools.

     

    Made sure it was all working before I started bolting it all down.

     

    Laser is almost running again, the pump I bought from lowes to replace the original chinese one fell apart so have to fix that now.

     

    I might make a change to the software so if you press up, hold it then press down it’ll go faster (or slower),, and vice a versa.

     
  • charliex 12:15 am on December 3, 2013 Permalink | Reply
    Tags: error 1747, windows 7   

    Error 1747 : The Authentication Service is Unknown 

    I had a Windows 7 machine in one of the racks with a bunch of services not starting, no networking so not much of anything since its headless and graphics are network remote, so I pulled it out and switched its graphics cards to see what was going on.

    Really slow to boot windows, after login black screen with mouse, sluggish response.
    dhcp, lass, service showing ‘starting’ and can’t be stopped or restarted
    ping etc gives no network, or various network errors
    Event logs stop working with “Error 1747 : The Authentication Service is Unknown”
    Even a BSOD on a reboot

    sfc /scannow  no issues, fsck, no issues. hardware all looked ok.

    As usual MVP advice is reformat and re-install, so sad.  So i did this instead

    From admin shell, cmd

    netsh winsock reset

    and rebooted, totally fine after that. sigh…

     
  • charliex 4:45 pm on October 24, 2013 Permalink | Reply
    Tags: #hacklu   

    hack.lu CTF 

    jking http://www.theamazingking.com/ and I worked on ELF

     

    first disassembled it with IDA, pulled out C code and attacked it from there, working backwards with what the key ought to be, one value at first just seemed to be anti debug , which was just the ptrace test, which would increment it.

    Also as eventually noted by fluxfingers team, if you happened to be running non root on ubuntu ( I was ) you’d get the wrong results because ubuntu doesn’t let child procs ptrace as a non root user…which would have been a big clue.

     

    unsigned char some_counter = 0xA ;

    unsigned char fluxFluxFLUX[] = "fluxFluxfLuxFLuxflUxFlUxfLUxFLUxfluXFluXfLuXFLuXflUXFlUXfLUXFLUX";

    int __cdecl ld_preload_ptrace()
    {
        int result; // eax@4
        int stat_loc; // [sp+14h] [bp-14h]@4
        int v2; // [sp+18h] [bp-10h]@6
        int v3; // [sp+1Ch] [bp-Ch]@3

        if ( getenv ( "LD_PRELOAD" ) )
        { ++counter; }

        v3 = fork();

        if ( !v3 ) {
            v2 = getppid();

            if ( ptrace ( PTRACE_ATTACH, v2, 0, 0 ) < 0 )
            { exit ( 1 ); }

            sleep ( 1u );
            ptrace ( PTRACE_DETACH, v2, 0, 0 );
            exit ( 0 );
        }

        wait ( &stat_loc );
        result = stat_loc;

        if ( stat_loc ) {
            sleep ( 1u );
            result = counter++ + 1;
        }

        return result;
    }

    int __cdecl main ( int argc, char *argv[] )
    {
        size_t password_length; // eax@4
        char v9[300]; // [sp+28h] [bp-374h]@8
        unsigned char *v10; // [sp+368h] [bp-34h]@13
        unsigned char *v11; // [sp+36Ch] [bp-30h]@10
        unsigned char *phase1_buffer; // [sp+370h] [bp-2Ch]@4
        const char *ptr_to_password; // [sp+374h] [bp-28h]@4

        unsigned int flag4; // [sp+378h] [bp-24h]@40
        unsigned int flag3; // [sp+37Ch] [bp-20h]@40
        unsigned int flag2; // [sp+380h] [bp-1Ch]@40
        unsigned int flag1; // [sp+384h] [bp-18h]@40

        size_t j; // [sp+388h] [bp-14h]@23
        size_t i; // [sp+38Ch] [bp-10h]@4

        if ( argc != 2 ) {
            printf ( "Usage: %s <flag>\n",  argv[0] );
            exit ( 0 );
        }

        puts ( "Calculating phase 1 …" );

        ptr_to_password =  argv[1];

        password_length = strlen ( argv[1] );
        phase1_buffer = ( unsigned char * ) malloc ( password_length + 1 );

        memset ( phase1_buffer, 0, password_length + 1 );

        for ( i = 0;  password_length > i; ++i ) {
            int i2;
            i2 = ( i – some_counter );

            phase1_buffer[ i ]  = ptr_to_password[ ( i – some_counter ) % password_length ];
        }

        sleep ( 1u );
        puts ( "done\n" );

        ++some_counter;

        for ( i = 0; i <= 207; ++i ) {
            v9[i] =  65;
        }

        v11 = ( unsigned char * ) malloc ( password_length + 1 );
        memset ( v11, 0, password_length + 1 );

        puts ( "Calculating phase 2 …" );

        for ( i = 0; ; ++i ) {

            if ( password_length <= i ) {
                break;
            }

            v11[i]  = some_counter ^ fluxFluxFLUX[i] ^  phase1_buffer[ i ];
        }

        sleep ( 1u );
        puts ( "done\n" );

        some_counter += 3;

    // I added the +1 for for dbg

        v10 = ( unsigned char* ) malloc ( password_length + 1 );

        memset ( v10, 0, password_length + 1 );

        for ( i = 0; ; ++i ) {

            if ( password_length <= i ) {
                break;
            }

            v10[i] = some_counter;
        }

        for ( i = 0; i <= 207; ++i ) {
            v9[i] =  66;
        }

        for ( i = 0; i <= 0xCF; ++i ) {
            v9[i] = 70;
        }

        // 3 on

        unsigned char index = 0;

        //memset ( v11, 0, password_length );

        some_counter  = 4;

    loop:

        for ( i = 0; i <= 2; ++i ) {

            printf ( "Calculating phase  %u …\n", i + 3 );

            for ( j = 0; ; ++j ) {

                if ( password_length <= j ) {
                    break;
                }

                v10[j]  ^= v11[ j ] ^ fluxFluxFLUX[ ( i + j + some_counter ) % password_length];
            }
        }

     

        for ( i = 0; i <= 0xCF; ++i ) {

            v9[i] =  69;
            v9[i] =  67;

            if ( v9 [ ( i + 3 ) % 0xD0] ==  65 ) {
                v9 [ ( i + 4 ) % 0xD0] =  83;
            }
        }

        for ( i = 0; i <= 0xCF; ++i ) {

            v9[i] = 67;

            if ( v9[ ( i + 3 ) % 0xD0] ==  65 ) {
                v9[ ( i + 4 ) % 0xD0] = 83;
            }

            if ( ( v9 ) [ ( i + 3 ) % 0xD0] ==  66 ) {
                v9[ ( i + 4 ) % 0xD0] = 83;
            }
        }

     

        flag1 = 0;
        flag2 = 0;
        flag3 = 0;
        flag4 = 0;

     

    // working backwards from below we get

    v10[0] = 17;
    v10[1] = 96;
    v10[2] = 50;
    v10[3] = 88;
    v10[4] = 97;
    v10[5] = 101;
            v10[6] = 81;
            v10[7] = 34;
            v10[8] = 102;
            v10[9] = 98;
            v10[10] = 107;
            v10[11] = 94;
            v10[12] = 75;
            v10[13] = 69;
            v10[14] = 110;
            v10[15] = 85;
     

     

    for ( i = 0; i <= 3; ++i ) {
        flag1 |= ( unsigned char ) v10[i] << 8 * i;
    }

    for ( i = 0; i <= 3; ++i ) {
        flag2 |= ( unsigned char ) v10[i + 4] << 8 * i;
    }

    for ( i = 0; i <= 3; ++i ) {
        flag3 |= ( unsigned char ) v10[i + 8] << 8 * i;
    }

    for ( i = 0; i <= 3; ++i ) {
        flag4 |= ( unsigned char ) v10[i + 12] << 8 * i;

    }

    //printf ( "%x %x %x %x %x\n", some_counter, flag1, flag2, flag3, flag4 );

    if ( flag1 != 0x58326011 || flag2 != 0x22516561 || flag3 != 0x5E6B6266 || flag4 != 0x556E454B ) {
        puts ( "Flag wrong!" );

    }

    else {
        puts ( "Flag correct!" );
    }

    return 0;

     

    }

     

    the thing that bothered me about my C version vs the elf binary was the speed difference, mine ran much faster for no apparent reason, so I looked harder at the initial ptrace test but even though it was forking I saw no way that it could be hooking and repeating itself, noping out the sleep code didn’t alter the speed.

     

    stracing showed that it was forking and sleeping again. so single stepping I saw that some of the libc’s were indeed going to different places. looking at the plt

     

    — SIGCHLD (Child exited) @ 0 (0) —
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({1, 0}, 0xffeca9f8)           = 0
    clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0) = 18192
    wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 18192
    — SIGCHLD (Child exited) @ 0 (0) —
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({1, 0}, 0xffeca9f8)           = 0
    fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), …}) = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7792000
    write(1, "Calculating phase 1 …\n", 24Calculating phase 1 …
    ) = 24
    clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0) = 18193
    wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 18193
    — SIGCHLD (Child exited) @ 0 (0) —
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({1, 0}, ^C <unfinished …>

     

     

    .got.plt:0804A150 18 89 7B F7                   off_804A150 dd offset dword_F77B8918    ; DATA XREF: .got.plt:0804A154                               ; int (*off_804A154)(void)
    .got.plt:0804A154 A0 B6 7A F7                   off_804A154 dd offset unk_F77AB6A0      ; DATA XREF: .got.plt:0804A158 AF 91 04 08                   ptr_to_printf dd offset another_ptrace_counter_increment_0
    .got.plt:0804A15C 76 84 04 08                   ptr_sleep dd offset loc_8048476         ; DATA XREF: _sleepr
    .got.plt:0804A160 86 84 04 08                   ptr_wait dd offset word_8048486         ; DATA XREF: _waitr
    .got.plt:0804A164 96 84 04 08                   ptr_getenv dd offset word_8048496       ; DATA XREF: _getenvr
    .got.plt:0804A168 D1 92 04 08                   ptr_malloc dd offset another_ptrace_counter_increment_1
    .got.plt:0804A168                                                                       ; DATA XREF: _mallocr
    .got.plt:0804A168                                                                       ; setup_hooks+1Aw
    .got.plt:0804A16C 60 90 04 08                   p_io_puts dd offset another_ptrace_counter_increment
    .got.plt:0804A16C                                                                       ; DATA XREF: _putsr
    .got.plt:0804A16C                                                                       ; setup_hooks+6w
    .got.plt:0804A170                               ; int (*off_804A170)(void)
    .got.plt:0804A170 C6 84 04 08                   off_804A170 dd offset word_80484C6      ; DATA XREF: ___gmon_start__r
    .got.plt:0804A174 D6 84 04 08                   off_804A174 dd offset word_80484D6      ; DATA XREF: _exitr
    .got.plt:0804A178 5F 94 04 08                   check_cc_buffer dd offset another_ptrace_counter_increment_2
    .got.plt:0804A178                                                                       ; DATA XREF: _strlenr
    .got.plt:0804A178                                                                       ; setup_hooks+24w
    .got.plt:0804A17C E0 53 5F F7                   ptr_libc_main dd offset __libc_start_main
    .got.plt:0804A17C                                                                       ; DATA XREF: ___libc_start_mainr
    .got.plt:0804A180 06 85 04 08                   ptr_libc_fork dd offset word_8048506    ; DATA XREF: _forkr
    .got.plt:0804A184 16 85 04 08                   off_804A184 dd offset word_8048516      ; DATA XREF: _getppidr
    .got.plt:0804A188                               ; int (*ptr_ptrace)(void)
    .got.plt:0804A188 26 85 04 08                   ptr_ptrace dd offset word_8048526       ; DATA XREF: _ptracer

     

    reverse_me:080491AF                               ; —————————————————————————
    reverse_me:080491AF
    reverse_me:080491AF                               loc_80491AF:
    reverse_me:080491AF 50                            push    eax
    reverse_me:080491B0 51                            push    ecx
    reverse_me:080491B1 E8 55 02 00 00                call    near ptr unk_804940B
    reverse_me:080491B6 B8 F4 00 00 00                mov     eax, 0F4h
    reverse_me:080491BB
    reverse_me:080491BB                               loc_80491BB:                            ; CODE XREF: reverse_me:080491DBj
    reverse_me:080491BB 8A 88 60 90 04 08             mov     cl, byte ptr loc_8049060[eax]
    reverse_me:080491C1 80 F9 CC                      cmp     cl, 0CCh
    reverse_me:080491C4 75 0F                         jnz     short loc_80491D5
    reverse_me:080491C6 50                            push    eax
    reverse_me:080491C7 A1 94 A1 04 08                mov     eax, ds:just0x //the increment of the counter
    reverse_me:080491CC 83 C0 01                      add     eax, 1
    reverse_me:080491CF A3 94 A1 04 08                mov     ds:just0x, eax
    reverse_me:080491D4 58                            pop     eax
    reverse_me:080491D5
    reverse_me:080491D5                               loc_80491D5:                            ; CODE XREF: reverse_me:080491C4j
    reverse_me:080491D5 83 F8 00                      cmp     eax, 0
    reverse_me:080491D8 74 03                         jz      short loc_80491DD
    reverse_me:080491DA 48                            dec     eax
    reverse_me:080491DB EB DE                         jmp     short loc_80491BB
    reverse_me:080491DD                               ; —————————————————————————
    reverse_me:080491DD
    reverse_me:080491DD                               loc_80491DD:                            ; CODE XREF: reverse_me:080491D8j
    reverse_me:080491DD B8 F0 00 00 00                mov     eax, 0F0h
    reverse_me:080491E2
    reverse_me:080491E2                               loc_80491E2:                            ; CODE XREF: reverse_me:08049202j
    reverse_me:080491E2 8A 88 5F 94 04 08             mov     cl, byte_804945F[eax]
    reverse_me:080491E8 80 F9 CC                      cmp     cl, 0CCh
    reverse_me:080491EB 75 0F                         jnz     short loc_80491FC
    reverse_me:080491ED 50                            push    eax
    reverse_me:080491EE A1 94 A1 04 08                mov     eax, ds:just0x // the increment again
    reverse_me:080491F3 83 C0 01                      add     eax, 1
    reverse_me:080491F6 A3 94 A1 04 08                mov     ds:just0x, eax
    reverse_me:080491FB 58                            pop     eax
    reverse_me:080491FC
    reverse_me:080491FC                               loc_80491FC:                            ; CODE XREF: reverse_me:080491EBj
    reverse_me:080491FC 83 F8 00                      cmp     eax, 0
    reverse_me:080491FF 74 03                         jz      short loc_8049204
    reverse_me:08049201 48                            dec     eax
    reverse_me:08049202 EB DE                         jmp     short loc_80491E2
    reverse_me:08049204                               ; —————————————————————————
    reverse_me:08049204
    reverse_me:08049204                               loc_8049204:                            ; CODE XREF: reverse_me:080491FFj
    reverse_me:08049204 59                            pop     ecx
    reverse_me:08049205 58                            pop     eax
    reverse_me:08049206 E9 5B F2 FF FF                jmp     near ptr word_8048466

     

    similar code again, and there were a couple of others.

     

    so from here we knew that it was incrementing the value during the run.

    jk wrote a python bruter based on the c code and we had been trying different values with the counter.

    He got “4v0iDsS3CtIOnSLd” the password was “Ld4v0iDsS3CtIOnS” I’d even rotated it since phase one did that, but the change of case on the Ld threw a spanner in that. unfortunately for us that was about 1400 seconds before the end of the CTF when we really started focusing on the change of value.

    http://charliex.pastebay.com/1332967

     

    Ahh well..

     
  • charliex 3:50 am on December 24, 2012 Permalink | Reply
    Tags: changing mac address, proxim   

    Changing the Mac address of a proxim 802.11abgn usb adapter 

    Windows 7 has a a limitation (That can be removed in the code of the individual driver) that you can’t set a fake mac id starting with 00 on a wireless usb So I did what any normal person would do, pulled apart the adapter, removed the eeprom found and edited the hardware MAC ID

     

    Use a spudger to open the case, its not glued or anything.

    I tried a few ways of programming the spi on board, but it just wouldn’t do it,, too much interference.

     

    The chip is glued down, some acetone will take care of that, desolder the chip and pop it into a eeprom reader its an ATMEGA AT61 series SPI EEPROM so easy enough. Once you have the hex file, grep for the mac address in hex. Edit it to what you want it to be and reflash it back to the chip, there is no checksum etc.

    You can send eeprom commands back to the chip via the driver, but I didn’t look into it too deeply;. Its pretty quick to remove it and change it, obviously this is more useful for cloning vs just changing.

     

    The Proxim / Orinoco is just a Taiwanese usb adapter, I haven’t seen it for sale under the different brands though, but its considerably cheaper.

     

    I’ll add some pictures to the post later.

     
    • Andrew Bailey 4:23 am on December 24, 2012 Permalink | Reply

      Dumb question, why did you want a 00 MAC address?

      • charliex 4:33 am on December 24, 2012 Permalink | Reply

        Its the first part, so 00-90-a9-a2-1a-33 isn’t cloneable it has to be 02-90-a9-a2-1a-33 06-90-a9-a2-1a-33 etc. Useful for when an ISP uses your MAC to identify your NIC for home internet connections etc.

        • timd8137 12:58 am on September 15, 2013 Permalink

          Charliex Please whenever you find time send pictures regarding this, and any EEprom programmer you can recommend?

          desolder the chip and pop it into a eeprom reader its an ATMEGA AT61 series SPI EEPROM so easy enough

          Thanks
          Tim

        • Tim 6:49 pm on September 27, 2013 Permalink

          Charlie , I am looking all over Amtel website to get some free samples but when I google ATML H118 64DM Y is says ST micro is the manufacturer? maybe your proxim had a different chip on it?

    • Freddy 6:06 am on June 29, 2013 Permalink | Reply

      How hard would this be to do for someone with no experience?? I have the exact same Proxim USB which i need to clone the MAC address.. Any change you could provide a detailed howto or even youtube video ?

      Thanks
      freddy

      • charliex 10:02 pm on June 30, 2013 Permalink | Reply

        open it, remove the flash chip (small soic 8 pin surface mount )with acetone and a soldering iron carefully use low melt (chip quik at frys) , clone it with programmer than can handle spi based flash memory. solder it back, close up case

    • Tim 1:52 pm on September 13, 2013 Permalink | Reply

      Charliex any reccomendations on a good programmer?
      Thanks

    • Tim 5:18 pm on September 15, 2013 Permalink | Reply

      Charlie, another question, where do you suggest getting blank SPI Eeproms from, so I could burn some clones? or I could just go in and modify the existing one to the MAC I want right?

      • charliex 6:06 pm on September 15, 2013 Permalink | Reply

        it has like 1000 write lifetime so plenty, digikey, mouser, rs components, element14, farnell all should have it. http://www.findchips.com and punch in the parttno for finding it it easily

    • Melkske 11:53 am on June 29, 2015 Permalink | Reply

      Hello charliex, is the eeprom a 24c04? Or which type is it? (is it the U2 on the mainboard?)

    • charliex 3:03 pm on June 29, 2015 Permalink | Reply

    • Cris 5:12 pm on September 9, 2015 Permalink | Reply

      I tried to read it as a ATMEL SPI FLASH device and I failed. It looks that it is an I2C device (ATMEL 24CXX). Could you, please, confirm? What is the file size of EEPROM? I saw that after 8192 bytes data repeats.

      • charliex 5:26 pm on September 9, 2015 Permalink | Reply

        i’ll try to dig up the adapter and take a look

        • Melkske 7:08 am on September 10, 2015 Permalink

          Hello Cris,

          I can confirm you, it’s a 24c64 eeprom.
          Done the test to read it and modify the mac, all works.
          If you are not able to change it, send me the stick.

    • Cris 5:13 am on September 17, 2015 Permalink | Reply

      Thank you, Melkske. I managed to change it. All works. I used a Raspberry PI as reader/writer (inexpensive and supports I2C).

      • Melkske 5:37 am on September 17, 2015 Permalink | Reply

        Hello Cris,

        Can you email me the raspberry pi reader/writer (software) or the procedure?
        aerohive24@gmail.com

        Regards

    • Nicolas 12:06 am on October 31, 2017 Permalink | Reply

      Can you change the mac address of the device?

  • charliex 7:02 pm on October 2, 2012 Permalink | Reply
    Tags: ARM, freescale, JL25Z, kinetis   

    Kinetis KL25Z Freescale freedom platform 

     

    Today my KL25Z dev board arrived from Newark,  I had it on pre-order as soon as i saw it, mainly because its cheap at $12.95 +tax and because its ARM M0+ that can go upto 48Mhz.

    Comes in a nice box you solder the headers in if you want too, otherwise you get nothing with it ( but that’s not a bad thing ) the box has a colour print diagram of the connections to the board on the underside and its a nicely packaged.

     

    Oddly, or not, the first thing I noticed was an unpopulated spot for an IC U5, a quick scan of the schematics and its for an AT45DB161D which is a 5V tolerant 3.3V SPI flash memory chip. Which is great because I just happen to have a stack of 16’, 32’s and 64’s at NullSpace. I’ll update the blog when I add it and see if it works, it is a fairly costly IC so that might be why its not included versus a build mistake.

    Underneath there is a space for a CR2032 PTH battery holder.

    It is a very nicely laid out board, going for the black mask with gold finish. Though placement of the RST button could be better, the placement of the pads underneath mean when you press the reset the board flips up, less so when the USB cables are plugged in, a minor annoyance. The captouch could also have done with something underneath as well, its just slightly off balance, again very minor and easily fixed.

     

    specs are :-

    • MKL25Z128VLK4 MCU – 48 MHz, 128 KB flash, 16 KB SRAM, USB OTG (FS), 80LQFP
    • Capacitive touch “slider,” MMA8451Q accelerometer, tri-colour LED
    • Easy access to MCU I/O
    • Sophisticated OpenSDA debug interface
    • Mass storage device flash programming interface (default) – no tool installation required to evaluate demo apps
    • P&E Multilink interface provides run-control debugging and compatibility with IDE tools
    • Open-source data logging application provides an example for customer, partner and enthusiast development on the OpenSDA circuit

     

    I dunno how I feel about the P&E stuff, Freescale must own stock in them or something, I have a bunch of P&E BDM’s, cyclones, cpu32/cpu16 etc. which i use for my reverse engineering work but they’re expensive and the software is about 1990’s level of basic, everything is an add on cost, the flash tool is one cost, programmer/debugger, capacity on the cyclone max etc. Also they don’t have a lot of protection, I’ve blown up my cyclone max with a bad PSU,  for such an expensive tool its poorly protected.

    Talking about questionable software, my old friend CodeWarrior rears its head again, anyone who has been in game development for a long time, especially console, probably has a special place for CodeWarrior, along with the Sony CD burners for PSONE. Freescale/Motorola bought them out a long time ago and so of course it keeps coming back to haunt me, and haunt me it does. Still I’m sure its gotten better?… I’m not sure why TI/Freescale etc wants to roll their own dev tools, maybe for QC or lock–in but GCC is OK enough to use it and ARM were smart and paid someone to make the ARM support better in GCC. Beyond that CodeSourcery seem to do a good job of keeping it all together. Maybe I do want to download another 1.5G Eclipse installer (not CW thankfully). I think its a mistake going down this route, but there you go.

    Link to CodeWarrior

    Keil  (needs a patch )and IAR have tools as well,

    Anyway enough of that, at least they’re trying and giving away what they can, my beefs lie with them mostly on the commercial side of things anyway.

    This is what the OpenSDA firmware zip file contains

    10/02/2012  11:48          79,583  DEBUG-APP_Pemicro_v102.SDA
    10/02/2012  11:48         213,461  MSD-FRDM-KL05Z_Pemicro_v105.SDA
    10/02/2012  11:48         213,461  MSD-FRDM-KL25Z_Pemicro_v105.SDA
    10/02/2012  11:48         213,501  MSD-XTWR-KL25Z48M_Pemicro_v105.SDA
    10/02/2012  11:47             177  Readme.txt
    10/02/2012  11:47         287,369  Updating the OpenSDA Firmware.pdf

    So no tools host side needed as such, just firmware for the connection to CW/PE multilinks.

    It supports ETM and SWD, OpenSDA this doc goes over how to setup and upload files.

    P&E’s tools are http://www.pemicro.com/opensda/pe_tools.cfm

    Its an CDC driver that should auto install on Windows, there are drivers for it available if not. After that its drag and drop binary or Motorola S record files, they note that the dev tools work primarily on Windows but CDC obviously works on the other platforms.

     

    < to be continued >

     
    • Erich Styger 5:55 pm on October 14, 2012 Permalink | Reply

      Yes. This board is a really nice and versatile one! I already have published several articles around the Freedom KL25Z board on my blog, along with tutorials and software projects:
      http://mcuoneclipse.wordpress.com/tag/kl25z-freedom-board/

    • Chris Smith 2:21 pm on November 2, 2012 Permalink | Reply

      >I’ll update the blog when I add it and see if it works, it is a fairly costly IC so that might be why its not >included versus a build mistake

      Did you find out if the 16GB chip at U5 was functional. DigiKey has the AT45DB161E-SSHD-B-ND for $2.26 – pretty cheap IMO.

  • charliex 6:25 pm on June 21, 2012 Permalink | Reply
    Tags: soldercore   

    Soldercore quick intro 

     

    I ordered a SolderCore from Mouser yesterday, it arrived this morning. Its a pretty nice little device. Oddly I’d had some interaction with one of the creators at Rowley Associates, Paul,  on an email list talking about C compilers/assembler etc and it turns out we’re both Lotus people as well as having ECU related experiences, it is a small world.

    Haven’t done much with yet, since i’m not at NullSpace and all my stuff is there.

    From http://www.soldercore.com

    • Arduino Form Factor
    • Based upon a 80 MHz Cortex-M3
    • 512KB Flash, 96KB contiguous RAM
    • Built in Ethernet support with an on-board RJ45 connector.
    • USB OTG support with an on board microAB connector.
    • On board microSD holder.
    • Support for additional Flash and FRAM devices.
    • CAN, I2S, 2xI2C, UART, PWM, ADC, SPI and QEI supported
    • On board standard 10 way SWD JTAG header. (Only fitted to the Commando variant)
    • Power can be supplied via USB or the barrel jack (6V – 9V DC).

    So decent enough specs.

    Nice things are , no drivers, so no one whining about CDC driver support in Windows 7.  All the help, examples firmware are net enabled. All you need is telnet to edit.

     

     

    compared to a Pandaboard ES, and one of our NSL ADK boards. Soldercore in the middle.

    If you want the headers, you can solder them on. I like the idea Sparkfun has with single row headers and to offset every other one so its easier to solder, but these aren’t difficult, just hold and tack the first one very lightly with solder, make sure its straight and do the others. That first tack is important so its aligned and then you can re-align easily.

    I plugged it in, pinged as per the label on the back, then i use Putty to login  in to it. (change from ssh to telnet) also it uses ^H for backspace so edit that too. I then posted to the forums with a hello world, but then realised it was a program that did it. So i went off to find it, since it isn’t in the examples but in the help section instead.

    Had to edit a bit first. Mine didn’t like the command $NL so i used $LF instead. Then came along figuring out how to set NET.SMTPSERVER (which is fairly futile for me at this point so all my SMTP servers require a login) but trying anyway. I of course battled ahead and did NET.SMTPSERVER = “smtp.mail.com” NET.SMTPSERVER = “10.0.0.1”  etc neither worked. It says ‘Digital I/O’ as the type.  My SMTP server will work even less with an IP address since it wants to use the FQDN to find it. But regardless..

    Luckily BASIC being immediate, i just did

    PRINT NET.SMTPSERVER

    Which yielded

    [0, 0, 0, 0]

    Aha!, So

    NET.SMTPSERVER = [192,168,1,1]

    Easy enough. But i don’t have an open relay… So i got as far as ?SMTP server down in 60:

    10 SUBJECT = "Hello from SolderCore!"
    20 FROM = NET.NAME + “charlie@xxx.com”  ‘ fill in your own e-mail address
    30 TOO = "soldercore@googlegroups.com"
    40 BODY = "Hello from " + NET.NAME + "." + $LF
    50 BODY = BODY + "My device address is 192.168.1.159 ." + $LF
    60 MAIL TOO, FROM, SUBJECT, BODY
    70 PRINT "e-mail away!"
    80 END

    It didn’t like the IP$(NET.ADDR) either so i replaced it with text.

    The original looks like http://soldercore.com/manual/corebasic_mail.htm

    > list ↵ 10 SUBJECT = "Hello from SolderCore!" 20 FROM = NET.NAME + "@local" ' fill in your own e-mail address 30 TOO = "soldercore@googlegroups.com" 40 BODY = "Hello from " + NET.NAME + "." + $NL 50 BODY = BODY + "My device address is " + IP$(NET.IPADDR) + "." + $NL 60 MAIL TOO, FROM, SUBJECT, BODY 70 PRINT "e-mail away!" 80 END > run ↵ e-mail away! >

     

    I concluded the problems/missing command might be an old firmware so I tried to do a firmware update with firmware run, but i realised it needed a FAT16 SD card (a good one not a cheap fakey one) All i had was  a 16B MicroSD so its too big, but normally you’d do. 2G is what you need.

    FORMAT n: /FS:FAT

    where n: is the drive letter. After a year or two , it’ll be formatted

    You should see something like :-

    “Insert new disk for drive J:

    and press ENTER when ready…

    The type of the file system is FAT32.

    The new file system is FAT.

    Verifying 15267M (this is a problem)

    You can also use this https://www.sdcard.org/downloads/formatter_3/ Which supposedly does a better job of the FAT format. I can’t try it at the moment, since format is still running.

    The soldercore.com website does go into this in detail, if the GUI doesn’t show FAT as an option, the card is too big..

    It has a few nice features, being able to update firmware over the internet is great, and you can type

    example

    and it’ll list all the examples available, over the net. Typing

    example “welcome”

    will load the welcome.bas, so that is pretty neat, most of the examples look like they need one of the add on boards though. It is case sensitive on the example filenames.

    Typing

    firmware

    Seems equivalent to firmware catalog and it stops me typing catalogue(j/k)

    A lot of people might gripe about BASIC but what BASIC looks like versus what goes on in the background are completely different things, look at BlitzBasic etc, they’re very quick. Having to do line numbers is a bit of a throwback for sure.

    Here are a lot of builtin commands that do useful math, dot products, matrices etc. sin/cos, etc. Very useful. At worst case you can pop on a JTAG and write everything in C/ASM to your hearts content.

    I have had one  reset so far, but its probably the usb port i have isn’t giving me enough juice, it has external power port too.  If i find a small enough SD card, i can try to update the firmware. My firmware is also at 0.9.5 which is older than they list at the website, so I’m sure some of the stuff has been fixed already. I’ll update the blog when i find an SD card.

    I did all this with it so far, and no drivers installed and no software installed i can run it from android or nokia phone as long as it has telnet.

    The usual BASIC commands like EDIT, RENUMBER work, its just like being back on the BBC or Archimedes.

    Haven’t done much else with it yet, but I’m really interested in at as we use the Stellaris chips for other projects. I also really like Paul from the small interactions I’ve had with them, and Rowley Associates , I don’t know Iain or K&I but they did a nice layout job, so I’m looking forward to where they go with it. Anyone who’s a Lotus nut is ok with me !

     

    Update

    I realised my Skyrocket had a 2GB card in it, so I swapped that out, formatted it as FAT16 and made the top level SYS folder, plugged it into the soldercore and typed

    firmware run

    After a few seconds its at 0.9.12 now. I retried the original syntax of the Mail  example and it accepted it fine, i still can’t relay the email but it does accept the commands that were missing. Easiest firmware update ever.

    Quickly, an open relay! To hMailServer !

     
  • charliex 8:28 pm on June 18, 2012 Permalink | Reply  

    NSL gets a laser cutter 

    We’ve been trying to sort out a laser cutter for a while now, last year we bought a 40W tube and a PSU and burned holes into things. While hugely entertaining, it lacked some precision

     

    That was as far as it went, so i had enough and just decided to order one, after a few stops and starts we bought an LC900N directly from wklaser in China, they’re the same ones FS laser and hurricane etc sell but they do some mods to the software/boards, but nothing that is worth the price increase that i can see. It cost us under $4,000 USD for a 90W laser with a 600x900mm cutting area, with a motorised Z table from china to us.

    We’re on the third floor and our elevator is ( A ) too small ( B ) out of order, so we had to levitate it in.. Having hindsight we could have taken it apart, but had  been previously assured we couldn’t do that. Anyway…..

    We did what anyone would do and removed the window, hired a crane and lifted it in through the window.

     

    Taking it out to inspect the contents etc.

     

    Building a landing table

    Scientific weight test, the window is gone and 3rd floor, proceed to start jumping. Some people were confused about what don’t go past the blue line meant.

    Yes this is a good idea.

     

    Test lift

    We put out cones, people removed them and parked anyway, I told a few people they may not want to park there, most people got annoyed and asked why not? So i explained, most of them changed location. But only most….

     

    ok we’ve gotten it to here, what now ? Time for a meeting.

    The tricky part, removing the first strap, everyone pitched in to help.

    The Rapunzel method started off well but we discovered a problem in the hair length dept.

     

    And its in!

    Books are useful. Dropping it the last meter was harder than getting it from China.

     

     

     

    Put the window back, respackle it and no one is the wiser.

     

    Ok so the laser is purchased, shipped from china, craned in through a window. What next?

     

     

    First install the tube

     

    The laser needs a vent, preferably hilarious. (needs video)

    Obviously take it apart and improve it.

    The height setting tool needed improvement, so made that and labelled it as such

    Test cuts. Lots of test cuts.

    Align mirrors, the manual came in useful here. (bits of paper)

    Now change mind, remove the mirrors, turns out they’re dirty! So we swapped them out for ii-iv’s, increasing the power 20%. Interestingly there is a technique to make CO2 laser mirrors from hard drive platters hard disk platter co2 laser mirror

    Frostbite hand.

    The new lens vs old one.

    Ply Wood 5.2mm 8speed 90power OLD LENS
    ply wood 5.2mm 15speed 40power NEW LENS

     

    The T4 28” fluorescent bulb burned out after a day or so so we replaced it with some halogens, especially since they’re hard to source locally, test fitted with duct tape. The whole machine gets covered in grease for shipping so lots of cleaning first.

    Next well they said we can’t cut steel. So quick trip to home depot for some O2.

    The glo-stick is vital

    well it is cut but…. not really useable, however great progress

     

    vertical video!

    Here we cut some 1” acrylic

     

    Next!

    The GUI is full of odd chinese-english conversions, but they use a UTF8 .ini file so you can edit it all you want. Instead of Datum, it is now Home . Just edit language.ini or use Resource Editor on the .exe for permanent changes the software does nt self check, even though it uses a senselock dongle.

     

    Next is hack the software, we figured out the control software, reversed most of the API it actually rasterises vectors in the PC side and sends them over as points!! I’m shocked and amazed since the machine has a ‘DSP’ based controller board. It generates a TXT file and compiles it ,then uploads it the API has move, p-move, arc and circle functions but the software never uses them..

    e.g.

    CLASS_DECLSPEC int APICALL M05_m_fast_line2(int chx,long disx,int chy,long disy);
    CLASS_DECLSPEC int APICALL M05_m_set_vector_profile(double ls,double hs,double ac);
    CLASS_DECLSPEC int APICALL M05_m_set_vector_profile2(double start_ls, double hs, double end_ls, double ac, double dc);
    CLASS_DECLSPEC int APICALL M05_m_curve_vertex();
    CLASS_DECLSPEC int APICALL M05_m_curve_begin();
    CLASS_DECLSPEC int APICALL M05_m_curve_end();
    CLASS_DECLSPEC int APICALL M05_m_set_period(double period);
    CLASS_DECLSPEC int APICALL M05_m_set_power(int LowPower,int HighPower);
    CLASS_DECLSPEC int APICALL M05_m_set_laser_mode(int mode);

    The are all set ramp speeds, laser on, move here, move here move here, move here. Not set point, radius calculate in controller.

     

    Oddly the first command we figured out (unintentionally) was fire the laser at full power indefinitely.

    Is it off, no, is it off now , no ? how about now ,, no ? OK what’s the tube temp? still not off ? Easy to fix though, its just a toggle on/off. You also can’t easily jog the laser head around with the laser on, it’ll work but you can’t turn it off easily!

     

    Knock out a quick GUI in Visual Studio.

    I updated the header file for the DLL on our SVN. I’ll document it as i go along

    http://www.032.la/svn/listing.php?repname=032&path=/NSL_LaserGUI/Controller/&#a80090f0f13e60006321d63b48b8768ea

    Example of the txt file, which it compiles on the PC side.

    SUB001
    CMD101,0
    SET002,20000
    SET014,1,0,2,2
    CMD109,1
    CMD102,416,20833,97222
    CMD104,6944
    CMD401,416,880,41666,900
    CMD402,900
    CMD409,416,880,3000,41666,900,5000 //set  ramp speeds and power
    CMD408,900,5000
    CMD050,2,1
    CMD002,63556,42631
    CMD050,1,1 //laser on
    CMD103,416,41666,69444
    CMD001,63556,42631 // move
    CMD050,1,0 //laser off
    i’m surprised it rasterises the vectors though, i was expecting to see a command for a circle that defined a center,radius etc.
    speed change. 100 to 300
    CMD401,416,880,13888,900
    CMD409,416,880,3000,13888,900,5000
    CMD103,416,13888,69444
    CMD401,416,880,41666,900
    CMD409,416,880,3000,41666,900,5000
    CMD103,416,41666,69444
    power change 9 to 99 ( *100)
    CMD401,416,880,13888,900
    CMD402,900
    CMD409,416,880,3000,13888,900,5000
    CMD408,900,5000
    CMD401,416,880,13888,9900
    CMD402,9900
    CMD409,416,880,3000,13888,9900,5000
    CMD408,9900,5000
    horizontal line moved in y
    CMD002,62466,45271
    CMD001,62466,45271
    CMD001,69328,45271
    CMD002,69328,45271
    SUB603,416,20833,97222,69328,45271
    CMD001,69328,45271
    CMD001,69328,45271
    CMD001,62466,45271
    CMD001,62466,45271
    CMD001,69328,45271
    CMD001,69605,44993
    CMD001,69605,45548
    CMD001,62188,45548
    CMD001,62188,44993
    CMD001,69605,44993
    CMD002,62466,45175
    CMD001,62466,45175
    CMD001,69328,45175
    CMD002,69328,45175
    SUB603,416,20833,97222,69328,45175
    CMD001,69328,45175
    CMD001,69328,45175
    CMD001,62466,45175
    CMD001,62466,45175
    CMD001,69328,45175
    CMD001,69605,44898
    CMD001,69605,45453
    CMD001,62188,45453
    CMD001,62188,44898
    CMD001,69605,44898
    horizontal line moved in x
    CMD001,69328,45271
    CMD002,69328,45271
    SUB603,416,20833,97222,69328,45271
    CMD001,69328,45271
    CMD001,69328,45271
    CMD001,69328,45271
    CMD001,69605,44993
    CMD001,69605,45548
    CMD001,69605,44993
    CMD001,69492,45271
    CMD002,69492,45271
    SUB603,416,20833,97222,69492,45271
    CMD001,69492,45271
    CMD001,69492,45271
    CMD001,69492,45271
    CMD001,69770,44993
    CMD001,69770,45548
    CMD001,69770,44993


    Lots of boxes were cut

    boxmaker scripty thing

    Found a nice dragon box on thingverse

    bVector made  a nice mod to that case.

    So this ends our first week with the cutter, we have to decide if we’re replacing the controller, Leetro apparently want us to buy $25,000 of stuff to get the SDK documentation, but we’re so far into reversing it, that won’t matter. The controller might be ok. It has some strangeness we want it to speak GCODE so maybe another GRBL based controller like we did for Pickobear.

    We’re also building a new frame for it, and updating it to 170W laser tube (maybe)

    more to come….

     
    • Sascha 9:00 am on June 20, 2012 Permalink | Reply

      Great post & congrats on getting yourselves a laser!!! I bought a laser 18 months ago from WKLaser and had a wonderful experience dealing with them. Oh, and I had to remove a window to get my laser inside, too 🙂

    • Hugo 12:56 pm on June 20, 2012 Permalink | Reply

      I’m curious (and surprised about your modest successes cutting steel. I had heard that (due to the reflectivity of the molten steel?) attempting it can damage the laser or the head/nozel/lens and that metal-cutting CO2 lasers have an additional polarizing filter system to control the reflections. Have you noticed any problems since cutting steel? Or any plans to improve the cuts in the future?

      • charliex 4:38 pm on June 20, 2012 Permalink | Reply

        The mirrors and the last part of the laser ( the focus stage ) are consumable basically, the very last stage will get eaten simply away with the heat etc, so we are looking to change them out to a copper ends and increasing the lasers power. We will probably only keep cutting the same thin steel but its useful for us. The 170W tube should get us where we want to be.

    • Dan 1:26 pm on June 20, 2012 Permalink | Reply

      Awesome! Keep us up to date on the controller reversing – I’ve got one with an MPC6535 controller and the lasercut software can be quite limiting and badly behaved on occasion.

    • oktane 12:48 am on June 21, 2012 Permalink | Reply

      Congrats guys, wish I was still in LA to see this and help reverse that controller app, sounds like fun. I volunteer for the first NSL laser tattoo, just spray my arm with flat black spraypaint. (semi-serious here) Remind that distinguished rapist enzo to send me the nsl stickers! (he promised)

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel