├── README.md ├── input_base_image.bmp ├── input_payload.txt └── steganim.nim /README.md: -------------------------------------------------------------------------------- 1 | # Steganim 2 | 3 | Nim implementation of storing a payload into the least significant bit of each byte of an image. 4 | Using this technique to remotely fetch shellcode or other secrets at runtime can help into removing some IOCs like payload entropy. 5 | 6 | ## How to use 7 | - Install Nim on Linux 8 | - Clone this repo 9 | - Change values if desired, then compile steganim.nim 10 | - Execute the payload to observe the creation of a new image and the subsequent extraction of the payload from it 11 | 12 | ## How to cross-compile from Linux to Windows 13 | - nim c -d=mingw -d=release --app=console --cpu=amd64 steganim.nim 14 | -------------------------------------------------------------------------------- /input_base_image.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffenseTeacher/Steganim/7c2ee4ddbffbbeaace6c39ff6c1311328de79e5c/input_base_image.bmp -------------------------------------------------------------------------------- /input_payload.txt: -------------------------------------------------------------------------------- 1 | Super_Secret_Something -------------------------------------------------------------------------------- /steganim.nim: -------------------------------------------------------------------------------- 1 | import winim 2 | import streams 3 | 4 | func toByteSeq*(str: string): seq[byte] {.inline.} = 5 | @(str.toOpenArrayByte(0, str.high)) 6 | 7 | proc toString(bytes: openarray[byte]): string = 8 | result = newString(bytes.len) 9 | copyMem(result[0].addr, bytes[0].unsafeAddr, bytes.len) 10 | 11 | proc nthBitPresent(b: byte,n: int): bool = 12 | var a = 1 shl n 13 | var b = int(b) and a 14 | return b != 0 15 | 16 | proc extractByte(a: seq[bool]): byte = 17 | 18 | var b: byte = 0 19 | for i in 0 .. 7: 20 | if a[i]: 21 | b = b or (byte)(1 shl (7 - i)) 22 | else: 23 | b = b or (byte)(0 shl (7 - i)) 24 | return b 25 | 26 | proc extractBytes(a: seq[byte],b: int): seq[byte] = 27 | 28 | var c: int = b 29 | 30 | var d: seq[bool] = newSeq[bool](0) 31 | for i in c .. len(a) - 1: 32 | d.add(nthBitPresent(a[i], 0)) 33 | var e: seq[byte] = newseq[byte](0) 34 | 35 | for i in countup(0, len(d), 8): 36 | if len(d) - i > 8: 37 | var tmp : byte = extractByte(d[i .. i + 8]) 38 | e.add(tmp) 39 | e = e[1 .. (e.len - 1)] 40 | var f = toByteSeq("\t")[0] 41 | var idx = e.find(f); 42 | var payloadLengthBytes: seq[byte] = e[0 .. idx - 1] 43 | var p: int 44 | var msg_len = (cast[ptr int32] (addr payloadLengthBytes[p]))[] 45 | var finalPayload = e[idx+1 .. (idx + msg_len)] 46 | return finalPayload 47 | 48 | proc getBytesFromFile(path: string): seq[byte] = 49 | try: 50 | var 51 | s = newFileStream(path, fmRead) 52 | valSeq = newSeq[byte]() 53 | while not s.atEnd: 54 | let element = s.readUInt8 55 | valSeq.add(element) 56 | s.close() 57 | return valSeq 58 | except: 59 | echo "!! ", path, " was not found !!" 60 | quit(1) 61 | 62 | proc mergeBaseImageWithPayload1BitPerByte(my_byte: byte, ends_in_one: bool): byte = 63 | var new_byte: byte = my_byte; 64 | if ends_in_one: 65 | if not nthBitPresent(my_byte, 0): 66 | new_byte = cast[byte](my_byte + 1) 67 | else: 68 | if (nthBitPresent(my_byte, 0)): 69 | new_byte = cast[byte](my_byte - 1) 70 | return new_byte; 71 | 72 | proc createSteganoImage(payloadPath: string, baseImagePath: string, outputFile: string): void = 73 | var 74 | payloadBytes: seq[byte] = getBytesFromFile(payloadPath) 75 | baseImageBytes: seq[byte] = getBytesFromFile(baseImagePath) 76 | delimiter = toByteSeq("\t")[0] 77 | start_offset: int = 50 78 | payloadLengthInBytes = cast[array[sizeof(int32), byte]](payloadBytes.len) 79 | payloadLengthInBytesReverse: seq[byte] = @[] 80 | for r in countdown(payloadLengthInBytes.len - 1, 0): 81 | payloadLengthInBytesReverse.add(payloadLengthInBytes[r]) 82 | payloadBytes = delimiter & payloadBytes 83 | 84 | for b in payloadLengthInBytesReverse: 85 | payloadBytes = b & payloadBytes 86 | 87 | payloadBytes = delimiter & payloadBytes 88 | var bits: seq[bool] = newSeq[bool](0) 89 | 90 | for i in countup(0, payloadBytes.len - 1): 91 | for j in countdown(7,0): 92 | bits.add(nthBitPresent(payloadBytes[i], j)) 93 | 94 | if len(bits) > len(baseImageBytes) + start_offset: 95 | echo "Payload too big for the image" 96 | quit(1) 97 | 98 | for i in 0 .. bits.len - 1: 99 | baseImageBytes[i + start_offset] = mergeBaseImageWithPayload1BitPerByte(baseImageBytes[i + start_offset], bits[i]) 100 | 101 | var fHandle = open(outputFile, fmWrite) 102 | discard writeBytes(fHandle,baseImageBytes,0,baseImageBytes.len) 103 | 104 | proc getFromStegano(path: string): seq[byte] = 105 | var 106 | c = getBytesFromFile(path) 107 | a = 50 108 | shellcode: seq[byte] = extractBytes(c, a) 109 | return shellcode 110 | 111 | when isMainModule: 112 | var 113 | inputPayload = "input_payload.txt" 114 | inputBaseImage = "input_base_image.bmp" 115 | outputSteganoFile = "output_stegano.bmp" 116 | 117 | createSteganoImage(inputPayload, inputBaseImage, outputSteganoFile) 118 | echo toString(getFromStegano(outputSteganoFile)) --------------------------------------------------------------------------------