ProjectSerotonin / Project-Serotonin

7 stars 0 forks source link

(bounty available) Firmware disassembly & patching #2

Open EIREXE opened 3 years ago

EIREXE commented 3 years ago

This is the first bounty for Project Serotonin, which includes a kindly donated initial bounty donated for by EIRTeam

What is Project Serotonin?

Project Serotonin is an effort to legally reverse engineer and patch the firmware on Fanatec steering wheel bases to remove anti-features/digital shackles and remove some limitations (under our own risk) that can be found in it.

Fanatec wheel bases disable force feedback when a non-official rim is connected, some may argue this is done in the name of safety, but it appears other manufacturers don't do this, so I decided to not buy this excuse.

Read more on the README

About EIRTeam

EIRTeam is a game development studio, founded by Álex Román Núñez after he left Hi-Speed Simulators, a racing simulation company. EIRTeam has a published game, Project Heartbeat, together with an in-development racing simulator and a new story-driven title.

EIREXE commented 3 years ago

I've pushed the initial bounty on bountysource: https://www.bountysource.com/issues/99003903-firmware-disassembly-patching-bounty

Goz3rr commented 3 years ago

I'm not sure if you had noticed already but it appears that even after decryption and removing the version "header" it's still not a completely valid Intel hex file. At one point in the file, you should be able to find the string :00000001FF. This marks the end of the file and there shouldn't be any data after it. Instead, there appears to be a few bytes of binary data followed by a partial line from earlier in the file. Then almost 1000 lines are repeated once again, followed by another truncated line at the end. I have no idea why this is but those few binary bytes might be of value?

Truncating the file after :00000001FF still doesn't help Ghidra make any sense of it. Converting it to binary also reveals not a single string to be found, while I was expecting at the very least some version strings or text relating to the DD2 display, or maybe even debug messages. Perhaps there's a separate file for data?

My experience is with AVR and ARM mainly so I'm still getting used to PIC. Using MPLAB X seems to disassemble the "fixed" hex file, and it seems to start off with a GOTO 0x200 and IVT as normal, but the instructions that follow really don't make much sense to me, perhaps that's because I'm not familiar with the instruction set though.

I currently only have a DD2 (which has a dsPIC33EP512MU810) and I'd rather not take that apart while it's still under warranty. I used to have a CSW2.5 that I wouldn't have minded taking apart but sadly I sold that last month. Have you taken your device apart yet, and perhaps checked if (unlikely) the protection of the MCU is disabled which could allow debugging or reading out the flash? Are there any additional EEPROM/Flash chips inside?

EIREXE commented 3 years ago

I'm not sure if you had noticed already but it appears that even after decryption and removing the version "header" it's still not a completely valid Intel hex file. At one point in the file, you should be able to find the string :00000001FF. This marks the end of the file and there shouldn't be any data after it. Instead, there appears to be a few bytes of binary data followed by a partial line from earlier in the file. Then almost 1000 lines are repeated once again, followed by another truncated line at the end. I have no idea why this is but those few binary bytes might be of value?

Truncating the file after :00000001FF still doesn't help Ghidra make any sense of it. Converting it to binary also reveals not a single string to be found, while I was expecting at the very least some version strings or text relating to the DD2 display, or maybe even debug messages. Perhaps there's a separate file for data?

My experience is with AVR and ARM mainly so I'm still getting used to PIC. Using MPLAB X seems to disassemble the "fixed" hex file, and it seems to start off with a GOTO 0x200 and IVT as normal, but the instructions that follow really don't make much sense to me, perhaps that's because I'm not familiar with the instruction set though.

I currently only have a DD2 (which has a dsPIC33EP512MU810) and I'd rather not take that apart while it's still under warranty. I used to have a CSW2.5 that I wouldn't have minded taking apart but sadly I sold that last month. Have you taken your device apart yet, and perhaps checked if (unlikely) the protection of the MCU is disabled which could allow debugging or reading out the flash? Are there any additional EEPROM/Flash chips inside?

It could be the file is invalid because it's a partial update? hmmm i'll check if I can find the routine that does updating in the FwClubSportBaseUpdater.exe, there might be something there.

Do note that there are usually two microcontrollers, a general purpose one like the dspic33ep512mu806 in the CSL Elite and a sister chip doing the motor control.

Regarding the board having more stuff on it, I only took it apart enough to get to the main microcontroller and get a pic on it.

imagen

I'll take apart my wheel again ASAP.

Goz3rr commented 3 years ago

I'm guessing the second chip is updated via FwBaseMotorUpdater.exe instead of FwClubSportBaseUpdater.exe so it might just use a separate file for that firmware?

I've also made a little progress in Ghidra, it seems that the support for dsPIC33E is a bit lacking, if I open a PIC-18 binary Ghidra will immediately label all the interrupt vectors etc. I'm guessing it doesn't know about this for the dsPIC33E family, so I'm looking into how to add that. You can at least manually do it by right clicking the memory view and selecting disassemble, then going to analyze and disable the COFF Header Annotation analyzer as that seems to fail for some reason. I know for ARM you have SVD files that describe all the peripherals, loading something similar for PIC should help a bit as well.

As for the actual dissembled code, it seems to make sense at first, with the reset instruction at 0x0 being GOTO 0x200 and the code at 0x200 doing a few things and ending in a reset as well, but when you start digging deeper it starts looking weirder and weirder. Still not sure if I'm missing something or if there is some more processing of the firmware during flashing going on.

EIREXE commented 3 years ago

I'm guessing the second chip is updated via FwBaseMotorUpdater.exe instead of FwClubSportBaseUpdater.exe so it might just use a separate file for that firmware?

I've also made a little progress in Ghidra, it seems that the support for dsPIC33E is a bit lacking, if I open a PIC-18 binary Ghidra will immediately label all the interrupt vectors etc. I'm guessing it doesn't know about this for the dsPIC33E family, so I'm looking into how to add that. You can at least manually do it by right clicking the memory view and selecting disassemble, then going to analyze and disable the COFF Header Annotation analyzer as that seems to fail for some reason. I know for ARM you have SVD files that describe all the peripherals, loading something similar for PIC should help a bit as well.

As for the actual dissembled code, it seems to make sense at first, with the reset instruction at 0x0 being GOTO 0x200 and the code at 0x200 doing a few things and ending in a reset as well, but when you start digging deeper it starts looking weirder and weirder. Still not sure if I'm missing something or if there is some more processing of the firmware during flashing going on.

Correct, that's what the second updater is for.

I think the reason the COFF header annotation analyzer fails is because the file you get when converting the .hex to a binary file using objcopy

objcopy --input-target=ihex --output-target=binary code00.hex code00.bin

Binwalk says the resulting file is a COFF file, so maybe it's an invalid one

I'll take apart the wheel now and take more board shots to see if there's anything I've missed on my first pass

I believe DSPIC33E is just a variant of PIC24 but with digital signal processing added, although I tried disassembling it was a pic24 binary but it didn't work, maybe with the partial file it will work better.

Here are the motor firmware file names btw: imagen

Goz3rr commented 3 years ago

I don't convert to a binary file at all, Ghidra can open the Intel hex files directly.

Selecting either dsPIC33E or PIC24E doesn't really seem to make a difference for the disassembly I'm getting. Trying to decrypt one of the motor firmwares with vlfx results in a bad data error

EIREXE commented 3 years ago

I don't convert to a binary file at all, Ghidra can open the Intel hex files directly.

Selecting either dsPIC33E or PIC24E doesn't really seem to make a difference for the disassembly I'm getting. Trying to decrypt one of the motor firmwares with vlfx results in a bad data error

Yeah, I was just saying that the file is identified as a COFF file, and since the file we have may be invalid that might be causing the crash, I'll look at why vlfx can't decrypt the files in a min.

EIREXE commented 3 years ago

I think this could be an eeprom, but there's no label on it imagen

Goz3rr commented 3 years ago

Yeah that could indeed be an EEPROM.

I've picked up a broken CSW wheelbase off ebay, seller was unable to tell me which version it was and as far as I know they all look the same from the outside. Suspect it's a 1.5 though, hoping that the electronics/firmware are similar enough to be useful. As a backup plan I've also ordered an evaluation board for the dsPIC33EP512, so I can use that to try and flash the firmware to it to see if it runs/i can step through it.

EIREXE commented 3 years ago

Yeah that could indeed be an EEPROM.

I've picked up a broken CSW wheelbase off ebay, seller was unable to tell me which version it was and as far as I know they all look the same from the outside. Suspect it's a 1.5 though, hoping that the electronics/firmware are similar enough to be useful. As a backup plan I've also ordered an evaluation board for the dsPIC33EP512, so I can use that to try and flash the firmware to it to see if it runs/i can step through it.

Godspeed man, I'm not very good at this myself!

Also from what I can tel they've been using the dspic33e for ages, the old porsche gt3rs wheel from back in the day uses it too: https://www.isrtv.com/forums/topic/5257-fanatec-gt3rs-v1v2-mod-prototype/

Goz3rr commented 3 years ago

Here's some quick pictures of my board:

image image

It has a dsPIC33EP256MU806 on it, no external EEPROM, and no daughter chip for motor control. Header X200 is the debug connector, it follows the standard PICKIT pinout: image pin 1 being the one furthest away from the USB connector

I'll check tonight if I can connect to it and debug or read out the flash (unlikely), and then connect it to my PC and see what the Fanatec drivers make of it, because I assume it's not supported anymore.

Goz3rr commented 3 years ago

Connecting was pretty easy, the only gotcha is that it appears the processor is responsible for latching the power button, so because it gets reset by the debugger you have to manually hold down the power button for the entire process:


*****************************************************

Connecting to MPLAB PICkit 3...

Currently loaded firmware on PICkit 3
Firmware Suite Version.....01.56.07
Firmware type..............dsPIC33E/24E

Target voltage detected
Target device dsPIC33EP256MU806 found.
Device Revision ID = 4002

Reading...

The following memory area(s) will be read:
program memory: start address = 0x0, end address = 0x2abff
configuration memory
User Id Memory
Auxiliary memory
Read complete
Target removed

That gives the following configuration bits: image

The general segment (main program flash) is protected, and reading it out results in all zeroes being returned. Strangely enough, the reset target is set to auxiliary flash (starts at 0x7FC000 instead of 0x200) which is not protected at all. I dumped a copy of it if you're interested, but it looks similar to the firmware updates and I can't make too much sense of it yet.

Sadly JTAG is also disabled so I can't attach to a running processor and step through it, still waiting for the other dev board and I'll try to flash what I have now to that and debug it.

Going to reconnect everything to the PCB now and check if it works, as it seems the previous owner has been in here before and knocked at least one decoupling cap off the board.

Goz3rr commented 3 years ago

I also realized that in the firmware dumps from before there are in fact just plain ASCII strings. They're encoded in pairs of two bytes followed by two null bytes.

image

Ghidra doesn't seem to understand or be able to find strings because of this, trying to figure out if there's a way to fix that. NationalSecurityAgency/ghidra#1507 is related.

I think that at least this shows that there's probably no further obfuscation/encryption happening between the updater and it writing to flash, otherwise there wouldn't be any plaintext strings

EIREXE commented 3 years ago

I also realized that in the firmware dumps from before there are in fact just plain ASCII strings. They're encoded in pairs of two bytes followed by two null bytes.

image

Ghidra doesn't seem to understand or be able to find strings because of this, trying to figure out if there's a way to fix that. NationalSecurityAgency/ghidra#1507 is related.

I think that at least this shows that there's probably no further obfuscation/encryption happening between the updater and it writing to flash, otherwise there wouldn't be any plaintext strings

That is amazing progress, and yes I also don't think there is any other encryption stuff with the data you found, thanks!

BTW not sure if I mentioned it but the fanatec firmware updater tool will actually just swallow the unencrypted hex files, I flashed my wheel with the file from vlfx and it worked fine.

mrkeuz commented 2 years ago

Just want share some results.

Short story

I decide to re-flash my CSL ELITE base after unsuccessful flash and bricked via official utility. I don't want to send gear to Fanatec. And decide fix it myself for experience and for ability fix same errors in the future. I made my investigation just for sure that firmware is valid for flush.

But, I want to share some info for people "after me". Here results.

I successfully run firmware in emulator

For run hex in emulator I made next preparation:

  1. Install MPLAB IDE 5.50 (official Microchip IDE)
  2. Extract firmware via vlfx
  3. Shrink firmware:
    • Remove First line header
    • Remove tail of firmware all after :00000001FF (mean end-of-file)
  4. Import hex via FileHex/Elf...
  5. Select valid processor
  6. Select Simulator option
  7. Install related MPLAB IDE plugin
  8. Click Debug main projectPause debuggerReset
    Note: reset needed as debugger try to goto to some address (in my case is 0x1a66c). As I understand it tries to execute some debug procedure. And after that I did'n found way return to main code.
  9. Now you can Step into/Step over and can view all registers

Seems firmware running successful, as well as not valid hex files just print myriad error s in console. With valid wheelbase firmware seems all executed as expected, at least my firmware executing some cycle near 000252 address without errors.

...
 296      00024C        8801A6                MOV W6, DSWPAG              
 297      00024E        E12C60                CP.B W5, #0                 
 298      000250        3A0008                BRA NZ, 0x262               
 299      000252        EB4900                CLR.B [W2]           <-- PC       
 300      000254        E80102                INC W2, W2                  
 301      000256        390002                BRA NC, 0x25C               
 302      000258        EC2034                INC DSWPAG                  
 303      00025A        A0F002                BSET W2, #15                
 304      00025C        E90183                DEC W3, W3                  
 305      00025E        3EFFF9                BRA GTU, 0x252              
 306      000260        370004                BRA 0x26A                   
 307      000262        E12861                CP W5, #1                   
 ...

Peripherals

MPLAB IDE can emulate all processor periphery somehow. But I'm high level developer and didn't face with ASM/embedded code early. I wanted to emulate at least "Power" button, but unfortunately I don't know how. I didn't found any pcb schematic how power button connected to pins. But anyway there are a several interesting views in IDE. See:

Useful tips

Summary

For now, I decide just share info. Maybe it will be helpful for somebody.

My environment

MPLAB IDE 5.50 Firmware: 37572778-6508-4e70-b85a-f5337216cfe7.hex (v402) CSL ELITE Ubuntu 20.04

Screenshots

image

image

image

image

image

image

Goz3rr commented 2 years ago

@mrkeuz That's very cool!

I've looked at it on and off a bit over the last few months but didn't make much progress. I have some experience with AVR and ARM chips but ran into issues trying to debug on the actual PIC hardware. I had no idea there was a simulator!

I will check the circuit for the power button on my CSW 1.5 and throw up a schematic for you, maybe if we're lucky it's the same as the CSL Elite since it's the same MCU as well.

mrkeuz commented 2 years ago

Guys, congratulate me. I successfully flash and repair my bricked wheelbase!

Tools used:

Tips:

Thanks, @Goz3rr, @EIREXE for vlfx tool and advices! You save my time and money!

mrkeuz commented 2 years ago

UPD: BREAKING

Issues

Tried different versions of drivers. I even tried flash with such strange tail. All worked, but same behavior. So be CAREFUL with experiments.

Any thoughts welcome!

EIREXE commented 2 years ago

Very interesting, there is a way to view logs on the update application (assuming it's the old one) so it would be nice to see if there's anything there, but I forgot how that worked hmm

mrkeuz commented 2 years ago

I will think how to collect logs. Maybe I will try sniff USB exchange when there is time and when next firmware outs.

Question, what tool you use for decompile updater in such manner?

image I want to investigate, what firmware updater does during update.

Forgot to add detail. When official updater trying flash, wheelbase just turnoff during update. Also, if you hold power several second (bootloader mode) led start blinking, but after release the button, the base just turns off. I tried hold button for avoid turnoff base, but nothing happened.

As I understand correctly wheelbase should stay in bootloader state. Therefore, it is very likely that the base itself lacks some part of the firmware responsible for flashing via USB (assumption).

EIREXE commented 2 years ago

I will think how to collect logs. Maybe I will try sniff USB exchange when there is time and when next firmware outs.

Question, what tool you use for decompile updater in such manner?

image I want to investigate, what firmware updater does during update.

Forgot to add detail. When official updater trying flash, wheelbase just turnoff during update. Also, if you hold power several second (bootloader mode) led start blinking, but after release the button, the base just turns off. I tried hold button for avoid turnoff base, but nothing happened.

As I understand correctly wheelbase should stay in bootloader state. Therefore, it is very likely that the base itself lacks some part of the firmware responsible for flashing via USB (assumption).

I used ghidra iirc the firmware update tool does save the logs somewhere you could use a file handler, I think system internals process explorer cna look at that

Goz3rr commented 2 years ago

As I understand correctly wheelbase should stay in bootloader state. Therefore, it is very likely that the base itself lacks some part of the firmware responsible for flashing via USB (assumption).

At least with my sacrificial CSW 1.5 base, there does appear to be a section of program memory that does not match what is in the updates, presumably this is the bootloader or something that normally would not get modified by an update. If you flash using the programmer you probably erase the whole program memory including the bootloader, but then don't flash it back.

JeffersonSantana commented 2 years ago

Hi guys, how are you? My name is Jefferson, I'm from Brazil, I'm desperate, my CSL DD is bricked and it's too expensive to send it to the United States at Fanatec and then return to Brazil, it's absurdly expensive, and I'm looking for a solution and I got here ... Is it possible for me to be able to force-flash new firmware and solve my problem?

EIREXE commented 2 years ago

Hi guys, how are you? My name is Jefferson, I'm from Brazil, I'm desperate, my CSL DD is bricked and it's too expensive to send it to the United States at Fanatec and then return to Brazil, it's absurdly expensive, and I'm looking for a solution and I got here ... Is it possible for me to be able to force-flash new firmware and solve my problem?

AFAIK bootloader mode should work, I think it's enabled by holding the power button

JeffersonSantana commented 2 years ago

Hi EIREXE, how are you?

Holding the power button it opens the bootloader, but it does not recognize any connected equipment, using Fanatec's own software, I thought I would use another firmware flash software and see if it recognizes any chip and also if it would be possible to record a new firmware, but I would still need know what the CSL DD HEX file is. Is this possible in your view?

JeffersonSantana commented 2 years ago

Just want share some results.

Short story

I decide to re-flash my CSL ELITE base after unsuccessful flash and bricked via official utility. I don't want to send gear to Fanatec. And decide fix it myself for experience and for ability fix same errors in the future. I made my investigation just for sure that firmware is valid for flush.

But, I want to share some info for people "after me". Here results.

I successfully run firmware in emulator

For run hex in emulator I made next preparation:

  1. Install MPLAB IDE 5.50 (official Microchip IDE)
  2. Extract firmware via vlfx
  3. Shrink firmware:

    • Remove First line header
    • Remove tail of firmware all after :00000001FF (mean end-of-file)
  4. Import hex via FileHex/Elf...
  5. Select valid processor
  6. Select Simulator option
  7. Install related MPLAB IDE plugin
  8. Click Debug main projectPause debuggerReset Note: reset needed as debugger try to goto to some address (in my case is 0x1a66c). As I understand it tries to execute some debug procedure. And after that I did'n found way return to main code.
  9. Now you can Step into/Step over and can view all registers

Seems firmware running successful, as well as not valid hex files just print myriad error s in console. With valid wheelbase firmware seems all executed as expected, at least my firmware executing some cycle near 000252 address without errors.

...
 296      00024C        8801A6                MOV W6, DSWPAG              
 297      00024E        E12C60                CP.B W5, #0                 
 298      000250        3A0008                BRA NZ, 0x262               
 299      000252        EB4900                CLR.B [W2]           <-- PC       
 300      000254        E80102                INC W2, W2                  
 301      000256        390002                BRA NC, 0x25C               
 302      000258        EC2034                INC DSWPAG                  
 303      00025A        A0F002                BSET W2, #15                
 304      00025C        E90183                DEC W3, W3                  
 305      00025E        3EFFF9                BRA GTU, 0x252              
 306      000260        370004                BRA 0x26A                   
 307      000262        E12861                CP W5, #1                   
 ...

Peripherals

MPLAB IDE can emulate all processor periphery somehow. But I'm high level developer and didn't face with ASM/embedded code early. I wanted to emulate at least "Power" button, but unfortunately I don't know how. I didn't found any pcb schematic how power button connected to pins. But anyway there are a several interesting views in IDE. See:

  • Menu → Window → Debugger → Io View
  • Menu → Window → Simulator → IoPins
  • Menu → Window → Simulator → LogicAnalyzer
  • Others...

Useful tips

  • For easy dumping intel hex files there is a simple and useful tool intelhex (see: docs) I've use hex2dump.py firmware_stripped.hex | less
  • Pic33 registers descriptions
  • For disassembly file built-in WindowDebuggingDisassembly tool don't work by some reason. But you can save disasm file from Program Memory tab, right-click Output to File.... Maybe will be useful for analysis.
  • If understand correctly you can override setup bits (like JTAG). Not tested but somewhere in MPLAB IDE I saw option for view and override setup bit. Seems in this case intel hex line will be overridden on F8000E address and CRC of this line will be recalculated. (see intel hex format) image
  • Flush firmware seems can be done via ICSP. I.e. via debugger mention in comment above (MPLAB PICkit 3). Not tested yet, I'm waiting hardware for that. I will write results here.

Summary

For now, I decide just share info. Maybe it will be helpful for somebody.

My environment

MPLAB IDE 5.50 Firmware: 37572778-6508-4e70-b85a-f5337216cfe7.hex (v402) CSL ELITE Ubuntu 20.04

Screenshots

image

image

image

image

image

image

Hello guys,

How can you identify the HEX file of each base? For example, I want to identify the HEX of the CSL DD, how to proceed?

I'm very grateful for the help.

mrkeuz commented 2 years ago

@JeffersonSantana hi! How it's going? Did you fix it?

JeffersonSantana commented 2 years ago

Hi @mrkeuz... I'm not fix it!

I have the DD base stopped, Fanatec asked to send it to the USA, but it is very expensive to send and return here to Brazil.

mrkeuz commented 2 years ago

I see two options

Official

For cheapest transporting just disassembly and extract board only. Think delivery only board without mechanic if Fanatec agree this option will be adequate pricing.

Non-official

Flush firmware your microcontroller. Pickit 3 device help you. (check compatible with you mcu (see you board processor 'dcMCUxxxxxx'))

You need:

NOTICES:

Links:

Looks like complicate - but be brave :)

I recommend fin some low level microcontroller engineer for help and consulting for disassembly and re-flush. Or some professional service, specialized in microelectronics (phones for example). Just show him these instructions.

Good look! Don't give up!

EIREXE commented 2 years ago

Honestly I learned a bit ago that the EU doesn't have an issue with us publishing the key, so I could modify vlfx to bundle the keys

EIREXE commented 1 year ago

I finally got around to modifying vlfx to not require the decryption key anymore: https://github.com/ProjectSerotonin/vlfx/releases/tag/0.4.0

mrkeuz commented 1 year ago

@JeffersonSantana, any news? Just curious about procedures? And how Fanatec support negotiate with you about you issue.

Actually big fan of Fanatec and in my specific device (I love belt drive units in common)

foogadgets commented 1 year ago

I'm not sure if you had noticed already but it appears that even after decryption and removing the version "header" it's still not a completely valid Intel hex file. At one point in the file, you should be able to find the string :00000001FF. This marks the end of the file and there shouldn't be any data after it. Instead, there appears to be a few bytes of binary data followed by a partial line from earlier in the file. Then almost 1000 lines are repeated once again, followed by another truncated line at the end. I have no idea why this is but those few binary bytes might be of value?

This happens because the vlfx have a bug. I had problem with running this vlxf program in windows 11, so I rewrote it in c instead as I do not know rust. I got it working. Before it worked fully I had a bug that I solved. That bug is also in vlfx the vlxf code.

In the file fanatec_decrypt.rs line 110 the decrypted buffer is copied to the output variable. All the buffer is copied. If CryptDecrypt-function have reached the end of the file the buffer is only partly filled, the rest of the buffer is filled with the previous content. You need to fill the buffer with 0 after every copy of the buffer to the output variable. Then you will get a nice hex-file with no garbage in the end.

EIREXE commented 1 year ago

I'm not sure if you had noticed already but it appears that even after decryption and removing the version "header" it's still not a completely valid Intel hex file. At one point in the file, you should be able to find the string :00000001FF. This marks the end of the file and there shouldn't be any data after it. Instead, there appears to be a few bytes of binary data followed by a partial line from earlier in the file. Then almost 1000 lines are repeated once again, followed by another truncated line at the end. I have no idea why this is but those few binary bytes might be of value?

This happens because the vlfx have a bug. I had problem with running this vlxf program in windows 11, so I rewrote it in c instead as I do not know rust. I got it working. Before it worked fully I had a bug that I solved. That bug is also in vlfx the vlxf code.

In the file fanatec_decrypt.rs line 110 the decrypted buffer is copied to the output variable. All the buffer is copied. If CryptDecrypt-function have reached the end of the file the buffer is only partly filled, the rest of the buffer is filled with the previous content. You need to fill the buffer with 0 after every copy of the buffer to the output variable. Then you will get a nice hex-file with no garbage in the end.

thanks for your investigation, i'll look into fixing it ASAP