AeroX2 / brother-cart-emulator

MIT License
6 stars 1 forks source link

Write of image binary file using PED Basic and cracker.py with radare2 fails #3

Open maehw opened 3 weeks ago

maehw commented 3 weeks ago

Hi @AeroX2 !

Thanks for your efforst in making writing the embroidery card binary images writeable to files!

I'ved tried to get the toolchain running on my machine but have failed so far. Maybe you could give me some guidance here?

Versions:

I don't really think that you had a different version of PED-Basic or the CardIO.dll as both seem pretty old - but you never know.

I somehow guess that I am running into Windows user rights issues. I had installed PED-Basic as the local admin and tried to execute pelite.exe via python cracker.py as normal user. Next, I tried to modify all access rights to all files in a way that a normal user should have full access. I then even copied everything into C:\Users\User\Documents\PED-Basic and it still does not work.

I'll share snippets from the log of python cracker.py, especially those that indicate warnings or errors:

WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
ERROR: Cannot debug file (pelite.exe) with permissions set to 0x7Reopening the original file in read-only mode.

INFO: Spawned new process with pid 13068, tid = 3036
INFO: File dbg://C:\\Users\\User\\Documents\\PED-Basic\\pelite.exe reopened in read-write mode
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
13068
(13068) loading library at 0x00007FFFD20B0000 (C:\Windows\System32\ntdll.dll) ntdll.dll
...

WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
[Relocations]

vaddr      paddr      type   ntype name
---------------------------------------
0x0003bda4 0x00036048 SET_32 3     CardIO.dll_public: void __thiscall CCardIO::constructor(int)
...

596 relocations

w \x90 @ 4273898
w \x90 @ 4273899
w \x90 @ 4273900
w \x90 @ 4273901
w \x90 @ 4273902
w \x90 @ 4273903
w \x90 @ 4310977
w \x90 @ 4310978
w \xeb @ 4275697
w \xeb @ 24027
ERROR: Cannot write. Use `omf`, `io.cache` or reopen the file in rw with `oo+`
w \xeb @ 24099
ERROR: Cannot write. Use `omf`, `io.cache` or reopen the file in rw with `oo+`
w \xeb @ 24174
ERROR: Cannot write. Use `omf`, `io.cache` or reopen the file in rw with `oo+`
WARN: base addr should not be larger than the breakpoint address
WARN: Cannot set breakpoint outside maps. Use dbg.bpinmaps to false
INFO: Continue until 0x00006ad5 using 1 bpsize

I guess that the ERROR: Cannot write. messages are what really makes me trouble. The file image.bin however is created with 64 kBytes but only contains 0xFF (what would be an empty flash EEPROM/memory). Is there anything I should inspect?

Any hints are very appreciated!

M.

PS: I've also tried to share my findings in the EEVblog forum thread https://www.eevblog.com/forum/reviews/brother-(possibly-also-bernina)-embroidery-machine-memory-cards/?all


Edit: Reinstalling PED Basic in C:\PED-Basic and running python cracker.py from cmd.exe with admin rights also did not help. So the caching part seems to be related to radare2 but I don't really know what to modify in the Python script.


Edit: Trying to use r = r2pipe.open('pelite.exe', ['-e', 'bin.cache=true', '-w']) gave me:

C:\PED-Basic>python cracker.py
ERROR: Cannot debug file (pelite.exe) with permissions set to 0x7Reopening the original file in read-only mode.

INFO: Spawned new process with pid 7076, tid = 6352
INFO: File dbg://C:\\PED-Basic\\pelite.exe reopened in read-write mode
ERROR: bin.relocs and io.cache should not be used with the current io plugin
7076
(7076) loading library at 0x00007FFFD20B0000 (C:\Windows\System32\ntdll.dll) ntdll.dll
...

ERROR: bin.relocs and io.cache should not be used with the current io plugin

Traceback (most recent call last):
  File "C:\PED-Basic\cracker.py", line 35, in <module>
    cardio_addr = int(re.findall(r"0x([0-9A-F]+)", cardios[-1])[0],16)
                                                   ~~~~~~~^^^^
IndexError: list index out of range

Edit: I've given up for now. Some files or folders (especially the radare ones) seem partiall write-protected. And even when I remove the protection as admin.. they come back right again. Also: rolling back to radare2-5.8.2-w64 which may have been the version you used (released Jan 23, 2023) gives me different warnings/errors - but also does not work overall:

C:\PED-Basic>python cracker.py
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
ERROR: Cannot debug file (pelite.exe) with permissions set to 0x7Reopening the original file in read-only mode.

ERROR: Parse error @ line 30 (Invalid register type)
ERROR: Parse error @ line 30 (Invalid register type)
INFO: Spawned new process with pid 2732, tid = 2264
ERROR: Parse error @ line 30 (Invalid register type)
ERROR: Parse error @ line 30 (Invalid register type)
ERROR: Parse error @ line 30 (Invalid register type)
ERROR: Parse error @ line 30 (Invalid register type)
ERROR: Parse error @ line 30 (Invalid register type)
ERROR: Parse error @ line 30 (Invalid register type)
INFO: File dbg://C:\\PED-Basic\\pelite.exe reopened in read-write mode
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
WARN: invalid type
...

I think the "ERROR: Cannot debug file (pelite.exe) with permissions set to 0x7Reopening the original file in read-only mode." might be the critical part here. I have no clues how to fix this under Windows... :(

AeroX2 commented 3 weeks ago

Yeah you are right on with radare2, this script is quite old at this point and so hasn't really kept up to date with radare2 hence the issues.

The read write issue is actually because it is trying to write into the wrong addresses since it was unable to find the correct memory address for CardIO.dll. I've just updated the script and tested it and it seems to work, though I'm testing within a virtual machine with ASLR turned off so your milage may vary.

The other issue I see you might be running into is if the program is still open in the background you also can't write into it, you need to close all instances of cracker.py and the program and then run it and it should hopefully work 🤞

AeroX2 commented 3 weeks ago

Just tested on a non-VM machine and everything seemed to worked with the updated script

Windows 11 Python - 3.12.0 r2pipe - 1.9.4 PELite 1.07

Edit: Actually just noticed my script forgot to take into account lowercase hex values so the script would sometimes work and sometimes not, updated it to working with both

maehw commented 3 weeks ago

Thank you very much for your fast reply and fix. Good news: it now also works for me!

I forked your repository and suggest a few minor changes, see #4 . I've done this primarily to help other users understand your code without doing their own research/debugging. Feel free to discard them. 😉

Thanks very much for submitting and publishing your code!

If you have time, I'd be happy if you could answer the following questions (no rush):

PED-Basic-CardMemoryUsage

🥳 🎉

AeroX2 commented 3 weeks ago

I forked your repository and suggest a few minor changes, see https://github.com/AeroX2/brother-cart-emulator/pull/4 . I've done this primarily to help other users understand your code without doing their own research/debugging. Feel free to discard them. 😉

Thanks for the PR, definitely will make it easier for future users

The card memory usage indicator doesn't work with the patches. Do you know why? Do you think there is an easy fix? Would be nice to have direct feedback instead of the "0%" bar.

Unlikely to be an easy fix, I suspect the reason for the 0% is because I'm not actually writing to a card, only looking at the memory and taking the data that would be written to the card, the progress bar is likely tied to the actual card writing.

Why is the output binary file 64 kiBytes (65'536 bytes)? If I get it right the upper technial limit is 512 kiBytes? Do you think this is fix-able as well?

Two things, one is I pick the smallest card size of 64kb, you can see in the README.md I pick the address 0x10005E6B, to override and this corresponds with 64kb, for 512kb, you'll need address 0x10005E7D (probably). Second is that pxj 0x10000 in cracker.py is dumping out only 64kb worth of data, so changing those will allow you to dump more.

What license is your repo (and my contributions)? I think you haven't added a LICENSE file yet. I'd be happy if this was open source (and maybe it kind of already is... never had repos without an explicit license).

I'll add a MIT license, that way people are free to use it the way they want

maehw commented 3 weeks ago

Hi James,

thanks for everything:

I just got a brother PED-Basic in the mail. I had not planned to buy one, but that one was even kind of affordable. This should make some research easier in case I keep being motivated.

It really seems that every time the usage bar (edit: "card capacity indicator" is what the manual calls it - makes sense!) is updated, the PED card reader is accessed (red busy LED turns off for a short moment). The blue color part is the amount of memory already reserved by the PES files "copied to the right side". The cyan color part is the extra amount of memory "copying" the selected PES file on the left would add on top:

CardMemoryUsageColored

Interestingly, the GUI seams to offer write-only access to the card. I have no clue if my card had been written before or erased in a way. But even after writing some of the samples and re-plugging the card... nothing on the right appears without copying PES files from left to right. So, I guess that the card is just completely whiped and the generated binary is written... w/o data being read. I guess that at least some manufacturer/model data from the EEPROM/flash memory should be read to determine the card size - but that's a wild guess.

Summing up: thanks for your help! Rowing this boat together definitely makes it more fun!

maehw commented 2 weeks ago

Hi again,

I am trying to print the disassembly of functions in pelite.exe and CardIO.dll to better understand the context of the patches/ your screenshots so that I can extend cracker.py. So I am asking you to shed some light into the darker spots I do not fully understand yet.

I've pushed a commit on the fork of your repo: https://github.com/maehw/brother-cart-emulator/commit/cf5b36798d20327e7ab338c64bfde90991501025

When being exeucted, I get the following output:

Printing imported CardIO functions...
signature: void__thiscallCCardIO::constructor(int), address: 0x00436048
32 bit word: 0x10001980
signature: enumCIOError__thiscallCCardIO::ChkCardWriterConnected(int,unsignedchar*,int*), address: 0x0043604c
32 bit word: 0x10001df1
signature: enumCIOError__thiscallCCardIO::Receive(classCObArray*,int,voidconst*,voidconst*), address: 0x00436050
32 bit word: 0x10001ca1
signature: void__thiscallCCardIO::ResetCardID(void), address: 0x00436054
32 bit word: 0x10001940
signature: enumCIOError__thiscallCCardIO::Send(classCObArray&,voidconst*,voidconst*,enumCCardAtrbType*), address: 0x00436058
32 bit word: 0x10001d0f
signature: enumCIOError__thiscallCCardIO::ChkCardVolume(classCObArray&,int&,int&,enumCCardAtrbType*), address: 0x0043605c
32 bit word: 0x10001d80

So it seems that the exported functions are known to r2 here. I used r2 command ii. As the address values are +4 byte each - I guess this is rather a function pointer table (32 bit addresses from good' ol' 32 bit world?) somewhere in memory where the lib is loaded during runtime.

When having a look at the values at those addresses pxw 4 @ ..., I get those 0x1000____ addresses. Where does this offset come from? I'v also spotted it in your code 0x10000000. Unfortunately, I wasn't able to get a dump of the functions there (command pdf @ ...).

What am I missing here?

I'd also like to understand why you chose cracker.py to run until addresse 0x6ad2/0x6b0e. Without the disassembly I am lacking context here.

Also, I'd like to explore the code area more where the different flash sizes are used.

How did you get the GUI view? Is it another RE tool? Prefereably, I'd like to get the disassemblies in the context of running cracker.py.

Your help is very much appreciated!

Cheers

AeroX2 commented 2 weeks ago

When having a look at the values at those addresses pxw 4 @ ..., I get those 0x1000____ addresses. Where does this offset come from? I'v also spotted it in your code 0x10000000. Unfortunately, I wasn't able to get a dump of the functions there (command pdf @ ...).

Hmm not sure why pdf might not be working but I'd probably recommend doing it in Ghidra since that tool is much more friendly, I only used r2pipe because it was the only scriptable debugger that I knew of and too be honest was a bit of a pain to work with. As for the 0x10000000, that address is the default address Windows uses for any DLL's that are loaded by a program that aren't rebased a different address so in this case the CardIO.dll https://devblogs.microsoft.com/oldnewthing/20141003-00/?p=43923#:~:text=Since%20the%20operating%20system%20itself,you%20start%20colliding%20with%20DLLs. image

I'd also like to understand why you chose cracker.py to run until addresse 0x6ad2/0x6b0e. Without the disassembly I am lacking context here.

Yeah all good, reverse engineering is quite difficult and you have to do a bit of guess work, so if you look at offset 0x6b11, there is a function which is setting up the Brother copyright header, this isn't writing the embroidery data that comes later image But I know that this is the function that writes into the card data memory location so I can breakpoint at this point and extract it with p8j 4 @ rcx, (technically I could have also done this at 0x6b0e but I felt it was just easier to do at this location because I knew the address was in register rcx)

The 0x6b11 function is only called by one other function (offset 0x6ac8), and there are three other functions called here which write the embroidery data so offset 0x6b0e is the ret instruction and is the first instruction where I know that the embroidery data is all written to memory and I can safely extract it.

https://github.com/user-attachments/assets/c987ba6e-fbaa-4aeb-a996-2f01ca683887

How did you get the GUI view? Is it another RE tool? Prefereably, I'd like to get the disassemblies in the context of running cracker.py

I use a tool called Ghidra (https://ghidra-sre.org/), x64dbg (https://x64dbg.com/), along with retsync (https://github.com/bootleg/ret-sync). You probably just need Ghidra, which can decompile the program into a C-ish program state but if you want to see what exactly is happening at each state you need to step through it with a debugger (x64dbg) and being able to look back and forth at what is happening with the debugger and Ghidra is where ret-sync comes in.

As a side note if you find this kind of thing interesting I would highly recommend the Flare-On challenge (https://flare-on.com/), its a set of reverse engineering challenges that runs every year and they are self contained so you even work on last years challenges and see people's solutions for them. It is where I learnt a lot of how to go about reverse engineering these programs and how other people go about solving it as well.

maehw commented 2 weeks ago

Hi James / @AeroX2 ,

thanks for your detailed explanations.

This gave me new insights as I am an embedded software developer and not very experienced with application development - at least not on the reverse engineering side of things.

Using Ghidra alone for analysis of the DLL's disassembly did the trick. If I find time and need for setting up the other tools, I might write you again.

In the meantime, I've added support for multiple card sizes - see pull request #5 .

Would be great to get the "progress bar" (memory card usage indicator) feature working as I've seen single PES design pattern files overrunning the default 64 kiBytes (see explanation in my pull request).

HTH / Cheers

maehw commented 1 week ago

Hello again,

I've dived deeper into the disassembled code of both pelite.exe and CardIO.dll.

Unfortunatel, I haven't been able to locate the code which calculates/draws the memory card usage indicator. Could you maybe give me some more guidance here to make this happen?

So far, I've "only" used Ghidra for static analysis.

I've mainly used your patches to get some context and the strings I found (many printf format specifiers). I've also seen some calculations which I thought were suspicious - without any luck.

The function at 0x00416361 seems to format the relative and absolute sizes (%3d%% resp. %5s%s X %5s%s) of the selected pattern which are displayed in the lower left corner of the PED-Basic window. I've verified this by replacing single characters in those format specifiers.

I've also found another percentage value being formatted in the function at 0x0041c67b -- it is the relative pattern size displayed on the right window side after copying it to the card (format specifier %d%%).

This was interesting, but it did not lead me in the right direction.. because the 100% value is never re-labeled. Only the blue/cyan rects are displayed. This should also happen when a pattern is selected on the left hand side.. but I did not find an entry function (on-item-selection-callback?!) where this is hooked.

In addition, I've also read @bezmi's comment https://github.com/AeroX2/brother-cart-emulator/issues/1#issuecomment-1435530216 -- I also see a corrupted string in the output binary file (ÿrother_sewing) - do you have an idea what could cause this issue? Did the corrupt images work with your machine?

Another hint: in the patch descriptions of image-dumper/README.md, you call it "Bypass ChkCardVolume". Strictly speaking the method seems to be called but the result value is ignored and your patches modify the code to make it look like everything turns out as expected. I found this pretty misleading.

This is fun.. and tedious at the same time.


Edit 2024-09-09: Strange thing is, "brother_sewing" seems to be written to various offsets (for different versions?!). This seems to be done by the function at 0x100071eb in CardIO.dll -- and there's another function at 0x1000261c which seems to check for this string. Valid offsets are 0x100, 0x170, 0xC0 and 0x280. Interestingly the checking function looks for 0xE=14d bytes which is the string length (w/o zero termination) but the writing function (which takes the function argument as input parameter for the switch-case)... uses offsets +1 and a length of 0xD = 13d to memcpy (memcpy((void *)(*this + 0x101),(undefined *)((int)&s_brot + 1),0xd);)... so it kind of would make sense why we only see "rother_sewing". However, the dumpd putty.txt clearly shows the "b" at the beginning of the string. Is it written separately from a different function?

maehw commented 1 day ago

Hi James,

You probably just need Ghidra, which can decompile the program into a C-ish program state but if you want to see what exactly is happening at each state you need to step through it with a debugger (x64dbg) and being able to look back and forth at what is happening with the debugger and Ghidra is where ret-sync comes in.

I'd be interested in a brief description how to set the three tools up in the sync'ed mode you described. Can you also see the decompiled C code? I can run pelite.exe from x64dbg but I have no clue how to start a Ghidra session for the dynamic analysis and also no idea what to do with the ret-sync release file (which seems to be a single *.dp64 resp. *.dp32 file).

Have a nice sunday!

AeroX2 commented 4 hours ago

This was interesting, but it did not lead me in the right direction.. because the 100% value is never re-labeled. Only the blue/cyan rects are displayed. This should also happen when a pattern is selected on the left hand side.. but I did not find an entry function (on-item-selection-callback?!) where this is hooked.

I suspect this is because it is using the Windows API for progress bars and so there isn't a "100%" label in the program just a function that is advancing the ticks for the progress bar but I haven't dived into this myself.

In addition, I've also read @bezmi's comment https://github.com/AeroX2/brother-cart-emulator/issues/1#issuecomment-1435530216 -- I also see a corrupted string in the output binary file (ÿrother_sewing) - do you have an idea what could cause this issue? Did the corrupt images work with your machine?

I don't think I've seen a ÿrother_sewing just ÿbrother_sewing but I suspect it would still work even with this corrupted image since I don't think the headers are really read by the machine just the offsets for the PES data and the PES data itself

I'd be interested in a brief description how to set the three tools up in the sync'ed mode you described.

ret-sync just makes the debugger and Ghidra talk to each other so that the line that you are currently breakpointing on is the same line that is highlighted in Ghidra. This setup makes it a touch easier to reverse engineer but you can still look at the line numbers manually and just match them up.

Can you also see the decompiled C code?

Ghidra shows decompiled C code but it is still heavily obfuscated and not easy to navigate.

I can run pelite.exe from x64dbg but I have no clue how to start a Ghidra session for the dynamic analysis and also no idea what to do with the ret-sync release file (which seems to be a single .dp64 resp. .dp32 file).

You can have a look at the ret-sync page for their Ghidra (https://github.com/bootleg/ret-sync?tab=readme-ov-file#ghidra-usage) instructions

(which seems to be a single .dp64 resp. .dp32 file).

These are the plugin files that x64dbg uses, you need to drag and drop them in x64dbg plugin folder

bezmi commented 4 hours ago

Hey @maehw, have you tried with the vikant writer workflow from my repo? There is an ipython notebook that shows how to locate the thumbnail data and the python script emulates a vikant writer so that you can create flash images from custom stitch data (you don't need any hardware to do it). I also have notes which I think are detailed enough to be able to replace thumbnails and stitch data in a given vikant/PED file with our own stuff using python directly. Let me know if you want to look at any of those binary files with custom stitch data.

I started with reverse engineering the binaries, but it gets complicated really fast and it was actually more instructive to jump around the card data in python.