Cover Image

Neuland CTF 2022 Winter - Reversing

 December 3, 2022    Neuland CTF


Download challenges: Neuland CTF Repository


Strings - Easy

Author: Manu
The flag is hidden somewhere in this binary.

strings

strings


The challenge is straightforward if you already know what strings is. It is a program that display printable strings in files. We can execute strings on our binary strings and we can find the flag in the output.

$ strings strings               
/lib64/ld-linux-x86-64.so.2
__cxa_finalize
__libc_start_main
puts
libc.so.6
GLIBC_2.2.5
GLIBC_2.34
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
PTE1
u+UH
nland{f0H
und_y0u}H
Try to get the flag!
;*3$"
...

The flag is nland{f0und_y0u}.


Tracer - Easy

Author: Manu
Can you trace down the admin password? Strings won't help you this time.
Flag format: nland{admin-password}

tracer

tracer


We are asked to find the admin password. We can't use strings this time as the binary contains a lot of strings. We have to find another tool that we can used for reverse engineering. The title and description hints us towards ltrace. We can use it to trace the library calls of a given program.

$ ltrace ./tracer            
printf("Enter admin password: ")                                                                             = 22
__isoc99_scanf(0x55cd2f78d01b, 0x7ffc5a4f50f0, 0, 0Enter admin password: test
)                                                                              = 1
strcmp("test", "42ceec6b744d41bc8044fee516003183"...)                                                        = 64
printf("Wrong password")                                                                                     = 14
Wrong password+++ exited (status 0) +++

Our input is compared with the string "42ceec6b744d41bc8044fee516003183" followed by the call to printf("Wrong password"). This seems promising. We will try this again with the found string.

$ ltrace ./tracer
printf("Enter admin password: ")                                                                             = 22
__isoc99_scanf(0x55ec8584001b, 0x7fff1f04d610, 0, 0Enter admin password: 42ceec6b744d41bc8044fee516003183
)                                                                              = 1
strcmp("42ceec6b744d41bc8044fee516003183"..., "42ceec6b744d41bc8044fee516003183"...)                         = 0
printf("Right password")                                                                                     = 14
Right password+++ exited (status 0) +++

We found the right password.

The flag is nland{42ceec6b744d41bc8044fee516003183}.


Snek Encoder - Medium

Author: Manu
I encoded the flag with a custom script that I wrote. I lost the source code to it. I just found this odd file in the project folder. It seems to describe operations and commands in some way to encode the flag. Maybe this helps you to find a way to decode the flag. This is the encoded flag: urfrg}qy6f-jZ.e-'U]((QSi&!POf.

encode

encode


The contents or the structure of the encode file are probably unfamiliar for most people. After some research you will find out that this is Python bytecode. It describes your source code as a low-level platform-independent representation. The challenge description states that this script is used to encode the flag. After we get the hang of how Python bytecode looks we can recover the original encode funtion.

def encode(flag):
    o = ''
    for i, b in enumerate(flag):
        b = ord(b)
        b = b + 7 - i
        a = chr(b)
        o += a
    return o

Now we know how the flag was encoded. However, we need to decode it. We are going to reverse the encode function to get the decode function. We end up with something like this.

def decode(flag):
    f = ''
    for i, b in enumerate(flag):
        c = ord(b)
        c = c - 7 + i
        c = chr(c)
        f += c
    return f

If we input the encoded flag into our decode function, we can retrieve the flag.

The flag is nland{py7h0n_4l50_h45_by73c0d3}.


Password - Medium

Author: Kevin
Don't Worry, Relax, Chill and Try harder

password

password


The program asks for a password. The challenge is to get the password through reversing the binary. There is some string stacking implemented within the binary.

The solution requires to reverse the binary and check which variables are used to fulfill the last if. Another solution requires to check what the last print prints. This is the flag, if the user provided the correct password. However, the printed variable (v4) is stacked too, which requires some work to determine which strings are used to build the flag.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "bytes"
)

func main() {
    s1 := "nland{th1s_1s_d3f1n3tly_n0t_th3_fl4g"
    s2 := "nland{w0w_y0u_f0und_"
    s3 := "th3_fl4g!}"
    s4 := "nland{"
    s5 := "s0_m3ny_"
    s6 := "4w3s0m3_fl4gs_"
    s7 := "1n_th1s_b1n4ry}"
    s8 := "nland{1t_1s_"
    s9 := "th3_fl4g_"
    s10 := "1_w4s_"
    s11 := "l00k1ng_for!}"

    var b bytes.Buffer
    b.WriteString(s4)
    b.WriteString(s5) 
    b.WriteString(s6)
    b.WriteString(s7)

    var sb strings.Builder
    sb.WriteString(s8)
    sb.WriteString(s9)
    sb.WriteString(s10)
    sb.WriteString(s11)

    fmt.Println("Welcome to NEULAND CTF!")
    fmt.Println("Please enter the password:")
    inp, _, err := bufio.NewReader(os.Stdin).ReadLine()
    if err != nil {
            fmt.Println("Uhm, something went wrong!", err)
            fmt.Println("nland{th1s_1s_d3f1n3tly_n0t_th3_fl4g")
    }

    v1 := s1
    _ = v1
    v2 := fmt.Sprintf("%s%s", s2, s3)
    _ = v2
    v3 := b.String()
    _ = v3
    v4 := sb.String()
    _ = v4

    if string(inp) == s6 + s9 + s11 {
        fmt.Println("You got it!")
        fmt.Println(v4)
    } else {
        fmt.Println("Don't Worry, Relax, Chill and Try harder")
    }
}

The password is: 4w3s0m3_fl4gs_th3_fl4g_l00k1ng_for!}

Flag: nland{1t_1s_th3_fl4g_1_w4s_l00k1ng_for!}


EarlyBird - Hard

Author: Dominik
The early bird catches the baby.

Insert name of file

Insert description


This challenge was marked as hard. We get the binary, and a server address to connect to. Connecting to the server with f.e. netcat (1) greets us with the following prompt:

integrity: f3ea30e1f4af98a5b805070ce0e8bff50749958bdfcc51047eae911508573f40395a219636daef20d913bb0548b7baef7487beaa035973dab78fbddce61a7e58
Enter password:
🔒

If this was a pwn-challenge, this would look like something where we have to trick the integrity check. Alas, this is reversing, so let's have a look at the provided earlybird file:

$ file earlybird
earlybird: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, not stripped

So, the provided file is a binary file. Since we are security professionals, the first thing we do, is run the application we just downloaded from the internet:

$ ./earlybird 
integrity: f3ea30e1f4af98a5b805070ce0e8bff50749958bdfcc51047eae911508573f40395a219636daef20d913bb0548b7baef7487beaa035973dab78fbddce61a7e58
Enter password:
🔒 foobar
💣 Nice try!

OK, so this is interesting. Looks like the server is running the same binary and we need to input some password. Either the binary is identical (same integrity tag), or the author is intentionally screwing with us. Let's assume the first option and start reversing!

We are going to use Ghidra for that. After loading the binary, and running a initial auto-analysis, we are presented with the disassembly listing, and the exported symbols in the "Exports"-view on the left. Ghidra automatically navigates us to the main()-function which is obviously the entry point of the application. We also get a decompiler listing, which is much easier to read than the raw assembly:


This looks pretty straight forward:

  • There is a init()-function, which does some weird iobuf-stuff (author's note: this makes the application run well with socat)
  • a checksum()-function, that prints the ominous checksum line that we saw in the netcat-output
  • a call to printf(), that prompts for the password, which is then read by fgets
  • the input is then trimmed with strcspn (very exotic :-))
  • and then compared with a string, that is first modified with the FUN_00401973 function. If they are equal, we get flag, else is prints the bomb icon.

Well, easy. We just have to figure out, what this FUN_00401973 does to the input string ysae_oot_yaw:


We can either analyze this manually, dynamically or by using a clever technique called FunctionID. Turns out, this ia a strrev-function, which just reverses the input string. Therefore, ysae_oot_yaw turns to way_too_easy - indeed, that was way too easy :-).

Let's use the password and collect the flag:

$ ./earlybird 
integrity: f3ea30e1f4af98a5b805070ce0e8bff50749958bdfcc51047eae911508573f40395a219636daef20d913bb0548b7baef7487beaa035973dab78fbddce61a7e58
Enter password:
🔒 way_too_easy
💣 Nice try!

😱

It didn't work. But why? We did everything correctly. We can even verify it in the debugger!


The strcmp-call is obviously correct and we even get the dummyflag. Wait, what? We didn't get the flag before. What is happening?

This is where the challenge gets interesting. It behaves differently, when we run it in the debugger. There are two options here: either you are familiar with anti-debugging techniques and spotted it immediately, or you should freshen up your tipps & tricks on malware reversing ;-)

The guide (Schallner, M. "Beginners guide to basic linux anti anti debugging techniques." Code Breakers Magazine 1 (2006)) mentions something about ptrace in chapter 4, "Detecting debugging". Let's test our theory, by tracing the syscalls:

$ strace -- ./earlybird
execve("./earlybird", ["./earlybird"], 0x7ffc8f2d6118 /* 41 vars */) = 0
[...]
ptrace(PTRACE_TRACEME)                  = -1 EPERM (Operation not permitted)
[...]


With our impressive deduction skills, we have finally caught the criminal. There is something fishy going on, where the application somehow detects the attached debugger and modifies it's behaviour. There are multiple options here:

  • either we try to figure out exactly, where the ptrace-syscall is and try to figure out our way from there
  • or we just patch away the ptrace and pray to the binary god.

For the sake of this writeup, let's go with the second option. Therefore,

Gentlemen, start your engines gdb.

$ gdb -q earlybird
> catch syscall ptrace
Catchpoint 1 (syscall 'ptrace' [101])
> run
Catchpoint 1 (call to syscall ptrace), 0x000000000045352e in ?? ()
> set $rax = 0
> b *(main+118)
Breakpoint 2 at 0x401a43
> continue
Continuing.

integrity: f3ea30e1f4af98a5b805070ce0e8bff50749958bdfcc51047eae911508573f40395a219636daef20d913bb0548b7baef7487beaa035973dab78fbddce61a7e58
Enter password:
🔒 foobar

Breakpoint 2, 0x0000000000401a43 in main ()


And suddenly, the password changed to crt0_trickzz? Validating this locally confirms that this is the password when running without the debugger.

Since we are very curious, we want to figure out, how this works. Luckily, gdb already gave us the return address for the ptrace-syscall, which is 0x45352e. Jumping to this address in Ghidra, we find a somewhat weird function that issues the syscall. This is the libc interface (ptrace) to the corresponding syscall, so we need to go deeper and get the calling functions. Ghidra presents those via the Xrefs, and indeed, following the first (and only) code Xref, we find a function at address 0x401755.

Ghidra presents us with the following decompiler listing:


The key takeaways here:

  • It first checkes the return value of the ptrace-stub, and only runs when there is no error, i.e. there was no debugger attached
  • It then "modifies" somehow (FUN_00425430, this is memfrob(3)) a data block starting at 0x4c70e0 + 0x2f20 = 0x4CA000.
  • Trying to check this function in Ghidra leaves us confused - it somehow disassembles, but it's complete nonsense. We can undo the memfrob in Ghidra by opening the Bytes-view and editing the mangled bytes.
  • After unfrobbing the function and instructing Ghidra to decompile at this address, we finally find the culprit function. The password (way_too_easy) gets patched by a code stub that is injected in-memory.
  • Applying the XOR-operations manually confirms the password crt0_trickzz.


Finally, we can use this alternative password to retrieve the flag from the server. The flag is nland{c47ch35_7h3_w02m}.

There is one question left: Why did we not see the ptrace call in the main disassembly? The answer to this is hinted in the modified password: the application uses a special trick, where it runs the modification code before even the main function, by hooking into the crt0 startup routine. This can also be confirmed by checking the Xrefs to the _entry and 0x401755-function. This is left as an exercise to the interested reader.