Reverse Engineering: LiveScore.com api encryption

Hello everyone!

Everything stated and reported on this post is for study and demonstration purpose.
There is no violation or usage of copyrighted code nor abuse of service. The code snippet that can be found on the post are reversed and made opensource under GPL license.

Platform: LiveScore.com
Api communication: JSON
Security measure: body encrypted


Request from the original client:

URL http://api.livescore.com/~~/app/07/home/soccer/1.0/
Status Complete
Response Code 200 OK
Protocol HTTP/1.1
SSL
Method GET
Kept Alive No
Content-Type text/plain
Client Address /192.168.1.133
Remote Address api.livescore.com/54.246.163.117


Headers:

GET /~~/app/07/home/soccer/1.0/ HTTP/1.1
user-agent LiveScore_Android_App/new_version
Host api.livescore.com
Connection Keep-Alive
Accept-Encoding gzip


What is superclear is that our response is encrypted, as you can notice by making a simple get request opening: http://api.livescore.com/~~/app/07/home/soccer/1.0/

By digging and debugging the code of the Android App (I’m really familiar with JAVA) I was able to reverse engineering the request structure and the decryption method to obtained styled JSON.

The reversed decryption method, that can be found here, takes 2 parameters, the byte array of the body response and an int32 that is a key obtained by the body. The key is obtained from another little function that takes the bytes from 16 to 35 of the response body (first 15 bytes are discarded and used elsewhere since it’s the query expiration) and from 35 to the end is the encrypted JSON.


Here is a little example on how to use the code, that can be ease ported as well to other languages:

byte[] body = response.body().bytes(); // The bytes of the body response
byte[] key = Arrays.copyOfRange(body, 16, 35);
body = Arrays.copyOfRange(body, 35, body.length);
String json = decrypt(body, key);


Big lacks:

  • SSL
  • Encryption/Decryption methods as well as magic bytes are too easy to spot.

Improvements/Fixes:

  • Encryption/Decryption take times and resources. It’s not needed at all except to hide sensitive informations.
  • Implement hashes on headers/request envelopes.
  • Track users for preventing api abuse

 

 

8 Comments

yaya September 16, 2017 Reply

Hello

have you ported this code in php?

regards

TheC April 4, 2018 Reply

Hello,

If I understood your code properly, the encryption key is “the current date”.
How did you notice about that? or you just tried and bingo!

ugly dude November 30, 2022 Reply

He saw the exact code from Java app and ported it to JS. That’s it.

James September 16, 2018 Reply

Don’t work for me 🙁

String url = “http://api.livescore.com/~~/app/07/home/soccer/1.0/”;

URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();

con.setRequestMethod(“GET”);

con.setRequestProperty(“User-Agent”, “LiveScore_Android_App/new_version”);
con.setRequestProperty(“Host”, “api.livescore.com”);
con.setRequestProperty(“Connection”, “Keep-Alive”);
con.setRequestProperty(“Accept-Encoding”, “gzip”);

byte[] body = IOUtils.toByteArray(con.getInputStream());

body = Arrays.copyOfRange(body, 35, body.length);

String json = Decrypter.decrypt(body, Decrypter.generateKey(body));
System.out.println(json);

Does anyone know where the problem is?

GiovanniRocca September 20, 2018 Reply

this post is quite old… they probably update the stuffs ^^

nawa July 5, 2020 Reply

The javascript code has been found on their site.
com.livescore.CryptUtil().decrypt(str)

com.livescore.CryptUtil = function() {
function a(a, b, c, d) {
switch (d.length) {
case 1:
return a == d.ch0;
case 2:
return a == d.ch0 && b == d.ch1;
case 3:
return a == d.ch0 && b == d.ch1 && c == d.ch2;
default:
return !1
}
}
function b(b) {
var d, e, f, g, h, i = b.charCodeAt(12), j = b.charCodeAt(13), k = b.charCodeAt(14), l = b.charCodeAt(15), m = 0, n = 0, o = 0, p = 0, q = “”, r = null, s = null, t = null, u = null, v = null;
for (58 == i && 32 == j ? (d = {
length: 1,
ch0: 33
},
e = {
length: 1,
ch0: 36
},
f = {
length: 1,
ch0: 37
},
o = 40,
p = 126,
m = c(b.substr(14, 19)),
n = 33) : 58 == i && 58 == j && 32 == k ? (d = {
length: 1,
ch0: 171
},
e = {
length: 1,
ch0: 169
},
f = {
length: 1,
ch0: 187
},
o = 40,
p = 126,
m = c(b.substr(15, 19)),
n = 34) : 58 == i && 58 == j && 58 == k && 32 == l && (d = {
length: 3,
ch0: 33,
ch1: 36,
ch2: 34
},
e = {
length: 3,
ch0: 35,
ch1: 37,
ch2: 38
},
f = {
length: 3,
ch0: 37,
ch1: 35,
ch2: 36
},
o = 40,
p = 126,
m = c(b.substr(16, 19)),
n = 35),
g = b.length – n,
h = g; h > 0; h–)
v = n + h – 1,
r = b.charCodeAt(v),
u = (g – h + 1) % 10,
r >= o && p >= r ? q += o > r – m – u ? String.fromCharCode(r – m – u + (p – o + 1)) : String.fromCharCode(r – m – u) : (s = null,
t = null,
v >= 1 && (s = b.charCodeAt(v – 1)),
v >= 2 && (t = b.charCodeAt(v – 2)),
a(t, s, r, d) ? (q += String.fromCharCode(10),
h -= d.length – 1) : a(t, s, r, e) ? (q += String.fromCharCode(13),
h -= e.length – 1) : a(t, s, r, f) ? (q += String.fromCharCode(32),
h -= f.length – 1) : q += b.charAt(v));
return q
}
function c(a) {
var b, c, e, f;
try {
return b = d(a.charCodeAt(17)),
c = d(a.charCodeAt(18)),
e = d(a.charCodeAt(11)),
f = d(a.charCodeAt(0)) + d(a.charCodeAt(1)) + d(a.charCodeAt(2)) + d(a.charCodeAt(3)) + d(a.charCodeAt(5)) + d(a.charCodeAt(6)) + d(a.charCodeAt(8)) + d(a.charCodeAt(9)) + e + d(a.charCodeAt(12)) + d(a.charCodeAt(14)) + d(a.charCodeAt(15)) + b + c,
c >= b ? f = f + c – b : f += 3 * (e + 1),
f
} catch (g) {
return 27
}
}
function d(a) {
if (a >= 48 && 57 >= a)
return a – 48;
throw “char value is not a number character”
}
return {
decrypt: b
}
}()

This maybe quite easy to convert.

ugly dude November 30, 2022 Reply

Hey, why did you decide to go through the app instead of using the dev tools debugger?

ugly dude November 30, 2022 Reply

Do you have more content on web based re? I’m kinda inclined towards that side and enjoy it more as compared to the others.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.