-
دوشنبه, ۲۶ آبان ۱۳۹۹، ۰۸:۳۶ ق.ظ
-
۱۶۹۸
نوشتن cracker پرسرعت در پایتون با قابلیت پردازش موازی (MultiProcessing)
درود به همه !
سلام دارم خدمت تمام دوستان عزیز . امروز با یک پست بسیار کاربردی و مفهومی در خدمت شما هستیم . در این پست در سطح نسبتا خوبی با مبحث multiprocessing یا پردازش های موازی آشنا خواهیم شد . پس از یادگیری مبحث پردازش های موازی میتوانیم cracker هایی بنویسیم که سرعت بسیار زیادی نسبت به cracker های ساده دارند . برای مثال در پست های قبلی ما روش ساخت cracker فایل های rar رو یادگرفتیم . ابزاری که در پست های قبلی نوشتیم ، به طور همزمان نمیتوانست بیش از یک پسوورد را تست کند . ولی ما با استفاده از پردازش های موازی کاری میکنیم تا اسکریپت بتواند برای مثال 16 پسوورد را همزمان تست کند و این باعث افزایش سرعت cracker ما خواهد شد .
مفاهیم :
Program : معمولا یک فایل اجرایی که روی هارد ذخیره میشود . در سیستم عامل های مختلف این فایل قابل اجرا است .
Process : وقتی یک Program در کامپیوتر اجرا میشود ، یک قسمتی از حافظه را به خود اختصاص میدهد و تبدیل به یک Process زنده در سیستم عامل میشود .
Thread : یک بخش کوچک اجرایی از یک Process . به عبارتی یک Process میتواند متشکل از چندین thread باشد .
بنابر توضیحات بالا فرض کنید ما یک برنامه نوشته ایم . فایل اجرایی این برنامه یک Program است . وقتی ما این برنامه را اجرا کردیم ، یک Process از آن برنامه در سیستم عامل ساخته میشود و این Process در حال اجرا و زنده است . همچنین هر یک از توابع برنامه میتوانند یک thread باشند چون یک واحد اجرایی از برنامه است .
و اما بریم یکم از ساختار زبان پایتون صحبت کنیم . زبان پایتون طوری طراحی شده که مفسر آن به شکلی عمل میکنه که اجازه نمیده به طور همزمان بیش از یک Thread اجرا شود . این یعنی چی ؟
سورس کد زیر رو فرض کنید :
def Function1(): print("Function 1 Started !")
def Function2(): print("Function 2 Started !")
Function1() Function2()
همونطور که میبینید ما در سورس بالا دوتا تابع تعریف کردیم . یکی Function1 و دیگری Function2 . نهایتا ابتدا Function1 رو فراخوانی کردیم و سپس Function2 . نکته اینه که طبق صحبتی که بالا کردیم ، مفسر پایتون به هیچ وجه این قابلیت رو به ما نمیده که کاری کنیم که این دو تابع به طور همزمان اجرا شوند . یعنی حتما باید پشت سر هم و به ترتیب اجرا شوند . چیزی به اسم اجرای همزمان نداریم !!!!!
خب مشکل همینجا بوجود میاد . خیلی از برنامه ها thread-based هستند و در برخی موقعیت ها نیاز دارند تا چند thread به طور همزمان اجرا شود .!! در پایتون بخشی به نام GIL مخفف Global Interpreter Lock وجود داره که اجازه نمیده به طور همزمان بیش از یک thread به مفسر پایتون دسترسی داشته باشه و اجرا بشه . بنابراین همین GIL در پایتون باعث میشه که در حالت عادی برنامه های پردازش موازی نتونیم بنویسیم . البته به دلیل روش مدیریت حافظه ای که پایتون ازش بهره میبره واقعا این GIL نیاز هستش ولی خب به هر حال در این مورد یه سنگیه جلوی پای ما .
نکته مهمتر اینه که پایتون به دلیل داشتن GIL ، نهایت استفاده از CPU را ندارد . مثلا اگر CPU شما 4 هسته ایه ، پایتون فقط از یک هسته ی اون برای پردازش استفاده میکنه . یک cracker خوب و بهینه باید از تمام هسته های CPUاستفاده کنه تا سرعت خوبی داشته باشه .
بنابراین تا اینجا به طور کلی میدونید پردازش های موازی چی هستن . به طور کلی پردازش موازی یعنی چنتا چیز همزمان پردازش بشه .
دقیق تر بخوایم برسیشون کنیم ما دو نوع پردازش موازی داریم که به اشتباه بعضی جاها اینو یکی میدونن :
- MultiThreading : در این نوع پردازش موازی Thread های برنامه با تکنیک های خاصی به طور همزمان اجرا میشوند . اگر بخواهیم این روش را در پایتون پیاده سازی کنیم ، کتابخانه هایی هستند که GIL پایتون را دور میزنند و بنابراین ما میتوانیم چند Thread را به طور همزمان اجرا کنیم . خوبی این روش این هستش که چون همه ی thread ها زیر مجموعه ی یک برنامه اصلی هستند میتوانند باهم در ارتباط باشن و اطلاعاتشون رو باهم به اشتراک بزارن . عیب این روش این هستش که در بیشتر مواقع ما از تمام هسته های cpu برای پردازش استفاده نمیکنیم و از یک هسته ی cpu برای پردازش همه ی thread ها استفاده میکنیم .
- MultiProcessing : در این نوع پردازش موازی ما میایم کلا هر قسمتی از برنامه که میخوایم به طور موازی پردازش بشه رو در قالب یک Process جدا از برنامه اصلی در کامپیوتر اجرا میکنیم . برای مثال اگر دو تابع Function1 و Function2 رو نیاز داشته باشیم همزمان اجرا بشن ، هر کدام از این توابع یک Process جدا میشوند و به طور کاملا مستقل مثل دو برنامه ی جدا از هم اجرا میشوند . خوبی این روش این هستش که ما از نهایت قدرت CPU برای پردازش هامون استفاده میکنیم . یعنی مثلا اگه CPU ما 4 هسته ای هستش ، ما از هر 4 هسته ی CPU برای پردازش استفاده میکنیم . و عیب این روش این هستش که Process هایی که به طور جداگانه اجرا میشوند نمیتوانند اطلاعاتشان را به راحتی باهم به اشتراک بزارن چون کاملا مستقل از همدیگر اجرا شده اند . در واقع در این روش هر Process که جداگانه اجرا میشه یک مفسر پایتون و GIL مخصوص به خودش رو داره .
با توجه به تعریف MultiThreading و MultiProcessing حالا دیگه تشخیصش با خودمونه که در هر موقعیتی از کدومش باید استفاده کرد .
در بحث Cracking ما معمولا از MultiProcessing استفاده میکنیم به دلیل اینکه میتونه از تمام هسته های cpu استفاده بهینه بکنه و در نتیجه سرعت کار رو بسیار بالا ببره . پایتون دوتا کتابخونه داره که به طور پیشفرض داخلش هست . threading و multiprocessing . همینطور که از اسمشون پیداس ، یکیشون برای multithreading و یکیشون برای multiprocessing هستش . ما از کتابخونه ی multiprocessing برای cracker هامون استفاده میکنیم .
فرض کنید میخواهیم در سورسی که بالا معرفی کردیم ، دو تابع Function1 و Function2 را با استفاده از MultiProcessing به طور همزمان اجرا کنیم . به صورت زیر عمل میکنیم :
import multiprocessing as m
def Function1(): print("Function 1 Started !") def Function2(): print("Function 2 Started !") p1 = m.Process(target = Function1) # Make Process For Function1 p2 = m.Process(target = Function2) # Make Process For Function2 p1.start() # Start Process 1 p2.start() # Start Process 2
توضیح : در خط اول که کتابخونه ی multiprocessing رو ایمپورت کردیم . سپس دو تابع Function1 و Function2 را تعریف کردیم . پس از اون با استفاده از متود m.Process اومدیم دوتا Process مستقل و جدا ساختیم که در ورودی اول آن یعنی پارامتر target ، مشخص کردیم این Process ها هر کدوم چه تابعی رو قراره اجرا کنن . سپس این دو Process رو با استفاده از متود start اجرا کردیم . وقتی این سورس اجرا شود دو Process مستقل از هم ساخته میشوند و هر کدوم یکی از دو تابع Function1 و Function2 را اجرا میکنند در نتیجه این دو تابع به طور همزمان اجرا خواهند شد و این یعنی پردازش موازی !!!
خب بریم این تکنیکی که یاد گرفتیمو روی یک cracker فایل های وینرار پیاده کنیم . برای اینکار میایم همون سورس cracker ساده وینرار رو تبدیل به cracker با پردازش های موازی میکنیم . پیشنهاد میکنم در ابتدا اگر پست ( آموزش کرک فایل های وینرار (Winrar) در پایتون ) رو نخوندین برین و بخونین و بعد بیاین ادامه ی این پست رو ببینید چون ما همون سورس رو تغییر میدیم .
اگه یادتون باشه سورس کرکر وینرار به شکل زیر بود :
import rarfile import sys rarfile_address = input("RarFile : ") passwordlist_address = input("Password List : ") rar_file = rarfile.RarFile(rarfile_address) passwordlist = open(passwordlist_address) password_found = False print("-----------------") for password in passwordlist: password = password.strip("\n") print("Testing : {}".format(password)) try: rar_file.setpassword(password) rar_file.testrar() print("*"*50) print("Password : {}".format(password)) password_found = True break except rarfile.RarWrongPassword: continue if password_found: sys.exit(0) else: print("*"*50) print("Sorry I can't find correct Password in your password list :(")
خب تغییری که در این سورس میدیم اینه که ما یه تابع میسازیم که کارش تست کردن یک پسوورد روی فایل وینرار هستش و این پسوورد رو هم در ورودی میگیره . یعنی یه تابع که یه ورودی داره . هرچی بهش ورودی بدی به عنوان پسوورد تست میکنه روی فایل وینرار . حالا این تابع رو میتونیم در دفعات مختلف با ورودی های متفاوت با استفاده از multiprocessing به طور همزمان اجرا کنیم .
سورس تغییر یافته :
import rarfile import time import multiprocessing as m
def testpassword(password): # important try: rar_file.setpassword(password) rar_file.testrar() end = time.time() print("*"*50) print("Password : {} . find in {} seconds".format(password , end-start)) os.exit(0) except: pass
rarfile_address = input("RarFile : ") passwordlist_address = input("Password List : ")
rar_file = rarfile.RarFile(rarfile_address)
passwordlist = open(passwordlist_address)
start = time.time() for password in passwordlist: while len(m.active_children()) > 16 : # important continue password = password.strip("\n") p = m.Process(target = testpassword , args= (password,)) # important p.start()
قسمت های تغییر یافته و مهم کد با کامنت important # مشخص شده اند . اولین قسمت مهم کد تابع testpassword هست که تعریف شده و طبق توضیحاتی که بالا دادم قراره یه ورودی بگیره و اونو روی فایل وینرار تست کنه . اگه درست بود پسوورد چاپ کنه پسوورد درسته و اگه درست نبود که هیچی .
قسمت بعدی حلقه ی while داخل حلقه for هستش . ما با این حلقه while مشخص کردیم تا زمانی که تعداد process های اجرا شده بیش از 16 تا است ، از ادامه تست کردن پسوورد ها دست بکشه . این باعث میشه حداکثر 16 تا پسوورد یا همزمان تست بشه یا 16 process همزمان اجرا بشه . اینکه اینا چنتا باشن بستگی به قدرت سیستمتون داره .
پس از حلقه ی while هم که میدونید . با استفاده از تابع m.Process یک Process برای هر پسوورد ساختیم و در ورودی target گفتیم تابع تست پسوورد که بالا نوشتیم رو اجرا کنه و با استفاده از ورودی args ورودی های تابع testpassword رو بهش دادیم که همون پسووردی هستش که میخوایم تست کنیم . توجه کنید چون ورودی args حتما باید یک تاپل باشه بنابراین ما یک کاما (,) اضافه در پرانتز ها گذاشتیم تا تبدیل به تاپل بشه .
بعد از اجرای اسکریپت میبینیم که با زمانی تقریبا نصف زمان قبلی فایل رو کرک میکنه . البته فایل های وینرار کلا کرکینگشون یه ذره سرعتش پایینه چون ما از کتابخونه ای استفاده میکنیم برای کرک وینرار که خودش به طور پیشفرض بین تست هر پسوورد یک تاخیر کوچیک میندازه .
زمان کرک کردن ابزار عادی (بدون قابلیت پردازش موازی) :
زمان کرک کردن ابزار با قابلیت پردازش موازی :
تعداد پسوورد های پسوورد لیست هم حدودا 500 تا بوده . همینطور که گفتم کرک فایل های وینرار به طور ذاتی زمان زیادی میخواد . ولی با استفاده از تکنیکی که در این پست یاد گرفتیم میتونید کار های فوق العاده ای بکنید .
در پست های بعدی در مورد مبحث MultiThreading و کاربرد های اون نیز صحبت میکنیم :)
امیدوارم مفید واقع بشه .
یا حق !
Telegram Channel : @mrpythonblog