Introduction
One day, I was playing around at the office with Flipper Zero’s “bad USB” mode. A colleague saw me and, while talking about USB sticks / ducks, he gave me one that was lost in a drawer. He thought I could give it a second life.
At first, I was skeptical about testing on any computer. I didn’t know its contents, and my standard paranoia level is a bit above normal. Reading this question on superuser also triggered me: How do I safely investigate a USB stick found in the parking lot at work?
Did I trust my collage? Absolutely, yes. Could I ask him about the contents or its origin? Of course, but an idea had already been implanted in my mind:
Is it possible to analyze the contents of a poor’s man rubber ducky without plugging in the USB and risking a possible infection?
Was this idea a clear sign to dive into low-level technical hell? Of course, but mentally, I had already fallen down the rabbit hole…
Previous knowledge
As I mentioned before, I had some experience experimenting with Flipper Zero. It’s so easy, plug and play, very skiddie.
I was already familiar with the original Rubber Ducky USB by Hak5 and its cheaper alternative, which I suspected my new USB stick might be.
A Rubber Ducky USB is a type of hardware-based hacking tool that looks and functions like a regular USB flash drive but can inject pre-programmed keystroke payloads into a computer when plugged in. It essentially acts as a keyboard and can execute a series of commands very quickly. These commands can range from harmless pranks to serious security breaches.
The USB Rubber Ducky uses a special scripting language called DuckyScript. The following example opens Notepad (on a Windows machine) and writes Hello World!
.
REM This is a comment
DELAY 500
GUI r
DELAY 500
STRING notepad
ENTER
DELAY 500
STRING Hello World!
The original Rubber Ducky USB was released around 2010 and used the Atmel AT32UC3B1256 microcontroller, a 32-bit AVR. Since then, it has evolved with more features like OS detection, defense evasion, and keystroke reflection.
I know, I’m so late to the party that I even had to search using the WayBackMachine.
Component identification
So what do we have here?
There is a USB device and a broken 3D-printed case. Very phishy.
Let’s take a closer look to identify the components of the device:
ATMEL20U TINY85 1804HRV
-> Atmel’s ATtiny85 microcontroller with an 8-bit AVR CPU.78M05 A1718
-> 3-terminal linear voltage regulator designed to provide a stable 5V output.S4
diode- A couple of LEDs
- Tiny SMD resistors and capacitors
- Missing component (???)
Searching for ATtiny85 + badusb
instantly leads to Digispark.
Learning the basics
Before doing anything with the device, I had to learn a lot about electronics and hardware hacking. Not in the order presented here, but I’ve tried to summarize and explain the basics.
It’s very possible that I’m wrong on some things, so please have some mercy.
Digispark
The Digispark is a tiny, affordable microcontroller development board based on the ATtiny85. It was designed by Digistump and launched around 2012-2013. With its compact size, built-in USB connector, and low cost (usually between 1€ and 5€), it has become very popular among hobbyists, makers, and developers.
This small USB board is also perfect for implementing a Rubber Ducky clone. It can be programmed to mimic keyboard inputs and execute pre-defined scripts and commands when plugged into a computer, acting as a custom USB Human Interface Device (HID).
Unlike the Rubber Ducky, the Digispark doesn’t have an integrated SD card, so the payloads have to be flashed into memory instead. This fact confirms that my goal is still possible :)
The board I have seems like a copy of the original Digispark. Here we can see the differences:
At the time of writing this post, the Digispark documentation is no longer available. That makes my task harder and funnier. Web Archive of Digispark’s wiki
The board has very few components, and the most interesting part is the ATtiny85. Here is the original schematic of the Digispark board:
ATTiny85
The brain of the Digispark is the ATTiny85. It is a microcontroller that belongs to the family of AVR microcontrollers, developed by Atmel since 1996 (acquired by Microchip Technology in 2016). AVR is a modified Harvard architecture 8-bit RISC single-chip microcontroller. The ATTiny85 was released in 2005.
This is the ATtiny85 Datasheet, and it has been the main and most reliable source of information about the microcontroller. The 200+ pages are hard to read but have been extremely useful for understanding how it works in detail.
The ATtiny microprocessor does not have hardware USB support, and the Digispark board doesn’t have a dedicated chip to handle USB. So, the USB connection goes directly to the ATtiny chip, which lacks built-in USB support. The V-USB software implementation is used to provide a software emulation of the hardware that the chip lacks. I discovered and loved the term bit banging, and how unreliable communications can be. I am amazed that this even works. But there is more.
The chip does not include UART for serial protocol either, so that also has to be bit banged. However, the ATtiny85 has Universal Serial Interface (USI), a multipurpose hardware communication module that can be used to implement SPI or I2C serial interfaces.
While writing the article and documenting several months later, I found the YouTube video Why a Digispark should not be your first Arduino. Thank God I didn’t find that before, because I would have dismissed my project entirely. The video is pretty good and very well explained, but truly depressing.
Conclusion of the video:
“If you have microcontroller programming experience generally, or are really curious about bit-banging USB, or have a high pain tolerance then, you know, knock yourself out.”
Yep, that’s exactly what happened to me.
Let’s continue with my ignorance.
In order to program the Digispark, it uses a bootloader with software emulations to take advantage of the built-in USB connector. But what is a bootloader?
Bootloaders
According to the Digispark wiki:
The bootloader is the code that is pre-programmed on your Digispark and allows it to act as a USB device so that it can be programmed by the Arduino IDE.
In more general terms, a bootloader is a small program that runs when a microcontroller or computer is powered on (before the reset interrupt). Its primary function is to initialize the hardware and load the main operating system or application software into memory.
In the context of microcontrollers like the Digispark, the bootloader enables the device to be self-programmed via USB without needing an external dedicated hardware programmer. It essentially acts as an intermediary that facilitates firmware updates and custom code uploads.
Different AVR bootloaders:
- The original Digispark came with the micronucleus version 1.02
- Recent versions of micronucleus (V2.6)
- micronucleus fork
- Arduino bootloaders
- USB bootloaders based on V-USB
These bootloaders take up between 1/3 and 1/5 of the total Flash memory, and our ATtiny only has 8KB, so the amount of free space available for the actual code is very limited.
At this point, my main hypothesis was that my device contained a specific bootloader and custom software for emulating a USB keyboard. Is it possible to extract the payload that is executed when the USB is plugged in? If I finally get a memory dump of the Flash, how do I know which bootloader is flashed? Where should it be located? What does it look like? Too many questions.
AVR
Let’s continue learning the basics.
AVR (Alf (Egil Bogen) and Vegard (Wollan)’s RISC processor) is a family of microcontrollers with a modified Harvard architecture 8-bit RISC single-chip. It is used in many different things, from industrial PLCs and home electronics to IoT devices.
(modified) Hardvard architecture definition:
The Harvard architecture is a computer architecture with separate storage and signal pathways for instructions and data.
The modified Harvard architecture is a variation of the Harvard computer architecture that, unlike the pure Harvard architecture, allows memory that contains instructions to be accessed as data.
Reduced Instruction Set Computer (RISC) definition:
Reduced Instruction Set Computer (RISC) is a computer architecture designed to simplify the individual instructions given to the computer to accomplish tasks.
Compared to the instructions given to a Complex Instruction Set Computer (CISC), a RISC computer might require more instructions (more code) in order to accomplish a task because the individual instructions are written in simpler code.
Ok, there is a lot to learn here. The Instruction Set Manual (Atmel 2016 or Microchip 2020) will be very useful later, but let’s focus on our ATtiny device.
Memory Map
In section 5. AVR Memories of the datasheet we can find the different memories of the ATtiny85.
Flash Program Memory
The ATtiny85 contains 8K bytes of reprogrammable Flash memory, which is where the program code is stored. Since all AVR instructions are 16 or 32 bits wide, the Flash is organized as 4096 x 16.
SRAM Data Memory
The first 32 locations of the data memory are the Register File, the next 64 locations are the standard I/O memory (I/O registers for accessing “hardware”), and the last 512 locations address the internal data SRAM for run-time variables.
EEPROM Data Memory
The ATtiny85 contains 512 bytes of data EEPROM memory. It is organized as a separate data space, in which single bytes can be read and written. This memory can be used by user code to store data that needs to be preserved when the MCU is turned off.
Lock Bits and Fuses
The lock/fuses form a fourth memory area available for programming. Just a few bytes set internal hardware configurations and define different features.
Fuse bit settings for the ATTiny85:
- Low Fuse Byte (lfuse): Clock source selection, clock start-up time, clock output and clock divide.
- High Fuse Byte (hfuse): Brown-out detection, preserve EEPROM through chip erase, watchdog timer, Enable Serial Program and Data Downloading, DebugWIRE, and External Reset pin functionality.
- Extended Fuse Byte (efuse): Self-Programming mode.
- Lockbit Fuse Preset (lock): Memory lock features for additional security.
Note that fuses are read as logical zero, “0”, when programmed. So “1” means unprogrammed and “0” programmed.
The website AVR Fuse Calculator is an amazing resource to understand the possible features of a wide variety of AVR chips. Another alternative with the lock bits is eleccelerator.com/fusecalc.
Dumping memory
Let’s be real, before doing all my research, I knew I didn’t want to plug in the USB directly, so I had already ordered an Integrated Circuit Clip.
Isn’t it cool to pinch a chip and read its secrets? Maybe this whole project was just an excuse to do that 😎.
So, I bought a SOIC8 Test Clip with a bunch of adapters and a cheap CH341A programmer, which is usually used to flash BIOS EEPROMs (?). I don’t know.
At that point, I was a bit overwhelmed and confused. A lot of the documentation was old, unmaintained, deprecated, discontinued, or just not working at all.
First attempt: CH341
The best article I found about using the CH341A to interact with the ATtiny was Flash the AVR Atmel ATtiny Chips with a Modified CH341A ISP Programmer by Eric Draken.
It had the same CH341A programmer pack as me, and was very well explained, so I followed it and tried to reproduce it myself.
The first thing the article says is:
The problem is that the CH341A is not designed for ATtiny chips, but for flashing motherboard BIOS chips.
Additionally, this programmer has another problem: the data lines are at 5V and could damage some chips. I found out that it’s a very disliked programmer in the hardware hacking community.
Great choice :D
Still, I decided to keep trying to use the programmer to dump the memory through SPI.
The SCK
, MISO
, MOSI
and Reset
(CS
) pins are labeled on the ATtiny datasheet and need to be matched to the CH341A/B.
The voltage should be set to 3.3V. Shorten pins 1 and 2 with a jumper.
I used the tool chavrprog, an “AVR programmer based on the Chinese CH341A”.
As already mentioned in the article, the CH341A is not designed for this, and I had to use an unreliable breadboard connection. Even the cables were not the same color.
Once the connection seemed fine, we could finally dump something:
$ sudo ./chavrprog -d tiny85 -f
Device reported its revision [4.03]
AVR answered!
Signature read as 1e 93 b
Signature is correct
Fuses: low high ext
e1 dd fe
$ sudo ./chavrprog -d tiny85 -L
Device reported its revision [4.03]
AVR answered!
Signature read as 1e 93 b
Signature is correct
Lock byte: ff
$ sudo ./chavrprog -d tiny85 -r f ./dump_chavrprog.bin
Device reported its revision [4.03]
AVR answered!
Signature read as 1e 93 b
Signature is correct Reading to ./dump_chavrprog.bin...
Summary of the fuse values:
Fuse | Value | Description |
---|---|---|
lfuse | 0xe1 |
PPL Clock; Start-up time 1K CK + 64 ms |
hfuse | 0xdd |
Enable Serial Program and Data Downloading and Brown-out Detector at 2.7 V |
efuse | 0xfe |
Self Programming enabled |
lock | 0xff |
No memory lock features enabled |
The progress was promising. The signature and the fuse bits seemed right, but the dump output didn’t make any sense. The dump was like that for all 8K bytes. The binwalk
tool did not detect anything either, of course.
$ xxd dump_chavrprog.bin
00000000: bfcb 7f01 0202 0303 0404 0505 0606 0707 ................
00000010: 0808 0909 0a0a 0b0b 0c0c 0d0d 0e0e 0f0f ................
00000020: 1010 1111 1212 1313 1414 1515 1616 1717 ................
00000030: 1818 1919 1a1a 1b1b 1c1c 1d1d 1e1e 1f1f ................
00000040: 2020 2121 2222 2323 2424 2525 2626 2727 !!""##$$%%&&''
00000050: 2828 2929 2a2a 2b2b 2c2c 2d2d 2e2e 2f2f (())**++,,--..//
00000060: 3030 3131 3232 3333 3434 3535 3636 3737 0011223344556677
00000070: 3838 3939 3a3a 3b3b 3c3c 3d3d 3e3e 3f3f 8899::;;<<==>>??
00000080: 4040 4141 4242 4343 4444 4545 4646 4747 @@AABBCCDDEEFFGG
00000090: 4848 4949 4a4a 4b4b 4c4c 4d4d 4e4e 4f4f HHIIJJKKLLMMNNOO
000000a0: 5050 5151 5252 5353 5454 5555 5656 5757 PPQQRRSSTTUUVVWW
000000b0: 5858 5959 5a5a 5b5b 5c5c 5d5d 5e5e 5f5f XXYYZZ[[\\]]^^__
000000c0: 6060 6161 6262 6363 6464 6565 6666 6767 ``aabbccddeeffgg
000000d0: 6868 6969 6a6a 6b6b 6c6c 6d6d 6e6e 6f6f hhiijjkkllmmnnoo
000000e0: 7070 7171 7272 7373 7474 7575 7676 7777 ppqqrrssttuuvvww
000000f0: 7878 7979 7a7a 7b7b 7c7c 7d7d 7e7e 7f7f xxyyzz{{||}}~~..
00000100: 8080 8181 8282 8383 8484 8585 8686 8787 ................
...
00001fc0: e0e0 e1e1 e2e2 e3e3 e4e4 e5c9 e6e6 e7e7 ................
00001fd0: e8e8 e9e9 eaea d6eb ecec eded eedc efef ................
00001fe0: f0f0 f1f1 f2f2 f3f3 f4f4 f5f5 f6f6 f7f7 ................ 00001ff0: f8f8 f9f9 fafa fbfb f8ff fdff feff ffff ................
I even tried to solder a permanent programmer rig like the one in the article. Sorry for the poor soldering skills.
But I obtained the same results.
I also tried another tool, avrdude, a very popular program for downloading and uploading the on-chip memories of AVR microcontrollers.
sudo avrdude -v -c ch341a -p t85 -B 8MHz -U flash:r:dump_avrdude.bin
Some bits were off, but it was almost the same result. What am I doing wrong?
Second attempt: Arduino
Several weeks later, with renewed energy, I tried again using an Arduino UNO R3. That should have been my first choice instead of the CH341A, as it’s easier, better documented, more standard, and more reliable.
First, we have to turn the Arduino into an in-circuit programmer using the Arduino ISP bootloader. The official documentation is excellent, and there’s nothing more to add here. Just follow the instructions.
According to the Arduino reference, the SPI pins are 10 (SS/CS), 11 (COPI/MOSI), 12 (CIPO/MISO), and 13 (SCK).
I found that MISO/MOSI are now referred to as CIPO/COPI. Great initiative! A Resolution to Redefine SPI Signal Names
Connecting the board…
…and reading with avrdude
Checking at the fuse values again:
$ sudo avrdude -v -c avrisp -p t85 -P /dev/ttyACM0 -b 19200 -U lfuse:r:-:h -U hfuse:r:-:h -U efuse:r:-:h -U lock:r:-:h
And dumping the memory:
$ sudo avrdude -v -c avrisp -p t85 -P /dev/ttyACM0 -b 19200 -U flash:r:dump_arduino.bin
...
avrdude: AVR device initialized and ready to accept instructions
avrdude: device signature = 0x1e930b (probably t85)
avrdude: processing -U flash:r:dump_arduino.bin:r
avrdude: reading flash memory ...
Reading | ################################################## | 100% 5.77 s avrdude: writing output file dump_arduino.bin
But once again, I still got the same similar results.
$ xxd dump_arduino.bin
00000000: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000010: ffff ffff ffff ffff ffff 0d0d 0e0e 0f0f ................
00000020: 1010 1111 1212 1313 1414 1515 1616 1717 ................
00000030: 1818 1919 1a1a 1b1b 1c1c 1d1d 1e1e 1f1f ................
00000040: 2020 2121 2222 2323 2424 2525 2626 2727 !!""##$$%%&&''
00000050: 2828 2929 2a2a 2b2b 2c2c 2d2d 2e2e 2f2f (())**++,,--..//
00000060: 3030 3131 3232 3333 3434 3535 3636 3737 0011223344556677
00000070: 3838 3939 3a3a 3b3b 3c3c 3d3d 3e3e 3f3f 8899::;;<<==>>??
00000080: 4040 4141 4242 4343 4444 4545 4646 4747 @@AABBCCDDEEFFGG
00000090: 4848 4949 4a4a 4b4b 4c4c 4d4d 4e4e 4f4f HHIIJJKKLLMMNNOO
000000a0: 5050 5151 5252 5353 5454 5555 5656 5757 PPQQRRSSTTUUVVWW
000000b0: 5858 5959 5a5a 5b5b 5c5c 5d5d 5e5e 5f5f XXYYZZ[[\\]]^^__
000000c0: 6060 6161 6262 6363 6464 6565 6666 6767 ``aabbccddeeffgg
000000d0: 6868 6969 6a6a 6b6b 6c6c 6d6d 6e6e 6f6f hhiijjkkllmmnnoo
000000e0: 7070 7171 7272 7373 7474 7575 7676 7777 ppqqrrssttuuvvww
000000f0: 7878 7979 7a7a 7b7b 7c7c 7d7d 7e7e 7f7f xxyyzz{{||}}~~..
00000100: 8080 8181 8282 8383 8484 8585 8686 8787 ................
00000110: 8888 8989 8a8a 8b8b 8c8c 8d8d 8e8e 8f8f ................
00000120: 9090 9191 9292 9393 9494 9595 9696 9797 ................
00000130: 9898 9999 9a9a 9b9b 9c9c 9d9d 9e9e 9f9f ................ ...
Conclusion (?)
Final conclusion: disappointment.
After failing miserably so many times, I finally gave up.
My main hypothesis is that the sequential data in the hexdumps might be a leftover from production tests, but I am not sure at all and couldn’t find anything related to that.
So, in the end, with a lot of frustration, I decided to forget about the original data and try something myself. But it’s better to explain that in another post…