<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】從popmaster理解程序分析理論部分內容

    VSole2021-07-02 17:02:55

    前言

    本人水平有限,如有錯誤,歡迎指點。

    看到作者的出題思路是抽象代碼樹和污點分析,因為之前發了幾篇程序分析理論的文章但是一直沒有實踐,所以拿這道題自己實踐一下。

    污點分析在第一篇程序設計分析理論中,我寫的是語義分析,但在后面的文章中還沒有正式出現。關于抽象代碼樹,要在后面的抽象解釋才提到。

    過程間分析的內容還沒有發出來,可以先看這篇文章幫助理解后面的過程間分析

    我們已經介紹的只是數據流分析的部分內容,所以這篇文章首先會是根據數據流分析的過程間分析編寫一個遍歷以入口函數為起點的所有路徑的腳本實現代碼分析。

    最后我們還會看一看出題人的exp。

    編程思路

    這一道題是一個單輸入多輸出的問題,根據之前流的理論,我們應該從輸入開始分析。同時這道題是函數的跳轉,所以我們選擇過程間分析。根據上下文敏感的想法,我們在分支處應該是調用函數,主函數暫停,執行調用函數,調用函數退出的狀態是主函數重新開始的狀態。基于這一理論,我們定義一個函數,當出現分支時繼續調用自身函數,未出現分支時,如果依舊是過程函數,還是調用自身,如果是eval輸出函數 ,那么結束并輸出pop鏈

    除此之外,不少師傅在比賽時,選擇從eval開始判斷,先尋找不覆蓋變量的eval,再往上找。是使用了逆流分析的方法, 在之前的文章里,我們提到逆流分析一般是多輸入單輸出的情況,為什么這里單輸入多輸出會使用逆流分析呢,主要是只需要找到一條鏈,多輸出轉換成了單輸出,把這道題變成了單輸入單輸出的問題。當然,這并不意味著多輸出的程序不能用逆流分析的方法。只是缺少了一些過程間分析的內容。

    編寫過程

    類,函數,變量

    函數有過程的函數和結束的eval函數

    變量就是每一次要賦值的類名

    這一個程序主要是跳轉比較多,每一個具體執行沒有太多需要注意的安全問題,要結合過程間分析編寫代碼。

    我們之前提到過程間分析需要記錄跳轉前跳轉后狀態。在這里主要是記錄上一個類和下一個類是哪個的問題。這個時候想到樹結構能夠記錄上下同時滿足多個分支。

    在眾多樹結構中,我們想到結構歸納法幫助我們簡化邏輯,在這個程序中最小單元是函數,我們用函數當作樹狀圖的元素。我們記錄每一個函數屬于哪個類便于變量賦值,把函數是eval的當作終點。

    對于一個形如

    class Un9h32{    public $msePmc1;    public function NndvxI($HeqXE){        for($i = 0; $i < 35; $i ++){            $aKMhTe= $HeqXE;        }        if(method_exists($this->msePmc1, 'T0bEwD')) $this->msePmc1->T0bEwD($HeqXE);        if(method_exists($this->msePmc1, 'CdFIxA')) $this->msePmc1->CdFIxA($HeqXE);
    }public function gQLRqZ($cMmLT){    eval($cMmLT);
    }
    }
    

    的類,我們需要記錄類名,函數,函數指向的函數/eval

    1.我們要讓python讀取class后面的內容直到{ 記錄為類名

    public $直到 ; 記錄為變量名

    public function 直到 ( 記錄為函數名

    $this->xxx-> 直到( 記錄為下一個函數名

    封裝成一個以函數名為主鍵的表,

    先是一個類的封裝

    因為不會字符匹配所以僅僅通過位置獲取,通用性差,后面再修改

    代碼如下

    #coding=utf-8import os, sys, re
    i = 1j = 1
    class func:    class_name = ''      #類名    cur_function = ''     #當前函數名    next_function = []    #下一個函數名    var = ''         #變量名    bool_cur = False     #是否是eval    bool_next = False   #下一個函數是否多個    
        
    with open('asd.php') as file:    for content in file:        if 'class' in content:            class_name = content[6:12]        if 'public $' in content:            var_name = content[12:19]        if 'public function' in content:            f = func()            f.cur_function = content[20:26]            f.class_name = class_name            f.var = var_name             i = i + 1            j = 1        if 'eval' in content:            f.bool_cur = True        if 'method_exists' in content:            f.next_function.append(content[62:68])            if j>1 :                f.bool_next = True            j = j + 1print(f.class_name)print(f.cur_function)print(f.var)print(f.bool_next)print(f.bool_cur)print(f.next_function[1])
    

    拓展到多個類

    由于樹狀結構本人不會,所以用寫一個文件的方式構造樹結構

    #coding=utf-8import os, sys, re
    i = 1j = 1m = 1
    
    class func:    class_name = ''      #類名    cur_function = ''     #當前函數名    next_function = []    #下一個函數名    var = ''         #變量名    bool_cur = False     #是否是eval    bool_next = False   #下一個函數是否多個
    
    
    w_file=open('tree.txt','w')
    #讀取記錄    
    with open('asd.php') as file:    for content in file:        if 'class' in content:            class_name = content[6:12]        if 'public $' in content:            var_name = content[12:19]        if 'public function' in content:            if i > 1 :                w_file.write(f.cur_function)                w_file.write('    class_name ')                w_file.write(f.class_name)                while j > m:                    w_file.write('      f ')                    w_file.write(f.next_function[m-1])                    m = m + 1
                w_file.write('    var ')                w_file.write(f.var)            w_file.write('    bool_cur ')            w_file.write(str(f.bool_cur))            w_file.write('    bool_next ')            w_file.write(str(f.bool_next))            w_file.write('')
    
                            f = func()            f.cur_function = content[20:26]            f.class_name = class_name            f.var = var_name             i = i + 1            
        if 'eval' in content:        f.bool_cur = True    if 'method_exists' in content:        f.next_function.append(content[62:68])        if j>1 :            f.bool_next = True        j = j + 1
    

    處理數據,使數據成為鏈狀結構。

    定義函數

    #處理數據
    def paixu(content,keyword):        pre_content = content + keyword +'-->'    position = tree_content.find(keyword)    key_position = tree_content.find('f ',position)    keyword = tree_content[key_position+2:key_position+8]                 post_next = tree_content.find('bool_next',position)    num = tree_content[post_next+10:post_next+11]    num = int(num)    while num >= 1:        paixu(pre_content,keyword)        key_position = tree_content.find('f ',key_position+1)        keyword = tree_content[key_position+2:key_position+8]        num = num - 1    post_cur = tree_content.find('bool_cur',position)    current = tree_content[post_cur+9:post_cur+10]    current = int(current)    print(pre_content)    if current == 1:        w_file.write(pre_content+'')        print(pre_content+'')
    

    但是python遞歸深度有限制,同時可能會導致棧溢出

    僅僅針對這一道題,我們限制鏈的長度,先找到一條能夠到eval的鏈

    t = 0
    #處理數據
    def paixu(content,keyword,t):        pre_content = content + keyword +'-->'    if t > 30:        print(pre_content)        r_file.write(pre_content+'')        return 0
    position = tree_content.find(keyword)key_position = tree_content.find('f ',position)keyword = tree_content[key_position+2:key_position+8]             post_next = tree_content.find('bool_next',position)num = tree_content[post_next+10:post_next+11]num = int(num)while num >= 1:    t = t + 1    paixu(pre_content,keyword,t)    key_position = tree_content.find('f ',key_position+1)    keyword = tree_content[key_position+2:key_position+8]    num = num - 1post_cur = tree_content.find('bool_cur',position)current = tree_content[post_cur+9:post_cur+10]current = int(current)    if current == 1:    print(pre_content)    r_file.write('')    r_file.write(pre_content+'')    r_file.write('')    return 0
    

    3.從入口開始找路徑

    #coding=utf-8import os, sys, re
    sys.setrecursionlimit(3000)
    class func:    class_name = ''      #類名    cur_function = ''     #當前函數名    next_function = []    #下一個函數名    var = ''         #變量名    bool_cur = 0     #是否是eval    bool_next = 0   #下一個函數是否多個
    
    
    
    
    #讀取記錄    def read():    i = 1    j = 0    m = 1    k = 0    w_file=open('tree.txt','w')    with open('class.php') as file:        for content in file:            if 'class' in content:                class_name = content[6:12]            if 'public $' in content:                var_name = content[12:19]            if 'public function' in content:                if i > 1 :                    w_file.write('cur ')                    w_file.write(f.cur_function)                    w_file.write('    class_name ')                    w_file.write(f.class_name)                    while j >= m:                        w_file.write('      f ')                        w_file.write(f.next_function[m-1])                        m = m + 1
                    w_file.write('    var ')                    w_file.write(f.var)                w_file.write('    bool_cur ')                w_file.write(str(f.bool_cur))                w_file.write('    bool_next ')                w_file.write(str(f.bool_next))                w_file.write('')
                k = 0            f = func()            f.cur_function = content[20:26]            f.class_name = class_name            f.var = var_name             i = i + 1                    if 'eval' in content:            f.bool_cur = 1        if 'method_exists' in content:            f.next_function.append(content[62:68])            j = j + 1            k = k + 1            f.bool_next = k        else:            if '$this->' in content:                next_position = content.find('$this->')                true = content.find('->',next_position+7)                if true != -1:                    f.next_function.append(content[next_position+16:next_position+22])                    j = j + 1                    k = k + 1                    f.bool_next = k
    
    t = 0
    #處理數據
    def paixu(content,keyword,t):        position = tree_content.find('cur '+keyword)    pre_content = content + keyword +'-->'    post_cur = tree_content.find('bool_cur',position)    current = tree_content[post_cur+9:post_cur+10]    current = int(current)    if current == 1:        print('yes  '+pre_content)        r_file.write('yes  ')        r_file.write(pre_content+'')        r_file.write('')        return 0    if t > 30:        print('no  '+pre_content)        r_file.write(pre_content+'no')        return 0
    key_position = tree_content.find('f ',position)keyword = tree_content[key_position+2:key_position+8]             post_next = tree_content.find('bool_next',position)num = tree_content[post_next+10:post_next+11]num = int(num)while num >= 1:    t = t + 1    paixu(pre_content,keyword,t)    key_position = tree_content.find('f ',key_position+1)    keyword = tree_content[key_position+2:key_position+8]    num = num - 1    t = t - 1
    
        
    
        content = ''
    \#read()
    r_file=open('result.txt','w')with open('tree.txt') as w_file :    keyword = 'qZ5gIM'    tree_content = w_file.read()    position = tree_content.find(keyword)        content = keyword + '-->'    key_position = tree_content.find('f ',position)    keyword = tree_content[key_position+2:key_position+8]    post_next = tree_content.find('bool_next',position)    num = tree_content[post_next+10:post_next+11]    num = int(num)    while num >= 1:        t = t + 1        paixu(content,keyword,t)        key_position = tree_content.find('f ',key_position+1)        keyword = tree_content[key_position+2:key_position+8]        num = num - 1
    

    4.驗證可行

    要把有覆蓋變量的eval去除掉

    我們將read 讀取eval 的語句改成

    if ‘eval’ in content:f.bool_cur = 1if ‘=\’’ in content[:-1]:f.bool_cur = -1
    

    到這一步已經可以完成pop鏈查找

    但是我比較好奇是不是有一條沒有拼接的鏈

    所以繼續加限制

    #coding=utf-8import os, sys, re ,linecache
    sys.setrecursionlimit(3000)
    class func:    class_name = ''      #類名    cur_function = ''     #當前函數名    next_function = []    #下一個函數名    var = ''         #變量名    bool_cur = 0     #是否是eval    bool_next = 0   #下一個函數是否多個    token = 0
    
    
    
    
    #讀取記錄    def read():    token = ''    i = 1    j = 0    m = 1    k = 0    last_last_last_content = ''    last_last_content = ''    last_content = ''    now_content = ''    w_file=open('tree.txt','w')    with open('class.php') as file:        for content in file:            last_last_last_content = last_last_content            last_last_content = last_content            last_content = now_content            now_content = content            if 'class' in content:                class_name = content[6:12]            if 'public $' in content:                var_name = content[12:19]            if 'public function' in content:                if i > 1 :                    w_file.write('cur ')                    w_file.write(f.cur_function)                    w_file.write('    class_name ')                    w_file.write(f.class_name)                    while j >= m:                        w_file.write('      f ')                        w_file.write(f.next_function[m-1])                        m = m + 1
                        w_file.write('    var ')                        w_file.write(f.var)                    w_file.write('    bool_cur ')                    w_file.write(str(f.bool_cur))                    w_file.write('    bool_next ')                    w_file.write(str(f.bool_next))                    w_file.write('    token ')                    w_file.write(str(f.token))                    w_file.write('')
                    k = 0                f = func()                    f.cur_function = content[20:26]                token = ''                f.class_name = class_name                f.var = var_name                 i = i + 1
                if 'eval' in content:                f.bool_cur = 1                if '=\'' in last_content:                    f.bool_cur = 2                if '.\'' in last_last_content:                    num_content = last_last_last_content                    if '>' in last_last_last_content:                        num1_before_position = num_content.find('if(')                        num1_after_position = num_content.find('>')                        num2_after_position = num_content.find(')')                        num1 = num_content[num1_before_position+3:num1_after_position]                        num1 = int(num1)                        num2 = num_content[num1_after_position+1:num2_after_position]                        num2 = int(num2)                        if num1 > num2:                            f.bool_cur = 2    
                if 'method_exists' in content:                f.next_function.append(content[62:68])                j = j + 1                k = k + 1                f.bool_next = k                if '=\'' in last_content:                    f.token = 1                if '.\'' in last_last_content:                    num_content = last_last_last_content                    if '>' in last_last_last_content:                        num1_before_position = num_content.find('if(')                        num1_after_position = num_content.find('>')                        num2_after_position = num_content.find(')')                        num1 = num_content[num1_before_position+3:num1_after_position]                        num1 = int(num1)                        num2 = num_content[num1_after_position+1:num2_after_position]                        num2 = int(num2)                        if num1 > num2:                            f.token = 1                else:                if '$this->' in content:                    next_position = content.find('$this->')                    true = content.find('->',next_position+7)                    if true != -1:                        f.next_function.append(content[next_position+16:next_position+22])                        j = j + 1                        k = k + 1                        f.bool_next = k                    if '=\'' in last_content:                        f.token = 1                    if '.\'' in last_last_content:                        num_content = last_last_last_content                        if '>' in last_last_last_content:                            num1_before_position = num_content.find('if(')                            num1_after_position = num_content.find('>')                            num2_after_position = num_content.find(')')                            num1 = num_content[num1_before_position+3:num1_after_position]                            num1 = int(num1)                            num2 = num_content[num1_after_position+1:num2_after_position]                            num2 = int(num2)                            if num1 > num2:                                f.token = 1        
    t = 0
    #處理數據
    def paixu(content,keyword,t):        position = tree_content.find('cur '+keyword)    token_position = tree_content.find('token ',position)    token = tree_content[token_position+6:token_position+7]    token = int(token)    if token == 1:        return 0    pre_content = content + keyword +'-->'    post_cur = tree_content.find('bool_cur',position)    current = tree_content[post_cur+9:post_cur+10]    current = int(current)    if current == 2:        return 0    if current == 1:        print('yes  '+pre_content)        r_file.write('yes  ')        r_file.write(pre_content+'')        r_file.write('')        return 0    if t > 30:        print('no  '+pre_content)        r_file.write(pre_content+'no')        return 0
    
        key_position = tree_content.find('f ',position)    keyword = tree_content[key_position+2:key_position+8]                 post_next = tree_content.find('bool_next',position)    num = tree_content[post_next+10:post_next+11]    num = int(num)    while num >= 1:        t = t + 1        paixu(pre_content,keyword,t)        key_position = tree_content.find('f ',key_position+1)        keyword = tree_content[key_position+2:key_position+8]        num = num - 1        t = t - 1
    
        
    
        content = ''
    # read()
    r_file=open('result.txt','w')with open('tree.txt') as w_file :    keyword = 'qZ5gIM'    tree_content = w_file.read()    position = tree_content.find(keyword)        content = keyword + '-->'    key_position = tree_content.find('f ',position)    keyword = tree_content[key_position+2:key_position+8]    post_next = tree_content.find('bool_next',position)    num = tree_content[post_next+10:post_next+11]    num = int(num)    while num >= 1:        t = t + 1        paixu(content,keyword,t)        key_position = tree_content.find('f ',key_position+1)        keyword = tree_content[key_position+2:key_position+8]        num = num - 1
    

    我將所有有拼接的數據全部過濾,最后結果是沒有,可能是我代碼還有問題,也有可能本身就是希望過濾拼接字符來實現pop鏈

    出題人exp

    接下來我們看看一下出題人的exp,筆者學歷尚淺,只是粗淺看看而已。

    大家可以去看https://www.anquanke.com/post/id/244153,這是出題人寫的出題思路,exp的鏈接也在文章中

    main.php

    get_ast是獲取抽象代碼樹

    deal_class是處理類的函數 如果是聲明變量的類名當作變量處理,如果是方法的類名當作函數

    get_func_call_map是獲取函數調用關系,包括調用和被調用

    get_func_tag是判斷函數是否是敏感函數

    get_CFG 處理上下文敏感的函數

    function_node查找函數所在節點

    get_path是遍歷路徑

    deal_block處理代碼塊,判斷其中變量是否被污染

    run_CFG 模擬跳轉流程

    run_path模擬代碼執行過程

    main函數流程:

    先看有沒有危險函數,對危險函數的參數判斷有沒有被污染,然后遍歷路徑看看有沒有調用可控參數的危險函數的路徑。

    config.php

    config.php是對一些輸入輸出進行分類,標記用途和是否可能存在安全問題。同時對某些安全問題所需要用到的方法進行整理。

    class.php

    class.php是對類進行定義:

    func_call_info 是關于調用函數的語句的類,記錄了調用函數的語句的標簽

    func_path_info 記錄執行完整程序所調用的全部函數和變量

    func_call_map既記錄函數調用關系又標記函數是否敏感

    relation是記錄調用關系和調用條件的類

    CFG_class 是記錄上下文數據的類

    function.php是處理函數參數是否是用戶可控的

    visiter.php是各種情況的遍歷器,記錄進入節點前狀態,退出節點時狀態,將進入節點前狀態轉換成進入節點的狀態,將退出節點時狀態轉換成原進程繼續的狀態。

    在我的理解里,visiter的各種定義應該都可以歸納成下面的式子

    init(p) = l_n

    final(p) = {l_x}

    blocks(p) = {is^l_n,end^l_x} ∪ blocks(S)

    labels(p) = {l_n,l_x} ∪ labels(S)

    flow(p) = {(l_n,init(S))} ∪ flow(S) ∪ {(l,l_x) | l ∈ final(S) }

    也就是過程間分析的理論原型

    語義分析部分應該是使用了基于方法的分析,將特殊結構單獨分析,在最后組合起來。

    其他部分不妄加評論,以上內容如有錯誤,歡迎指點。exp分析如和原作者有沖突,一切以原作者為準。

    總結

    我的代碼主要是用了基于語句的分析,就是遍歷所有情況,每次遍歷判斷是否危險,出題人的代碼基于方法,更加高級。

    其他的關于過程間分析的上下文敏感原理是一樣的,就是我的代碼水平更差,記錄方式沒有通用性。

    對于危險函數或者污染參數肯定是出題人的代碼更加完善。我的代碼沒有通用性。

    希望這篇文章能夠幫助各位理解之前的程序分析理論。筆者水平有限,如有錯誤,歡迎指點。

    函數調用eval
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    CVE-2021-22205-Gitlab ExifTool RCE漏洞分析之一:ExifTool RCE漏洞CVE-2021-22004起源深入分析。
    RCE系統交互條件與受限環境下的利用
    本篇針對該JS中的字符串混淆進行還原。字符串是如何混淆的解密方式想要對字符串反混淆就要先分析該樣本是如何對字符串進行混淆的。而處于全局作用域的_0x1f1a68實際上也是對另一個函數的調用。
    動態分析側重檢測程序在實際運行的過程中污點數據的傳播情況,因此會遺漏運行過程中未觸發的路徑,且資源開銷較大。作者將這種情況產生新污點變量的污點源稱為 intermediate source。雖然 value-based 動態污點分析技術無法處理控制依賴以及加密數據,但是其在黑盒的處理場景下仍具備優勢。
    動態函數PHP中支持一個功能叫 variable function ,變量函數的意思。//最終是system;當一個變量后邊帶括號,那他就被視作一個函數。編譯器會解析出變量的值,然后會去找當前是否存在名為“system()”的函數并執行它。這里就不給實例了,很多免殺案例中都用到了這個特性。也是被瘋狂查殺的特征。回調函數回調函數,簡單來說就是一個函數不是由我直接調用,而是通過另一個函數去調用它。
    2022年04月12日,Apache官方發布了Apache Struts2的風險通告,漏洞編號為CVE-2021-31805,漏洞等級:高危,漏洞評分:8.5。Apache Struts 2是一個用于開發Java EE網絡應用程序的開放源代碼網頁應用程序架構。它利用并延伸了Java Servlet API,
    PHP代碼審計學習
    2021-09-18 12:43:18
    這是一次分享準備。自己還沒有總結這個的能力,這次就當個搬運工好了~~0x
    目前的處理方法對于語句間的控制依賴處理過于粗糙,且并未指明語句對應的路徑。并且在代碼片重組的過程中存在暴力堆疊的問題,使不在同一控制范圍內的語句彼此直接相鄰,從而導致了路徑不敏感。Design of SEVulDet下圖展示了SEVulDet的訓練階段與檢測階段,訓練階段相較于檢測階段多了打標簽這一環節。因此作者通過為PDG添加相應的葉子節點使其能夠確定控制范圍,避免了語義的缺失。
    就需要了解一下名稱空間python的名稱空間,是從名稱到對象的映射,在python程序的執行過程中,至少會存在兩個名稱空間。python中一切均為對象,均繼承于object對象,python的object類中集成了很多的基礎函數,假如我們需要在payload中使用某個函數就需要用object去操作。
    Latte-SSTI-Payloads總結
    2021-12-14 16:56:16
    TL;DR最近西湖論劍有一道使用Latte的題目,當時我也是用的偷雞辦法做的,當時時間限制就沒有仔仔細細的去尋找逃逸的辦法。直到賽后我發現逃逸的辦法很簡單{="${system}"}. 這里使用的是php的基礎語法,我就不過多贅述了。這個復雜變量網上也有很多文章。賽后我無聊的時候簡單看了下Latte,找了點后續的利用,比如獲取$this變量,還有任意代碼執行。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类