5

I am planning to do game data mining in LOL but stuck at parsing replay files. I find that the most popular replay recorder is LOL Replay which records games in .lrf files. They are saved as binary files. I try to print a lrf file to find some patterns in it. As far as I know, the file has two parts:

  1. The initial part is meta data. It's human readable. At the end of it, it shows an encryption key(32bytes) and a client hash for this .lrf file.

  2. The second part has several sections. Each section is in "RESTful URL+encryption+padding(possibly)" format. For example:

    ?S4GI____GET /observer-mode/rest/consumer/getGameDataChunk/EUW1/1390319411/1/token
    ?S4GH____?¥?G??,\??1?q??"Lq}?n??&??????l??(?^P???¥I?v??k>x??Z?£??3Gug
    ......
    ??6GI____GET /observer-mode/rest/consumer/getGameDataChunk/EUW1/1390319411/2/token
    

    Some are even unreadable characters.3

I have followed this link and this wiki. It seems like they use BlowFish ECB Algorithm plus PKCS5Padding to encrypt after using GZIP to compress contents. But I failed to decrypt contents using the 32 bytes encryptionkey in meta data. And I am not sure where I should start to read and where to stop because JVM keeps warning me that Given final block not properly padded.

So my question is:

  1. Is there any one who is familiar with Blowfish Algorithm and PKCS5Padding? Which part of those binary files should I read to decrypt between two consecutive RESTful URL? Do I use the right key to decrypt? (the 32 bytes encryption key in the meta data)
  2. Given the patterns around each RESRful URL, could anyone make a guess which algorithm exactly LOL uses to encrypt/decrypt contents? Is it Blowfish algorithm?

Any help would be appreciated. Thank you guys.



Edit @6.17:

Following Divis and avbor's answers, I tried the following Java snippet to decode chunks:

    // Decode EncryptKey with GameId
    byte[] gameIdBytes = ("502719605").getBytes();
    SecretKeySpec gameIdKeySpec = new SecretKeySpec(gameIdBytes, "Blowfish");
    Cipher gameIdCipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding");
    gameIdCipher.init(Cipher.DECRYPT_MODE, gameIdKeySpec);
    byte[] encryptKeyBytes = Base64.decode("Sf9c+zGDyyST9DtcHn2zToscfeuN4u3/");
    byte[] encryptkeyDecryptedByGameId = gameIdCipher.doFinal(encryptKeyBytes);

    // Initialize the chunk cipher
    SecretKeySpec chunkSpec = new SecretKeySpec(encryptkeyDecryptedByGameId, "Blowfish");
    Cipher chunkCipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding");
    chunkCipher.init(Cipher.DECRYPT_MODE, chunkSpec);

    byte[] chunkContent = getChunkContent();
    byte[] chunkDecryptedBytes = chunkCipher.doFinal(chunkContent);

It works with no error when decoding encryptionkey with gameid. However it doesn't work in the last two lines. Currently I just hard coded getChunkContent() to return an byte array containing the bytes between two RESTful URLs. But Java either returns "Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher"

Or

returns "Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded".

I notice that the hex pattern between two RESTful URLs are as follows: (hex for first URL e.g. /observer-mode/rest/consumer/getKeyFrame/EUW1/502719605/2/token) + 0a + (chunk contents) + 000000 + (hex for next URL)

My questions are:

  1. Which part of chunks need to be included? Do I need to include "0a" right after the last URL? Do I need to include "000000" before the next URL?

  2. Am I using the right padding algorithm (Blowfish/ECB/PKCS5Padding)?

My test lrf file could be downloaded on : https://www.dropbox.com/s/yl1havphnb3z86d/game1.lrf



EDIT @ 6.18

Thanks to Divis! Using the snippet above, I successfully got some chunk info decrypted without error. Two things worth noting when you write your own getChunkContent():

  1. The chunk content starts right after "hex for previous url 0a".

  2. The chunk content ends as close as possible to "0000000 (hex for next url)" when its size reaches exactly a multiple of 8.

But I still got two questions to ask:

  1. Here is an example of what I decode for the content between two .../getKeyframe/... RESTful urls.

    39117e0cc2f7e4bb1f8b080000000000000bed7d0b5c15d5 ... 7f23a90000
    

    I know Gzip compressed data starts with "1f8b08..." according to this RFC doc. Can I just discard "39117e0cc2f7e4bb" and start gzip decompress the proceeding content? (Actually I've already tried to start decoding from "1f8b08..", at least it could be decompressed without error)

  2. After the gzip decompression, the result is still a long sequence of binary (with some readable strings like summoners names, champions names, etc.) When I look at the wiki, it seems like it is far from finish. What I expect is to read every item, rune, or movement in readable string. How exactly can I read those game events from it? Or we just need some patience to figure them out ourselves with the community?

Millions of thanks!

Community
  • 1
  • 1
czxttkl
  • 486
  • 4
  • 14
  • A lot of '?' characters in your example means that first you need to resolve the data encoding problem - either the data is text but not in the encoding you are trying to use or the data is raw binary and should not be converted to text by simple byte-to-character conversion at all. – Oleg Estekhin Apr 03 '14 at 06:17
  • I know I couldn't simply convert those bytes into characters. The reason that I print them brutally is to see if there is some pattern in those bytes. I tried several encoding methods, e.g. UTF-8 and US-ASCII, which all ended up with printing unreadable characters. I honestly don't know how to convert those bytes before I could decrypt them. – czxttkl Apr 04 '14 at 04:09
  • Sounds like a super fun project. I'm doing my own LoL-related project by intercepting and analyzing network traffic. If you get past your current roadblock and want another developer on your project, hit me up. – dhalsim2 Jun 12 '14 at 18:49
  • Not yet. I tried many ways to decrypt it but they all didn't work. @dhalsim2 – czxttkl Jun 16 '14 at 02:46
  • @czxttkl do you have code which works for your example rlf file or at least and example of decrypted and unpacked data? I'm trying to get this to work for .rofl files but I'm completely lost – ditoslav Jul 02 '20 at 09:41

2 Answers2

3

Repository dev contributor here, according to the wiki, the key is the base64 Blowfish ECB "encryption_key" (with game id as key for the blowfish).

Then, use this decrypted key to decode the content (blow fish ECB too). Then, gzip decode.

base64decode encryptionkey = decodedKey
blowfishECBdecode decodedKey with (string) gameId as key = decodedKey

blowfishECBdecode content with decodedKey as key = decodedContent
gzipdecode decodedContent = binary

I made a library to download and decode replay files : https://github.com/EloGank/lol-replay-downloader and the CLI command is also available : https://github.com/EloGank/lol-replay-downloader-cli
Hope it'll help :)

Divi
  • 800
  • 1
  • 7
  • 16
  • Hi @Divi I updated my progress. Can you kindly help me with that? Thank you very much. My project is really in bad need of help from someone like you. – czxttkl Jun 18 '14 at 03:53
  • I edited my answer. Your gameId must be casted as string. Don't forget to gzdecode your decrypted chunk/keyframe content. I don't know about retrieving chunks or keyframes from LOLReplay software, because I donwload them directly from the official REST service. – Divi Jun 18 '14 at 08:49
  • Answer for your last edit : decrypt a keyframe, you should able to see strings like the summoners name, champions name, neutral monsters name, etc. Then, if you are able to see these strings, you have succeed :) – Divi Jun 18 '14 at 21:14
  • Hey Divis, thanks! I could see those names in strings after decoding! However, the remaining binary content still boggles me a lot. I think the wiki is far from getting whole mappings of all items, runes, etc. What I expect (naively) to use in my research project is to get everything in readable strings so that we could know everything of players. Can we achieve that or we are still on the road to it? Thank you. – czxttkl Jun 19 '14 at 01:31
  • We are getting to parse some new data. I'm actually parsing 20+ new data based on the keyframe. But for the chunk specification, we need more time/help. You have to create a parser and read the whole keyframe, decrypt hex to dec or float, etc. If you wait for this week-end, I'll update the wiki keyframe spec with new data. – Divi Jun 19 '14 at 11:20
  • Sorry to chim in on your conversation, but does any of you know a way to get the original encryptionKey via REST API to begin with ? The wiki only mentions getting it via getGameMetaData, but since that just has an emptry string there's got to be another way ? – DNR Jul 27 '14 at 17:31
  • 1
    You'll always have an empty string. Maybe because in the past Riot saved this data in the REST API. But for now, you have to fetch it from the RTMP API (from the service : retrieve_in_progress_spectator_game_info). I made an opensource RTMP API, take a look here : https://github.com/EloGank/lol-php-api/blob/master/doc/routing.md#route-list – Divi Jul 27 '14 at 20:53
1

To my knowledge, you decrypt the chunks and keyframes using Blowfish. In order to get the key to decrypt said chunks and keyframes, you take the given encryption key, base64 encode it, and then use Blowfish on that using the game id as the key in order to get the actual encryption key for the chunks and keyframes.

avbor
  • 43
  • 7
  • Hi @avbor, I have followed your step to get the actual encryption key. However I still encounter some problems when decoding chunks. Can you kindly help me with that? Thanks! – czxttkl Jun 18 '14 at 03:54