Last month, NorthSec conference took place in an online format; and, with it, a very nice CTF was held. Apparently, the intention was for the participant groups to be formed by around 20 people. Nevertheless my 3 workmates and I decided to give it a shot and it didn’t went so bad, after all (around the 40th position :D).

I don’t have the full list of challenges, nor the solution for all of those that we solved. However, I do have a couple of solutions for challenges that I found interesting:

  • A mysterious scroll
  • Ancient Language
  • Dressed to impress

A mysterious scroll

The description for this challenge states:

Under the summer’s dew
Yours truly was taking a stroll
Following the wealthy few
In my hands, I received a scroll.

Accidently, it fell out of a purse.
Exquisite paper, a piece of secret.
Cryptic letters and numbers, I curse.
Decode it, and ample GPs you get.

Attached, we have a document: x.docx

When opening this document, we have something that resembles Python code, but not quite:

Word document with some text that resembles Python code, but composed of just the letter X in different fonts.

This looks a lot like some code, so let’s try to analyse what might it be doing:

  • First, it declares a variable x (using the Calibri font), which contains a charset
  • Then, it performs a bunch of mathematical operations, type conversions and array accesses to create a bunch of other variables (each with a different font)
  • Finally, it prints some characters (probably, the flag) using those variables to access the charset

One possible solution would be to transcribe it all by hand, depending on the font being used, and then try to run it (as Python code, most likely).

Since my patience is limited and doing those tasks by hand is not fun, I decided to try a different method.

My first try was to see what happens if I just copy the contents of the file into a plaintext document to start manipulating it with vim. Obviously, this fails spectacularly because the clipboard doesn’t store the style information, and it just copies a bunch of X’s without distinguishing their type.

So, the question is “what does have style and is easy to manipulate?”

Different people might think of different solutions, but to me the answer would be: HTML

Therefore, I started looking for a way to convert .docx documents into HTML. I don’t remember specifically which tool did I use; but I basically tried a couple of online services and after two or three attempts, I got some pretty decent results:

HTML showing the same contents as the docx, where each different font has a different CSS class assigned.

As you can see on the previous image, each element has a different CSS class assigned. This makes it really easy for us to manipulate and substitute every “X” with a different variable name, depending on its font. To do that, I added this JS script on the HTML file:

/**
 * Change all elements with the given class name to the desired new value
 */
function changeTo (className, newVal) {

  var text_1 = document.getElementsByClassName (className);

  for (const c of text_1) {

    if (c.innerText.trim ().toLowerCase () == "x") {
      c.innerText = newVal;
    }
  }
}

changeTo ("text_1", " A ");
changeTo ("text_2", " B ");
changeTo ("text_3", " C ");
changeTo ("text_4", " D ");
changeTo ("text_5", " E ");
changeTo ("text_6", " F ");
changeTo ("text_7", " G ");
changeTo ("text_8", " H ");
changeTo ("text_9", " I ");
changeTo ("text_10", " J ");
changeTo ("text_11", " K ");
changeTo ("text_12", " L ");
changeTo ("text_13", " M ");
changeTo ("text_14", " N ");
changeTo ("text_15", " O ");
changeTo ("text_16", " P ");
changeTo ("text_17", " Q ");
changeTo ("text_18", " R ");
changeTo ("text_19", " S ");

This yields something way more beautiful and manageable:

x = "abcdefghijklmnopqrstuvwxyz1234567891-"
A = int(x[30])
B = A + A +1
C = A - A
D = B - A + C
E = D * D + C
F = B - A - A + B - A - A + B - A - A + B - A - A + B - A - A + B - A - A + B - A - A + B - A - A
G = ( A -(( B - A - A )*( B - A - A + B - A - A )))* B
H = int( B + C + C + ( F * C )+ D /( B - A - A - A + B - A ))
I = H + B - A - A - A + B - A + B - A - A
J = int(str( E )[ C + B - A - A -1]+str( E )[ C ])
K = len(x) - B
L = int(( K + J * C )/( E - ( K + F )))
M = K + ( H - L )
N = int(( E -( K + F ))/( B - A - A ))
O = ( J - G ) + B + B + B - D + A
P = I + ( E -( K + F ))
Q = int(x [( G - D + A )])
R = int(str( B )[( G - J )]+str( L )[ P -( Q + Q + A )])
S = int(( F * ( I - H ) - N ))
print(x[ A ]+ x[ B ]+ x[ C ]+ x[ D ]+ x[ E ]+x[ F ]+x[ G ]+x[ H ]+x[ I ]+x[ J ]+x[ K ]+x[ L ]+x[ M ]+x[ N ]+x[ O ]+x[ P ]+x[ Q ]+x[ R ]+x[ S ])

If we run this code, we get the flag: flag-i8or81n2c7thlw

Ancient language

The description for this challenge states:

Our army has recently conquered the region of Cîteaux.
Its population is not very cooperative nor interested in the ways of our King, mainly because they use a foreign language.

We have found a tapestry in an abbey that could maybe be used to understand their language.

Can we trust you to help us in our efforts to assimilate the region?

Attached, there was an image which, unfortunately, I forgot to save. However, I do have the transcript I used to solve this challenge:

Sheet of paper showing a matrix with symbols, each composed only by straight lines

On that image there’s obviously a solution (the number besides each symbol), but let’s assume there isn’t any :D

The description of the challenge has a subtle clue: the region is called “Cîteaux”. Currently, it doesn’t mean anything to us, but it will come handy for later.

As with any challenge of the like, I started searching the web for keywords like “alien language”, “runes ctf”, etc. Another very useful resource, though, is to carefully look at the symbols ciphers list on dcode.fr/szmbols-ciphers (a great webpage which has a loot of tools for basic crypto challenges of most CTFs). Among those ciphers, we find one that might look like our mysterious alphabet: the “Cistercian Monk Numerals”.

Don’t you think that “Cîteaux” and “Cistercian” sound quite similar?

This is basically a way to encode numbers using straight lines in a quadrant. The quadrants are interpreted from right to left and top to bottom (top-right: units; top-left: tens; bottom-right: hundreds; bottom-left: thousands), where each pattern means a specific number. For more info, you can always head over to dcode.fr/cistercian-numbers, where it’s really well explained.

Anyways, the decoding is written on the image above, and this yields a bunch of numbers. If we interpret them as ASCII, we get the following text: The flag is: FLAG-MonksAre1337

Dressed to impress

The description for this challenge states:

A prestigious ball is coming soon,
And this humble bard is a guest.

My goal is to impress the room,
And make sure my outfit is the best.

The Spider’s Web is an amazing tailor,
And has an online order registry.

Can you get in there and do me a favor,
To make sure no one dresses like me?

There’s just one link to http://swta.ctf/

This is the most interesting (and harder) of the challenges I managed to solve. Unfortunately, I can’t connect to the VPN anymore and can’t take screenshots of the webpage.

This page is just a login prompt doing client-side validation of the username and password.

At first glance, it should be pretty easy. However, a quick look at the JS code that performs the validation crushes our hopes of an easy flag: it’s validated via WebAssembly, and this is the .wasm file

WebAssembly is a somewhat recent (2015) standard which allows developers to create binaries that can be executed by any web browser on any platform. This improves performance and allows them to, for example, develop complex video-games that can run smoothly on a browser (or, like this case, obfuscate the password validation).

Suddenly, our easy peasy web challenge morphed into a binary reversing one :O

The major challenge here is the availability of tools. Especially, a debugger. We can’t simply run gdb with the assembly file; because it’s not a regular ELF file (nor MachO/PE, for those non GNU/Linux-ers), since WebAssembly defines a custom architecture, kind of like Java with its custom JVM architecture.

First, we need some tools to try to decompile and run this file. Although I’m not an expert on this field, the best tool-set I found is the WebAssembly Binary Toolkit Another tool I used to run and try to decompile the .wasm is wasmtime.

There might be more tools out there, but those are the ones I know ¯\_(ツ)_/¯

Using wasm2wat, we can see that there are three interesting exports:

(export "fl1" (func 12))
(export "fl2" (func 15))
(export "verifyPassword" (func 66))

First flag

To get the first flag, we can locate the fl1 function and see that it’s quite easy to follow:

/* This is actually a pointer to d_DX9m7KF8Qbj1BiPwvn2osTH_UVYO,
which contains the obfuscated flags */
global g_a:int = 1048576;
/* ... */
data d_DX9m7KF8Qbj1BiPwvn2osTH_UVYO(offset: 1048576) =
  "\90\e2\85\a2\0cD\99#\86X9m\f17\8d{\d3K\c6!F\bb\148\da\8c\07\c2\19\ea\c4"
	// The data block is like 10-20 lines more ...
/* ... */
export function fl1():int {
  var a:int = g_a + -64;
  g_a = a;
  f_ua(a + 17);
  /* Not available on the .wasm file because
  it was defined on the JS code; but it basically
  allocates N Bytes (47, in this case) for an Array */
  a[3]:int = wbg_wbg_newwithlength_e0c461e90217842c(47);
  f_ac(a + 12, 0, a[17]:ubyte);
  f_ac(a + 12, 1, a[18]:ubyte);
  f_ac(a + 12, 2, a[19]:ubyte);
  f_ac(a + 12, 3, a[20]:ubyte);
  f_ac(a + 12, 4, a[21]:ubyte);
  /* ... */
  f_ac(a + 12, 43, a[60]:ubyte);
  f_ac(a + 12, 44, a[61]:ubyte);
  f_ac(a + 12, 45, a[62]:ubyte);
  f_ac(a + 12, 46, a[63]:ubyte);
  let t0 = a[3]:int;
  g_a = a - -64;
  return t0;
}
/* ... */
function f_ac(a:int_ptr, b:int, c:int) {
  wbg_wbg_setindex_977ce435f7b2f4ea(a[0], b, c & 255)
}

This is like the regular warm-up reversing challenge on most CTFs: just manually moving the flag on memory, Byte by Byte.

In this case, we can solve it in two different ways:

  • Extracting the values from d_DX9m7KF8Qbj1BiPwvn2osTH_UVYO and performing the logical operation & 255.
  • Being lazy and taking advantage of the wasm-interp --trace flag to see which values are actually moved in memory

Since I’m a lazy person, I opted for the second option. It’s worth noting that, for wasm-interp to work, we have to add the --dummy-import-func, which ignores the errors generated by the missing imports (like wbg_wbg_setindex_977ce435f7b2f4ea and wbg_wbg_newwithlength_e0c461e90217842c):

$ wasm-interp --dummy-import-func --trace spider_webs_tailor_assembly_password_validator_bg.wasm --run-all-exports
>>> running export "fl1":
#0. 86896: V:0  | alloca 2
#0. 86904: V:2  | global.get $0
#0. 86912: V:3  | i32.const 4294967232
#0. 86920: V:4  | i32.add 1048576, 4294967232
// ...
#1. 117956: V:3  | drop
#1. 117960: V:2  | return
#0. 86968: V:2  | local.get $2
#0. 86976: V:3  | i32.const 47
#0. 86984: V:4  | call_import $0
called host wbg.__wbg_newwithlength_e0c461e90217842c(i32:47) => i32:0
#0. 86992: V:4  | i32.store $0:1048512+$12, 0
#0. 87004: V:2  | local.get $2
#0. 87012: V:3  | i32.const 12
#0. 87020: V:4  | i32.add 1048512, 12
#0. 87024: V:3  | i32.const 0
#0. 87032: V:4  | local.get $4
#0. 87040: V:5  | i32.load8_u $0:1048512+$17
#0. 87052: V:5  | call $78
#1. 121948: V:5  | local.get $3
#1. 121956: V:6  | i32.load $0:1048524+$0
#1. 121968: V:6  | local.get $3
#1. 121976: V:7  | local.get $3
#1. 121984: V:8  | i32.const 255
#1. 121992: V:9  | i32.and 70, 255
#1. 121996: V:8  | call_import $1
called host wbg.__wbg_setindex_977ce435f7b2f4ea(i32:0, i32:0, i32:70) =>
#1. 122004: V:5  | drop_keep $3 $0
#1. 122016: V:2  | return
// ...
#1. 121992: V:9  | i32.and 76, 255
#1. 121996: V:8  | call_import $1
called host wbg.__wbg_setindex_977ce435f7b2f4ea(i32:0, i32:1, i32:76) =>
#1. 122004: V:5  | drop_keep $3 $0
#1. 122016: V:2  | return
// ...
#1. 121992: V:9  | i32.and 65, 255
#1. 121996: V:8  | call_import $1
called host wbg.__wbg_setindex_977ce435f7b2f4ea(i32:0, i32:2, i32:65) =>

Note how the trace already gives us the correct value for the third argument: 70, 76, 65… Which, translated using the ASCII table, corresponds to “F”, “L”, and “A”, respectively.

Hmmm…

Might be the beginning of the word “FLAG”?

Let’s figure it out, grepping by the __wbg_setindex_ function, extracting the values out of the third argument thanks to sed, converting them to hexadecimal thanks to print (using a little trick with xargs so the arguments can be piped), and finally converting it back to a string using xxd:

$ wasm-interp --dummy-import-func --trace spider_webs_tailor_assembly_password_validator_bg.wasm --run-all-exports \
	| grep -i __wbg_setindex_977ce435f7b2f4ea \
	| sed -e 's/called host wbg.__wbg_setindex_977ce435f7b2f4ea(i32:0, i32:[0-9]\+, i32://' \
	| sed -e 's/) =>//' \
	| xargs -I{} printf "%02x" "{}" \
	| xxd -r -ps
FLAG-{4fceb951ebf7c8cc10efb1a1dfc4ffb7eaabf624}+�`�����$�R1�E9P�p��
                                                                   (E92t

And there’s our flag :) FLAG-{4fceb951ebf7c8cc10efb1a1dfc4ffb7eaabf624}

Second flag

This second flag was kind of funny, because I just got frustrated and did it the unintended way, which means that I basically reused the same trick as with the first flag :D.

The function for this flag is slightly different as the first one:

export function fl2():int {
  var a:int = g_a - 48;
  g_a = a;
  f_u(a + 16);
  a[3]:int = wbg_wbg_newwithlength_e0c461e90217842c(32);
  f_ac(a + 12, 0, a[16]:ubyte);
  f_ac(a + 12, 1, a[17]:ubyte);
  f_ac(a + 12, 2, a[18]:ubyte);
  f_ac(a + 12, 3, a[19]:ubyte);
  /* ... */
  f_ac(a + 12, 28, a[44]:ubyte);
  f_ac(a + 12, 29, a[45]:ubyte);
  f_ac(a + 12, 30, a[46]:ubyte);
  f_ac(a + 12, 31, a[47]:ubyte);
  let t0 = a[3]:int;
  g_a = a + 48;
  return t0;
}

Although subtle, the most important differences are:

  • The starting pointer a. While on fl1 this pointer had the value g_a + -64;, now it’s g_a - 48;
  • The init function. On fl1, the first function called before starting the Bytes manipulation is f_ua(a + 17);, while now it’s f_u(a + 16);.

This second little change renders useless the first method we used to extract the flag, since f_u() performs some operations which I honestly got bored trying to understand. However, we can still use the trace method to seek the “.store” operations, to weed out the dummy moves from the real ones.

To do so, we can again use a similar Bash one-liner, but grepping for the .store operations, instead:

$ wasm-interp --dummy-import-func --trace spider_webs_tailor_assembly_password_validator_bg.wasm --run-all-exports \
	| grep  '.store' \
	| sed -e 's/^.\+, //' \
	| xargs -I{} printf "%02x" "{}" \
	| xxd -r -ps \
	| xxd00000000: 7d34 3236 6662 6161 6537 6266 6634 6366  }426fbaae7bff4cf
00000010: 6431 6131 6266 6530 3163 6338 6337 6662  d1a1bfe01cc8c7fb
00000020: 6531 3539 6265 6366 347b 2d47 414c 4600  e159becf4{-GALF.
00000030: f673 ec6a dd4e f7b6 9663 09e9 b6ce 365e  .s.j.N...c....6^
00000040: 9b29 7660 9bae cce3 408e 3b0c d6e5 ee2b  .)v`....@.;....+
00000050: 59a3 50cb dec9 e625 bbdb 29c6 146f 6c5b  Y.P....%..)..ol[
# There are more Bytes, but we do not care about them...

Alas, there’s our (reversed) flag! The only missing touch is to reverse it and we get: FLAG-{4fceb951ebf7c8cc10efb1a1dfc4ffb7eaabf624}

Third flag

Unfortunately, I run out of time to tackle the third flag. I doubt I’d even be able to solve it anyways…

There’s a good possibility I would have lost my mind trying to understand the incredibly convoluted code on f_d and f_g (called from verifyPassword) T_T

Conclusion

Although we didn’t manage to get even close to the top of the classification, I really had fun with the challenges we managed to solve and, as always, I learned a few things on the way.

I’m really looking forward to the next edition :D