My attempt at reverse engineering Toram Online

This page needs editing!!!!

Goals

Actually achieved

Table of contents

Preface

I wanted to reverse engineer this simple MMO called Toram Online. I worked with the Steam version, since it's newer (1.0.6 vs 1.0.5p on AsobimoGames Launcher), and I found it on Steam first. I have already played it for some time before attempting to crack it. My goal was not to make hacks, but instead, to make private server in case official one's will die (RIP The Matrix Online). On this page I want to document what I did, how I did it, why it failed, and how to move forward.

0. What should I look for - wireshark

Okay, first things first. What should I look for? Let's open up Wireshark and check what's up. Clearly game run's on UDP protocol, contacting only one server. Sometimes there's HTTPS request for unknown thing, sometimes DNS query, and sometimes random packet that's not related to the game since I catch every packet going out of my system. From this information I can deduce that game communication run's on UDP (like running, changing weapon, actions), and some informations are requested over HTTPS.

1. I need links - x64dbg

I know what I'm looking for, maybe I could dump HTTPS URL strings from memory? I already knew that Toram Online had antycheat (look 2. Trying something else - il2cppDumper + dnSpy + IDA), so I didn't even bother trying Cheat Engine and browsing memory. What if I could attach debugger? After attaching x64dbg to running process, game crashed without any error. Let's try starting the game from debugger. It start's, but immediately after 3rd default breakpoint, it throws error that "there's a process attached to the game, so it won't run".

I don't know what to do at this point, only couple of times tried to break anti-debugger functionality without any knowledge how this works. I found great video on basic anti-debugger stuff with Windows API, so let's check that out.

After watching this, I knew what I should look for. First: isDebuggerPresent. I tried replacing function return with "mov eax, 0; ret", however at every application restart within x64dbg, it simply went back to original. I've spent some time trying to fight with x64dbg, but finally failed. Now that I think about it, there are three threads spawned at application start, which have ticks (just like in the video). Maybe they could check if ticks between them are equal, and check if debugger is attached. However, I don't know how to pause single threads in x64dbg, so that's something You could try.

2. Trying something else - il2cppDumper + dnSpy + IDA

This one I actually did waaaaay before going elbow deep into reverse engineering Toram, but I think it should be here. Basically, with Unity games, You have two methods of compilation (as far as I know): il2cpp, and native. Il2cpp might be encrypted (like Fortnite, google "Fortnite AES key archive"), while native apps can be directly disassembled with dnSpy. Toram Online is compiled using il2cpp, but without any encryption, so we are able to use il2cppDumper, create dummy DLLs, and read code structure using dnSpy. I've read somewhere that VA's (virtual addresses) shown in dnSpy, should correspond to subroutines in raw executable, however I didn't have such luck while using IDA; they were offseted +- 0x10 bytes, and just didn't look good. At least we can see how the game is written using objects (which was lesson in itself for me).

3. I got domains - wireshark

Back to wireshark, this one's simple. For whatever reason, Unity (or my Windows) does not cache DNS queries (or TTL for these domains is so low). Every time You start Toram Online, it queries DNS for a couple of domains: api.ipify.org, auth.asobimo.com, common.asobimo.com, app.toram.jp, tgs16.toram.jp, tgs36.toram.jp, toram-jp.akamaized.net, and some steam authentication stuff. Most interesting thing here for me was api.ipify.org. It just returns Your IP address. What for? No idea, maybe some anti-proxy measure, or simply statistics. We now know domains, let's check what they do.

4. I need data (failed) - mitmproxy

Wow, I didn't know how bad mitmproxy sucks. I used it years ago to check some android app, so I thought that it could help me now. Well, nope. Mitmproxy certificates are ALWAYS insecure, as in, even being hosted on windows machine, they do not install root certificates. Toram Online is good enough to know, that the certificate is not secure.

5. I need data (actually working) - dnsmasq + nginx + mkcert

An now for something great, let's make a quick and easy DNS honeypot, so we could spoof our domains, let's use nginx for file hosting (and later for HTTPS requests debugging), and mkcert for making our little custom root certificate and certificates for domains in local network. This setup works great, simply add new domain to /etc/hosts:

192.168.1.54 app.toram.jp
    

Then, add configuration to nginx config:

server {
        listen 80;
        listen 443 ssl;
        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name app.toram.jp;
        ssl_certificate /etc/nginx/cert/_wildcard.toram.jp.pem;
        ssl_certificate_key /etc/nginx/cert/_wildcard.toram.jp-key.pem;
        access_log /var/log/nginx/access.app.toram.jp.log;
        location / {
                try_files $uri $uri/ =404;
        }

        error_page 405 =200 $uri;
}
    

And finally, let's generate our certificates:

mkcert -install
mkcert '*.toram.jp'
    

One more thing, we need to install our root certificate on windows machine, so it will see all of our new certificates as trusted and secure. We do this using Microsoft Management Console, by clicking File->Add or Remove Snap-ins->Certificates->OK, and importing our root CA into "Trusted Root Certification Authorities". It should show new certificate with "user@linux-hostname" in name. After we're done with configuration, let's run Toram, and check what's up. It should show what kind of files over HTTPS it's trying to get. Here are some examples:

192.168.1.13 - - [11/Jan/2022:19:38:41 +0000] "GET /resources/windows/releaseC/CommonAssetBundle.unity3d HTTP/1.1" 200 304131 "-" "UnityPlayer/2018.4.27f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)"
192.168.1.13 - - [11/Jan/2022:19:38:50 +0000] "GET /resources/GamesystemFlag/Version0/GamesystemFlag.csv?637775303276712239 HTTP/1.1" 200 33 "-" "UnityPlayer/2018.4.27f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)"
192.168.1.13 - - [11/Jan/2022:19:38:56 +0000] "GET /resources/windowsOrbShop/pc_o_h/RevisionInfo.csv?637775303341494627 HTTP/1.1" 404 162 "-" "UnityPlayer/2018.4.27f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)"
192.168.1.13 - - [11/Jan/2022:19:46:05 +0000] "GET /resources/windows/releaseC/Field/Field_88.unity3d HTTP/1.1" 404 162 "-" "UnityPlayer/2018.4.27f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)"
    

Using this knowledge, we now know that map files are stored under /resources/windows/releaseC/Field/. GamesystemFlag.csv is actually not needed for game operation. With only toram-jp.akamaized.net, I was able to launch the game, and download map, MBG, and common asset files from local server. Sic!

6. Wait, I need to authenticate - nginx + netcat

I got files, and file paths, but what about authentication? On every game start, it connects to common.asobimo.com, auth.asobimo.com, and app.toram.jp. They all are needed for authentication stack. Nginx show only what path is requested, but what about POST header? (AFAIK it is possible to log headers into log file in nginx, but I had another idea) Let's use reverse proxy and netcat. Nginx will handle HTTPS connection, and forward raw HTTP request to netcat listening on whatever port locally. First we need to add proxy_pass in our nginx configuration, then run netcat in listening mode, and off we go.

location / {
    proxy_pass http://127.0.0.1:8080;
}
    
nc -l -p 8080
    

This should show us raw HTTP request header, and so we can work with that.

(app.toram.jp/mainteinfo_staging/index.php)

POST /mainteinfo_staging/index.php HTTP/1.0
Host: 127.0.0.1:8080
Connection: close
Content-Length: 37
User-Agent: UnityPlayer/2018.4.27f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)
Accept: */* 
Accept-Encoding: identity 
Content-Type: application/x-www-form-urlencoded
X-Unity-Version: 2018.4.27f1

cont=mainte&act=checkApp&aid=801&id=1
    

7. Maybe other version - AsobimoGames Launcher

The only difference is that official AsobimoGames Launcher uses older version of the game (which could mean that the servers are not compatible, but I think I saw same server domain names being requested in both cases), and the launcher itself is needed for game to run (automatic logging in). Everything else is just the same.

8. I don't care anymore - What You can do next

Yeah, I failed, but learned a lot. If You want to crack this game even further, here are my tips: