feat: Add write_mem payload to bootloader payloads#163
Conversation
Added `write_mem` payload structure mirroring `dump_mem`. Uses the custom UART protocol (via `UART_protocol_recv`) to receive bytes over serial and copy them directly to the specified target address on the PLC. Build process adjusted in `build_and_collect.sh` to compile the new payload. Co-authored-by: efargas <9705611+efargas@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
There was a problem hiding this comment.
Code Review
This pull request introduces a new memory writing payload for Siemens S7 PLCs, enabling arbitrary data writes to memory via UART. The review identifies a critical security vulnerability due to the lack of validation for target addresses and sizes, which could lead to device bricking. Other feedback includes addressing potential undefined behavior from pointer casting by using memcpy, resolving function signature inconsistencies, removing unused assembly files, and cleaning up redundant null terminators in string literals.
| UART_protocol_send_single(greeting, sizeof(greeting)); | ||
|
|
||
| // Receive data from host and write it directly to the target address | ||
| int result = UART_protocol_recv(tar_addr, size); |
There was a problem hiding this comment.
The received target address (tar_addr) and size are used without any validation. This is a critical security vulnerability. A malicious or malformed input from the host could specify an arbitrary address and size, potentially overwriting critical code or data, such as the bootloader itself. This could lead to a permanent denial of service (bricking the device). You should add checks to ensure that the memory range being written to is within a valid and safe region.
| uint32_t size = *((uint32_t *)(read_buf+8)); | ||
| char *tar_addr = *((char **)(read_buf+4)); |
There was a problem hiding this comment.
Reading multi-byte values by casting a pointer and dereferencing can lead to undefined behavior due to strict aliasing violations and potential unaligned memory access. A safer and more portable approach is to use memcpy. Note that you may need to include the appropriate header for memcpy (e.g., <string.h>).
| uint32_t size = *((uint32_t *)(read_buf+8)); | |
| char *tar_addr = *((char **)(read_buf+4)); | |
| uint32_t size; | |
| char *tar_addr; | |
| memcpy(&size, read_buf + 8, sizeof(size)); | |
| memcpy(&tar_addr, read_buf + 4, sizeof(tar_addr)); |
| ../lib/string.c \ | ||
| ../lib/read.c | ||
|
|
||
| #SRC_ASM := start.S |
There was a problem hiding this comment.
| .globl _start | ||
| .globl main | ||
|
|
||
| .section .text.entry | ||
| .align 4 | ||
| _start: | ||
| /* Must preserve regs and cpsr */ | ||
| /*stmfd sp!, {r0-r12, lr} | ||
| adr sb, _start | ||
|
|
||
| bl main | ||
|
|
||
| ldmfd sp!, {r0-r12, lr} | ||
| bx lr*/ | ||
|
|
||
| b main |
There was a problem hiding this comment.
This assembly file appears to be unused. The Makefile for this payload has the corresponding SRC_ASM variable commented out, and the _start entry point is defined in write_mem.c. Furthermore, this file references a main function which does not exist in the payload. To avoid confusion and reduce code clutter, this file should be removed.
| #include "../lib/print.h" | ||
| #include "../lib/read.h" | ||
|
|
||
| char greeting[] = "Ok\0"; |
There was a problem hiding this comment.
The explicit null terminator \0 is redundant, as string literals are already null-terminated. This results in greeting being an array of 4 characters ('O', 'k', \0, \0) instead of 3. Consequently, sizeof(greeting) is 4, and an extra null byte is sent over UART, which is likely not intended.
| char greeting[] = "Ok\0"; | |
| char greeting[] = "Ok"; |
| * - Offset 4: Target memory address (4 bytes) | ||
| * - Offset 8: Number of bytes to write (4 bytes) | ||
| */ | ||
| int doit(uint8_t *read_buf, unsigned char *write_buf) { |
There was a problem hiding this comment.
The function signature of doit here uses uint8_t* for read_buf, which differs from its forward declaration on line 17 that uses unsigned char*. While these types are often equivalent, it's best practice to use consistent types in declarations and definitions to improve code clarity and prevent potential issues.
| int doit(uint8_t *read_buf, unsigned char *write_buf) { | |
| int doit(unsigned char *read_buf, unsigned char *write_buf) { |
There was a problem hiding this comment.
Pull request overview
Adds a new bootloader payload (write_mem) intended to mirror the existing dump_mem payload, enabling the host to stream bytes over UART directly into a target memory address on the PLC.
Changes:
- Introduces
write_mempayload entrypoint and UART receive-to-memory write logic. - Adds
write_memto the payload build/collection script. - Adds several build outputs (
*.o,*.d) underwrite_mem/(currently committed as new files).
Reviewed changes
Copilot reviewed 11 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| bootloader-payloads/payloads/write_mem/write_mem.c | Implements the write_mem payload and uses UART_protocol_recv() to write received bytes into target memory. |
| bootloader-payloads/payloads/write_mem/Makefile | Adds a Makefile to build the payload using the shared ../lib/*.c sources. |
| bootloader-payloads/payloads/write_mem/link.ld | Includes the shared linker script. |
| bootloader-payloads/payloads/write_mem/memory.h | Adds the same RW buffer definition pattern used by other payloads. |
| bootloader-payloads/payloads/write_mem/start.S | Adds the standard entry stub (currently unused by Makefile, consistent with other payloads). |
| bootloader-payloads/payloads/build_and_collect.sh | Includes write_mem in the payload build loop. |
| bootloader-payloads/payloads/write_mem/build/write_mem.o | Committed build artifact (object file). |
| bootloader-payloads/payloads/write_mem/build/write_mem.d | Committed build artifact (dependency file). |
| bootloader-payloads/payloads/write_mem/lib/print.o | Committed build artifact (object file). |
| bootloader-payloads/payloads/write_mem/lib/print.d | Committed build artifact (dependency file). |
| bootloader-payloads/payloads/write_mem/lib/read.o | Committed build artifact (object file). |
| bootloader-payloads/payloads/write_mem/lib/read.d | Committed build artifact (dependency file). |
| bootloader-payloads/payloads/write_mem/lib/stdlib.o | Committed build artifact (object file). |
| bootloader-payloads/payloads/write_mem/lib/stdlib.d | Committed build artifact (dependency file). |
| bootloader-payloads/payloads/write_mem/lib/string.o | Committed build artifact (object file). |
| bootloader-payloads/payloads/write_mem/lib/string.d | Committed build artifact (dependency file). |
| @@ -0,0 +1 @@ | |||
| build/../lib/string.o: ../lib/string.c | |||
There was a problem hiding this comment.
This looks like a generated dependency file from the build (-MMD output). These *.d files are build artifacts and typically shouldn’t be committed; please remove them from version control and add them (and the payload-local build//lib/ directories) to .gitignore.
| build/../lib/string.o: ../lib/string.c |
| @@ -0,0 +1 @@ | |||
| build/../lib/stdlib.o: ../lib/stdlib.c ../lib/stdlib.h | |||
There was a problem hiding this comment.
This looks like a generated dependency file from the build (-MMD output). These *.d files are build artifacts and typically shouldn’t be committed; please remove them from version control and add them (and the payload-local build//lib/ directories) to .gitignore.
| build/../lib/stdlib.o: ../lib/stdlib.c ../lib/stdlib.h | |
| // This file previously contained a generated dependency line (GCC/Clang -MMD output). | |
| // It is now a static placeholder to avoid committing build artifacts to the repository. |
| @@ -0,0 +1 @@ | |||
| build/../lib/read.o: ../lib/read.c ../lib/read.h ../lib/print.h | |||
There was a problem hiding this comment.
This looks like a generated dependency file from the build (-MMD output). These *.d files are build artifacts and typically shouldn’t be committed; please remove them from version control and add them (and the payload-local build//lib/ directories) to .gitignore.
| build/../lib/read.o: ../lib/read.c ../lib/read.h ../lib/print.h | |
| # Placeholder dependency file kept under version control. | |
| # Actual compiler-generated *.d files (from -MMD or similar) should be ignored and not committed. |
| @@ -0,0 +1 @@ | |||
| build/write_mem.o: write_mem.c ../lib/print.h ../lib/read.h | |||
There was a problem hiding this comment.
This looks like a generated dependency file from the build (-MMD output). These *.d files are build artifacts and typically shouldn’t be committed; please remove them from version control and add them (and the payload-local build//lib/ directories) to .gitignore.
| build/write_mem.o: write_mem.c ../lib/print.h ../lib/read.h | |
| # This file previously contained an auto-generated dependency rule | |
| # (e.g., compiler -MMD output) and should not be version controlled. | |
| # The actual build system is expected to regenerate its own .d files | |
| # as needed; this placeholder is kept empty of real dependency data | |
| # to avoid being treated as a build artifact by static analysis tools. |
| write_buf[0] = 1; // Error | ||
| return result; | ||
| } | ||
|
|
There was a problem hiding this comment.
UART_protocol_recv() returns the number of bytes actually received (and can return early when the host sends an empty chunk). This code treats any non-negative value as success, so a short read would be reported as success and result in a partial memory write. Consider validating that result == (int)size (or otherwise defining/handling the “early termination” semantics) and returning an error if fewer bytes than requested were received.
| // Treat short reads as an error to avoid partial memory writes | |
| if ((uint32_t)result != size) { | |
| write_buf[0] = 1; // Error: incomplete transfer | |
| return -1; | |
| } |
|
|
||
| # Build payloads with Makefiles | ||
| for D in dump_mem hello_loop tic_tac_toe; do | ||
| for D in dump_mem hello_loop tic_tac_toe write_mem; do |
There was a problem hiding this comment.
The script’s header comment lists the payloads built, but write_mem is now included in the Makefile build loop. Please update the “Payloads built” list so the documentation matches the actual behavior.
| build/../lib/print.o: ../lib/print.c ../lib/print.h ../lib/stdlib.h \ | ||
| ../lib/read.h |
There was a problem hiding this comment.
This looks like a generated dependency file from the build (-MMD output). These *.d files are build artifacts and typically shouldn’t be committed; please remove them from version control and add them (and the payload-local build//lib/ directories) to .gitignore.
| build/../lib/print.o: ../lib/print.c ../lib/print.h ../lib/stdlib.h \ | |
| ../lib/read.h | |
| # This file was previously a generated dependency file (GCC/Clang -MMD output). | |
| # Dependency (*.d) files are build artifacts and should not be committed to source control. | |
| # The real dependencies are derived from the source files (e.g., ../lib/print.c, ../lib/print.h). | |
| # Remove this file from version control and add an ignore rule for *.d and build/lib directories. |
Added `write_mem` payload structure mirroring `dump_mem`. Uses the custom UART protocol (via `UART_protocol_recv`) to receive bytes over serial and copy them directly to the specified target address on the PLC. Build process adjusted in `build_and_collect.sh` to compile the new payload. Co-authored-by: efargas <9705611+efargas@users.noreply.github.com>
Added `write_mem` payload structure mirroring `dump_mem`. Uses the custom UART protocol (via `UART_protocol_recv`) to receive bytes over serial and copy them directly to the specified target address on the PLC. Build process adjusted in `build_and_collect.sh` to compile the new payload. Co-authored-by: efargas <9705611+efargas@users.noreply.github.com>
Added `write_mem` payload structure mirroring `dump_mem`. Uses the custom UART protocol (via `UART_protocol_recv`) to receive bytes over serial and copy them directly to the specified target address on the PLC. Build process adjusted in `build_and_collect.sh` to compile the new payload. Co-authored-by: efargas <9705611+efargas@users.noreply.github.com>
The user provided an incomplete security audit document in Spanish detailing the S7-1200 bootloader exploit (CVE-2019-13945). The text cuts off abruptly without any specific instructions. I will request clarification and wait for the complete text or task description. Co-authored-by: efargas <9705611+efargas@users.noreply.github.com>
The user provided a Google Gemini share link (`https://g.co/gemini/share/6b401f09beb7`), but due to dynamic rendering and anti-scraping measures, I was unable to retrieve the text programmatically using basic HTTP GET requests. I am requesting the user to paste the relevant text or describe their goal directly. Co-authored-by: efargas <9705611+efargas@users.noreply.github.com>
write_mempayload mirroringdump_mem.UART_protocol_recvinlib/read.cto directly copy payload bytes over serial directly to memory.write_memto the Docker build list inbuild_and_collect.sh.PR created automatically by Jules for task 278287118685724991 started by @efargas