Tag Archives: web

SpyderSec: Challenge

I can’t believe it’s been nine months since my last post. I must have been busy. Or lazy.

Just as well I recently completed SpyderSec‘s nice little X-Files themed encryption challenge to get me out of my blog funk.

This was a really fun one, and totally worth a look if you’re interested in video steganography. This is a walkthrough to how I completed the challenge, so if you fancy trying yourself look away now.

Flag #1

The challenge spec already makes clear that this is a web application challenge and a full nmap TCP scan confirmed that only port 80 was open. The web page served doesn’t reveal too much:

Website front page

Just in case an administrator left some content or administration pages open, I scanned the host with dirb.

root@worry64:~/work/spyder# dirb http://192.168.57.8/

-----------------
DIRB v2.22
By The Dark Raver
-----------------

START_TIME: Tue Oct6 19:57:56 2015
URL_BASE: http://192.168.57.8/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612

---- Scanning URL: http://192.168.57.8/ ----
+ http://192.168.57.8/favicon.ico (CODE:200|SIZE:1150)
+ http://192.168.57.8/index.php (CODE:200|SIZE:8883)
==> DIRECTORY: http://192.168.57.8/v/

---- Entering directory: http://192.168.57.8/v/ ----

-----------------
END_TIME: Tue Oct6 19:58:48 2015
DOWNLOADED: 9224 - FOUND: 2

Search turns up a /v/ directory, but this is Forbidden.

Looking at the html source of the page reveals some packed JavaScript:

eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c])}}return p}('7:0:1:2:8:6:3:5:4:0:a:1:2:d:c:b:f:3:9:e',16,16,'6c|65|72|27|75|6d|28|61|74|29|64|62|66|2e|3b|69'.split('|'),0,{}))

You can dump this packed program into one of a few public JavaScript unpackers, but I wanted to see if it would evaluate fine in nodejs, which it did:

root@worry64:~# js
> function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c])}}return p}('7:0:1:2:8:6:3:5:4:0:a:1:2:d:c:b:f:3:9:e',16,16,'6c|65|72|27|75|6d|28|61|74|29|64|62|66|2e|3b|69'.split('|'),0,{})
'61:6c:65:72:74:28:27:6d:75:6c:64:65:72:2e:66:62:69:27:29:3b'

The hex values appeared to be ASCII characters, so I tried converting to a string and got some interesting output:

root@worry64:~# echo 61:6c:65:72:74:28:27:6d:75:6c:64:65:72:2e:66:62:69:27:29:3b | sed 's/://g' | xxd -r -ps -
alert('mulder.fbi');

I wasn’t sure what to make of “muder.fbi” at this point. Plugging it into the dirb search didn’t turn up any hidden content.

Plugging away at the page for a while, I turned to look at the logs of the requests I had made in BurpSuite. I noticed that the page was including a cookie and that it mentioned a URI:

HTTP/1.1 200 OK
Date: Tue, 06 Oct 2015 08:52:02 GMT
Server: Apache
Set-Cookie: URI=%2Fv%2F81JHPbvyEQ8729161jd6aKQ0N4%2F; expires=Wed, 07-Oct-2015 08:52:02 GMT; path=/; httponly
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 8883

I already knew of the /v/ directory, so it stood that there was a good chance I would find something interesting if I accessed this location directly. However, I found disappointment in the fact that the URI was also a forbidden location:

HTTP/1.1 403 Forbidden
Date: Tue, 06 Oct 2015 08:56:22 GMT
Server: Apache
Content-Length: 293
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /v/81JHPbvyEQ8729161jd6aKQ0N4/
on this server.</p>
<hr>
<address>Apache Server at 192.168.57.8 Port 80</address>
</body></html>

The challenge was beginning to feel very much like a hidden data exercise. I tried plugging various strings I had captured into the web server to see if I could find something. I can’t remember how long it took, but eventually putting the URI together with ‘mulder.fbi’ caused the web server to offer me a 13MB file:

root@worry64:/tmp# wget http://192.168.57.8/v/81JHPbvyEQ8729161jd6aKQ0N4/mulder.fbi
--2015-11-09 23:16:52--  http://192.168.57.8/v/81JHPbvyEQ8729161jd6aKQ0N4/mulder.fbi
Connecting to 192.168.57.8:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13960421 (13M) [text/plain]
Saving to: ‘mulder.fbi’

mulder.fbi                                           100%[=======================================================================================================================>]  13.31M  12.5MB/s   in 1.1s

2015-11-09 23:16:53 (12.5 MB/s) - ‘mulder.fbi’ saved [13960421/13960421]

A cursory examination of the file showed it as a video file.

root@worry64:~/work/spyder# file mulder.fbi
mulder.fbi: ISO Media, MP4 v2 [ISO 14496-14]

Flag #2: Kill Switch

The video turned out to be a song with lyrics slides. (The song incidentally is a reference to an episode of The X-Files – I had to look that up.)

At this point I figured that there was something hidden in the video file. Hidden data – particularly where video is concerned – is really not my strong suit. I searched the file for strings (which didn’t help) and for container metadata (didn’t find any). On a hunch I decided to run the file through a video processor. I asked the program to make a direct stream copy of the audio and video, meaning that the whole content would be copied without recompression into a new file. If there was any hidden data in the file, it might reveal a difference in file size.

root@worry64:~/work/spyder# ls -la mulder.fbi
-rw-r--r-- 1 root root 13960421 Oct  6 20:02 mulder.fbi
me@me:~/Desktop$ ffmpeg -i mulder.fbi -codec copy out.mp4
ffmpeg version 2.5.8-0ubuntu0.15.04.1 Copyright (c) 2000-2015 the FFmpeg developers
  built with gcc 4.9.2 (Ubuntu 4.9.2-10ubuntu13)
  configuration: --prefix=/usr --extra-version=0ubuntu0.15.04.1 --build-suffix=-ffmpeg --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --shlibdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --enable-shared --disable-stripping --enable-avresample --enable-avisynth --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-libschroedinger --enable-libshine --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libwavpack --enable-libwebp --enable-libxvid --enable-opengl --enable-x11grab --enable-libdc1394 --enable-libiec61883 --enable-libzvbi --enable-libzmq --enable-frei0r --enable-libvpx --enable-libx264 --enable-libsoxr --enable-gnutls --enable-openal --enable-libopencv --enable-librtmp --enable-libx265
  libavutil      54. 15.100 / 54. 15.100
  libavcodec     56. 13.100 / 56. 13.100
  libavformat    56. 15.102 / 56. 15.102
  libavdevice    56.  3.100 / 56.  3.100
  libavfilter     5.  2.103 /  5.  2.103
  libavresample   2.  1.  0 /  2.  1.  0
  libswscale      3.  1.101 /  3.  1.101
  libswresample   1.  1.100 /  1.  1.100
  libpostproc    53.  3.100 / 53.  3.100
[h264 @ 0x1c6da40] AVC: nal size -1892486350
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 255724199
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 1346868359
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -1492585362
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -550881863
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -922972230
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 1259521664
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 2031147485
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -477496776
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -235462333
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 275654111
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -1078662316
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 1744092848
[h264 @ 0x1c6da40] no frame!
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'mulder.fbi':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2013-12-08 02:32:33
  Duration: 00:02:47.53, start: 0.000000, bitrate: 666 kb/s
    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 640x360, 223 kb/s, 25 fps, 25 tbr, 50 tbn, 50 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 95 kb/s (default)
    Metadata:
      creation_time   : 2013-12-08 02:32:33
      handler_name    : IsoMedia File Produced by Google, 5-11-2011
Output #0, mp4, to 'out.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    encoder         : Lavf56.15.102
    Stream #0:0(und)ice: Video: h264 ([33][0][0][0] / 0x0021), yuv420p, 640x360, q=2-31, 223 kb/s, 25 fps, 12800 tbn, 50 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac ([64][0][0][0] / 0x0040), 44100 Hz, stereo, 95 kb/s (default)
    Metadata:
      creation_time   : 2013-12-08 02:32:33
      handler_name    : IsoMedia File Produced by Google, 5-11-2011
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame= 4188 fps=0.0 q=-1.0 Lsize=    6648kB time=00:02:47.53 bitrate= 325.1kbits/s
video:4578kB audio:1963kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.621213%

me@me:~/Desktop$ ls -l out.mp4
-rw-rw-r-- 1 me me 6807163 Oct  6 20:57 out.mp4

Several megabytes of data were missing from the stream copy, but the file played exactly the same; a strong indication that there was data to be found in the file. Since the data was not part of the video and audio streams, I felt I had a chance with this. (If the data had been hidden as data in the streams themselves then my chances of success would have been much lower.)

Still, I don’t really know much about video container formats, so I took the least resistance path and did some web searches first of all. Actually, doing a search for “hide data in mp4 video” turns up some pretty interesting stuff immediately. (That link is seriously worth a read if you’re interested.)

Working on the hunch that the video file contains a TrueCrypt encrypted volume, the challenge essentially becomes “find the correct password”. And the thing about TrueCrypt volumes is that they’re practically indiscernible from random data. All you can do is keep trying passwords until one works and even if you never find one that works, you will never know whether the data is actually random or you’ve just not found the correct password.

Anyway, you probably know where this is going. Eventually (too long) I found the password. I can’t remember exactly when I turned my attention to the website images (I had already gone over the BurpSuite logs several times to no avail, and was considering writing or searching for a tool to automate TrueCrypt password attempts) when I dumped the webpage content and started looking for strings.

The password was hidden in the metadata in the following file:

SpyderSec Challenge.png

root@worry64:~/work/spydersec/SpyderSec | Challenge_files# exiftool Challenge.png
ExifTool Version Number         : 9.74
File Name                       : Challenge.png
Directory                       : .
File Size                       : 83 kB
File Modification Date/Time     : 2015:10:06 21:17:08+01:00
File Access Date/Time           : 2015:11:09 23:44:54+00:00
File Inode Change Date/Time     : 2015:10:06 21:17:08+01:00
File Permissions                : rw-r--r--
File Type                       : PNG
MIME Type                       : image/png
Image Width                     : 540
Image Height                    : 540
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Background Color                : 255 255 255
Pixels Per Unit X               : 2835
Pixels Per Unit Y               : 2835
Pixel Units                     : meters
Comment                         : 35:31:3a:35:33:3a:34:36:3a:35:37:3a:36:34:3a:35:38:3a:33:35:3a:37:31:3a:36:34:3a:34:35:3a:36:37:3a:36:61:3a:34:65:3a:37:61:3a:34:39:3a:33:35:3a:36:33:3a:33:30:3a:37:38:3a:34:32:3a:34:66:3a:33:32:3a:36:37:3a:33:30:3a:34:61:3a:35:31:3a:33:64:3a:33:64
Image Size                      : 540x540
root@worry64:~/work/spydersec/SpyderSec | Challenge_files# echo 35:31:3a:35:33:3a:34:36:3a:35:37:3a:36:34:3a:35:38:3a:33:35:3a:37:31:3a:36:34:3a:34:35:3a:36:37:3a:36:61:3a:34:65:3a:37:61:3a:34:39:3a:33:35:3a:36:33:3a:33:30:3a:37:38:3a:34:32:3a:34:66:3a:33:32:3a:36:37:3a:33:30:3a:34:61:3a:35:31:3a:33:64:3a:33:64 | sed 's/://g' | xxd -r -ps -
51:53:46:57:64:58:35:71:64:45:67:6a:4e:7a:49:35:63:30:78:42:4f:32:67:30:4a:51:3d:3d
root@worry64:~/work/spydersec/SpyderSec | Challenge_files# echo 51:53:46:57:64:58:35:71:64:45:67:6a:4e:7a:49:35:63:30:78:42:4f:32:67:30:4a:51:3d:3d | sed 's/://g' | xxd -r -ps -
QSFWdX5qdEgjNzI5c0xBO2g0JQ==
root@worry64:~/work/spydersec/SpyderSec | Challenge_files# echo QSFWdX5qdEgjNzI5c0xBO2g0JQ== | base64 -d
A!Vu~jtH#729sLA;h4%
root@worry64:~/work/spydersec/SpyderSec | Challenge_files#

And there we have it. The TrueCrypt password is A!Vu~jtH#729sLA;h4%. After that, getting to the final flag is a breeze…

SpyderSec TyueCrypt password

SpyderSec TrueCrypt volume mounted successfully

SpyderSec challenge completed