[Python3] 코드 난독화

Alexandria·2023년 9월 4일

Python3 Advanced

목록 보기
1/27
post-thumbnail

문자열화

import와 같은 코드는 문자열이 아니기 때문에 코드를 쉽게 읽을 수 있습니다.

따라서 import, 클래스의 함수 등을 문자열로 변환하여 난독화할 수 있습니다.

다음의 예제는 subprocess를 난독화하는 방법을 서술합니다. 먼저 원본은 다음과 같습니다.

import subprocess
p = subprocess.Popen(['whoami'], stdout=subprocess.PIPE, shell=True)
print(p.communicate())

import를 통한 모듈은 __import__로 변경해서 다음과 같이 문자열로 변경할 수 있습니다.

p = __import__("subprocess").Popen(["whoami"], stdout=__import__("subprocess").PIPE, shell=True)
print(p.communicate())

다음으로는 Popen이나 PIPE, communicate와 같은 속성 값들은 getattr를 이용하여 문자열로 변경할 수 있습니다.

p = getattr(__import__("subprocess"), "Popen")(["whoami"], stdout=getattr(__import__("subprocess"), 'PIPE'), shell=True)
print(getattr(p, "communicate")())

난독화 방법

✏️문자열 난독화

문자열의 난독화는 python 기본 문법을 이용하여 진행할 수 있습니다.

예를 들어, True의 클래스 이름은 bool이고 인덱싱을 통해 b를 추출할 수 있습니다.

print(True.__class__.__name__[0]) # b

이와 같은 패턴으로 모든 영문자를 정의할 수 있고, 대문자의 경우 lower 혹은 upper를 이용할 수 있으며 이를 다시 getattr로 난독화 시킬 수 있습니다.

다음의 스크립트는 Popen을 난독화한 예시입니다.

obfuscation_dict    = {
    'a':'().__class__.__eq__.__class__.__name__[2]',
    'b':'True.__class__.__name__[0]',
    'c':'().__class__.__eq__.__class__.__name__[11]',
    'd':'{}.__class__.__name__[0]',
    'e':'().__class__.__name__[-1]',
    'f':'vars.__class__.__name__[8]',
    'g':'True.__doc__[41]',
    'h':'vars.__class__.__name__[-3]',
    'i':'().__class__.__eq__.__class__.__name__[-5]',
    'j':'{}.__doc__[92]',
    'k':'{}.__doc__[104]',
    'l':'[].__class__.__name__[0]',
    'm':'vars.__class__.__name__[-6]',
    'n':'vars.__class__.__name__[6]',
    'o':'True.__class__.__name__[1]',
    'p':'str.__class__.__name__[2]',
    'q':'vars.__doc__[50]',
    'r':'().__class__.__eq__.__class__.__name__[-1]',
    's':'().__class__.__eq__.__class__.__name__[10]',
    't':'str.__class__.__name__[0]',
    'u':'().__class__.__name__[1]',
    'v':'{}.__doc__[109]',
    'w':'().__class__.__eq__.__class__.__name__[0]',
    'x':'True.__doc__[5]',
    'y':'str.__class__.__name__[1]',
    'z':'().__doc__[56]',
    '_':'abs.__class__.__name__[7]',
}

def Obfuscator(string):
    result = ''
    for s in string:
        if s.isupper(): s = 'getattr(' + Obfuscator(s.lower()) + ', ' + Obfuscator('upper') + ')()'
        try: result += (obfuscation_dict[s] + '+')
        except: result += (s + '+')
    return result[:-1]

print(Obfuscator('Popen')) # getattr(str.__class__.__name__[2], ().__class__.__name__[1]+str.__class__.__name__[2]+str.__class__.__name__[2]+().__class__.__name__[-1]+().__class__.__eq__.__class__.__name__[-1])()+True.__class__.__name__[1]+str.__class__.__name__[2]+().__class__.__name__[-1]+vars.__class__.__name__[6]

Popen이 난독화된 whoami를 호출하는 코드는 다음과 같습니다.

p = getattr(__import__("subprocess"), getattr(str.__class__.__name__[2], ().__class__.__name__[1]+str.__class__.__name__[2]+str.__class__.__name__[2]+().__class__.__name__[-1]+().__class__.__eq__.__class__.__name__[-1])()+True.__class__.__name__[1]+str.__class__.__name__[2]+().__class__.__name__[-1]+vars.__class__.__name__[6])(["whoami"], stdout=getattr(__import__("subprocess"), "PIPE"), shell=True)
print(getattr(p, "communicate")())

🔢숫자 난독화

문자열이 난독화가 되었다면 숫자도 난독화할 수 있습니다.

언더 바(_)의 경우 개수를 보기 난해하고, 기본 속성에도 사용되는 문자라서 가독성을 해치기 좋습니다.

1의 경우 다음과 같이 lambda를 이용하여 난독화를 수행할 수 있습니다.

print(
    (lambda _,__,___,____,_____:_)
    (*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))
) # 1

💡 편의상 여러 줄로 나누었지만, 한 줄로 쓴다면 구분하기 피곤해지기 때문에 효과적입니다.

다른 수의 경우 첫 번째 lambda에서 쌍 점(:) 뒤의 언더 바(_) 개수로 조절이 가능합니다.

print(
    (lambda _,__,___,____,_____:___) # <--- : 뒤의 _갯수를 통해 조정
    (*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))
)

사칙연산을 통해서도 수를 조절할 수 있습니다.

print(
    (lambda _,__,___,____,_____:__*___) # 2 * 3 = 6
    (*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))
)

코드 난독화

다음은 print("Hello")를 난독화한 코드입니다.

print(getattr(vars.__class__.__name__[-(lambda _,__,___,____,_____:___)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))], ().__class__.__name__[(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+str.__class__.__name__[(lambda _,__,___,____,_____:__)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+str.__class__.__name__[(lambda _,__,___,____,_____:__)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+().__class__.__name__[-(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+().__class__.__eq__.__class__.__name__[-(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))])()+().__class__.__name__[-(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+[].__class__.__name__[0]+[].__class__.__name__[0]+True.__class__.__name__[(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))])

해당 코드를 문자열로 취급한 뒤 base64로 인코딩을 수행합니다.

import base64

data = "print(getattr(vars.__class__.__name__[-(lambda _,__,___,____,_____:___)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))], ().__class__.__name__[(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+str.__class__.__name__[(lambda _,__,___,____,_____:__)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+str.__class__.__name__[(lambda _,__,___,____,_____:__)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+().__class__.__name__[-(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+().__class__.__eq__.__class__.__name__[-(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))])()+().__class__.__name__[-(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))]+[].__class__.__name__[0]+[].__class__.__name__[0]+True.__class__.__name__[(lambda _,__,___,____,_____:_)(*(lambda _,__,___: _(_,__,___))((lambda _,__,___:[__(___[(lambda: _).__code__.co_nlocals])]+_(_,__,___[(lambda _: _).__code__.co_nlocals:]) if ___ else []),lambda _: _.__code__.co_argcount,(lambda _:_,lambda _,__:_,lambda _,__,___:_,lambda _,__,___,____:_,lambda _,__,___,____,_____:_)))])"
print(base64.b64encode(data.encode()))

문자열을 코드로 변환하여 실행하는 evalcompile의 복합 사용으로 난독화한 코드를 다시 한번 난독화해볼 수 있습니다.

import base64

eval(compile(base64.b64decode(b'cHJpbnQoZ2V0YXR0cih2YXJzLl9fY2xhc3NfXy5fX25hbWVfX1stKGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl9fXykoKihsYW1iZGEgXyxfXyxfX186IF8oXyxfXyxfX18pKSgobGFtYmRhIF8sX18sX19fOltfXyhfX19bKGxhbWJkYTogXykuX19jb2RlX18uY29fbmxvY2Fsc10pXStfKF8sX18sX19fWyhsYW1iZGEgXzogXykuX19jb2RlX18uY29fbmxvY2FsczpdKSBpZiBfX18gZWxzZSBbXSksbGFtYmRhIF86IF8uX19jb2RlX18uY29fYXJnY291bnQsKGxhbWJkYSBfOl8sbGFtYmRhIF8sX186XyxsYW1iZGEgXyxfXyxfX186XyxsYW1iZGEgXyxfXyxfX18sX19fXzpfLGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl8pKSldLCAoKS5fX2NsYXNzX18uX19uYW1lX19bKGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl8pKCoobGFtYmRhIF8sX18sX19fOiBfKF8sX18sX19fKSkoKGxhbWJkYSBfLF9fLF9fXzpbX18oX19fWyhsYW1iZGE6IF8pLl9fY29kZV9fLmNvX25sb2NhbHNdKV0rXyhfLF9fLF9fX1sobGFtYmRhIF86IF8pLl9fY29kZV9fLmNvX25sb2NhbHM6XSkgaWYgX19fIGVsc2UgW10pLGxhbWJkYSBfOiBfLl9fY29kZV9fLmNvX2FyZ2NvdW50LChsYW1iZGEgXzpfLGxhbWJkYSBfLF9fOl8sbGFtYmRhIF8sX18sX19fOl8sbGFtYmRhIF8sX18sX19fLF9fX186XyxsYW1iZGEgXyxfXyxfX18sX19fXyxfX19fXzpfKSkpXStzdHIuX19jbGFzc19fLl9fbmFtZV9fWyhsYW1iZGEgXyxfXyxfX18sX19fXyxfX19fXzpfXykoKihsYW1iZGEgXyxfXyxfX186IF8oXyxfXyxfX18pKSgobGFtYmRhIF8sX18sX19fOltfXyhfX19bKGxhbWJkYTogXykuX19jb2RlX18uY29fbmxvY2Fsc10pXStfKF8sX18sX19fWyhsYW1iZGEgXzogXykuX19jb2RlX18uY29fbmxvY2FsczpdKSBpZiBfX18gZWxzZSBbXSksbGFtYmRhIF86IF8uX19jb2RlX18uY29fYXJnY291bnQsKGxhbWJkYSBfOl8sbGFtYmRhIF8sX186XyxsYW1iZGEgXyxfXyxfX186XyxsYW1iZGEgXyxfXyxfX18sX19fXzpfLGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl8pKSldK3N0ci5fX2NsYXNzX18uX19uYW1lX19bKGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl9fKSgqKGxhbWJkYSBfLF9fLF9fXzogXyhfLF9fLF9fXykpKChsYW1iZGEgXyxfXyxfX186W19fKF9fX1sobGFtYmRhOiBfKS5fX2NvZGVfXy5jb19ubG9jYWxzXSldK18oXyxfXyxfX19bKGxhbWJkYSBfOiBfKS5fX2NvZGVfXy5jb19ubG9jYWxzOl0pIGlmIF9fXyBlbHNlIFtdKSxsYW1iZGEgXzogXy5fX2NvZGVfXy5jb19hcmdjb3VudCwobGFtYmRhIF86XyxsYW1iZGEgXyxfXzpfLGxhbWJkYSBfLF9fLF9fXzpfLGxhbWJkYSBfLF9fLF9fXyxfX19fOl8sbGFtYmRhIF8sX18sX19fLF9fX18sX19fX186XykpKV0rKCkuX19jbGFzc19fLl9fbmFtZV9fWy0obGFtYmRhIF8sX18sX19fLF9fX18sX19fX186XykoKihsYW1iZGEgXyxfXyxfX186IF8oXyxfXyxfX18pKSgobGFtYmRhIF8sX18sX19fOltfXyhfX19bKGxhbWJkYTogXykuX19jb2RlX18uY29fbmxvY2Fsc10pXStfKF8sX18sX19fWyhsYW1iZGEgXzogXykuX19jb2RlX18uY29fbmxvY2FsczpdKSBpZiBfX18gZWxzZSBbXSksbGFtYmRhIF86IF8uX19jb2RlX18uY29fYXJnY291bnQsKGxhbWJkYSBfOl8sbGFtYmRhIF8sX186XyxsYW1iZGEgXyxfXyxfX186XyxsYW1iZGEgXyxfXyxfX18sX19fXzpfLGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl8pKSldKygpLl9fY2xhc3NfXy5fX2VxX18uX19jbGFzc19fLl9fbmFtZV9fWy0obGFtYmRhIF8sX18sX19fLF9fX18sX19fX186XykoKihsYW1iZGEgXyxfXyxfX186IF8oXyxfXyxfX18pKSgobGFtYmRhIF8sX18sX19fOltfXyhfX19bKGxhbWJkYTogXykuX19jb2RlX18uY29fbmxvY2Fsc10pXStfKF8sX18sX19fWyhsYW1iZGEgXzogXykuX19jb2RlX18uY29fbmxvY2FsczpdKSBpZiBfX18gZWxzZSBbXSksbGFtYmRhIF86IF8uX19jb2RlX18uY29fYXJnY291bnQsKGxhbWJkYSBfOl8sbGFtYmRhIF8sX186XyxsYW1iZGEgXyxfXyxfX186XyxsYW1iZGEgXyxfXyxfX18sX19fXzpfLGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl8pKSldKSgpKygpLl9fY2xhc3NfXy5fX25hbWVfX1stKGxhbWJkYSBfLF9fLF9fXyxfX19fLF9fX19fOl8pKCoobGFtYmRhIF8sX18sX19fOiBfKF8sX18sX19fKSkoKGxhbWJkYSBfLF9fLF9fXzpbX18oX19fWyhsYW1iZGE6IF8pLl9fY29kZV9fLmNvX25sb2NhbHNdKV0rXyhfLF9fLF9fX1sobGFtYmRhIF86IF8pLl9fY29kZV9fLmNvX25sb2NhbHM6XSkgaWYgX19fIGVsc2UgW10pLGxhbWJkYSBfOiBfLl9fY29kZV9fLmNvX2FyZ2NvdW50LChsYW1iZGEgXzpfLGxhbWJkYSBfLF9fOl8sbGFtYmRhIF8sX18sX19fOl8sbGFtYmRhIF8sX18sX19fLF9fX186XyxsYW1iZGEgXyxfXyxfX18sX19fXyxfX19fXzpfKSkpXStbXS5fX2NsYXNzX18uX19uYW1lX19bMF0rW10uX19jbGFzc19fLl9fbmFtZV9fWzBdK1RydWUuX19jbGFzc19fLl9fbmFtZV9fWyhsYW1iZGEgXyxfXyxfX18sX19fXyxfX19fXzpfKSgqKGxhbWJkYSBfLF9fLF9fXzogXyhfLF9fLF9fXykpKChsYW1iZGEgXyxfXyxfX186W19fKF9fX1sobGFtYmRhOiBfKS5fX2NvZGVfXy5jb19ubG9jYWxzXSldK18oXyxfXyxfX19bKGxhbWJkYSBfOiBfKS5fX2NvZGVfXy5jb19ubG9jYWxzOl0pIGlmIF9fXyBlbHNlIFtdKSxsYW1iZGEgXzogXy5fX2NvZGVfXy5jb19hcmdjb3VudCwobGFtYmRhIF86XyxsYW1iZGEgXyxfXzpfLGxhbWJkYSBfLF9fLF9fXzpfLGxhbWJkYSBfLF9fLF9fXyxfX19fOl8sbGFtYmRhIF8sX18sX19fLF9fX18sX19fX186XykpKV0p'),"<string>","exec"))
profile
IT 도서관

0개의 댓글