All Articles

DUCTF 2023 Writeup

This page has been machine-translated from the original page.

We participated in DUCTF 2023, which started on September 1, as 0nePadding, and placed 124th out of 1,424 teams.

image-20230903214642827

As usual, I solved a few Rev challenges, so I am writing them up.

Table of Contents

the bridgekeepers 3rd question(Rev)

What is your name? What is your quest? What is your favourite colour?

When I opened the challenge site in a browser and clicked on the screen, a prompt appeared as shown below.

image-20230903214909357

Looking at the HTML source, I found the following script embedded in it.

<script id="challenge" src="text/javascript">
  function cross() {
    prompt("What is your name?");
    prompt("What is your quest?");
    answer = prompt("What is your favourite colour?");
    if (answer == "blue") {
      document.getElementById('word').innerText = "flag is DUCTF{" + answer + "}";
      cross = escape;
    }
    else {
      document.getElementById('word').innerText = "you have been cast into the gorge";
      cross = unescape;
    }
  }
</script>

It looks like entering blue into prompt("What is your favourite colour?"); should give the correct flag, but simply entering blue is not enough to obtain it.

This seems to be because the prompt handling is overridden by another JavaScript as follows.

prompt = function (fun, x) {
  let answer = fun(x);
  if (!/^[a-z]{13}$/.exec(answer)) return "";
  let a = [], b = [], c = [], d = [], e = [], f = [], g = [], h = [], i = [], j = [], k = [], l = [], m = [];
  let n = "blue";
  a.push(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, a, a, a, a, a, a, a, a);
  b.push(b, b, b, b, c, b, a, a, b, b, a, b, a, b, a, a, b, a, b, a, a, b, a, b, a, b);
  c.push(a, d, b, c, a, a, a, c, b, b, b, a, b, c, a, b, b, a, c, c, b, a, b, a, c, c);
  d.push(c, d, c, c, e, d, d, c, c, c, c, b, c, c, d, c, b, d, a, d, c, c, c, a, d, c);
  e.push(a, e, f, c, d, e, a, e, c, d, c, c, c, d, a, e, b, b, a, d, c, e, b, b, a, a);
  f.push(f, d, g, e, d, e, d, c, b, f, f, f, a, f, e, f, f, d, a, b, b, b, f, f, a, f);
  g.push(h, a, c, c, g, c, b, a, g, e, e, c, g, e, g, g, b, d, b, b, c, c, d, e, b, f);
  h.push(c, d, a, e, c, b, f, c, a, e, a, b, a, g, e, i, g, e, g, h, d, b, a, e, c, b);
  i.push(h, a, d, b, d, c, d, b, f, a, b, b, i, d, g, a, a, a, h, i, j, c, e, f, d, d);
  j.push(b, f, c, f, i, c, b, b, c, j, i, e, e, j, g, j, c, k, c, i, h, g, g, g, a, d);
  k.push(i, k, c, h, h, j, c, e, a, f, f, h, e, g, c, l, c, a, e, f, d, c, f, f, a, h);
  l.push(j, k, j, a, a, i, i, c, d, c, a, m, a, g, f, j, j, k, d, g, l, f, i, b, f, l);
  m.push(c, c, e, g, n, a, g, k, m, a, h, h, l, d, d, g, b, h, d, h, e, l, k, h, k, f);

  walk = a;
  for (let c of answer) {
    walk = walk[c.charCodeAt() - 97];
  }
  if (walk != "blue") return "";
  return {toString: () => _ = window._ ? answer : "blue"};
}.bind(null, prompt);

eval(document.getElementById('challenge').innerText);

After reading the code, I found that if I could make walk end up storing n, then answer would become blue, allowing me to get the flag.

As shown below, by tracing the indices from the head of walk, starting with a and continuing in order until n, you can see that walk eventually stores n.

walk = a;
for (let c of answer) {
    walk = walk[c.charCodeAt() - 97];
}

So I wrote the following solver, which let me determine that the required string for obtaining the flag was rebeccapurple.

a = ["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","b","a","a","a","a","a","a","a","a"]
b = ["b","b","b","b","c","b","a","a","b","b","a","b","a","b","a","a","b","a","b","a","a","b","a","b","a","b"]
c = ["a","d","b","c","a","a","a","c","b","b","b","a","b","c","a","b","b","a","c","c","b","a","b","a","c","c"]
d = ["c","d","c","c","e","d","d","c","c","c","c","b","c","c","d","c","b","d","a","d","c","c","c","a","d","c"]
e = ["a","e","f","c","d","e","a","e","c","d","c","c","c","d","a","e","b","b","a","d","c","e","b","b","a","a"]
f = ["f","d","g","e","d","e","d","c","b","f","f","f","a","f","e","f","f","d","a","b","b","b","f","f","a","f"]
g = ["h","a","c","c","g","c","b","a","g","e","e","c","g","e","g","g","b","d","b","b","c","c","d","e","b","f"]
h = ["c","d","a","e","c","b","f","c","a","e","a","b","a","g","e","i","g","e","g","h","d","b","a","e","c","b"]
i = ["h","a","d","b","d","c","d","b","f","a","b","b","i","d","g","a","a","a","h","i","j","c","e","f","d","d"]
j = ["b","f","c","f","i","c","b","b","c","j","i","e","e","j","g","j","c","k","c","i","h","g","g","g","a","d"]
k = ["i","k","c","h","h","j","c","e","a","f","f","h","e","g","c","l","c","a","e","f","d","c","f","f","a","h"]
l = ["j","k","j","a","a","i","i","c","d","c","a","m","a","g","f","j","j","k","d","g","l","f","i","b","f","l"]
m = ["c","c","e","g","n","a","g","k","m","a","h","h","l","d","d","g","b","h","d","h","e","l","k","h","k","f"]

print(chr(a.index("b") + 97),end="")
print(chr(b.index("c") + 97),end="")
print(chr(c.index("d") + 97),end="")
print(chr(d.index("e") + 97),end="")
print(chr(e.index("f") + 97),end="")
print(chr(f.index("g") + 97),end="")
print(chr(g.index("h") + 97),end="")
print(chr(h.index("i") + 97),end="")
print(chr(i.index("j") + 97),end="")
print(chr(j.index("k") + 97),end="")
print(chr(k.index("l") + 97),end="")
print(chr(l.index("m") + 97),end="")
print(chr(m.index("n") + 97),end="")

# rebeccapurple
# DUCTF{rebeccapurple}

All Father’s Wisdom(Rev)

We found this binary in the backroom, its been marked as “The All Fathers Wisdom” - See hex for further details. Not sure if its just old and hex should be text, or they mean the literal hex.

Anyway can you get this ‘wisdom’ out of the binary for us?

I tried to run the ELF file provided as the challenge binary, but the program exited without displaying anything.

Analyzing it in Ghidra showed that an exit() function was placed before the code that prints the flag, so I patched it to NOP and obtained the flag.

44 55 43 54 46 7b 4f 64 31 6e 5f 31 53 2d 4e 30 74 5f 43 7d
# DUCTF{Od1n_1S-N0t_C}

pyny(Rev)

I’ve never seen a Python program like this before.

The following Python code was given as the challenge binary, and it turned out to be a Python script encoded in punycode.

#coding: punycode
def _(): pass
('Correct!' if ('Enter the flag: ') == 'DUCTF{%s}' % _.____ else 'Wrong!')-gdd7dd23l3by980a4baunja1d4ukc3a3e39172b4sagce87ciajq2bi5atq4b9b3a3cy0gqa9019gtar0ck

Punycode is an encoding method used for things like Japanese domain names. It places the ASCII string at the beginning and then appends, after the -, a string in which Unicode characters are converted into ASCII codes indicating their positions and character types.

Reference: Punycode - Wikipedia

In other words, in the Python script above, the Unicode characters defined by -gdd7dd23l3by980a4baunja1d4ukc3a3e39172b4sagce87ciajq2bi5atq4b9b3a3cy0gqa9019gtar0ck are inserted into the appropriate positions in the earlier Python code, completing an executable script.

It seemed like simply debugging the Python script would quickly reveal the flag, but perhaps because the offsets shift when punycode is decoded, debugging at startup caused errors.

So I installed the Python debugging plugin for gdb with the following command and decided to debug it using gdb.

sudo apt install python3-dbg

Reference: DebuggingWithGdb - Python Wiki

Reference: Features/EasierPythonDebugging - Fedora Project Wiki

Extracting strings from the heap after running the script showed that the value of ᵖʸᵗʰºⁿ_ʷªʳᵐᵘᵖ.__ⁿªᵐᵉ__ would become the flag.

So, after advancing execution to an appropriate point, I identified the address of the python_warmup function with the info threads command and printed its information with the following command.

p *(PyCodeObject*)(((PyFunctionObject*)0x7ffff7b5fd90)->func_code)

As a result, I found that the correct flag was DUCTF{python_warmup}.

image-20230903150158472

SPACEGAME(Rev)

ALL YOUR BASE ARE BELONG TO US. YOU ARE ON THE WAY TO DESTRUCTION.

When I launched the EXE given as the challenge binary, the following invader game started.

img

After extracting the script from love.dll, which had packed Lua scripts, I obtained code like the following.

image-20230901235503022

I looked through all of the scripts inside love.dll, but could not find any information that seemed likely to lead directly to the flag. However, I noticed that conf.lua, which I found referenced inside love.dll, was not actually present inside love.dll.

image-20230901235558051

So I analyzed the challenge EXE binary and found that what was probably an encrypted ZIP containing conf.lua seemed to be embedded as shown below.

image-20230901235424188

Since this is unpacked in memory at runtime, I started the game, paused it, and searched the in-memory strings with Process Hacker. As a result, I was able to obtain the correct flag string as shown below.

image-20230901235635717

Looking at the official writeup, it seems that you can also obtain the flag by tampering with the Lua script to remove the lose condition, allowing you to play the game in an invincible state.

Reference: Challenges2023Public/rev/spacegame/solve/solve.md Main · DownUnderCTF/Challenges2023Public

Summary

I need more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice, more practice.