Locked - 4/4 - InterIUT2022

Introduction

I attended the CTF InterIUT, where I placed 2nd with my team. Here is a write up of the last Forensic challenge (which in fact was a reversing challenge), which I've been the only one to solve.

At the beginning, the executable wasn't present in the initial statement, but the admins decided to give it to us since very few people solved the previous forensic challenge.

Analysis

As usual, we can run file on the binary to acknow its file type and architecture.

file Wordle.exe 
Wordle.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

I didn't do the previous challenges, I think the initial compromission is from a user that downloaded a malware thinking it was a Wordle game. We can see it's a portable executable (PE) and its architecture is PE32+.

Let's dive into the reversing.

The very first function go_buildid tells us it's probably written in go.

Often, the main function in go binaries is called main_main so I can hit up G and look for the main function.

Actually, the code is very huge, with a lot of variable (137). It will be quite long (and unnecessary) to explain each instruction. So I'll get to the main points.
First, we can see there's some sort of while True at the beginning, telling us that most of the logic should be inside that function. That's great since we won't have to search too much to find significant operations.

One of the interesting features of go is that it can download libraries from github sources during the compilation. That's what has been done here. The name of the function includes the name of the github repository.

We can take a look at the sources. The URL already tells us it's a keylogger. An example is even given in the repo.

package main

import (
	"fmt"
	"time"

	"github.com/kindlyfire/go-keylogger"
)

const (
	delayKeyfetchMS = 5
)

func main() {
	kl := keylogger.NewKeylogger()
	emptyCount := 0

	for {
		key := kl.GetKey()

		if !key.Empty {
			fmt.Printf("'%c' %d                     \n", key.Rune, key.Keycode)
		}

		emptyCount++

		fmt.Printf("Empty count: %d\r", emptyCount)

		time.Sleep(delayKeyfetchMS * time.Millisecond)
	}
}

Which looks quite similar to our program !

Here the keylogger will take an input using the getKey() function and check if it's empty and prints the key if pressed, else it increases a counter.


Let's get back to our PE.

We can see there's quite a lot of functions that look to be network related. As the code is quite hard to read, I decided to switch to the graph view.

From then I decided to focus on the goal of our challenge.

We can see there's a really interesting pattern that's often present when looking for codes, which is per-char comparison.

We can then check the uncompiled code:

if ( v12 == 6 )
        {
          v0 = (_BYTE *)v88[10];
          if ( v88[11] == 1LL && *v0 == 97 )
          {
            v0 = (_BYTE *)v88[2];
            if ( v88[3] == 1LL && *v0 == 118 )
            {
              v0 = (_BYTE *)v88[8];
              if ( v88[9] == 1LL && *v0 == 85 )
              {
                v0 = (_BYTE *)v88[6];
                if ( v88[7] == 1LL && *v0 == 57 )
                {
                  v0 = (_BYTE *)*v88;
                  if ( v88[1] == 2LL && *(_WORD *)v0 == 0xA9C3 )
                  {
                    v0 = (_BYTE *)v88[4];
                    if ( v88[5] == 1LL && *v0 == 46 )if ( v12 == 6 )

Looks like we got our code ! Basically, 97, 118. 85, 57 are all ASCII values. We can assume v12 is the length of the string, which is 6. We can put them in order and assemble them.

Wait... what's 0xA9C3 ? Well, I didn't guess it a first, and in fact it's a é. At first, I guessed it, but you can quickly recover the chars matching that kind of character using python:

So our pass is: év.U9a. At first I thought I could wrap it in interiut{} and submit it ! And I got an Incorrect Flag... In fact I missed something ! A character was needed to take that input. Looking back at the graph view, we can see a really interesting block:

In fact, a char is compared with 0x2A witch is '*' in ASCII !

Our flag is interiut{*év.U9a}


Going further:

If we found this malware in the wild, something interesting to do would be analysing internet traffic to find the command and control for exemple. We now know we can deactivate the malware by putting a code. We also know it only does net communications, it is (almost) safe to debug and analyse dynamically !

since the program exits if it doesn't succeed connection to the C&C it isn't possible to do a dynamic analysis of that malware. I would have used Wireshark to monitor the traffic while running the malware. Although the ip is present directly in the malware.

Thanks for the fun challenge !