Brew It Yourself: Hacking a Coffee Machine for Custom Timer Settings


Recently, I invested in a coffee machine that, while almost perfect, shut off automatically after 30 minutes—a serious problem for someone who spaces out their caffeine fixes over four hours. The machine did offer options to extend this time up to three hours, but even this was short of my ideal five-hour interval. Determined to bend the machine to my will, I embarked on a technical odyssey filled with trial and error, a sea of uncertainties.

In this article, I'll skip the detours and dead ends to focus on the crucial steps that led to my ultimate victory over the auto-off timer. Join me on this journey of hardware and firmware reverse engineering where we push the limits of what our gadgets can do.

Ethical Considerations

The techniques and processes described in this article are shared for educational purposes only. Readers are advised to proceed with caution and be aware of the legal implications associated with hacking and reverse engineering. Modifying firmware, altering device functionalities, or accessing hardware configurations can violate terms of service, void warranties, and potentially breach local laws depending on your jurisdiction.

Always ensure that any modifications or explorations of technology are conducted ethically and in compliance with all applicable laws and regulations. The intent of sharing this information is to promote understanding and innovation within the bounds of ethical conduct and legal frameworks.

Inspecting the Machine

Embarking on my journey to extend the coffee machine's auto-off timer, the first step was to boldly go where no warranty would survive— cracking open the case. Thankfully, the machine's designers didn't bury its heart under layers of plastic; the main circuit board was surprisingly accessible.

As I delved into the heart of the coffee machine, the first two elements that immediately drew my attention were the main IC, and a series of connectors on the side of the board. These connectors seemed to hint at a potential debug or programming port, possibly for firmware flashing or diagnostics, setting the stage for deeper exploration.


 
After identifying the main IC as a PIC18LF2520, I quickly located its datasheet online. Armed with this essential document, I gained a detailed understanding of the IC's pinout, which was crucial for the next steps in my investigation.

Using my multimeter, I methodically traced the connections from the side connectors on the board to see if they corresponded to any specific pins on the IC. This careful probing paid off—I discovered that these connectors were indeed directly tied to the IC pins labeled VCC, GND, MCLR, PGC, PGD, TX, and RX. This finding was a significant breakthrough as it confirmed the connectors’ role in debugging and programming the microcontroller, providing a clear pathway for firmware interaction.

Extracting the Firmware

To interact with the firmware of the PIC18 microcontroller, I needed to understand the flashing method used by the PIC18 family. My research revealed that these microcontrollers utilize the In-Circuit Serial Programming (ICSP) protocol. This was fortunate because it meant that I could use a PicKit programmer, a tool specifically designed for this protocol.

The ICSP protocol employs five essential lines: 
  • VCC and GND for power
  • MCLR for putting the IC into programming mode
  • PGC as the programming clock
  • PGD for data transmission. 
Conveniently, all these pins were accessible through the side connectors on the board.

Before I could start programming, I removed the main board along with the front control panel board from the machine to ensure easier access and to avoid damaging other components. Next, I soldered additional pins to these side connectors to establish a reliable connection.



After successfully interfacing with the microcontroller using the PicKit, I used the "pk3cmd.exe" tool to dump the firmware. This tool saved the firmware in Intel Hex format. While the Intel Hex format is excellent for verification and programming because it includes address information and checksums, it's not optimal for reverse engineering purposes.

To prepare the firmware for detailed analysis, I converted it to a binary format using the objcopy tool from GNU Binary Utilities. This step is essential as binary format aligns better with the tools used for reverse engineering, such as IDA Pro, by providing a straightforward, contiguous block of data. The command I used was:

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

This conversion strips away the address and checksum data, producing a raw binary file that reflects the actual data layout in the device's memory.

First Reverse Engineering Attempt

With the firmware successfully converted to binary format, I opened it in IDA Pro, ready to dive into the analysis. However, I quickly realized that my familiarity with x86 and ARM architectures didn't prepare me for the nuances of PIC18 assembly language. This realization marked the beginning of a steep learning curve.

As I delved into the PIC18 microcontroller's architecture, several key characteristics stood out:
  • Harvard Architecture: The PIC18 uses separate buses for code and data, a typical feature of this architecture.
  • Banked Memory Organization: The memory is divided into banks, which facilitates modular access but adds complexity to data management.
  • Special Function Registers (SFRs): Located in the last memory bank, these registers are crucial for configuring and controlling the IC’s operations.
To make sense of the firmware, I had to familiarize myself with fundamental PIC18 instructions such as movf (move), clrf (clear), bsf (bit set), and btfss (bit test skip if set). I also needed to understand the role of the 'work' register and reset vectors, both pivotal for manipulating device behavior.

Fortunately, the datasheet for the PIC18LF2520 was comprehensive, providing all the necessary details to bridge my knowledge gaps and equip me with the tools to decode the firmware's functionality.

Second Reverse Engineering Attempt

My initial optimism about finding useful debug strings in the firmware quickly faded when I discovered the absence of any such strings. This lack of straightforward indicators meant I couldn't easily discern the functionality of various code segments through simple text hints. It was time to delve into more rigorous reverse engineering tactics.

In such scenarios, you generally have two primary approaches: tracing the code from the entry point or focusing on specific I/O interactions to unravel the associated functionality. I began by following the code from the entry point, sifting through the initial sequences of memory and hardware initialization. This path led me through a maze of extensively used memory addresses and a plethora of "magic numbers," which proved challenging to interpret without a more profound system understanding.

Realizing the complexity, I pivoted to a more targeted approach—concentrating on I/O interactions. This method aimed to isolate and analyze the segments of code responsible for setting the auto-off timer, hypothesizing that such interactions would reveal the crucial mechanisms I needed to manipulate.

Reversing the I/O board

To decode the intricate I/O interactions of the coffee machine, I turned my focus to the front panel board, which houses the LEDs and buttons integral to user input. This board interfaces with the main board via an 8-wire cable. My goal was to understand how the microcontroller reads the switches and controls the LEDs to accurately map these actions within the firmware.


The front panel board is equipped with 7 push buttons, 9 LEDs, a potentiometer, a shift register, and a multiplexer. Utilizing a microscope and a multimeter, I meticulously traced the wiring from this board to the main IC, discovering that only 6 of the 8 wires were in use—two for power and four connected to the IC's pins RC3, RC4, RC5, and RA1.



The schematic I constructed unveiled the clever design behind the I/O interactions. The system employs a shared line strategy between the multiplexer and shift register, organizing operations into two distinct phases for managing the LED display and button readings:
  • Phase 1: (QP6 high): Controls and reads LEDs 1-4 and buttons 1-4.
  • Phase 2: (QP7 high): Manages LEDs 5-9 and buttons 5-7.
During each phase, the IC adjusts the outputs on RC3 and RC4 to cycle through all four possible values. This enables the multiplexer to route each button press associated with the current phase to RA1. This method ensures that the shift register remains unaffected during button reads because the clock line is not engaged, thereby keeping the LED and phase selection stable.

Unraveling the LED Control Mechanism

Continuing my analysis of the I/O interactions, I focused on how the microcontroller utilizes the RC3, RC4, and RC5 pins. I identified a critical function that manipulates these pins to control the shift register. Specifically, the function shifts out the value stored in byte_RAM_37 on RC3 while simultaneously sending pulses through RC4. This action transfers the specified value directly into the shift register.



To understand the source of  byte_RAM_37, I traced where it is written. It receives its values from two different locations:
  • byte_RAM_15 after being OR-ed with 0x40
  • byte_RAM_14 after being OR-ed with 0x80


These operations are controlled by the value in byte_RAM_20, which alternates, dictating the two phases. The OR operations with 0x40 and 0x80 set specific bits corresponding to QP6 and QP7. These bits are crucial as they control the appropriate LEDs and switches for each phase of the operation.

Upon delving deeper into the functionality of byte_RAM_14 and byte_RAM_15, I discovered that these were essentially duplicates, mirroring the values stored in byte_RAM_F6 and byte_RAM_F7.


By examining further up the function, I discovered how byte_RAM_F6 and byte_RAM_F7 are filled:


Specific bits from byte_RAM_2A are transferred directly into corresponding positions within byte_RAM_F6 and byte_RAM_F7.

The firmware's mechanism for controlling the LEDs unfolds in a carefully orchestrated series of data manipulations:
  • Initial State Storage: The current state of all LEDs is held in byte_RAM_2A.
  • Phase State Allocation: byte_RAM_F6 and byte_RAM_F7 store the state for each phase (LEDs 1-4 and LEDs 5-9, respectively). These registers are populated based on the data from byte_RAM_2A.
  • Register Duplication: byte_RAM_14 and byte_RAM_15 serve as direct duplicates of byte_RAM_F6 and byte_RAM_F7, maintaining a backup of the phase states.
  • Phase Marking and Storage: The data in byte_RAM_14 and byte_RAM_15 is combined (OR-ed) with 0x40 and 0x80 to indicate the respective phase before being stored in byte_RAM_37.
  • Shift Register Transfer: Finally, the data from byte_RAM_37 is shifted out to the shift register, effectively updating the LED display according to the phase-specific states.

Knowing the logic, I was able to do the mapping between the bits in byte_RAM_2A and their corresponding LEDs, I created an enumeration for these bits, replacing obscure magic numbers with descriptive names. This enumeration simplifies the reverse engineering process by clarifying the code’s functionality, making modifications easier and more intuitive.


Employing these enumerated values not only improves our understanding of the firmware's functionality but also eases the reverse engineering process, allowing for a clearer grasp of the underlying control logic.

Uncovering Auto-Off Timer Settings

Before delving deeper, it's important to outline the feature that allows changing the auto-off time. The machine offers a special button sequence to access a menu where users can select from five pre-programmed intervals: 15 minutes, 30 minutes, 1 hour, 2 hours, and 3 hours. Each interval is represented by a specific light code, as detailed in the user manual.




Equipped with knowledge about the internal LED states and their mapping, I searched for the code segment responsible for displaying the current auto-off setting. I found a function that adjusts the LEDs based on the value in byte_RAM_588. The correlation between these values and the LED indicators aligns perfectly with the descriptions in the user manual.





Further exploration revealed that byte_RAM_588 is initialized from up into byte_RAM_CA and diving deeper, I followed byte_RAM_CA to a snippet where it seemed to fetch data from program memory:


Essentially, the code retrieves two bytes from addresses calculated using the formula: 
byte_RAM_CA * 2 + 0x26 + 0x600. This calculation means that, depending on the auto-off level, the memory address read by the code ranges from 0x626 to 0x630. Let's now inspect the code at these addresses. Since the data is read in two-byte increments, these bytes can be cast into words (two-byte integers):

The values retrieved are 90, 180, 360, 720, and 1080. These correspond to the pre-set times of 15 minutes, 30 minutes, 1 hour, 2 hours, and 3 hours, respectively. Interestingly, the data isn't stored in seconds, but in ten-second increments. For example, a value of 90 translates to 90 * 10 seconds = 900 seconds = 15 minutes. This understanding confirms that the firmware maps these values to specific timing settings, marking a significant breakthrough in decoding the machine's functionality.

Modifying the Timer Settings

With the correct memory location identified, my next step was to adjust the auto-off time to my desired five hours. First, I converted five hours into seconds (5 hours = 18,000 seconds), and then divided by 10 to match the firmware's unit of measurement (1,800). I replaced the existing value (0x438, which corresponds to 1,080 or 3 hours) with the new value, 0x708, reflecting the 1,800 ten-second increments. This change was made directly in the Intel HEX file I had previously extracted.

To ensure the integrity of the modified HEX file, I needed to update the checksum, which is the last byte of each line. This checksum is an 8-bit two's complement of the summation of each byte in that record, starting from the Record Length byte to the last byte in the Data/Argument Field. Here's how I recalculated it using Python:

s = "100000000CEF00F0FFFFFFFFD8CFFFF5FACFFEF5"
checksum = hex(0x100 - sum(int(s[x*2:x*2+2], 16) for x in range(len(s)//2)) % 0x100)

With the checksum updated and the HEX file ready, I used the flash tool to upload the modified firmware back into the machine. Now, the machine is set to wait five hours before shutting off, allowing me to enjoy my second coffee made four hours later without any manual reactivation.

Conclusion

In this exploration of firmware reverse engineering, we've not just adapted a coffee machine to better suit personal preferences but also showcased the potency of technical perseverance. We focused on understanding and modifying existing settings—specifically, extending the auto-off timer from its default limits.

This project embodies the essence of hacking: leveraging a deep understanding of technology to modify it in practical ways. By methodically tracing the timer settings and adjusting their values, the machine now perfectly aligns with my daily routine. This demonstrates that sometimes, the key to making technology meet your needs is not in redesigning but in reconfiguring what is already there.

Whether you're a seasoned hacker or just starting out, I hope this account inspires you to look under the hood of your own devices. With a bit of curiosity and technical insight, you might find that you, too, can bend a machine to your will.

Comments