├── .gitignore ├── README.md └── imgect.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | *.pyc 3 | *.gif 4 | *.bmp 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imgect: BMP/GIF Shellcode Injector 2 | 3 | `imgect` will take shellcode (an explicit string or a payload file) and inject it into a valid BMP/GIF image. It will append the shellcode as secondary data to the end of the image which can then be extracted. 4 | 5 | This tool can embed the shellcode into an existing GIF/BMP image file or create a new, valid GIF/BMP and embed the shellcode into it. 6 | 7 | The injected shellcode can be extracted from the image by collecting all data between the following points: 8 | ``` 9 | 2f 2f 2f 2f 2f <-- ... --> EOF-1 10 | 11 | * The default delimiter is five (5) `/` that is used as a break point between the end 12 | of the image data and the beginnning of the shellcode. 13 | * EOF-1 accounts for a final `;` being written to the end of the file. 14 | ``` 15 | 16 | During execution, imgect will output the offset of the payload within the file. 17 | 18 | This tool is essentially a Python port of the following work: 19 | * https://github.com/chinarulezzz/pixload/blob/master/bmp.pl 20 | * https://github.com/chinarulezzz/pixload/blob/master/gif.pl 21 | 22 | This tool is also heavily based on the following work: 23 | * https://github.com/Urinx/SomeCodes/blob/master/Python/others/bmp-js-injector.py 24 | * https://github.com/jhaddix/scripts/blob/master/gif_header_xss.py 25 | 26 | The reason for this tool is to provide another quick Python solution to inject shellcode into a BMP/GIF image without corrupting it that will also allow basic encoding of the data being injected. 27 | 28 | ## Usage 29 | 30 | ``` 31 | usage: imgect.py [-h] (-g | -b) (-s SHELLCODE | -f FILE) [--encode] [-k KEY] 32 | [-o OUTPUT] [--debug] 33 | 34 | BMP/GIF Shellcode Injector -- v1.0 35 | 36 | optional arguments: 37 | -h, --help show this help message and exit 38 | 39 | -g, --gif Inject into GIF image file 40 | 41 | -b, --bmp Inject into BMP image file 42 | 43 | -s SHELLCODE, --shellcode SHELLCODE 44 | Explicit shellcode to inject into the image 45 | 46 | -f FILE, --file FILE Payload file containing shellcode 47 | 48 | --encode Encode shellcode before injection 49 | 50 | -k KEY, --key KEY Key to perform XOR encoding with 51 | 52 | -o OUTPUT, --output OUTPUT 53 | Output image file (Default: payload.{gif|bmp}) 54 | 55 | --debug Enable debug output 56 | ``` 57 | 58 | ### Generating a GIF 59 | 60 | Pass a payload file: 61 | 62 | ```sh 63 | python3 imgect.py --gif --file shellcode.bin --output image.gif 64 | ``` 65 | 66 | Pass an excplicit payload string: 67 | 68 | ```sh 69 | python3 imgect.py --gif --shellcode 'Shellcode goes here' --output image.gif 70 | ``` 71 | 72 | ### Generating a BMP 73 | 74 | Pass a payload file: 75 | 76 | ```sh 77 | python3 imgect.py --bmp --file shellcode.bin --output image.bmp 78 | ``` 79 | 80 | Pass an excplicit payload string: 81 | 82 | ```sh 83 | python3 imgect.py --bmp --shellcode 'Shellcode goes here' --output image.bmp 84 | ``` 85 | 86 | ### Examples 87 | 88 | Direct injection of shellcode without encoding: 89 | 90 | ``` 91 | $ python3 imgect.py --gif --shellcode 'Shellcode goes here' --output image.gif --debug 92 | 93 | *** BMP/GIF Shellcode Injector *** 94 | v1.0 95 | 96 | [>] Storing shellcode in memory 97 | [*] Shellcode contents size: 19 bytes 98 | [>] SHA256 Hash of original payload: c118268405bf9f3c2643081821d8aac4afa395ee32c3b3ef327df206d8f8863e 99 | [>] Getting image data 100 | [*] Original image contents size: 26 bytes 101 | [>] Injecting shellcode into image 102 | [+] Payload was injected successfully 103 | [*] Malicious image contents size: 51 bytes 104 | 105 | [+] Index of payload: 31 106 | 107 | 00000000: 47 49 46 38 39 61 0a 00 0a 00 00 ff 00 2c 00 00 |GIF89a.......,..| 108 | 00000010: 00 00 0a 00 0a 00 00 02 00 3b 2f 2f 2f 2f 2f 53 |.........;/////S| 109 | 00000020: 68 65 6c 6c 63 6f 64 65 20 67 6f 65 73 20 68 65 |hellcode goes he| 110 | 00000030: 72 65 3b |re;| 111 | ``` 112 | 113 | Inject encoded shellcode using a user-specified key: 114 | 115 | ``` 116 | $ python3 imgect.py --gif --shellcode 'Shellcode goes here' --output image.gif --encode \ 117 | --key 'secret password' --debug 118 | 119 | *** BMP/GIF Shellcode Injector *** 120 | v1.0 121 | 122 | [>] Storing shellcode in memory 123 | [*] Shellcode contents size: 19 bytes 124 | [>] SHA256 Hash of original payload: c118268405bf9f3c2643081821d8aac4afa395ee32c3b3ef327df206d8f8863e 125 | [>] Base64 encoded key: c2VjcmV0IHBhc3N3b3Jk 126 | [>] Writing Base64 encoded XOR key to file: image.key 127 | [>] Encoding payload 128 | [>] Getting image data 129 | [*] Original image contents size: 26 bytes 130 | [>] Injecting shellcode into image 131 | [+] Payload was injected successfully 132 | [*] Malicious image contents size: 51 bytes 133 | 134 | [+] Index of payload: 31 135 | 136 | 00000000: 47 49 46 38 39 61 0a 00 0a 00 00 ff 00 2c 00 00 |GIF89a.......,..| 137 | 00000010: 00 00 0a 00 0a 00 00 02 00 3b 2f 2f 2f 2f 2f 20 |.........;///// | 138 | 00000020: 0d 06 1e 09 17 4f 14 04 53 14 18 0a 01 44 1b 00 |.....O..S....D..| 139 | 00000030: 11 17 3b |..;| 140 | ``` 141 | -------------------------------------------------------------------------------- /imgect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | References: https://github.com/chinarulezzz/pixload/blob/master/bmp.pl 5 | https://github.com/Urinx/SomeCodes/blob/master/Python/others/bmp-js-injector.py 6 | https://github.com/jhaddix/scripts/blob/master/gif_header_xss.py 7 | """ 8 | 9 | import os 10 | import re 11 | import sys 12 | import string 13 | import random 14 | import base64 15 | import hashlib 16 | from argparse import ArgumentParser 17 | from itertools import cycle 18 | 19 | 20 | __version__ = '1.0' 21 | 22 | 23 | # == Helper Functions == # 24 | 25 | def xor_crypt(data, key): 26 | ''' XOR encode data passed in with a specified key ''' 27 | return bytes([d^k for d,k in zip(data, cycle(key))]) 28 | 29 | def prompt(question): 30 | ''' Prompt the user with a y/n question ''' 31 | reply = str(input(question + ' [Y/n]: ') or "Y").lower().strip() 32 | 33 | # Default to 'Yes' 34 | if reply[0] == 'y' or reply == '': 35 | return True 36 | 37 | elif reply[0] == 'n': 38 | return False 39 | 40 | else: 41 | return prompt("Please enter") 42 | 43 | def hexdump(src, length=16, sep='.'): 44 | ''' Hexdump - taken from https://gist.github.com/7h3rAm/5603718 ''' 45 | # Build a list of printable characters, otherwise set as '.' 46 | FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or sep for x in range(256)]) 47 | 48 | # Iterate over the source data 49 | lines = [] 50 | for c in range(0, len(src), length): 51 | # Get slice from source data - 16-bytes at a time 52 | chars = src[c:c+length] 53 | 54 | # Convert the 16 byte chunk to a hex string 55 | hexstr = ' '.join(["%02x" % ord(x) for x in chars]) if type(chars) is str else ' '.join(['{:02x}'.format(x) for x in chars]) 56 | 57 | if len(hexstr) > 24: 58 | hexstr = "%s %s" % (hexstr[:24], hexstr[24:]) 59 | 60 | printable = ''.join(["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or sep) for x in chars]) if type(chars) is str else ''.join(['{}'.format((x <= 127 and FILTER[x]) or sep) for x in chars]) 61 | lines.append("%08x: %-*s |%s|" % (c, length*3, hexstr, printable)) 62 | 63 | return '\n'.join(lines) 64 | 65 | 66 | # == Image Functions == # 67 | 68 | def gif_header_data(): 69 | ''' Minimal GIF image data ''' 70 | # GIF structure uses file terminator characters which allows 71 | # us to pack our shellcode in after the GIF file termination 72 | # without corrupting the image 73 | 74 | # Little-Endian 75 | # GIF Header (13 bytes) 76 | header = b'\x47\x49\x46\x38\x39\x61' # Signature and version (GIF89a) 77 | header += b'\x0a\x00' # Logical Screen Width (10 pixels) 78 | header += b'\x0a\x00' # Logical Screen Height (10 pixels) 79 | header += b'\x00' # GCTF 80 | header += b'\xff' # Background Color (#255) 81 | header += b'\x00' # Pixel Aspect Ratio 82 | 83 | # Global Color Table + Blocks (13 bytes) 84 | header += b'\x2c' # Image Descriptor 85 | header += b'\x00\x00\x00\x00' # NW corner position of image in logical screen 86 | header += b'\x0a\x00\x0a\x00' # Image width and height in pixels 87 | header += b'\x00' # No local color table 88 | header += b'\x02' # Start of image 89 | header += b'\x00' # End of image data 90 | header += b'\x3b' # GIF file terminator 91 | 92 | # Payload offset starts at: +31 (header bytes + enable script) 93 | 94 | return header 95 | 96 | 97 | def bmp_header_data(): 98 | ''' Minimal BMP image data ''' 99 | # BMP structure uses explicit size values which allows 100 | # us to pack our shellcode in at the end of the image 101 | # file without corrupting the image 102 | 103 | # Little-Endian 104 | # BMP Header (14 bytes) 105 | header = b'\x42\x4d' # Magic bytes header (`BM`) 106 | header += b'\x1e\x00\x00\x00' # BMP file size (30 bytes) 107 | header += b'\x00\x00' # Reserved (Unused) 108 | header += b'\x00\x00' # Reserved (Unused) 109 | header += b'\x1a\x00\x00\x00' # BMP image data offset (26 bytes) 110 | 111 | # DIB Header (12 bytes) 112 | header += b'\x0c\x00\x00\x00' # DIB header size (12 bytes) 113 | header += b'\x01\x00' # Width of bitmap (1 pixel) 114 | header += b'\x01\x00' # Height of bitmap (1 pixel) 115 | header += b'\x01\x00' # Number of color planes (1 plane) 116 | header += b'\x18\x00' # Number of bits per pixel (24 bits) 117 | 118 | # BMP Image Pixel Array (4 bytes) 119 | header += b'\x00\x00\xff' # Red, Pixel (0,1) 120 | header += b'\x00' # Padding for 4 byte alignment 121 | 122 | # Payload offset starts at: +35 (header bytes + enable script) 123 | 124 | return header 125 | 126 | 127 | def inject(payload, contents, out_file): 128 | '''Inject shellcode into BMP/GIF image 129 | 130 | Keyword arguments: 131 | payload -- shellcode to inject into image 132 | contents -- image data 133 | out_file -- name of output image file 134 | ''' 135 | 136 | # Open the image file 137 | f = open(out_file, "w+b") 138 | 139 | # Write the original image data 140 | f.write(contents) 141 | 142 | # Write `/////` as an offset identifier 143 | f.write(b'\x2f\x2f\x2f\x2f\x2f') 144 | 145 | # Write the payload 146 | f.write(payload) 147 | 148 | # Write a final `;` to break up shellcode from 149 | # just going to EOF 150 | f.write(b'\x3b') 151 | 152 | # Close the file 153 | f.close() 154 | 155 | print("[+]\tPayload was injected successfully") 156 | 157 | 158 | 159 | if __name__ == '__main__': 160 | 161 | parser = ArgumentParser(description='BMP/GIF Shellcode Injector -- v%s' % __version__) 162 | 163 | # Image type 164 | group_t = parser.add_mutually_exclusive_group(required=True) 165 | group_t.add_argument('-g', '--gif', action='store_true', help='Inject into GIF image file') 166 | group_t.add_argument('-b', '--bmp', action='store_true', help='Inject into BMP image file') 167 | 168 | # Shellcode input type 169 | group_s = parser.add_mutually_exclusive_group(required=True) 170 | group_s.add_argument('-s', '--shellcode', type=str, help='Explicit shellcode to inject into the image') 171 | group_s.add_argument('-f', '--file', type=str, help='Payload file containing shellcode') 172 | 173 | # Encoding 174 | parser.add_argument('--encode', action='store_true', help='Encode shellcode before injection') 175 | parser.add_argument('-k', '--key', type=str, help='Key to perform XOR encoding with') 176 | 177 | # Misc. 178 | parser.add_argument('-o', '--output', type=str, help='Output image file (Default: payload.{gif|bmp})', default='payload') 179 | parser.add_argument('--debug', action='store_true', help='Enable debug output') 180 | args = parser.parse_args() 181 | 182 | 183 | # Print simple banner 184 | print("\n*** BMP/GIF Shellcode Injector ***") 185 | print(" v%s\n" % __version__) 186 | 187 | 188 | # -> Parse shellcode provided by the user 189 | 190 | if args.file: 191 | if not os.path.exists(args.file): 192 | print("[!]\tFile does not exist: %s" % args.file) 193 | sys.exit() 194 | 195 | print("[>]\tReading payload file into memory") 196 | with open(args.file, 'r+b') as f: 197 | shellcode = f.read() 198 | 199 | else: 200 | print("[>]\tStoring shellcode in memory") 201 | shellcode = args.shellcode.encode() 202 | 203 | if args.debug: 204 | print("[*]\tShellcode contents size: %d bytes" % len(shellcode)) 205 | 206 | print("[>]\tSHA256 Hash of original payload: %s " % hashlib.sha256(shellcode).hexdigest()) 207 | 208 | 209 | # -> Handle XOR encoding - when enabled 210 | 211 | if args.encode: 212 | if not args.key: 213 | print("[>]\tGenerating random XOR key") 214 | xor_key = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(16)) 215 | print("[>]\tXOR Key: %s" % xor_key) 216 | else: 217 | xor_key = args.key 218 | 219 | # Convert key to bytes 220 | xor_key = bytes([ord(c) for c in xor_key]) 221 | 222 | print("[>]\tBase64 encoded key: %s" % base64.b64encode(xor_key).decode()) 223 | 224 | # Store key in file for remote hosting 225 | print("[>]\tWriting Base64 encoded XOR key to file: image.key") 226 | with open('image.key', 'wb') as xor_file: 227 | xor_file.write(base64.b64encode(xor_key)) 228 | 229 | print("[>]\tEncoding payload") 230 | shellcode = xor_crypt(shellcode, xor_key) 231 | 232 | 233 | # -> Collect image data 234 | 235 | print("[>]\tGetting image data") 236 | out_file = args.output 237 | 238 | # Make sure the output file has the correct image extension 239 | extension = 'bmp' if args.bmp else 'gif' 240 | if out_file[-4:] != ('.' + extension): 241 | out_file += ('.' + extension) 242 | 243 | # Check if image file exists 244 | # If not, use minimal image header data 245 | if not os.path.exists(out_file): 246 | contents = bmp_header_data() if args.bmp else gif_header_data() 247 | 248 | else: 249 | print("\n[!]\tThe file `%s` already exists." % out_file) 250 | append_sc = prompt("[?]\tAre you sure you want to append shellcode to the existing file?") 251 | print('') 252 | 253 | # Exit to avoid writing to file 254 | if not append_sc: 255 | sys.exit() 256 | 257 | # Append our shellcode to the existing image 258 | with open(out_file, 'r+b') as f: 259 | contents = f.read() 260 | 261 | original_length = len(contents) 262 | if args.debug: 263 | print("[*]\tOriginal image contents size: %d bytes" % original_length) 264 | 265 | 266 | # -> Inject our shellcode 267 | 268 | # Inject the shellcode into the BMP image 269 | print("[>]\tInjecting shellcode into image") 270 | inject(shellcode, contents, out_file) 271 | 272 | # Read the new data back in 273 | with open(out_file, 'r+b') as f: 274 | malicious_image = f.read() 275 | 276 | if args.debug: 277 | print("[*]\tMalicious image contents size: %d bytes" % len(malicious_image)) 278 | 279 | 280 | # -> Calculate the offset of our shellcode 281 | 282 | new_bytes = b'\x2f\x2f\x2f\x2f\x2f' 283 | byte_index = original_length + len(new_bytes) 284 | 285 | print("\n[+]\tIndex of payload: %d\n" % byte_index) 286 | 287 | 288 | # -> Hexdump based on content size 289 | 290 | if len(malicious_image) > 256: 291 | print(hexdump(malicious_image[0:128])) 292 | print('*') 293 | print(hexdump(malicious_image[byte_index-13:byte_index+128])) 294 | 295 | else: 296 | print(hexdump(malicious_image)) 297 | --------------------------------------------------------------------------------