VLC of the Mariner

Challenge statement:

A long long time ago, a ship was driven by storms to the cold country towards the South Pole. From there, strange things happened and the men of the ship were never seen again. What happened to these people? To find out, solve the mystery of the VLC mariner.


Downloading and extracting that zip file shows us the source code for VLC. My first approach is to try to find the version and download the real source code, in case we can see what is different.

We can find the version in the configure file:

# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for vlc 3.0.12.
# Copyright 1999-2020 VLC authors and VideoLAN

We can now download the real VLC 3.0.12 and compare it with diff -r vlc-3.0.12 vlc-mariner-main.

diff -r vlc-3.0.12/bin/vlc.c vlc-mariner-main/bin/vlc.c
> static void look_at_the_main_method() 
> {
> }
>     /* Is this the flag? Almost but not quite.
>      * Four times fifty, but their count is 0.
>      * Divided in their rank, none of them a hero.
>      * Let’s leave the riddles here, find a divide by zero.  */
diff -r vlc-3.0.12/modules/access/dshow/filter.cpp vlc-mariner-main/modules/access/dshow/filter.cpp
> int CapturingPin() {
>     // A cryptic message was sent to the submariner over a secure line:
>     //
>     // /--- BEGIN TRANSMISSION----|
>     // 
>     // Captain, please input the following key in the special tool we referenced in SECURITY 
>     // documents:
>     //
>     //     cpg.call.name("malloc").where(_.ar             .....t12ry3tyru@%#$^$ghgjj        
>     //
>     // RECONNECTING...
>     // 3...2...1
>     // 
>     // Captain, listen to me! 
>     // Memory allocations with arithmetic operations are dangerous... 
>     // Please find a memory allocation with multiplication in our sinking ship!
>     return 0;
> }
> }
> int CapturedPin() {
>     int zero = 200;
>     int two_hundred = 0;
>     if (zero == 0) {
>         return two_hundred/zero;
>     } else {
>         if (zero != 0) {
>             return zero/two_hundred;
>         } else {
>             return CapturingPin();
>         }
>     }
diff -r vlc-3.0.12/src/misc/update_crypto.c vlc-mariner-main/src/misc/update_crypto.c
<                 p_key->psz_username = (uint8_t*)malloc( i_packet_len + 1);
>                       int HERE_LIE_THE_200 = 1
>                 // |[]      SUBMIT:       |[]
>                 // | I shot the albatross |
>                 p_key->psz_username = (uint8_t*)malloc( i_packet_len * HERE_LIE_THE_200_MEN);
diff -r vlc-3.0.12/src/network/httpd.c vlc-mariner-main/src/network/httpd.c
> #include "networking.h"
> }
> void begin_quote (char **body, int code, const char *url)
> {
>     const char *errname = httpd_ReasonFromCode (code);
>     assert (errname);
>     int res = 1;
>     if (res == -1) {
>         *body = NULL;
>         i(code);
>     }
diff -r vlc-3.0.12/src/network/io.c vlc-mariner-main/src/network/io.c
> #include "networking.h"
> void end_quote (int code)
> {
>     int 🚩;
>     int have_no_meaning = 1;
>     🚩 = have_no_meaning;
> }
> void albatross (int code)
> {
>     int ation = code;
>     struct addrinfo hints = {
>         .ai_socktype = type,
>         .ai_protocol = protocol,
>         .ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_IDN,
>     }, *res;
>     msg_Dbg (p_this, "net: listening to %s port %d",
>              (psz_host != NULL) ? psz_host : "*", i_port);
>     int i_val = vlc_getaddrinfo (psz_host, i_port, &hints, &res);
>     if (i_val)
>     {
>         end_quote(ation);
>     }
> }
diff -r vlc-3.0.12/src/network/rootbind.c vlc-mariner-main/src/network/rootbind.c
> #include "networking.h"
> }
> void the (int code)
> {
>     int pew = code;
>     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
>     struct sockaddr_storage ss;
>     int fd, sock = -1;
>     const char *sockenv = getenv ("VLC_ROOTWRAP_SOCK");
>     if (sockenv != NULL)
>         sock = atoi (sockenv);
>     albatross(pew * 2);
diff -r vlc-3.0.12/src/network/tcp.c vlc-mariner-main/src/network/tcp.c
> #include "networking.h"
> void shot (int code) 
> {
>     int thing = code;
>     int *pi_fd;
>     unsigned n = 0;
>     while (pi_fd[n] != -1)
>         n++;
>     struct pollfd ufd[n];
>     /* Initialize file descriptor set */
>     for (unsigned i = 0; i < n; i++)
>     {
>         ufd[i].fd = pi_fd[i];
>         ufd[i].events = POLLIN;
>     }
>     the(thing);
> }
diff -r vlc-3.0.12/src/network/udp.c vlc-mariner-main/src/network/udp.c
> #include "networking.h"
> void i( int code )
> {
>     int gun = code;
>     char *ifname = var_InheritString (obj, "miface");
>     if (ifname == NULL)
>         shot(gun);
> }
diff -r vlc-3.0.12/src/video_output/display.c vlc-mariner-main/src/video_output/display.c
<         msg_Dbg(vd, "VoutDisplayEvent 'double click'");
>         msg_Dbg(vd, "Look at the main method ⚓");

There is also two interesting files. One is src/network/networking.h which contains the following:

void begin_quote (char, int, const char*);
void i(int);
void shot(int);
void the(int);
void albatross(int);
void end_quote(int);

(Spoiler, that is the flag)

And the second is refered to in the mariner version of filter.cpp: security.txt

Static (adj.)
Lacking in movement, action, or change, especially in a way viewed as undesirable or uninteresting.

You’ve probably reviewed your own code at some point in your life. 

Performing a source code review is one of the best ways to find security issues in applications. Looking at source code to identify vulnerabilities without running the program is also called "static code analysis".

There are many ways you can start reviewing your code for security bugs. But in general, it’s good to start by learning what the most common vulnerabilities and security pitfalls are for the type of application you are building. Then, get familiar with the indicators and signatures of those vulnerabilities so that you can identify similar patterns in source code. As an example, the signature for an XXE vulnerability is passing user-supplied XML to a parser without disabling DTDs or external entities. 

During a code review session, you typically look for bad security patterns, universal issues like input validation and injection issues, and low-level coding flaws like arithmetic issues and memory access bugs. You can also search for strings, keywords, and code patterns known to be indicators for vulnerabilities or misconfiguration: hardcoded API keys, encryption keys, and database passwords, outdated dependencies versions, methods known to be unsafe, etc. For more information about conducting code reviews, visit this guide: https://owasp.org/www-pdf-archive/OWASP_Code_Review_Guide_v2.pdf.

Automated static code analysis techniques are often driven by compiler technologies. One of these methods is data-flow analysis. Data flow analysis studies how data propagate in a program. For instance, you can track sensitive information or user-controlled input and how they interact with parts of the program. Think of data as water: flowing from the source of the water (the tap) to the sink. A "source" is the code that allows a vulnerability to happen, whereas a "sink" is where the vulnerability actually happens. Take command injection vulnerabilities, for example. A "source" in this case could be a function that takes in user input. Whereas the "sink" would be functions that execute system commands. If the untrusted user input can get from "source" to "sink" without proper sanitization or validation, there is a command injection vulnerability. Many common vulnerabilities can be identified by tracking this "data flow" from appropriate sources to corresponding sinks. The ancient mariners have long told a tale, of a tool named Joern that can hunt down code quality issues made from hell. Remember this phrase as you near the end of the game, these queries sometimes look like a chain. But first, to learn the craft of the ancient mariner, you must first find a string with an anchor ⚓. 

As you can probably tell, this process is super dependent on the programming language in use, etc. On the other hand, dynamic analyses tools act more like robo pentesters. They paste "<script>alert()</script>" into form fields is what I’ve heard.

So if we put the pieces back together, it’s a static code analysis challenge. Looking at the Joern tool online, and googling with the part of the query that is given to us, we can find the full query to be the following: cpg.call("malloc").where(_.argument(1).arithmetics), which confirms the suspicious message Memory allocations with arithmetic operations are dangerous... Please find a memory allocation with multiplication in our sinking ship!. We can find one such occurence in update_crypto.c where the code was changed to p_key->psz_username = (uint8_t*)malloc( i_packet_len * HERE_LIE_THE_200_MEN);. Right above it was a comment saying: SUBMIT: I shot the albatross, which was the flag. Therefore, all functions chaining together, starting with begin_quote() going to i(), shot(), the(), albatross() and ending with end_quote() was another way to find the flag.