Embedded Hacking
Embedded Hacking
Embedded Hacking
Kevin Thomas
Copyright © 2024 My Techno Talent
1
Forward
I remember when I started learning programming to which my first
language was 6502 Assembler. It was to program a Commodore 64 and
right from the beginning I learned the lowest level development
possible.
We will take our time and learn the basics of C utilizing a Pico
microcontroller.
Raspberry Pi Pico
https://www.amazon.com/Raspberry-Pi-Pico-RP2040-microcontroller/dp/
B092S2KCV2
Breadboard Kit
https://www.amazon.com/Breadboards-Solderless-BreadboardDistribution-
Connecting/dp/B07DL13RZH
2
6x6x5mm Momentary Tactile Tact Push Button Switches
https://www.amazon.com/DAOKI-Miniature-Momentary-Tactile-Quality/dp/
B01CGMP9GY
NOTE: The item links may NOT be available but the descriptions allow
you to shop on any online or physical store of your choosing.
Let’s begin...
3
Table Of Contents
Chapter 1: hello, world
Chapter 2: Debugging hello, world
Chapter 3: Hacking hello, world
Chapter 4: Embedded System Analysis
Chapter 5: Intro To Variables
Chapter 6: Debugging Intro To Variables
Chapter 7: Hacking Intro To Variables
Chapter 8: Uninitialized Variables
Chapter 9: Debugging Uninitialized Variables
Chapter 10: Hacking Uninitialized Variables
Chapter 11: Integer Data Type
Chapter 12: Debugging Integer Data Type
Chapter 13: Hacking Integer Data Type
Chapter 14: Floating-Point Data Type
Chapter 15: Debugging Floating-Point Data Type
Chapter 16: Hacking Floating-Point Data Type
Chapter 17: Double Floating-Point Data Type
Chapter 18: Debugging Double Floating-Point Data Type
Chapter 19: Hacking Double Floating-Point Data Type
Chapter 20: Static Variables
Chapter 21: Debugging Static Variables
Chapter 22: Hacking Static Variables
Chapter 23: Constants
Chapter 24: Debugging Constants
Chapter 25: Hacking Constants
Chapter 26: Operators
Chapter 27: Debugging Operators
Chapter 28: Hacking Operators
Chapter 29: Static Conditionals
Chapter 30: Debugging Static Conditionals
Chapter 31: Hacking Static Conditionals
Chapter 32: Dynamic Conditionals
Chapter 33: Debugging Dynamic Conditionals
Chapter 34: Hacking Dynamic Conditionals
Chapter 35: Functions, w/o Param, w/o Return
Chapter 36: Debugging Functions, w/o Param, w/o Return
Chapter 37: Hacking Functions, w/o Param, w/o Return
4
Chapter 1: hello, world
We begin our journey building the traditional hello, world example in
Embedded C.
To setup our environment we will follow the details in the link below
which covers all operating systems.
https://www.raspberrypi.com/documentation/microcontrollers/raspberry-
pi-pico.html#technical-specification
The next thing we will setup is the Raspberry Pi Pico Debug Probe as
there are detailed instructions below as well to get started.
https://www.raspberrypi.com/documentation/microcontrollers/debug-
probe.html#about-the-debug-probe
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
Open VS Code and click File then Open Folder … then click on the
Embedded-Hacking folder and then select 0x0001_hello-world.
It may ask you for an active kit of which you will choose Pico ARM
GCC.
5
Now we are ready to flash our code onto the Pico.
Press and hold the push button we attached to the breadboard while
pressing the white BOOSEL button on the Pico then release the white
BOOTSEL button on the Pico and then release the push button we
attached to the breadboard.
drag and drop 0x0001_hello-world.uf2 file from the build directory to the RPI-RP2
https://www.putty.org
If you are on Windows you can open up the Device Manager and look for
the COM port that will be used to connect PuTTY to. There are at
minimum two ports one for the Pico UART and the other for the Pico
Debug Probe. Try both and one of them will be UART that we are
looking for.
6
Next step is to run PuTTY.
You want to put in your COM port, in my case COM6, and click the Open
button.
If you are on MAC or Linux you can follow the instructions in the
below link to use minicom.
https://www.raspberrypi.com/documentation/microcontrollers/debug-
probe.html#serial-connections
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
stdio_init_all();
while (true)
printf("hello, world\r\n");
}
7
Let’s break down this code.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void)
The above line declares the main() function, which is the entry point
for all C and Python programs.
stdio_init_all();
while (true)
printf("hello, world\r\n");
8
In our next lesson we will debug hello, world using the ARM embedded
GDB with OpenOCD to which we will actually connect LIVE to our
running Pico!
9
Chapter 2: Debugging hello, world
Today we debug!
Before we get started, we are going DEEP and I mean DEEP! Please do
not get discouraged as I will take you through literally every single
step of the binary but we need to start small.
Assembler is not natural to everyone and I have another FREE book and
primer on Embedded Assembler below if you feel you need a good
primer. PLEASE take the time to read this book if you are new to
this so that you can get the full benefit of this book.
https://github.com/mytechnotalent/Embedded-Assembler
cd ..
.\openocd.ps1
10
If you are on MAC or Linux, simply run the below command.
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0001_hello-world.bin
11
We need to touch base on what XIP is within the RP2040 MCU or
microcontroller. This is the actual chip that powers the Pico.
Our goal is to find the main function within our binary to reverse
engineer it. Before our main function there will be a large amount
of setup code to include the vector table which will handle hardware
interrupts and exceptions within our firmware which will be at the
address close to the beginning of 0x10000000.
The stack is a region of memory used for managing function calls and
local variables. It grows and shrinks automatically as functions
like main are called and return. Each time a function is called, a
stack frame is created to store local variables and return addresses.
The stack pointer (SP) register keeps track of the current position
in the stack.
12
Push: Adding data to the stack (e.g., pushing function parameters).
Pop: Removing data from the stack (e.g., popping values after a
function call).
If the stack grows beyond its allocated size, it can lead to a stack
overflow, causing unpredictable behavior or crashes. In contrast,
the heap is a region of memory used for dynamic memory allocation.
It is managed by the programmer, and memory must be explicitly
allocated and deallocated.
13
These are general-purpose registers used for temporary storage of
data during program execution.
The APSR reflects the status of the ALU (Arithmetic Logic Unit) after
arithmetic operations.
14
Bit 0 is the privilege level bit, determining whether the processor
is in privileged or unprivileged mode.
Function Calls:
APSR flags are used for conditional branching and checking the
outcome of arithmetic/logic operations.
Stack Usage:
The stack (SP) is used for managing function calls and local
variables.
Let’s re-examine our main function and we will see an arrow pointing
to the instruction we are about to execute. Keep in mind, we have
NOT executed it yet.
(gdb) x/5i 0x10000304
=> 0x10000304: push {r4, lr}
0x10000306: bl 0x1000406c
0x1000030a: ldr r0, [pc, #8] ; (0x10000314)
0x1000030c: bl 0x10003ff4
15
0x10000310: b.n 0x1000030a
We are about to start off pushing the R4 register and the LR register
to the stack.
Keep in mind, the base pointer (BP) is not a register in the RP2040
ARM Cortex-M0+ architecture, and therefore, it is not present as a
dedicated register like in some other architectures such as x86.
Instead, the ARM Cortex-M0+ architecture relies on the use of the
stack pointer (SP) and the link register (LR) for managing the stack
during function calls.
In the context of the RP2040 and ARM Cortex-M0+, you would primarily
rely on the stack pointer (SP) and link register (LR) for managing
the stack and tracking return addresses during function calls. The
base pointer concept is not part of the standard conventions for this
architecture.
We have not executed our first main Assembler function yet so let’s
first examine what our stack contains.
(gdb) x/10x $sp
0x20042000: 0x00000000 0x00000000 0x00000000 0x00000000
0x20042010: 0x00000000 0x00000000 0x00000000 0x00000000
0x20042020: 0x00000000 0x00000000
(gdb) si
0x10000306 in ?? ()
(gdb) x/5i 0x10000304
0x10000304: push {r4, lr}
=> 0x10000306: bl 0x1000406c
0x1000030a: ldr r0, [pc, #8] ; (0x10000314)
0x1000030c: bl 0x10003ff4
0x10000310: b.n 0x1000030a
16
(gdb) x/10x $sp
0x20041ff8: 0x10000264 0x10000223 0x00000000 0x00000000
0x20042008: 0x00000000 0x00000000 0x00000000 0x00000000
0x20042018: 0x00000000 0x00000000
We can see that we have two new addresses that were pushed onto our
stack.
Keep in mind, the stack grows downward so we first see the LR pushed
to the top of the stack. Our stack pointer is currently at
0x20041ff8.
We see the stack pointer was first at 0x20041ffc and then it was
pushed DOWN to 0x20041ff8.
I hope this helps you understand how the stack works. We will
continue to examine the stack throughout this book.
stdio_init_all();
Because we are working with a binary without any symbol info we need
to do two steps which are si and then ret to return out of the
function call.
17
#0 0x1000030a in ?? ()
(gdb) x/5i 0x10000304
0x10000304: push {r4, lr}
0x10000306: bl 0x1000406c
=> 0x1000030a: ldr r0, [pc, #8] ; (0x10000314)
0x1000030c: bl 0x10003ff4
0x10000310: b.n 0x1000030a
Hmm… This does not look like an address however it does look like
ascii chars to me. Let’s look at an ascii table.
https://www.asciitable.com
We can see the full pointer to this char array or string by doing the
below.
This has been quite a bit of information but please take the time and
work through this several times and I again encourage you to read the
FREE Embedded Assembler if you want a deeper dive into this.
https://github.com/mytechnotalent/Embedded-Assembler
18
Chapter 3: Hacking hello, world
Today we hack!
Let’s run our serial monitor and observe hello, world in the infinite
loop.
19
Open up a terminal within VSCode.
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0001_hello-world.bin
20
Once it loads, we need to target our remote server.
target remote :3333
21
(gdb) x/5i 0x10000304
0x10000304: push {r4, lr}
0x10000306: bl 0x1000406c
0x1000030a: ldr r0, [pc, #8] ; (0x10000314)
0x1000030c: bl 0x10003ff4
0x10000310: b.n 0x1000030a
The first thing we need to do to hack our system LIVE is to set the
pc to the address right before the call to printf and then set a
breakpoint and then continue.
(gdb) set $pc = 0x1000030c
(gdb) b *0x1000030c
Breakpoint 1 at 0x1000030c
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) c
Continuing.
The next thing we need to do is hijack the value of hello, world from
0x10000314 and create our own data within SRAM and fill it with a
hacked malicious string ;)
(gdb) set {char[14]} 0x20000000 = {'h','a','c','k','y',',','
','w','o','r','l','d','\r','\0'}
(gdb) x/s 0x20000000
0x20000000: "hacky, world\r"
22
Let’s verify our hack!
BOOM! We did it! We successfully hacked our LIVE binary! You can
see hacky, world now being printed to our serial monitor!
After we hacked the centrifuges, we need to make sure the value that
the Engineers are seeing on their monitor shows normal.
23
Chapter 4: Embedded System Analysis
We are working with a microcontroller so there is no operating system
in use. This is what we refer to as bare-metal programming.
Let’s first review the RP2040 datasheet and examine a few areas of
interest before we start to examine our hello world binary.
We see that ROM begins at 0x00000000 and something called XIP begins
at 0x10000000. We also see SRAM starting at 0x20000000 and our
microcontroller peripherals start at 0x40000000.
24
addresses, you can configure and interact with the various hardware
features of the microcontroller.
Let’s break down these items in more detail and the bootroom contents
exist between 0x00000000 and 0x0000001F.
25
Pointer to Boot NMI Handler Function (0x00000008):
NMI stands for Non-Maskable Interrupt. This pointer points to the
function that will handle non-maskable interrupts, which are
interrupts that cannot be disabled or ignored.
The first entry in the Vector Table is the initial stack pointer.
The second entry is the address of the reset handler function.
Subsequent entries are usually addresses of exception and interrupt
handlers.
These handlers include the NMI handler, Hard Fault handler, and
others. When an exception or interrupt occurs, the microcontroller
looks up the corresponding address in the Vector Table and jumps to
that location to execute the associated handler code.
Understanding and, if necessary, customizing the Vector Table is
crucial for bare-metal programming, as it allows you to define how
the microcontroller responds to various events during its operation.
26
There are a number of tools to examine our firmware.
The Executable and Linkable Format (ELF) is a common file format for
executables, object code, shared libraries, and even core dumps. It
is a standard format used for binaries in many software development
environments.
27
sections that define the structure and layout of the binary. Tools in
the development process, such as compilers and linkers, generate and
manipulate ELF files.
We will revisit our hello world binary and take a deeper look into
how this all works. Keep in mind we debugged with the .bin file
which had no symbols or helpful locators of where things are as in
the real-world of reverse engineering you will rarely get symbols.
Open VS Code and click File then Open Folder … then click on the
Embedded-Hacking folder and then select 0x0001_hello-world.
It may ask you for an active kit of which you will choose Pico ARM
GCC.
Let's break down what this command will show and how it relates to
the RP2040 ELF binary:
28
Section Headers:
Sections are parts of the ELF file that organize and hold specific
types of data. Section headers contain information about each
section, such as its name, type, virtual address, size, and other
attributes.
Output Format:
The output of the arm-none-eabi-objdump -h command typically includes
a table with columns representing different attributes of each
section.
For the RP2040 microcontroller, the ELF binary would contain sections
that represent different parts of the program, including code
sections, data sections, and potentially sections related to
microcontroller-specific features.
The ELF file for RP2040 would likely include sections for the
bootloader, firmware, and potentially user applications. The
bootloader, for example, might reside at a specific address in the
memory space, and the ELF file's section headers would provide
details about the location and size of the bootloader code and data.
29
Let’s run the following.
NOTE: ADDRESSES WILL VARY FROM MACHINE TO MACHINE
arm-none-eabi-objdump -h .\build\0x0001_hello-world.elf
Sections:
Idx Name Size VMA LMA File off Algn
0 .boot2 00000100 10000000 10000000 00001000 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .text 00006818 10000100 10000100 00001100 2**3
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .rodata 00001758 10006918 10006918 00007918 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .binary_info 00000028 10008070 10008070 00009070 2**2
CONTENTS, ALLOC, LOAD, DATA
4 .ram_vector_table 000000c0 20000000 20000000 00009cb8 2**2
CONTENTS
5 .data 00000bf8 200000c0 10008098 000090c0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
6 .uninitialized_data 00000000 20000cb8 20000cb8 00009d78 2**0
CONTENTS
7 .scratch_x 00000000 20040000 20040000 00009d78 2**0
CONTENTS
8 .scratch_y 00000000 20041000 20041000 00009d78 2**0
CONTENTS
9 .bss 00000d94 20000cb8 20000cb8 00009cb8 2**3
ALLOC
10 .heap 00000800 20001a4c 20001a4c 00009d78 2**2
CONTENTS, READONLY
11 .stack_dummy 00000800 20041000 20041000 0000a580 2**5
CONTENTS, READONLY
12 .ARM.attributes 00000028 00000000 00000000 0000ad80 2**0
CONTENTS, READONLY
13 .comment 00000049 00000000 00000000 0000ada8 2**0
CONTENTS, READONLY
14 .debug_info 00025b3b 00000000 00000000 0000adf1 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
15 .debug_abbrev 00006d3c 00000000 00000000 0003092c 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
16 .debug_aranges 00001400 00000000 00000000 00037668 2**3
CONTENTS, READONLY, DEBUGGING, OCTETS
17 .debug_ranges 00004bd0 00000000 00000000 00038a68 2**3
CONTENTS, READONLY, DEBUGGING, OCTETS
18 .debug_line 0001804f 00000000 00000000 0003d638 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
19 .debug_str 00007638 00000000 00000000 00055687 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
20 .debug_frame 00002ac0 00000000 00000000 0005ccc0 2**2
CONTENTS, READONLY, DEBUGGING, OCTETS
21 .debug_loc 00018ed3 00000000 00000000 0005f780 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
30
1. .boot2 Section:
Size: 256 bytes (0x100)
VMA (Virtual Memory Address): 0x10000000
LMA (Load Memory Address): 0x10000000
File Offset: 0x1000
Attributes: CONTENTS, ALLOC, LOAD, READONLY, CODE
This section likely contains the bootloader code. It is read-only,
loaded into memory at the specified address (0x10000000), and
contains executable code.
2. .text Section:
Size: 26680 bytes (0x6818)
VMA: 0x10000100
LMA: 0x10000100
File Offset: 0x1100
Attributes: CONTENTS, ALLOC, LOAD, READONLY, CODE
This is the main code section (.text) of your program. It is read-
only, loaded into memory at 0x10000100, and contains the executable
instructions of your program.
3. .rodata Section:
Size: 5976 bytes (0x1758)
VMA: 0x10006918
LMA: 0x10006918
File Offset: 0x7918
Attributes: CONTENTS, ALLOC, LOAD, READONLY, DATA
This section likely contains read-only data (constants, strings,
etc.) used by your program. It is loaded into memory at 0x10006918.
4. .binary_info Section:
Size: 40 bytes (0x28)
VMA: 0x10008070
LMA: 0x10008070
File Offset: 0x9070
Attributes: CONTENTS, ALLOC, LOAD, DATA
This section contains binary information. It is loaded into memory at
0x10008070.
5. .ram_vector_table Section:
Size: 192 bytes (0xC0)
VMA: 0x20000000
LMA: 0x20000000
File Offset: 0x9CB8
Attributes: CONTENTS
This section likely contains the vector table for interrupts. It's
loaded into RAM at 0x20000000.
6. .data Section:
31
Size: 3064 bytes (0xBF8)
VMA: 0x200000C0
LMA: 0x10008098
File Offset: 0x90C0
Attributes: CONTENTS, ALLOC, LOAD, READONLY, CODE
This is the initialized data section. It includes initialized global
and static variables. It is loaded into RAM at 0x200000C0.
7. .uninitialized_data Section:
Size: 0 bytes
VMA: 0x20000CB8
LMA: 0x20000CB8
File Offset: 0x9D78
Attributes: CONTENTS
This section is for uninitialized data. It's likely that the .bss
section will fulfill the same purpose.
32
arm-none-eabi-objdump: The main command for disassembling and
inspecting binary files in the ARM embedded toolchain.
-D: This option tells objdump to disassemble the contents of the ELF
file. It means that the tool will convert the machine code
instructions in the binary file into human-readable assembly language
instructions. The disassembly output will include not only the code
sections but also information about other sections like data,
symbols, and more.
| less: The pipe (|) operator is used to send the output of objdump
to the less command. less is a pager program that allows you to
scroll through large amounts of text one screen at a time.
Disassembling all sections means that you get information about not
only the code sections (.text), but also other sections like data
(.data, .bss), symbols, and potentially debug information. This can
be valuable for debugging, analyzing memory usage, and understanding
the structure of your program.
33
development, especially when working with resource-constrained
devices like the RP2040.
One of the issues with this tool is that the data sections are
misinterpreted. The -D option doesn't discriminate between code
sections (text) and data sections. It attempts to disassemble all
sections in the ELF file. This means that sections containing non-
executable data, such as initialized or uninitialized variables, will
be disassembled as if they were instructions.
Example:
Consider a simple data section that contains an array of 32-bit
integers:
// Data section
int data_array[] = {0x12345678, 0xAABBCCDD, 0xDEADBEEF};
When disassembling this data section with -D, the tool might
interpret the data values as instructions and attempt to disassemble
them. However, these values don't represent valid ARM instructions,
leading to nonsense output.
Another import part of this analysis is the Vector Table. The Vector
Table is a critical part of ARM Cortex-M microcontrollers. It
34
contains addresses of interrupt service routines (ISRs) and is
usually located at the beginning of the program memory. Let's discuss
how you can find and interpret the Vector Table in the disassembly
output:
Scroll through the disassembled code using the arrow keys. The Vector
Table is typically located at the beginning of the disassembly
output.
The first entry in the Vector Table is the address of the Reset
Handler, which is the starting point of your program. It's the
address where the microcontroller jumps to when it is powered on or
reset.
10000000 <__boot2_start__>:
10000000: 4b32b500 blmi 10cad408 <__flash_binary_end+0xca4778>
10000004: 60582021 subsvs r2, r8, r1, lsr #32
10000008: 21026898 ; <UNDEFINED> instruction:
0x21026898
1000000c: 60984388 addsvs r4, r8, r8, lsl #7
10000010: 611860d8 ldrsbvs r6, [r8, -r8]
35
10000014: 4b2e6158 blmi 10b9857c <__flash_binary_end+0xb8f8ec>
10000018: 60992100 addsvs r2, r9, r0, lsl #2
1000001c: 61592102 cmpvs r9, r2, lsl #2
10000020: 22f02101 rscscs r2, r0, #1073741824 ; 0x40000000
10000024: 492b5099 stmdbmi fp!, {r0, r3, r4, r7, ip, lr}
10000028: 21016019 tstcs r1, r9, lsl r0
1000002c: 20356099 mlascs r5, r9, r0, r6
10000030: f844f000 ; <UNDEFINED> instruction:
0xf844f000
10000034: 42902202 addsmi r2, r0, #536870912 ; 0x20000000
10000038: 2106d014 tstcs r6, r4, lsl r0
1000003c: f0006619 ; <UNDEFINED> instruction:
0xf0006619
10000040: 6e19f834 mrcvs 8, 0, APSR_nzcv, cr9, cr4, {1}
10000044: 66192101 ldrvs r2, [r9], -r1, lsl #2
10000048: 66182000 ldrvs r2, [r8], -r0
1000004c: f000661a ; <UNDEFINED> instruction:
0xf000661a
10000050: 6e19f82c cdpvs 8, 1, cr15, cr9, cr12, {1}
10000054: 6e196e19 mrcvs 14, 0, r6, cr9, cr9, {0}
10000058: f0002005 ; <UNDEFINED> instruction:
0xf0002005
1000005c: 2101f82f tstcs r1, pc, lsr #16 ; <UNPREDICTABLE>
10000060: d1f94208 mvnsle r4, r8, lsl #4
10000064: 60992100 addsvs r2, r9, r0, lsl #2
10000068: 6019491b andsvs r4, r9, fp, lsl r9
1000006c: 60592100 subsvs r2, r9, r0, lsl #2:
…
The contents are quite large but when you run this on your own you
will see the extent of the result. The key is this is everything and
you need to ignore the data representation of attempted Assembler
code as noted above.
36
| less: The pipe (|) operator is used to send the output of objdump
to the less command. less is a pager program that allows you to
scroll through large amounts of text one screen at a time.
Disassembling the code helps you understand the flow of your program
at the assembly level. You can see the instructions that make up each
function and analyze the control flow between different parts of your
code.
Example:
If you have a simple C program like:
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
stdio_init_all();
while (true)
printf("hello, world\r\n");
}
This command below will output the disassembled code for the .text
section, showing the assembly instructions generated by the compiler
for your main function and any other code in the program.
37
10000000 <__boot2_start__>:
10000000: 4b32b500 .word 0x4b32b500
10000004: 60582021 .word 0x60582021
10000008: 21026898 .word 0x21026898
1000000c: 60984388 .word 0x60984388
10000010: 611860d8 .word 0x611860d8
10000014: 4b2e6158 .word 0x4b2e6158
10000018: 60992100 .word 0x60992100
…
Like our previous command, the results are many pages long but will
be good to run in your terminal to get an idea of how our binary
looks which starts at 0x10000000 XIP as we discussed prior.
| less: The pipe (|) operator is used to send the output of objdump
to the less command, allowing you to scroll through large amounts of
text one screen at a time.
The -s option is used to view the raw binary data within specific
sections of the ELF file. This can include code sections, data
sections, symbol tables, and more.
38
For non-code sections, such as .data or .rodata, the -s option allows
you to inspect the actual data values stored in these sections. This
is valuable for understanding the initialized and constant data used
by your program.
The -s option can also be used to inspect symbol tables and other
debug-related sections. This is important for debugging tools and for
understanding the relationship between high-level source code and
low-level machine code.
Inspecting section contents is helpful for low-level analysis,
especially when you need to understand how specific data or code is
laid out in memory. It complements the information provided by
disassembly (-d) by giving you a more direct view of the raw binary
data stored in different sections of your executable.
arm-none-eabi-objdump -s .\build\0x0001_hello-world.elf | less
Like many of the previous commands, we are only showing the first
page.
39
-a: This option tells readelf to display all available information
about the ELF file. This includes information about the ELF header,
program headers, section headers, symbol tables, and more.
The ELF header contains essential information about the binary, such
as the architecture, entry point, program header table offset,
section header table offset, and other metadata.
Program Headers:
Program headers provide information about how the ELF file should be
loaded into memory. This includes details about the different
segments of the program, such as code and data segments.
When running the command, you will see a detailed output that
includes information about the ELF header, program headers, section
headers, symbol tables, and other relevant information about the
structure and content of your ELF file.
ELF Header:
40
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x100001e9
Start of program headers: 52 (bytes into file)
Start of section headers: 524904 (bytes into file)
Flags: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 3
Size of section headers: 40 (bytes)
Number of section headers: 26
Section header string table index: 25
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .boot2 PROGBITS 10000000 001000 000100 00 AX 0 0 1
[ 2] .text PROGBITS 10000100 001100 006818 00 AX 0 0 8
[ 3] .rodata PROGBITS 10006918 007918 001758 00 A 0 0 8
[ 4] .binary_info PROGBITS 10008070 009070 000028 00 WA 0 0 4
[ 5] .ram_vector_table PROGBITS 20000000 009cb8 0000c0 00 W 0 0 4
…
Like the rest, this is just a small section you can try this out on
your own to get the full contents.
41
| less: The pipe (|) operator is used to send the output of nm to the
less command, allowing you to scroll through large amounts of text
one screen at a time.
Example Output:
You will see an output that lists symbols along with their addresses
and types. Here's a simplified example:
08000100 T _start
08000104 T main
08000120 T printf
08000200 D data_variable
T: Indicates a code symbol (function).
D: Indicates a data symbol (variable).
42
20000b50 t ____wrap_memset_veneer
10006908 t ___hw_endpoint_buffer_control_update32_veneer
10003534 t __aeabi_bits_init
10003a76 t __aeabi_dfcmple_guts
10003bc0 T __aeabi_double_init
10003d00 T __aeabi_float_init
20000b08 W __aeabi_idiv0
20000b08 W __aeabi_ldiv0
10003d88 T __aeabi_mem_init
10002290 T __assert_func
20000b90 t __best_effort_wfe_or_timeout_veneer
10007c8c r __bi_188.0
100002ac t __bi_22
100002a0 t __bi_30
10007be8 r __bi_33.4
10007bf4 r __bi_34.5
10000294 t __bi_38
10007b7c r __bi_44
10000288 t __bi_50
10007b88 r __bi_75
10007b94 r __bi_81
10008094 d __bi_ptr188.4
10008070 d __bi_ptr22
10008074 d __bi_ptr30
…
When you run on your own you will see the entire contents.
| less: The pipe (|) operator is used to send the output of strings
to the less command, allowing you to scroll through large amounts of
text one screen at a time.
43
The strings command helps you identify and inspect human-readable
text within the binary. This includes strings used in your code, such
as debug messages, error messages, and other informative text.
You'll see a mix of strings that are part of your code, standard
library functions, and potentially other information embedded in the
binary. By examining these strings, you can gain a better
understanding of the content and functionality of the compiled
program. Keep in mind that not all strings extracted may be directly
visible in your source code; some might be library or system-related.
arm-none-eabi-strings .\build\0x0001_hello-world.elf | less
2K! X`
aXa.K
pG H
`SL{
RPT"
""@ *
K#+p
"iF(
FWFNFEF
FdDch
#hZ@
`CFc`BF
!hy@
-J.H
*K,JZb
`'K[l
XpGD
h@"b@
F`DA`
FaD J
K#CC
FWFNFEF
FRFAFHF
VKVJ
VKSJ
44
GKCJ
+K+J
KZFAFR
RASC
…
Keep parsing through the output. You should see our hello, world
sting in there.
You can use grep to find specifically what you are looking for like
this.
arm-none-eabi-strings .\build\0x0001_hello-world.elf | grep -i "hello"
hello, world
0x0001_hello-world
C:/Users/mytec/Documents/Embedded-Hacking/0x0001_hello-world
C:/Users/mytec/Documents/Embedded-Hacking/0x0001_hello-world/main.c
C:\Users\mytec\Documents\Embedded-Hacking\0x0001_hello-world\build
I know this was a longer chapter but please take the time and
understand these tools and getting a better understanding of what
firmware is and in this case how it is specific to the M0+ and RP2040
MCU.
45
Chapter 5: Intro To Variables
In this chapter we are going to introduce the concept of a variable.
If we have a series of boxes all layed out in a row and we numbered
them from 0 to 9 (we start with 0 in Engineering) and then placed
item 0 in box 0 and then item 1 in box 1 all the way to item 9 in box
9.
The boxes in this analogy represents our SRAM. The items are nothing
more than variables of different types, which we will discuss later,
that are stored in each of these addresses.
For the Developer, you simply provide a type and a name and the
compiler will assign to the value to an actual address.
The process of declaration provides the compiler the size and name of
the variable you are creating.
uint8_t age;
Here we have a data type which is uint8_t and the name of the
variable which is age.
The data type determines how much space a variable is going to occupy
in memory. This will signal the compiler to allocate space for it.
46
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
uint8_t age = 42;
age = 43;
stdio_init_all();
while (true)
printf("age: %d\r\n", age);
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
The first lines you should be familiar with and if not again refer to
Chapter 1 to get re-familiar with those lines.
age = 43;
Then inside the while loop we have a printf where we print text to
indicate that we are going to print the age and then use what we
refer to as a format specifier which is %d to indicate we are using a
decimal value and then our new line chars \r\n and then we have the
value that will populate %d which is 43.
Let’s open up PuTTY or your terminal editor of choice and we will see
our values being printed in an infinite loop.
47
In our next chapter we will debug this.
48
Chapter 6: Debugging Intro To Variables
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0005_intro-to-variables.bin
I want to make sure you REALLY understand what is going on. The XIP
is the start of FLASH at 0x10000000. The entry point to our vector
table begins with the MSP or master stack pointer and this will be at
0x10000100.
This should make sense if not please re-read the last chapter.
49
Let’s examine instructions from the beginning of our vector table.
x/1000i 0x10000100
50
Chapter 7: Hacking Intro To Variables
Let’s pick up where we were in our last lesson and set a breakpoint
on the line after the assignment of decimal 43.
(gdb) b *0x10000312
Breakpoint 2 at 0x10000312
(gdb) c
51
Chapter 8: Uninitialized Variables
In this chapter we are going to examine what happens in memory when
we create variables that are not initialized.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
uint8_t age;
stdio_init_all();
while (true)
printf("age: %d\r\n", age);
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
The only difference is that we have no idea what the value will be
inside of age or do we?
The entire .bss section is assigned an address in RAM via the linker
and does not reside in the binary or flash.
When the Pico boots, behind the scenes memset which is a C standard
lib function is zeroing out the entire .bss so this is why these
values are in fact 0.
52
In our next lesson we will debug this.
53
Chapter 9: Debugging Uninitialized
Variables
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0008_uninitialized_variables.bin
This should make sense if not please re-read the last few chapters.
54
Let’s examine instructions from the beginning of our vector table.
x/1000i 0x10000100
We know that 308 offset is our C SDK init which we have seen before.
Let’s set a breakpoint after the ldr instruction and see what is
going on.
(gdb) b *0x10000310
Breakpoint 1 at 0x10000310
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) c
Continuing.
Thread 1 "rp2040.core0" hit Breakpoint 1, 0x10000310 in ?? ()
(gdb) x/x $r0
0x10006920: 0x3a656761
(gdb) x/s $r0
0x10006920: "age: %d\r\n"
We see our age variable however what does it store? If you look at
the movs r4, #0 it tells us our answer. In the last chapter we
talked about how initialized variables were init to 0.
55
Chapter 10: Hacking Uninitialized
Variables
Let’s pick up where we were in our last lesson and set a breakpoint
on the line after the assignment of decimal 0.
(gdb) b *0x10000308
Breakpoint 2 at 0x10000308
(gdb) c
56
Chapter 11: Integer Data Type
In this chapter we are going to discuss the integer data type. We
have already covered examples with this however this book’s goal is
to continue to reinforce learning so that you have a mastery of the
embedded process.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
uint8_t age = 0;
int8_t range = 0;
age = 43;
range = -42;
stdio_init_all();
while (true) {
printf("age: %d\r\n", age);
printf("range: %d\r\n", range);
}
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
The first lines you should be familiar with and if not again refer to
Chapter 1 to get re-familiar with those lines.
uint8_t age = 0;
age = 43;
57
int8_t range = 0;
Then inside the while loop we have a printf where we print text to
indicate that we are going to print the age and then use what we
refer to as a format specifier which is %d to indicate we are using a
decimal value and then our new line chars \r\n and then we have the
value that will populate %d which is 43.
Let’s open up PuTTY or your terminal editor of choice and we will see
our values being printed in an infinite loop.
58
Chapter 12: Debugging Integer Data Type
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x000b_integer-data-type.bin
x/1000i 0x10000100
59
0x10000318: bl 0x10003bd0
0x1000031c: b.n 0x1000030a
b *0x10000304
c
60
Chapter 13: Hacking Integer Data Type
Let’s pick up where we were in our last lesson and set a breakpoint
on the line after the assignment of decimal 42.
(gdb) b *0x1000030a
Breakpoint 2 at 0x1000030a
(gdb) c
(gdb) b *0x10000312
Breakpoint 3 at 0x10000312
(gdb) c
Continuing.
61
Let’s open PuTTY or our terminal emulator of choice.
62
Chapter 14: Floating-Point Data Type
In this chapter we are going to discuss the floating-point data type.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
float fav_num = 42.5;
stdio_init_all();
while (true)
printf("fav_num: %f\r\n", fav_num);
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
The first lines you should be familiar with and if not again refer to
Chapter 1 to get re-familiar with those lines.
Then inside the while loop we have a printf where we print text to
indicate that we are going to print the fav_num and then use what we
refer to as a format specifier which is %f to indicate we are using a
float value and then our new line chars \r\n and then we have the
value that will populate %f which is 42.5.
63
In our next chapter we will debug this.
64
Chapter 15: Debugging Floating-Point Data
Type
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x000e_floating-point-data-type.bin
x/1000i 0x10000100
65
0x10000318: b.n 0x1000030e
0x1000031a: nop ; (mov r8, r8)
And there we have it! In our next lesson we will hack this value.
66
Chapter 16: Hacking Floating-Point Data
Type
Let’s pick up where we were in our last lesson and set a breakpoint
at the beginning of main and at the end of our main after we assign
the value into R3.
(gdb) b *0x10000308
Breakpoint 1 at 0x10000308
(gdb) b *0x10000314
Breakpoint 2 at 0x10000314
(gdb) c
Let’s get to after the step where our 42.5 is assigned into R3.
(gdb) si
0x1000030a in ?? ()
(gdb) si
0x10003b80 in ?? ()
(gdb) ret
Make selected stack frame return now? (y or n) y
#0 0x1000030e in ?? ()
(gdb) si
0x10000310 in ?? ()
(gdb) si
0x10000312 in ?? ()
(gdb) si
0x10000314 in ?? ()
67
(gdb) set $r3 = 0x40455000
(gdb) c
Continuing.
68
Chapter 17: Double Floating-Point Data
Type
In this chapter we are going to discuss the floating-point data type.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
double fav_num = 42.52525;
stdio_init_all();
while (true)
printf("fav_num: %lf\r\n", fav_num);
}
Let’s flash the uf2 file onto the pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
The first lines you should be familiar with and if not again refer to
Chapter 1 to get re-familiar with those lines.
Then inside the while loop we have a printf where we print text to
indicate that we are going to print the fav_num and then use what we
refer to as a format specifier which is %lf to indicate we are using
a double value and then our new line chars \r\n and then we have the
value that will populate %f which is 42.5.
69
In our next chapter we will debug this.
70
Chapter 18: Debugging Double Floating-
Point Data Type
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0011_double-floating-point-data-type.bin
x/1000i 0x10000100
71
0x10000314: b.n 0x1000030a
0x10000316: nop ; (mov r8, r8)
(gdb) b *0x10000310
(gdb) x/s *0x10000320
0x10003eb8: "fav_num: %lf\r\n"
72
Chapter 19: Hacking Double Floating-Point
Data Type
Let’s pick up where we were in our last lesson and set a breakpoint
at the point before we call printf and we assign the value into R3.
(gdb) b *0x10000310
Breakpoint 1 at 0x10000310
(gdb) c
Continuing.
Once again we need to reverse engineer how this works! We know that
0x4045433b is 42.52525 so what if we try 0x40455000?
73
Success! We hacked it! In our next lesson we will look at static
variables.
74
Chapter 20: Static Variables
In this chapter we are going to discuss static variables.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
stdio_init_all();
while (true) {
static int static_fav_num = 42;
int regular_fav_num = 42;
static_fav_num++;
regular_fav_num++;
}
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
The first lines you should be familiar with and if not again refer to
Chapter 1 to get re-familiar with those lines.
Take note that we are initializing two variables within a while loop.
The difference here is that when we iterate through the while loop
the static_int_fav_num will increase where the regular_fav_num will
get re-initialized through each run of the loop.
75
In our next lesson we will debug this.
76
Chapter 21: Debugging Static Variables
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0014_static-variables.bin
x/1000i 0x10000100
77
0x1000031c: bl 0x10003b78
0x10000320: ldr r3, [r4, #0]
0x10000322: adds r3, #1
0x10000324: str r3, [r4, #0]
0x10000326: b.n 0x1000030e
We see two values being loaded in below, 31a offset is where we load
42 and the 326 offset we jump back to the beginning of our while
loop.
(gdb) b *0x1000031a
(gdb) b *0x10000326
Here I have let the program iterate a bit and when we look at r3 we
can see the value of our static variable. When it iterates next it
will be 673 decimal as the current value in PuTTY is 672 on
static_fav_num.
(gdb) i r
r0 0x15 21
r1 0x0 0
r2 0x0 0
r3 0x2a1 673
r4 0x20000378 536871800
r5 0x20041f01 537140993
r6 0x18000000 402653184
r7 0x0 0
r8 0xffffffff -1
r9 0xffffffff -1
r10 0xffffffff -1
r11 0xffffffff -1
r12 0x20000305 536871685
sp 0x20041ff8 0x20041ff8
78
Chapter 22: Hacking Static Variables
Let’s pick up where we were in our last lesson and set a breakpoint
at the point before we call printf and we assign the value into r3.
(gdb) b *0x1000031c
c
79
(gdb) x/13i 0x10000308
0x10000308: push {r4, lr}
0x1000030a: bl 0x10003b90
0x1000030e: ldr r4, [pc, #24] ; (0x10000328)
0x10000310: ldr r1, [r4, #0]
0x10000312: ldr r0, [pc, #24] ; (0x1000032c)
=> 0x10000314: bl 0x10003b78
0x10000318: ldr r0, [pc, #20] ; (0x10000330)
0x1000031a: movs r1, #42 ; 0x2a
0x1000031c: bl 0x10003b78
0x10000320: ldr r3, [r4, #0]
0x10000322: adds r3, #1
0x10000324: str r3, [r4, #0]
0x10000326: b.n 0x1000030e
(gdb) del br 1
(gdb) del br 2
(gdb) del br 3
(gdb) del br 4
(gdb) b *0x10000324
c
Remember str instructions store the left value into the right value!
80
Let’s look in PuTTY!
81
Chapter 23: Constants
In this chapter we are going to discuss constants.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
#define FAV_NUM 42
int main(void) {
stdio_init_all();
while (true) {
printf("FAV_NUM: %d\r\n", FAV_NUM);
printf("regular_fav_num: %d\r\n", OTHER_FAV_NUM);
}
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
82
In our next chapter we will debug this.
83
Chapter 24: Debugging Constants
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0017_constants.bin
x/1000i 0x10000100
84
0x1000031a: b.n 0x1000030a
85
Chapter 25: Hacking Constants
Let’s examine 9 instructions.
(gdb) b *0x1000030a
Breakpoint 1 at 0x1000030a
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) b *0x10000314
Breakpoint 2 at 0x10000314
(gdb) b *0x1000031a
Breakpoint 3 at 0x1000031a
(gdb) c
Continuing.
86
Let’s step into once and set R1 to 44 instead of 42.
(gdb) si
0x1000030c in ?? ()
(gdb) set $r1 = 44
87
Chapter 26: Operators
In this chapter we are going to discuss operators.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
stdio_init_all();
int x = 5;
int y = 10;
int arithmetic_operator = (x * y);
int increment_operator = x++;
bool relational_operator = (x > y);
bool logical_operator = (x > y) && (y > x);
int bitwise_operator = (x<<1); // x is now 6 because of x++ or 0b00000110 and (x<<1) is
0b00001100 or 12
int assignment_operator = (x += 5);
while (true) {
printf("arithmetic_operator: %d\r\n", arithmetic_operator);
printf("increment_operator: %d\r\n", increment_operator);
printf("relational_operator: %d\r\n", relational_operator);
printf("logical_operator: %d\r\n", logical_operator);
printf("bitwise_operator: %d\r\n", bitwise_operator);
printf("assignment_operator: %d\r\n", assignment_operator);
}
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
88
bool relational_operator = (x > y); checks if the value of x is
greater than the value of y and stores the result in the boolean
variable relational_operator.
89
Chapter 27: Debugging Operators
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x001a_operators.bin
x/1000i 0x10000100
90
0x1000031a: movs r1, #0
0x1000031c: ldr r0, [pc, #36] ; (0x10000344)
0x1000031e: bl 0x10003bfc
0x10000322: movs r1, #0
0x10000324: ldr r0, [pc, #32] ; (0x10000348)
0x10000326: bl 0x10003bfc
0x1000032a: movs r1, #12
0x1000032c: ldr r0, [pc, #28] ; (0x1000034c)
0x1000032e: bl 0x10003bfc
0x10000332: movs r1, #11
0x10000334: ldr r0, [pc, #24] ; (0x10000350)
0x10000336: bl 0x10003bfc
0x1000033a: b.n 0x1000030a
After the next bl to printf, we see 0 being loaded into r1 for our
logical_operator which is false.
After the next bl to printf, we see 12 being loaded into r1 for our
bitwise_operator as we need to recall earlier we had x++ so x
originally was 5 but is now 6 and when we use the << bitwise operator
with 1 that literally doubles the value and if we used >> 1 that
would floor divide the value by 2.
91
Chapter 28: Hacking Operators
Let’s examine 21 instructions.
(gdb) b *0x1000030c
(gdb) c
(gdb) set $r1 = 42
(gdb) c
92
In our next lesson we will cover static conditionals.
93
Chapter 29: Static Conditionals
In this chapter we are going to discuss static conditionals.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
stdio_init_all();
int choice = 1;
while (true) {
if (choice == 1) {
printf("1\r\n");
} else if (choice == 2) {
printf("2\r\n");
} else {
printf("?\r\n");
}
switch (choice) {
case 1:
printf("one\r\n");
break;
case 2:
printf("two\r\n");
break;
default:
printf("??\r\n");
}
}
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
94
Chapter 30: Debugging Static Conditionals
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x001d_static-conditionals.bin
x/1000i 0x10000100
95
We see the compiler optimized away our conditionals. This is why we
will have different chapters so you understand that values are static
likely the compiler will simply optimize away.
96
Chapter 31: Hacking Static Conditionals
Let’s examine 7 instructions.
(gdb) b *0x10000310
(gdb) x/x $r0
0x10003e60: 0x31
(gdb) set {char[3]} 0x20000000 = { '2', '\r', '\0' }
(gdb) set $r0 = 0x20000000
(gdb) x/s $r0
0x20000000: "2\r"
(gdb) b *0x10000316
(gdb) c
Continuing.
(gdb) set {char[5]} 0x20000000 = { 't', 'w', 'o', '\r', '\0' }
(gdb) set $r0 = 0x20000000
(gdb) x/s $r0
0x20000000: "two\r"
(gdb) c
97
Let’s look at our output!
Isn’t hacking fun! In our next lesson we will cover dynamic conditionals.
98
Chapter 32: Dynamic Conditionals
In this chapter we are going to discuss dynamic conditionals.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
#include "input.h"
int main(void) {
stdio_init_all();
uart0_init();
uint8_t choice = 0;
while (true) {
choice = on_uart_rx();
if (choice == ONE) {
printf("1\r\n");
} else if (choice == TWO) {
printf("2\r\n");
} else {
printf("??\r\n");
}
switch (choice) {
case '1':
printf("one\r\n");
break;
case '2':
printf("two\r\n");
break;
default:
printf("??\r\n");
}
}
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
99
In order to properly handle input, I created a driver that is
abstracted away at this time as I do not want to cover functions and
drivers at this early stage so you can focus on the basics first.
Here when we press 1 in the terminal we will get the 1 printed along
with the word one.
When we press 2 in the terminal we get the 2 printed along with the
word two.
When we go to the switch it will parse the list to find the correct
item if a 1 or 2 is selected otherwise it will follow the default.
100
Chapter 33: Debugging Dynamic
Conditionals
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0020_dynamic-conditionals.bin
x/1000i 0x10000100
101
0x10000312: movs r0, #0
0x10000314: bl 0x10000734
0x10000318: movs r1, #2
0x1000031a: movs r0, #1
0x1000031c: bl 0x10000734
0x10000320: pop {r4, pc}
0x10000322: nop ; (mov r8, r8)
0x10000324: ands r0, r0
0x10000326: ands r3, r0
0x10000328: sub sp, #8
0x1000032a: ldr r3, [pc, #48] ; (0x1000035c)
0x1000032c: ldr r3, [r3, #24]
0x1000032e: lsls r3, r3, #27
0x10000330: bmi.n 0x10000336
0x10000332: add sp, #8
0x10000334: bx lr
0x10000336: movs r2, #0
0x10000338: mov r3, sp
0x1000033a: adds r1, r3, #7
0x1000033c: cmp r2, #0
0x1000033e: bne.n 0x10000354
0x10000340: ldr r3, [pc, #24] ; (0x1000035c)
0x10000342: ldr r3, [r3, #24]
=> 0x10000344: lsls r3, r3, #27
0x10000346: bmi.n 0x10000340
0x10000348: ldr r3, [pc, #16] ; (0x1000035c)
0x1000034a: ldr r3, [r3, #0]
0x1000034c: strb r3, [r1, #0]
0x1000034e: adds r2, #1
0x10000350: adds r1, #1
0x10000352: b.n 0x1000033c
0x10000354: mov r3, sp
0x10000356: ldrb r0, [r3, #7]
0x10000358: b.n 0x10000332
(gdb) b *0x1000033c
(gdb) c
(gdb) i r $r3
r3 0x31
102
In our next lesson we will hack this.
103
Chapter 34: Hacking Dynamic Conditionals
Let’s examine 40 instructions.
(gdb) b *10000386
(gdb) c
104
Press 1 in the terminal and lets look at R0!
(gdb) i r $r0
r0 0x31 49
(gdb) i r $r0
r0 0x31 49
(gdb) x/5i 0x10000386
=> 0x10000386: movs r4, r0
0x10000388: cmp r0, #49 ; 0x31
0x1000038a: beq.n 0x1000036c
0x1000038c: cmp r0, #50 ; 0x32
0x1000038e: beq.n 0x10000374
(gdb) set $r0 = 42
(gdb) c
BOOM! We entered in 1 and should have got 1 one but we got ?? ??!
In our next lesson we will cover functions, w/o param, w/o return.
105
Chapter 35: Functions, w/o Param, w/o
Return
In this chapter we are going to discuss functions that do not have
any params nor any return value.
Now let’s review our main.c file as this is located in the main
folder.
#include <stdio.h>
#include "pico/stdlib.h"
#define FAV_NUM 42
void print_me(void);
int main(void) {
stdio_init_all();
while (true)
print_me();
}
void print_me(void) {
printf("FAV_NUM: %d\r\n", FAV_NUM);
}
Let’s flash the uf2 file onto the Pico. If you are unsure about this
step please take a look at Chapter 1 to get re-familiar with this
process.
This is our first use of working with our own custom functions.
void print_me(void);
If we did not have the forward declaration, the function would not be
seen under main.
106
Our function print_me takes no parameters nor does it return any
value. We use void to preface the function name to indicate there is
no return value and we put void within the param to indicate no
parameters.
107
Chapter 36: Debugging Functions, w/o
Param, w/o Return
Today we debug!
cd ..
.\openocd.ps1
Open up a new terminal and cd .\build\ dir and then run the
following.
arm-none-eabi-gdb .\0x0023_functions-wo-params-wo-return.bin
x/1000i 0x10000100
108
0x10000304: push {r4, lr}
0x10000306: movs r1, #42 ; 0x2a
0x10000308: ldr r0, [pc, #4] ; (0x10000310)
0x1000030a: bl 0x10003bc8
0x1000030e: pop {r4, pc}
0x10000310: subs r6, #176 ; 0xb0
0x10000312: asrs r0, r0, #32
0x10000314: push {r4, lr}
0x10000316: bl 0x10003be0
0x1000031a: bl 0x10000304
0x1000031e: b.n 0x1000031a
(gdb) b *0x10003bc8
Breakpoint 1 at 0x10003bc8
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) c
Continuing.
(gdb) x/11i $pc
=> 0x10003bc8: push {r0, r1, r2, r3}
0x10003bca: push {lr}
0x10003bcc: sub sp, #12
0x10003bce: add r1, sp, #16
0x10003bd0: ldmia r1!, {r0}
0x10003bd2: str r1, [sp, #4]
0x10003bd4: bl 0x10003b88
0x10003bd8: add sp, #12
0x10003bda: pop {r3}
0x10003bdc: add sp, #16
0x10003bde: bx r3
(gdb) b *0x10003bd8
Breakpoint 2 at 0x10003bd8
(gdb) c
109
Continuing.
110
The first four registers are where params are utilized. In our case
we do not have any params.
The R0 register is where the return value is sent back to the calling
function. In our case we do not have any.
The LR is the link register which holds the address of where we need
to return to in our main function.
We see a sub sp, #12 where we are setting up room on our stack, in
our case 12 bytes, for local variables. Remember that the stack
grows downward.
(gdb) c
Continuing.
111
(gdb) si
0x10003bcc in ?? ()
(gdb) si
0x10003bcc in ?? ()
(gdb) x/11i 0x10003bc8
0x10003bc8: push {r0, r1, r2, r3}
0x10003bca: push {lr}
=> 0x10003bcc: sub sp, #12
0x10003bce: add r1, sp, #16
0x10003bd0: ldmia r1!, {r0}
0x10003bd2: str r1, [sp, #4]
0x10003bd4: bl 0x10003b88
0x10003bd8: add sp, #12
0x10003bda: pop {r3}
0x10003bdc: add sp, #16
0x10003bde: bx r3
(gdb) x/x $sp
0x20041fdc: 0x1000030f
(gdb) si
0x10003bce in ?? ()
(gdb) x/11i 0x10003bc8
0x10003bc8: push {r0, r1, r2, r3}
0x10003bca: push {lr}
0x10003bcc: sub sp, #12
=> 0x10003bce: add r1, sp, #16
0x10003bd0: ldmia r1!, {r0}
0x10003bd2: str r1, [sp, #4]
0x10003bd4: bl 0x10003b88
0x10003bd8: add sp, #12
0x10003bda: pop {r3}
0x10003bdc: add sp, #16
0x10003bde: bx r3
(gdb) x/x $sp
0x20041fd0: 0x18000000
112
Let’s set a breakpoint to then understand how the stack will return
from the function.
(gdb) b *0x10003bd8
Note: breakpoint 2 also set at pc 0x10003bd8.
Breakpoint 3 at 0x10003bd8
(gdb) c
Continuing.
(gdb) si
0x10003bda in ?? ()
(gdb) x/11i 0x10003bc8
0x10003bc8: push {r0, r1, r2, r3}
0x10003bca: push {lr}
0x10003bcc: sub sp, #12
0x10003bce: add r1, sp, #16
0x10003bd0: ldmia r1!, {r0}
0x10003bd2: str r1, [sp, #4]
0x10003bd4: bl 0x10003b88
0x10003bd8: add sp, #12
=> 0x10003bda: pop {r3}
0x10003bdc: add sp, #16
0x10003bde: bx r3
(gdb) x/x $sp
0x20041fdc: 0x1000030f
113
(gdb) si
0x10003bdc in ?? ()
(gdb) x/11i 0x10003bc8
0x10003bc8: push {r0, r1, r2, r3}
0x10003bca: push {lr}
0x10003bcc: sub sp, #12
0x10003bce: add r1, sp, #16
0x10003bd0: ldmia r1!, {r0}
0x10003bd2: str r1, [sp, #4]
0x10003bd4: bl 0x10003b88
0x10003bd8: add sp, #12
0x10003bda: pop {r3}
=> 0x10003bdc: add sp, #16
0x10003bde: bx r3
(gdb) x/x $sp
0x20041fe0: 0x10003eb0
(gdb) si
0x10003bde in ?? ()
(gdb) x/11i 0x10003bc8
0x10003bc8: push {r0, r1, r2, r3}
0x10003bca: push {lr}
0x10003bcc: sub sp, #12
0x10003bce: add r1, sp, #16
0x10003bd0: ldmia r1!, {r0}
0x10003bd2: str r1, [sp, #4]
0x10003bd4: bl 0x10003b88
0x10003bd8: add sp, #12
0x10003bda: pop {r3}
0x10003bdc: add sp, #16
=> 0x10003bde: bx r3
(gdb) x/x $sp
0x20041ff0: 0x10000264
114
Higher Memory Addresses
|
V
+-----------------+
| 0x10000264 | <-- Original Stack Pointer (sp = 0x20041ff0)
+-----------------+
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
+-----------------+
| 0x10003eb0 | <-- Value at sp = 0x20041fe0
+-----------------+
| |
| |
| |
| |
+-----------------+
| 0x1000030f | <-- Value at sp = 0x20041fdc
+-----------------+
| 0x18000000 | <-- Value at sp = 0x20041fd4
+-----------------+
| 0x10003eb0 | <-- Value at sp = 0x20041fd0
+-----------------+
| |
| |
| |
| |
+-----------------+
| 0x1000030f | <-- Value at sp = 0x20041fe0
+-----------------+
115
Now you have the full picture of how a function works in Embedded C!
116
Chapter 37: Hacking Functions, w/o Param,
w/o Return
Let’s examine our main function.
(gdb) b *10000304
(gdb) c
(gdb) x/11i $pc
=> 0x10000304: push {r4, lr}
0x10000306: movs r1, #42 ; 0x2a
0x10000308: ldr r0, [pc, #4] ; (0x10000310)
0x1000030a: bl 0x10003bc8
0x1000030e: pop {r4, pc}
0x10000310: subs r6, #176 ; 0xb0
0x10000312: asrs r0, r0, #32
0x10000314: push {r4, lr}
0x10000316: bl 0x10003be0
0x1000031a: bl 0x10000304
117
0x1000031e: b.n 0x1000031a
(gdb) si
0x10000306 in ?? ()
(gdb) si
0x10000308 in ?? ()
BOOM!
118