hxp CTF 2022: required

Writeup of challenge required of our recent CTF.

While solving BalsnCTF 2022’s 2linenodejs (writeup) we realized that prorotype pollution in NodeJS is more like a “feature” and less like a “bug”. What are “bugs” anyways? Right, just undiscovered features.

So let’s use this newly discovered feature and secureobfuscate some softwarestuff.

required.js

The entrypoint / main script.

In there, we first read the flag into f.

Then there are a set of instructions all following the same principle:

require('./AAA')(XX, YY, ZZ)

and in the end the flags gets printed with require('./37')(f).

There also exist many JavaScript files containing small arbitrary pieces of code.

Not all of the “required” files even exist. So some kind of magic is happening. To figure out what’s going on, it’s easiest to patch console.log into every existing file and starting the program with a random input. This gives us a list of real files to check and which files to discard.

Afterwards, we can reverse all used files and figure out how to reverse the performed calculation.

Calculations used:

  • flag[?] = ? ^ ? (? either of XX, YY, ZZ)
  • flag[?] = ? - ? (? either of XX, YY, ZZ)
  • flag[?] = ? + ? (? either of XX, YY, ZZ)
  • flag[i] = reverse_bits(i) (i either of XX, YY, ZZ)
  • flag[i] = i << 7 & 0xff | i >> 1 (i either of XX, YY, ZZ)
  • flag[i] = i << 1 & 0xff | i >> 7 (i either of XX, YY, ZZ)
  • flag[i] = ~i 0xff (i either of XX, YY, ZZ)
  • flag[i] = graycode(i) (i either of XX, YY, ZZ, Wikipedia)

All of these calculations are reversible. The flag length doesn’t change, so we know it from the “encrypted” hex-string 0xd19ee193b461fd8d1452e7659acb1f47dc3ed445c8eb4ff191b1abfa7969.

To solve, just apply the calculations in reverse order to get the flag:

hxp{Cann0t_f1nd_m0dule_'fl4g'}

The solver script with all calculations in reverse order can be found in solver.py.