 February 4, 2022

# Retrieving the Spanish Wordle Wordlist

My friend recently sent me a Spanish version of Wordle. English Wordle simply includes all current and future solutions in the source code sent to the browser, so naturally I wondered if that was also the case for Wordle ES. What I found was quite interesting! Here's a blogpost about my attempts to disassemble this curious game, written in real time.

# First Impressions §

The site appears to be built using Next.js, so the source code is a frightening nightmare. There's two obfuscated scripts (here and here) that appears to implement the logic for the game.

# Experimentation §

Some fiddling with devtools guides us to the code which checks if the answer is correct:

if (U(c), G(a(X).concat([n])), b(a(S).concat(a(r))), E(a(x).concat(a(i))), T(a(N).concat(a(s))), t == (0, o.pe)(m)) setTimeout((function () {
A("WIN"), et(a(tt).concat([c.length]))
}), 2e3);
else if (6 == c.length) setTimeout((function () {
A("LOSE"), et(a(tt).concat([-1]))
}), 2e3)

Basically this code does a few things:

var e = ut(t),
n = e.newMatrixRow,
r = e.newTried,
i = e.newPresent,
s = e.newCorrect,
c = a(V).concat([t]);

U(c)
G(a(X).concat([n]))
b(a(S).concat(a(r)))
E(a(x).concat(a(i)))
T(a(N).concat(a(s)))
t == (0, o.pe)(m)

The last part appears to be the most important. We need to figure out what t, o.pe, and m are; nothing a quick override can't fix. t is just the guessed word. m is a base64 value, U2FsdGVkX19YlMJMbsvp5tFryTPxtaJOcFpisHmdFFw= at the time of writing, which decodes to mostly unprintable nonsense but is prepended with Salted__. It's part of an array of similar base64 values on line 6374, maybe this will become significant later. o.pe is this function:

function d(t) {
var e = i().AES.decrypt(t, c).toString(i().enc.Utf8);
return e.startsWith(u) && (e = e.slice(u.length)), e
}

Some more probing shows that this function returns the correct answer in plaintext! Success!

# Final Touches §

Okay, now we just need to automate the process by fleshing out all the other details of this code; namely, how it gets the ciphertext, and how it picks which key to decrypt with.

Some quick googling suggests that the site is probably using crypto-js for its cryptographic primitives, meaning the array of values from earlier is probably a list of current and future ciphertexts. The key is just atob("bGxhbm9z"), or "llanos". And finally, decrypting the list of ciphertexts reveals the full list of solutions.

# Closing Remarks §

The developers made a valiant effort to defend their game, but it was ultimately no match for my procrastination.