[Juniors CTF] Typical Admin, but...

Writeup by ipu

The task:

Rick was developing a patch for time machine, but his love for alcohol products slowed the process. The only thing he managed is to write one line of code, and booze finished the rest. After finishing work on the project he was able to behold his creation more sober eyes. Then he got drunk again. As it turned out, clouding his mind, Rick did not create a workable patch, just a puzzle. Take a look at this: Repository. Try to solve it. To find solution use:

So we got a link to a git repo and when we opened it we noticed there was only one file with one line in it so we needed to find the rest of the code.
We also noticed we are on branch 1 and that there are other branches numbered from 1 to 108, each has commits with description in the pattern: Version is {}. Next commits in {} and 1 line of code.

After we looked at a few branches manually we found out that branch 3 contains all the commits and they were in order so we made a script that generate the complete file from the commits. If we would not have found it, we could use the command git rev-list --remotes and get a list of all commits from all branches. This is the script we used:

#!/usr/bin/env python

import subprocess

repo_path = '/home/_/Downloads/TM-Patch'
out = open('main.go', 'w+')

cmd = 'cd {}; git checkout origin/3; git log --pretty=oneline 3'.format(repo_path)
result = subprocess.check_output(cmd, shell=True)

commits = [line.split(' ')[0] for line in result.split('\n')]
commits.reverse()
commits.pop(0)

for commit in commits:
    cmd = 'cd {}; git checkout {}'.format(repo_path, commit)
    subprocess.call(cmd, shell=True)

    f = open(repo_path + '/main.go')
    line = f.read()
    f.close()

    out.write(line)

Sadly the file we got was not complete, trying to build it gave us an error:

[~/Downloads] go run out.go 
# command-line-arguments
./out.go:106:1: syntax error: unexpected EOF, expecting }

We counted how many { and } there are in the files using the command tr -cd { < out.go | wc -c and found out there are 3 missing closing brackets, the same amount of missing commits (there are 105 commits but 108 branches, each branch is a version). We converted the commits into a linked list in python using a script and found out the 3 commits that were missing: 52 92 16. This is the script we used:

#!/usr/bin/env python

import subprocess


def find_in_dict(val):
    for item in llist:
        if item['current'] == val:
            return item

    return None


def get_commit_desc(commit):
    cmd = 'cd {}; git log --format=%B -n 1 {}'.format(repo_path, commit)
    return subprocess.check_output(cmd, shell=True)


repo_path = '/home/_/Downloads/TM-Patch'

batcmd = 'cd {}; git checkout origin/3; git log --pretty=oneline 3'.format(repo_path)
result = subprocess.check_output(batcmd, shell=True)

commits = [line.split(' ')[0] for line in result.split('\n')]
commits.reverse()
commits.pop(0)

llist = []
for commit in commits:
    result = get_commit_desc(commit)

    try:
        version = result.split('.')[0].split(' ')[2]
        next = result.split('.')[1].split(' ')[4].strip()
        llist.append({'current': version, 'next': next})
    except Exception as e:
        print(e)

# 58 is the first commit
head = find_in_dict('58')
while head is not None:
    current = head['current']
    next = head['next']

    print(current)
    head = find_in_dict(next)

    if head is None:
        print('missing {}'.format(next))

For each missing version we inserted a closing bracket and this is the code we got (after gofmt of course):

package main

import (
	"archive/tar"
	"compress/gzip"
	"encoding/base64"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
)

var MAGIC int

func main() {
	f, err := os.OpenFile("flag.tar.gz", os.O_RDONLY, 0444)
	if err != nil {
		panic(err)
	}
	gr, err := gzip.NewReader(f)
	if err != nil {
		panic(err)
	}
	tr := tar.NewReader(gr)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			panic(err)
		}
		path := hdr.Name
		switch hdr.Typeflag {
		case tar.TypeDir:
			if err := os.MkdirAll(path, os.FileMode(hdr.Mode)); err != nil {
				panic(err)
			}
		case tar.TypeReg:
			ow, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0777)
			if err != nil {
				ow, err = os.Create(path)
				if err != nil {
					panic(err)
				}
			}
			if _, err := io.Copy(ow, tr); err != nil {
				panic(err)
			}
			ow.Close()
		}
	}
	gr.Close()
	f.Close()
	file, err := os.OpenFile("morse_flag", os.O_RDONLY, 0444)
	if err != nil {
		panic(err)
	}
	stat, err := file.Stat()
	if err != nil {
		panic(err)
	}
	data := make([]byte, stat.Size())
	_, err = file.Read(data)
	if err != nil {
		panic(err)
	}
	file.Close()
	splStr := strings.Split(string(data), " ")
	var binaryFlag []string
	for _, v := range splStr {
		var temp []string
		for i := range v {
			if string(v[i]) == "*" {
				temp = append(temp, "1")
			} else if string(v[i]) == "-" {
				temp = append(temp, "0")
			} else {
				continue
			}
		}
		binaryFlag = append(binaryFlag, strings.Join(temp, ""))
	}
	binaryFlag = binaryFlag[:len(binaryFlag)-1]
	encodedFlag := make([]string, len(binaryFlag))
	for i := range binaryFlag {
		temp, _ := strconv.ParseInt(binaryFlag[i], 2, 64)
		encodedFlag[i] = string(temp)
	}
	baseFlag := strings.Join(encodedFlag, "")
	unBaseFlag, err := base64.StdEncoding.DecodeString(baseFlag)
	if err != nil {
		panic(err)
	}
	var rotFlag []byte
	MAGIC = 13
	for _, v := range unBaseFlag {
		rotFlag = append(rotFlag, v+byte(MAGIC))
	}
	deBaseRot, err := base64.StdEncoding.DecodeString(string(rotFlag))
	if err != nil {
		panic(err)
	}
	var flag []byte
	MAGIC = (1 << 3) + (1 << 5) - 1
	for _, v := range deBaseRot {
		flag = append(flag, v^byte(MAGIC))
	}
	fmt.Print(string(flag))
}

We tried to run it but we don’t have the flag.tar.gz file. The program only decrypt it but it needs the encrypted flag. Since the repository is the only info we got about this task we searched for the file in one of the commits using a python script:

#!/usr/bin/env python

import sys
import os
import subprocess

repo_path = '/home/_/Downloads/TM-Patch'

batcmd = 'cd {}; git checkout origin/3; git log --pretty=oneline 3'.format(repo_path)
result = subprocess.check_output(batcmd, shell=True)

commits = [line.split(' ')[0] for line in result.split('\n')]
commits.reverse()
commits.pop(0)

for commit in commits:
    subprocess.call('cd {}; git checkout {}'.format(repo_path, commit), shell=True)

    f = os.listdir(repo_path)
    if len(f) > 2:
        print(commit)
        sys.exit(0)

Now we can cd into the repo directory and find the file flag.tar.gz or download it from GitHub.
All that is left to do is run the Go code wit the flag.tar.gz file and get the flag: ItSoHardToProgSolo