We participated in the ISITDTU CTF 2024 competition, finishing in 15th place out of 315 teams with 21 challenges successfully solved.
"Everything is working fine, except for the crypto guys" - Quote by sondt
Review the src code
/register
endpoint blocks any registration attempts that include the string "admin" in the raw JSON data (json_data).Login with this account to obtain JWT Token
=> Blind SSTI => To view the command output we can create a webhook and send that to this endpoint
The payload running the ls
command returns an error because the output contains control characters -> we can see the name of the flag file is gnp6kw338gg6
Now simply run the command cat gnp6kw338gg6
The version used is DOMPurify 3.1.6., in this tweet https://x.com/kinugawamasato/status/1843687909431582830 the payload has been fixed in DOMPurify version 3.1.7.
=> This is most likely the solution payload
Tried and successfully triggered XSS
Upgraded payload a bit for easier use
And encode base64 this js script
Save note and submit link for bot -> get the flag
In docker image there are echo fake flag commands. This could be a hint of Confusion Attacks in apache
Read more here: https://blog.orange.tw/posts/2024-08-confusion-attacks-en/
RewriteRule Flags used
Investigate the provided Docker image. Realize that the root user has been acting suspiciously
Check folder /usr/share/vulnx/
Found that the file /usr/share/vulnx/shell/VulnX.php can be exploited to upload
However, the owner of these folders is root. While if using the web to upload, it will be in the ww-data user. However, the file /usr/share/vulnx/shell/uploads/shell.html is a "world writable" file.
=> We now can use RewriteRule ^/website-(.*).doc$ /$1.html
and VulnX.php to upload a file to overwrite the shell.html file. But how to use the uploaded file to read the flag at .htpasswd
.
Here the admin.php file
Contains an LFI vulnerability. However, to access admin.php, we need to go through Basic Auth due to the .htaccess file. We can bypass basic auth by using admin.php%3Fooo.php
like in orange's blog. And then use this LFI vuln to view the shell.html file with bad content to get the flag from .htpasswd
Now we got the chain: Upload shell.html -> view this file using admin.php
Something happened that I can't exploit on the server anymore. Only have the screenshot of the flag taken by my teammate @teebow1e
This website uses spring boot and Velocity
The MainController class controls all the logic of the website. Basically, the website will work like this:
-> Can be SSTI. There are many articles about velocity SSTI but all of them need to use #set
to create a variable then getClass and start using other java Classes from that.
But server has blocked the #
character -> need a variable available to trigger the error. And right in MainController there is a variable $data
This is my the payload serialize, hex encode, base64 encode
Again, the Flag file is named random -> need to run ls
command to know the file name. Use this payload
Convert array to readable ASCII characters:
So the name of the flag file is m62dyeu1gr3t
. Now read flag with payload
This is a Liferay challenge, quick look at the source code reveals the version of this application:
This Liferay version has a deserialization CVE:
PoC: https://gist.github.com/testanull/4bb77519acf2c8e919f8d9b015eda880
However, author might have considered this CVE an unintended solution, therefore, this can not be exploited remotely:
However, if I don't exploit JSONWS, I can still refer to another blog (which have PoC): https://sec.vnpt.vn/2019/02/liferay-bypass-story-part-2/
Using the endpoint #/../api/liferay
, I can still exploit by sending payload as raw payload (bytes) from ysoserial:
In this challenge, we need to identify the location where the video of burning buildings was filmed using a TikTok link …with precise coordinates.
https://www.tiktok.com/@juleko_o/video/7206026807483796741
In the video description of "Cháy toà nhà chọc trời ở Trung Quốc" along with the tag #china, it can be inferred that this location is somewhere in China.
Since this is HOT information, we can search for details based on news articles on Google.
Fortunately, the publication date is March 3, 2023 (matching the video upload date), which allows me to confidently confirm that the article refers to this particular building.
TPO - A large fire broke out at 11:11 p.m. on March 2 at a high-rise building under construction in the busy commercial district of
Tsim Sha Tsui
(Hong Kong, China).
According to local authorities, as of the morning of March 3, no casualties were recorded. Police said about 130 people living in the nearbyStar Mansion
,Far East Mansion
andChungking Mansions
have been evacuated.,
We can search forTsim Sha Tsui
, Star Mansion
, Far East Mansion
, and Chungking Mansions
on Google Maps to narrow down the area. Additionally, by looking at other news articles, it appears the building is located on Middle Road
.
At the angle of the video, I think the cameraman will be standing on top of a certain building behind the burning building.
We have just received information from our spy that Arlen is also using an alias "arlen.nnnnnnnn". From this information, can you track down his home address?
So we need to search for information on arlen.nnnnnnnn on social media because, in the challenge Two Steps Ahead
, it was mentioned that Arlen is addicted to social media. After searching, we found this guy's Instagram account.
We have a total of four posts. What information can we gather from these four hints?
In this post, I found a house. After using Google Images to identify it, I confirmed it as Centre culturel Calixa-Lavallée in Quebec, Canada, with the Google Maps link here. So, his house is about a 20-minute bike ride from this location.
In this post, we can infer that his house is located near a school.
In this post, he mentions that a pet shop is very close to his house, so we decided to use Google Images to identify the species of frog featured in the post and determine which shop sells it in Canada.
So, I used ChatGPT to search for stores in Quebec, Canada, that sell this type of frog, and Magazoo is the pet store that meets this criterion.
https://maps.app.goo.gl/8KsQaAvVqs1BAg1z6. Checking Time?!
This challenge basically hides íts actual flow in TLS Callbacks
functions (call before main
):
I just have to patch the IsDebuggerPresent()
and debug. Here is the solve script:
ISITDTU{Congrats_You_Solved_TLS_Callback_Re01_Have_Fun_:)}
This challenge will load the check_flag
from stack after some calculation so IDA
cannot analyze and decompile it. So I have to debug to this function and analyze statically. To get into this funtion, our input must be 36 characters with some conditions:
Here is a piece of the check_flag
function after I debug into it:
Those instructions before jnz
looks like equations so I write down into my note:
The last step is to use z3-solver
to find the flag. Script:
ISITDTU{a_g0lden_cat_1n_y0ur_area!!}
I recognized this as a challenge involving a NES (Nintendo) file, so I referred to several write-ups, particularly from Flare-On 2019 (challenge 6), which seemed quite similar but unfortunately doesn’t seem to provide much help for me in this case.
In a challenging moment, we discovered an extension for the Ghidra tool at https://www.retroreversing.com/nes-ghidra
we can see that the following conditions are simple equations, which we can solve using Z3 to find the values for IDAT_XXX
In the result of this Z3 solver, we can choose tuanlinhlinhtuan
because it has meaning.
Can see some char: ISITDTU{
, LAB_PPUDATA_8567
like as printf
We can observe a loop that iterates 43 times, processing each character of the data set with the DAT value we just found as tuanlinhlinhtuan
.
The pointer to the data was named DAT_0310
in Ghidra so I checked the memory viewer in Mesen
at address 0310
and found this:
Solve Script
The first thing we have to bypass is the first check pass after we chooses Login
option
We found the code at here, it use sha256 algorithm to encrypt the password
Here is the data that our input was compared to after being encrypted:
Out input has just 6 characters which are digits so my teammate wrote a script to bruteforce and then we got a password: 808017
brute_force.py:
In the next step, i try 5 options but only the option flag
looks explorable, and finally i found the piece of code
After patching and debugging, I found that the decrypt_flag
function is used to decrypt some data using AES CBC mode
with key
and iv
passed in argument. Here is the key
and iv
:
But somehow the buffer of the flag isn't true, and we decide to look for it in the whole program, and when i look in the buffer szNiceCatchFlag
, i found some bytes that may work, and they have 0xCAFE each 16 bytes, so i decide to combine 16 bytes into the full buffer 64 bytes
And we got this
It's a SSS cryptosystem but the input is shuffled
Along with that, we can query at most 256 numbers
To address the challenge, a straightforward approach is to send a sequence of 32 unique numbers with the following replication pattern: five numbers appear once, four numbers appear twice, three numbers appear three times, two numbers appear four times, two numbers appear five times, and all the remaning numbers appears once. With that, we need to bruteforce in total to get the correct order. At the end, we get 32 corrected equations and a bit of linear algebra to solve
This challenge is the same as the previous one but with a small modification: we can just querry at most 32 times. So obviously, we can't use the navie strategy here :( .
The solution here is to send 32th root of unitities to the server and caculate the sum of the returned numbers. The we can divide the result by 32 and we get a0. With a bit of luck, we can get flag :v
In this challenge, we are given a memorydump, which could be from Windows system, and we have to answer the following questions:
I assumed that the malicious process is currently running when this memorydump is captured, so I run windows.pslist.PsList
module of Voltality and immediately found dlIhost.exe
- which is a file that fakes the legitimate file dllhost.exe
. We also got the PPID 264
.
In order to get the path of the malware, I use windows.filescan.FileScan
.
To get the C&C IP that the malware is connecting to, I use windows.netscan.NetScan
.
Now, in order to find the malware family, I need to get the malware file itself to push into antivirus detection system and check the signature to determine the family. I use windows.dumpfiles.DumpFiles
to get the dump of that executable, then upload to VirusTotal.
The final flag is: ISITDTU{dlIhost.exe-C:\Users\m4shl3\AppData\Roaming\DLL\dlIhost.exe-264_45.77.240.51-harharminer}
This challenge requires us to address a series of questions to uncover the flag. I'll proceed through each question systematically.
Q1. What is the starting address of the LBA address? Format (0xXXXXX)
The LBA (Logical Block Addressing) starting address is determined by the offset from the beginning of the disk to the first sector of the partition.
Upon examining the disk structure, I located the partition starting at sector 128. This translates to a starting address of 0x10000
, which is our answer.
Q2. What is the tampered OEM ID? Format (0xXXXXXXXXXXXXXXXX)
In this task, our goal is to identify the OEM ID. The OEM ID is a unique string indicating the file system type, like NTFS, exFAT, etc.
It is typically located at offset 3 in the file system structure, where we can inspect it to find any alterations.
Here’s an example of how to locate the OEM ID. To do this, I used HxD to open the disk file and navigated to the byte at offset 3.
Answer: 0x4E54460020202020
Q3. After Fixing the disk, my friend downloaded a file from Google, what is the exact time when he clicked to download that file?
I suspect the files might have been renamed based on the download timestamp. Nonetheless, opening Autopsy and navigating to the "Web Downloads" feature should help us verify this.
From there, we identify the file as Blue_Team_Notes.pdf
, located within the MustRead
folder. Let’s navigate to it.
We'll take the Created Time and convert it to UTC, resulting in: 2024-10-22 21:51:13
.
Q4. How much time did that file take to for download (in seconds)??
For this question, I know that during a download, a temporary file like .crdownload
is created. We can parse both $LogFile
and $UsnJrnl
to trace this process. In this challenge, I opted to use $LogFile
.
Upon inspection, I noticed a discrepancy in the timestamps, which likely provides the answer.
(I ended up brute-forcing the timestamp since locating the exact column became tedious) –> Answer: 126
Q5. The first directory he moved this file to?
In this challenge, I used the remove method, knowing that the MustRead
folder is a carved folder, which indicates it was likely deleted. This left me with two options: best
and secret
. I tried both and found the answer to be: best
Q6. Last directory the suspicious move the file to?
As mentioned, the final directory where we located the PDF file is the MustRead
folder — making MustRead
the answer.
Q7. The time he of the deletion??
I used $UsnJrnl
because this file logs creation, deletion, and modification activities for files and directories, making it a valuable source of information.
Tool used: UsnJrnl2Csv
To parse the file, we utilized the search shortcut for efficient findings.
Answer: 2024-10-22 22:20:28
To be honest, this challenge is much simpler that I have expected. Let's have a look at the challenge description:
Some key information:
Since this is a special artifacts that are not mentioning much, I have to perform a Google search and this is what I found:
The 2nd post mentions that:
Searching that registry key returns:
I will decode using CyberChef:
Flag: ISITDTU{N0w_I_kn0w_about_search-ms}
The flow is pretty straight-forward:
mmap
a rwx address spaceseccomp
So I just use side-channel attack, to bruteforce the flag
The code (for loop) at line 18 (main) is checking whether each byte of input (shellcode) that we give is an odd byte or not. If its even, it will be replaced with 0x90 (nop)
Lets look at assembly code
We will set a breakpoint here to check the values of the registers.
I will focus on 4 registers:
'''
rax = 0
rdi = 0
rsi = 0xaabbc000 (address of shellcode)
rdx = 0xaabbc000 (address of shellcode)
'''
Great, that mean we can call read (syscall) to read to address 0xaabbc000 our "real" shellcode because there is no filter here :)). Just input 2 bytes
Note that when "call rdx", it pushes the return address onto the stack, that is 0x00005555555553ff
The flag is at 0x555555558040
The offset from "return address" and "flag" is 0x2c41
My goal is to do write(1, buf, 0x100) so my new shellcode will be
Script:
Result:
I will focus on 2 functions (sub_40140E() and sub_4015B6())
Generate a random number v1 >=0 and <100 (at rbp-0xC => Important!!) and print out (Lucky number)
Then in the main function, it will call the sub_4015B6 function.
We will take a look at the play_401480() function.
In play_401480(), it will re-seed (by calling clock()) and ask us to guess what v2 is (v2 is the number generated from rand()). We input the guessed number via the sub_4013BB() function (read and atoi)
If the guess is correct it will call the format_vuln_401534() function (format string!!!)
The problem here is, it is difficult to guess the seed (clock) in just one guess. If we guess wrong, the program will exit immediately.
Here we go…
At line 13 in sub_4015B6(), scanf limits the input to a maximum of one character. This means that only a single digit (0-9) will be read and converted into an unsigned integer.
So we cannot just input 0x44 (=choice) to call the function format_vuln_401534().
Try debugging and set a breakpoint right there
Run the program a few times and see that the value of choice before scanf is not the same.
Why?
"choice" is at rbp-0xC… Our lucky number is also at rbp-0xC.
Reason:
So, if lucky number is 0x44, and we input "-" when entering choice. The choice is still 0x44!! (scanf will not change the value of 0x44 if we enter "-"). The probability is 1/100 :)).
What do we do next
We can use format string bug (input "-") multiple times. First, leak GOT to get the address of libc and use libc.rip to find the libc version. Then, overwrite atoi@got to the address of the system. Finally, enter "1" to enter the play_401480() function. In the play_401480() function, it will call the sub_4013BB() function.
In read, we enter "/bin/sh\x00". Then atoi(buf) will be equivalent to system(buf) => system("/bin/sh")
Script
What can be better than an AARCH64 challenge, obviously the stripped one :ok_hand:
At first I struggled with the debugging step, and also the custom qemu that this challenge use.
The flow of this program:
main
vuln1()
sub_D7C()
a.k.a vuln1()
vuln2()
sub_C64()
a.k.a vuln2()
read
reading data to bufprintf(buf)
that allow format string bugsub_BD4()
a.k.a vuln3()
vuln1()
1st vuln2()
vuln2()
to the return address of vuln1()
(previouly is *main+24
), now we will have infinite loop to vuln2()
vuln2()
-> *vuln1+296
-> vuln2()
-> vuln2()
-> vuln2()
-> …2nd and 3rd vuln2()
vuln3()
we will never escape infinite loop because the return address in stack frame is lower than buf -> cannot overflowvuln3()
as this instruction STP X29, X30, [SP,#var_30]!
will extend the stack and cause the infinite loopvuln3()
where it call read()vuln3()
return as the stack is corruptvuln2()
to patch the address in stackvuln3()
It's a damn adorable challenge! I love itttttttttttttttttt <3