cchudant commited on
Commit
3d1c35c
·
0 Parent(s):
Files changed (10) hide show
  1. .gitattributes +1 -0
  2. .gitignore +2 -0
  3. .vscode/settings.json +3 -0
  4. LICENCE +17 -0
  5. LSBSteg.py +198 -0
  6. README.md +14 -0
  7. __init__.py +0 -0
  8. app.py +89 -0
  9. requirements.txt +3 -0
  10. sample-picture.png +3 -0
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__
2
+ gradio_cached_examples
.vscode/settings.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "python.formatting.provider": "black"
3
+ }
LICENCE ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright © 2017, Robin David - MIT-Licensed
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5
+ the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
6
+ to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ The Software is provided "as is", without warranty of any kind, express or implied, including but not limited
11
+ to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall
12
+ the authors or copyright holders X be liable for any claim, damages or other liability, whether in an action
13
+ of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other
14
+ dealings in the Software.
15
+
16
+ Except as contained in this notice, the name of the Robin David shall not be used in advertising or otherwise
17
+ to promote the sale, use or other dealings in this Software without prior written authorization from the Robin David.
LSBSteg.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding:UTF-8
3
+ """LSBSteg.py
4
+
5
+ Usage:
6
+ LSBSteg.py encode -i <input> -o <output> -f <file>
7
+ LSBSteg.py decode -i <input> -o <output>
8
+
9
+ Options:
10
+ -h, --help Show this help
11
+ --version Show the version
12
+ -f,--file=<file> File to hide
13
+ -i,--in=<input> Input image (carrier)
14
+ -o,--out=<output> Output image (or extracted file)
15
+ """
16
+
17
+ import cv2
18
+ import docopt
19
+ import numpy as np
20
+
21
+ channel_order = (2, 1, 0, 3)
22
+
23
+ class SteganographyException(Exception):
24
+ pass
25
+
26
+
27
+ class LSBSteg():
28
+ def __init__(self, im):
29
+ self.image = im
30
+ self.height, self.width, self.nbchannels = im.shape
31
+ self.size = self.width * self.height
32
+
33
+ self.maskONEValues = [1,2,4,8,16,32,64,128]
34
+ #Mask used to put one ex:1->00000001, 2->00000010 .. associated with OR bitwise
35
+ self.maskONE = self.maskONEValues.pop(0) #Will be used to do bitwise operations
36
+
37
+ self.maskZEROValues = [254,253,251,247,239,223,191,127]
38
+ #Mak used to put zero ex:254->11111110, 253->11111101 .. associated with AND bitwise
39
+ self.maskZERO = self.maskZEROValues.pop(0)
40
+
41
+ self.curwidth = 0 # Current width position
42
+ self.curheight = 0 # Current height position
43
+ self.curchan = 0 # Current channel position
44
+
45
+ def put_binary_value(self, bits): #Put the bits in the image
46
+ for c in bits:
47
+ val = list(self.image[self.curheight,self.curwidth]) #Get the pixel value as a list
48
+ if int(c) == 1:
49
+ val[self.curchan] = int(val[self.curchan]) | self.maskONE #OR with maskONE
50
+ else:
51
+ val[self.curchan] = int(val[self.curchan]) & self.maskZERO #AND with maskZERO
52
+
53
+ self.image[self.curheight,self.curwidth] = tuple(val)
54
+ self.next_slot() #Move "cursor" to the next space
55
+
56
+ def next_slot(self):#Move to the next slot were information can be taken or put
57
+ if self.curchan == self.nbchannels-1: #Next Space is the following channel
58
+ self.curchan = 0
59
+ if self.curwidth == self.width-1: #Or the first channel of the next pixel of the same line
60
+ self.curwidth = 0
61
+ if self.curheight == self.height-1:#Or the first channel of the first pixel of the next line
62
+ self.curheight = 0
63
+ if self.maskONE == 128: #Mask 1000000, so the last mask
64
+ raise SteganographyException("No available slot remaining (image filled)")
65
+ else: #Or instead of using the first bit start using the second and so on..
66
+ self.maskONE = self.maskONEValues.pop(0)
67
+ self.maskZERO = self.maskZEROValues.pop(0)
68
+ else:
69
+ self.curheight +=1
70
+ else:
71
+ self.curwidth +=1
72
+ else:
73
+ self.curchan +=1
74
+
75
+ def read_bit(self): #Read a single bit int the image
76
+ val = self.image[self.curheight,self.curwidth,channel_order[self.curchan]]
77
+ val = int(val) & self.maskONE
78
+ self.next_slot()
79
+ if val > 0:
80
+ return "1"
81
+ else:
82
+ return "0"
83
+
84
+ def read_byte(self):
85
+ return self.read_bits(8)
86
+
87
+ def read_bits(self, nb): #Read the given number of bits
88
+ bits = ""
89
+ for i in range(nb):
90
+ bits += self.read_bit()
91
+ return bits
92
+
93
+ def byteValue(self, val):
94
+ return self.binary_value(val, 8)
95
+
96
+ def binary_value(self, val, bitsize): #Return the binary value of an int as a byte
97
+ binval = bin(val)[2:]
98
+ if len(binval) > bitsize:
99
+ raise SteganographyException("binary value larger than the expected size")
100
+ while len(binval) < bitsize:
101
+ binval = "0"+binval
102
+ return binval
103
+
104
+ def encode_text(self, txt):
105
+ l = len(txt)
106
+ binl = self.binary_value(l, 16) #Length coded on 2 bytes so the text size can be up to 65536 bytes long
107
+ self.put_binary_value(binl) #Put text length coded on 4 bytes
108
+ for char in txt: #And put all the chars
109
+ c = ord(char)
110
+ self.put_binary_value(self.byteValue(c))
111
+ return self.image
112
+
113
+ def decode_text(self):
114
+ ls = self.read_bits(16) #Read the text size in bytes
115
+ l = int(ls,2)
116
+ i = 0
117
+ unhideTxt = ""
118
+ while i < l: #Read all bytes of the text
119
+ tmp = self.read_byte() #So one byte
120
+ i += 1
121
+ unhideTxt += chr(int(tmp,2)) #Every chars concatenated to str
122
+ return unhideTxt
123
+
124
+ def encode_image(self, imtohide):
125
+ w = imtohide.width
126
+ h = imtohide.height
127
+ if self.width*self.height*self.nbchannels < w*h*imtohide.channels:
128
+ raise SteganographyException("Carrier image not big enough to hold all the datas to steganography")
129
+ binw = self.binary_value(w, 16) #Width coded on to byte so width up to 65536
130
+ binh = self.binary_value(h, 16)
131
+ self.put_binary_value(binw) #Put width
132
+ self.put_binary_value(binh) #Put height
133
+ for h in range(imtohide.height): #Iterate the hole image to put every pixel values
134
+ for w in range(imtohide.width):
135
+ for chan in range(imtohide.channels):
136
+ val = imtohide[h,w][chan]
137
+ self.put_binary_value(self.byteValue(int(val)))
138
+ return self.image
139
+
140
+
141
+ def decode_image(self):
142
+ width = int(self.read_bits(16),2) #Read 16bits and convert it in int
143
+ height = int(self.read_bits(16),2)
144
+ unhideimg = np.zeros((width,height, 3), np.uint8) #Create an image in which we will put all the pixels read
145
+ for h in range(height):
146
+ for w in range(width):
147
+ for chan in range(unhideimg.channels):
148
+ val = list(unhideimg[h,w])
149
+ val[chan] = int(self.read_byte(),2) #Read the value
150
+ unhideimg[h,w] = tuple(val)
151
+ return unhideimg
152
+
153
+ def encode_binary(self, data):
154
+ l = len(data)
155
+ if self.width*self.height*self.nbchannels < l+64:
156
+ raise SteganographyException("Carrier image not big enough to hold all the datas to steganography")
157
+ self.put_binary_value(self.binary_value(l, 64))
158
+ for byte in data:
159
+ byte = byte if isinstance(byte, int) else ord(byte) # Compat py2/py3
160
+ self.put_binary_value(self.byteValue(byte))
161
+ return self.image
162
+
163
+ def decode_binary(self):
164
+ l = int(self.read_bits(64), 2)
165
+ output = b""
166
+ for i in range(l):
167
+ output += bytearray([int(self.read_byte(),2)])
168
+ return output
169
+
170
+
171
+ def main():
172
+ args = docopt.docopt(__doc__, version="0.2")
173
+ in_f = args["--in"]
174
+ out_f = args["--out"]
175
+ in_img = cv2.imread(in_f, cv2.IMREAD_UNCHANGED)
176
+ steg = LSBSteg(in_img)
177
+ lossy_formats = ["jpeg", "jpg"]
178
+
179
+ if args['encode']:
180
+ #Handling lossy format
181
+ out_f, out_ext = out_f.split(".")
182
+ if out_ext in lossy_formats:
183
+ out_f = out_f + ".png"
184
+ print("Output file changed to ", out_f)
185
+
186
+ data = open(args["--file"], "rb").read()
187
+ res = steg.encode_binary(data)
188
+ cv2.imwrite(out_f, res)
189
+
190
+ elif args["decode"]:
191
+ raw = steg.decode_binary()
192
+ with open(out_f, "wb") as f:
193
+ f.write(raw)
194
+
195
+
196
+ if __name__=="__main__":
197
+ main()
198
+
README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Non-suspicious image decoder
3
+ emoji: 🥳
4
+ colorFrom: red
5
+ colorTo: yellow
6
+ sdk: gradio
7
+ sdk_version: "3.13.2"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+
13
+ # Non Suspicious Image Decoder
14
+ Recover pandas dataframe hidden in image
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import io
4
+ import pandas as pd
5
+
6
+ from LSBSteg import LSBSteg
7
+
8
+
9
+ def convert(file):
10
+ print(f"Converting file {file}")
11
+ in_img = cv2.imread(file, cv2.IMREAD_UNCHANGED)
12
+ lsbsteg = LSBSteg(in_img)
13
+ data = lsbsteg.decode_binary()
14
+ bytes = io.BytesIO(data)
15
+ dataframe = pd.read_parquet(bytes)
16
+
17
+ # dataframe.to_csv('output.csv')
18
+ return dataframe
19
+
20
+
21
+ with gr.Blocks() as demo:
22
+ gr.Markdown("""
23
+ ## Non-Suspicious image decoder
24
+
25
+ This tool shows the extraction a dataframe hidden inside an image.
26
+
27
+ There are a few ways to hide data into a PNG file, notably:
28
+ * adding it after the end of the file (after the PNG IEND chunk), so that it gets
29
+ ignored by image viewers
30
+ * adding it as comments in the PNG file (tEXt chunks)
31
+
32
+ These methods are kind of easy to spot! Also, a lot of software, browsers, image upload
33
+ websites etc often just strip them.
34
+
35
+ So, here, we have a different, more thoughtful (and arguably cooler) method.
36
+
37
+ This class hides the data using a basic kind of **[steganography](https://en.wikipedia.org/wiki/Steganography)**:
38
+ it hides it in the
39
+ *least significant bits* of the raw (uncompressed) picture: tiny differences in the red, green and blue
40
+ channel of the image encodes the data we're interested in.
41
+
42
+ This means the resulting picture
43
+ looks **very close to the original image**; and for the data we hide here, it is **inperceptible
44
+ to the naked eye**.
45
+
46
+ The resulting PNG file will probably get a little bit bigger as a result, since PNG uses compression,
47
+ which will have a harder time when we have our stolen data injected into the image. This is
48
+ not that much of a problem since it stays <100Ko, so it's not that noticeable.
49
+
50
+ """)
51
+ with gr.Row():
52
+ im = gr.Image(label="Input image file", type="filepath")
53
+
54
+ def preprocess(encoding: str) -> str:
55
+ # We do our own preprocessing because gradio's deletes PNG metadata :(
56
+ import tempfile
57
+ import base64
58
+
59
+ content = encoding.split(";")[1]
60
+ image_encoded = content.split(",")[1]
61
+ png_content = base64.b64decode(image_encoded)
62
+ file_obj = tempfile.NamedTemporaryFile(
63
+ delete=False,
64
+ suffix=".input.png",
65
+ )
66
+ file_obj.write(png_content)
67
+ return file_obj.name
68
+
69
+ im.preprocess = preprocess
70
+ df_out = gr.Dataframe(
71
+ label="Output dataframe", max_rows=20, overflow_row_behaviour="paginate"
72
+ )
73
+ # file_out = gr.File(label="Full output CSV file")
74
+ btn = gr.Button(value="Extract")
75
+ gr.Markdown("Click on the example below to get the data from the associated colab notebook :)")
76
+ gr.Examples(
77
+ examples=["sample-picture.png"],
78
+ inputs=[im],
79
+ outputs=[df_out],
80
+ fn=convert,
81
+ cache_examples=True,
82
+ )
83
+ # demo = gr.Interface(convert, im, im_2)
84
+ btn.click(convert, inputs=[im], outputs=[df_out])
85
+
86
+ # example_img = os.path.join(os.path.dirname(__file__), "example-picture.png")
87
+
88
+ if __name__ == "__main__":
89
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ opencv-python
2
+ docopt
3
+ numpy
sample-picture.png ADDED

Git LFS Details

  • SHA256: 1b1710c660e5b06f369e65233e6ac77628c49ca1478a4bb6b41bcb10332801d6
  • Pointer size: 130 Bytes
  • Size of remote file: 41.4 kB