88693546_xxl-1

Check Point’s Cyber CTF Challenges

Share on facebook
Share on twitter
Share on email

Check Point is one of the leading Cyber Security firms in the world. It was established in 1993 and has only grown from strength to strength. Its Cyber Security products are used globally. Check Point is also one of HackerU’s hiring partners and students of HackerU have found successful employment with Check Point.

In the middle of summer 2018, Check Point – the leading Israeli company for software security, published a series of Cyber challenges.

The challenges were divided into six different categories:

  1. Logic
  2. Web
  3. Programming
  4. Networking
  5. Reversing
  6. Surprise(LOL)

Each category had two challenges. I’ve a deep respect for Check Point and decided to try those challenges. Since I was very busy at that point in time, I attempted 8 challenges (four different categories) and managed to solve 7 of them.

The challenges’ official deadline, as stated on their info page, was the end of September 2018.

This is a step-by-step explanation of my solutions to each of these challenges:

  1. Logic Challenge: PNG++
  2. Web Challenge: Return of the Robots
  3. Web Challenge: Diego’s Gallery
  4. Programming Challenge: Careful Steps
  5. Programming Challenge: Puzzle
  6. Networking Challenge: Ping Pong
  7. Networking Challenge: Protocol

 

Logic Challenge: PNG++

Description:

This (link to encrypted PNG file) image was encrypted using a custom cipher.
We managed to get most of its code here (link to Python code)
Unfortunately, while moving things around, someone spilled coffee all over key_transformator.py.
Can you help us decrypt the image?

Python code:

The logic of encryption works like this:

  1. Set key_length (key size) to 4 bytes.
  2. Read bytes of a file flag.png into variable img.
  3. Add padding to a file, to the first number that can be divided by four. The padding is the number of the missing bytes. For example, if the file length is 29 bytes (missing 3), add 3 bytes with a decimal value of 3 (ASCII 0x03); or, in other words, the padding byte can’t be null (ASCII 0x00), because its decimal value is 0, which means no padding needed.
  4. Set the initial key to a random 4 upper-case letters ([A-Z]).
  5. Iterate over all bytes from the file, with a cipher block chain of 4 bytes at a time.
    1. Every byte from a file is XORed with a byte from a key
    2. Each key is dynamically changed to another key, that is generated by               key_transformator.transform.(key)
    3. Encrypted bytes are appended to enc_data
  6. Write enc_data (encrypted bytes) to encrypted.png

First, I checked the header of the PNG format and found that the first 8 bytes were:

[137, 80, 78, 71, 13, 10, 26, 10]

I took the first 8 bytes of the encrypted file and XORed them with the 8 bytes of the header PNG:

The key of the first block was:

[85, 80, 82, 81]

The key of the second block was:

[86, 81, 83, 82]

Since every next key is dynamically generated using the previous key,

key = key_transformator.transform(key), I could clearly see that the logic was increasing by increments of 1 in relation to each byte in the previous key.

85 -> 86

80 -> 81

82 -> 83

81 -> 82

At this point, I realized that the name of the challenge was actually a huge hint.

I wrote two helping functions:

  • A nextKey that returns the next key based on the previous key
  • A nextChar that returns the next char which, in most cases, is just increased by increments of – unless it reaches the byte boundary (255)

I converted the initial 4 bytes [85, 80 82, 81] into chars, UPRQ, and ran reverse logic.

The decrypted file revealed the flag:

Web Challenge: Return of the Robots

Description:

Robots are cool, but trust me: their access should be limited!                                                                          Check it out (link)”

When entering the link, I arrived at a page about robots. Everything on the page prompted me to check the robots.txt file.

Adding ./robots.txt to the URL revealed the following:

Adding ./robots.txt to the URL revealed the following:

So, I checked the ./secret_login.html and was presented with a login form:

I checked the source code to find the password validation and found the following JavaScript:

It seemed that when the value was submitted, it sent to function auth, which decoded the passed value with the function btoa (base64 decoding function in JavaScript). Then compared to string: SzMzcFQjM1IwYjB0JDB1dA==.

I decoded this string:

Then, I succeeded in passing the decoded value K33pT#3R0b0t$0ut to the input field, and got the flag:

Return

The challenge was solved!

Web Challenge: Diego’s Gallery

Description:

Recently I’ve been developing a platform to manage my cat’s photos and keep my flag.txt safe. Please check out my beta (link to a sign up form).
To avoid security loopholes like SQL injections, I developed my own scheme.
A short description is available here (link that shows the log scheme)

A sign up form:

A given scheme:

After I provided a test username and password, I was logged in to see a public gallery of a cat named Diego.

Since I was logged in as a regular user, I assumed the challenge wanted me to escalate my privileges. Looking at the given scheme of logged users, I saw that the difference between a simple user and an admin was the ‘role,’ which was located in the third section of the scheme. As you can see in the figure above, the scheme uses three pipe chars to separate values.

Based on what I observed, I figured that if the first value (username) went to the first section and the second value (password) went to the second section, then the role value must be added by the web service; immediately after that, the user is logged in with the current role.

Illustration:

START|||First value|||Second value|||user(Added by web service)|||END

My payload was:

  • First value: niki|||niki|||admin|||END\nSTART|||other
  • Second value: other

Which we can assume created the following log rows:

START|||niki|||niki|||admin|||END

START|||other|||other|||user|||END

Once I delivered the payload, I was logged as an Admin user and had access to the Admin Panel:

Clicking on the buttons just showed an alert: “Not implemented yet”

However, I could see the following inside URL:

http://35.194.63.219/csa_2018/diegos_gallery/_nwryqcttstvs/admin-panel/index.php? view=log.txt

This definitely looks like LFI (Local File Inclusion) through URL.

Then, I tried:

http://35.194.63.219/csa_2018/diegos_gallery/_nwryqcttstvs/admin-panel/index.php?view=flag.txt

I successfully completed the challenge and revealed the flag.

 

Programming Challenge: Careful Steps

 Description:

This (link to the file) is a bunch of archives we’ve found. We believe a secret flag is somehow hidden inside them.

We’re pretty sure the information we’re looking for is in the comments section of each file.

Can you step carefully between the files and find the flag?

Good luck!

After extracting the file, you’ll get access to a directory called archives with 2000 files named unzipme.[number].

I used the binwalk program to check the first file and it provided me with the following information:

I could see that it had a letter and a number (W,245) in the comments section. It seemed to be some kind of hint. My first guess was that I should probably concatenate the letters according to the indexes included in the zip files. After this deduction, I checked the next file for index 245. But I had a surprise—the second file wasn’t a zip at all; it was a RAR file.

Therefore, I needed to unRAR it just to access the comments:

My logic was to run a binwalk on the file. If it found RAR, I would need to unRAR it to access the comments, otherwise look for the comments section directly to find the next step.

My first guess was that the number was an index of the file (the files are indexed between 0 and 1999), but I discovered that some of the numbers were negative and therefore, this number couldn’t be an index.

My second guess was that the number was a jump and therefore, could be negative, as well. For each number, I needed to jump to the next file. I knew that if my code iteratively jumped to the next calculated files each time, it could easily get stuck in an infinite loop if the files had a built-in trap. Because of this, I wrote a Python code with a count limitation, and after a few tests, I found that a count of 120 was just enough:

Then, I found the flag:

Mission accomplished!

Programming Challenge: Puzzle

Description:

At last, we’ve found you!

We must solve this puzzle, and according to the prophecy – you are the one to solve it.

This puzzle is weird. It consists of a board with 10 columns and 10 rows, so there are 100 pieces. Yet, each piece is weird! It has four ‘slices’—a top slice, a right slice, a bottom slice, and a left slice.

Each slice consists of a number. For example, consider this piece:

————

|  \ 12 /  |

| 5 \  / 3 |

|   /  \   |

|  / 4  \  |

————

Its top is 12, its right is 3, its bottom is 4 and its left is 5.

To solve the puzzle, all pieces must be sorted into the board, where each slice is equal to its adjacent slice.

In addition, a slice that has no adjacent slice (that is, the slice is a part of the board’s border), must be 0. Other slices are never 0.

For example, the following board (with 4 pieces) is valid:

————————

|  \ 0  /  ||  \ 0  /  |

| 0 \  / 9 || 9 \  / 0 |

|   /  \   ||   /  \   |

|  / 17 \  ||  / 11 \  |

————————

————————

|  \ 17 /  ||  \ 11 /  |

| 0 \  / 6 || 6 \  / 0 |

|   /  \   ||   /  \   |

|  / 0  \  ||  / 0  \  |

————————

In the board above, all the border slices are equal to 0.

Consider the top-left piece. Its right slice is equal to 9, and its adjacent slice (the left slice of the top-right piece) also equals 9.

Unfortunately, all the pieces have been shuffled and are not in the correct order. Each piece has been constructed in the following format:

cube_id, [slices]; cube_id, slices; … cube_id, slices

Where cube_id is a number from 0 to 99, and slices include the numbers in the order: top, right, bottom, left.

For instance, consider the following shuffled board:

————————————

|  \ 0  /  ||  \ 0  /  ||  \ 5  /  |

| 18\  / 12|| 19\  / 7 || 19\  / 0 |

|   /  \   ||   /  \   ||   /  \   |

|  / 2  \  ||  / 6  \  ||  / 0  \  |

————————————

————————————

|  \ 6  /  ||  \ 14 /  ||  \ 7  /  |

| 10\  / 2 || 10\  /  0|| 0 \  / 12|

|   /  \   ||   /  \   ||   /  \   |

|  / 9  \  ||  / 5  \  ||  / 0  \  |

————————————

————————————

|  \ 0  /  ||  \ 0  /  ||  \ 0  /  |

| 7 \  / 0 || 7 \  / 17|| 17\  / 0 |

|   /  \   ||   /  \   ||   /  \   |

|  / 18 \  ||  / 9  \  ||  / 14 \  |

————————————

The board above is described by the following string:

‘0,[0, 12, 2, 18]; 1,[0, 7, 6, 19]; 2,[5, 0, 0, 19]; 3,[6, 2, 9, 10]; 4,[14, 0, 5, 10]; 5,[7, 12, 0, 0]; 6,[0, 0, 18, 7]; 7,[0, 17, 9, 7]; 8,[0, 0, 14, 17]’

We need you to solve the puzzle!

Provide us with a string constructed exactly like the following example:

cube_id, times_to_rotate_clockwise; cube_id, times_to_rotate_clockwise;… cube_id, times_to_rotate_clockwise

For example, a “solution” string looks like:

2,2; 1,0; 6,0; 4,2; 3,0; 0,1; 8,2; 7,2; 5,3

The above string corresponds to the following (valid) puzzle:

————————————

|  \ 0  /  ||  \ 0  /  ||  \ 0  /  |

| 0 \  / 19|| 19\  / 7 || 7 \  / 0 |

|   /  \   ||   /  \   ||   /  \   |

|  / 5  \  ||  / 6  \  ||  / 18 \  |

————————————

————————————

|  \ 5  /  ||  \ 6  /  ||  \ 18 /  |

| 0 \  / 10|| 10\  / 2 || 2 \  / 0 |

|   /  \   ||   /  \   ||   /  \   |

|  / 14 \  ||  / 9  \  ||  / 12 \  |

————————————

————————————

|  \ 14 /  ||  \ 9  /  ||  \ 12 /  |

| 0 \  / 17|| 17\  / 7 || 7 \  / 0 |

|   /  \   ||   /  \   ||   /  \   |

|  / 0  \  ||  / 0  \  ||  / 0  \  |

————————————

Consider the top-left piece. In the string, it corresponds to ‘2,2’, as we take cube number 2 from the input:

2,[5, 0, 0, 19]

But we rotate it clock-wise, twice, so we get [0,19,5,0].

Now, consider the top-middle piece. In the string, it corresponds to ‘1,0’. That is, we take cube number 1 from the input:

1,[0, 7, 6, 19]

We don’t rotate it at all (that is, rotate it 0 times), as it’s already in the right direction.

Got it?

Help us solve the puzzle!

The puzzle we have is:

0,[16, 18, 16, 10]; 1,[11, 13, 17, 20]; 2,[10, 18, 11, 18]; 3,[13, 14, 14, 18]; 4,[7, 3, 3, 1]; 5,[12, 17, 10, 0]; 6,[16, 6, 4, 7]; 7,[0, 9, 16, 16]; 8,[5, 7, 20, 0]; 9,[9, 0, 20, 2]; 10,[6, 11, 16, 14]; 11,[10, 11, 20, 16]; 12,[17, 15, 5, 0]; 13,[8, 11, 12, 0]; 14,[15, 13, 10, 11]; 15,[4, 6, 15, 14]; 16,[0, 9, 8, 7]; 17,[11, 1, 0, 16]; 18,[7, 11, 16, 11]; 19,[7, 7, 9, 9]; 20,[13, 7, 16, 20]; 21,[12, 0, 13, 5]; 22,[2, 9, 4, 0]; 23,[19, 10, 1, 20]; 24,[11, 18, 2, 18]; 25,[18, 6, 9, 18]; 26,[1, 5, 15, 2]; 27,[0, 17, 9, 5]; 28,[12, 1, 1, 1]; 29,[6, 1, 0, 10]; 30,[3, 7, 12, 0]; 31,[0, 5, 15, 17]; 32,[3, 1, 10, 14]; 33,[6, 14, 14, 18]; 34,[8, 7, 10, 0]; 35,[14, 13, 14, 14]; 36,[20, 2, 0, 17]; 37,[18, 11, 8, 0]; 38,[16, 9, 11, 17]; 39,[0, 1, 7, 18]; 40,[6, 7, 17, 20]; 41,[2, 8, 17, 7]; 42,[16, 16, 17, 16]; 43,[6, 2, 2, 6]; 44,[10, 13, 6, 7]; 45,[9, 11, 16, 11]; 46,[6, 0, 16, 11]; 47,[12, 17, 9, 18]; 48,[16, 1, 4, 14]; 49,[11, 18, 13, 2]; 50,[13, 15, 3, 18]; 51,[1, 4, 14, 9]; 52,[0, 2, 11, 19]; 53,[4, 2, 0, 0]; 54,[0, 1, 3, 0]; 55,[0, 0, 1, 17]; 56,[2, 14, 3, 11]; 57,[4, 15, 7, 4]; 58,[3, 19, 10, 11]; 59,[0, 6, 9, 0]; 60,[0, 11, 8, 8]; 61,[10, 15, 16, 10]; 62,[10, 15, 7, 14]; 63,[11, 13, 10, 20]; 64,[9, 18, 15, 10]; 65,[5, 11, 18, 0]; 66,[1, 15, 12, 19]; 67,[0, 18, 14, 17]; 68,[19, 11, 7, 20]; 69,[11, 14, 15, 14]; 70,[11, 11, 2, 7]; 71,[0, 17, 20, 11]; 72,[13, 1, 5, 11]; 73,[8, 13, 12, 6]; 74,[14, 1, 16, 18]; 75,[16, 18, 0, 12]; 76,[17, 1, 15, 20]; 77,[5, 0, 10, 1]; 78,[7, 1, 13, 12]; 79,[10, 18, 11, 20]; 80,[7, 9, 20, 12]; 81,[7, 4, 14, 17]; 82,[16, 7, 2, 6]; 83,[10, 12, 11, 9]; 84,[12, 16, 0, 7]; 85,[16, 6, 0, 18]; 86,[6, 6, 8, 11]; 87,[3, 7, 12, 16]; 88,[1, 16, 3, 13]; 89,[12, 4, 20, 0]; 90,[16, 3, 11, 17]; 91,[9, 16, 9, 14]; 92,[16, 13, 0, 6]; 93,[2, 3, 5, 7]; 94,[8, 18, 4, 20]; 95,[12, 10, 7, 13]; 96,[10, 4, 11, 13]; 97,[17, 0, 19, 15]; 98,[13, 20, 11, 2]; 99,[1, 0, 20, 10]

Good luck!

Theoretically, this challenge could be solved in many different ways. I thought of several ways and came to a few conclusions:

First: At any given time, I only needed to assign one cube. For each cube, I always knew for sure that there were two sides: Top and Left. In the beginning, it was zero and zero. Next, I calculated zero on top. I looked on the left side of my current cube for the number located on the right side of the previous cube.

Second: Because of a tremendous amount of different possibilities and combinations between the cubes, I needed to try to take as few steps as possible and exit the unneeded check at the first negative conclusion.

Third: To work with different cubes, I always needed to remember the original position (ID), the amount of rotations taken from the original starting point, and obviously, the numbers on each side (top, right, bottom, left).

Fourth: The initial input and output data were strings (not the actual positions).

Based on the above postulations, I chose the following strategy to solve the puzzle:

  • To move linearly through the cubes, from left to right, until I hit the right corner. Then, to go one row down and start from the left with the top number of the cube above.
  • Matching would be checked after each rotation, so if 4 rotations were made and there was still no match for the current cube, I would move on to the next cube. Every target cube (currently checked) could potentially be checked with all the other cubes, therefore, I could recursively reinitialize each one with a full stack of all other cubes.
  • To keep track of all the changes I’ve made to the cubes versus the original, I used the OOP approach to create a Cube object with an ID, rotation counter, and four sides (top, right, bottom, and left).
  • Instead of copying the original orientation of the cube, and to prevent errors in counting following multiple rotations, I always set the counter to calculate in increments of one (1). When I check the counter after the final rotation of each cube, I always use a modulo of 4.

For example, if the correct rotation required is 1 (from [1,2,3,4] to [4,3,2,1]), but a mistake was made and a rotation of 3 was made (currently [2,3,4,1]), then the next rotation would need to be 2 (from current [2,3,4,1] to the required [4,3,2,1]). Therefore, 3 plus 2 equals 5 and 5 modulo 4 = 1, which is exactly what’s needed.

  • Throughout the process, I needed to track used cubes and prevent duplicate checks from occurring. To prevent duplicate checks, I used a map of cube IDs. Therefore, if the cube I’m comparing my current cube to matches, I can easily remove it according to its ID; if it doesn’t match, I can put it back to Map.

Data structures I chose were:

  • Map – for associating ID (original position) and cube, built from the input string
  • Stack – for testing connected cube IDs
  • Array – for temporary testing of output cubes

Cube object code:

String manipulation methods:

Solution (logic) for finding matching cubes:

Execution:

Networking Challenge: Ping Pong

Description:

I bet you’re not fast enough to defeat me.

I’m at:
nc 35.157.111.68 10158

After running the given command, you get:

At this point, it looked like this was a Streams challenge.

However, after reviewing the first bit of data, the result seemed to wait and then fail in timeout.

For each number that returned, I needed to send the last received number before the timeout.

There was a new line separator at the end of the data.

I recreated the stream with Python code. It seemed to work, so I added iterative code that repeated this ping-pong logic and waited for the flag to appear.

After a lot of ping-ponging (probably more than a thousand) between the client and server, I eventually got the flag:

Challenge solved!

 Networking Challenge: Protocol

Description:

Hi there!

We need to extract secret data from a special file server.

We don’t have many details about this server, but we did manage to intercept traffic containing communication with the server.

We also know that this secret file’s path is: /usr/PRivAtE.txt

You can find the sniff file here (link to a file).

Please tell us what the secret is!

Good luck!

I downloaded the given file and opened it in Wireshark:

I checked registered conversations:

It helped me to see what was going on more clearly:

I saw conversations with IP 35.157.111.68 on port 20120 and SSH port 22. At that point, I thought to check port 20120 and then considered moving to the SSH (I later realized this wasn’t necessary).

I used the nc command to check the IP and Port, then received the response: “0 8 Welcome

I wasn’t sure which input it was expecting, so I returned to Wireshark to see if I could find any hints there.

I filtered all the data packets for 35.157.111.68 on port 20120 and found some readable conversations:

I recreated the same scenario with the nc command tool:

I noticed that the response to my “2 3 XOR” request was different than the one from the file.

I repeated the whole process again and again, to see if the response changed every time.

As I thought, the received 4 chars were different each time, as well as the final data.

It seemed like the receiving 4 chars were the key to decrypting the final data.

I also noticed that the prefix numbers in each data packet weren’t random.

It looked like:

  • The first number represented the order: 0 -> first, 1 -> second, 2 -> third, etc…
  • The second number represented the length of the content: 8 -> “Welcome”, 5 -> “Hello” ->, etc…

I figured maybe the 4 random chars were the key to decryption, since the final data was built as a contiguous string of 4 HEX chars and the XOR key was also 4 HEX chars. I wondered if I could iterate over each block and split them into separate bytes before XORing them with the bytes from the key.

I tried to decrypt the data packet:

I succeeded, but the data I received wasn’t the flag.

I returned to the rules and noticed “secret file’s path is: /usr/PRivAtE.txt

After noting the secret file’s path, I recreated the process again, but this time requested the path “/usr/PRivAtE.txt”:

Once again, I tried to decrypt the final data:

And I finally got the flag!

Another challenge solved!

WE WANT TO HEAR FROM YOU

Skip to content