GreyCTF 2024 Pattern Enigma Matrix
GreyCTF 2024 Pattern Enigma Matrix
GreyCTF 2024
Pattern Enigma Matrix
I made this program that checks if the flag is correct. However, I forgot the flag…
Can you help me recover it?
Files given:
Testing the output:
From the program output, we know we have to pass 11 cases.
IDA
Upon opening the file in IDA, there was already a problem loading it in graph form.
Go to Options > General… > Graph, and change “Max number of nodes” to 4096.
Click “OK” and <spacebar> to change to the graph view.
Analysis Part 1
The program checks whether there is more than one argument.
If there is no argument, it loads {flag_guess} to the output and exits.
If there is an argument, the program will proceed to take it as an input.
The rest of the code seems like a control flow graph.
The strings that could form potential decisions are as follows:
- M4TCH/m4tch
- 1Ng/1nG
- 1M3/1m3
- T1M/t1m
- C0MP/c0mp
- 1LE/1le
- EF6F33A17D0B9C}/ef6f33a17d0b9c}
- GREY{/grey{
- DEF/def
- 669870FC2FEC/669870fc2fec
- @F/`f
- /9
- @Z/`z
- /9
- _
- @Z/`z
- /9
- _
- @Z/`z
- /9
- _
- @Z/`z
- g/G
- /9
- _
- @F/`f
- /9
Going to the first comparison (m4tch/M4TCH), there seems to be a check on byte [rbp-7Eh] which determines whether we go to the path with “M” or “m”.
- Since check byte [rbp-7Eh] is 0, “test al, al” will give 0 and zero flag ZF will be set to 1. With “jz short loc_15D1”, the jump will be taken and we will go to the path with “m”.
- If ( (!check_byte | char!=M) AND (char!=m) ), jump to loc_181D, which skips through all of the following comparisons.
- The (!check_byte | char!=M) would already be 1 since the first condition is fulfilled. With the AND operator, the character must be “m”, and it cannot be both “m” and “M” at the same time.
- This happens across all of the Cases where there are 2 possible variations (uppercase/lowercase) of a character, and this is why only 1 path is taken.
After matching for “m”, the program moves on to “4” -> “t” -> “c” -> “h” to form “m4tch”. It moves on to the next 10 comparisons, until the end where they count how many cases you have passed out of the 11 cases.
The cases found so far in the same manner were as follows:
1
2
3
4
5
6
- Case 1: m4tch
- Case 2: 1nG
- Case 3: 1m3
- Case 4: t1m
- Case 5: c0mp
- Case 6: 1le
I deduced from the decisions found earlier that the string definitely starts with “grey{“ and ends with “ef6f33a17d0b9c}”.
Next, there is a for loop from 0 which breaks if the loop count exceeds 43h (67) (up to 68 characters). …
- This simply checks the length of the input, whether it is 68 characters.
More cases:
1
2
3
- Case 7: ef6f33a17d0b9c}
- Case 8: grey{
- Case 9: 68 chars
Script bc lazy:
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
# input = 'GREY{M4TCH1Ng_T1M3_C0MP1LE_EF6F33A17D0B9C}' # 0/11 cases passed
# input = 'm4tch1nG' # 2/11 cases passed
# input = 'grey{m4tch1nG}' # 2/11 cases passed
# input = 'grey{m4tch1nG_c0mp1le}' # 2/11 cases passed
# input = 'grey{m4tch1nG_t1m3}' # 4/11 cases passed
# input = 'grey{m4tch1nG_t1m3_c0mp1le}' # 6/11 cases passed
input = 'grey{m4tch1nG_t1m3_c0mp1le_ef6f33a17d0b9c}' # 8/11 cases passed
p = process(['./dist (2)/a_stripped.out', input])
resp = p.recvall()
print(resp)
Analysis Part 2
The next part is different: rather than simply matching a few word segments, we will need to match a specific number of occurrence of regex.
This article provides some explanation and has a graph of a similar structure - Regular expressions obfuscation under the microscope.
At the next function where DEF/def is at, there is a for loop from 0 which breaks if the loop count exceeds 3 (aka a few comparisons will take place for up to 4 characters). …
Recall the comparison with “M” or “m” earlier, where there was a check on byte [rbp-7Eh] which determines whether we go to one path or another.
- This happens across all of the Cases where there are 2 possible variations (uppercase/lowercase) of a character, and this is why only 1 path is taken.
If ( (check_byte | char==D) AND (char==d) )
-> check_byte = 1. With this, in the comparison with “D” or “d”, we will take the path to “d”.
- Following the comparison with “d”:
- If the character is not “d”, continue the next comparison with “e” and if it is also not “e”, continue the next comparison with ‘f’. This whole thing loops again until 4 of our input characters have been compared. It then moves on to the next few comparisons, which also involves this kind of comparison in a for loop.
- This gives the regex
[def]{4}
.
After the for loop, there is the comparison to 669870fc2fec
.
Following that is another for loop from 0 that breaks if the loop count exceeds 15 (up to 16 characters). …
In the for loop, the comparisons set the following conditions for each character:
- If ( ( check_byte AND (char > “@” (40h) AND char <= “F” (46h)) ) | (char > “`” (60h) AND char <= “f” (66h)) )
-> check_byte = 1, char has to be [a-f] - This gives the regex
[a-f0-9]{16}
.
All of these 3 ([def]{4}, 669870fc2fec, [a-f0-9]{16}) are in 1 big loop, so it forms a single case.
Another one:
1
- Case 10: [def]{4}669870fc2fec[a-f0-9]{16}
After this, there are multiple other similar loops:
- Checks for 7 char, a-z/0-9
- ‘_’
- Checks for 4 char, a-z/0-9
- ‘_’
- Checks for 7 char, a-z/G/0-9
- ‘_’
- Checks for 8 char, a-z/0-9
- ‘_’
- Checks for 32 char, a-f/0-9
This gives the regex [a-z0-9]{7}\_[a-z0-9]{4}\_[a-zG0-9]{7}\_[a-z0-9]{8}\_[a-z0-9]{32}
.
Last one:
1
- Case 11: [a-z0-9]{7}_[a-z0-9]{4}_[a-zG0-9]{7}_[a-z0-9]{8}_[a-z0-9]{32}
Case 1: m4tch
Case 2: 1nG
Case 3: 1m3
Case 4: t1m
Case 5: c0mp
Case 6: 1le
Case 7: ef6f33a17d0b9c}
Case 8: grey{
Case 9: 68 chars
Case 10: [def]{4}(669870FC2FEC|669870fc2fec)[a-f0-9]{16}
Case 11: [a-z0-9]{7}_[a-z0-9]{4}_[a-z0-9]{7}_[a-z0-9]{8}_[a-z0-9]{32}
Flag:
grey{c0mp1le_t1m3_aaaaaaa_m4tch1nG_deff669870fc2fec5cef6f33a17d0b9c}
Exceptions:
- “deff” can be any 4 char that is either “d”, “e” or “f”.
- “aaaaaaa” can be any 7 char as there are no other restrictions other than Case 11.
- “compile” and “aaaaaaa” can be swapped around as they are both 7 char (Case 11).
The intended flag by the challenge authors is
grey{c0mp1le_t1m3_p4tt3rn_m4tch1nG_eefd669870fc2fec5cef6f33a17d0b9c}
.