【技术分享】从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
网络安全专家