picoCTF2023 Writeup
Overview
The challenges I accomplished during competition:
- Web Exploitation (4/7)
- findme
- MatchTheRegex
- SOAP
- More SQLi
- Java Code Analysis!?
- cancri-sp
- msfroggenerator2
- Cryptography (3/7)
- HideToSee
- ReadMyCert
- rotation
- SRA
- PowerAnalysis: Warmup
- PowerAnalysis: Part 1
- PowerAnalysis: Part 2
- Reverse Engineering (9/9)
- Ready Gladiator 0
- Ready Gladiator 1
- Ready Gladiator 2
- Reverse
- Safe Opener 2
- timer
- Virtual Machine 0
- Virtual Machine 1
- No way out
- Forensics (4/7)
- hideme
- PcapPoisoning
- who is it
- FindAndOpen
- MSB
- Invisible WORDs
- UnforgottenBits
- General Skills (8/8)
- chrono
- money-ware
- Permissions
- repetitions
- Rules 2023
- useless
- Special
- Specialer
- Binary Exploitation (6/7)
- two-sum
- hijacking
- tic-tac
- VNE
- babygame01
- babygame02
- Horsetrack
Finally I get 5200 scores and rank 301/6924 as an invidual team OneAngryMan
. Below are my writeups.
Writeups
Web Exploitation
findme
Input username as test
and password as test!
then click test button, we can find the title of current tab changing to “flag” quickly and the “flag” title disappear immediately. Since the hint is about redirection
, I try to extract infomation from redirections through Chrome DevTools. But the weird thing is that if I open DevTools to intercept the network packets of login process, the redirections are completely jammed. Maybe this is a question of my local network, and I must use other tools to get the information in the redirections of url.
I recommend 2 ways here: Burpsuite and Python requests module. Then get two base64-like string in id field of redirection requests:
Python requests module also works:
1 | import requests |
Finally we combine two string and use base64 encode to get flag:
1 | echo cGljb0NURntwcm94aWVzX2FsbF90aGVfd2F5XzgxZDRkODMxfQ== | base64 -d |
MatchTheRegex
We can find below javascript code in front-end of website:
1 | function send_request() { |
^p.....F!?
is a regex string which means a string start with character p
then following 5 any character plus a F
character then end with a !
or omit it. So input picoCTF
and then we get flag.
SOAP
The hint is XML external entity Injection and I directly google it then find a usable payload for this challenge:
More SQLi
First stage we should bypass the authentication of login process which is a typical SQL injection so we can fuzz the password from usual SQLi examples, here I use admin' or '1'='1'--
as password.
After login, we need to extract the tables’ infomation from database. Since this is a SQLite database, I google sth like “SQL injection in SQLite” and get a really useful reference: https://www.exploit-db.com/docs/english/41397-injecting-sqlite-database-based-applications.pdf .Then I follow this reference step by step and get flag through below query sentences:
1 | 1' union select 1,2,3 --+ |
Cryptography
HideToSee
Atbash cipher is a really simple algorithm but this challenge makes a lot of people confused at the beginning. I get stuck in this challenge for about a week and finally solve it through almost every picture steganography I can find on the web.
Use steghide
to extract information from picture we get from:
1 | steghide extract -sf atbash.jpg |
Then we use online atbash cipher decrypt tool to decrypt extracted infomation.
ReadMyCert
Google online csr decoder and get flag.
rotation
A typical Caesar cipher and we can directly solve it online.
Reverse Engineering
Ready Gladiator 0/1/2
The Ready Gladiator series are more OSINT than reverse engineering for me, because I find all the solutions on the web. Anyway, Core_War is definitely an interesting game from both programming’s and mathematics’ perspective. These 3 challenge we confront a same recode program – imp, which copies itself recursively aiming to turn another program to imp too and go to a tie.
Challenge 0 requires always loses, no ties. The easiest way is to do nothing and program will end itself immediately, which is:
1 | ;assert 1 |
Challenge 1 requires wins which means we need kill the running imp sometimes. I am tired to understand redcode programming method so I directly google some core_war warriors:
1 | ;assert 1 |
Challenge 2 requires wins for 100/100 times. Cause imp is such a famous strategy in Core_War so there must be some existing anti-imp redcode programs. I try to use both google and ChatGPT, and ChatGPT gives me a program but doesn’t work:
1 | ; Anti-IMP program |
Finally I find a useful redcode through Google:
1 | ;assert 1 |
Reverse
Use Linux strings
then get flag.
1 | strings ret | grep 'pico' |
Safe Opener 2
Use an online java class decomplier website and get java code below:
1 | import java.io.IOException; |
Or directly strings
it.
timer
Use online jadx decompiler such as http://www.javadecompilers.com/apk or https://www.unboxapk.com/apk-decompiler to get decompiled data.
Then grep
our flag in the directory including decompiled files:
1 | grep -Ri "picoCTF" ./timer_source_from_JADX/ |
Virtual Machine 0/1
.dae file is a kind of 3D model file and can be opened through many softwares such as SelfCAD, Autodesk Maya (cross-platform), or Blender. I choose to use blender on my local machine.
In the challenge 0, we can see blue and red axles and hint indicates that the rotation of the red axle is input, the rotation of the blue axle is output. But this hint actually confused a lot of people during the competition because of the puzzled input file.
I analyse the 3D model and find this is actually a gear transmission model after deleting some facial blocks:
We can easily compute that the speed of the blue gear is 5 times that of the red gear, so the input is actually the times of total rotating turns of red gear. But where is the flag? What we get is a long integer! If you are familiar with cryptography challenge, you can associate long integer with long_to_bytes
in python crypto module:
1 | from Crypto.Util.number import long_to_bytes |
Then we get flag. This is also why this challenge gets the most dislike :)
In the challenge 1, we get another more sophisticated gear transmission model:
I ask my highschool classmate (major in Mechanical) to explain how to compute the ratio between three adjacent gears and the answer is average. I use a pen and paper to compute the ratio by hand. The final ratio is 9359
.
No way out
A unity reverse challenge. At first we try to find the flag by playing this game directly :) then we are blocked by an invisible wall at board so we can’t go to the place where flag is.
I google sth about “unity reverse engineering” and find some useful tools especially dnSpy. After looking up some CTF reverse challenge writeups about unity games (https://tripoloski1337.github.io/ctf/2019/09/09/reverse-engineering-unity-game.html and https://github.com/imadr/Unity-game-hacking) , I find the file named Assembly-CSharp.dll
located at pico_Data/Managed
contains the compiled Csharp files, and also the main program logic:
I change the moveDirection.y
of jump button to a constant value, then save it and compile again. When come back to game, I jump like on the moon! The invisible wall seems restricted by a height so I jump over it and go to the flag:
Forensics
hideme
Use binwalk -e
to extract hidden files in png file, then we get a png file under secret
directory:
OCR tool performs badly in recognize text of this picture so I read it by my eyes.
PcapPoisoning
strings ${filename} | grep pico
who is it
Extract the ip in .eml file, then use whois 173.249.33.206 | grep person
to find person name in the dumped data.
MSB
I search sth about MSB and find a useful tool in github:
1 | https://github.com/Pulho/sigBits |
Then grep “pico” in the output.txt
.
General Skills
chrono
cd /challenge
directly and cat
, probably a mistake of challenge makers.
money-ware
I directly google 1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX
and find a news about it:
The answer is Petya.
Permissions
Use vim to read file in /challenge (vim /challenge
) and get flag.
repetitions
cat ./enc_flag | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d
.
Rules 2023
Open DevTools then Ctrl+F
to search “pico”.
useless
man useless
.
Special
I find the solution to bypass bash restrictions at https://book.hacktricks.xyz/linux-hardening/bypass-bash-restrictions. The space character is forbidden in this challenge, so we overwrite IFS(Internal Field Separator) variable and read flag:
1 | IFS=];b=cat]/challenge/metadata.json;$b |
Specialer
In this challenge we can only use bash built-in command. https://book.hacktricks.xyz/linux-hardening/bypass-bash-restrictions considerates ways to read file in this circumstance but the demo command can’t read file content without newline character:
1 | while read -r line; do echo $line; done < /etc/passwd |
So I google about “how to use linux read
to read a text file that ends without a newline”, I get this answer (https://stackoverflow.com/questions/9408103/shell-script-how-to-read-a-text-file-that-does-not-end-with-a-newline-on-window) and it works:
1 |
|
Binary Exploitation
two-sum
The source C code doesn’t prevent us from integer overflow:
1 | else if (addIntOvf(sum, num1, num2) == -1) |
So we let one of num1 and num2 be the maxinum of integer and the other be random positive integer. Since the int
type in C language is 4 bytes and use Two’s complement to express integer number, the maxinum of int is 2147483647
which also can be easily searched with google. So we let num1 be 2147483647
and num2 be a arbitary postive number then we get flag.
hijacking
I am stuck in this challenge for about a week and pay my almost whole attention to that python file due to the hints. The tag of this challenge includes privilege-escalation
but I tried some uncorrect methods and missed the right way. During this time I am also attracted by the word “Social Engineering”, and I try to many ways to make that python file usable for privilege-escalation such as write a new ping
shell-script in PATH dir, not surprisingly, all failed.
I check the basic privilege-escalation techs (https://book.hacktricks.xyz/linux-hardening/privilege-escalation) again and find a step I missed:
1 | sudo -l |
Then I find vi
is allowed in challenge’s environment. So easily, I use sudo vi /root
to get root privilege and read flag through vi. But the keyword in flag is about python library so maybe this an unintended solution.
tic-tac
Simply search the keyword toctou at google and youtube then I get really useful resource for this challenge:
https://www.youtube.com/watch?v=5g137gsB9Wk . This challenge is really similar to that in video, cause the most important reason of the vulnerability is that race condition involving the checking of the state of a part of a system (such as a security credential) and the use of the results of that check , according to TOCTOU wikipedia .
In this challenge, the reason why TOCTOU exists is that we cannot check the uid of file and open file at the same time, aka this program is not atomic . The solution to this vulnerability is simply adding a lock before check and unlock after open but that’s another topic about Mutual exclusion.Another important reason is that we use file name to read contents:
1 | std::string filename = argv[1]; |
Since process can be interrupted by any other process, we can establish a soft link pointing to flag file and a empty file, for convenience I named them flag_link
and tic-tac
, implemented through:
1 | ln -s ${flag_file} flag_link |
We use ls -l
to check outputs and will find the owner of both flag_link
and tic-tac
is the same(not root), and flag_link
is a symbol link pointing to flag file.
The second thing we need to know is when we actually open the file? I know nothing about C++, to solve this problem I extract the code below:
1 |
|
Then I set breakpoint at open@plt
using gdb at the second time running (because of lazy binding
we can’t get libc address at the beginning), thus we can lookup the libc function call backtrace to attain the position where file opens at. According to the little program I made above, I finally found the file is open at std::ifstream file(filename);
.
During txtreader
running time, we can cycle exchanging file name of flag_link
and tic-tac
to attain the running frame as below:
1 | std::string filename = argv[1]; |
We do two exchanges before open file and before the uid check, aiming to open the flag_file and pass the check. The keypoint is that we can’t control the timespot of file name exchange, but we can attain our purpose by infinite loop of exchanging process, and the running frame above will happen probabilistically.
But the thing is, if the exchanging operation is not atomic, running frame above will be more difficult to gain. Thankfully, we don’t need to pay attention with this issue because the exchanging operation has atomic implementation:
1 |
|
All the advance preparation done, we can concentrate on final exploiting. First, we running a process to exchange file names forever at backend. Second, we use txtreader
to read flag_link
or tic-tac
until we read flag. I use disown
to make a command line running in the backend because I’m not sure tmux or sth else is allowed in this challenge. So the whole exploitation is:
1 | gcc rename.c -o rename |
A really interesting tactou challenge.
VNE
This program’s logic is to ls
the directory specified by environment variable SECRET_DIR
with root privilege. Then use ghidra or IDAPro to figure out how this program do ls
:
1 | pbVar1 = std::operator<<((basic_ostream *)std::cout,"Listing the content of "); |
The decompiled C++ program is ugly and I don’t want to format it either. But we can see that the program call the system
function during running, so we can set breakpoint at the position before call system to lookup what command is executed in this program using gdb. Then we will find the command is ls $SECRET_DIR
.
Things become easy after figuring out what the program is doing. Using a little knowledge of shell script, we simply wirte command as below:
1 | export SECRET_DIR=';bash' |
Then we get root
privilege and read flag under /root
directory.
babygame01/02
babygame01 requires us to change a local virable on the stack frame. For convenience I use ghidra to decompile the binary file and make virable names easy to understand:
1 | undefined4 main(void) |
Our player only occupied 1 byte on the map, but can change value on arbitary memory by moving player. Since we need to change 1 byte at position of target_char
, the keypoint is to compute the offset of target_char
and player position. I use gdb to check how many steps we need to move, and the final payload is:
1 | aaaawwwwaaaap |
Decompiled babygame02’s main function is as below:
1 | undefined4 main(void) |
Function win
still exists but we don’t have regular way to make program execute win
function. Thus, also a typical method in stack overflow challenge, we need to modify the return address during function call. The frustrating thing is, if we check the program logic of move_player
function carefully, we will find that we can only write 1 arbitary byte on the memory, and leave 0x2E
at old position after moving. So, we can only write 1 byte at the return address, but which byte should we rewrite?
With ghidra/IDA Pro or gdb we will get function win
‘s address – 0x804975d
. And during debug, if we set breakpoint at move_player
, we will get function call backtrace as below:
1 | ► f 0 0x8049479 move_player+5 |
The return address 0x8049479
has only 1 byte difference! So our target is to rewrite 1 byte in the return address.
But there are two tips we need to pay attention with. First, we cannot do a lot of “a” before “d”, because this method will taint local variable such as player, opt and map, which also locates near the return address. So we need to “d” first and use a single “a” to arrive our destination.
Second, also the most weird, we probably make it on our local machine use 0x5d
, but will fail on remote. We need to use offset such as 0x5e
, 0x60
, 0x61
and 0x64
. I still don’t figure it out today, perhaps this issue related to implemention of libc printf function, cause through debug, the control flow changes indeed.
Final payload:
1 | ladddddddddddddddddddddddddddddddddddddddddddddddwwwww |