<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tools/5199

    0x00 前言


    自動生成正則表達式這個話題其實國外有相關的研究,這次的使用的方法是我按一個論文的思路做實現的時候想到的。所謂的自動生成其實沒有想象的那么高大上,其實就是使用了一些非常簡單的思路進行組合。文中提到的方法已經使用python進行了實現,效果就我本身提供的幾個二逼的數據來說是不錯的,但是我不好評價真實場景下的效果,還有需要注意的就是我文中提供的代碼適合生成一些簡單的正則表達式。

    正則表達式本身的存在是為了匹配具有某個固定模式的文本,既然目標具有固定的模式,那么自動生成就是存在可能性的,先思考一個思路,我們寫一個正則表達式的時候我們在思考什么,我們在思考例子,比如我們想去匹配網址,那么我們就會思考不同的例子,比如www.baidu.com www.python.org au.fuck.com,都代表了不同的例子,我們會從其中抽取他們共有的模式編寫可以進行匹配的正則表達式語法。

    0x01 思考模式


    1. 正則表達式


    我們需要先去思考正則表達式本身所具有的模式,我們先做一個簡單的假設,假設正則表達式本身具有兩種模式,一種用于匹配具體的字符串 比如 “baidu”,“google”,“fuck you”,我們稱為 FullPatten,另外一種用于匹配模仿的字符串,比如純數字等等,稱為HalfPatten。

    2. 共有模式


    所謂的共有,就是都擁有的元素。比如 www.baidu.com www.google.com www.hello.com 。我們可以顯而易見的看出,三個例子共同存在的元素很多,比如都存在 "www.", ".com" 或者點號和 w,c,o,m....可以看出這我們思考的這幾個例子都有一些共同的模式。

    3. 丟一起


    現在把我們思考出來的模式組合到一起,就是說我們要做的事只是簡單的給計算機輸入幾個栗子,然后讓計算機得出共有的模式去生成我們想要的正則表達式。打個比方,我們現在有幾個栗子:

    example = [www.baidu.com, www.google.com, www.hello.com]
    

    然后計算機得出最長的的共有字符串:

    union = ['www.', '.com']
    

    我們轉化成正則表達式的模式后:

    regexPatten = [FullPatten, HalfPatten, FullPatten]
    

    然后迭代生成正則表達式:

    re = "www.\w+.com"
    

    好了,本次教程就到這里。

    開個玩笑,這種二逼的東西就怎么可能浪費我這幾天的美劇時間,只要思考幾個相反的例子我們就會很容易的得出上面基于規則生成的破逼玩意有多么坑爹。比如如果我們想匹配 “www.au.baidu.com” 比如 我們想匹配 .com 和 .au 結尾的怎么辦,或者我們只是想匹配所有的url?下面我們就需要去解決這個問題。

    4. 信息熵


    當年香農大神提出了信息熵這個概念用于描述事物的不確定性,什么是不確定性,比如1+1=?對于學過加法的你來說它具有不確定性嗎?沒有,那么他的信息熵就是0。如果問的是 你明天會不會在公司樓下碰見美女,它具有不確定性么?有,很大。。。。。

    0x02 數據的多樣性


    上面的機制的缺點在于沒有考慮到數據的多樣化,和外面的世界太危險。我們先思考HalfPatten然后將其寫成模塊,那么現在我們先思考HalfPatten的價值,就是用于匹配模糊的數據,比如 數字 \d ,字母\w 任意字符 . ,有一個問題,所謂的模糊數據是所有的,還是有固定長度的。比如上面的模糊字符串 google,hello,baidu 我們是匹配長度為 5到6的還是 直接任意字符 ?

    我們需要一種簡單暴力的方式可以描述數據的不確定性,就是我剛才說的信息熵。比如我們有一些需要模糊匹配的數字長度為 length= [5,6,5,6],那么它的信息熵就是 (1/2) · log(1/2) – (1/2) · log(1/2) ≈ 0.693 又或者說我們有 5,5,5,5這有的數字長度,那么他的shan就是0. 接下來我們轉化成代碼。

    #!python
    from math import log
    def shan(x):
        shan = 0.
        for i in set(x):
            shan -= (float(x.count(i))/len(x))*log(float(x.count(i))/len(x))
        return shan
    
    print shan([5,6,5,6])
    print shan([5,5,5,5])
    

    out:

    0.69314718056
    0.0
    

    1. HalfPatten


    現在我們可以來寫HalfPatten模塊了。我們可以從下面的代碼看出 halfPatten其實也存在 all zone length這三種模式,比如我們檢測長度的信息熵為0那么我們只需要單純的返回 \d{長度} 同時我們加入了一個 熵參數shan,只要長度的信息熵超過這個值就說明數據不確定性太大,匹配任意長度就行,低于這個值 我們就只需要匹配某個區間的長度就行。

    #!python
    class HalfPatten:
    
        def __init__(self,x):
            self.x = x
            self.type = "halfPatten"
            self.shan = 0.
            self.regShan= 0.
            self.x_length = [len(i) for i in x]
            for i in set(self.x_length):
                self.shan -= (float(self.x_length.count(i))/len(self.x_length))*log(float(self.x_length.count(i))/len(self.x_length))
            for c in set(self.x):
                self.regShan -= (float(self.x.count(c))/len(self.x))*log(float(self.x.count(c))/len(self.x))
    
    
        def detection(self,string):
            s = ''.join(string)
            if len(s)==0:
                return ""
            if s.isalnum():
                if s.isdigit():
                    return "\d"
                return "\w"
            else:
                return "."
    
        def gener(self,patten):
            if patten=="all":
                return "%s+"%(self.detection(self.x))
            elif patten=="zone":
                x_len = self.x_length
                x_len.sort()
    
                return "%s{%i,%i}"%(self.detection(self.x),x_len[0],x_len[-1])
            else:
                return "%s{%i}"%(self.detection(self.x),self.x_length[0])
    
        def regex(self,shanRule):
            if self.detection(self.x)=="":
                return ""
            if self.shan>0:
                if self.shan>shanRule:
                    return self.gener("all")
                else:
                    return self.gener("zone")
            else:
                return self.gener("length")
    
    def main(shan=1.):
        exmple = ['123','123']
        exmple2 = ['123','1234']
        half = HalfPatten(exmple)
        half2 = HalfPatten(exmple2)
        print half.regex(shan)
        print half2.regex(shan)
    
    if __name__ =="__main__":
        main()
        main(0.)
    

    out:

    \d{3}
    \d{3,4}
    \d{3}
    \d+
    

    我們可以看到我們得出了一個非常不錯的結果,那么接下來我們需要來寫FullPatten模塊。

    2. FullPatten


    fullPatten其實相對于half來說要簡單了許多,我們只需要根據確定性的字符串生成對應的正則表達式就好了。需要注意的是所謂的確定性其實也有不同的模式, 比如 我們想匹配 .com 和 .cn結尾的url而不想要其他的東西。現在寫成代碼:

    #!python
    class FullPatten:
    
        def __init__(self,x):
            self.x = x
            self.type = "fullPatten"
            self.shan = 0.
            self.regShan = 0.
    
        def gener(self,patten):
            if patten=="all":
                return "(%s)"%(self.x[0])
            else:
                return "(%s)"%('|'.join(list(set(self.x))))
    
        def regex(self,shanRule):
            if len(set(self.x))>1:
                return self.gener("half")
            else:
                return self.gener("all")
    
    def main():
        example = ['.fuck','.fuck']
        example1 = ['.com','.cn']
        full = FullPatten(example)
        full1 = FullPatten(example1)
        print full.regex(example)
        print full1.regex(example1)
    
    if __name__ == "__main__":
        main()
    

    out:

    (.fuck)
    (.com|.cn)
    

    接下來我們來把這兩個模塊組合到一起。

    3. 組合


    在把這幾個玩意丟一起之前我們需要先思考我們要的是什么,我們需要一個模塊,能夠根據我們提供的確定性的字符串對數據分割成序列的形式。寫成代碼:

    #!python
    class strspl:
    
        def __init__(self,y,shan):
            self.sentence = []
            self.y = y
            self.shan = shan
    
        def re_split(self,string):
            s = []
            for x in self.y:
                s.append([x[:x.index(string)],string,x[x.index(string)+len(string):]])
            half_1 = []
            full_1 = []
            half_2 = []
            for q,w,e in s:
                half_1.append(q)
                full_1.append(w)
                half_2.append(e)
            self.sentence.append(HalfPatten(half_1))
            self.sentence.append(FullPatten(full_1))
            self.sentence.append(HalfPatten(half_2))
    
            for l,i in enumerate(self.sentence):
                if i.shan!=0.:
                    if i.regShan<self.shan:
                        self.sentence[l]=FullPatten(i.x)
    
    def main(shan=1.5):
        example = ['asb.baidu.go','ww.baidu.com','www.baidu.fuck']
        a = strspl(example,shan)
        a.re_split('.baidu.')
        sentence=[]
        regex = []
        for i in a.sentence:
            sentence.append(i.type)
            regex.append(i.regex(0.))
        return sentence,''.join(regex)
    
    if __name__ == "__main__":
        s,r = main()
        print s
        print r
        s,r = main(0.)
        print s
        print r
    

    out:

    ['fullPatten', 'fullPatten', 'fullPatten']
    (ww|asb|www)(.baidu.)(go|com|fuck)
    ['halfPatten', 'fullPatten', 'halfPatten']
    \w+(.baidu.)\w+
    

    我們可以看到我們在模塊中又提供了一個參數,這個參數在于決定,.com 和 .cn 是full還是half,我們可以看到我們對保存著 類型序列的sentence進行了一次迭代,抽取其中的regShan進行判斷,regShan與剛才的長度不同,由數據本身的的差異得出,由于full的regShan寫死了0.所以其實是判斷half的。目的在于選定一個值來判斷 這個類型是否需要改為full類型。

    4. 丟一起


    現在我們需要把他組成一個成品了,現在我們已經有了各種模塊,我們就還需要一個能夠自動提取共有模式并且轉換的模塊,我們可以從上面的代碼看到,算法切割數據的方式是找到第一個匹配的字符串然后進行切割,那么,比如這樣的數據 www.baidu.com www.python.com.ho www.sb.com.as 我們經過切割后會變成 [www., baidu.com] [www., python.com] [www, .sb.com.as] 會被轉化成類似 www..{7,8} 類似的玩意,但是我們知道剩下的字符串還存在可以被提取的共有模式就是.com,所以我們設置一個迭代值iter,讓機器能夠多次提取共有模式,為了讓我們能夠更好的調整算法的參數,在有多個halfPatten的情況下,每次機器只會對其中一個halfPatten進行提取,每次迭代算法會選取其中 regShan最大的進行再提取,就是說,不確定性更大的數據其中存在共有模式的可能性越高,but一但其中提取不到共有模式就會自動退出循環。寫成代碼:

    #!python
    class Auto:
    
    
        def __init__(self,x,shan=.3,regShan=.8):
            self.x = x
            self.sentence = []
            self.U = ReU()
            self.shan = shan
            self.regShan = regShan
    
        def __guess(self,x,y):
            if y in x:
                return 1
            return 0
    
    
        def generation(self,iter=1):
            x = self.x
            location = 0.
            for i in range(iter):
                if len(self.sentence)==0:
                    str = strspl(self.x,self.regShan)
                    di = {}
                    di['data']= x
                    frame = pd.DataFrame(di)
                    #這個proccess是我之前寫的一個坑爹的模塊,其實就是提取共有的字符串,然后key = sorted(x,key=len,reverse=True) return key[0]
                    key = self.U.proccess(frame,'data')
                    sen = str.re_split(key)
                    self.sentence  = str.sentence
                    continue
                print "counting %i"%(i)
                chanceShan = 0.
                chance = None
                for l,i in enumerate(self.sentence):
                    if i.regShan !=0.:
                        if chanceShan<i.regShan:
                            chance = l
                            chanceShan = i.regShan
                if chance!=None:
    
                    di={}
                    di['data']=self.sentence[chance].x
                    str = strspl(di['data'],self.regShan)
                    frame = pd.DataFrame(di)
                    try:
                        key = self.U.proccess(frame,'data')
                    except IndexError,e:
                        break
                    sen = str.re_split(key)
                    self.sentence = self.sentence[:chance]+str.sentence+self.sentence[chance+1:]
    
    
        def build(self):
            reg = []
            shan = []
            for i in self.sentence:
                reg.append(i.regex(self.shan))
                shan.append(i.regShan)
            return shan,"".join(reg)
    
    def main(shan=.7,regShan=.3,it=1):
        s = ['www.asb.baids.com','www.ww.baidu.com','www.www.baidu.com']
        a = Auto(s,shan,regShan)
        a.generation(it)
        shan,reg =  a.build()
        print shan
        print reg
    
    
    if __name__=="__main__":
        main()
        main(0,0,)
        main(0,1.5)
        main(0,1.5,it=2)
        main(it=2)
    

    out:

    [1.0986122886681096, 0.0, 0.6365141682948128]
    .{6,7}(.bai).{6}
    [1.0986122886681096, 0.0, 0.6365141682948128]
    .+(.bai).{6}
    [0.0, 0.0, 0.6365141682948128]
    (www.ww|www.www|www.asb)(.bai).{6}
    counting 1
    [0.0, 0.0, 0.6365141682948128, 0.0, 0.0]
    (www.ww|www.www|www.asb)(.bai)\w{2}(.com)
    counting 1
    [0.0, 0.0, 1.0986122886681096, 0.0, 0.6365141682948128]
    (www.)\w{2,3}(.bai).{6}
    

    我們可以看到,三個參數的不同可以導致生成不同類型的正則表達式。其實到這里差不多就結束了,這里的代碼其實在于提供一種思路,里面的邏輯非常簡單,關鍵在于我們可以通過類似信息熵這種可以量化不確定性的方式來生成正則表達式,整個算法思路其實還有很多需要改進的地方。不過還是有另外一點需要講.

    5. 選取最佳


    我們可以看到上面的代碼必要時候需要我們自己調整參數,再數據較為簡單的情況下,不同參數的生成的正則其實是可以被枚舉完的,我們就需要一種可以度量性能的公式。

    enter image description here

    函數R 表示正則表達式匹配,t表示要匹配的文本 R(t)表示,正則表達式匹配后的值,s表示要匹配的值,函數d表示編輯距離。這樣我們就可以度量他的性能,枚舉枚舉所有的可能性并選取最小值。

    0x03 結語


    幾個事說下,如果有幸你打算把這個方法用在項目中請重寫代碼,我文中的代碼是為了快速實現而寫的,其中有很多需要優化的地方,支持的正則符號也不多,主要還是思路本身的可行性。

    97av