├── poc.gif
├── examples
├── simple.gif
└── complex.jpg
├── LICENSE
├── README.MD
├── .gitignore
└── stegextract
/poc.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evyatarmeged/stegextract/HEAD/poc.gif
--------------------------------------------------------------------------------
/examples/simple.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evyatarmeged/stegextract/HEAD/examples/simple.gif
--------------------------------------------------------------------------------
/examples/complex.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evyatarmeged/stegextract/HEAD/examples/complex.jpg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Evyatar Meged
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Stegextract
2 | Bash script to extract hidden files and strings from images.
3 |
4 | ## Description
5 | Stegextract extracts any trailing data after the image's closing bytes, and any hidden files (or other images) embedded within the image.
6 | Short byte combinations such as JPEG's `FFD8 FFE0` might sometimes create false positives.
7 | Manually reviewing the hexdump is sometimes inevitable in cases of highly complex embedded files.
8 | Stegextract is not the solution for any color/pixel/filter/LSB related Steganography, nor does it try to be. It
9 | relies on magic numbers, hexdumps and binary data alone.
10 | Currently supports PNG, JPG, and GIF.
11 |
12 | **Update**: `--analyze` flag was deprecated and is now being performed automatically with every scan.
13 |
14 | ## Installation
15 | ```
16 | sudo sh -c 'curl https://raw.githubusercontent.com/evyatarmeged/stegextract/master/stegextract > /usr/local/bin/stegextract'
17 | sudo chmod +x /usr/local/bin/stegextract
18 | ```
19 |
20 | ## Usage
21 | ```
22 | Usage: stegextract [options]
23 |
24 | -h, --help Print this and exit
25 | -o, --outfile Specify an outfile
26 | -s, --strings Extract strings from file
27 | -q, --quiet Do not output to stdout
28 | --force-format Force this image format instead of detecting
29 | ```
30 |
31 | ## POC:
32 | 
33 |
34 | Image examples from the above GIF can be found in the [examples](https://github.com/evyatarmeged/stegextract/tree/master/examples) folder.
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### VirtualEnv template
3 | # Virtualenv
4 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
5 | .Python
6 | [Bb]in
7 | [Ii]nclude
8 | [Ll]ib
9 | [Ll]ib64
10 | [Ll]ocal
11 | [Ss]cripts
12 | pyvenv.cfg
13 | .venv
14 | pip-selfcheck.json
15 | ### Python template
16 | # Byte-compiled / optimized / DLL files
17 | __pycache__/
18 | *.py[cod]
19 | *$py.class
20 |
21 | # C extensions
22 | *.so
23 |
24 | # Distribution / packaging
25 | build/
26 | develop-eggs/
27 | dist/
28 | downloads/
29 | eggs/
30 | .eggs/
31 | lib/
32 | lib64/
33 | parts/
34 | sdist/
35 | var/
36 | wheels/
37 | *.egg-info/
38 | .installed.cfg
39 | *.egg
40 | MANIFEST
41 |
42 | # PyInstaller
43 | # Usually these files are written by a python script from a template
44 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
45 | *.manifest
46 | *.spec
47 |
48 | # Installer logs
49 | pip-log.txt
50 | pip-delete-this-directory.txt
51 |
52 | # Unit test / coverage reports
53 | htmlcov/
54 | .tox/
55 | .coverage
56 | .coverage.*
57 | .cache
58 | nosetests.xml
59 | coverage.xml
60 | *.cover
61 | .hypothesis/
62 |
63 | # Translations
64 | *.mo
65 | *.pot
66 |
67 | # Django stuff:
68 | *.log
69 | .static_storage/
70 | .media/
71 | local_settings.py
72 |
73 | # Flask stuff:
74 | instance/
75 | .webassets-cache
76 |
77 | # Scrapy stuff:
78 | .scrapy
79 |
80 | # Sphinx documentation
81 | docs/_build/
82 |
83 | # PyBuilder
84 | target/
85 |
86 | # Jupyter Notebook
87 | .ipynb_checkpoints
88 |
89 | # pyenv
90 | .python-version
91 |
92 | # celery beat schedule file
93 | celerybeat-schedule
94 |
95 | # SageMath parsed files
96 | *.sage.py
97 |
98 | # Environments
99 | .env
100 | env/
101 | venv/
102 | ENV/
103 | env.bak/
104 | venv.bak/
105 |
106 | # Spyder project settings
107 | .spyderproject
108 | .spyproject
109 |
110 | # Rope project settings
111 | .ropeproject
112 |
113 | # mkdocs documentation
114 | /site
115 |
116 | # mypy
117 | .mypy_cache/
118 | .idea/
119 | xtrx.py
--------------------------------------------------------------------------------
/stegextract:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | command -v xxd >/dev/null 2>&1 || {
4 | echo "This program requires command 'xxd' which is not installed. Please install it before running this program.";
5 | exit 1;
6 | }
7 | command -v grep >/dev/null 2>&1 || {
8 | echo "This program requires command 'grep' which is not installed. Please install it before running this program.";
9 | exit 1;
10 | }
11 |
12 | exec 6>&1
13 |
14 | if [ $# -eq 0 ]; then
15 | echo "Usage: stegextract [options]"
16 | echo "stegextract -h for help"
17 | exit 0
18 | fi
19 |
20 | while (( "$#" )); do
21 | case "$1" in
22 | -h|--help)
23 | echo "Extract hidden data from images"
24 | echo " "
25 | echo "Usage: stegextract [options]"
26 | echo "-h, --help Print this and exit"
27 | echo "-o, --outfile Specify an outfile"
28 | echo "-s, --strings Extract strings from file"
29 | echo "-q, --quiet Do not output to stdout"
30 | echo "--force-format Force this image format instead of detecting"
31 | exit 0
32 | ;;
33 | "-o"|"--outfile")
34 | outfile="$2"
35 | shift 2
36 | ;;
37 | "--force-format")
38 | ext="$2"
39 | shift 2
40 | ;;
41 | "-s"|"--strings")
42 | get_strings="true"
43 | shift 1
44 | ;;
45 | "-q"|"--quiet")
46 | exec > /dev/null
47 | shift 1
48 | ;;
49 | --) # end argument parsing
50 | shift
51 | break
52 | ;;
53 | -*|--*=) # unsupported flags
54 | echo "Error: Unsupported flag $1" >&2
55 | exit 1
56 | ;;
57 | *) # preserve positional arguments
58 | image="$1"
59 | stripped=${image%.*}
60 | shift
61 | ;;
62 | esac
63 | done
64 |
65 | file_type=""
66 |
67 | readonly jpg_start="ffd8"
68 | readonly jpg_end="ff d9"
69 | readonly png_start="8950 4e47 0d0a 1a0a"
70 | readonly png_end="49 45 4e 44 ae 42 60 82"
71 | readonly gif_start="4749 4638 3961"
72 | readonly gif_end="00 3b"
73 |
74 | if [ ! -f "$image" ]; then
75 | echo "$0: File $image not found."
76 | exit 1
77 | fi
78 |
79 | if [ -z ${outfile+x} ]; then
80 | outfile=$stripped"_dumps";
81 | elif [ -f "$outfile" ]; then
82 | read -p "$outfile exists, overwrite ? (y/n) " choice
83 | case "$choice" in
84 | y|Y ) :;;
85 | n|N ) echo "Exiting"; exit 1;;
86 | * ) echo "Invalid option"; exit 1;;
87 | esac
88 | fi
89 |
90 | extract_trailing() {
91 | curr=${@:1}
92 | xxd -c1 -p $image | tr "\n" " " | sed -n -e "s/.*\( $curr \)\(.*\).*/\2/p" | xxd -r -p > $outfile
93 | }
94 |
95 | jpeg() {
96 | file_type="jpg"
97 | # Grab everything after 0xFF 0xD9
98 | echo "Detected image format: JPG"
99 | extract_trailing "$jpg_end"
100 | }
101 |
102 | png() {
103 | file_type="png"
104 | # Grab everything after "IEND.B`" chunk
105 | echo "Detected image format: PNG"
106 | extract_trailing "$png_end"
107 | }
108 |
109 | gif() {
110 | file_type="gif"
111 | # Grab everything after "0x00 0x3B"
112 | echo "Detected image format: GIF"
113 | extract_trailing "$gif_end"
114 | }
115 |
116 | extract_embedded() {
117 | local curr_ext="$1"; shift
118 | magic="$@"
119 |
120 | magic_no_ws=$(echo -e "$magic" | tr -d '[:space:]')
121 | located=$(xxd -ps -c1000 $image | grep $magic_no_ws)
122 | if [[ $located ]]; then
123 | upper_ext=$(echo "$curr_ext" | tr /a-z/ /A-Z/)
124 | echo "Found embedded: $upper_ext"
125 | echo "$magic" | xxd -r -p > "$stripped"."$curr_ext"
126 | xxd -c1 -p "$image" | tr "\n" " " | sed -n -e "s/.*\( $magic \)\(.*\).*/\2/p" | xxd -r -p >> $stripped.$curr_ext
127 | fi
128 | }
129 |
130 | analysis() {
131 | # Did not include zlib, bzip because of a high false positive percentage
132 | extensions=("png" "jpg" "gif" "zip" "rar" "7z")
133 | for i in "${extensions[@]}"; do
134 | # Look for magic numbers in file except for the already detected image type
135 | if [ "$i" != "$file_type" ]; then
136 | case "$i" in
137 | "png")
138 | extract_embedded "png" "89 50 4e 47 0d 0a 1a 0a"
139 | ;;
140 | "jpg")
141 | extract_embedded "jpg" "ff d8 ff e0"
142 | ;;
143 | "gif")
144 | extract_embedded "gif" "47 49 46 38 39 61"
145 | ;;
146 | "zip")
147 | extract_embedded "zip" "50 3b 03 04"
148 | ;;
149 | "rar")
150 | extract_embedded "rar" "52 61 72 21 1a 07"
151 | ;;
152 | "7z")
153 | extract_embedded "7z" "37 7a bc af 27 1c"
154 | ;;
155 | esac
156 | fi
157 | done
158 | }
159 |
160 | if [[ ! -z ${ext+x} ]]; then
161 | case ${ext,,} in
162 | # Lazy format detection
163 | "jpg"|"jpeg")
164 | jpeg
165 | ;;
166 | "png")
167 | png
168 | ;;
169 | "gif")
170 | gif
171 | ;;
172 | *)
173 | echo "Unsupported image format"
174 | exit 1
175 | ;;
176 | esac
177 | else
178 | # Look for SOI bytes in xxd output to detect image type
179 | head_hexdump=$(xxd $image 2> /dev/null | head)
180 | if [[ $(grep "$png_start" <<< $head_hexdump) ]]; then
181 | png
182 | elif [[ $(grep "$jpg_start" <<< $head_hexdump) ]]; then
183 | jpeg
184 | elif [[ $(grep "$gif_start" <<< $head_hexdump) ]]; then
185 | gif
186 | else
187 | echo "Unknown or unsupported image format"
188 | exit 1
189 | fi
190 | fi
191 |
192 | data=$(file "$outfile")
193 | data=${data##*:}
194 | result=$(echo $data | head -n1 | sed -e 's/\s.*$//')
195 | if [ $result = "empty" ]; then
196 | echo "No trailing data found in file"
197 | rm "$outfile"
198 | elif [ "$result" = "data" ]; then
199 | echo "Extracted trailing file data: binary data, might contain embedded files."
200 | else
201 | echo "Extracted trailing file data: $data"
202 | fi
203 |
204 |
205 | if [[ "$get_strings" ]]; then
206 | echo "Extracting strings..."
207 | strings -6 "$image" > "$stripped".txt
208 | fi
209 |
210 | echo "Performing deep analysis"
211 | analysis
212 | echo "Done"
213 |
214 | exec 1>&6 6>&-
215 |
--------------------------------------------------------------------------------