Post

Git Hackfest 2023

Back in october 2023, I took part in the Hackfest 24h CTF which was quite intense. During this CTF, 2 git challenges were available for a decent amount of points for each of them. With my programmer background and the fact that I mastered the Bandit challenges, I though I had enough knowledge to get over it so I told my team that I could easily handle them. In fact it took me a couple of hours to figure them out and shown me how creative you can be with git when it comes to hide data.

I kept only one of the git challenge on my computer -the easiest one- and erased the other not knowing I would be able to post about them later. However, the idea was quite the same.

So I downloaded the archive and found myself with this architecture:

1
2
3
4
.
..
.git
flag.txt

After realizing that the flag.txt had only one letter, I just though it was going to be easy, there is going to be maximum 5 commits and I just had to manually rollback to the version having the flag so I tried to get the number of commits:

1
git log --oneline | wc -l #printing the commits and piping the result through a line counter
1
2612

Desktop View

After such a betrayal, I tried to investigate more on what was going on, doing:

1
git rebase -i HEAD~2611

And replaced all the “pick” by “edit”, I could manually see each commit and how they modify the flag.txt file so I chained the 2 following commands over and over again:

1
git rebase --continue; cat flag.txt

Until I realized that it will not lead me anywhere and made me abort the rebase. From here I though I needed a more visual point of view on what was happening so I opened my VSCode into this folder and took advantage of the git graph extension to explore the commits.

I was going down the commits desperately looking for a useful piece of information that could get me on track until I found this:

Desktop View

I realized that each commit title was a hash, and VSCode just gave me the hint I needed: some commits have hashes of other commits. From here you can guess that if you have a commit A having the hash of the commit B, you can jump to commit B which may have the hash of the commit C to which you can jump to and so on. Then among those 2612 commits you have commits chained by their titles that can make something !

The thing is: I don’t see how long is the “chain” I am talking about and there’s no way for me to do that manually, so I wrote a Shell script collecting the commit chain for me (during the Hackfest, the script was really messy, I modified it, removed useless echos and added comments to make it more readable):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/bin/bash

function get_parent_hash(){
     # Set the commit hash from the command line argument
    local commit_hash="$1"
    # Get the parent commit hash using Git log
    local parent_commit_hash=$(git log --format=%P -n 1 "$commit_hash" 2>/dev/null)
    # Check if the commit hash exists in the Git repository
    if [ -n "$parent_commit_hash" ]; then
        echo "$parent_commit_hash"
    else
        return 1
    fi
}

function get_commit_title(){
    # Set the commit hash from the command line argument
    local commit_hash="$1"
    # Get the commit title using Git log
    local commit_title=$(git log --format=%s -n 1 "$commit_hash" 2>/dev/null)
    # Check if the commit hash exists in the Git repository
    if [ -n "$commit_title" ]; then
        echo "$commit_title"
    else
        return 1
    fi
}

function commit_exists(){
    # Set the commit hash from the command line argument
    commit_hash="$1"
    # Check if the commit hash exists in the Git repository
    if git cat-file -e "$commit_hash^{commit}" 2>/dev/null; then
        echo 0
    else
        echo 1
    fi
}

LATEST_COMMIT="3431e57c288848b15394b4c70eabaccc7c06c36e"

commit_hash=$LATEST_COMMIT
#Iterating through the commits to find the first element of the chain
while [[ $(commit_exists "$(get_commit_title "$commit_hash")") == "1" ]]; do
    commit_hash=$(get_parent_hash "$commit_hash")
done

#Getting each commit hash and concatening letters from flag.txt
flag_string=""
while [[ $(commit_exists "$(get_commit_title "$commit_hash")") == "0" ]]; do
    git checkout "$commit_hash" 2>/dev/null
    flag_string="$flag_string$(cat flag.txt)"
    commit_hash=$(get_commit_title "$commit_hash")
done

echo "$flag_string"

# Coming back to the original commit when we're done
git checkout 3431e57c288848b15394b4c70eabaccc7c06c36e 2>/dev/null

Which gave me some giberish:

1
SEYtYWE4ODQ0NDI5MGM5ZDExMjI3NzljZDU5MWFmMTQzMDA

By this time I was so tired I didn’t realized what encoding it would be and I didn’t even know if I was onto something or not, so I just threw the result to CyberChef with its magic tool and it woke me up when I suddenly recognized the Hackfest flag format (it was base64 encoded):

1
HF-aa88444290c9d1122779cd591af14300

This was just another way to play around with Git !

This post is licensed under CC BY 4.0 by the author.