Hacking a Proform 12.0TT Treadmill with iFit Live

I recently bought a Proform 12.0TT treadmill from Costco. One of the major reasons I picked this particular treadmill was because it supports iFit Live, which is supposed to allow you to make custom workouts with google maps. Unfortunately, iFit Live has turned out to be complete garbage. My first frustrations started when I tried to connect my treadmill to my iFit account. I spent a couple of hours and got nowhere, because it kept complaining that it wasn’t able to contact the iFit server. I thought maybe it was that my WiFi wasn’t configured correctly, even though the treadmill was able to obtain a valid IP address. I tried submitting a support ticket, and was completely appalled by the horrible total lack of support. They didn’t even bother to respond to my ticket for more than a week, and by that time, I’d figured out that there server had gone down for a few days… one day, it finally just started talking to their server.

iFit Live, which is the only way to make custom workouts is quite expensive, and the treadmill doesn’t even come with so much as a free trial. It costs $9.99/mo or $99/yr. I was thinking of trying it out, but with their crappy support, I don’t want to give them my money. The worst part is that there’s this really cool iFit app that runs on iOS and Android that lets you visualize your workouts in realtime, but it only works on a handful of treadmills, and mine is not included on the list. Of course, they don’t tell you this, so I didn’t figure it out until I’d already bought the treadmill. Doing the workouts on the treadmill’s rather ugly and primitive display isn’t nearly as cool as via google Street View, or at least tracking it on a live google map.

The treadmill’s controller is garbage, too. It doesn’t let you customize the built-in workouts, and doesn’t even let you input things such as age/gender/weight, so the calorie calculations are completely worthless. I started thinking about hacking my own controller for the treadmill, but then decided to first see what could be done via software.

It turns out the treadmill has a built in telnetd, and if you telnet to its IP number, you can log in as root without using any password. ssh is also supported, but it requires the root password. Once you telnet in, it’s readily apparent that it’s running embedded Linux of some sort on an ARM processor. The treadmill control stuff is all in directory /icon/bin. Here is a list of the running processes:

# ps
PID Uid VSZ Stat Command
1 root 652 S init
2 root SWN [ksoftirqd/0]
3 root SW< [events/0]
4 root SW< [khelper]
5 root SW< [kthread]
16 root SW< [kblockd/0]
19 root SW< [khubd]
50 root SW [pdflush]
51 root SW [pdflush]
52 root SW [kswapd0]
53 root SW< [aio/0]
666 root SW [sdioeventd]
667 root SW [sdiod0]
669 root SW [mtdblockd]
709 root SW [nandeventd]
710 root SW [nandthread]
713 root SW< [scsi_eh_0]
739 root 3148 S /usr/bin/syslogd -S -b 10 -D -s 32 -O /tmp/logfile.tx
753 root 2292 S /usr/bin/dropbear
783 root SW [RtmpTimerTask]
784 root SW [RtmpMlmeTask]
785 root SW [RtmpCmdQTask]
786 root SW [RtmpWscTask]
792 root 3124 S /usr/bin/httpd -f -p 8080 -h /icon/bin/html
793 root 3020 S sslwrap -cert /usr/bin/sslwrap.cert -accept 443 -port
794 root 2880 S ./utwatch
795 root 652 S init
796 root 652 S init
798 root 652 S init
800 root 652 S init
810 root 2740 S utaudio
811 root 6476 S utcompete
812 root 2864 S utcontrol
813 root 2744 S utdevcom
814 root 2764 S utinput
815 root 2740 S utlog
816 root 6940 S utnetstatus
817 root 2200 S utuserlog
819 root 2740 S utvalues
820 root 2888 S utwifi
826 root 2872 S utwpl
827 root 2272 S utdisplay
828 root 2740 S utworkout
831 root 4640 R N web_serv
928 root 652 S udhcpc -i apcli0 -a -n -t 4
974 root 664 S telnetd
7348 root 764 S /bin/sh
26665 root 764 S /bin/sh
14635 root 656 R ps
14636 root 648 R sh -c iwconfig

From dmesg, I found that it’s running on a NuMicro NUC950 MCU running at 200MHz with 32MB RAM and 130MB of NAND flash. Here is the entire output of dmesg after bootup:

# dmesg
Linux version (sbarton@sbarton-VirtualBox) (gcc version 4.2.1) #95 PREEMPT Thu Nov 21 10:08:23 MST 2013
CPU: ARM926EJ-Sid(wb) [41069265] revision 5 (ARMv5TEJ)
Machine: NUC950
Memory policy: ECC disabled, Data cache writeback
On node 0 totalpages: 8192
DMA zone: 8192 pages, LIFO batch:1
CPU NUC950 (id 0x02900910 system clock:200MHZ)
CPU0: D VIVT write-back cache
CPU0: I cache: 8192 bytes, associativity 4, 32 byte lines, 64 sets
CPU0: D cache: 8192 bytes, associativity 4, 32 byte lines, 64 sets
Built 1 zonelists
Kernel command line: root=/dev/ram0 console=ttyS0,115200n8 initrd=0xa00000,4000000 mem=32M
PID hash table entries: 256 (order: 8, 1024 bytes)
Console: colour dummy device 80×30
selected clock e4e1c0 quot 7
Dentry cache hash table entries: 4096 (order: 2, 16384 bytes)
Inode-cache hash table entries: 2048 (order: 1, 8192 bytes)
Memory: 32MB = 32MB total
Memory: 26440KB available (1676K code, 309K data, 80K init)
Calibrating delay loop… 99.73 BogoMIPS (lpj=498688)
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
checking if image is initramfs…it isn’t (no cpio magic); looks like an initrd
Freeing initrd memory: 3906K
NET: Registered protocol family 16
* You selcet NUC950,Start Init NUC950EVB *
SCSI subsystem initialized
usbcore: registered new driver usbfs
usbcore: registered new driver hub
NET: Registered protocol family 2
IP route cache hash table entries: 256 (order: -2, 1024 bytes)
TCP established hash table entries: 1024 (order: 0, 4096 bytes)
TCP bind hash table entries: 512 (order: -1, 2048 bytes)
TCP: Hash tables configured (established 1024 bind 512)
TCP reno registered
NetWinder Floating Point Emulator V0.97 (double precision)
io scheduler noop registered (default)
NUC900 uart driver has been initialized successfully!
nuc900-uart.0: nuc900_serial0 at MMIO 0xb8000000 (irq = 7) is a NUC900
nuc900-uart.1: nuc900_serial1 at MMIO 0xb8000100 (irq = 8) is a NUC900
nuc900-uart.2: nuc900_serial2 at MMIO 0xb8000200 (irq = 9) is a NUC900
RAMDISK driver initialized: 16 RAM disks of 8192K size 1024 blocksize
loop: loaded (max 8 devices)
Card0 Detect !!!
NUC900 SD driver has been initialized successfully!
CFI: Found no nuc900nor device at location zero
probe failed
NUC900 USB host driver has been initialized successfully!
nuc900-ehci nuc900-ehci: Nuvoton nuc900 EHCI Host Controller
nuc900-ehci nuc900-ehci: new USB bus registered, assigned bus number 1
nuc900-ehci nuc900-ehci: irq 15, io mem 0xb0005000
nuc900-ehci nuc900-ehci: USB 2.0 started, EHCI 0.95, driver 10 Dec 2004
usb usb1: configuration #1 chosen from 1 choice
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
nuc900-ohci nuc900-ohci: Nuvoton nuc900 ohci Host Controller
nuc900-ohci nuc900-ohci: new USB bus registered, assigned bus number 2
nuc900-ohci nuc900-ohci: io mem 0xb0007000
usb usb2: configuration #1 chosen from 1 choice
hub 2-0:1.0: USB hub found
hub 2-0:1.0: 2 ports detected
Initializing USB Mass Storage driver…
usb 1-2: new high speed USB device using nuc900-ehci and address 2
usb 1-2: configuration #1 chosen from 1 choice
usbcore: registered new driver usb-storage
USB Mass Storage support registered.
ts: Compaq touchscreen protocol output
i2c /dev entries driver
nuc900-i2c-p0 nuc900-i2c-p0: bus frequency set to 100 KHz
nuc900-i2c-p0 nuc900-i2c-p0: i2c-0: nuc900 I2C port0 adapter
client [NAU882] registered with bus id 0-001a
NUC900 Audio driver has been initialized successfully!
TCP bic registered
NET: Registered protocol family 1
NET: Registered protocol family 17
RAMDISK: Compressed image found at block 0
VFS: Mounted root (romfs filesystem) readonly.
Freeing init memory: 80K
selected clock e4e1c0 quot 7
selected clock e4e1c0 quot 7
selected clock e4e1c0 quot 7
gnand: module license ‘Nuvoton’ taints kernel.
NAND: nand_init!!!!!
1. ===> write SMCSR
scsi0 : Nuvoton NUC900 GNAND DRIVER!
nand card init
1. ===> write SMCSR
Card0 Removed
card reset4. ===> write SMCSR
6. ===> write SMCSR
fmiCheckInvalidBlock pSM0->uLibStartBlock=2
Valid P2LN 577, block 1022

===> nand total sectors 253696 <size 129892352>
Vendor: NUVOTON Model: GNAND DRIVER Rev: 2.00
Type: Direct-Access ANSI SCSI revision: 00
SCSI device sda: 253696 512-byte hdwr sectors (130 MB)
sda: Write Protect is off
sda: Mode Sense: 03 00 00 00
sda: got wrong page
sda: assuming drive cache: write through
SCSI device sda: 253696 512-byte hdwr sectors (130 MB)
sda: Write Protect is off
sda: Mode Sense: 03 00 00 00
sda: got wrong page
sda: assuming drive cache: write through
sda: sda1 sda2 sda3 sda4 < sda5 >
sd 0:0:0:0: Attached scsi removable disk sda
EXT2-fs warning: mounting unchecked fs, running e2fsck is recommended
rtusb init rt3070AP —>
=== pAd = c2804000, size = 778504 ===

RTMPAllocTxRxRingMemory, Status=0
RTMPAllocAdapterBlock, Status=0
usbcore: registered new driver rt3070AP
(Efuse for 3062/3562/3572) Size=0x2d [2d0-2fc]
RTMP_TimerListAdd: add timer obj c2893b98!
RTMP_TimerListAdd: add timer obj c2861ed0!
RTMP_TimerListAdd: add timer obj c2861eb8!
RTMP_TimerListAdd: add timer obj c2861ea0!
RTMP_TimerListAdd: add timer obj c28081a8!
RTMP_TimerListAdd: add timer obj c2807dc4!
RTMP_TimerListAdd: add timer obj c280818c!
RTMP_TimerListAdd: add timer obj c2808414!
RTMP_TimerListAdd: add timer obj c2808394!
RTMP_TimerListAdd: add timer obj c280b240!
RTMP_TimerListAdd: add timer obj c280ae5c!
RTMP_TimerListAdd: add timer obj c280b224!
RTMP_TimerListAdd: add timer obj c280b4ac!
RTMP_TimerListAdd: add timer obj c280b42c!
RTMP_TimerListAdd: add timer obj c280e2d8!
RTMP_TimerListAdd: add timer obj c280def4!
RTMP_TimerListAdd: add timer obj c280e2bc!
RTMP_TimerListAdd: add timer obj c280e544!
RTMP_TimerListAdd: add timer obj c280e4c4!
RTMP_TimerListAdd: add timer obj c2811370!
RTMP_TimerListAdd: add timer obj c2810f8c!
RTMP_TimerListAdd: add timer obj c2811354!
RTMP_TimerListAdd: add timer obj c28115dc!
RTMP_TimerListAdd: add timer obj c281155c!
RTMP_TimerListAdd: add timer obj c2814408!
RTMP_TimerListAdd: add timer obj c2814024!
RTMP_TimerListAdd: add timer obj c28143ec!
RTMP_TimerListAdd: add timer obj c2814674!
RTMP_TimerListAdd: add timer obj c28145f4!
RTMP_TimerListAdd: add timer obj c28174a0!
RTMP_TimerListAdd: add timer obj c28170bc!
RTMP_TimerListAdd: add timer obj c2817484!
RTMP_TimerListAdd: add timer obj c281770c!
RTMP_TimerListAdd: add timer obj c281768c!
RTMP_TimerListAdd: add timer obj c281a538!
RTMP_TimerListAdd: add timer obj c281a154!
RTMP_TimerListAdd: add timer obj c281a51c!
RTMP_TimerListAdd: add timer obj c281a7a4!
RTMP_TimerListAdd: add timer obj c281a724!
RTMP_TimerListAdd: add timer obj c28380c0!
RTMP_TimerListAdd: add timer obj c2837cdc!
RTMP_TimerListAdd: add timer obj c28380a4!
RTMP_TimerListAdd: add timer obj c283832c!
RTMP_TimerListAdd: add timer obj c28380dc!
RTMP_TimerListAdd: add timer obj c28380f8!
RTMP_TimerListAdd: add timer obj c2838114!
RTMP_TimerListAdd: add timer obj c286a7e4!
RTMP_TimerListAdd: add timer obj c286a854!
RTMP_TimerListAdd: add timer obj c286a7fc!
RTMP_TimerListAdd: add timer obj c283864c!
RTMP_TimerListAdd: add timer obj c28056c8!
RTMP_TimerListAdd: add timer obj c2808760!
RTMP_TimerListAdd: add timer obj c280b7f8!
RTMP_TimerListAdd: add timer obj c280e890!
RTMP_TimerListAdd: add timer obj c2811928!
RTMP_TimerListAdd: add timer obj c28149c0!
RTMP_TimerListAdd: add timer obj c2817a58!
RTMP_TimerListAdd: add timer obj c2838378!
RTMP_TimerListAdd: add timer obj c28623d0!
Key1Str is Invalid key length(0) or Type(0)
Key2Str is Invalid key length(0) or Type(0)
Key3Str is Invalid key length(0) or Type(0)
Key4Str is Invalid key length(0) or Type(0)
1. Phy Mode = 9
2. Phy Mode = 9
NVM is Efuse and its size =2d[2d0-2fc]
(Efuse for 3062/3562/3572) Size=0x2d [2d0-2fc]
3. Phy Mode = 9
MCS Set = ff 00 00 00 01
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
SYNC – BBP R4 to 20MHz.l
RTMP_TimerListAdd: add timer obj c28620e8!
Main bssid = ac:a2:13:2b:a4:c4
== rt28xx_init, Status=0
0x1300 = 00064320
selected clock e4e1c0 quot 7
selected clock e4e1c0 quot 7
selected clock e4e1c0 quot 7
RTMP_TimerListAdd: add timer obj c28a97a0!
RTMP_TimerListAdd: add timer obj c28a9944!
/home/user/2011_0517_RT5370_RT5372_RT5390U_Linux_AP_V2.6.0.0_DPA_1/MODULE/os/linux/../../ap/ap_data.c:3731 assert pEntry->Aid == pRxWI->WirelessCliIDfailed
RTMP_TimerListAdd: add timer obj c28bd0ec!
Rcv Wcid(1) AddBAReq
Start Seq = 00000003
RTMP_TimerListAdd: add timer obj c28be500!
ra0 (WE) : Driver using old /proc/net/wireless support, please fix driver !

There are a bunch of shell scripts in there to do various things. When the machine boots up, it runs go.sh, which in turn runs loadapp.sh, and then runapp.sh. This loads a bunch of utXXX processes, which are all ELF binaries, unfortunately, and run the core functions of the treadmill. download.sh downloads the latest scheduled workout in your iFit account into /icon/bin/iFit/download/wpl2. It uses http downloads from iFit’s server, machines.iconfitness.com. The server host name is stored in the IFIT_SERVER environment variable, so it’s quite easy to redirect it to download from a different server. I envision writing a local http server to emulate some of the iFit server’s functionality. It’s easy to follow what the script does by just typing bash -x download.sh in the shell. The server uses SOAP for its API (templates contained in /icon/bin/iFit/network), and downloads are handled via a binary called soap_get.
It turns out that the workouts are consist of 2 files: layout.fit, and a file with .wpl extension, which is the actual workout data. the name of the .wpl file is the first field in layout.fit. Unfortunately, wpl is a proprietary binary format, so it will take some effort to figure it out, but once that is done, it will be possible to create your own custom workouts without using ifit.com! The built-in workouts are all stored in /icon/bin/iFit/builtin/builtin.xx where xx is a number.

To get the files off of the machine for easy examination, all you have to do is launch the built-in ftpd command to get an anonymous ftp server. Even though it’s already configured in inetd.conf, the server won’t start unless you launch it manually on the command line:

# tcpsvd -vE 21 ftpd /
tcpsvd: listening on, starting

Substitute your treadmill’s IP number where I have I dumped out the whole /icon/bin directory for perusal. I used the Filezilla ftp client on MS Windows.

One final thing… download_firmware.sh checks for and downloads the latest firmware. The firmware updates are in the form of a .tgz file. You can take a look at the contents by looking at /icon/restore/app.tgz, which is a copy of the firmware that’s probably for recovering from a botched update..

That’s all for now. I’m surprised that iFit left the system so wide open. It’s possible that when they find out that people are hacking their systems that they will close the telnet backdoor, so if you want to play, it’s probably a good idea not to update your firmware.

If you play around with your iFit, please share your discoveries in the comments below.


Update 2016-04-04: Thanks to Stefano Livi’s uber hacking skillz, I’ve created a github repo: iFitWPL. Using information provided by Stefano, I wrote a C++ program to (mostly) decode WPL files. I have not yet had time to write code to create new WPL files, but there is a file where Stefano shows code in an obscure programming language to create a new WPL: Notes_on_wpl.txt. Please contribute to the github repo, and feel free to discuss any progress in the comments below.

Update 2017-01-10: My Proform treadmill’s motor crapped out. The treadmill never lived up to my expectations, and Costco is letting me return it, so I’m not going to be doing any more hacking on this. Believe it or not, Costco sent a truck to pick up the treadmill, even though I’d had it over a year. Now looking for a better quality treadmill to replace it!