Classroom Activities
- Classroom Activities
- How to use this page
- Week 1 — Introduction and hardware fundamentals
- Week 2 — UART and instrumentation
- Week 3 — I2C and SPI
- Week 4 — JTAG and SWD
- Week 5 — Firmware extraction from memory
- Week 6 — Networks, WiFi, BLE, and firmware from the wire
- Week 7 — RF capture and spectrum analysis
- Week 8 — Radio hacking: LoRa, Zigbee, Thread, and Matter
- Week 9 — Software reverse engineering and bootloaders
- Week 10 — IoT operating systems and secure architecture
- Key takeaways
- References
How to use this page
Three activities per week, sized for a class meeting rather than a lab session:
- A 5-minute warm-up that reviews the previous week. Week 1 has no previous week, so its warm-up is the Linux fluency everything else depends on.
- Two 10–15 minute hands-on blocks on that week’s material.
That is roughly 35 minutes of the week’s contact time, split across the two meetings — warm-up plus one activity on Monday, warm-up recap plus the second activity on Wednesday, or both blocks in one sitting if the lecture runs short. Week 7 has a single meeting because the university is closed on 11 November, so both of its blocks land on the 9th.
Some ground rules that make these work:
- Pairs, not individuals. One person drives, one person takes notes, and they swap at the halfway point. The note-taker’s job is the deliverable.
- Every activity ends in the notebook. The deliverable listed under each activity is a commit to your GitLab repo, not a verbal answer. Five minutes of writing during class beats an hour of reconstruction later, and it is the same discipline Week 1 asks for.
- These are rehearsals, not the graded work. Every activity deliberately uses a different board, capture, or binary than the homework covering the same skill. If an activity seems to be solving your current assignment for you, you have the wrong artifact — ask.
- Get the files before class. Six of the activities work from supplied captures and images, all collected on the activity handouts page. Download them beforehand; the room’s WiFi is not part of the exercise.
- Hardware fails. If your bench setup will not cooperate inside the time box, stop and write down what you tried, what you observed, and what you would check next, then come back to it in office hours. That write-up is the deliverable; a working bench is not. The point is the reasoning, and the reasoning is what gets graded. Several activities need no hardware at all — Week 4 Activity 1 and Week 8 Activity 2 are paper exercises, and Weeks 2, 3, 5, 7 and 9 work from supplied capture and image files.
⚠️ Authorization applies in the classroom too. The boards in the swap bin are course property and fair game. Your laptop, your phone, and the building’s access control system are not. The scope note on the schedule is not a formality.
Week 1 — Introduction and hardware fundamentals
Warm-up (5 min): Linux skills — find the board without a GUI
Everything in the next nine weeks starts with a device node and permission to open it. Find both now, on the machine you will actually use all term.
Run, in order, and read the output rather than skimming it:
$ lsusb
$ dmesg | tail -20
$ ls -l /dev/serial/by-id/
$ id -nG
Then answer three questions in your notebook:
- What is the full
/dev/serial/by-id/path for your Feather? Use that path, not/dev/ttyACM0— theby-idname survives a reboot and a second board, and the numbered one does not. - Are you in the group that owns that device node? Compare the group in the
ls -loutput against yourid -nGlist. On Debian and Ubuntu that group isdialout; on Arch and Fedora it isuucp. - If you are not in it, what is the command to add yourself, and why do you have to log out afterwards?
🛠️ Fix the group membership now. It is the single most common reason a student loses the first twenty minutes of the Week 2 bench activity to a
Permission deniedthey read as a broken board.
Deliverable: the three answers, plus the ls -l line for your board, in
notebook/week01.md.
Activity 1 (15 min): The four questions, on someone else’s board
Setup: each pair draws an unfamiliar board from the swap bin — a dead router, a thermostat, a disassembled IP camera. Phone camera, good light.
Run:
- Photograph both sides at the highest resolution your phone will do, before you touch anything. This is the photo you will want in ten minutes.
- Work the four questions of hardware recon in order, writing each answer down before moving to the next: biggest chip, what is next to it, what talks to the outside world, what is left over.
- For the biggest chip only, transcribe the markings exactly — including the line you think is a date/lot code — and find one candidate datasheet.
Debrief: pairs read out their answer to question 4 only. Unpopulated headers and test-pad rows are the whole course in one observation, and hearing six boards in a row all have them makes the point better than a slide does.
Deliverable: both photographs, the four answers, the transcribed markings, and the datasheet URL.
Activity 2 (10 min): The FCC ID race
Setup: the exterior label of a radio-bearing device — from your swap-bin board if it has one, otherwise use the one projected at the front.
Run: an FCC ID looks like 2AB3C-XYZ100: a grantee code, then a product
code. Search it at https://fccid.io or the FCC’s own
equipment authorization search
— the grantee code goes in the first field and the product code in the second
— and find:
- The internal photographs.
- The test report, and the frequency range it was tested across.
- The block diagram, if the grantee did not get it withheld.
Debrief: compare the internal photos against your Activity 1 answers. Did the filing name a chip you could not read? The point lands hard: for any device with a radio sold in the US, someone has already taken it apart, photographed it, and published the results — and in a real assessment that is a free first hour.
⚠️ Confidentiality requests routinely hide schematics and internal photos. A filing with everything withheld is a legitimate finding, not a failed search — note which exhibits exist and which are confidential.
Deliverable: the FCC ID, a link to the filing, the tested frequency range, and one thing the filing told you that the board did not.
Week 2 — UART and instrumentation
Warm-up (5 min): Week 1 review — packages and date codes
Four photographs on the projector, sixty seconds each. For each one, write down the package type and the most likely role on the board:
- An 8-pin SOIC next to a large QFP.
- A 48-pin TQFP with a crystal beside it.
- A BGA with no visible markings.
- A 6-pin SOT-23 near the power input.
Then one arithmetic question: a part is marked 2417. What is the earliest the
firmware on that board could have been built, and what does that let you say
when the vendor claims the unit shipped with version 3.2 from 2023?
Deliverable: four package/role pairs and the date-code answer.
Activity 1 (15 min): Ground, then voltage, then talk
Setup: the class demo board with a four-pad header, a multimeter, and a logic analyzer or scope. Your homework board stays in your bag for this one.
Run — in this order, because the order is the lesson:
- Ground first. Multimeter in continuity mode, one probe on a known ground (a shield can, or the negative terminal of a large electrolytic), the other walking the four pads. One will beep. Mark it.
- VCC second. Power the board. Measure each remaining pad against your ground. The one sitting steady at 3.3 V — or 1.8 V — and not moving is VCC. Write the number down. That number decides what you are allowed to connect in step 3.
- TX third. Put the analyzer on the two remaining pads and power-cycle the board. TX produces a burst of activity in the first second or two. RX idles high and does nothing.
Debrief: ask who connected anything before completing step 2. The voltage-domain callout exists because this is how probes and pads die, and doing the steps out of order once in a classroom is cheaper than doing it once on a customer’s only unit.
Deliverable: a labelled version of your Activity-1 photo — which pad is ground, VCC, TX, RX — and the measured idle voltage.
Activity 2 (10 min): Baud from the pulse, and prove it
Setup: week02-mystery.sr — or
the .vcd if you would rather not install
sigrok — plus PulseView or sigrok-cli. One channel named tx, sampled at
8 MHz. No hardware needed.
Run:
- Open the capture and zoom until individual bit cells are visible.
- Find the narrowest pulse in the capture. That is one bit time, because
somewhere in the data there is an isolated
1between two0s. - Compute
baud = 1 / bit_periodand round to the nearest standard rate. -
Prove it by decoding. From the command line:
$ sigrok-cli -i week02-mystery.sr \ -P uart:baudrate=<yours>:tx=tx:format=ascii -A uart=tx-dataSubstitute the rate you computed. Bytes with no printable form appear in brackets, so
[0A]is a newline.
Debrief: now re-run the decode at 9600, and at 115200. Neither is silent — every rate produces bytes:
| Rate | Bytes decoded |
|---|---|
| 9600 | 48 |
| 19200 | 88 |
| 38400 | 177 |
| correct | 290 |
| 115200 | 451 |
A wrong rate does not fail loudly. It hands you a plausible-looking pile of bytes, and the only thing distinguishing the right answer is that the output is readable and the byte count is stable. “The decoder produced output” is not evidence of anything. That is worth internalizing now, because the same trap is waiting in Week 9 with base addresses.
Deliverable: the measured pulse width, the computed baud, the nearest standard rate, and the decoded text.
Week 3 — I2C and SPI
Warm-up (5 min): Week 2 review — baud arithmetic sprint
Three numbers on the board; everyone computes, no calculators beyond the one on the phone:
- Narrowest pulse 8.68 µs → baud? Nearest standard rate?
- Narrowest pulse 104 µs → baud? Nearest standard rate?
- Your decoder produces plausible byte boundaries but every character is wrong. Is your problem the rate or the framing, and what do you change first?
Deliverable: three answers, with the reasoning on the third.
Activity 1 (15 min): Scan the bus, then watch the scan
Setup: your Feather with the OLED FeatherWing seated, plus a logic analyzer on SDA and SCL. The OLED is the only I2C device on the stack, which is what makes it a good first target — you know what the right answer is.
Run:
-
Scan the bus from the CircuitPython REPL:
import board i2c = board.STEMMA_I2C() while not i2c.try_lock(): pass print([hex(a) for a in i2c.scan()]) i2c.unlock()You should get
['0x3c']— the SH1107 controller’s default address. The STEMMA QT connector and the FeatherWing header pins are the same bus, so it does not matter which the Wing uses. - Now put the analyzer on SDA and SCL and run the scan again while capturing. Decode one transaction and identify, by hand, from the waveform: the START condition, the seven address bits, the R/W bit, and the ACK.
- Find a transaction for an address with nothing on it, and identify the NAK — SDA staying high during the ninth clock because nobody pulled it down.
Debrief: the scan is the NAK. i2c.scan() addresses all 112 valid
addresses and reports the ones that answered; every other address produced the
waveform you found in step 3. A tool’s output and the wire are the same story.
Deliverable: the scan result, an annotated screenshot of one decoded transaction with address / R/W / ACK marked, and one showing a NAK.
Activity 2 (10 min): JEDEC ID from a capture
Setup: week03-flash.sr — five channels at
16 MHz: CS_A, CS_B, SCK, MOSI, MISO. Decoding from a capture rather
than a live part keeps this separate from
HW #2 Task 2, which asks you
to do it in situ.
Two chip selects share one set of data lines, which is how a board with more
than one SPI device is always wired. Only one of them is talking to a flash.
sigrok-cli takes one annotation class at a time, so decode each direction
separately:
$ sigrok-cli -i week03-flash.sr \
-P spi:clk=SCK:mosi=MOSI:miso=MISO:cs=CS_B -A spi=mosi-data
$ sigrok-cli -i week03-flash.sr \
-P spi:clk=SCK:mosi=MOSI:miso=MISO:cs=CS_B -A spi=miso-data
Run:
- Decode the SPI transaction. The master sends one byte,
0x9F— Read JEDEC ID — and the part answers with three. - Byte 1 is the JEDEC manufacturer ID. Byte 2 is the memory type; byte 3 is
the capacity, encoded as a power of two:
0x18means 2^24 bytes = 16 MiB. - Identify the manufacturer and the most likely part number, and say how big the dump you are about to take in Week 5 will be.
Debrief: three bytes told you the vendor, the size of the image, and the command set you will use to read it. Note which line CS is on and what it did before the transaction — that is how you will find the flash on a board with four SPI devices sharing MOSI, MISO, and SCK.
Deliverable: the three ID bytes, the decoded manufacturer and capacity, the expected dump size in bytes, and a datasheet link.
Week 4 — JTAG and SWD
Warm-up (5 min): Week 3 review — two buses from memory
Reproduce this table without looking it up, then check it against the schedule:
| I2C | SPI | |
|---|---|---|
| Wires | ? | ? |
| How a device is addressed | ? | ? |
| Does the receiver acknowledge? | ? | ? |
Then one question: you are watching four signals. One of them goes low just before each burst of activity on the other three, and high again after. Which bus is this, which signal is that, and what does it tell you about how many devices are on the bus?
Deliverable: the filled table and the answer.
📋 Run Activity 1 first this week. It is the paper exercise, and it gives you the vocabulary you need before the probe touches a target.
Activity 1 (10 min): Decode an IDCODE
Setup: paper, or a hex calculator. No hardware.
Run: IEEE 1149.1 fixes the layout of the 32-bit IDCODE register:
| Bits | Field |
|---|---|
| 31–28 | Version |
| 27–12 | Part number |
| 11–1 | Manufacturer identity (JEDEC) |
| 0 | Always 1 |
Given 0x4BA00477, split it into the four fields and answer:
- What is the manufacturer ID, and who is it? Shift right by 1 and mask to 11 bits. That 11-bit value is not a flat number: the low 7 bits are the JEDEC ID and the top 4 are a continuation count naming which bank of JEP106 to look in. Split it before you search, or you will find nothing.
- What is the part number? Look it up — and note that on ARM parts this field names the debug port, not the CPU. Which part of the chip have you actually identified, and what have you not?
- Bit 0 is always 1. Why does that matter to a scanner that does not yet know whether anything is on the chain?
Debrief: bit 0 is how a scan distinguishes “a device answered” from “the
line is floating”. That single bit is why IDCODE is the first thing every
JTAG tool tries.
Deliverable: the four field values, the decoded manufacturer, and the answer to question 3.
Activity 2 (15 min): Halt a running core
Setup: a class-set Black Magic Probe and one of the demo Cortex-M target boards. Not your Feather — the Feather RP2040 RFM95 does not break out SWDIO and SWCLK, which is itself worth two minutes of discussion at the end.
Run:
$ arm-none-eabi-gdb
(gdb) target extended-remote /dev/ttyACM0
(gdb) monitor swdp_scan
(gdb) attach 1
(gdb) info registers
(gdb) x/16xw 0x20000000
monitor swdp_scan scans the Serial Wire Debug Port; use monitor jtag_scan
instead if the target is wired for JTAG. attach 1 attaches to the first
target the scan reported.
Debrief: you just stopped a running processor mid-instruction and read its RAM, without the firmware’s cooperation and without anything having been printed to a console. Contrast that with Week 2: UART gives you what the firmware chose to say; SWD gives you the silicon. Then ask what the vendor should have done — and note that the Feather in your bag already does it, by simply not bringing the pads out.
Deliverable: the scan output, the register dump, and one paragraph on what an attacker gains here that a UART console would not have given them.
Week 5 — Firmware extraction from memory
Warm-up (5 min): Week 4 review — what does the silicon give you?
Two questions, written answers:
- Name three things SWD gives you that a root shell on a UART console does not. Name one thing the UART console gives you that SWD does not.
- A device answers an IDCODE scan on the main SoC but not on the second core. Give two different explanations, and say how you would tell them apart.
Deliverable: both answers.
Activity 1 (15 min): Triage three blobs
Setup: blob-a.bin,
blob-b.bin and blob-c.bin,
plus binwalk. Three images from three different devices, none of them the
one HW #3 uses.
Run: for each blob, in under five minutes:
$ binwalk blob-a.bin
$ binwalk -E blob-a.bin
Classify each as one of:
- Structured and extractable — binwalk names a filesystem or a known header.
- Compressed — high entropy, but with visible structure or a recognizable header at the front.
- Encrypted or already-compressed-and-stripped — entropy pinned flat near 1.0 across the whole file with no structure anywhere.
- Mostly padding — long runs of
0xFFor0x00, with something small hiding in them.
For each, write one sentence on what you would do next.
⚠️
binwalk -Ewrites a PNG graph rather than printing numbers. Open it. The shape is the signal — a flat line at the top means stop, a line with structure means keep going.
Debrief: the third category is where assessments end, and knowing that in five minutes rather than five hours is the entire value of the technique. A flat 1.0 means either good encryption or somebody’s compression — and the header, or its absence, is usually what tells you which.
Deliverable: three classifications, the entropy graph for each, and the next-step sentence.
Activity 2 (10 min): Carve it yourself
Setup: whichever blob from Activity 1 binwalk found a filesystem in.
Run: binwalk told you an offset. Use it by hand rather than letting
binwalk -e do it:
$ binwalk blob-a.bin
DECIMAL HEXADECIMAL DESCRIPTION
65536 0x10000 SquashFS filesystem, little endian, version 4.0, ...
$ dd if=blob-a.bin bs=1 skip=65536 of=rootfs.sqfs status=none
$ unsquashfs -d root rootfs.sqfs
$ ls root/
Debrief: two points, both worth the ten minutes.
binwalk -eruns external extractors against attacker-controlled data. A malformed archive crafted by the vendor you are assessing is a code execution path into your analysis machine.ddis auditable; the wrapper is convenient. Choose deliberately, and run the wrapper in a container.- You did not need the length.
ddwithskipand nocountruns to the end of the file, andunsquashfsreads the size out of the superblock. Knowing which tool will find its own end saves you a second binwalk run.
Deliverable: the dd command you ran, the directory listing, and one
sentence on why you would not run binwalk -e on a client’s firmware on your
own laptop.
Week 6 — Networks, WiFi, BLE, and firmware from the wire
Warm-up (5 min): Week 5 review — read the entropy
Three entropy graphs on the projector. For each, say what it is and what you would do next:
- Flat at roughly 0.5 across the whole file.
- Flat near 1.0 across the whole file.
- Near 1.0 for most of the file, dropping to about 0.3 for the last eighth.
Then: which of the three is the one you most want, and why?
Deliverable: three classifications and the answer.
Activity 1 (15 min): Capture your own device updating
Setup: your Feather with the Airlift FeatherWing, your laptop, and
tcpdump or Wireshark. You are making your own capture here —
HW #3 Task 5 has you do
the recovery against the course capture, so do not open that file in class.
Run:
-
Serve a small file from your laptop over plain HTTP:
$ python3 -m http.server 8000 - Start a capture on the interface your laptop’s WiFi is on.
- Have the Airlift fetch the file. The ESP32SPI requests example you already got running in Lab 2 does this in three lines.
- In Wireshark, find the transfer with Statistics → Conversations → TCP, sorted by bytes. Recover the file with File → Export Objects → HTTP.
-
Hash what you recovered and compare it against the original:
$ sha256sum served-file.bin recovered-file.bin
Debrief: you extracted a file off the wire in about four minutes, with no access to the device. Now the two questions that matter:
- Your device is a microcontroller with a few hundred kilobytes of RAM. What does that cost it to do this over TLS instead, and which corners do vendors cut when they decide it is too expensive?
- Suppose the file had arrived with a published SHA-256 alongside it. Would that have stopped someone in your position from substituting a different file? Be precise about who publishes the hash and over what channel.
Deliverable: the conversation statistics screenshot, the two matching hashes, and written answers to both debrief questions.
Activity 2 (10 min): BLE advertising census
Setup: a phone with nRF Connect or LightBlue, or a laptop with
bluetoothctl. Everyone at once — the room is the dataset.
Run:
$ bluetoothctl
[bluetooth]# scan on
Over five minutes, build a table of the ten strongest advertisers:
| Address | Type | Name (if any) | RSSI | Manufacturer data | | — | — | — | — | — |
For each, note whether the address is public, random static, or resolvable private. Then identify one device by its manufacturer-specific data alone — the first two bytes are a Bluetooth SIG company identifier.
Debrief: count how many devices in the room advertise a stable identifier. Those are trackable across locations and sessions by anyone with a $30 radio, with no pairing and no interaction. Then ask whose devices they are — the answer is usually “ours”, and that lands better than any slide about privacy.
Deliverable: the table, the company identifier you decoded, and a count of stable versus rotating addresses.
Week 7 — RF capture and spectrum analysis
📅 One meeting this week (Monday 9 November; the university is closed on Veterans Day). Both blocks run in that meeting, with the warm-up between them as a breather.
Warm-up (5 min): Week 6 review — which extraction path?
Three scenarios. For each, name the cheapest extraction path and what would have to be true for it to work:
- The vendor publishes firmware images on a public support page.
- The device pulls updates over HTTPS with certificate pinning, and the UART is silent.
- The device has a U-Boot prompt on an unlabelled four-pad header.
Then: in Activity 1 last week you verified a hash matched. What did that prove, and what did it not prove?
Deliverable: three paths and the hash answer.
Activity 1 (15 min): Find your partner’s transmitter
Setup: one SDR per pair — an RTL-SDR is enough for this — and a partner pair with a Feather transmitting on the 915 MHz band.
Run:
-
Sweep the whole US 915 MHz ISM band before you tune anything:
$ hackrf_sweep -f 902:928 -w 100000 -1 -r sweep.csvor, with an RTL-SDR:
$ rtl_power -f 902M:928M:25k -i 20 -e 2m -g 40 us915.csv - Have the other pair transmit repeatedly. Find the peak. Write down its centre frequency.
- Now tune a capture to it and confirm you can see it in a waterfall.
- Break it on purpose. Re-tune 2 MHz away, keeping the same bandwidth. The signal disappears completely. Then set the sample rate too low to span the channel and watch what survives.
Debrief: the three numbers — centre frequency, sample rate, gain. Step 4 is the point: a capture with the wrong centre frequency looks exactly like “there is no signal here”, and that misreading has ended more assessments than any technical limitation.
Deliverable: the sweep output, the peak frequency, a waterfall screenshot with the signal visible, and one with it missing because you re-tuned.
Activity 2 (10 min): Name that modulation
Setup: the four week07-signal-*.cs8 captures, opened in
inspectrum. No transmitting.
They are complex signed 8-bit at 1 Msps, 100 ms each. A raw IQ file records none of that, so you have to be told:
$ inspectrum -r 1000000 -f cs8 week07-signal-1.cs8
Run: identify each by eye, using both the waterfall and the time-domain amplitude. You are looking for:
- OOK — carrier present or absent; amplitude drops to the noise floor between symbols; one vertical line in the waterfall.
- FSK — constant amplitude, two (or more) discrete frequencies; parallel lines in the waterfall.
- LoRa — diagonal sweeps. Chirps ramp across the channel and wrap around; unmistakable once you have seen one.
- FHSS — short bursts scattered across a wide band, each at a different frequency, repeating on a schedule.
For each, write down the one visual feature that decided it for you.
Debrief: you did not demodulate anything. Modulation identification is a visual skill and it is fast, and it tells you which of the Week 8 pages to open before you have written a line of DSP.
Deliverable: four identifications, each with the deciding feature named.
Week 8 — Radio hacking: LoRa, Zigbee, Thread, and Matter
Warm-up (5 min): Week 7 review — sample-rate arithmetic
- You need to see a 500 kHz-wide channel. What is the minimum sample rate, and what would you actually set, and why is that more than the minimum?
- At 2 Msps with 8-bit I and 8-bit Q samples, how many megabytes does a 30-second capture consume?
- Your capture shows the signal pinned at full scale with flat tops. What is wrong, and which knob fixes it?
Deliverable: three answers with the arithmetic shown.
Activity 1 (15 min): Replay, and the counter that stops it
Setup: two pairs working together. Pair A transmits, Pair B captures and replays, using the RFM95 radios you already have running from Lab 1.
Run:
- Pair A sends a fixed message on 915 MHz; Pair B’s receiver prints it.
- Pair B records the payload it received, then transmits those same bytes back. Pair A’s receiver accepts it — it has no way to tell the difference, because there is none.
- Now Pair A adds a counter to the payload and rejects any message whose counter is not greater than the last one seen. Ten lines of CircuitPython.
- Pair B replays the same captured bytes again. Rejected.
- Now break it again. Pair B captures a newer message and replays that. What happens? What if Pair B suppresses Pair A’s transmission and replays it later?
Debrief: step 5 is where this activity earns its time. A frame counter stops naive replay and nothing else — it does not authenticate the sender, it does not provide confidentiality, and it has a well-known failure at rollover and at rejoin. That is precisely the LoRa versus LoRaWAN boundary: the counter is a LoRaWAN feature and your raw RFM95 link has nothing.
⚠️ Transmit only on the bench radios, at the lowest power that works. You are in an ISM band, but the building has real LoRa traffic in it and Lab 3 depends on that traffic staying intact.
Deliverable: the code diff adding the counter, evidence of the accepted replay before and the rejection after, and your answer to step 5.
Activity 2 (10 min): Key management on paper
Setup: paper. No hardware.
Run: a Zigbee light bulb ships with a well-known default link key. It is joined to a network in front of you, and you are listening.
- What can you recover from the join exchange, and what does it get you?
- Once you have it, what else on that network can you read — and why is the answer “everything”, not “that bulb”?
- An install code changes this. What exactly does it change, where does the code come from, and why do so many devices ship without one?
- Matter uses a J-PAKE-based commissioning flow with a per-device passcode and attestation certificates. Which of problems 1–3 does that actually solve, and which does it leave alone?
Debrief: question 2 is the one to dwell on. A single shared network key means the security of the network equals the security of its weakest-provisioned device, and that is an architectural property, not a bug in any one product.
Deliverable: four written answers.
Week 9 — Software reverse engineering and bootloaders
Warm-up (5 min): Week 8 review — LoRa versus LoRaWAN
- Which layer provides the AppKey, and which provides nothing at all?
- Name one thing a LoRaWAN frame counter protects against and two things it does not.
- OTAA versus ABP — which one re-derives session keys on join, and why does that matter after a power cycle?
Deliverable: three answers.
Activity 1 (15 min): Two words, one base address
Setup: week09-cm.bin, xxd, and Ghidra. A
raw flash image — no ELF, no headers, no symbols — from a different device and
needing a different method than
HW #5 Task 1.
Run: a Cortex-M image begins with a vector table, and its first two words tell you where the image belongs.
$ xxd -e -g4 -l 8 week09-cm.bin
00000000: 20004000 100001c1 .@. ....
- Word 0 is the initial stack pointer:
0x20004000. Cortex-M SRAM starts at0x20000000, so that is a RAM address, and it confirms you are looking at a vector table rather than at random data. - Word 1 is the reset handler:
0x100001c1. It is odd — bit 0 is the Thumb bit, which is always set on a Cortex-M branch target, so the real address is0x100001c0. - That address lies in flash. Which flash depends on the part:
0x08000000on an STM32,0x10000000on an RP2040. Round the reset handler down to the region base and you have your load address.
Load the image in Ghidra at that base, ARM:LE:32:Cortex, and let
auto-analysis run. It will not find much on its own — a raw blob has no entry
point, so Ghidra has nothing to start disassembling from. Give it one: go to
the reset handler address, press D to disassemble, and F to make it a
function. Re-run auto-analysis and the call graph unrolls from there.
Debrief: two 32-bit words, read in ten seconds, replaced a pointer histogram. Always check for a header or a vector table before you reach for the statistical method — and note that this trick works because Cortex-M mandates the layout. An ARM Linux image has no such guarantee, which is exactly why HW #5 is harder than this.
Deliverable: both words, your derived base address with the reasoning, and a screenshot of the Ghidra import dialog.
Activity 2 (10 min): Prove the base address is wrong
Setup: the same image, still open in Ghidra.
Run:
- In your correctly-loaded image, open Window → Defined Strings and note how many of them have a cross-reference. Pick one and follow it to the function that uses it.
- Now import the same file again, at base
0x00000000. - Repeat step 1 on the second import, and compare.
You should see something close to this:
| Base | Functions | Strings | Strings with xrefs |
|---|---|---|---|
| the one you derived | 6 | 13 | 13 |
0x00000000 |
0 | 12 | 0 |
- One more check on the wrong load: go to address
0x00000004and read the reset vector. It still contains the same value it always did — but that address is now nowhere near the image you have loaded.
Debrief: a wrong base address does not produce an error. Ghidra imports it without complaint, shows you bytes, and disassembles anything you point it at. What it cannot do is resolve anything: the literal pools still hold absolute addresses, and at the wrong base those addresses land outside the program, so every string reference dangles.
That is why the test is not “does it disassemble” but “do cross-references resolve” — and it is the same lesson as the baud rate in Week 2, one layer up. Both tools produce confident output at the wrong setting. Step 4 is the cheapest tell of all: if the reset vector points somewhere your image does not cover, your base is wrong before you have disassembled a single instruction.
Deliverable: the two tables side by side, one screenshot of a resolving cross-reference and one of a dangling one, and the reset-vector observation from step 4.
Week 10 — IoT operating systems and secure architecture
Warm-up (5 min): Week 9 review — evidence and trust
- You used two words to find a base address. Name a second source of evidence for a load address, and one kind of image where neither is available.
- Secure boot verifies the next stage before running it. Name the three things that verification has to establish, and the one thing an on-device SHA-256 check establishes on its own.
Deliverable: both answers.
Activity 1 (15 min): Five provisions, one device
Setup: ETSI EN 303 645 V3.1.3 open on one screen, your term’s accumulated findings on the other.
Run: the standard has thirteen outcome-focused clauses. Take these five:
| Clause | Provision |
|---|---|
| 5.1 | No universal default passwords |
| 5.3 | Keep software updated |
| 5.5 | Communicate securely |
| 5.6 | Minimize exposed attack surfaces |
| 5.7 | Ensure software integrity |
For each, assess the device you have been working on all term as pass, fail, or not assessable, and cite the specific evidence — a capture, a dump offset, a file path, a photo. “Not assessable” is a legitimate verdict and it requires you to say what you would need in order to decide.
Then write one finding properly, in this shape:
- Title — one line, stating the defect, not the technique.
- Provision — the numbered clause it fails, e.g.
5.3-10. - Evidence — what you observed, reproducibly.
- Impact — what an attacker gains, stated in terms of the device’s job.
- Remediation — what the vendor changes, specifically.
Debrief: read two findings aloud and let the class attack them. The most common failure is an Impact section that describes the technique (“an attacker can intercept the update”) rather than the consequence (“an attacker on the same network can install arbitrary firmware, permanently”). The second is a Remediation that says “use TLS” without saying what the device does about certificate validation on a part with no real-time clock.
Deliverable: the five verdicts with evidence, and one fully written finding.
Activity 2 (10 min): Three changes
Setup: your findings from Activity 1. Pairs, then whole class.
Run: you have spent ten weeks building an attack chain against this device. Now spend ten minutes on the other side.
- Write down the minimum three changes that would have broken your chain. Not a wish list — three, and they have to be changes a product team could actually ship.
- For each, estimate the cost in the terms a vendor uses: unit BOM cost, engineering time, support burden, and what it breaks in the field.
- Rank them by cost-effectiveness and defend the ranking.
Debrief: the answers converge on remarkably cheap things — do not populate the debug header, do not ship a universal default password, sign the update and check the signature. None of those has a meaningful BOM cost. Then ask the real question: if they are this cheap, why is the device in front of you the way it is? The answer is organizational, not technical, and recognizing that is the difference between a report that gets acted on and one that does not.
Deliverable: three changes, three cost estimates, the ranking, and a paragraph on why the vendor did not make them.
Key takeaways
- The warm-up is not filler. Five minutes of recall before new material is the cheapest retention you will get, and the arithmetic drills (baud, sample rate, entropy) are exactly the arithmetic you will fumble under time pressure on the homework.
- Order matters on the bench. Ground, then voltage, then signal. Every activity that touches hardware enforces this order, because the failure mode of getting it wrong is destroyed hardware rather than a wrong answer.
- Break it on purpose. Several activities end by deliberately misconfiguring something — the wrong baud, the wrong centre frequency, the wrong base address. Recognizing the signature of each mistake is worth more than getting it right first time, because in the field nobody tells you which mistake you made.
- Tool output and the wire are the same story. The I2C scan is a sequence of
NAKs. The binwalk offset is a
ddyou could have run yourself. Understanding the tool as a convenience over something you could do by hand is what lets you proceed when the tool gives up. - Every activity ends in the notebook. Undocumented work did not happen — in this class, and in a real engagement.
References
- ETSI EN 303 645 V3.1.3, Cyber Security for Consumer IoT: Baseline Requirements — https://www.etsi.org/deliver/etsi_en/303600_303699/303645/03.01.03_60/en_303645v030103p.pdf
- FCC Equipment Authorization Search — https://apps.fcc.gov/oetcf/eas/reports/GenericSearch.cfm
- FCC ID Search, the FCC’s own explanation of the ID format — https://www.fcc.gov/oet/ea/fccid
- fccid.io, a friendlier front end to the same filings — https://fccid.io
- JEDEC JEP106, Standard Manufacturer’s Identification Code — https://www.jedec.org/standards-documents/docs/jep-106ab
- Bluetooth SIG Assigned Numbers, including company identifiers — https://www.bluetooth.com/specifications/assigned-numbers/
- sigrok / PulseView documentation, including the protocol decoders — https://sigrok.org/wiki/Main_Page
- Black Magic Debug documentation, including the GDB monitor commands — https://black-magic.org/docs/
- Adafruit 128x64 OLED FeatherWing, CircuitPython — https://learn.adafruit.com/adafruit-128x64-oled-featherwing/circuitpython
- Adafruit Feather RP2040 RFM95 pinouts — https://learn.adafruit.com/feather-rp2040-rfm95/pinouts
- Adafruit AirLift FeatherWing, CircuitPython — https://learn.adafruit.com/adafruit-airlift-featherwing-esp32-wifi-co-processor-featherwing/circuitpython
- OWASP IoT Security Testing Guide — https://owasp.org/www-project-iot-security-testing-guide/
- inspectrum, for the Week 7 IQ captures — https://github.com/miek/inspectrum
- SigMF, the metadata format raw IQ files lack — https://sigmf.org/
Related course pages: Activity Handouts · Schedule · Tools of the Trade · UART · I2C · SPI · JTAG and SWD · Dumping flash · Radio Protocols · LoRa and LoRaWAN · Zigbee · Thread and Matter · Software Reverse Engineering · Attacks
🛠️ Maintenance note: the handout files are generated, and the generator carries a verification pass — run it at the start of each offering to confirm every artifact still behaves the way the activity above says it does. The deconfliction notes throughout assume the current homework task numbering; if a homework task moves, re-check that the matching activity still rehearses the skill rather than solving the assignment. ETSI EN 303 645 is at V3.1.3 (September 2024) and the clause numbering in Week 10 tracks that revision. The Week 4 activity assumes Black Magic Probe firmware where the scan command is
monitor swdp_scan; checkmonitor helpif the probe’s firmware has moved on. The Week 9 figures come from Ghidra’s raw binary loader withARM:LE:32:Cortex— a future release may find a different number of functions, but the xref column is structural and will not move.