Lime SJ2.5 hacking

hahaha
Posts: 1
Joined: Fri Feb 07, 2020 10:21 am

Lime SJ2.5 hacking

Postby hahaha Fri Feb 07, 2020 10:29 am

With this, I was able to spoof the Lime receiver and turn abandoned scooters on until they updated this month. https://scootertalk.org/forum/viewtopic.php?f=56&t=5767

I've gotten pretty far trying to tease out the latest serial commands needed to turn them on. I wrote a Java program that interacts with them and is brute forcing codes now... If anyone has any interest or info, I'd love some help!
The new FW version reads out as SANTR_FW_2.4.26_20191119100000
An old working one I have here reads LIS01_FW_1.8.3_201907250912XX
import com.github.snksoft.crc.CRC;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.fazecast.jSerialComm.SerialPort;;

class Code {
public String name;
public byte[] code;

public Code(String inName, byte[] inCode) {
name = inName;
code = inCode;
}

public String toString() {
return name;
}
}

public class LimeProbe extends JPanel implements Runnable, SerialResponder {
final static byte read = 0x11, write = 0x16;
final static byte light = 0x12, fw = 0x14, hw = 0x18, id = 0x20, reboot = 0x21, motor = 0x61, lock = 0x63,
timer = 0x71;
private static final Font font = new Font("Monospaced", Font.PLAIN, 12);

Thread tthis;
byte[] readBuffer = { 0 };
byte[] data = { 0 };
long lastDraw;
SerialPort comPort = null;
boolean initialized = false;
ProbeReadThread reader = null;
Vector<byte[]> responses;
Vector<byte[]> validResponses;
JComboBox<Code> comboBox;

public LimeProbe() throws InterruptedException {
Vector<Code> codes = new Vector<Code>();
codes.add(new Code("lock on", buildCode(write, lock, new byte[] { (byte) 0xF1 }))); // lock on
codes.add(new Code("lock off", buildCode(write, lock, new byte[] { (byte) 0xF0 }))); // lock off
codes.add(new Code("motor on", buildCode(write, motor, new byte[] { (byte) 0xF1 }))); // motor on
codes.add(new Code("motor off", buildCode(write, motor, new byte[] { (byte) 0xF0 }))); // motor off
codes.add(new Code("light on", buildCode(write, light, new byte[] { (byte) 0xF1 }))); // light on
codes.add(new Code("light off", buildCode(write, light, new byte[] { (byte) 0xF0 }))); // light off
codes.add(new Code("light flash", buildCode(write, (byte) 0x13, new byte[] { (byte) 0xFF }))); // Light flash
codes.add(new Code("light stop flash", buildCode(write, (byte) 0x13, new byte[] { (byte) 0x00 }))); // noflash
codes.add(new Code("FW", buildCode(read, fw, new byte[] {}))); // firmware ver
codes.add(new Code("reboot?", buildCode(write, reboot, new byte[] { (byte) 0xF1 })));
codes.add(new Code("write custom",
buildCode(write, (byte) 0xF2, new byte[] { (byte) 0xAB, (byte) 0xCA, (byte) 0x98, (byte) 0xB2 })));// AB
// CA
// 98
// B2
codes.add(new Code("write custom", buildCode(write, (byte) 0xF3, new byte[] { (byte) 0x09, (byte) 0x60 })));// 54
// 21
// 56
codes.add(new Code("read custom", buildCode(read, (byte) 0xF3, new byte[] {})));
codes.add(new Code("scan", new byte[] {}));
comboBox = new JComboBox<Code>(codes);
comboBox.setSelectedIndex(codes.size() - 1);
add(comboBox);
responses = new Vector<byte[]>();
validResponses = new Vector<byte[]>();
tthis = new Thread(this);
tthis.start();
while (!initialized) {
Thread.sleep(5);
}
reader = new ProbeReadThread(comPort, this);
}

public static void main(String[] args) throws InterruptedException {
JFrame probeFrame = new JFrame();
probeFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
LimeProbe probe = new LimeProbe();
probeFrame.setSize(1600, 600);
probeFrame.add(probe);
probeFrame.setVisible(true);
}

public void paint(Graphics g) {
g.setFont(font);
if (System.currentTimeMillis() - lastDraw > 42) {
lastDraw = System.currentTimeMillis();
super.paint(g);
// g.setColor(Color.lightGray);
// g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.black);
g.drawString(bToS(data), 40, 80);
g.drawString(bToS(reader.readArray), 40, 140);
g.drawString(bToC(reader.readArray), 40, 160);
g.setColor(new Color(100, 0, 0));
for (int i = 0; i < Math.min(20, responses.size()); i++) {
// for(int i=Math.max(0, responses.size()-10); i<Math.min(10,responses.size());i++) {
byte[] rs = responses.get(i);
if (rs[0] == (byte) 0xFF && rs[1] == (byte) 0xFF && rs[2] == (byte) 0xFF) {
g.setColor(new Color(0, 150, 0));
g.drawString(bToS(Arrays.copyOfRange(rs, 3, rs.length)), 800, i * 14 + 210);
} else {
g.setColor(new Color(100, 0, 0));
g.drawString(bToS(rs), 40, i * 14 + 200);
}
}
for (int i = 0; i < Math.min(20, validResponses.size()); i++) {
// for(int i=Math.max(0, responses.size()-10); i<Math.min(10,responses.size());i++) {
byte[] rs = validResponses.get(i);
g.setColor(new Color(0, 100, 0));
g.drawString(bToS(rs), 40, i * 14 + 200);
}
}
}

public static boolean checkCRC(byte[] data) {
if (data.length >= 3) {
long crc = CRC.calculateCRC(CRC.Parameters.XMODEM, Arrays.copyOf(data, data.length - 2));
byte[] crca = { (byte) ((crc & 0xFF00) >> 8), (byte) (crc & 0xFF) };
byte[] rcrc = Arrays.copyOfRange(data, data.length - 2, data.length);
if (crca[0] == rcrc[0] && crca[1] == rcrc[1]) {
return true;
}
}
return false;
}

public void recieveResponse(byte[] response) {
if (checkCRC(response) && response[6] == (byte) 0xF1) {
// System.out.println("Got good code: " + bToS(response));
responses.add(cat(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, }, response));
validResponses.add(response);
} else {
responses.add(response);
}
if (responses.size() > 20) {
responses.remove(0);
}
}

@Override
public void run() {

try {

//
for (int i = 0; i < SerialPort.getCommPorts().length; i++) {
System.out.println("port found: " + SerialPort.getCommPorts());
}

comPort = SerialPort.getCommPorts()[0];
initialized = true;
System.out.println("Connecting to " + comPort);
comPort.setBaudRate(9600);
comPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
comPort.openPort();

readBuffer = new byte[comPort.bytesAvailable()];
int numRead = comPort.readBytes(readBuffer, readBuffer.length);
if (numRead != 0) {
System.out.println("Cleared buffer of " + numRead);
}

// // Scanning ///////////////////
for (int i = (int)((byte)0x0A); i < 256; i++) {
Code code = (Code) comboBox.getSelectedItem();
for (int j = 0; j < 256; j++) {
if (code.name == "scan") {
data = buildCode(write, (byte) 0x61, new byte[] { (byte) 0xF1, (byte) i, (byte) j });
// (byte)i,(byte)j}); // 2D BRUTE FORCE
// data = buildCode(read, (byte) i, new byte[] {}); // Scan for codes
} else if (code.name == "custom read") {
data = code.code;
} else {
data = code.code;
}
// data = buildCode(read, (byte) 0x61, new byte[] {});
// data = buildCode(write, motor, new byte[] { (byte) 0xF1 }); // motor on
// data = buildCode(write, motor, new byte[] { (byte) 0xF1 }); // motor on
while (reader.busy) {
Thread.sleep(1);
}
comPort.writeBytes(data, data.length);
System.out.println("sent: " + bToS(data));
Thread.sleep(30);// 40
repaint();
}
}
Thread.sleep(500);
System.out.println("Valid responses found:");
for (int i = 0; i < validResponses.size(); i++) {
System.out.println(bToS(validResponses.get(i)));
}
//////////////////////////////
comPort.closePort();
Thread.sleep(100);
} catch (InterruptedException e) {
comPort.closePort();
e.printStackTrace();
}

}

private static byte[] buildCode(byte rw, byte code, byte[] msg) {
if (msg == null) {
msg = new byte[] {};
}
byte[] data = { 0x46, 0x43, rw, code, 0x00, (byte) msg.length };
data = cat(data, msg);
long crc = CRC.calculateCRC(CRC.Parameters.XMODEM, data);
byte[] crca = { (byte) ((crc & 0xFF00) >> 8), (byte) (crc & 0xFF) };
data = cat(data, crca);
// System.out.printf("%02X ", (crc & 0xFF00)>>8);
// System.out.printf("Printing: " + stringOut + "\n");
return data;
}

public static String bToS(byte[] bytes) {
String strOut = "";
for (int i = 0; i < bytes.length; i++) {
strOut += String.format("%02X ", bytes);
}
return strOut;
}

public static String bToC(byte[] bytes) {
String strOut = "";
for (int i = 0; i < bytes.length; i++) {
strOut += (char) bytes;
}
return strOut;
}

public static byte[] cat(byte[] a, byte[] b) {
byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}

class ProbeReadThread implements Runnable {
public boolean busy = false;
public byte[] readArray = { 0 };
SerialPort comPort = null;
SerialResponder serialResponder;

public ProbeReadThread(SerialPort inCom, SerialResponder toBeNotified) {
serialResponder = toBeNotified;
comPort = inCom;
(new Thread(this)).start();
}

@Override
public void run() {
try {
while (true) {
int waited = 0;
boolean verbose = false;
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(513);
byte[] readBuffer = {};
while (waited < 40) {
if (comPort.bytesAvailable() <= 0) {
Thread.sleep(1);
if (busy) {
System.out.print("-");
waited++;
}
} else {
busy = true;
readBuffer = new byte[comPort.bytesAvailable()];
int numRead = comPort.readBytes(readBuffer, readBuffer.length);
// System.out.println(LimeProbe.bToS(data) + " sent");
System.out.print("\n(size:" + numRead + ")");
if (verbose) {
System.out.println(LimeProbe.bToS(readBuffer) + " response");
// System.out.println(bToC(readBuffer) + " chars");
System.out.println();
}
outBuffer.write(readBuffer);
}
} // end of message?
busy = false;
readArray = outBuffer.toByteArray();
serialResponder.recieveResponse(readArray);
System.out.println('\n' + LimeProbe.bToS(readArray) + '\n');
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}

}

Return to “Other scooters”

Who is online

Users browsing this forum: No registered users and 2 guests