logo elektroda
logo elektroda
X
logo elektroda

Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows

p.kaczmarek2 186 1
ADVERTISEMENT
Treść została przetłumaczona polish » english Zobacz oryginalną wersję tematu
📢 Listen (AI):
  • Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    How do you recognise the architecture of an unknown microcontroller from a Flash memory dump? What microprocessor is an old HP printer based on? Will it be possible to revive the memory dump so that it displays something on the UART with proper emulation of its registers on a Windows PC? That's what I'll try to test here. The neighbours threw out the old printer and I took it in for analysis.
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    This printer has essentially just knocked 20 years off its age - it's a 2006 piece of hardware, a PSC 1400 series. The kit is pretty poor, as even the power supply didn't come with it, but maybe it's worth taking a look inside anyway?
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Look in vain for stepper motors inside, everything is based on DC motors and encoders, I presented this in detail the other day:
    Teardown of an HP Deskjet D1360 printer and an example of using its parts with an Arduino
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    The motherboard is small, in the corner of the printer:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Here another photo of one of the dials for the encoders (the carriage also has its own bar):
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Basically I recovered two boards - the one from the buttons, based on the sliding registers, with one display segment:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    And the motherboard:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    You can see the really big savings and design simplifications here. The motherboard has encoders on it, and is matched throughout the mechanical design.
    The date on the board indicates 2005.
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    The main MCU is an Agilent 2BB3-007 QGNQL - I can't find anything about it. The D56V62160 is a 2*512K*16 bit DRAM. The MX chip is probably a program flash memory.
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Flash was successfully soldered and ripped out using CH341:
    Flash memory connected but programmer does not see it? Solution to CH341 NeoProgrammer problem
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    I have placed a copy of the batch on my repository: https://github.com/openshwprojects/FlashDumps/commit/c183ec39e092d085d77f4b822aa392a94090270a
    I started with an analysis in a simple hex editor. You can see the ASCII captions.
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Among them, quite a lot of text related to initialisation and errors is striking, but the most important is probably the code name of the whole chip (SoC): "Eridani".
    
    0x000630	[BIST] START
    0x000640	[BIST] RESULT:
    0x00095C	[Boot] Bootstrapping:
    0x0009B8	[Boot] Decompressing:
    0x0009D0	[Boot] Jumping to:
    0x0009E8	[Boot] ROMOBJ found:
    0x000A1E	[Boot] Version:
    0x000A33	[Boot] Boot ID:
    0x000A48	[Boot] NVM Code: 0x
    0x000ABC	[Boot] ERIDANI ERROR DETECTED:
    0x000F68	[Boot] Header record data: '
    0x000F98	[Boot] Downloaded Program ID: '
    0x0011AC	[Boot] Bad Download type:
    0x0011D0	[Boot] Download address:
    0x0011EE	[Boot] Download length :
    0x001218	[Boot] Downloads complete
    0x001292	[Boot] To execute program you must type '
    0x0012C2	[Boot] Continuing to execute program
    0x0012EC	[Boot] Jumping to downloaded program
    0x001334	[Boot] Downloading s-records over USB
    0x001AA0	[Boot] Something very bad happened in USB code (1)
    0x001AF0	[Boot] USB disconnected
    0x001B10	[Boot] USB re-connected
    0x001B2C	[Boot] USB connected
    0x001B8C	[Boot] USB re-disconnected
    

    You can also hit the USB device descriptor:
    
    MFG:HEWLETT-PACKARD;MDL:OFFICEJET REFLASH;CLASS:PRINTER;DESCRIPTION:Hewlett-Packard OfficeJet Reflash;SERN:000000000011;
    

    A version of the ThreadX system is also thrown up, which suggests to us an ARM9 architecture:
    
    Copyright (c) 1996-2001 Express Logic Inc. * ThreadX ARM9/Green Hills Version G4.0.4.0 
    

    ARM9 - which means we have a clue! This makes it a lot easier, because there are different architectures and instruction sets, I don't need to check them here anymore, I know that for example it's not MIPS, etc.... now let's check the first bytes of the file:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    0xE5 0x9F 0xF2 0xB8 is repeated twice. This looks like an ARM interrupt table, LDR PC instruction, [PC, #0x2B8]. Jumps to the memory value from PC offset + 0x2B8.
    https://developer.arm.com/documentation/dui03.../ARM-and-Thumb-Instructions/LDR--PC-relative-
    https://www.macs.hw.ac.uk/~hwloidl/Courses/F2...0406C_C_arm_architecture_reference_manual.pdf
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    I also helped myself with this converter to instructions from ARM:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    The PC is already on next instruction, so at offset 8, after adding 0x2B8 we come out with offset 0x2C0, there is 0xE0001C.
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    The second instruction in that case is at offset 0x00E0001C. It looks like a flash memory mirror to me, because it's too big an index for a 1 MB program, so I looked at the file at offset 0x1C. There it is in turn E3 A0 D0 00:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Setting the stack pointer to 0 undoubtedly looks like something that might be done at the start of the program.... i think all went well, you can emulate.
    This is how I started to make a simple ARM emulator complete with condition and jump support. Each instruction changes the state of registers, memory and the program counter accordingly.
    Code: Python
    Log in, to see the code

    This is how one instruction is handled - it is broken down into bits, which then allow you to determine what to execute. Let's take the example of those LDRs mentioned:
    ARM documentation excerpt for LDR instruction with bitfield encoding diagram and bits 27–25 highlighted
    We detect bits 25-27.

    Code: Python
    Log in, to see the code

    I managed to do the first jumps and quickly discovered that either I was miscounting addresses or the flash memory has a mirror in the range 0x00E00000 - 0x00EFFFFF. For now, I took the liberty of defining the latter. This is what the first steps of the simulator looked like:
    
    [     0] 0x00000000: E59FF2B8
    [     1] 0x00E0001C: E3A0D000
    [     2] 0x00E00020: E3A010D3
    [     3] 0x00E00024: E12FF001
    [     4] 0x00E00028: EE110F10
    [     5] 0x00E0002C: E3A01080
    [     6] 0x00E00030: E1810000
    [     7] 0x00E00034: EE010F10
    

    Then I developed the memory accesses, i.e. write32 helper functions etc.
    Code: Python
    Log in, to see the code

    Code: Python
    Log in, to see the code

    Along the way I hit transition to Thumb mode . What is Thumb mode? Thumb are abbreviated 16-bit instructions designed to save and speed up Flash memory. They involve the same addresses, registers and memory, but are coded slightly differently. They do not support some of the operations, so they represent a compromise between code size and flexibility.
    A transition to Thumb is recognised by the fact that the jump target, the youngest PC bit, is lit.
    
      592 | 0x00E0026C | E59FD098 |  ARM | LDR SP, [PC, #0x98]
      593 | 0x00E00270 | E59FF090 |  ARM | LDR PC, [PC, #0x90]

    Instruction 593 loads offset 0x00E008A1 into the PC, and the value 0x00E008A1 has the youngest bit lit:
    Code: Python
    Log in, to see the code

    I include this later in the function that performs the simulator step:
    Code: Python
    Log in, to see the code

    Of course, there is a separate set of instructions for Thumb and this also needs to be implemented. Arithmetic operations, logical operations, memory accesses, jumps....
    Here one could stop here, but I wanted to add something else that could demonstrate that there is indeed something working in this emulation. I decided to find the UART register. I tried my luck and the force approach, that is, I simply plugged in the memory access and collected the performed operations on the individual registers, grouping them into pages. I hoped to execute enough code to catch something that looked like ASCII.
    The results (truncated):
    
    
    [inContentAd]
    
      +-- Page 0x20812300 -------------------------------------
      |  Total: 2 writes, 0 reads
      |  Registers touched:
      |    +0x00 (1W)
      |          written: 0xBCBC
      |    +0x02 (1W)
      |          written: 0xBCBC
      +--------------------------------------------------
    
      +-- Page 0x20812900 -------------------------------------
      |  Total: 11 writes, 122583 reads
      |  Registers touched:
      |    +0x00 (1W)
      |          written: 0x0000
      |    +0x02 (1W)
      |          written: 0x0000
      |    +0x04 (1W)
      |          written: 0xFFFF
      |    +0x06 (7W)
      |          written: 0x000A, 0x000D, 0x005B, 0x0042, 0x006F, 0x006F, 0x0074
      |    +0x08 (1W)
      |          written: 0x0000
      |    +0x0C (6R)
      |    +0x0E (122577R)
      |
      |  >>> ASCII from +0x06 lo_byte: "\n\n[Boot"
      +--------------------------------------------------
    
      +-- Page 0x20813500 -------------------------------------
      |  Total: 9 writes, 6 reads
      |  Registers touched:
      |    +0x00 (1W)
      |          written: 0x0036
      |    +0x02 (1W)
      |          written: 0x0030
      |    +0x04 (1W)
      |          written: 0x0031
      |    +0x06 (1W)
      |          written: 0x0258
      |    +0x08 (1W)
      |          written: 0x0003
      |    +0x0A (3W)
      |          written: 0x0000, 0x0008, 0x000A
      |    +0x0C (1R, 1W)
      |          written: 0x0001
      |    +0x0E (5R)
      +--------------------------------------------------
    
      +-- Page 0x20813A00 -------------------------------------
      |  Total: 156 writes, 0 reads
      |  Registers touched:
      |    +0x00 (152W)
      |          written: 0x7832, 0x7832, 0x7832, 0x7832, 0x7832, 0x7832, 0x7832, 0x7832, 0x7832, 0x7832 ... (152 total)
      |    +0x02 (1W)
      |          written: 0x0020
      |    +0x04 (1W)
      |          written: 0x0002
      |    +0x06 (1W)
      |          written: 0x08FF
      |    +0x08 (1W)
      |          written: 0x00CF
    

    Right away we have a candidate - 0x20812900+0x06, well you can see that the bytes stored there look like ASCII codes! Now this can be sorted out... this is how I added the UART peripheral. My script simply catches the writes to that offset and adds them to the UART buffer.
    Code: Python
    Log in, to see the code

    The result after firing:
    
    [UART] [Boot] Version:  04091301
    [UART] [Boot] Boot ID:  otto
    

    Screenshot of a hex editor showing ASCII text “[Boot] Version:” within memory data
    I couldn't believe it was working. I changed two letters in "otto" to "qq" in the source dump to verify this, but the program still correctly displayed what I expected:
    Inside the HP PSC1410 printer, analysis and emulation of firmware from inside on Windows
    Finally, the full log from the emulator:
    
    PS W:\GIT\EridaniEmulator> python .\emulator.py
    Loading firmware from readResult_GenericSPI_2026-28-2-14-13-42.bin...
      Loaded 8,388,608 bytes (8192 KB)
      Memory map:
        Flash:  0x00000000 - 0x007FFFFF (8 MB)
        Mirror: 0x00E00000 - 0x00EFFFFF (1 MB boot mirror)
        SRAM:   0xFC000000 - 0xFEFFFFFF (8+8+4 MB)
      Peripherals:
        CLK0:   0x20813500 (Clock/PLL controller 0)
        CLK1:   0x20813A00 (Clock/PLL controller 1)
        UART:   0x20812900 (Debug UART)
        GPIO:   0x20813200 (GPIO/misc)
    
    ============================================================
      HP OfficeJet 'Eridani' ARMv5 Emulator
      Starting execution at PC=0x00000000
    ============================================================
    
    [UART] [Boot] Version:  04091301
    [UART] [Boot] Boot ID:  otto
    [UART] [Boot] NVM Code: 0xFE
    [UART] [Boot] ERIDANI ERROR DETECTED: 7
    [UART] [BIST] START
    [UART] [BIST] RESULT: 0100
    [UART] [Boot] Bootstrapping: 0x00f0217c
    [UART] [Boot] ROMOBJ found:  0x00042d68 bytes
    [UART] [Boot] Copying:       0x00008944 bytes from 0x00f450c4 to 0xfd1ae5a4
    [UART] [Boot] Copying:       0x00000040 bytes from 0x00f4da18 to 0xfd2620a0
    [UART] [Boot] Copying:       0x00001408 bytes from 0x00f4da68 to 0xfd2620e0
    [UART] [Boot] Decompressing: 0x0007d288 bytes from 0x00f4ee80 to 0xfe0d8fc0
    [UART] [Boot] Decompressing: 0x00012bf8 bytes from 0x00fcc118 to 0xfe1850ec
    [UART] [Boot] Copying:       0x00000144 bytes from 0x00fded20 to 0xfe1ae420
    [UART] [Boot] Copying:       0x00000004 bytes from 0x00fdee74 to 0xfe1ae564
    [UART] [Boot] Jumping to:    0xfe15fed4
    
    [CPU] Max instructions (500000) reached
    
    ============================================================
      Emulation stopped after 500,000 instructions
      ARM: 594  Thumb: 499,406
      Elapsed time: 7.897s
      Speed: 63,315 instructions/sec
    

    You can see here the messages from the early bootloader, the chip and program identifications ("otto"?), the logs from copying the instructions to RAM and the jump to the actual program. It's hard to say if all the offsets are correct at all, because all it takes is one badly emulated register and everything in turn "falls over like dominoes", but still the result for me is satisfactory.

    In summary , despite initial problems with the flash , it managed to make a copy of the program memory from the printer and then identify it, as 32-bit ARM machine code. It was then possible to run a simple ARM emulator, which was then enhanced to support 16-bit Thumb instructions. Finally, it was possible to locate the UART registers, a force method was used to do this (I collected all write operations into one place and checked which bytes looked like text). The developed emulator seems to correctly simulate the messages sent which really do look like the device is booting.
    There were a few other problems along the way, but I didn't want to get so deep into the description already. Perhaps I will come back to it again.
    Of the tasks that remain to be done:
    - locate this UART on the PCB and verify that it also displays messages there
    - try to simulate most of the firmware
    - try to locate the GPIO registers
    - try to compile something of your own and upload it to this Flash
    Project repository: https://github.com/openshwprojects/EridaniEmulator
    Copy of the batch: https://github.com/openshwprojects/FlashDumps/commit/c183ec39e092d085d77f4b822aa392a94090270a

    Cool? Ranking DIY
    Helpful post? Buy me a coffee.
    About Author
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    p.kaczmarek2 wrote 14108 posts with rating 11960, helped 642 times. Been with us since 2014 year.
  • ADVERTISEMENT
  • #2 21853647
    gregor124
    Level 28  
    Hello, I think you have set the address wrong.
    The first byte 0xE5 indicates that this is an unconditional branch "b" instruction.
    The address in it is bits 23...0, which is 0x9FF2B8 and is a number with a sign that needs to be shifted 2 bits to the left. Bit 23 is copied to bits 31...26.
    After the transformation, I came up with 0xFE7FCAE0.
    I attach the code in Assembler that converts the address in the R1 register to the real address:
    mov r1,r1,lsl#8
    mov r1,r1,ASR#6

    Alternatively:
    LSL R1,R1,#8
    ASR R1,R1,#6
    Helpful post? Buy me a coffee.
📢 Listen (AI):
ADVERTISEMENT