Skip to content

Commit e0485b1

Browse files
committed
First trys with SPI direct interaction
1 parent 2936faf commit e0485b1

File tree

9 files changed

+526
-20
lines changed

9 files changed

+526
-20
lines changed

README.md

Lines changed: 223 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,113 @@
1-
# VanMooof-Module
1+
# VanMooof-Module S3
22

3-
### How to get started?
3+
This currently covers the MX25L51245GMI-08G-TR SPI Flash Chip on the SX3 and SX4 if you happen to have one of the few.
4+
5+
This Chip features 512Megabits (64 Megabytes) of Flash capacity.
46

5-
You need the backside of the PCB from the Module to dump the SPI Flash which contains
7+
### Features
68

7-
- ble key
9+
- ble keys (read/write)
10+
- bike comm
11+
- bike Sharing
12+
- Workshop
13+
- bikecomm.vanmoof.com / ublox1.vanmoof.com
14+
- /upload
15+
- /ping-response
16+
- 'guid':'%s','statistics':{'batt':%d,'mac':'%s','swv':'%s','dist':%d}
17+
- /bike-message
18+
-
819
- fmn key
920
- all Firmwares
10-
- Mainware
11-
- Batteryware
12-
- Shifterware
13-
- bleware
21+
- Mainware (STM32F413VGT6 LQFP100)
22+
- Batteryware (STM32L072CZT6 LQFP48)
23+
- Shifterware
24+
- Motorware
25+
- bleware (TMS320)
1426
- Logs
27+
- Shell (UART) for the Module, BLE,
28+
29+
30+
m2m.vanmoof.com
31+
ALARM_BMS_REMOVED
32+
SET_SHIPPING
33+
START_FROM_SHIPPING
34+
PLAY_FIRE
35+
RIDING_MODE
36+
CPU_STOP_MODE
37+
CPU_STOPPED
38+
SHOW_LOCK
39+
AUTOWAKEUP
40+
CARDRIDGE_REMOVED
41+
LIPOCHARGE
42+
RESET
43+
OAD_FILE_TRF
44+
OAD_UPDATE
45+
DIAGNOSE
46+
DIAG_RDY
47+
OAD_FINISH
48+
OAD_FAILED
49+
OAD_RX_SOUND
50+
FACTORY_TEST
51+
PLAY_SHTDN
52+
PLAY_LOCK_SHTDN
53+
PLAY_LOCK_FROM_SLEEP
54+
PLAY_SHTDN_RDY
55+
ALARM_DELAY_ON
56+
TURN_ON
57+
LOW_SOC
58+
PIN_START
59+
PIN_STUCK
60+
PIN_1ST
61+
PIN_2ND
62+
PIN_3ND
63+
PIN_CHECK
64+
PIN_OK
65+
PIN_SHOW_OK
66+
PIN_NOK
67+
PIN_NOK_SHOW
68+
UNLOCK
69+
EXTRA_ALREADY_UNLOCKED
70+
UNLOCK_COUNT
71+
UNLOCK_COUNT_TIMEOUT
72+
LOCK_PLAY_UNLOCK
73+
LOCK_PLAY_START
74+
LOCK_DIM_OFF
75+
LOCK_CLEAR
76+
LOCK_SETUP
77+
LOCK_PIC
78+
LOCK_COUNT
79+
COUNT_OFF
80+
COUNT_CLEAR
81+
FIND_MY_PLAY
82+
BIKE_SHIPPING_ACCIDENTAL_WAKE
83+
BIKE_SHIPPING_LIPOCHARGE
84+
POWERON
85+
SMS_INIT
86+
SMS_READ
87+
SMS_WRITE
88+
CTX_ACT
89+
CTX_DEACT
90+
PING_SEND
91+
MESSAGE_SEND
92+
LOCATION_SEND
93+
POWEROFF
94+
IDLE
95+
WST_DISCHARGE_MODE
96+
WST_CHARGE_MODE
97+
WST_BYPASS_MODE
98+
WST_NONE
99+
100+
### How to get started?
101+
102+
You need the backside of the PCB from the Module to dump the SPI Flash
15103

16104
1. unlock bike and remove Module from the Frame
17-
2. if you do not unlock the bike, the Alarm stays on and will annoy you. I used duct tape to cover the speaker.
105+
2. if you do not unlock the bike, the Alarm stays on and will annoy you. I used duct tape to cover the speaker if i forgot it.
18106
2. open Module and unscrew all internal screws of the PCB to remove the PCB.
19-
3. On the backside of the PCB is a Winbond MX25L51245G with 512Mbit, so 64MB of Flash
107+
3. On the backside of the PCB is the Macronix 16 Pin SPI Flash Chip near the port for the back light.
20108
4. Dump that Flash with an 16 Pin! SPI Flash Chip clamp and a Pi
21-
5. I used an Raspberry Pi Zero v1.1. There you have to enable the SPI Interface with raspi-config
109+
5. I used an Raspberry Pi Zero v1.1. There you have to enable the SPI Interface with raspi-config
110+
22111
```console
23112
# sudo flashrom -p linux_spi:dev=/dev/spidev0.0 -r rom.rom
24113
flashrom v1.2 on Linux 6.1.21+ (armv6l)
@@ -28,4 +117,126 @@ Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
28117
Using default 2000kHz clock. Use 'spispeed' parameter to override.
29118
Found Macronix flash chip "MX66L51235F/MX25L51245G" (65536 kB, SPI) on linux_spi.
30119
Reading flash... done.
31-
```
120+
```
121+
122+
### Bootloader
123+
124+
Press ESC until the MCU reboots and holds itself in the Bootloader.
125+
126+
pack-process�ÀFÀprocess pack files in external flash memory�source/monitor/cmd_packfs.c�Processing pakfs
127+
128+
129+
### Shell login
130+
131+
Look for a HEX Value starting with 76 45 56 6A 47 46 and ending with 00 00 00
132+
If you search for it in a Hex Editor you will notice the end very clearly because the next line begins with "Welcome to ES3"
133+
134+
the sha512sum of the Password is
135+
7edd23b1c75e070db66475bb1869bee9dc64def2cb163dfea39ef8efcb534bf44db2da9e7307590222c516875fb4b07c7450556efd6520d986c5757ce3441bdd
136+
137+
PBNjh0V46Eev8CcfS4LPJg
138+
139+
140+
### Err 23
141+
142+
143+
### Err Sim Card
144+
145+
The Module is looking for a SIM Card with a specific ICCID (Integrated Circuit Card Identifier).
146+
147+
The Prefix of that is
148+
89 31 44 0400
149+
MM CC II N{12} C
150+
151+
MM = 89 (Mobile Networks)
152+
CC = 31 (Netherlands, The)
153+
II = 4X Vodafone
154+
N{12} = Account ID
155+
C = Checksum
156+
157+
158+
### Firmware
159+
We are looking for
160+
50 41 43 4B BC 16 09 00 40 01 00 00 4F 41 44 20 (PACK¼ �@��OAD)
161+
162+
rom.rom
163+
164+
The OAD PACK is like, strings were in 108A5C
165+
bleware.bin (It differs for the FMI Versions. Old Bikes do support FMI, it is just not in the Firmware. Just test it out.)
166+
mainware.bin
167+
motorware.bin
168+
shifterware.bin
169+
batteryware.bin
170+
171+
172+
173+
0x0002000 ?
174+
0x005A000 BLE Secrets (60)
175+
0x005af80 M-ID/M-KEY (60)
176+
0x007c000
177+
0x0280000 VM_SOUND
178+
0x0300000 VM_SOUND
179+
0x0380000 VM_SOUND
180+
0x2621440 VM_SOUND
181+
0x3145728 VM_SOUND
182+
0x4194304 VM_SOUND
183+
0x4718592 VM_SOUND
184+
0x5242880 VM_SOUND
185+
0x5767168 VM_SOUND
186+
0x6291456 VM_SOUND
187+
0x640XXXX unknown
188+
0x6815744 VM_SOUND
189+
0x7340032 VM_SOUND
190+
0x7864336 VM_SOUND
191+
0x8388608 VM_SOUND
192+
0x8912896 VM_SOUND
193+
0x9437184 VM_SOUND
194+
0x9961472 VM_SOUND
195+
0x10485760 VM_SOUND
196+
0x11010064 VM_SOUND
197+
0x11534336 VM_SOUND
198+
0x12058624 VM_SOUND
199+
0x12582912 VM_SOUND
200+
0x13107200 VM_SOUND
201+
0x13631488 VM_SOUND
202+
0x14155776 VM_SOUND
203+
0x14680064 VM_SOUND
204+
0x15204352 VM_SOUND
205+
0x15728640 VM_SOUND
206+
0x16252928 VM_SOUND
207+
0x16777216 VM_SOUND
208+
0x17301504 VM_SOUND
209+
0x17825792 VM_SOUND
210+
0x66965504 LOGS?
211+
// WRONG?
212+
0x3fdd000 LOGS
213+
214+
215+
READ AND DECODE LOGS
216+
6 GSM power ok
217+
1723060256 Info ask tracking
218+
1723060256 GSM_CMD_SMS_INIT
219+
1723060256 GSM: sms= 0
220+
1723060256 GSM_CMD_POWEROFF
221+
1723060256 Poweroff g350..
222+
1723060261 Poweroff g350 ok
223+
1723060261 ES3.1 Main 1.08.02
224+
1723060261 ES3 boot 1.09
225+
1723060261 Motorware S.0.00.22
226+
1723060261 BMSWare 1.14 RSOC 100 Cycles 64 HW 3.10
227+
1723060261 Shifterware 0.237 stored: 0.237
228+
1723060261 BLEWare 2.4.01
229+
1723060261 GSMWare 08.90
230+
1723060261 CMD_BLE_MAC 24:9F:89:86:A9:1F
231+
1723060261 PDOCP 0
232+
1723060261 PDSCP 1
233+
1723060261 iccid 89314404000979522399
234+
1723060261 Modem ready
235+
1723060261 GSM_CMD_IDLE
236+
1723060280
237+
238+
239+
### Important caveats.
240+
241+
This is all based on reverse Engineering. So there might be some Versions differences.
242+
So make a backup, save it in a save place like 1Password. If you compress the dump, the file gets very small.

dumpFlash.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
const (
9+
readCommand = 0x03 // Low-speed read command
10+
flashSize = 64 * 1024 * 1024 // 64 MB
11+
chunkSize = 256 // 256 bytes per read operation
12+
)
13+
14+
func dumpFlash() {
15+
// MAC
16+
// FRAME
17+
18+
macAddress := ""
19+
frameNumber := ""
20+
21+
conn, err := spiConnect()
22+
if err != nil {
23+
fmt.Println("")
24+
}
25+
26+
// Open output file
27+
file, err := os.Create("VMES3-" + frameNumber + "-" + macAddress + ".bin")
28+
if err != nil {
29+
fmt.Printf("Failed to create output file: %v", err)
30+
return
31+
}
32+
defer file.Close()
33+
34+
// Sequentially read 64MB in 256-byte chunks
35+
for offset := 0; offset < flashSize; offset += chunkSize {
36+
// Set up 24-bit address
37+
address := []byte{
38+
byte(offset >> 16),
39+
byte(offset >> 8),
40+
byte(offset),
41+
}
42+
43+
// Prepare the read command with the address
44+
readBuffer := append([]byte{readCommand}, address...)
45+
46+
// Buffer to store incoming data
47+
data := make([]byte, chunkSize)
48+
49+
// Execute SPI read
50+
if err := conn.Tx(readBuffer, data); err != nil {
51+
fmt.Printf("SPI transaction failed at offset 0x%X: %v", offset, err)
52+
return
53+
}
54+
55+
// Write the data to the file
56+
if _, err := file.Write(data); err != nil {
57+
fmt.Printf("Failed to write data to file at offset 0x%X: %v", offset, err)
58+
return
59+
}
60+
61+
fmt.Printf("Read and saved chunk at offset 0x%X\n", offset)
62+
}
63+
}

go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
module VanMooof-Module
22

3-
go 1.19
3+
go 1.22
44

5+
toolchain go1.23.3
6+
7+
require (
8+
periph.io/x/conn/v3 v3.7.1 // indirect
9+
periph.io/x/host/v3 v3.8.2 // indirect
10+
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
periph.io/x/conn/v3 v3.7.1 h1:tMjNv3WO8jEz/ePuXl7y++2zYi8LsQ5otbmqGKy3Myg=
2+
periph.io/x/conn/v3 v3.7.1/go.mod h1:c+HCVjkzbf09XzcqZu/t+U8Ss/2QuJj0jgRF6Nye838=
3+
periph.io/x/host/v3 v3.8.2 h1:ayKUDzgUCN0g8+/xM9GTkWaOBhSLVcVHGTfjAOi8OsQ=
4+
periph.io/x/host/v3 v3.8.2/go.mod h1:yFL76AesNHR68PboofSWYaQTKmvPXsQH2Apvp/ls/K4=

main.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,30 @@ import (
55
)
66

77
var (
8-
moduleFileName = flag.String("f", "", "Module file name")
8+
moduleFileName = flag.String("f", "", "Module file name")
9+
changeUnlockKey = flag.String("u", "", "Change unlock key")
10+
debugLogging = flag.Bool("d", false, "Enable debug logging")
11+
sudo = flag.Bool("iKnowWhatIAmDoingISwear", false, "Use sudo")
12+
showBLESecrets = flag.Bool("show", false, "Show BLE secrets")
13+
//file os.File
914
)
1015

1116
func main() {
1217

1318
flag.Parse()
1419

20+
//if *moduleFileName != "" {
1521
file := loadFile()
16-
readSecrets(*file)
22+
//}
23+
24+
if *showBLESecrets && *moduleFileName != "" {
25+
readSecrets(*file)
26+
readLogs(*file)
27+
}
28+
29+
if *changeUnlockKey != "" {
30+
writeSecrets("unlock", *changeUnlockKey)
31+
}
1732

1833
err := file.Close()
1934
if err != nil {

readLogs.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
"os"
7+
)
8+
9+
func readLogs(file os.File) {
10+
11+
offset := int64(0x3fdd000)
12+
length := 16 // Number of bytes to read
13+
14+
buf := readFromFile(file, offset, length)
15+
16+
// Convert to hex and print
17+
fmt.Println("Logs:", hex.EncodeToString(buf))
18+
}

0 commit comments

Comments
 (0)