파이썬 실전/브루트 포스

파이썬 실전) 브루트 포스 프로그램 2. 멀티스레딩을 이용한 브루트 포스

마리사라 2021. 3. 21. 16:31
반응형

이번 강의는 파이썬의 멀티스레딩을 이용하여 브루트 포스를 하는 방법에 대해서 알려드리려고 합니다.


1. 멀티스레딩

기본적으로 프로그램은 한 번에 하나의 명령을 수행합니다. 그 속도가 매우 빠르기 때문에 일반적인 상황에서는 문제가 되지 않습니다만, 브루트 포스처럼 엄청난 양의 연산을 해야 되는 경우에는 하나씩 처리하는데 시간이 많이 걸립니다. 그렇기에 나온 것이 바로 멀티스레딩입니다.

 

Intel SMT 기술

예를 들어 자동차를 만든다고 가정할 때, 한 명이 하나의 자동차를 만드는 것은 매우 힘든 일입니다. 하지만 100명이 붙어서 만든다면 그 힘은 현저히 줄어들 것입니다. 이러한 원리를 컴퓨터에서도 적용할 수 있습니다.

 

먼저 멀티스레딩은 두 가지로 나누어집니다. 하나는 일시적 멀티스레딩이며, 나머지는 동시 멀티스레딩입니다.

 

일시적 멀티스레딩여러 가지의 작업을 혼자서 하는 것과 같습니다. 1번 작업을 하던 도중, 2번 작업이 급하게 추가되어 1번 작업을 잠시 미루고 2번 작업을 진행합니다. 이후 2번 작업이 끝나면 1번 작업으로 돌아와서 1번 작업을 마저 처리하게 됩니다. 이러한 멀티스레딩은 작업 속도에는 차이가 없지만 과거 싱글코어 CPU일 시절에 여러 프로그램을 동시에 처리하기 위해서 사용된 방법이라고 할 수 있으며, 이번 브루트 포스와는 크게 관련은 없는 방식입니다.

 

동시 멀티스레딩하나의 팀이 일을 분담해서 하는 것과 같습니다. 10명의 사람이 개인별로 10개의 작업을 맡아서 총 100개의 작업을 하는 것입니다. 동시 멀티스레딩은 하던 작업을 멈추고 다른 작업을 하는 것이 아닌, 동시에 진행하는 것이기 때문에 성능이 눈에 띄게 향상되는 효과가 있습니다. 단점으로는 동시 멀티스레딩을 하기 위해서는 스레드나 CPU가 동시 처리가 가능할 정도의 자원이 필요하다는 점이 있습니다. 그리고 이번 브루트 포스에서 사용되는 멀티스레딩이 바로 동시 멀티스레딩입니다.


2. 구현

파이썬에서 기본적인 코드들은 하나의 스레드에서만 진행되며, 멀티스레딩은 모듈이 필요합니다. 이때 사용되는 모듈은 multiprocessing과 Threading이 있습니다. 저는 6 코어를 가진 CPU를 사용하고 있고, I/O가 많이 필요한 작업이 아니기 때문에 multiprocessing을 사용하도록 하겠습니다.

import multiprocessing as mp

multiprocessing모듈은 anaconda를 기준으로 기본적으로 설치되어 있으며, 보통 mp로 표현합니다.

 

multiprocessing을 사용하기 위해 main함수에 multiprocessing코드를 넣어주어야 합니다.

if __name__ == '__main__':
    procs = []

    for process in [1, 2, 3, 4]:
        proc = mp.Process(target=calculate, args=(process,))
        procs.append(proc)
        proc.start()

    for proc in procs:
        proc.join()

mp.Process에서 for문을 통해 총 4개의 process를 만들고, 해당 process들을 실행합니다. 두 번째 for문에서는 join함수를 통해 process의 종료를 기다립니다.

 

이제 이전에 만들었던 계산 함수를 살짝 변형해 보겠습니다.

def calculate(process):
    print("프로세스 id : " + str(process))
    for i in attempt:
        if str("".join(i)) == password:
            print("비밀번호 : " + str("".join(i)))
            print("소요시간 : " + str(time.time() - start))

process를 인자로 받아서, 몇 번째 process인지 출력하고 계산을 시작하게 하였습니다.

 

프로세스 id 출력
4개의 Python이 실행됨

이때 프로세스는 랜덤 한 순서로 실행되며, 이것은 CPU의 스케쥴러에 따라 다릅니다.

순서가 섞인 경우

 

이렇게 하면 멀티스레딩은 만들어졌습니다. 하지만 이것은 브루트 포스에 전혀 도움이 되지 않습니다. 왜냐하면 4개의 프로세스가 완전히 같은 연산을 하기 때문입니다. 이것은 4자리의 숫자 암호로 되어있는 휴대폰을 4명에서 똑같이 0000부터 순서대로 누르는 것과 다름이 없습니다. 즉, 인원은 4명으로 늘었지만 효율성은 하나도 개선되지 않았습니다.

 

이제 브루트 포스에 맞게 코드를 수정해 보도록 하겠습니다. 먼저 가능한 경우의 수를 product 할 때, 길이를 원래 password길이보다 하나 작게 만듭니다.

attempt = product(possibility, repeat=len(password) - 1)

 

이후 process를 만드는 for문에서 1부터 4까지의 숫자가 아닌, 입력 가능한 종류가 적혀있는 문자열을 리스트 화하여 넣어줍니다.

for first in list(possibility):
    proc = mp.Process(target=calculate, args=(first,))
    procs.append(proc)
    proc.start()

 

이제 계산하는 함수를 다음과 같이 수정해 줍니다.

def calculate(first):
    print("첫번째 글자 : " + first)
    for i in attempt:
        if str(first + "".join(i)) == password:
            print("비밀번호 : " + str(first + "".join(i)))
            print("소요시간 : " + str(time.time() - start))

이렇게 만들게 되면 입력 가능한 문자를 첫 글자로 두고, 뒷부분만 연산하게 됩니다. 그렇기에 각각의 process가 하는 연산들은 모두 다른 연산을 하게 됩니다. 단점은 입력 가능한 모든 문자를 기준으로 process를 생성하기에 엄청난 숫자의 process가 생성되며, 연산하는 동안에는 컴퓨터에 무리를 줄 수 있습니다.

 

알파벳(26개) + 숫자(10개) = 36개의 process가 실행됨

위 사진에서 알 수 있듯, CPU를 거의 100%(표기는 99% 이지만 완전히 100% 사용은 불가능함) 사용하면서 연산하는 것을 볼 수 있습니다.

36개의 process 실행중

process가 시작할 때, 첫 번째 글자를 출력하도록 하였는데, 알파벳과 영어를 포함한 총 36개가 동시에 진행되는 것을 볼 수 있습니다.

결과 : 약 54.6초

대신 엄청난 연산을 통해 world1이라는 비밀번호를 54초 만에 찾아낸 것을 볼 수 있습니다. 이전 게시글에서 싱글 스레드로 연산한 237초보다 4배 정도 빠른 결과입니다.


3. 마치며

멀티스레딩을 통해서 브루트 포스를 더욱 빠르게 만들어 보았습니다. 일반적으로 저처럼 36개의 프로세스를 만들어서 연산하는 것은 추천드리지 않습니다. 중첩 for문 등을 사용하여 프로세스의 개수를 조절하거나 Process가 아닌, Pool을 사용하여 애초에 사용되는 프로세스의 개수를 결정하는 것을 추천드립니다.

import time
from itertools import product
import multiprocessing as mp


password = "world1"
number = "0123456789"
lowercase = "abcdefghijklmnopqrstuvwxyz"
uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
symbol = "!@#$%^&*()_+-=`~"
possibility = lowercase + number
attempt = product(possibility, repeat=len(password) - 1)
start = time.time()


def calculate(first):
    print("첫번째 글자 : " + first)
    for i in attempt:
        if str(first + "".join(i)) == password:
            print("비밀번호 : " + str(first + "".join(i)))
            print("소요시간 : " + str(time.time() - start))


if __name__ == '__main__':
    procs = []

    for first in list(possibility):
        proc = mp.Process(target=calculate, args=(first,))
        procs.append(proc)
        proc.start()

    for proc in procs:
        proc.join()
반응형