-
سه شنبه, ۸ شهریور ۱۴۰۱، ۱۲:۲۸ ق.ظ
-
۱۰۱۰
سلام دوستان :)
توی پست امروز قراره نحوه ساخت یک اسکریپت پایتونی که یک سری اطلاعات رو داخل یک عکس مخفی می کنه رو ببینیم.
خب اول از همه باید بهتون توضیح بدم که اینکار اصلا چطور انجام میشه.
Steganography چیست؟
کاری که قراره توی این پست بکنیم اسمش Steganography هست که در واقع از واژه های یونانی steganos به معنای مخفی و graphe به معنای نوشتن گرفته شده.
هکر ها معمولا از این تکنیک برای مخفی کردن پیام های محرمانه یا داده و یا مخفی کردن بد افزار در قالب فایل های ویدیویی، فایل های صوتی، فایل های متنی و... استفاده می کنن.
توی این پست ما یک اسکریپت پایتون می نویسیم که با استفاده از تکنیک Least Significant Bit اطلاعاتمون رو داخل یک فایل عکس مخفی کنیم.
Least Significant Bit چیست؟
Least Significant Bit یا LSB تکنیکی هست که آخرین بیت هر pixel دستکاری میشه و با بیت اطلاعات مورد نظرمون عوض میشه.
این تکنیک فقط روی عکس های Lossless-compression یا فشرده سازی بی اتلاف به کار میاد، یعنی اینکه عکس در قالب یک فرمت فشرده سازی شده ذخیره میشه که البته از کیفیت عکس کم نمیشه.
PNG, TIFF و BMP مثال هایی از فرمت های فشرده سازی بی اتلاف هستن.
همونطوری که احتمالا میدونید عکس ها دارای چندین پیکسل هستن که هر کدومشون سه تا مقدار دارن: قرمز، سبز و آبی. این مقادیر از عدد 0 تا 250 یا اعداد 8 بیتی هستن.
برای یک مثال فرض کنید من میخوام کلمه "hi" رو داخل یک عکس با این پیکسل ها مخفی کنم:
[[(225, 12, 99), (155, 2, 50), (99, 51, 15), (15, 55, 22)], [(155, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66)], [(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]]
با نگاه کردن به جدول ASCII میتونیم متن "hi" رو به عدد decimal یا ده دهی تبدیل کنیم و بعد هم به عدد binary یا مبنای دو تبدیلش کنیم:
0110100 0110101
حالا تمام پیکسل های عکس رو به binary تبدیل میکنیم.
مثلا 225 میشه 11100001 حالا آخرین بیت(bold شده) رو با اولین بیت متنمون عوض میکنیم که میشه 11100000 یعنی 224.
بعدش همینطوری اطلاعاتمون رو با آخرین بیت هر مقدار یک پیکسل عوض میکنیم.
نتیجه رو ببینین:
[[(224, 13, 99), (154, 3, 50), (98, 50, 15), (15, 54, 23)], [(154, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66)], [(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]]
ما اینجا فقط پیکسل ها رو -1 یا +1 کردیم اما شما میتونین این عدد رو به دو یا سه هم تغییر بدین. اما بهتون توصیه نمیکنم که بیشتر از 6 بیت رو تغییر بدین چون معلوم میشه که یک جای عکس مشکل داره و شکل ظاهریش عوض میشه.
خب حالا که تکنیک ها و روش ها رو بهتون توضیح دادم بریم شروع کنیم!
برای انجام این کار باید یک کتابخونه برای کار کردن با عکس هامون انتخاب کنیم. من opencv رو انتخاب کردم شما میتونین از یک کتابخونه دیگه مثلا PIL هم استفاده کنین.
برای نصب کتابخونه های مورد نیاز توی ویندوز وارد cmd بشین و دستور زیر رو وارد کنین:
pip install opencv-python numpy
برای نصب کتابخونه های مورد نیاز توی مک یا لینوکس هم وارد Terminal بشین و دستور زیر رو وارد کنین:
sudo pip3 install opencv-python numpy
خب چیزی که میخوایم اینه:
یک اسکریپت که بهش دو تا فایل، یکی "فایل داده" و یکی "عکسی" که میخوایم داده رو داخلش مخفی کنه بدیم و بعد اون اسکریپت، داده رو داخل اون عکس رمزنگاری کنه، یا اینکه اطلاعات رو از فایلی که بهش دادیم استخراج کنه.
اول یک فایل پایتون باز کنین و کد های زیر رو داخلش وارد کنین:
import cv2 import numpy as np import os def to_bin(data): """Convert `data` to binary format as string""" if isinstance(data, str): return ''.join([ format(ord(i), "08b") for i in data ]) elif isinstance(data, bytes): return ''.join([ format(i, "08b") for i in data ]) elif isinstance(data, np.ndarray): return [ format(i, "08b") for i in data ] elif isinstance(data, int) or isinstance(data, np.uint8): return format(data, "08b") else: raise TypeError("Type not supported.") def encode(image_name, secret_data, n_bits=2): # read the image image = cv2.imread(image_name) # maximum bytes to encode n_bytes = image.shape[0] * image.shape[1] * 3 * n_bits // 8 print("[*] Maximum bytes to encode:", n_bytes) print("[*] Data size:", len(secret_data)) if len(secret_data) > n_bytes: raise ValueError(f"[!] Insufficient bytes ({len(secret_data)}), need bigger image or less data.") print("[*] Encoding data...") # add stopping criteria if isinstance(secret_data, str): secret_data += "=====" elif isinstance(secret_data, bytes): secret_data += b"=====" data_index = 0 # convert data to binary binary_secret_data = to_bin(secret_data) # size of data to hide data_len = len(binary_secret_data) for bit in range(1, n_bits+1): for row in image: for pixel in row: # convert RGB values to binary format r, g, b = to_bin(pixel) # modify the least significant bit only if there is still data to store if data_index < data_len: if bit == 1: # least significant red pixel bit pixel[0] = int(r[:-bit] + binary_secret_data[data_index], 2) elif bit > 1: # replace the `bit` least significant bit of the red pixel with the data bit pixel[0] = int(r[:-bit] + binary_secret_data[data_index] + r[-bit+1:], 2) data_index += 1 if data_index < data_len: if bit == 1: # least significant green pixel bit pixel[1] = int(g[:-bit] + binary_secret_data[data_index], 2) elif bit > 1: # replace the `bit` least significant bit of the green pixel with the data bit pixel[1] = int(g[:-bit] + binary_secret_data[data_index] + g[-bit+1:], 2) data_index += 1 if data_index < data_len: if bit == 1: # least significant blue pixel bit pixel[2] = int(b[:-bit] + binary_secret_data[data_index], 2) elif bit > 1: # replace the `bit` least significant bit of the blue pixel with the data bit pixel[2] = int(b[:-bit] + binary_secret_data[data_index] + b[-bit+1:], 2) data_index += 1 # if data is encoded, just break out of the loop if data_index >= data_len: break return image def decode(image_name, n_bits=1, in_bytes=False): print("[+] Decoding...") # read the image image = cv2.imread(image_name) binary_data = "" for bit in range(1, n_bits+1): for row in image: for pixel in row: r, g, b = to_bin(pixel) binary_data += r[-bit] binary_data += g[-bit] binary_data += b[-bit] # split by 8-bits all_bytes = [ binary_data[i: i+8] for i in range(0, len(binary_data), 8) ] # convert from bits to characters if in_bytes: # if the data we'll decode is binary data, # we initialize bytearray instead of string decoded_data = bytearray() for byte in all_bytes: # append the data after converting from binary decoded_data.append(int(byte, 2)) if decoded_data[-5:] == b"=====": # exit out of the loop if we find the stopping criteria break else: decoded_data = "" for byte in all_bytes: decoded_data += chr(int(byte, 2)) if decoded_data[-5:] == "=====": break return decoded_data[:-5] if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Steganography encoder/decoder, this Python scripts encode data within images.") parser.add_argument("-t", "--text", help="The text data to encode into the image, this only should be specified for encoding") parser.add_argument("-f", "--file", help="The file to hide into the image, this only should be specified while encoding") parser.add_argument("-e", "--encode", help="Encode the following image") parser.add_argument("-d", "--decode", help="Decode the following image") parser.add_argument("-b", "--n-bits", help="The number of least significant bits of the image to encode", type=int, default=2) args = parser.parse_args() if args.encode: # if the encode argument is specified if args.text: secret_data = args.text elif args.file: with open(args.file, "rb") as f: secret_data = f.read() input_image = args.encode # split the absolute path and the file path, file = os.path.split(input_image) # split the filename and the image extension filename, ext = file.split(".") output_image = os.path.join(path, f"{filename}_encoded.{ext}") # encode the data into the image encoded_image = encode(image_name=input_image, secret_data=secret_data, n_bits=args.n_bits) # save the output image (encoded image) cv2.imwrite(output_image, encoded_image) print("[+] Saved encoded image.") if args.decode: input_image = args.decode if args.file: # decode the secret data from the image and write it to file decoded_data = decode(input_image, n_bits=args.n_bits, in_bytes=True) with open(args.file, "wb") as f: f.write(decoded_data) print(f"[+] File decoded, {args.file} is saved successfully.") else: # decode the secret data from the image and print it in the console decoded_data = decode(input_image, n_bits=args.n_bits) print("[+] Decoded data:", decoded_data)
توضیح: خب اول از همه کتابخونه های مورد نیازمون رو import کردیم.
بعدش یک تابع به اسم to_bin تعریف کردیم که برای تبدیل کردن اطلاعات به binary بعدا استفادش می کنیم.
حالا یک تابع به نام encode تعریف کردیم که در واقع کارش اینه که اطلاعات رو رمزنگاری کنه. این تابع رو یک جوری تعریف کردیم که هم متن و هم فایل های دیگه رو تبدیل به باینری کنه و با تعداد بیت هایی که خودمون توی ورودی های برنامه مشخص می کنیم داخل عکس رمزنگاری کنه.
بعد از اون تابع decode رو تعریف کردیم که اول عکس رو بخونه و بعد اطلاعات binary رو تبدیل به 8 بیتی کنه و بعدش با تعداد بیت هایی که بهش دادیم اطلاعات رو رمزنگاری کنه.
حالا توی برنامه اصلی گفتیم که ورودی های مورد نیاز رو از کاربر بگیره و با استفاده از توابعی که تعریف کردیم اطلاعات رو رمزنگاری یا رمزگشایی کنه.
فقط حواستون باشه که حجم عکس باید از حجم داده ای که میخواین مخفی کنین بیشتر باشه وگرنه داده داخلش جا نمیشه.
بریم برناممون رو تست کنیم!
اینجا من دو تا عکس دارم:
test:
test2:
خب کاری که میخوایم انجام بدیم اینه که فایل test2.jpeg رو داخل test.png مخفی کنیم!
پس وارد ترمینال میشیم و دستور زیر رو وارد می کنیم:
python hide_data.py -f test2.jpeg -e test.png -b 2
برنامه این رو به ما نشون میده:
[*] Maximum bytes to encode: 423225 [*] Data size: 132550 [*] Encoding data... [+] Saved encoded image.
خب این یعنی اینکه عملیات با موفقیت انجام شده و فایل رمزنگاری شده رو ذخیره کرده:
خب همونطور که میبینین یک فایل به نام test_encoded.png اضافه شده که هیچ فرقی با فایل اصلی نداره و اصلا معلوم نیست که یک عکس دیگه رو داخلش جا دادیم!
حالا بریم سراغ decode کردن عکس.
پس وارد ترمینال میشیم و کد زیر رو وارد می کنیم:
python hide_data.py -d test_encoded.png -f test_decoded.png -b 2
خروجی برنامه:
[+] Decoding... [+] File decoded, test_decoded.png is saved successfully.
خب دیدیم که فایل test_decoded.png توی همون فولدر ذخیره شده و عین همون عکس اصلیه:
پس ما در واقع عکس 1 رو داخل عکس 2 مخفی کردیم بدون اینکه عکس 2 تغییر کنه و بعدش عکس 1 رو دوباره از توی عکس 2 در آوردیم و میبینیم که هر دوتا عکس عین روز اولشونن! :)))
امیدوارم براتون مفید بوده باشه D: