REPLY CTF 2020

Nir Alfasi
2 min readOct 10, 2020

--

Wells Read — Coding 100

In the challenge, we download a zip file that contains two text files:

  • A dictionary (a file which has one word in each line)
  • The book: “The Time Machine”

Now we can see that some words got “scrambled” in the book, looking at a few examples we can see that they all have the same characteristic:

Only one character was replaced!

This is great news, it means that we’re looking for words that are not in the dictionary file with a Levenshtein distance of 1 from a word that is in the dictionary.

Coding it is not difficult (written in Nodejs):

const fs = require('fs');
// read the dictionary
let lines = fs.readFileSync('./words.txt', 'utf-8').split(/\r?\n/);
// load it into an object which will be used as a set, save the words in lower-case
const set = {};
for(let line of lines) {
set[line.toLowerCase()] = true;
}
// accumulate the result here
let r = '';

// read "The Time Machine" into memory
lines = fs.readFileSync('./TTT.txt', 'utf-8').split(/\r?\n/);

/**
* This function receives a word that is not in the
* dictionary and looks for a word in the dictionary
* that is in a 1-letter distance
*
* @param w - a word (lowercase) to search
* @returns {*}
*/
function distance1(w) {
o = w; // save the original word the way it was - because the result IS case-sensitive
w = w.toLowerCase();
const ab = 'abcdefghijklmnopqrstuvwxyz';
for(let i = 0; i < w.length; i++) {
for(let j = 0; j < ab.length; j++) {
const c = ab[j];
const n = w.substring(0,i) + c + w.substring(i+1);
if (set[n]) {
return [n, o[i]];
}
}
}
return false;
}

/* Main Logic */

// go over the book
for( let line of lines) {
// get rid of unwanted characters, split into an array of words
const words = line.replace(/[ -.,?"]/g,'§sep§').split('§sep§').map(x => x);
for (let word of words) {
if (!word) continue;
// remove the first character in the token
// if it's not in the alphabet
if (!word[0].match(/[a-zA-Z]/)) {
word = word.substring(1);
}
if (!word) continue;
// remove the last character in the token
// if it's not in the alphabet
if (!word[word.length-1].match(/[a-zA-Z]/)) {
word = word.substring(0, word.length-1);
}
// look for the word in the dictionary
if (set[word.toLowerCase()]) {
continue;
}
// since it's not in the dictionary - we'll look for a word in the dict with
// a distance of 1
const res = distance1(word);
if (res) {
r += res[1];
}
}
}
console.log('res:', r);
// OUTPUT
// res: eGEteGOZyIy:otFMeFFFUAqDYXVu}gHjNp54E6qqM{BFD:5LHXJoYpwvjthF_BpSp7Kd}PC}q5vFFFxfSMFC97yllwRF74lXlF{bF5SYMLY5{VVt_FZ4{q{RwdP:pNfv4ApnK43XSl2:FFF23q1hHCwGKQ{sFjppwhjiyBXy85tp0eYKM3DZvPUwFklB1IFwFMFM5JDm7cyENCR2r{iDDNqits9rmMs1skCxHyDaBb68gkPK{V0x1HBPieEU0H{gUV2_t9SHGJsIbxIvE{HIoA9Fw0p0ZmvpPmZu5b}I5UEeH9dSeqp:Vdw_otv1Ouu_LK8{6G_V2o8ga}xj0T29VlC5aO0wmU8Ph{5WxJ0M4FCs5JsQq:jk38vzR3RFroDXvfBq{EYK{5dIEARz{6IV34iVIF98aqb4aa{Av:9mYAAE8gv}uOii2GQYYoxbWS101G3dyIV:A63c}fO{BMH8WFU_b5j{je61W{HkGaoqmgkeyl{5Lf{7lRqNPvl9aXp4vpiw6x38}kOGjcoOMFtQLM847e}uGU_}s:lqrs:M{0K_baDC:Rku197CAVdq_r_TDOKDNqLNJ8b_{PLQyD:FM:kb{}zcK6vajYFVKlaebq2zc{xYp_}wqLlVV6odWO7I}252f1J{H}BOQ7h5{s4OjoZAAhjY{FLG:1_kn0w_3v3ryth1ng_4b0ut_t1m3_tr4v3ls}0:w8Wj_SJeGS_og{6k{6NshxTn:INYsHC0FHNmiz{:jpXQB28ZksKq{rsEOFzkF}TaYFX_6RZ:MA83{TjL0W4zOn{Onxaldtlw6AquWq89K_1ofz1QzQcLje8WvVW{Xlz3xv6:BFU1TCwO9qVof2kg{s6v4jSXLJSHauo8y:06CMlaWS:h:UBx4BCKVLj}gRrYoPSAAH4tz6Y1cSAvL:Dgatm9IyGc2u1j2lJEJoT{uMvU2X5xomiEN9CklJlB4Wo_zWRajzXlsalz}BWD5CRFz2Dw6oVPikFrfSWQazV2DzeHWWvGJ{9{aQJW}Fd1WW01ugfn:EGp8{W_Tbrvacq{GHT:MheEVmxXYQrxrDLVLmzut{aLIva4780ZBJ9YQkTMr}5T}I2WlaeJAxVyWqHdeQelgTUfEU0MUwOflZT{8:jD6h{MWIb_8Wm:6qWSG{YWrkLsi3FjwgPbhBWTs_567vW:QWaYF:TpThGe5fTfYSXWu2eKUHdEeR}1xWE0MslcWpfhap23lzTkG{W6_5q7UV:w2__NWBpdILP4nYDhq9X3akWDC6LyLsHS0iDD9Lw5ZJF3Ic}K41jWn3b6WV5zykM13W4DzWlMMWyIpWb:Y_xyfvlW30C0NMWqDWFmt1{OO:vqoTvtaDtw5rjKI_l_HW5G0qCpF2qbwyiKNCMT3KSmI:bat9WW:jC7FSN3}ef6TvCLWJ0hKWC5X6oBMjWBFMlW0aIqWghWx5_W8yZvOi8zWZi0ZT0IK7BWVcl9q3dfQbXWQs{YlM6vyH5pTkWGJ}}111qLxZLyx2WWdpw{4jm495WrBJQQ5RW2{_hAGW{ZI2tvp4:x4XuKeY2tb00{bKjaigNBl_2_tniQA6DA9KUb_{w5Y3uSMihDQSgffi_UmqtS4h6:_AX{DrG6Vo4DTfNjkIUw516}QBOcuXqlb8kpxw1WsMsrc{GA}w}G}T7nmOxxa7QT5OGd6G_u_GGXjklQafi_kaAiZ_s:{jGHaKjpk{VU5Kj_klqTzWKMHPag8vgj05lNNB:yj{kPOkBhUxZRu:G8gRzfacsqVgLI101mg

Now we can search the output for the string FLG: and we’ll find the flag:

{FLG:1_kn0w_3v3ryth1ng_4b0ut_t1m3_tr4v3ls}

I was able to find another couple of good write-ups about other challenges in Replay 2020:

Enjoy!

--

--

Nir Alfasi

“Java is to JavaScript what Car is to Carpet.” - Chris Heilmann