天天看點

腳本代碼混淆-Python篇-pyminifier(1)

前言

最近研究了一下腳本語言的混淆方法,比如 

python,javascript

等。腳本語言屬于動态語言,代碼大多無法直接編譯成二進制機器碼,發行腳本基本上相當于暴露源碼,這對于一些商業應用是無法接受的。是以對腳本代碼進行加強,成為很多應用的首選。代碼加強的一項措施是代碼混淆,增加逆向人員閱讀代碼邏輯的難度,拖延被破解的時間。

今天講解一下Python代碼的混淆方法,Python代碼一般用作web,提供服務接口,但也有一些桌面的應用,這一部分就需要對代碼進行混淆保護。以一個開源項目pyminifier (https://github.com/qiyeboy/pyminifier)來說明混淆的技巧方法,這個項目已經有4年沒更新,有一些bug,但是依然值得我們學習和入門。

項目結構

腳本代碼混淆-Python篇-pyminifier(1)

架構詳情:

analyze.py - 用于分析Python代碼
compression.py - 使用壓縮算法壓縮代碼
minification.py - 用于簡化Python代碼
obfuscate.py - 用于混淆Python 代碼
token_utils.py - 用于收集Python Token
      

  

從項目代碼中,可以看到pyminifier的混淆方法是基于Token的,即基于詞法分析,假如大家之前做過混淆的話,這應該屬于混淆的初級方案,因為這樣的混淆并不會修改代碼原有的邏輯結構。

提取Token

如何提取Python語言的Token呢?Python中提供了專門的包進行詞法分析: 

tokenize

。使用起來很簡單,在token_utils.py中代碼如下:

def listified_tokenizer(source):

"""Tokenizes *source* and returns the tokens as a list of lists."""
     io_obj = io.StringIO(source)
     return [list(a) for a in tokenize.generate_tokens(io_obj.readline)]      

首先讀取源檔案,然後通過tokenize.generate_tokens生成token清單。咱們就将這個提取token的函數儲存起來,然後讓他自己提取自己,看一下token清單的結構。

[[1, 'def', (1, 0), (1, 3), 'def listified_tokenizer(source):\n'],
[1, 'listified_tokenizer', (1, 4), (1, 23), 'def listified_tokenizer(source):\n'],
[53, '(', (1, 23), (1, 24), 'def listified_tokenizer(source):\n'],
[1, 'source', (1, 24), (1, 30), 'def listified_tokenizer(source):\n'],
[53, ')', (1, 30), (1, 31), 'def listified_tokenizer(source):\n'],
[53, ':', (1, 31), (1, 32), 'def listified_tokenizer(source):\n'],
[4, '\n', (1, 32), (1, 33), 'def listified_tokenizer(source):\n'],
......      

每一個Token對應一個list,以第一行 

[1,'def',(1,0),(1,3),'def listified_tokenizer(source):\n']

為例子進行解釋:

  1. 1代表的是token的類型
  2. def是提取的token字元串
  3. (1, 0)代表的是token字元串的起始行與列
  4. (1, 3)代表的是token字元串的結束行與列
  5. 'def listified_tokenizer(source):\n' 代表所在的行

Token還原代碼

能從源檔案中提取token 清單,如何從token清單還原為源代碼呢?其實很簡單,因為提取token 清單裡面有位置資訊和字元串資訊,是以進行字元串拼接即可。

def untokenize(tokens):
    """
    Converts the output of tokenize.generate_tokens back into a human-readable
    string (that doesn't contain oddly-placed whitespace everywhere).
    .. note::

        Unlike :meth:`tokenize.untokenize`, this function requires the 3rd and
        4th items in each token tuple (though we can use lists *or* tuples).
    """
    out = ""
    last_lineno = -1
    last_col = 0
    for tok in tokens:
        token_string = tok[1]
        start_line, start_col = tok[2]
        end_line, end_col = tok[3]
        # The following two conditionals preserve indentation:
        if start_line > last_lineno:
            last_col = 0
        if start_col > last_col and token_string != '\n':
            out += (" " * (start_col - last_col))
        out += token_string
        last_col = end_col
        last_lineno = end_line
    return out      

精簡與壓縮代碼

在pyminifier中,有兩個縮小Python代碼的方法:一個是精簡方式,另一個是使用壓縮算法的方式。

精簡

在minification.py中使用的是精簡方式,具體代碼如下:

def minify(tokens, options):
    """
    Performs minification on *tokens* according to the values in *options*
    """
    # Remove comments
    remove_comments(tokens)
    # Remove docstrings
    remove_docstrings(tokens)
    result = token_utils.untokenize(tokens)
    # Minify our input script
    result = multiline_indicator.sub('', result)
    result = fix_empty_methods(result)
    result = join_multiline_pairs(result)
    result = join_multiline_pairs(result, '[]')
    result = join_multiline_pairs(result, '{}')
    result = remove_blank_lines(result)
    result = reduce_operators(result)
    result = dedent(result, use_tabs=options.tabs)
    return result       

上面的代碼總共使用了9種方法來縮小腳本的體積:

remove_comments

去掉代碼中的注釋,但是有兩類要保留:1.腳本解釋器路徑 2. 腳本編碼

#!/usr/bin/env python
# -*- coding: utf-8 -*-      

remove_docstrings

去掉doc所指定的内容,example:

__doc__ = """\
Module for minification functions.

"""      

fix_empty_methods

修改空函數變成pass

def myfunc():
'''This is just a placeholder function.'''      

轉化為:

def myfunc():pass      

join_multiline_pairs

(1) 第一種情況:

test = (
"This is inside a multi-line pair of parentheses"
)      
test = ( "This is inside a multi-line pair of parentheses")      

(2)第二種情況:

test = [
"This is inside a multi-line pair of parentheses"
]      

轉化為:

test = [ "This is inside a multi-line pair of parentheses"]
      

(3)第三種情況:

test = {


"parentheses":"This is inside a multi-line pair of parentheses"


}      
test = { "parentheses":"This is inside a multi-line pair of parentheses"}      

remove_blank_lines

移除空白行。

test = "foo"

 

test2 = "bar"      
test = "foo"
test2 = "bar"      

reduce_operators

移除操作符之間的空格。

def foo(foo, bar, blah):
    test = "This is a %s" % foo      

修改為:

def foo(foo,bar,blah):
    test="This is a %s"%foo
      

dedent

替換代碼間的縮進,比如替換成單個空格

def foo(bar):

    test = "This is a test"      
def foo(bar):

 test = "This is a test"
      

壓縮

在這個項目中的compression.py,提供了4種代碼壓縮的方法,其中3個原理是一樣,隻不過使用的壓縮算法不一樣。

bz2,gz,lzma 壓縮執行原理

假如建立一個1.py,并儲存如下内容:

if __name__=="__main__":

    print(__name__)      

以bz2為例子,首先使用bz2算法壓縮代碼,然後轉化成base64編碼。

code='''


if __name__=="__main__":


    print(__name__)


'''

import bz2,base64
compressed_source = bz2.compress(code.encode("utf-8"))
print(base64.b64encode(compressed_source).decode('utf-8'))      

輸出:

QlpoOTFBWSZTWdfQmoEAAAHbgEAQUGAAEgAAoyNUACAAIam1NNGgaaFNMjExMQ2Za0TTvJepAjgXb2pDBBGoliFIT04+LuSKcKEhr6E1Ag==

代碼壓縮完成後,如何執行呢?其實就用到了exec這個函數/關鍵字。将編碼好的内容,先base64解碼,再使用bz2算法解壓縮,最後獲得真實的代碼,并使用exec執行

import bz2, base64
exec(bz2.decompress(base64.b64decode("QlpoOTFBWSZTWdfQmoEAAAHbgEAQUGAAEgAAoyNUACAAIam1NNGgaaFNMjExMQ2Za0TTvJepAjgXb2pDBBGoliFIT04+LuSKcKEhr6E1Ag==")))
      

 

這段代碼就代表了最原始的代碼,而使用gz,lzma壓縮方式,将bz2包換成zlib 或者lzma即可。

zip執行原理

可能很多朋友不知道,Python是可以直接運作zip檔案的(特别的),主要是為了友善開發者管理和釋出項目。Python能直接執行一個包含 __main__.py的目錄或者zip檔案。

舉個例子:

|—— ABC/

|—— A.py

|—— __main__.py      

示例代碼:

# A.py
def echo():

    print('ABC!')

# __main__.py
if __name == '__main__':

    import A
    A.echo()
      

可以直接将多個檔案壓縮成一個zip檔案,直接運作zip檔案就可以。目錄結構:

|—— ABC.zip/

|—— A.py

|—— __main__.py
      

運作情況:

$ python ABC.zip

ABC!      

未完待續。。。

最後

關注公衆号:七夜安全部落格

腳本代碼混淆-Python篇-pyminifier(1)
  • 回複【1】:領取 Python資料分析 教程大禮包
  • 回複【2】:領取 Python Flask 全套教程
  • 回複【3】:領取 某學院 機器學習 教程
  • 回複【4】:領取 爬蟲 教程
  • 回複【5】:領取 編譯原理 教程 
  • 回複【6】:領取 滲透測試 教程 
  • 回複【7】:領取 人工智能數學基礎 教程

本文章屬于原創作品,歡迎大家轉載分享,禁止修改文章的内容。尊重原創,轉載請注明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/

本文章屬于原創作品,歡迎大家轉載分享,禁止修改文章的内容。尊重原創,轉載請注明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/