Dispo Adventures, Episode 1: Reverse-engineering and “running Windows 95” on a disposable vape with a colour LCD screen

As seen on Hackaday!

TL;DR: Disposable vapes can have LCD screens…?! At least they’re moddable now – check out my GitHub for docs, tools, and links to similar projects if you want to try this on a Kraze HD7K or Raz TN9000 (maybe some others too).

Disposable vaping devices have become distressingly common in recent years, and an increasing ecological burden given how many (perfectly reusable!) lithium-ion batteries are discarded into landfills and roadsides alike. Even worse, some of them include flashy colour LCD screens! I’ve been collecting and harvesting disposable vapes from my friends for a few years now, but only recently have I had a chance to take some of these LCD-equipped models apart; while doing so, I figured I could pioneer some reverse-engineering efforts while adding custom theme/skin support along the way. (For the record, I don’t vape or smoke but plenty of my friends do. I’m just in it for the batteries and electronics.)

A modified Kraze HD7K disposable vape, with my custom Windows 95 theme applied.

A modified Kraze HD7K disposable vape, with my custom Windows 95 theme applied.

The vape in question is found in my area of Canada as the Kraze HD7K, but it seems to go by other names like the Raz TN9000. Internally, they are the same minus some firmware and logo differences.

Disclaimer & Safety Information

Harvesting disposable vapes is not without its own risks, and I will not accept any responsibility if something goes wrong if you try it yourself. This should only be attempted if you are familiar with handling lithium-ion batteries and the safety implications that they pose.

Nicotine can enter your bloodstream simply by getting vape juice on your skin (so wear gloves!), and can be messy if it gets on your clothes and workspace.  The enclosed lithium-ion battery can also cause burns or property damage if short-circuited, and are usually not equipped with protection circuits; the fact that these cells can push several amps in such a small package is the precise reason they’re (wastefully) used in this application. Rechargeable disposable vapes pose a slightly higher risk if the cell is near or at full charge when disassembled, whereas pure disposables are almost always fully discharged. Discharged cells inherently hold less energy and this reduces the fire risk should a catastrophic failure occur during disassembly, but they still need to be treated with respect. Overdischarged cells below 2 volts may not necessarily recover, but if you decide to attempt a recovery, you must bring them back up to 3 volts at a very low current (on the order of tens of milliamps), after which it is possible to charge them normally.

Vapes harvested from the roadside should only be picked up when it is safe to do so, and should be inspected for damage as soon as possible. These “street lithium” vapes could be water-damaged and also physically damaged from getting run over by vehicles. Any cells with a punctured or torn pouch/casing are unsalvageable and must be recycled. My own experimental data finds that the majority of run-over cells survive, but I would not recommend using them without being absolutely sure that there is no further internal damage (after charging the cell with supervision, it should have at least 90% of its rated capacity and no appreciable self-discharge after a few weeks of rest. Otherwise, put it in the recycle bin where it belongs). Even then, only use them in applications where their failure will not cause collateral damage, like in an outdoor solar-powered lamp that is kept away from anything flammable. If you’re not comfortable with these conditions, just recycle the vape at an appropriate facility in accordance with local regulations (some vape shops will have recycling bins, and hardware stores often will accept rechargeable batteries for recycling too).

Teardown

Without any screw holes, I needed to find another way to (non-destructively) open the vape. I noticed a seam in the bottom end cap, and found that sliding a slotted screwdriver into that seam and some gentle prying action was able to dislodge the internal clips. Once that cap was free, a sliding airflow “switch” fell out as well. Removing the core of the vape was pretty straightforward, but it did require some pliers to pull it out due to its friction-fit construction.

With the core exposed, I could see that the vape uses a 13450-size lithium-ion pouch cell (13mm diameter, 45mm length) and a generously-sized vape juice reservoir, with a sponge to absorb the juice and prevent it from leaking. I also noticed a small 9-pin right angle header that connected a black “logic board” that held a microcontroller and LCD screen, and a green “power board” that had the inhalation sensor, heating coil connector, USB-C connector and solder pads for the battery.

Reverse-Engineering

The three main components of the vape’s logic board are the microcontroller, an SPI NOR Flash chip, and the LCD screen.

The microcontroller is a Nations Tech N32G031K8Q7-1, containing a 48MHz Arm Cortex-M0 CPU, 64k of internal Flash and 8k of SRAM; the SPI Flash is a 1MB Giantech GT25Q80A-UZLI; and the LCD is of an unknown maker but has a part number of FXD096QQ08B-F, with a resolution of 80×160 pixels.

Given that the LCD is what makes this vape particularly special, I decided to look into what controller and pinout it uses. I had a hunch that it would use a common interface and controller (to keep costs low), and searched for a 0.96″ LCD with 13-pin FPC (flexible printed circuit) connector. This led me to the Smart Prototyping #102106, which used a 13-pin connector and seemed to have a compatible pinout. The aforementioned LCD uses a common ST7735S controller, and is controlled through a 4-wire SPI interface (clock, data, command/data select, chip select). (Additionally, I looked on Alibaba for a similar LCD, and found one source that sells them for about $1.25 each in large quantities; this suggests that the displays themselves add significant cost to the vape, but are still cheap enough to be utilized in a throwaway device.)

I searched other websites for LCDs with the same dimensions and connector, and they all used this pinout:

Pin Name Function
1 TP0/NC Unused (may be used for some touch sensor?)
2 TP1/NC Unused (may be used for some touch sensor?)
3 SDIN SPI data to LCD
4 SCLK SPI clock
5 RS Logic low = command, high = data
6 /RST Reset (active-low)
7 /CS Chip select (active-low)
8 GND Power supply/signal ground
9 NC Not connected
10 VDD Power supply (3.3V)
11 LEDK LED backlight cathode
12 LEDA LED backlight anode
13 GND Power supply/signal ground

To ensure that the pinout I was looking at really matched up with what was in the vape, I connected the data, clock and other control lines to my DSLogic Plus logic analyzer and sniffed the traffic as the vape initializes the display. A glance at the logic analyzer data confirmed the pinout.

To further confirm that I had an ST7735S-based display, I resoldered the LCD onto a TSSOP-to-DIP breakout board (which was annoying, as the pin pitch was slightly different and alignment needed much precision), and used the Adafruit Graphics Library and ST7735S driver on a Teensy 3.0 microcontroller, and it worked! Well, mostly. While black-and-white text worked perfectly, the red and blue channels seem to be swapped around when using the tft.initR(INITR_MINI160x80) routine, and it seems that I wasn’t the only one with this issue.

I decided I was finished with the idea of reusing the display alone, and began looking into how the vape itself works, and how it displays images. The first step was to look into the memory contents of the 1MB SPI Flash, as that is a pretty large amount of memory for such a simple device. I desoldered the chip and mounted it on a SSOP-to-DIP adapter, and dumped its contents to a file using my MiniPro TL866CS universal programmer.

My suspicions were confirmed when I analyzed the image data being sent to the LCD matching what was in the beginning of the SPI Flash. The data also looked like it was in a raw “RGB565” format, which is a method of packing a 16-bit pixel into two bytes of data; the term “565” refers to 5, 6, and 5 bit values belonging to the red, blue and green channels respectively. Since the image on the screen used blue in the top-left of the image, this supported my findings since the data similarly had only set bits belonging to the blue channel, and also further describes the data as being “big-endian” assuming that the red bits are read out before the green and blue.

The data seemed to be transferred in 4096-byte blocks, which I speculated to be performed using DMA (direct memory access); the data transfers seemed too fast and regular to be done in software alone. This later proved to be true when I had a chance to analyze the RAM of a working vape.

(Flash) Map-Making

With the 1MB SPI Flash chip dumped, it was time to take that apart too… but digitally! Armed with a spreadsheet, a hex editor, ImageMagick and an online RGB565 renderer by Rinky-Dink Electronics, it was time to begin the (grueling) process of unpacking the entire SPI Flash’s contents.

My first step was to try extracting the first image frame, which I calculated to be 25,600 bytes in size (80 x 160 x 2 bytes per pixel). The next step was to use ImageMagick to convert the raw RGB565 data into a PNG for viewing on a computer:

magick convert -size 80x160 rgb565:<file>.bin <file>.png

The result looked like an image, but the colours were all incorrect! I then tried the Rinky-Dink online RGB565 renderer, which didn’t have this issue. Most of my attempts were performed with ImageMagick since it was easy for me to carve and test different image sizes. (Later on, I decided to pipe in an RGB test image and it seems that ImageMagick interprets what should be RGB data as BRG…)

Attempting to convert the rest of the images felt like I was staring into The Matrix, trying to make sense of scrambled image data that looked like an autostereogram (the ones where you need to cross your eyes to form an image). Since the data on the Flash was raw and undocumented, I had no idea where each image began and ended, and what their resolutions were. It took a lot, and I mean a LOT, of trial and error to figure out the mappings.

This slideshow requires JavaScript.

One by one, I catalogued each image and added it to my spreadsheet, documenting its address, length, resolution, what category it belonged to, and if it was in a sequence of images (an animation, essentially).

This slideshow requires JavaScript.

This continued up until I was over 95% through the entire 1MB address space, and I even found unused animations (or at least unused for the version customized for my area). There was a bit of blank space, which I ignored for a while… but as it turned out, there was some other hidden data stored further down (it was the counter that the vape used to determine how many “bars” of juice to display). I won’t show the whole table since it’s over 100 rows long, but here’s an excerpt (you can find the whole table on my GitHub if you’re interested):

Index (#)

Offset (Hex)

Length (Hex)

Frame H (px)

Frame V (px)

Category

Seq (#)

0 0 6400 80 160 Background 0
1 6400 2880 72 72 Battery Icon 0
2 8C80 2880 72 72 Battery Icon 1
19 33D00 6400 80 160 Vaping Animation 0
20 3A100 6400 80 160 Vaping Animation 1
72 D53E2 6400 80 160 Plugin Background 3 0
73 DB7E2 E9A 21 89 Charger Logo Wipe 0
74 DC67C E9A 21 89 Charger Logo Wipe 1
104 F8000 4 N/A N/A Total Vape Time x0.01s (LSB->MSB) N/A
105 F8004 1 N/A N/A Vape In Use Flag (0xBB) N/A

Toolcrafting

With the whole SPI Flash mapped out, I was able to, with much help from ChatGPT, make a couple custom tools: a Flash image splitter and repacker. I also used the Rinky-Dink UTFT library‘s conversion tools to enable replacement of the image data (i.e. custom theme support). Imagine that – custom theme/skin support on a vape, now a reality! With no help from the manufacturer!

Rationale

You might be asking: why anyone would do this? Honestly… I’m not sure; I just was fascinated by a colourful disposable vape and just had to thoroughly take it apart and learn everything I could about how it worked. Maybe this could give a reason for people to attempt to refill (and therefore reuse) their disposable vapes, and a reused vape is a vape that doesn’t end up in the landfill or roadside – and frankly, that’s good enough reason for me.

With the tooling set up, it was time for me to prove that these vapes can be rethemed, and I thought of a great retro aesthetic that is completely different from the original theme, yet easy for me to make with simple image editing tools: Windows 95.

It runs Windows 95?!

Okay, not really… but I wanted a cohesive but entirely different theme to apply to the user interface, and I’m not nearly artistic enough to go about making a Doom theme (I tried, but couldn’t think of much that would work within the limitations of the original firmware). Using little more than a copy of Windows NT 4.0 (essentially the enterprise/professional version of Windows 95), some virtual machines, screenshot and recorder tools, and Microsoft Paint, I was able to create a UI that faithfully recreates the nostalgic Windows 95 user interface in a tiny 80×160 space.

Tip: if you need to frequently reprogram a device that uses an external (serial) memory chip, consider adding or making a socket for it. Additionally, vapes that use a 2-pin microphone element for inhalation sensing can be activated by shorting the pins to ground with a pushbutton, or even capacitively with your finger. If the vape requires a load to be present, a small halogen bulb will do the trick.

Main Screen

The main screen features the classic teal background, with two “windows” to represent the battery charge and vape juice levels. In keeping with Windows UI mechanics, the juice level is rendered as if it were an inactive window.

Battery Level

The battery icon, while looking trivial to implement, required a lot of manual work to create the icons. From Windows 95 through XP, the default Windows UI’s battery icons were very limited in granularity: full, half, near empty, and empty. I had to employ “tweening” techniques by copying rows of pixels and aligning them to the desired levels, then manually painting in pixels where required. I also used a slightly more-detailed icon set from Windows XP which added a bit more of a highlight on the left side of the icon, rather than a flat blue colour. While it does somewhat detract from the “purity” of the icon set, the improved appearance was worth the tradeoff.

Vape Juice Level

The juice level representation was a lot more challenging to conceive. I thought of using the Recycle Bin icon, but figured that making icons between empty and full wouldn’t produce a very unambiguous representation. I also thought about the magenta/blue pie chart used to represent disk usage, but that option was ruled out for similar ambiguity issues (I know that magenta is free space, and blue is used space, but this might not be intuitive enough without some sort of legend).

I finally figured that I had just enough room to make the world’s tiniest Explorer window, with just enough space for six 16×16 small icons. I picked a selection of icons that one would see in the C:\ root directory: folder, batch file, system file, application, .ini settings file, and unknown file (the one with a small Windows logo on it). As the vape juice is depleted, an icon is removed from the window. Once the juice is deemed by the microcontroller to be completely depleted, the blinking folder icon almost becomes homage to a similar phenomenon on Mac computers if it cannot find an operating system to boot.

Charging Animation

This one was another challenge, mainly due to the limited animation size of thirty 21×89 frames in an off-centre position atop a static background; not only that, the firmware dwells on the last animation for about a second, so any animation loop will need to stop cleanly at the last frame. The lack of animatable screen space meant that a file copy dialog was a non-starter, and the Windows 95 boot screen was also infeasible. I eventually settled on a small “Charging…” dialog box, with the animated hourglass cursor in the middle. One concession that I had to make was doubling each cursor animation frame, as well as losing one corner and edge of the hourglass during the initial animation sequence, as the hourglass is 22 pixels wide for the first few frames. Thankfully, this happens quickly enough that it isn’t particularly noticeable. I had pondered overlaying this dialog box atop the main UI’s “windows” but found the result to be too crowded in appearance, so a single dialog on the teal background was chosen instead.

Vaping Animation

This one was arguably the most fun yet the most frustrating to actually implement. I knew I wanted to use a screensaver for the vaping animation, but which one? I needed a screensaver that was both iconic, but also looped cleanly. Flying Windows was a contender, but proved to scale down poorly and, due to its random nature, looped poorly as well. 3D Flower Box was skipped for the same reason (and wouldn’t have looked good looping once per second anyway). 3D Maze, 3D Text, and the simple graphic screensavers like Marquee, Mystify and Beziers were out too… so what’s left? Only one screensaver was left in the pipeline… a 3D pipeline – heh, get it? 😉 (sorry. I’ll see myself out for that joke.)

3D Pipes is one of the most famous Windows 95-era screensavers, and despite its randomly-generated nature, had relatively clean transitions which make looping animations easier. It also scaled well both in terms of resolution and in time, but was an absolute pain to record. I eventually settled by running the Windows 95 version in a Windows XP virtual machine, with a custom screen size as close to a 1:2 aspect ratio as possible. After taking a screen recording, I had to then choose the sequence that looked the best, extract 16 frames from the video, scale those down to 80×160, and only then did I get my looped screensaver animation, but the result was well worth the effort. The particular run that I captured even had a flame-like shape to it!

Other Discoveries

With my main goal of extracting and replacing images on the vape accomplished, I decided to look further into its inner workings and learned a few things along the way.

A Secret Port

During my initial teardown, I noticed that the microcontroller’s SWD (Serial Wire Debug) programming lines were brought out to the USB-C port, but in an unusual way. The CC1/CC2 lines were not only used as regular 5.1k pulldowns to enable USB-C chargers to recognize the vape, but also as a programming connector. I had to build a custom cable to interface with it, but was able to use my Segger J-Link to communicate with the microcontroller in situ. As a bonus, the firmware is fully readable, without any encryption or readout prevention enabled!

It’s Not About The Puffs

For a device marketed with a capacity of several thousands “puffs”, one would think that this would be tracked as a means to provide a vape juice gauge to the user. However, this turned out to not be the case, in more than one way.

Vape Usage Counter

The vape usage/time counter at exactly 10 seconds of usage. 0x3E8 = 1000, or 10.00 seconds.

Unlike many other disposable vape implementations, the juice meter’s data was stored in a non-volatile memory location, since neither a reset nor power cycle would reset the meter back to full. I was initially unsure where the microcontroller was holding this variable, and scanning the internal Flash memory and the extra few “option bytes” available above the usual firmware region showed no changes. It wasn’t until I decided to run a difference on the external SPI Flash between “puffs”, where I noticed bytes changing near location 0xF8000, in a sea of empty/0xFF bytes. Erasing the bytes from locations 0xF8000-0xF8004 resulted in the vape juice meter resetting back to full! Since vape inhalation sensors typically include a 10 second timeout, I decided to trigger a 10-second “puff” and noticed the memory location had updated to 0x3E8, which corresponds to 1000 in decimal. It was then easy to determine that the microcontroller was counting vape time with 0.01-second granularity.

A Beeline to the Buffer

Since I had access to the microcontroller’s memories in near-real time, I decided to experimentally determine where the data from the SPI Flash was getting copied into RAM. Since I already knew the microcontroller was using DMA to stream image data from the SPI Flash, I figured that I could fill its entire contents with human-readable text (in my case, I chose consecutive copies of the “Bee Movie” script) and observe the RAM’s contents changing in Segger J-Mem. This technique worked immediately, and it was obvious where the DMA buffer was held in RAM!

Blinking Power, Hidden Version (Screen)

Out of boredom and wanting to repeatedly test the charging animation during development, I was rapidly connecting and disconnecting power to the charging port. After several consecutive fast insertions and removals, I saw a black screen with red text pop up. It read “GV-K23 0904V1” and trying to capture it was difficult until I had the idea to use J-Link Commander to halt the CPU as soon as I triggered it. Given that the version screen had text, yet I saw no text strings in the firmware dump, I figured it had to be a bitmap image, and it was! It was stored near the very end of the firmware. While identifying where it was inside the dump was simple, trying to reconstruct it from the dump was not. The extra padding of 0x00 bytes (basically black when interpreted as RGB565) that didn’t align with 60 pixel/120-byte rows, it took a lot of experimentation with image sizes and offsets until I got a legible picture. Even now I’m still not entirely sure if I’ve captured the entire bitmap.

Further Efforts

In the time between me starting this reverse-engineering/modding project and my publication of this blog post, I released my documentation and rudimentary tools on my GitHub profile in the hopes of drumming up more support from the community. Within days, another GitHub user by the name of “xbenkozx” was able to apply and extend my research into the external Flash memory mapping and debugging facilities. He created his own Flash dumping and modding tools, and even built custom reflashing firmware that only needed access to a SWD debugging/programming dongle and a custom cable – no surface-mount soldering required (i.e. programming that is beyond my current skillset 🙂 ). He also made more detailed analyses of various board revisions, and some important incompatibilities that could have resulted in damage due to overheating of the vape’s heating coil. Check it out!

Hopefully this reverse-engineering and reuse campaign can branch even further. Perhaps development environments (maybe an Arduino board profile?) or alternative firmwares can be developed to allow these boards to be reused in other applications, given that these vapes already provide easy access to a bright LCD screen, a decently powerful Arm microcontroller, Li-ion battery charging, and a megabyte of SPI Flash storage plus some extra LED indicators. Some board versions even have Rx/Tx pads for a UART serial port, which could make communication between different devices a possibility.

In terms of just theme creation, I made an editable template on my GitHub repository, with example bar graphs and animation frames denoting positioning and frame count. Maybe someone out there with more graphics/artistic/programming talent than me could figure out a way to “run Doom” on these vapes! 😀

Conclusion

Disposable vapes are almost everywhere, and their wastefulness goes beyond just some rechargeable batteries and circuit boards – some have bright LCD screens… and yet somehow these are considered throwaway devices!

If you have some of these LCD-equipped disposable vapes like the Kraze HD7K or RAZ TN9000, perhaps try playing around with them a bit. I’ve spearheaded a community campaign on GitHub to reverse-engineer these vapes, which allows them to be reused or repurposed more easily. If you want to take the path of reuse, I and some others have made customization and reflashing tools available. There is also potential in reusing just the microcontroller board since the vape features a pretty powerful microcontroller and a lot of storage space for images.

Reverse-engineering and analysis of SanDisk High Endurance microSDXC card

As seen on Hackaday!

TL;DR – The SanDisk High Endurance cards use SanDisk/Toshiba 3D TLC Flash. It took way, way more work than it should have to figure this out (thanks for nothing, SanDisk!).
In contrast, the SanDisk MAX Endurance uses the same 3D TLC in pMLC (pseudo-multi-level cell) mode.

In a previous blog post, I took a look at SanDisk’s microSD cards that were aimed for use in write-intensive applications like dash cameras. In that post I took a look at its performance metrics, and speculated about what sort of NAND Flash memory is used inside. SanDisk doesn’t publish any detailed specifications about the cards’ internal workings, so that means I have no choice but to reverse-engineer the can of worms card myself.

The Help Desk That Couldn’t

In the hopes of uncovering more information, I sent an email to SanDisk’s support team asking about what type of NAND Flash they are using in their High Endurance lineup of cards, alongside endurance metrics like P/E (Program/Erase) cycle counts and total terabytes written (TBW). Unfortunately, the SanDisk support rep couldn’t provide a satisfactory answer to my questions, as they’re not able to provide any information that’s not listed in their public spec sheets. They said that all of their cards use MLC Flash, which I guess is correct if you call TLC Flash 3-bit MLC (which Samsung does).

Dear Jason,

Thank you for contacting SanDisk® Global customer care. We really appreciate you being a part of our SanDisk® family.

I understand that you wish to know more about the SanDisk® High Endurance video monitoring card, as such please allow me to inform you that all our SanDisk® memory cards uses Multi level cell technology (MLC) flash. However, the read/write cycles for the flash memory is not published nor documented only the read and write speed in published as such they are 100 MB/S & 40 MB/s. The 64 GB card can record Full HD video up to 10,000 hours. To know more about the card you may refer to the link below:

SANDISK HIGH ENDURANCE VIDEO MONITORING microSD CARD

Best regards,
Allan B.
SanDisk® Global Customer Care

I’ll give them a silver star that says “You Tried” at least.

Anatomy of an SD Card

While (micro)SD cards feel like a solid monolithic piece of technology, they’re made up of multiple different chips, each performing a different role. A basic SD card will have a controller that manages the NAND Flash chips and communicates with the host (PC, camera, etc.), and the NAND Flash itself (made up of 1 or more Flash dies). Bunnie Huang’s blog, Bunnie Studios, has an excellent article on the internals of SD cards, including counterfeits and how they’re made – check it out here.

SD Card Anatomy

Block diagram of a typical SD card.

MicroSD cards often (but not always!) include test pads, used to program/test the NAND Flash during manufacture. These can be exploited in the case of data recovery, or to reuse microSD cards that have a defective controller or firmware by turning the card into a piece of raw NAND Flash – check out Gough Lui’s adventures here. Note that there is no set standard for these test pads (even for the same manufacturer!), but there are common patterns for some manufacturers like SanDisk that make reverse-engineering easier.

Crouching Controller, Hidden Test Pads

microSD cards fall into a category of “monolithic” Flash devices, as they combine a controller and raw NAND Flash memory into a single, inseparable package. Many manufacturers break out the Flash’s data bus onto hidden (and nearly completely undocumented) test pads, which some other memory card and USB drive manufacturers take advantage of to make cheap storage products using failed parts; the controller can simply be electrically disabled and the Flash is then used as if it were a regular chip.

In the case of SanDisk cards, there is very limited information on their cards’ test pad pinouts. Each generation has slight differences, but the layout is mostly the same. These differences can be fatal, as the power and ground connections are sometimes reversed (this spells instant death for a chip if its power polarity is mixed up!).

CORRECTION (July 22, 2020): Upon further review, I might have accidentally created a discrepancy between the leaked pinouts online, versus my own documentation in terms of power polarity; see the “Test Pad Pinout” section.

My card (and many of their higher-end cards – that is, not their Ultra lineup) features test pads that aren’t covered by solder mask, but are instead covered by some sort of screen-printed epoxy with a laser-etched serial number on top. With a bit of heat and some scraping, I was able to remove the (very brittle) coating on top of the test pads; this also removed the serial number which I believe is an anti-tamper measure by SanDisk.

After cleaning off any last traces of the epoxy coating, I was greeted with the familiar SanDisk test pad layout, plus a few extra on the bottom.

Building the Breakout Board

The breakout board is relatively simple in concept: for each test pad, bring out a wire that goes to a bigger test point for easier access, and wire up the normal SD bus to an SD connector to let the controller do its work with twiddling the NAND Flash bus. Given how small each test pad is (and how many), things get a bit… messy.

I started by using double-side foam adhesive tape to secure the SD card to a piece of perfboard. I then tinned all of the pads and soldered a small 1uF ceramic capacitor across the card’s power (Vcc) and ground (GND) test pads. Using 40-gauge (0.1 mm, or 4-thousandths of an inch!) magnet wire, I mapped each test pad to its corresponding machine-pin socket on the perfboard. Including the extra test pads, that’s a total of 28 tiny wires!

For the SD connector side of things, I used a flex cable for the XTC 2 Clip (a tool used to service HTC Android devices), as it acted as a flexible “remote SD card” and broke out the signals to a small ribbon cable. I encased the flex cable with copper tape to act as a shield against electrical noise and to provide physical reinforcement, and soldered the tape to the outer pads on the perfboard for reinforcement. The ribbon cable end was then tinned and each SD card’s pin was wired up with magnet wire. The power lines were then broken out to an LED and resistor to indicate when the card was receiving power.

Bus Analysis

With all of the test pads broken out to an array of test pins, it was time to make sense of what pins are responsible for accessing the NAND Flash inside the card.

Test Pad Pinout

Diagram of the test pads on SanDisk's High Endurance microSD card.

Diagram of the test pads on SanDisk’s High Endurance microSD card. (click to enlarge)

The overall test pad pinout was the same for other microSD cards from SanDisk, but there were some differences, primarily regarding the layout of the power pads; notably, the main power pins are backwards! This can destroy the card if you’re not careful when applying power.

CORRECTION (July 22, 2020): I might actually have just gotten my own documentation mixed up in terms of the power and ground test pads. Regardless, one should always be careful to ensure the correct power polarity is sent to a device.

I used my DSLogic Plus logic analyzer to analyze the signals on all of the pins. Since the data pinout was previously discovered, the hard part of figuring out what each line represented (data bus, control, address, command, write-protect, ready/busy status) was already done for me. However, not all of the lines were immediately evident as the pinouts I found online only included the bare minimum of lines to make the NAND Flash accessible, with one exception being a control line that places the controller into a reset state and releases its control of the data lines (this will be important later on).

By sniffing the data bus at the DSLogic’s maximum speed (and using its 32 MB onboard buffer RAM), I was able to get a clear snapshot of the commands being sent to the NAND Flash from the controller during initialization.

Bus Sniffing & NAND I/O 101 (writing commands, address, reading data)

In particular, I was looking for two commands: RESET (0xFF), and READ ID (0x90). When looking for a command sequence, it’s important to know how and when the data and control lines change. I will try to explain it step-by-step, but if you’re interested there is an introductory white paper by Micron that explains all of the fundamentals of NAND Flash with much more information about how NAND Flash works.

When a RESET command is sent to the NAND Flash, first the /CE (Chip Select, Active Low) line is pulled low. Then the CLE (Command Latch Enable) line is pulled high; the data bus is set to its intended value of 0xFF (all binary ones); then the /WE (Write Enable, Active Low) line is pulsed from high to low, then back to high again (the data bus’ contents are committed to the chip when the /WE line goes from low to high, known as a “rising edge”); the CLE line is pulled back low to return to its normal state. The Flash chip will then pull its R/B (Ready/Busy Status) line low to indicate it is busy resetting itself, then releases the line back to its high state when it’s finished.

The READ ID command works similarly, except after writing the command with 0x90 (binary 1001 0000) on the data bus, it then pulls the ALE (Address Latch Enable) line high instead of CLE, and writes 0x00 (all binary zeroes) by pulsing the /WE line low. The chip transfers its internally programmed NAND Flash ID into its internal read register, and the data is read out from the device on each rising edge of the /RE (Read Enable, Active Low) line; for most devices this is 4 to 8 bytes of data.

NAND Flash ID

For each NAND Flash device, it has a (mostly) unique ID that identifies the manufacturer, and other functional data that is defined by that manufacturer; in other words, only the manufacturer ID, assigned by the JEDEC Technology Association, is well-defined.

The first byte represents the Flash manufacturer, and the rest (2 to 6 bytes) define the device’s characteristics, as set out by the manufacturer themselves. Most NAND vendors are very tight-lipped when it comes to datasheets, and SanDisk (and by extension, Toshiba/Kioxia) maintain very strict control, save for some slightly outdated leaked Toshiba datasheets. Because the two aforementioned companies share their NAND fabrication facilities, we can reasonably presume the data structures in the vendor-defined bytes can be referenced against each other.

In the case of the SanDisk High Endurance 128GB card, it has a NAND Flash ID of 0x45 48 9A B3 7E 72 0D 0E. Some of these values can be compared against a Toshiba datasheet:

Byte Value (Hex) Description/Interpretation
45
  • Manufacturer: SanDisk
48
  • I/O voltage: Presumed 1.8 volts (measured with multimeter)
  • Device capacity: Presumed 128 GB (unable to confirm against datasheet)
9A
  • NAND type: TLC (Triple-Level Cell / 3 bits per cell)
  • Flash dies per /CE: 4 (card uses four 32GB Flash chips internally)
B3
  • Block size: 12 MiB (768 pages per block) excluding spare area (determined outside datasheet)
  • Page size: 16,384 bytes / 16 kiB excluding spare area
7E
  • Planes per /CE: 8 (2 planes per die)
72
  • Interface type: Asynchronous
  • Process geometry: BiCS3 3D NAND (determined outside datasheet)
0D
  • Unknown (no information listed in datasheet)
0E
  • Unknown (no information listed in datasheet)

Although not all byte values could be conclusively determined, I was able to determine that the SanDisk High Endurance cards use BiCS3 3D TLC NAND Flash, but at least it is 3D NAND which improves endurance dramatically compared to traditional/planar NAND. Unfortunately, from this information alone, I cannot determine whether the card’s controller takes advantage of any SLC caching mechanisms for write operations.

The chip’s process geometry was determined by looking up the first four bytes of the Flash ID, and cross-referencing it to a line from a configuration file in Silicon Motion’s mass production tools for their SM3271 USB Flash drive controller, and their SM2258XT DRAM-less SSD controller. These tools revealed supposed part numbers of SDTNAIAMA-256G and SDUNBIEMM-32G respectively, but I don’t think this is accurate for the specific Flash configuration in this card.

External Control

I wanted to make sure that I was getting the correct ID from the NAND Flash, so I rigged up a Texas Instruments MSP430FR2433 development board and wrote some (very) rudimentary code to send the required RESET and READ ID commands, and attempt to extract any extra data from the chip’s hidden JEDEC Parameter Page along the way.

My first roadblock was that the MSP430 would reset every time it attempted to send the RESET command, suggesting that too much current was being drawn from the MSP430 board’s limited power supply. This can occur during bus contention, where two devices “fight” each other when trying to set a certain digital line both high and low at the same time. I was unsure what was going on, since publicly-available information didn’t mention how to disable the card’s built-in controller (doing so would cause it to tri-state the lines, effectively “letting go” of the NAND bus and allowing another device to take control).

I figured out that the A1 test pad (see diagram) was the controller’s reset line (pulsing this line low while the card was running forced my card reader to power-cycle it), and by holding the line low, the controller would release its control of the NAND Flash bus entirely. After this, my microcontroller code was able to read the Flash ID correctly and consistently.

Reading out the card's Flash ID with my own microcontroller code.

Reading out the card’s Flash ID with my own microcontroller code.

JEDEC Parameter Page… or at least what SanDisk made of it!

The JEDEC Parameter Page, if present, contains detailed information on a Flash chip’s characteristics with far greater detail than the NAND Flash ID – and it’s well-standardized so parsing it would be far easier. However, it turns out that SanDisk decided to ignore the standard format, and decided to use their own proprietary Parameter Page format! Normally the page starts with the ASCII string “JEDEC”, but I got a repeating string of “SNDK” (corresponding with their stock symbol) with other data that didn’t correspond to anything like the JEDEC specification! Oh well, it was worth a try.

I collected the data with the same Arduino sketch as shown above, and pulled 1,536 bytes’ worth of data; I wrote a quick program on Ideone to provide a nicely-formatted hex dump of the first 512 bytes of the Parameter Page data:

Offset 00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F 0123456789ABCDEF
------ --+--+--+--+--+--+--+--+--+--+--+--+--+--+--+-- ----------------
0x0000 53 4E 44 4B 53 4E 44 4B 53 4E 44 4B 53 4E 44 4B SNDKSNDKSNDKSNDK
0x0010 53 4E 44 4B 53 4E 44 4B 53 4E 44 4B 53 4E 44 4B SNDKSNDKSNDKSNDK
0x0020 08 08 00 08 06 20 00 02 01 48 9A B3 00 05 08 41 ..... ...H.....A
0x0030 48 63 6A 08 08 00 08 06 20 00 02 01 48 9A B3 00 Hcj..... ...H...
0x0040 05 08 41 48 63 6A 08 08 00 08 06 20 00 02 01 48 ..AHcj..... ...H
0x0050 9A B3 00 05 08 41 48 63 6A 08 08 00 08 06 20 00 .....AHcj..... .
0x0060 02 01 48 9A B3 00 05 08 41 48 63 6A 08 08 00 08 ..H.....AHcj....
0x0070 06 20 00 02 01 48 9A B3 00 05 08 41 48 63 6A 08 . ...H.....AHcj.
0x0080 08 00 08 06 20 00 02 01 48 9A B3 00 05 08 41 48 .... ...H.....AH
0x0090 63 6A 08 08 00 08 06 20 00 02 01 48 9A B3 00 05 cj..... ...H....
0x00A0 08 41 48 63 6A 08 08 00 08 06 20 00 02 01 48 9A .AHcj..... ...H.
0x00B0 B3 00 05 08 41 48 63 6A 08 08 00 08 06 20 00 02 ....AHcj..... ..
0x00C0 01 48 9A B3 00 05 08 41 48 63 6A 08 08 00 08 06 .H.....AHcj.....
0x00D0 20 00 02 01 48 9A B3 00 05 08 41 48 63 6A 08 08  ...H.....AHcj..
0x00E0 00 08 06 20 00 02 01 48 9A B3 00 05 08 41 48 63 ... ...H.....AHc
0x00F0 6A 08 08 00 08 06 20 00 02 01 48 9A B3 00 05 08 j..... ...H.....
0x0100 41 48 63 6A 08 08 00 08 06 20 00 02 01 48 9A B3 AHcj..... ...H..
0x0110 00 05 08 41 48 63 6A 08 08 00 08 06 20 00 02 01 ...AHcj..... ...
0x0120 48 9A B3 00 05 08 41 48 63 6A 08 08 00 08 06 20 H.....AHcj..... 
0x0130 00 02 01 48 9A B3 00 05 08 41 48 63 6A 08 08 00 ...H.....AHcj...
0x0140 08 06 20 00 02 01 48 9A B3 00 05 08 41 48 63 6A .. ...H.....AHcj
0x0150 08 08 00 08 06 20 00 02 01 48 9A B3 00 05 08 41 ..... ...H.....A
0x0160 48 63 6A 08 08 00 08 06 20 00 02 01 48 9A B3 00 Hcj..... ...H...
0x0170 05 08 41 48 63 6A 08 08 00 08 06 20 00 02 01 48 ..AHcj..... ...H
0x0180 9A B3 00 05 08 41 48 63 6A 08 08 00 08 06 20 00 .....AHcj..... .
0x0190 02 01 48 9A B3 00 05 08 41 48 63 6A 08 08 00 08 ..H.....AHcj....
0x01A0 06 20 00 02 01 48 9A B3 00 05 08 41 48 63 6A 08 . ...H.....AHcj.
0x01B0 08 00 08 06 20 00 02 01 48 9A B3 00 05 08 41 48 .... ...H.....AH
0x01C0 63 6A 08 08 00 08 06 20 00 02 01 48 9A B3 00 05 cj..... ...H....
0x01D0 08 41 48 63 6A 08 08 00 08 06 20 00 02 01 48 9A .AHcj..... ...H.
0x01E0 B3 00 05 08 41 48 63 6A 08 08 00 08 06 20 00 02 ....AHcj..... ..
0x01F0 01 48 9A B3 00 05 08 41 48 63 6A 08 08 00 08 06 .H.....AHcj.....

Further analysis with my DSLogic showed that the controller itself requests a total of 4,128 bytes (4 kiB + 32 bytes) of Parameter Page data, which is filled with the same repeating data as shown above.

Reset Quirks

When looking at the logic analyzer data, I noticed that the controller sends the READ ID command twice, but the first time it does so without resetting the Flash (which should normally be done as soon as the chip is powered up!). The data that the Flash returned was… strange to say the least.

Byte Value (Hex) Interpreted Value
98
  • Manufacturer: Toshiba
00
  • I/O voltage: Unknown (no data)
  • Device capacity: Unknown (no data)
90
  • NAND type: SLC (Single-Level Cell / 1 bit per cell)
  • Flash dies per /CE: 1
93
  • Block size: 4 MB excluding spare area
  • Page size: 16,384 bytes / 16 kiB excluding spare area
76
  • Planes per /CE: 2
72
  • Interface type: Asynchronous
  • Process geometry: 70 nm planar

This confused me initially when I was trying to find the ID from the logic capture alone; after talking to a contact who has experience in NAND Flash data recovery, they said this is expected for SanDisk devices, which make liberal use of vendor-proprietary commands and data structures. If the fourth byte is to be believed, it says the block size is 4 megabytes, which I think is plausible for a modern Flash device. The rest of the information doesn’t really make any sense to me apart from the first byte indicating the chip is made by Toshiba.

Conclusion

I shouldn’t have to go this far in hardware reverse-engineering to just ask a simple question of what Flash SanDisk used in their high-endurance card. You’d think they would be proud to say they use 3D NAND for higher endurance and reliability, but I guess not!

Downloads

For those that are interested, I’ve included the logic captures of the card’s activity shortly after power-up. I’ve also included the (very crude) Arduino sketch that I used to read the NAND ID and Parameter Page data manually:

Recovering the SIM card PIN from the ZTE WF721 cellular home phone

As seen on Hackaday!

TL;DR – If you have a ZTE WF721 that’s PIN-locked your SIM card, try 2376.

Recently I picked up a used Samsung Galaxy Core LTE smartphone from a relative after they upgraded to an iPhone. As the Core LTE is a low-end smartphone, I suspected that the phone was SIM locked to its original carrier (Virgin Mobile), but in order to test this I needed a different SIM card. My personal phone was on the same carrier, so I wasn’t able to use that to test it.

However, the previous summer I picked up a ZTE WF721 cellular home phone base station (that is, it’s a voice-only cell phone that a landline phone plugs into), which came with a Telus SIM card. The issue is that the WF721 sets a SIM card PIN to essentially “lock” the card to the base station, and it wasn’t the default 1234 PIN; brute-forcing a SIM card is not possible as you get 3-5 attempts before the card needs to be unblocked using a PUK (PIN Unblock Key), failing that, the card is permanently rendered unusable. I decided to take the base station apart, and use my knowledge in electronics and previous research into smart cards to see if I could recover the PIN.

(Yes, I went through all this work instead of just buying a prepaid SIM card from the dollar store. I’m weird like that.)

Test Pads & Signals

After a bit of disassembly work involving removing screws hidden under rubber non-slip feet and a lot of spudgering open plastic clips, I got access to the four test pads that connect to the SIM card, accessible on the opposite side of the PCB from the SIM card socket.

ZTE WF721 Opened

The ZTE WF721 opened, with test pads broken out and connected to DSLogic for reverse engineering.

An ISO 7816-compliant smart card (and a SIM card is one) require 5 different lines to work: Vcc (power), ground, clock, I/O (data), and reset. The I/O is an asynchronous half-duplex UART-type interface, whose baud rate is determined by the card’s characteristics and the clock frequency that it is given by the reader (in this case, the WF721). The details of how the interface work can be obtained for free in their TS 102 221 specification from the ETSI (European Telecommunications Standards Institute).

ZTE WF721 SIM Card Test Pads

The test pads that connect to the WF721’s SIM card socket.

I then soldered the ground wire to a free test pad elsewhere on the board, whereas the four other wires were soldered to the test pads near the SIM card socket. I then connected these wires to a pin header and plugged it into my DSLogic Plus logic analyzer. I analyzed the logic captures after turning the WF721 on and allowing it to initialize the SIM card and attempt to connect to the cellular network (the service to it has been disconnected so it doesn’t actually succeed).

Command Analysis

After looking at the raw logic capture, there was a lot that I had to sift through. I needed to create a custom setting for the UART decoder as the serial output isn’t your traditional “9600-8-N-1” setting. Rather, the interface uses 8 data bits, even parity, and 2 stop bits. The baud rate is determined by a parameter in the card’s initial identification, the ATR (Answer to Reset). I parsed the card’s ATR that I previously captured on the PC using the SpringCard PC/SC Diagnostic tool using Ludovic Rousseau’s online tool, I determined I needed to use a baud rate of 250 kbit/s, since the card was being fed a 4 MHz clock.

T=0 Smart Card Command (APDU) Structure

The smart card communicates to and from the host through APDUs (Application Protocol Data Units). The command header for a T=0 smart card (character-based I/O, which most cards use) is made up of 5 bytes: class, instruction, 2 parameter bytes and a length/3rd parameter byte. To acknowledge the command, the card sends the instruction byte back to the reader and the data is transferred to/from the card, depending on the command used. The card then sends two status bytes that indicate whether the command is successful; if it is, the response is 0x9000. A graphical representation of this process can be seen in the next section.

VERIFY PIN Command Decoding

The raw data structure of a SIM card's VERIFY PIN command. Each part of the flow is labeled for ease of understanding.

The raw data structure of a SIM card’s VERIFY PIN command. Each part of the flow is labeled for ease of understanding (click image to see full size).

The command I’m looking for is 0x20 (VERIFY PIN), and I had to sift through the command flow in the logic analyzer until I found it. After a lot of preceding commands, I found the command I was looking for, and I found the PIN… and it’s in plaintext! As it turns out, it is sent as an ASCII string, but it’s not null terminated like a regular string. Instead, the data is always 8 bytes (allowing up to an 8-digit PIN), but a PIN shorter than 8 digits will have the end bytes padded with 0xFF (all binary ones). It was easy to determine that the bytes 0x32 33 37 36 is the ASCII representation of the PIN 2376, and after the card waited many tens of milliseconds, it acknowledged the PIN was correct as it gave the expected 0x9000 response code.

PIN Testing & Unlocking

SIM Opened in Dekart SIM Manager

Dekart SIM Manager showing the phone number programmed into the SIM card (censored for privacy).

I tried the PIN in the Dekart SIM Manager software on my computer, and it worked! I was able to read out the contents of the SIM and find out what phone number it used to have, although no other useful information was found.

By using the legacy GSM class 0xA0, I was able to manually verify the PIN by directly communicating with the SIM card using the same command syntax in PC/SC Diagnostic:

SIM Card VERIFY PIN Test

Testing the VERIFY PIN command directly in SpringCard PC/SC Diagnostic.

I took the SIM card out and put it in my Galaxy Core LTE phone, entered the PIN, and as expected it brought up the network unlock prompt. I was able to contact my carrier to get the phone unlocked, and they did it for free (as legally required in Canada) – it turned out to be helpful I was on the same network, as they needed an account to authenticate the request against, even if the phone is registered to another account holder. After entering the 8-digit unlock PIN they provided, the phone was successfully unlocked!

The WF721 is in all likelihood also network locked, but that’s a bridge I haven’t crossed yet.

Conclusion

After a bit of sleuthing into how SIM cards communicate with a cell phone, I was able to decipher the exact command used to authenticate a SIM card PIN inside a disused cellular home phone, all to check if a hand-me-down smartphone was network-locked to its original carrier. Was it a lot of effort just to do that? Maybe, but where’s the fun in buying a prepaid SIM card? 🙂