Let's view what javascript is doing. The Javascript is obfuscated... after some analysis in VSCode this is what I ended up with.
Note: You can view this values from console tab in Developer Tools without running script, but you'll have to use original names.
So script gets some file, collects imports (some functions), calls copy_char a lot and finally checks if flag is correct. The file it gets is WebAssembly.
Without diving too much into assembly we can view flag with strings.
We are given almost same code (I converted it for more readability). Reorder array -> Get Wasm (Web Assembly) -> Use assembly functions -> Compare to flag -> Profit.
Downloading wasm from server and running strings shows:
Looking at the last line it seems like flag, but encrypted, 110% bet it's an XOR.
Opening wasm into wasm2wat gives us somewhat readable code. Searching for XOR (local.set $l8 (i32.xor(local.get $l6) (local.get $l7))), now it's clear that XOR is involved, but which one is the key and which one is the flag.
Instructions before XOR are following. In XOR key is constant so the KEY must be 8.
Viewing JavaScript the file looks the same as previous ones, but URI is provided to us without obfuscating. let _0x487b31 = await fetch("./qCCYI0ajpD")
In wasm2wat we can see some odd values, probably flag and key (XOR operation can be found in code):
While it's not 100% clear what the code is doing, it's understandable that input gets XOR-ed with key and this key get cycled through, as commonly seen in XOR ciphers.
└─$ ./wasm-decompile code.wasm
...
function copy(a:int, b:int) {
var c:int = g_a;
var d:int = 16;
var e:int_ptr = c - d;
e[3] = a; // Input?
e[2] = b; // Key?
var f:int = e[3];
if (eqz(f)) goto B_a;
var g:int = 4;
var h:int = e[2];
var i:int = 5;
var j:int = h % i; // Key % 5?
var k:ubyte_ptr = g - j;
var l:int = k[1067];
var m:int = 24;
var n:int = l << m;
var o:int = n >> m;
var p:int = e[3];
var q:int = p ^ o; // XOR Operation
e[3] = q;
label B_a:
var r:int = e[3];
var s:byte_ptr = e[2];
s[1072] = r;
}
cipher = "\x9dn\x93\xc8\xb2\xb9A\x8b\x94\xc6\xdf3\xc0\xc5\x95\xde7\xc3\x9f\x93\xdf?\xc9\xc3\xc2\x8c2\x93\x90\xc1\x8ee\x95\x9f\xc2\x8c6\xc8\x95\xc0\x90\x00\x00"
key = "\xf1\xa7\xf0\x07\xed"
key_len = len(key)
flag = ""
for i, byte in enumerate(cipher):
cipher_byte = ord(cipher[i])
key_byte = ord(key[(key_len - 1) - (i % key_len)])
flag += chr(cipher_byte ^ key_byte)
print(flag)