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();
}
}

}
PetertheMD
Posts: 1
Joined: Sun Mar 08, 2020 1:06 am

Re: Lime SJ2.5 hacking

Postby PetertheMD Sun Mar 08, 2020 1:24 am

Hello!

I also have the updated version, unable to turn on the newer versions with the coding from before. I was not even able to read out the firmware version with the previous coding using SPP, so I am not sure how I can actually proceed. I got to the auction one month late it seems. I was able to simply sniff communications between the scooter and my SPP and I am getting repeating hex codes (as expected) but no response to anything. It seems none of the previous commands worked. But I recall the firmware data length being upped to 3 bytes (could be wrong). Anyway, if you need any help, I have a computer and a scooter that both need jobs, I'd be happy to help. I have most of the knowhow but am not too versed in java.

Maybe you can shed some light, as this command worked(used to) to read firmware: 46 43 16 14 00 00 00 C0 B0
CDogg
Posts: 79
Joined: Tue Sep 03, 2019 7:08 am

Re: Lime SJ2.5 hacking

Postby CDogg Tue Apr 28, 2020 9:40 am

Thanks for your efforts and info on this particular model. I bought a case lot of misc. junked scooters from my local city auction and I was able to identify and work on just about everything except for this one Lime scoot. This one was completely devoid of any identifying stickers, or model info. Having ridden the latest top 3 hive models in my city, it does resemble the current Lime on the street (minus the box on the front, which I assume to be the tracker box). Being that this unit is missing the IoT box, I figured there should be a way to turn it on at the ESC.
After poppin off the handle bars, it looked like the ESC is just sitting right there at the top. I could identify all the different wire harness except 1, which was taped up to the chassis of the ESC (sorry for the lack of pics- hindsight is 20/20)
My question is, being that I have exposed this ESC now, could I power up this behemoth scoot with some sort of power button?
CDogg
Posts: 79
Joined: Tue Sep 03, 2019 7:08 am

Re: Lime SJ2.5 hacking

Postby CDogg Tue Apr 28, 2020 9:50 am

Of course my pics didn't get uploaded with my reply 🤦‍♂️
User avatar
MrShell
Posts: 2
Joined: Tue Dec 15, 2020 9:05 pm

Re: Lime SJ2.5 hacking

Postby MrShell Tue Dec 15, 2020 10:13 pm

Hi everyone! Why is this topic dead?

Thought about reviving it with some info. Assuming everyone stopped trying to reverse the 20191116 fw, I just started 😄.

Here are the working codes so far:
Note1: if you send codes with realterm, they must have this form 0x46 0x43 ... etc
Note2: if you send codes with Termite 3.4, they must have this form 0x4643...etc

1. Show firmware version: 0x464311140000EAFB
2. Show UART version: 0x464311150000DDCB
3. Show hardware version: 0x4643111800009F9A
4. Show some weird id: 0x464311200000F39E
5. Blink front light: 0x464316130001F15D92
6. Lock motor wheel: 0x464316630001F11FE7
7. Unlock motor wheel: 0x464316630001F00FC6

I guess point 5 has some connection with the functions from the Lime app, like ring and light blink.

Those codes work on firmware version: LIS01_FW_1.8.5_201911162121000

A few codes I found online for locking and unlocking the scooter, turning the lights on, off don't work.

If anyone managed to figure this out, kindly send me a private message.

TY all and enjoy!
yardcotulsa
Posts: 1
Joined: Wed Sep 30, 2020 2:44 am

Re: Lime SJ2.5 hacking

Postby yardcotulsa Mon Dec 21, 2020 8:12 am

Cant seem to PM you MrShell..'
User avatar
MrShell
Posts: 2
Joined: Tue Dec 15, 2020 9:05 pm

Re: Lime SJ2.5 hacking

Postby MrShell Wed Dec 23, 2020 12:31 pm

Cant seem to PM you MrShell..'
Hello!

Yeah, seems I can't use private messaging. I sent you an email.

Thanks!
pmihai
Posts: 1
Joined: Sat Dec 26, 2020 6:24 pm

Re: Lime SJ2.5 hacking

Postby pmihai Sat Dec 26, 2020 6:35 pm

hello
I have 1.8.5_201911162121 firmware and I try to find the motor enable command.
I tried this commands:
46 43 16 61 00 01 F1 F2 8F
46 43 16 61 00 03 F1 00 07 88 4B
and
46 43 16 61 00 03 F1 00 00 F8 AC
without success.
Any suggestion is welcome.
Thnks
karelix
Posts: 1
Joined: Thu Jan 21, 2021 7:51 pm

Re: Lime SJ2.5 hacking

Postby karelix Thu Jan 21, 2021 8:04 pm

So now Im stuck with blinking scooter :-D Warning, logical change of 1to0 and adjusting checksum doesnt work with that command. I didnt manage to make it drive with the new firmware as well, but Im working on more automated approach. Do you have any ideas on probable bytes? for example F1 seemed obvious, but I'm now starting to question everything.
mondoc447
Posts: 1
Joined: Sun May 02, 2021 2:33 pm

Re: Lime SJ2.5 hacking

Postby mondoc447 Sun May 02, 2021 2:36 pm

so.. how can I make it work with a Bluetooth adapter?
And what are the commands?

Return to “Other scooters”

Who is online

Users browsing this forum: No registered users and 2 guests