-
Hardware Digging, K, and S Progress
12/15/2018 at 04:12 • 6 commentsWhat's happening now is a period where there isn't going to be much interesting to see. I'm looking at how the firmware and bootloaders manipulate the lerdge hardware for clues to how I will do it myself. Most of this is done in binaryNinja, and the process is intexact. I name a function, rename it as I understand what it does, rename it as I make progress.
The LCD controller remains unknown, and right now I can't init FSMC to attempt to read the id out. However, I have been able to decipher chunks of functionality. How? Well, while many LCD controller commands are unique, they tend to share some commonalities, which is why I've guessed this is likely a fill:The 2A is X location, the 2B is Y, and the other variable is probably a color. The loop repeats a write.
How about some others?
This is some of the code which intializes the LCD. Unfortunately it doesn't save off the results of the id/model read, but once I can init the FSMC correctly, I'll write my own version.
And what about the SD Card?
There we go. The SD Card initialization code. I am slowly learning the details of the FSMC & SDIO components, but it's going to be a while. In the mean time, I'll be soldering leads to the lerdge-x wifi printing module. This accepts a standard ESP8266-S1, and the header has nice pin tops for the SWD leads. Pictures of that when it's done, and then I'll 100% dump the lerdge-K board. The bootloader actually initializes some hardware that doesn't exist on the -X, which makes me think the X and the K share very, very similar hardware.
Oh, and one Hackaday user reports that the base bootloader, when flashed on the -S, boots up. I haven't done so, and I frankly didn't expect it to work. The -S bootloader, when downloaded from Lerdge's website, is certainly different.
One goal, when SD works, is to build a dumper which saves off the firmware, CCRAM, SPI Flash, and if possible, display RAM. Then, should there be updates, users can save off their own.
-
The Beginnings of Marlin Support
12/14/2018 at 15:25 • 1 commentHaving taken over the MCU and being able to load my own firmware, I had a few different directions I could go. First off, thanks to a hackaday member (who I'm not mentioning because I didn't ask if was ok first), I have a lerdge-K board to test with. It technically has a broken port on it, but for what I'm doing, that matters exactly zero.
I'm working now on writing up the bootloader dumping process, and one goal is to build a firmware that if loaded using the default firmware, saves off flash, CCRAM, and SPI RAM. This will be the "You can always go back" sketch. To do that, I have to get SDFAT playing nicely with ST's arduino core and thus far, I'm not having a ton of success. It *might* be simpler to write it as a CubeMX project rather than Arduino, but time is not pressing here. I can afford to tinker, or wait.And speaking of waiting, waiting brought a USB PR to the official ST arduino core. I integrated that into my build, but my macbook air crashes every time I plug in the lerdge, because the CH drivers are probably buggy. With no serial and no USB, I fell back on debug via LED (similar to my fan debugging for the Malyan M200) and now have a marlin 2.0 build which starts up, runs for four seconds, and then resets when the watchdog kicks in.
This implies that the watchdog isn't getting fed, and given what I just went through with the M200, I'm going to start by validating which timers are in use for PWM and make sure the temperature ISR isn't getting obliterated by ST's "only hardware PWM, ever" core.
The other thing I've been doing is researching the LCD. Once I can get code running solidly, I can afford to build some tests around this, but for now, I'm looking at how the bootloader interacts, and I've basically worked out a few key things.
1. The LCD uses "normal" exit from sleep, write to ram, set x and y commands. As in I can find other LCDs that use the same commands.
2. The Bootloader initializes LCD pins that don't seem to be connected on the Lerdge-X, supporting the theory that the X and K use the same bootloader (this would certainly make sense - the S does NOT, from what I can tell). most graphics are done using point by point drawing rather than a blit from RAM (the equivalent for LCD).
So, now the less fun work begins. Working out timer conflicts if any. Figuring out how to configure the library and gain access to the SD card. I published to Github my preliminary pins file for Marlin 2.x. One day this will likely be a PR, but it will be AFTER I figure out all the conflicts.
There will likely not be an example configuration file for the lerdge, because this is a board you stick in other machines, not a machine definition by itself.
-
Breaking Serial Wire Debug (don't do it this).
12/12/2018 at 17:05 • 0 commentsAfter a great deal more debugging, I finally got something running on the lerdge. To begin with, I saved off the PC register and VTOR addresses to make sure my settings were correct, or at least matched what the board expected.
Not surprisingly, they were right.After a bunch of debugging, I followed the chain of functions from start to the static init function, which is actually where the clocks are configured (which is odd - you'd expect them to be earlier in case another initialier needs to delay). It may be that there's code that forces the init to happen first.
But here's the thing - the board was hanging because I was re-initializing everything, and it was already running. And SWD on STM32 chips does not run well without a clock signal. The ST link reported "unknown chip id", something it won't do even if you refuse to enable anything.
In STM32DUINO there are some warnings in the clock initialization code that an error there can leave the system without a functioning clock and those are not idle messages.
Solution? Remove main clock init. After checking, most of the peripheral clocks are configured correctly anyway, and I can initialize the others. Just don't mess with the primary clock source once the board is up and running.
After I did this, I uploaded a mildly modified blink sketch..and got this:
-
A Question of Crystals
12/11/2018 at 06:17 • 1 commentMy code wasn't taking control of the lerdge, as I might have mentioned, and with a little bit of in-memory debugging, I determined that we never exit the clock setup. I cloned the arduino variant from the discovery 407, which means I basically ignored all the normal guidance - you're supposed to run the cube, generate a definition for the board,a nd copy the clock init out of that with all the appropriate peripherals set up.
I'm on a mac and having trouble getting it to run, so I'm looking closer at the hardware and not seeing the obvious style of crystal I"m used to on the lerdge-X. So tomorrow I'll probably go part by part until I figure out what it is. Then I can generate a proper definition and try again to get proper Arduino code running.
-
If you own a Lerdge board...
12/09/2018 at 21:07 • 1 commentIn the files I've put out the first 64k of RAM as seen my by code, plus the decrypted bootloader.bin file (which I swear looks like it ought to run at an offset of 0x10000. ) If you have a lerdge board you've accidentally or on purpose wiped, try flashing the first 64k one and see if you can then update to lerdge's normal firmware via the force method.
-
Some Semblance of Control
12/09/2018 at 20:48 • 0 commentsNow I had the decrypt broken, the first thing I did was adjust an arduino blink sketch and attempt to use it to blink the status LED.
Failure.
The firmware would update and then the board would hang.
I got fancy and moved to blinking pretty much everything.
No dice.
I put code in to buzz the speaker, turn on a fan, nothing.
I could decrypt and re-encrypt existing lerdge firmware and by and large, it would work. But anything else I tried, it simply hung. I modified a few existing functions, patching the ARM assembly. Failure.
Then I stepped back and asked myself "Why do I think it's failing?"
I had a fairly decent database of all the operations built up in BinaryNinja, and I simply didn't see it proceed to "Checking the User UI."
It turnes out, that's because that is not in the bootloader - it's in the printer firmware. While my hardware control attempts did not work at all, the hang was because control was being passed to my code.
So I modified my Arduino Variant's startup code to immediately branch to a function, and modified that code to copy "JASON" into ram at the base. Hooked up GDB, rebooted--and there was my name.
I originally planned to reverse engineer the bootloader and make my firmware play nice with theirs, but after a lot of looking, I'm not certain that's what I should do. THe board supports DFU. If we had a complete dump of RAM, we could easily allow users to boot to something not requiring encryption, then DFU back to lerdge if they so decide.
As I have said before, locking people into MY choices is no better than locking people into other choices.
But if I can copy raw bytes to RAM, I can do something ugly ugly. Disable interrupts and copy 10k of data from the base out. Every instance where I connect the STLINK to the lerdge, the lerdge freezes after GDB attaches and never recovers, but if I can get around that, I can make a build which watches a ram location for a flag that it's ok to continue. I attach the debugger, dump 10k. Set the flag, let it copy the next 10k out, and so on, and so on. We have code executing on the lerdge. It's ugly code, difficult to work with. But if we can compile anything, we can make it work.
-
Finally, results
12/05/2018 at 07:35 • 0 commentsAs I went back to basics and began looking at what the output was showing me, i noticed something. If I decrypted a value twice, I got the right entry. This is a fairly good indicator there was a problem with the python script that assembled my hacked ram data, and with one twist (changing the order) I generated a new table.
Immediately i was able to decrypt the lerdge firmware and see that it did appear to be valid code. All the strings decrypted perfectly.
The bootloader also decrypted fine.
Now I need a test subject with a lerdge x board who has already reset their bootloader to burn it on, and to do that, I reached out to the open lerdge people.
We shall see what happens next.
-
Sidetracks, Setbacks, and other Issues
12/03/2018 at 19:57 • 0 commentsSo now I had a plan - I knew of an address in the encrypted ROM which would be decrypted to a given value in RAM, which was accessible. I whipped up a python script which prepared 16 different firmwares, each exposing 16 encrypted values in a set range, and proceeded to move through the process of flashing the firmware on, starting st-util, and then, in gdb, using:
target remote localhost:4242 dump binary memory ~/documents/lerdge/ram/ramtest<x>.bin 0x20000000 disconnect
to dump out the table. A python script then pulled the decrypted values from the RAM files and output them as a table.
Wonderful, except that many, many of the output bytes didn't match values I knew. For instance:
# Byte Range 0x0-0xf 0xaa: 0x0, 0xae: 0x1, 0xb2: 0x2, 0xb6: 0x3, 0xba: 0x4, 0xbe: 0x5, 0xa2: 0x6, 0xa6: 0x7, 0xca: 0x8, 0xce: 0x9, 0xd2: 0xa, 0xd6: 0xb, 0xda: 0xc, 0xde: 0xd, 0xc2: 0xe, 0xc6: 0xf,
We know the value of 0x08 - encrypted, it must be oxDF, because that's the lead byte in the interrupt table. It's certainly possible there's byte swapping going on, but I proceeded to do a series of tests and discovered some interesting behavior. After verfying that my script had written the correct bytes into place, I saw my results didn't fit the known value of 0x65 being the encrypted version of 0x20 (lead byte of the stack pointer)
# Byte Range 0x20-0x2f 0x2b: 0x20,
This lead me to do a few more tests, and discovered that while many bytes changed values, 0x5D always mapped to 0x00. So I inserted known values from my manual mapping, and low and behold, when mixed with 5D, I get the correct value, EXCEPT for the leading 00, which does not have two 5Ds in front of it:
5D5D5555 5D 00 00 5D 5D 65 65 5D 5D 5F 5F 5D 0000FFFF 00 AA AA 00 00 20 20 00 00 08 08 00 5D5D 00 5D 02 5D 03 5D 04 5D 05 5D 06 5D 07 5D 0000 AA 00 B2 00 B6 00 BA 00 BE 00 A2 00 A6 00
0x5D seems to change how bytes afterwards are interpreted, particularly a double 5D. So next I need to determine if there are sets of bytes which change interpretation, if a single byte can, and so on.
-
RAMing the ROM
12/02/2018 at 19:03 • 0 commentsSo, once I finally sat down to debug with the ST-LINK, I found myself doing some new things. First off, I was debugging a program I had no source for, secondly, I was debugging without PlatformIO taking care of the details of setting up the debugger and launching.
GDB requires a debug server, st-util.
Once ST-util was running, I could then launch gdb and attache to the remote target on port 4242.
Which left me looking at nothing, because, again, memory was protected. Switching GDB to register layout showed me nothing, which broke one of my primary plans:
SWD isn't supposed to prevent reading registers, and my plan had been to hack the firmware to set the stack pointer to a known value, then reset and wait for a watchpoint to hit. The resulting registers would have MY values.
And this did not work.
So I had to come up with a plan B.
Plan B started with me dumping the RAM using:
dump memory binary ~/Documents/lerdge/ram2.bin 0x20000000 0x20020000
In the hex editor, this RAM file wound up giving me some fantastic results...like, for instance:
0123456789abcdefABCDEF
0123456789abcdefABCDEF
Very nice.
#%')+-/1357:<>@BDFHKMOQSUWY[]_acegikmoqsuwz|~’±²³´µ¶·¸¹º»¼½¾¿ÀÁÃÄÅÆÇÈÉÊ0123456789:;<=>?@ABCDEFGHIJKLMNOQRSTUVWXYZ[\^_p!q!r!s!t!u!v!w!x!y!z!{!|!}!~!!AÿBÿCÿDÿEÿFÿGÿHÿIÿJÿKÿLÿMÿNÿOÿPÿQÿRÿSÿTÿUÿVÿWÿXÿYÿZÿABCDEFGHIJKLMNOPQRSTUVWXYZ!
And this...this led me to some ideas.
See, I'd made some custom startup code for the Malyan M200, and it got me to thinking.
If we look at ST's startup code for the STM32F407x, we see:
CopyDataInit: ldr r3, =_sidata ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4 LoopCopyDataInit: ldr r0, =_sdata ldr r3, =_edata adds r2, r0, r1 cmp r2, r3 bcc CopyDataInit ldr r2, =_sbss b LoopFillZerobss
What this means in english is that to initialize globals/statics, there's a single loop that copies data from ROM into RAM, and one of these is an ASCII table lookup.
So this table and those bytes inside could form a clue. And most interesting is that the bytes between the characters also form a clue. Some of them have zeros...and zeros we know the byte for. So I wrote a python script that searched the encrypted ROM for byte sequences exactly the length of each character set that alternated with a fixed value.
With a few false starts, I identified some theoretical tables, and proceeded to write a relatively simple lookup that mapped the ASCII bytes, ran it through. MOST of what I saw was garbage. But, buried in the muck was this:
@ûç&RAMNANDW5QXXSSDUUSBUSB3
What followed was a series of trial and error. on a hunch, I simply mapped all bytes in a given range to "s." Remember, we know the value for " " = it's 0x65, and we are 100% certain we know that value, thanks to our friend the stack pointer.
The result of this run gave me gems like "SD ssss IO Essss." You know what that looks like to me? "SD card IO Error."
Which, with a bit more futzing, is exactly what it turned out to be.
Now I was confident about how the encryption worked. I even had ~60 values I'd worked out with confidence, but overall, there wasn't a clear pattern. I was certain that for some ranges, the encryption worked by starting from a seed value and incrementing up by four bits, nibble swapped.
But that didn't work across the board and conflicted with well known, 100% confidence bytes like 0x65.
So I came up with a new plan.
-
Foundational Work
12/02/2018 at 02:23 • 0 commentsSo to start with, I grabbed the Lerdge firmware from their website, and unpacked it, then read up about the update process. Like most firmwares, there's an update from the UI and a force update mode in which the bootloader forcibly updates the firmware.
Step one was to test force update and make sure I could make the firmware update.
Step two? Open a hex editor and zero out some random bytes in the firmware, then force the update.
What we're looking for here is that the bootloader refuses to update because it's checking a CRC of the firmware.
Nope, it updated just fine, which puts us in business. At this point, I asked for help from a friend with amazing soldering skills, and he soldered leads to the SWD and reset pins, so I could hook up an ST-LINK to the board.
But...
Read protection was set. Not surprising at all - it's the least effort possible and it's completely expected, so no worries. I can't dump the bootloader without being able to load a trojan, and to load a trojan, I need to know how the ROM works.
So...to the hex editor.
These project logs will likely be heavy on hex dumps. Get used to it.
Here we have the first few bytes of the firmware:
8B7D5D65 A6919D5F 2BCB9D5F ABCB9D5F 2CCB9D5F ACCB9D5F 2DCB9D5F 5D5D5D5D 5D5D5D5D 5D5D5D5D 5D5D5D5D B5CB9D5F 36CB9D5F 5D5D5D5D B6CB9D5F 37CB9D5F AD919D5F AE919D5F AF919D5F A8919D5F A9919D5F AA919D5F AB919D5F AC919D5F
Wow, that looks like a mess. Just to compare, let's look at an unencrypted one:
D8FF0020 FD020708 C9020708 CD020708 D1020708 D3020708 D5020708 00000000 00000000 00000000 00000000 D7020708 D9020708 00000000 DB020708 15770308 6D030708 71030708 75030708 79030708 7D030708 81030708 49000708 91760308
First off, it helps to know that this is an ARM processor, and the first few bytes are most likely the NVIC table. The NVIC table has a very particular layout, and in particular, it requires a few fields to be zero at the beginning.
Right where we have zeros in the unecnrypted one, we have 0x5D bytes in the lerdge one.
So the first thing to do is break out an xor tool. I ran one and not surprisingly, it calculated that the most likely XOR key would be 0x5D.
It does so based on zero being the most likely byte, and we can see that 5D XOR'd with 00 would yield it. But the xored result was garbage. We can be fairly certain it's not a nibble substitution, either. Why?
Because we know some particulars of STM32Fx hardware. First, let's start with the last byte to the right in that first row. That's an interrupt vector, and it *must* be 0x08 on this hardware.
Similarly, the first word? That one is the stack pointer and it MUST end in 0x20. If it's a nibble swap, we'd see the same nibble values in the 0x08 and 0x20.
The other thing we can be fairly sure of is that it's not a true encryption. The reason has to come from scanning the OTHER end of the firmware. Many toolchains put things together in a very similar fashion and we see zeros laid out in pretty much exactly the right places--if 5D is a zero.
So we're probably looking at a byte substitution, not a rolling encryption, and we can tell three byte values for sure:
0x5F must be 0x80.
0x65 must be 0x20.
0x5D must be 0x00.
And everything else is unknown. So we start.