Ocean SSTool (anticheat.site) writeup.

Background
Ocean is another Minecraft "Screensharing tool", which executes intrusive scans on your computer to try and figure out if you're cheating. Ocean's client is written in Python, and their API and program security is about as secure as a Cheeto door deadbolt.

Ocean markets themselves as "A first choice solution to all Screensharing complications". Apart from this making no sense as a slogan, their client and API are very terribly written, and I will demonstrate why.
Ocean's client supports both Windows and Linux, as it is written in Python and then (badly) obfuscated and then packed into a PyInstaller executable, which asks for Administrator permissions on Windows. Yikes.
Ocean's Windows client triage report (7/10): https://tria.ge/230630-q1rv1sda98
Decompilation and Initial Analysis
The python source code is first obfuscated using this (really crappy) obfuscator: https://github.com/billythegoat356/Specter, before being fed into PyInstaller to be converted into a single binary. Anyone with an intermediate knowledge of python can simply work backwards from the obfuscator source code, and write your own deobfuscator.
Firstly, unpacking the binary from it's PyInstaller shell is trivial, one can simply use https://github.com/extremecoders-re/pyinstxtractor a wonderful tool known as PyInstxtractor.
This returns all the guts of the program, but what we're curious in is Ocean.pyc, which is the main bytecode for their program.

The first stage is mostly just a million characters of pure gibberish, as it's a massive bytearray of the executable's second stage.

This isn't very difficult though, and one can quickly move onto the third stage, which is more readable.

The only sneaky part about this stage, is that it uses the colon character to separate the lines and hides them off screen, trying to confuse a very sleepy reverse engineer.

This is the last real step before getting the actual source code, which looks like this.
def temp ():#line:1029
O00OO0000O0OO0000 =os .environ ['TEMP']#line:1030
for O0O0O000OOO00OOOO ,O0000O0O0OOO0O00O ,O0O00OOOO0O0OO0O0 in os .walk (O00OO0000O0OO0000 ):#line:1032
try :#line:1033
for OOOO000000OO0OOO0 in O0O00OOOO0O0OO0O0 :#line:1034
if 'jnativehook'in OOOO000000OO0OOO0 .lower ():#line:1035
detect .add (f"JNativeHook Library:::Out of instance",json_data )#line:1036
debug .add (f"Detected JNativeHook in {OOOO000000OO0OOO0}",json_data )#line:1037
except :#line:1038
continue #line:1039
This is very much readable code, but needs a bit of refactoring...
I have painstakingly gone through and refactored all the variables we care about, and now I will go through and explain the various issues in the code.
The first step is a quick API call to validate the support pin given by the Admin.

This is done with this function:
def pinverify(): # line:1130
global r # line:1131
global pin # line:1132
randomHash = ''.join(random.choices(string.ascii_letters + string.digits, k=545)) # line:1134
headers = {"programHash": randomHash} # line:1138
pin = str(dpg.get_value(pin_box)) # line:1140
r = requests.post(f'https://anticheat.site/api/pins/check/{pin}/', headers=headers) # line:1142
if r.status_code != 200: # line:1144
return # line:1145
if True == r.json()['success']: # line:1147
scan(r, pin) # line:1148
The program generates a completely random string of length 545, and sends that via a POST request to the API (https://anticheat.site/api/pins/check/{pin}) with the "hash" included in a header, and the pin in the path.

The server then responds with a hash of it's own that's remains the same, no matter what you send as a random "hash", due to the fact that it simply sha256 hashes the pin code and uses that as the hash. How secure.

If that succeeds, the program then begins it's series of scans, which are shown in order here:

Don't worry, I will be going through them soon. They are terribly written.
After executing these scans, it POST requests the findings to their API, in a very insecure and abusable manner.
durtext = scandur(timeElapsed) # line:1194
debug.add(f"Scan time: {durtext}", json_data) # line:1195
scanResultData = {'authorization': pinVerifyRequest.json()['hash'], 'vpn': vpn,
'country': country_data[f'{ip_address}'][
'country'] if country_data and f'{ip_address}' in country_data and 'country' in
country_data[f'{ip_address}'] else 'unknown', 'logon': boottext,
'javaw': javawstart, 'recyclebin': datares, 'alts': str(uuids), 'cheating': '0',
'prefetch': durtext, 'hosts': gameurl, 'threads': OSystem, 'journal': '1', 'pcasvc': '1',
'detects': json.dumps(sorted(list(set(json_data.get('detect', []))))),
'warning': json.dumps(sorted(list(set(json_data.get('warning', []))))),
'debug': json.dumps(sorted(list(set(json_data.get('debug', [])))))} # line:1214
isCheating = False # line:1216
if "detect" in json_data: # line:1217
isCheating = True # line:1218
if isCheating: # line:1220
scanResultData["cheating"] = "2" # line:1221
elif "warning" in json_data: # line:1223
scanResultData["cheating"] = "1" # line:1224
currentUTCTime = requests.get('https://worldtimeapi.org/api/timezone/Etc/UTC') # line:1226
UTCTimeAsJson = currentUTCTime.json() # line:1227
DateAndTimeAsISOString = UTCTimeAsJson['datetime'] # line:1228
datetime = xd.strptime(DateAndTimeAsISOString, "%Y-%m-%dT%H:%M:%S.%f%z") # line:1229
AESKey = datetime.strftime("%H:%M:%S").encode() # line:1230
if len(AESKey) not in [16, 24, 32]: # line:1232
AESKey += b' ' * (32 - len(AESKey)) # line:1233
cipher = AES.new(AESKey, AES.MODE_CBC, b'0123456789ABCDEF') # line:1235
scanDataEncoded = str(scanResultData).encode() # line:1236
if len(scanDataEncoded) % 16 != 0: # line:1237
scanDataEncoded += b' ' * (16 - len(scanDataEncoded) % 16) # line:1238
cipherText = cipher.encrypt(scanDataEncoded) # line:1240
resultHeaders = {'Content-Type': 'application/json'} # line:1242
data = {'ciphertext': base64.b64encode(cipherText).decode('utf-8')} # line:1243
try: # line:1244
req = requests.post(f"https://anticheat.site/api/pins/set_result/{pin}",
headers=resultHeaders, data=json.dumps(data))
The server will happily accept anything you send it, which is how I was able to get Notch and Jeb_ marked as cheaters with this obviously fake data:

The vectors it detects
The client fetches a list of signatures to scan for from the API, using these endpoints:
- https://anticheat.site/detections/jar
- https://anticheat.site/detections/raws
- https://anticheat.site/detections/javaw
- https://anticheat.site/detections/dps
- https://anticheat.site/detections/dns
However, attempting to read the files results in more gibberish, so we must look at the code for answers, and this is where the shockingly bad programming comes into play!

The code responsible for de-encrypting the text back into a readable form is as follows:
# Probably *the* worst AES encryption i've seen in my life
dllName = "Kernel32.dll".encode()
if len(dllName) not in [16, 24, 32]:
dllName += b' ' * (32 - len(dllName))
aesKey = dllName
javaDetectionsURL = "https://anticheat.site/detections/jar"
req = requests.get(javaDetectionsURL)
IV = req.content[:16]
cipher = AES.new(aesKey, AES.MODE_CBC, IV)
cipherText = req.content[16:]
decodedCipherText = cipher.decrypt(cipherText)
slicedCipherText = decodedCipherText[-1]
decodedCipherText = decodedCipherText[:-slicedCipherText]
strippedAndDecodedText = decodedCipherText.decode().strip()
jsonCipher = json.loads(strippedAndDecodedText)
You read that right. It uses b'Kernel32.dll '
as the AES key. Wow. The absolute pinnacle of security. Fort Knox cowers in the presence of anticheat.site!
In any case, I used this script I wrote to dump the detection signatures into their own text files, which I will also attach here, for the curious.
The program then downloads and drops strings64.exe by SysInternals and simply starts running it on all programs and compares it to the above files. Truly advanced anticheat software! RIOT should hire them for Vanguard!
Conclusion
Overall, there's not much to say about this infograbber tool. I implore you to download the refactored sample and have a look at it, you will be amazed by what you'll find.
This file isn't really malicious, it's just really terrible at it's job, and you could easily just send a fake result to the API if someone attempts to use this tool on you, clearing you of any wrongdoings.
You could also mark random users as Cheaters and possibly get them banned from servers permanently, which really means people shouldn't trust this software's judgements in the slightest.
Credits:
- nope, for being one of the smartest reverse engineers I know and helping with initial decompilation.
- Zach for helping me trawl through this terrible code.
Bonus!
One of the developers of this tool replied to a tweet of mine recently with the following message:

Hmmm.... Something about people who live in glass houses shouldn't throw stones? Not sure how that goes.