# Concolic模糊测试

> 原文： [https://www.fuzzingbook.org/html/ConcolicFuzzer.html](https://www.fuzzingbook.org/html/ConcolicFuzzer.html)

前面我们已经看到了如何使用动态污点而不是仅仅寻找程序崩溃来产生更智能的测试用例。 我们还看到了如何使用污点来更新语法，从而更加关注危险的方法。

尽管异味很有帮助，但未解释的字符串只是攻击向量之一。 在执行过程中的任何时候，我们能否再谈谈变量的属性？ 例如，我们可以确定地说一个函数将始终接收正确长度的缓冲区吗？ 共谋执行提供了解决方案。

在函数上进行*一致执行*的想法如下：我们从该函数的样本输入开始，然后在跟踪下执行该函数。 在执行的每个点都经过一个条件，我们以符号变量之间的关系形式保存所遇到的条件。 （*符号变量*可以看作是实变量的占位符，类似于x在代数中求解x时所用的符号。符号变量可用于指定关系，而无需实际求解它们。 ）

借助一致执行，您可以收集执行路径遇到的约束，并使用它来回答我们在程序执行路径上偏爱的任何时候有关程序行为的问题。 我们可以进一步使用conconolic执行来增强模糊性。

在本章中，我们将深入探讨如何以一致方式执行Python函数，以及如何使用conolicly执行来增强模糊测试。

**前提条件**

*   您应该已经阅读了关于的[一章。](Coverage.html)
*   您应该已经阅读了有关信息流的[一章。](InformationFlow.html)
*   熟悉 [SMT求解器](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories)的基本概念将很有用。

我们首先设置基础架构，以便可以使用先前定义的功能。

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.ConcolicFuzzer](ConcolicFuzzer.html) import <identifier>

```

然后利用以下功能。

本章定义了两个主要类：`SimpleConcolicFuzzer`和`ConcolicGrammarFuzzer`。 `SimpleConcolicFuzzer`首先使用样本输入来收集遇到的谓词。 然后，模糊器将随机谓词取反，以生成新的输入约束。 解决后，这些输入将产生探索与原始路径接近的路径的输入。 可以如下使用。

覆盖范围章节中的`cgi_decode()`功能[是目标程序。](Coverage.html)

```py
>>> from [Coverage](Coverage.html) import cgi_decode

```

我们首先使用`ConcolicTracer`获得约束。

```py
>>> with ConcolicTracer() as _:
>>>     _[cgi_decode]('abcd')

```

这些约束将添加到圆锥形模糊器中，如下所示：

```py
>>> scf = SimpleConcolicFuzzer()
>>> scf.add_trace(_)

```

然后，conconolic模糊测试器使用添加的约束来指导其模糊测试，如下所示：

```py
>>> for i in range(10):
>>>     v = scf.fuzz()
>>>     if v is None:
>>>         break
>>>     print(v)
%\x00
\x00\x00+\x00
\x00\x00+\x04
\x00\x00+\x02
\x00\x00\x00+\x00
\x00+\x00
\x00\x00+@
\x00+ 
\x00%\x00
\x00+\x08

```

`SimpleConcolicFuzzer`简单地探索样本输入所遍历的原始路径附近的所有路径。 它不使用任何来自模糊测试的反馈。 另一方面，`ConcolicGrammarFuzzer`可以在模糊测试中收集来自主题的反馈，并消除语法上遇到的一些限制，从而可以进行更深层次的模糊测试。 它的用法如下：

```py
>>> from [InformationFlow](InformationFlow.html) import INVENTORY_GRAMMAR, SQLException
>>> cgf = ConcolicGrammarFuzzer(INVENTORY_GRAMMAR)
>>> cgf.prune_tokens(prune_tokens)
>>> for i in range(10):
>>>     query = cgf.fuzz()
>>>     print(query)
>>>     with ConcolicTracer() as _:
>>>         with ExpectError():
>>>             try:
>>>                 res = _[db_select](query)
>>>                 print(repr(res))
>>>             except SQLException as e:
>>>                 print(e)
>>>         cgf.update_grammar(_)
>>>         print()
update NK5f set L=D,o=G,q=X where A!=S
Table ('NK5f') was not found

update vehicles set V9=B,y=r,E=E where r(U)/x(i)>b
Column ('V9') was not found

insert into V6S (V,R2,O) values ('2/','@')
Table ('V6S') was not found

update months set q=J1,Y=n,r=X where o-S*B!=(u!=q)
Column ('q') was not found

select e-e/B>Z(i)-(_) from [vehicles](https://docs.python.org/3/library/vehicles.html) [where](https://docs.python.org/3/library/where.html) 59.2!=S==C
Invalid WHERE ('(59.2!=S==C)')

delete from [months](https://docs.python.org/3/library/months.html) [where](https://docs.python.org/3/library/where.html) (M)*I<p+e(S)-R*O(_)*p(8,u)
Invalid WHERE ('(M)*I<p+e(S)-R*O(_)*p(8,u)')

insert into months (CRl,i,AW5,_j) values ('6')
Column ('CRl') was not found

select a(A),J from [K8z](K8z.html) [where](https://docs.python.org/3/library/where.html) [p](https://docs.python.org/3/library/p.html)-f-i<H
Table ('K8z') was not found

delete from [vehicles](https://docs.python.org/3/library/vehicles.html) [where](https://docs.python.org/3/library/where.html) [h](https://docs.python.org/3/library/h.html)(2,a)>((Sg90))+M5((az1(p)*r/W/V+b9(H)))
Invalid WHERE ('h(2,a)>((Sg90))+M5((az1(p)*r/W/V+b9(H)))')

select 9.7 from [months](https://docs.python.org/3/library/months.html)
[9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7]

```

## 跟踪约束

在有关[信息流](InformationFlow)的章节中，我们已经看到如何通过指示输入的哪一部分到达了有趣的地方来使用动态污点来指导模糊测试。 但是，动态污点跟踪在其可以传播的信息方面受到限制。 例如，我们可能想研究当输入的某些属性更改时会发生什么。

例如，假设我们有一个函数`factorial()`，该函数返回其输入的*阶乘值*。

```py
def factorial(n):
    if n < 0:
        return None
    if n == 0:
        return 1
    if n == 1:
        return 1
    v = 1
    while n != 0:
        v = v * n
        n = n - 1
    return v

```

我们以`5`的值行使功能。

```py
factorial(5)

```

```py
120

```

这足以探索该功能的所有功能吗？ 我们怎么知道？ 验证我们已探究所有功能的一种方法是查看获得的覆盖率。 首先，我们需要从覆盖的[章扩展`Coverage`类，以向我们提供覆盖弧。](Coverage.html)

```py
from [Coverage](Coverage.html) import Coverage

```

```py
import [inspect](https://docs.python.org/3/library/inspect.html)

```

```py
class ArcCoverage(Coverage):
    def traceit(self, frame, event, args):
        if event != 'return':
            f = inspect.getframeinfo(frame)
            self._trace.append((f.function, f.lineno))
        return self.traceit

    def arcs(self):
        t = [i for f, i in self._trace]
        return list(zip(t, t[1:]))

```

接下来，我们使用`Tracer`获得覆盖弧。

```py
with ArcCoverage() as cov:
    factorial(5)

```

现在，我们可以使用coverage弧线可视化获得的coverage。

```py
from [ControlFlow](ControlFlow.html) import PyCFG, CFGNode, to_graph, gen_cfg

```

```py
from [graphviz](https://docs.python.org/3/library/graphviz.html) import Source, Graph

```

```py
Source(to_graph(gen_cfg(inspect.getsource(factorial)), arcs=cov.arcs()))

```

<svg height="564pt" viewBox="0.00 0.00 422.50 564.00" width="423pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 560)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="101" y="-530.3">1: enter: factorial(n)</text></g> <g class="node" id="node7"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="101" y="-454.3">2: if: n < 0</text></g> <g class="edge" id="edge5"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="145" y="-18.3">1: exit: factorial(n)</text></g> <g class="node" id="node3"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="49" y="-166.3">3: return None</text></g> <g class="edge" id="edge1"><title>4->2</title></g> <g class="node" id="node4"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="126" y="-310.3">5: return 1</text></g> <g class="edge" id="edge2"><title>6->2</title></g> <g class="node" id="node5"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="196" y="-238.3">7: return 1</text></g> <g class="edge" id="edge3"><title>8->2</title></g> <g class="node" id="node6"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="233" y="-94.3">12: return v</text></g> <g class="edge" id="edge4"><title>13->2</title></g> <g class="edge" id="edge6"><title>3->4</title></g> <g class="node" id="node8"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="154" y="-382.3">4: if: n == 0</text></g> <g class="edge" id="edge7"><title>3->5</title></g> <g class="edge" id="edge8"><title>5->6</title></g> <g class="node" id="node9"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="259" y="-310.3">6: if: n == 1</text></g> <g class="edge" id="edge9"><title>5->7</title></g> <g class="edge" id="edge10"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="284" y="-238.3">8: v = 1</text></g> <g class="edge" id="edge11"><title>7->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="311" y="-166.3">9: while: n != 0</text></g> <g class="edge" id="edge12"><title>9->10</title></g> <g class="edge" id="edge16"><title>10->13</title></g> <g class="node" id="node13"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="336" y="-94.3">10: v = v * n</text></g> <g class="edge" id="edge14"><title>10->11</title></g> <g class="node" id="node12"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="372" y="-18.3">11: n = n - 1</text></g> <g class="edge" id="edge13"><title>12->10</title></g> <g class="edge" id="edge15"><title>11->12</title></g></g></svg>

我们看到路径`[1, 2, 4, 6, 8, 9, 10, 11, 12]`被覆盖（绿色），但是诸如`[2, 3]`，`[4, 5]`和`[6, 7]`之类的子路径未被探索（红色）。 我们需要的是生成输入的能力，以使`True`分支位于`2`处。 我们该怎么做？

## 辅助执行

一种方法是查看正在采用的执行路径，并收集该路径遇到的条件约束。 然后，我们可以尝试产生输入，使我们采取非遍历的路径。

首先，让我们逐步完成该功能。

```py
lines = [i[1] for i in cov._trace if i[0] == 'factorial']
src = {i + 1: s for i, s in enumerate(
    inspect.getsource(factorial).split('\n'))}

```

*   行（1）只是函数的入口点。 我们知道输入是`n`，它是一个整数。

```py
src[1]

```

```py
'def factorial(n):'

```

*   行（2）是谓词`n < 0`。 由于下一行是第（4）行，因此我们知道在执行路径的这一点上，谓词为`true`。

```py
src[2], src[3], src[4]

```

```py
('    if n < 0:', '        return None', '    if n == 0:')

```

我们注意到，这是未采用`true`分支的谓词之一。 我们如何在此处生成一个采用`true`分支的值？ 一种方法是使用符号变量来表示输入，对约束进行编码，并使用 *SMT求解器*来解决约束的否定问题。

正如我们在本章引言中提到的那样，可以将符号变量视为实变量的一种占位符，类似于在代数中求解`x`时的`x`。 这些变量可用于编码放置在程序中变量上的约束。 我们确定变量应遵循的约束条件，最后产生一个值，该值遵循所有施加的约束条件。

## SMT解算器

为了解决这些限制，可以使用*满意度模理论*（SMT）求解器。 SMT求解器构建在*可满足性*（SAT）求解器之上。 SAT求解器用于检查是否可以使用变量的任何赋值（例如`a = true, b = false`）来满足一阶逻辑（例如`(a | b ) & (~a | ~b)`）中的布尔公式。 SMT求解器将这些SAT求解器扩展到特定的背景理论-例如，*整数理论*或*字符串理论*。 也就是说，给定一个用字符串变量（例如`h + t == 'hello,world'`）表示为公式的字符串约束，可以使用了解*字符串理论*的SMT求解器来检查该约束是否可以满足以及是否可满足 ，为公式中使用的变量（例如`h = 'hello,', t = 'world'`）提供具体值的实例化。

在本章中，我们将使用SMT求解器`Z3`。

```py
import [z3](https://docs.python.org/3/library/z3.html)

```

为确保成功评估本章中使用的字符串约束，我们需要指定`z3str3`求解器。 此外，我们将Z3计算的超时设置为30秒。

```py
assert z3.get_version() == (4, 8, 0, 0)
z3.set_option('smt.string_solver', 'z3str3')
z3.set_option('timeout', 30 * 1000)  # milliseconds

```

对约束进行编码需要为输入`n`声明一个对应的符号变量。

```py
zn = z3.Int('n')

```

还记得`factorial()`中第2行的约束`(n < 0)`吗？ 现在，我们可以对约束进行如下编码。

```py
zn < 0

```

```py
n < 0

```

我们先前跟踪了`factorial(5)`。 我们看到，使用输入`5`，执行在谓词`n < 0`上执行了`else`分支。 我们可以将这种观察表达如下。

```py
z3.Not(zn < 0)

```

```py
Not(n < 0)

```

`z3.solve()`方法还可用于检查约束条件是否可满足，如果满足，则为变量提供一个值，以便满足约束条件。 例如，我们可以要求z3输入将采用`else`分支的输入，如下所示：

```py
z3.solve(z3.Not(zn < 0))

```

```py
[n = 0]

```

这是*解决方案*（尽管是微不足道的）。 SMT求解器可用于解决更困难的问题。 例如，这是一种求解二次方程的方法。

```py
x = z3.Real('x')
eqn = (2 * x**2 - 11 * x + 5 == 0)
z3.solve(eqn)

```

```py
[x = 5]

```

同样，这是*一种解决方案*。 我们可以要求z3给我们另一个解决方案，如下所示。

```py
z3.solve(x != 5, eqn)

```

```py
[x = 1/2]

```

实际上，`x = 5`和`x = 1/2`都是二次方程的解$ 2x ^ 2 -11x + 5 = 0 $

同样，我们可以要求 *Z3* 输入满足`factorial()`第2行编码的约束的输入，以便我们采用`if`分支。

```py
z3.solve(zn < 0)

```

```py
[n = -1]

```

也就是说，如果将`-1`用作`factorial()`的输入，则可以确保在执行期间采用第2行中的`if`分支。

让我们尝试在我们的报道中使用它。 在这里，`-1`是上面的解决方案。

```py
with cov as cov:
    factorial(-1)

```

```py
Source(to_graph(gen_cfg(inspect.getsource(factorial)), arcs=cov.arcs()))

```

<svg height="564pt" viewBox="0.00 0.00 422.50 564.00" width="423pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 560)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="101" y="-530.3">1: enter: factorial(n)</text></g> <g class="node" id="node7"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="101" y="-454.3">2: if: n < 0</text></g> <g class="edge" id="edge5"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="145" y="-18.3">1: exit: factorial(n)</text></g> <g class="node" id="node3"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="49" y="-166.3">3: return None</text></g> <g class="edge" id="edge1"><title>4->2</title></g> <g class="node" id="node4"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="126" y="-310.3">5: return 1</text></g> <g class="edge" id="edge2"><title>6->2</title></g> <g class="node" id="node5"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="196" y="-238.3">7: return 1</text></g> <g class="edge" id="edge3"><title>8->2</title></g> <g class="node" id="node6"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="233" y="-94.3">12: return v</text></g> <g class="edge" id="edge4"><title>13->2</title></g> <g class="edge" id="edge6"><title>3->4</title></g> <g class="node" id="node8"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="154" y="-382.3">4: if: n == 0</text></g> <g class="edge" id="edge7"><title>3->5</title></g> <g class="edge" id="edge8"><title>5->6</title></g> <g class="node" id="node9"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="259" y="-310.3">6: if: n == 1</text></g> <g class="edge" id="edge9"><title>5->7</title></g> <g class="edge" id="edge10"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="284" y="-238.3">8: v = 1</text></g> <g class="edge" id="edge11"><title>7->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="311" y="-166.3">9: while: n != 0</text></g> <g class="edge" id="edge12"><title>9->10</title></g> <g class="edge" id="edge16"><title>10->13</title></g> <g class="node" id="node13"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="336" y="-94.3">10: v = v * n</text></g> <g class="edge" id="edge14"><title>10->11</title></g> <g class="node" id="node12"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="372" y="-18.3">11: n = n - 1</text></g> <g class="edge" id="edge13"><title>12->10</title></g> <g class="edge" id="edge15"><title>11->12</title></g></g></svg>

好的，因此我们设法覆盖了更多图表。 让我们继续我们的原始输入`factorial(5)`：

*   在第（4）行中，我们遇到一个新的谓词`n == 0`，为此我们再次采用了假分支。

```py
src[4]

```

```py
'    if n == 0:'

```

遵循该路径直到该点所需的谓词如下。

```py
predicates = [z3.Not(zn < 0), z3.Not(zn == 0)]

```

*   如果继续执行第（6）行，则会遇到另一个谓词，为此，我们再次使用了`false`分支

```py
src[6]

```

```py
'    if n == 1:'

```

到目前为止遇到的谓词如下

```py
predicates = [z3.Not(zn < 0), z3.Not(zn == 0), z3.Not(zn == 1)]

```

要获得（6）处的分支，我们基本上必须服从谓词直到该点，但要反转最后一个谓词。

```py
last = len(predicates) - 1
z3.solve(predicates[0:-1] + [z3.Not(predicates[-1])])

```

```py
[n = 1]

```

我们在这里所做的是使用具体的值跟踪与特定输入`factorial(5)`对应的执行，并与之保持*符号阴影变量*，使我们能够捕获约束。 正如我们在引言中所提到的，这种使用符号变量跟踪具体执行的特定执行方法称为 *Concolic Execution* 。

我们如何使这一过程自动化？ 一种方法是使用与[信息流](InformationFlow.html)上的章节相似的基础结构，并使用Python继承来创建可跟踪具体执行的符号代理对象。

## Concolic Tracer

假定存在一个在其下执行程序的符号上下文（即在程序执行中使用的符号变量），我们定义了一个称为`ConcolicTracer`的上下文管理器，用于跟踪上下文。

`ConcolicTracer`接受一个参数，该参数包含到目前为止所看到的符号变量的声明以及前提条件（如果有）。

```py
class ConcolicTracer:
    def __init__(self, context=None):
        self.context = context if context is not None else ({}, [])
        self.decls, self.path = self.context

```

我们为上下文管理器添加了`enter`和`exit`方法。

```py
class ConcolicTracer(ConcolicTracer):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        return

```

我们使用自省来确定函数的参数，该参数已挂接到`getitem`方法中。

```py
class ConcolicTracer(ConcolicTracer):
    def __getitem__(self, fn):
        self.fn = fn
        self.fn_args = {i: None for i in inspect.signature(fn).parameters}
        return self

```

最后，使用`call`方法调用函数本身。

```py
class ConcolicTracer(ConcolicTracer):
    def __call__(self, *args):
        self.result = self.fn(*self.concolic(args))
        return self.result

```

现在，我们将`concolic()`定义为透明函数。 稍后将对其进行修改以生成符号变量。

```py
class ConcolicTracer(ConcolicTracer):
    def concolic(self, args):
        return args

```

可以如下使用

```py
with ConcolicTracer() as _:
    _[factorial](1)

```

```py
_.context

```

```py
({}, [])

```

`context`为空，因为我们尚未将必要的基础结构连接到`ConcolicTracer`。

### 辅助代理对象

现在，我们定义可用于策略跟踪的策略代理对象。 首先，我们定义`zproxy_create()`方法，该方法给出了一个类名，正确地创建了该类的实例，以及符号对应变量，并将该符号变量注册到上下文信息`context`中。

```py
def zproxy_create(cls, sname, z3var, context, zn, v=None):
    zv = cls(context, z3var(zn), v)
    context[0][zn] = sname
    return zv

```

#### 布尔值的代理类

首先，我们定义`zbool`类，该类用于跟踪遇到的谓词。 它是一个包装类，包含符号（`z`）和具体（`v`）值。 具体值用于确定采用哪个路径，而符号值用于收集遇到的谓词。

初始化分为两个部分。 第一个是使用`zproxy_create()`正确初始化和注册与传递的参数相对应的影子符号变量。 仅在需要首先初始化符号变量时使用此选项。 在所有其他情况下，将使用预先存在的符号值来调用构造函数。

```py
class zbool:
    @classmethod
    def create(cls, context, zn, v):
        return zproxy_create(cls, 'Bool', z3.Bool, context, zn, v)

    def __init__(self, context, z, v=None):
        self.context, self.z, self.v = context, z, v
        self.decl, self.path = self.context

```

这是如何使用它。

```py
with ConcolicTracer() as _:
    za, zb = z3.Ints('a b')
    val = zbool.create(_.context, 'my_bool_arg', True)
    print(val.z, val.v)
_.context

```

```py
my_bool_arg True

```

```py
({'my_bool_arg': 'Bool'}, [])

```

##### 否定编码公式

`zbool`类允许否定其具体值和符号值。

```py
class zbool(zbool):
    def __not__(self):
        return zbool(self.context, z3.Not(self.z), not self.v)

```

Here is how it can be used.

```py
with ConcolicTracer() as _:
    val = zbool.create(_.context, 'my_bool_arg', True).__not__()
    print(val.z, val.v)
_.context

```

```py
Not(my_bool_arg) False

```

```py
({'my_bool_arg': 'Bool'}, [])

```

##### 在条件[上注册谓词](#Registering-Predicates-on-Conditionals)

`zbool`类用于跟踪程序执行过程中出现的布尔条件。 它通过在上下文中注册相应的符号表达式来跟踪此类条件。

```py
class zbool(zbool):
    def __bool__(self):
        r, pred = (True, self.z) if self.v else (False, z3.Not(self.z))
        self.path.append(pred)
        return r

```

`zbool`类可用于跟踪执行期间遇到的布尔值和条件。 例如，我们可以对`factorial()`中第6行遇到的条件进行编码，如下所示：

首先，我们定义具体值（`ca`）及其阴影符号变量（`za`）。

```py
ca, za = 5, z3.Int('a')

```

然后，将其包装在`zbool`中，并在条件中使用它，强制条件在上下文中注册。

```py
with ConcolicTracer() as _:
    if zbool(_.context, za == z3.IntVal(5), ca == 5):
        print('success')

```

```py
success

```

我们可以按以下方式检索已注册的条件。

```py
_.path

```

```py
[5 == a]

```

#### 整数的代理类

接下来，我们为`int`定义符号包装器`zint`。 此类跟踪`context`中使用的int变量和遇到的谓词。 最后，它还会保留所使用的具体值，以便可以用来确定采用的路径。 随着`zint`扩展原始`int`类，我们必须定义*新的*方法以将其打开以进行扩展。

```py
class zint(int):
    def __new__(cls, context, zn, v, *args, **kw):
        return int.__new__(cls, v, *args, **kw)

```

与`zbool`一样，初始化分为两部分。 如果正在注册新的符号参数，则首先使用`create()`，然后使用常规初始化。

```py
class zint(zint):
    @classmethod
    def create(cls, context, zn, v=None):
        return zproxy_create(cls, 'Int', z3.Int, context, zn, v)

    def __init__(self, context, z, v=None):
        self.z, self.v = z, v
        self.context = context

```

`zint`对象的`int`值是其具体值。

```py
class zint(zint):
    def __int__(self):
        return self.v

    def __pos__(self):
        return self.v

```

使用这些代理如下。

```py
with ConcolicTracer() as _:
    val = zint.create(_.context, 'int_arg', 0)
    print(val.z, val.v)
_.context

```

```py
int_arg 0

```

```py
({'int_arg': 'Int'}, [])

```

`zint`类通常用于与其他`int`进行算术运算或比较。 这些`int`可以是变量，也可以是常数。 我们定义了一个辅助方法`_zv()`，该方法检查给定值是哪种`int`，并产生正确的符号等效项。

```py
class zint(zint):
    def _zv(self, o):
        return (o.z, o.v) if isinstance(o, zint) else (z3.IntVal(o), o)

```

It can be used as follows

```py
with ConcolicTracer() as _:
    val = zint.create(_.context, 'int_arg', 0)
    print(val._zv(0))
    print(val._zv(val))

```

```py
(0, 0)
(int_arg, 0)

```

##### 整数之间的相等性

可以使用 *ne* 和 *eq* 比较两个整数的相等性。

```py
class zint(zint):
    def __ne__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, self.z != z, self.v != v)

    def __eq__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, self.z == z, self.v == v)

```

如果要比较的int位于左侧，我们还使用 *eq* 定义 *req* 。

```py
class zint(zint):
    def __req__(self, other):
        return self.__eq__(other)

```

可以如下使用。

```py
with ConcolicTracer() as _:
    ia = zint.create(_.context, 'int_a', 0)
    ib = zint.create(_.context, 'int_b', 0)
    v1 = ia == ib
    v2 = ia != ib
    v3 = 0 != ib
    print(v1.z, v2.z, v3.z)

```

```py
int_a == int_b int_a != int_b 0 != int_b

```

##### 整数之间的比较

还可以比较整数的排序方式，下面定义了此方法。

```py
class zint(zint):
    def __lt__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, self.z < z, self.v < v)

    def __gt__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, self.z > z, self.v > v)

```

我们使用比较和相等运算符来提供其他缺少的运算符。

```py
class zint(zint):
    def __le__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, z3.Or(self.z < z, self.z == z),
                     self.v < v or self.v == v)

    def __ge__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, z3.Or(self.z > z, self.z == z),
                     self.v > v or self.v == v)

```

这些功能可以如下使用。

```py
with ConcolicTracer() as _:
    ia = zint.create(_.context, 'int_a', 0)
    ib = zint.create(_.context, 'int_b', 1)
    v1 = ia > ib
    v2 = ia < ib
    print(v1.z, v2.z)
    v3 = ia >= ib
    v4 = ia <= ib
    print(v3.z, v4.z)

```

```py
int_a > int_b int_a < int_b
Or(int_a > int_b, int_a == int_b) Or(int_a < int_b, int_a == int_b)

```

##### 整数[的二进制运算符](#Binary-Operators-for-Integers)

如 [Python文档](https://docs.python.org/3/reference/datamodel.html#object.__add__)中所述，我们为整数实现了相关的算术运算符。 （注释掉的运算符不能直接用于`z3.ArithRef`。如果需要，它们需要单独实现。有关如何实现的信息，请参见练习。）

```py
INT_BINARY_OPS = [
    '__add__',
    '__sub__',
    '__mul__',
    '__truediv__',
    # '__div__',
    '__mod__',
    # '__divmod__',
    '__pow__',
    # '__lshift__',
    # '__rshift__',
    # '__and__',
    # '__xor__',
    # '__or__',
    '__radd__',
    '__rsub__',
    '__rmul__',
    '__rtruediv__',
    # '__rdiv__',
    '__rmod__',
    # '__rdivmod__',
    '__rpow__',
    # '__rlshift__',
    # '__rrshift__',
    # '__rand__',
    # '__rxor__',
    # '__ror__',
]

```

```py
def make_int_binary_wrapper(fname, fun, zfun):
    def proxy(self, other):
        z, v = self._zv(other)
        z_ = zfun(self.z, z)
        v_ = fun(self.v, v)
        if isinstance(v_, float):
            # we do not implement float results yet.
            assert round(v_) == v_
            v_ = round(v_)
        return zint(self.context, z_, v_)

    return proxy

```

```py
for fname in INT_BINARY_OPS:
    fun = getattr(int, fname)
    zfun = getattr(z3.ArithRef, fname)
    setattr(zint, fname, make_int_binary_wrapper(fname, fun, zfun))

```

```py
with ConcolicTracer() as _:
    ia = zint.create(_.context, 'int_a', 0)
    ib = zint.create(_.context, 'int_b', 1)
    print((ia + ib).z)
    print((ia + 10).z)
    print((11 + ib).z)
    print((ia - ib).z)
    print((ia * ib).z)
    print((ia / ib).z)
    print((ia ** ib).z)

```

```py
int_a + int_b
int_a + 10
11 + int_b
int_a - int_b
int_a*int_b
int_a/int_b
int_a**int_b

```

##### 整数一元运算符

我们还实现了以下相关的一元运算符。

```py
INT_UNARY_OPS = [
    '__neg__',
    '__pos__',
    # '__abs__',
    # '__invert__',
    # '__round__',
    # '__ceil__',
    # '__floor__',
    # '__trunc__',
]

```

```py
def make_int_unary_wrapper(fname, fun, zfun):
    def proxy(self):
        return zint(self.context, zfun(self.z), fun(self.v))

    return proxy

```

```py
for fname in INT_UNARY_OPS:
    fun = getattr(int, fname)
    zfun = getattr(z3.ArithRef, fname)
    setattr(zint, fname, make_int_unary_wrapper(fname, fun, zfun))

```

我们可以如下使用上面定义的一元运算符：

```py
with ConcolicTracer() as _:
    ia = zint.create(_.context, 'int_a', 0)
    print((-ia).z)
    print((+ia).z)

```

```py
-int_a
int_a

```

##### 在布尔上下文中使用整数

整数可以在条件语句中或作为布尔谓词（例如`or`，`and`和`not`）的一部分转换为布尔上下文。 在这些情况下，将调用`__bool__()`方法。 不幸的是，此方法需要一个原始的布尔值。 因此，我们将当前整数公式强制为布尔谓词，并将其注册到当前上下文中。

```py
class zint(zint):
    def __bool__(self):
        # return zbool(self.context, self.z, self.v) <-- not allowed
        # force registering boolean condition
        if self != 0:
            return True
        return False

```

它的用法如下

```py
with ConcolicTracer() as _:
    za = zint.create(_.context, 'int_a', 1)
    zb = zint.create(_.context, 'int_b', 0)
    if za and zb:
        print(1)

```

```py
_.context

```

```py
({'int_a': 'Int', 'int_b': 'Int'}, [0 != int_a, Not(0 != int_b)])

```

#### ConcolicTracer [的其余方法](#Remaining-Methods-of-the--ConcolicTracer)

现在，我们完成了`ConcolicTracer`的一些方法。

##### 转换为SMT表达式格式

假设我们正在使用SMT规划求解z3，则为符号表达式检索对应的SMT表达式通常很有用。 可以将其用作`z3`或其他SMT求解器的参数。

SMT表达式的格式（ [SMT-LIB](http://smtlib.github.io/jSMTLIB/SMTLIBTutorial.pdf) ）如下：

*   [S-EXP](https://en.wikipedia.org/wiki/S-expression) 格式的变量声明。 例如。 以下内容声明了符号整数变量`x`

    ```py
    (declare-const x Int)
    ```

    这声明了长度`8`的`bit vector` `b`

    ```
    (declare-const b (_ BitVec 8))
    ```

    这声明了符号实型变量`r`

    ```
    (declare-const x Real)
    ```

    这声明了符号字符串变量`s`

    ```
    (declare-const s String)
    ```

声明的变量可用于以 *S-EXP* 格式编码的逻辑公式中。 例如，这是一个逻辑公式。

```
(assert
    (and
        (= a b)
        (= a c)
        (! b c)))
```py

这是另一个使用字符串变量的示例。

```
(or (< 0 (str.indexof (str.substr my_str1 7 19) " where " 0))
    (= (str.indexof (str.substr my_str1 7 19) " where " 0) 0))
```py

```
class ConcolicTracer(ConcolicTracer):
    def smt_expr(self, show_decl=False, simplify=False, path=[]):
        r = []
        if show_decl:
            for decl in self.decls:
                v = self.decls[decl]
                v = '(_ BitVec 8)' if v == 'BitVec' else v
                r.append("(declare-const %s  %s)" % (decl, v))
        path = path if path else self.path
        if path:
            path = z3.And(path)
            if show_decl:
                if simplify:
                    return '\n'.join([
                        *r,
                        "(assert %s)" % z3.simplify(path).sexpr()
                    ])
                else:
                    return '\n'.join(
                        [*r, "(assert %s)" % path.sexpr()])
            else:
                return z3.simplify(path).sexpr()
        else:
            return ''

```

要查看如何使用`smt_expr()`，让我们考虑一个示例。 `triangle()`函数用于确定三角形的给定边是否导致`equilateral`三角形，`isosceles`三角形或`scalene`三角形。 它的实现如下。

```py
def triangle(a, b, c):
    if a == b:
        if b == c:
            return 'equilateral'
        else:
            return 'isosceles'
    else:
        if b == c:
            return 'isosceles'
        else:
            if a == c:
                return 'isosceles'
            else:
                return 'scalene'

```

```py
triangle(1, 2, 1)

```

```py
'isosceles'

```

为了使其在`ConcolicTracer`下运行，我们首先定义参数。 所定义的三角形具有边`1, 1, 1`。 即它是`equilateral`三角形。

```py
with ConcolicTracer() as _:
    za = zint.create(_.context, 'int_a', 1)
    zb = zint.create(_.context, 'int_b', 1)
    zc = zint.create(_.context, 'int_c', 1)
    triangle(za, zb, zc)
print(_.context)

```

```py
({'int_a': 'Int', 'int_b': 'Int', 'int_c': 'Int'}, [int_a == int_b, int_b == int_c])

```

现在，我们可以调用`smt_expr()`来检索SMT表达式，如下所示。

```py
print(_.smt_expr(show_decl=True))

```

```py
(declare-const int_a Int)
(declare-const int_b Int)
(declare-const int_c Int)
(assert (and (= int_a int_b) (= int_b int_c)))

```

收集的谓词也可以使用Python z3 API直接解决。

```py
z3.solve(_.path)

```

```py
[int_c = 0, int_a = 0, int_b = 0]

```

##### 生成新名称

在使用代理类时，我们经常必须生成新的符号变量，其名称以前没有使用过。 为此，我们定义了`fresh_name()`，它始终为名称生成唯一的整数。

```py
COUNTER = 0

```

```py
def fresh_name():
    global COUNTER
    COUNTER += 1
    return COUNTER

```

It can be used as follows

```py
fresh_name()

```

```py
1

```

##### 将参数翻译为辅助代理

我们之前已将`concolic()`定义为透明函数。 现在，我们提供此功能的完整实现。 它检查给定函数的参数，并从传入的具体参数中推断出参数类型。然后，使用此信息为每个参数实例化正确的代理类。

```py
class ConcolicTracer(ConcolicTracer):
    def concolic(self, args):
        my_args = []
        for name, arg in zip(self.fn_args, args):
            t = type(arg).__name__
            zwrap = globals()['z' + t]
            vname = "%s_%s_%s_%s" % (self.fn.__name__, name, t, fresh_name())
            my_args.append(zwrap.create(self.context, vname, arg))
            self.fn_args[name] = vname
        return my_args

```

这是如何使用的：

```py
with ConcolicTracer() as _:
    _[factorial](5)

```

使用新的`concolic()`方法，阶乘的参数正确地与符号变量相关联，这使我们能够检索遇到的谓词。

```py
_.context

```

```py
({'factorial_n_int_2': 'Int'},
 [Not(0 > factorial_n_int_2),
  Not(0 == factorial_n_int_2),
  Not(1 == factorial_n_int_2),
  0 != factorial_n_int_2,
  0 != factorial_n_int_2 - 1,
  0 != factorial_n_int_2 - 1 - 1,
  0 != factorial_n_int_2 - 1 - 1 - 1,
  0 != factorial_n_int_2 - 1 - 1 - 1 - 1,
  Not(0 != factorial_n_int_2 - 1 - 1 - 1 - 1 - 1)])

```

和以前一样，我们也可以打印出SMT表达式，该表达式可以直接传递给命令行SMT求解器。

```py
print(_.smt_expr(show_decl=True))

```

```py
(declare-const factorial_n_int_2 Int)
(assert (let ((a!1 (distinct 0 (- (- (- factorial_n_int_2 1) 1) 1)))
      (a!2 (- (- (- (- factorial_n_int_2 1) 1) 1) 1)))
  (and (not (> 0 factorial_n_int_2))
       (not (= 0 factorial_n_int_2))
       (not (= 1 factorial_n_int_2))
       (distinct 0 factorial_n_int_2)
       (distinct 0 (- factorial_n_int_2 1))
       (distinct 0 (- (- factorial_n_int_2 1) 1))
       a!1
       (distinct 0 a!2)
       (not (distinct 0 (- a!2 1))))))

```

接下来，我们定义在Python中和从命令行评估SMT表达式的方法。

##### 评估共轭表达式

我们定义`zeval()`来解决上下文中的谓词，并返回结果。 它有两种模式。 `python`模式使用`z3` Python API求解并返回结果。 如果`python`模式为false，则它将SMT表达式写入文件，并调用命令行`z3`寻求解决方案。

```py
class ConcolicTracer(ConcolicTracer):
    def zeval(self, python=False, log=False):
        r, sol = (zeval_py if python else zeval_smt)(self.path, self, log)
        if r == 'sat':
            return r, {k: sol.get(self.fn_args[k], None) for k in self.fn_args}
        else:
            return r, None

```

##### 使用Python API

给定一组函数遇到的谓词以及执行该函数的跟踪器，`zeval_py()`函数首先声明相关的符号变量，然后使用`z3.Solver()`提供一组跟踪相同路径的输入 通过功能。

```py
def zeval_py(path, cc, log):
    for decl in cc.decls:
        if cc.decls[decl] == 'BitVec':
            v = "z3.%s('%s', 8)" % (cc.decls[decl], decl)
        else:
            v = "z3.%s('%s')" % (cc.decls[decl], decl)
        exec(v)
    s = z3.Solver()
    s.add(z3.And(path))
    if s.check() == z3.unsat:
        return 'No Solutions', {}
    elif s.check() == z3.unknown:
        return 'Gave up', None
    assert s.check() == z3.sat
    m = s.model()
    return 'sat', {d.name(): m[d] for d in m.decls()}

```

可以如下使用：

```py
with ConcolicTracer() as _:
    _[factorial](5)

```

```py
_.zeval(python=True)

```

```py
('sat', {'n': 5})

```

也就是说，给定一组约束，分配`n == 5`符合所有约束。

##### 使用命令行

`zeval_smt()`函数将SMT表达式写入文件系统，然后调用`z3` SMT求解器命令行对其进行求解。 SMT表达的结果再次是`sexpr`。 因此，我们首先定义`parse_sexp()`以解析并返回正确的值。

```py
import [re](https://docs.python.org/3/library/re.html)

```

```py
import [subprocess](https://docs.python.org/3/library/subprocess.html)

```

```py
SEXPR_TOKEN = r'''(?mx)
 \s*(?:
 (?P<bra>\()|
 (?P<ket>\))|
 (?P<token>[^"()\s]+)|
 (?P<string>"[^"]*")
 )'''

```

```py
def parse_sexp(sexp):
    stack, res = [], []
    for elements in re.finditer(SEXPR_TOKEN, sexp):
        kind, value = [(t, v) for t, v in elements.groupdict().items() if v][0]
        if kind == 'bra':
            stack.append(res)
            res = []
        elif kind == 'ket':
            last, res = res, stack.pop(-1)
            res.append(last)
        elif kind == 'token':
            res.append(value)
        elif kind == 'string':
            res.append(value[1:-1])
        else:
            assert False
    return res

```

`parse_sexp()`功能可以如下使用

```py
parse_sexp('abcd (hello 123 (world "hello world"))')

```

```py
['abcd', ['hello', '123', ['world', 'hello world']]]

```

现在，我们定义`zeval_smt()`，它直接使用`z3`命令行，并使用`parse_sexp()`解析并返回函数参数的解（如果有）。

```py
import [tempfile](https://docs.python.org/3/library/tempfile.html)

```

```py
def zeval_smt(path, cc, log):
    s = cc.smt_expr(True, True, path)

    with tempfile.NamedTemporaryFile(mode='w', suffix='.smt') as f:
        f.write(s)
        f.write("\n(check-sat)")
        f.write("\n(get-model)")
        f.flush()

        if log:
            print(s, '(check-sat)', '(get-model)', sep='\n')
        output = subprocess.getoutput("z3 " + f.name)

    if log:
        print(output)
    o = parse_sexp(output)
    kind = o[0]
    if kind == 'unknown':
        return 'Gave up', None
    elif kind == 'unsat':
        return 'No Solutions', {}
    assert kind == 'sat'
    assert o[1][0] == 'model'
    return 'sat', {i[1]: i[-1] for i in o[1][1:]}

```

现在，我们可以如下使用`zeval()`。

```py
with ConcolicTracer() as _:
    _[factorial](5)

```

```py
_.zeval()

```

```py
('sat', {'n': '5'})

```

确实，使用命令行与使用Python API可获得相似的结果（`n == 5`）。

#### 字符串[的代理类](#A-Proxy-Class-for-Strings)

在这里，我们定义代理字符串类`zstr`。 首先，我们定义初始化例程。 由于`str`是原始类型，因此我们定义`new`对其进行扩展。

```py
class zstr(str):
    def __new__(cls, context, zn, v):
        return str.__new__(cls, v)

```

与以前一样，初始化将使用`create()`和构造函数进行。

```py
class zstr(zstr):
    @classmethod
    def create(cls, context, zn, v=None):
        return zproxy_create(cls, 'String', z3.String, context, zn, v)

    def __init__(self, context, z, v=None):
        self.context, self.z, self.v = context, z, v
        self._len = zint(context, z3.Length(z), len(v))
        #self.context[1].append(z3.Length(z) == z3.IntVal(len(v)))

```

我们还定义了`_zv()`帮助器，以帮助我们提供接受另一个字符串的方法

```py
class zstr(zstr):
    def _zv(self, o):
        return (o.z, o.v) if isinstance(o, zstr) else (z3.StringVal(o), o)

```

##### 检索序数值

我们定义`zord`，给定一个符号长字符串，为此获得`ord()`。 它返回两个值。 第一个是与`ord()`对应的变量，第二个是将变量链接到传入的单个字符串的谓词。

```py
def zord(context, c):
    bn = "bitvec_%d" % fresh_name()
    v = z3.BitVec(bn, 8)
    context[0][bn] = 'BitVec'
    z = (z3.Unit(v) == c)
    context[1].append(z)
    return v

```

我们如下使用它

```py
zc = z3.String('arg_%d' % fresh_name())

```

```py
with ConcolicTracer() as _:
    zi = zord(_.context, zc)

```

符号位向量在`zi`中。 它链接到`context`中传入的参数

```py
_.context

```

```py
({'bitvec_6': 'BitVec'}, [seq.unit(bitvec_6) == arg_5])

```

我们可以指定`ord()`的结果是什么，然后调用`z3.solve()`为我们提供可以提供所需结果的解决方案。

```py
z3.solve(_.path + [zi == 65])

```

```py
[arg_5 = "A", bitvec_6 = 65]

```

##### 将有序值转换为ASCII

同样，我们可以使用`zchr()`将ASCII值转换回单个字符串

```py
def zchr(context, i):
    sn = 'string_%d' % fresh_name()
    s = z3.String(sn)
    context[0][sn] = 'String'
    z = z3.And([s == z3.Unit(i), z3.Length(s) == 1])
    context[1].append(z)
    return s

```

为了使用它，我们首先定义一个8位长的位向量。

```py
i = z3.BitVec('bv_%d' % fresh_name(), 8)

```

现在，我们可以按以下方式检索`chr()`表示形式。

```py
with ConcolicTracer() as _:
    zc = zchr(_.context, i)

```

```py
_.context

```

```py
({'string_8': 'String'},
 [And(string_8 == seq.unit(bv_7), str.len(string_8) == 1)])

```

和以前一样，我们可以指定调用`chr()`的最终结果应该是获得原始参数。

```py
z3.solve(_.path + [zc == z3.StringVal('a')])

```

```py
[bv_7 = 97, string_8 = "a"]

```

##### 字符串之间的相等

`zstr`的相等性定义与`zint`相同

```py
class zstr(zstr):
    def __eq__(self, other):
        z, v = self._zv(other)
        return zbool(self.context, self.z == z, self.v == v)

    def __req__(self, other):
        return self.__eq__(other)

```

`zstr`类的用法如下。

```py
def tstr1(s):
    if s == 'h':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr1]('h')

```

```py
_.zeval()

```

```py
('sat', {'s': 'h'})

```

即使我们有多个字符也可以使用。

```py
def tstr1(s):
    if s == 'hello world':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr1]('hello world')

```

```py
_.context

```

```py
({'tstr1_s_str_10': 'String'}, [tstr1_s_str_10 == "hello world"])

```

```py
_.zeval()

```

```py
('sat', {'s': 'hello world'})

```

##### 字符串串联

如果我们需要连接两个字符串怎么办？ 我们需要其他帮助者来实现这一目标。

```py
class zstr(zstr):
    def __add__(self, other):
        z, v = self._zv(other)
        return zstr(self.context, self.z + z, self.v + v)

    def __radd__(self, other):
        return self.__add__(other)

```

这是如何使用它。 首先，我们创建包装的参数

```py
with ConcolicTracer() as _:
    v1, v2 = [zstr.create(_.context, 'arg_%d' % fresh_name(), s)
              for s in ['hello', 'world']]
    if (v1 + ' ' + v2) == 'hello world':
        print('hello world')

```

```py
hello world

```

符号变量的添加保留在`context`中

```py
_.context

```

```py
({'arg_11': 'String', 'arg_12': 'String'},
 [str.++(str.++(arg_11, " "), arg_12) == "hello world"])

```

##### 产生子字符串

同样，访问子字符串也需要额外的帮助。

```py
class zstr(zstr):
    def __getitem__(self, idx):
        if isinstance(idx, slice):
            start, stop, step = idx.indices(len(self.v))
            assert step == 1  # for now
            assert stop >= start  # for now
            rz = z3.SubString(self.z, start, stop - start)
            rv = self.v[idx]
        elif isinstance(idx, int):
            rz = z3.SubString(self.z, idx, 1)
            rv = self.v[idx]
        else:
            assert False  # for now
        return zstr(self.context, rz, rv)

    def __iter__(self):
        return zstr_iterator(self.context, self)

```

##### 字符串的迭代器类

我们定义迭代器如下。

```py
class zstr_iterator():
    def __init__(self, context, zstr):
        self.context = context
        self._zstr = zstr
        self._str_idx = 0
        self._str_max = zstr._len  # intz is not an _int_

    def __next__(self):
        if self._str_idx == self._str_max:  # intz#eq
            raise StopIteration
        c = self._zstr[self._str_idx]
        self._str_idx += 1
        return c

    def __len__(self):
        return self._len

```

Here is how it can be used.

```py
def tstr2(s):
    if s[0] == 'h' and s[1] == 'e' and s[3] == 'l':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr2]('hello')

```

同样，上下文显示遇到的谓词。

```py
_.context

```

```py
({'tstr2_s_str_13': 'String'},
 [str.substr(tstr2_s_str_13, 0, 1) == "h",
  str.substr(tstr2_s_str_13, 1, 1) == "e",
  str.substr(tstr2_s_str_13, 3, 1) == "l"])

```

函数`zeval()`返回谓词的解决方案。 请注意，返回的值不完全是我们传入的参数。这是我们拥有谓词的结果。 也就是说，我们对`s[2]`上的字符值没有限制。

```py
_.zeval()

```

```py
('sat', {'s': 'he\\x00l'})

```

##### 转换为上下等效

一个主要的并发症是支持`upper()`和`lower()`方法。 我们使用先前定义的`zchr()`和`zord()`函数来完成此操作。

```py
class zstr(zstr):
    def upper(self):
        empty = ''
        ne = 'empty_%d' % fresh_name()
        result = zstr.create(self.context, ne, empty)
        self.context[1].append(z3.StringVal(empty) == result.z)
        cdiff = (ord('a') - ord('A'))
        for i in self:
            oz = zord(self.context, i.z)
            uz = zchr(self.context, oz - cdiff)
            rz = z3.And([oz >= ord('a'), oz <= ord('z')])
            ov = ord(i.v)
            uv = chr(ov - cdiff)
            rv = ov >= ord('a') and ov <= ord('z')
            if zbool(self.context, rz, rv):
                i = zstr(self.context, uz, uv)
            else:
                i = zstr(self.context, i.z, i.v)
            result += i
        return result

```

`lower()`功能类似于`upper()`，不同之处在于字符范围已切换，并且小写字母位于大写字母上方。 因此，我们将差异添加到序数上以使字符变为小写。

```py
class zstr(zstr):
    def lower(self):
        empty = ''
        ne = 'empty_%d' % fresh_name()
        result = zstr.create(self.context, ne, empty)
        self.context[1].append(z3.StringVal(empty) == result.z)
        cdiff = (ord('a') - ord('A'))
        for i in self:
            oz = zord(self.context, i.z)
            uz = zchr(self.context, oz + cdiff)
            rz = z3.And([oz >= ord('A'), oz <= ord('Z')])
            ov = ord(i.v)
            uv = chr(ov + cdiff)
            rv = ov >= ord('A') and ov <= ord('Z')
            if zbool(self.context, rz, rv):
                i = zstr(self.context, uz, uv)
            else:
                i = zstr(self.context, i.z, i.v)
            result += i
        return result

```

使用方法如下。

```py
def tstr3(s):
    if s.upper() == 'H':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr3]('h')

```

同样，我们使用`zeval()`来解决收集的约束，并验证我们的约束正确。

```py
_.zeval()

```

```py
('sat', {'s': 'h'})

```

这是使用`upper()`的更大示例

```py
def tstr4(s):
    if s.lower() == 'hello world':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr4]('Hello World')

```

```py
_.zeval()

```

```py
('sat', {'s': 'Hello World'})

```

同样，我们获得正确的输入值。

##### 检查字符串前缀

我们定义`startswith()`。

```py
class zstr(zstr):
    def startswith(self, other, beg=0, end=None):
        assert end is None  # for now
        assert isinstance(beg, int)  # for now
        zb = z3.IntVal(beg)

        others = other if isinstance(other, tuple) else (other, )

        last = False
        for o in others:
            z, v = self._zv(o)
            r = z3.IndexOf(self.z, z, zb)
            last = zbool(self.context, r == zb, self.v.startswith(v))
            if last:
                return last
        return last

```

一个例子。

```py
def tstr5(s):
    if s.startswith('hello'):
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr5]('hello world')

```

```py
_.zeval()

```

```py
('sat', {'s': 'hello'})

```

```py
with ConcolicTracer() as _:
    r = _[tstr5]('my world')

```

```py
_.zeval()

```

```py
('sat', {'s': '\\x00hello'})

```

如前所述，谓词仅确保`startswith()`返回了真实值。 因此，我们的解决方案反映了这一点。

##### 查找子字符串

我们还定义了`find()`

```py
class zstr(zstr):
    def find(self, other, beg=0, end=None):
        assert end is None  # for now
        assert isinstance(beg, int)  # for now
        zb = z3.IntVal(beg)
        z, v = self._zv(other)
        zi = z3.IndexOf(self.z, z, zb)
        vi = self.v.find(v, beg, end)
        return zint(self.context, zi, vi)

```

An example.

```py
def tstr6(s):
    if s.find('world') != -1:
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr6]('hello world')

```

```py
_.zeval()

```

```py
('sat', {'s': '\\x00world'})

```

如前所述，谓词仅确保`find()`返回的值大于-1。 因此，我们的解决方案反映了这一点。

##### 从端点[删除空间[HTG1]](#Remove-Space-from-Ends)

接下来我们实现`strip()`。

```py
import [string](https://docs.python.org/3/library/string.html)

```

```py
class zstr(zstr):
    def rstrip(self, chars=None):
        if chars is None:
            chars = string.whitespace
        if self._len == 0:
            return self
        else:
            last_idx = self._len - 1
            cz = z3.SubString(self.z, last_idx.z, 1)
            cv = self.v[-1]
            zcheck_space = z3.Or([cz == z3.StringVal(char) for char in chars])
            vcheck_space = any(cv == char for char in chars)
            if zbool(self.context, zcheck_space, vcheck_space):
                return zstr(self.context, z3.SubString(self.z, 0, last_idx.z),
                            self.v[0:-1]).rstrip(chars)
            else:
                return self

```

```py
def tstr7(s):
    if s.rstrip(' ') == 'a b':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr7]('a b   ')
    print(r)

```

```py
True

```

```py
_.zeval()

```

```py
('sat', {'s': 'a b   '})

```

```py
class zstr(zstr):
    def lstrip(self, chars=None):
        if chars is None:
            chars = string.whitespace
        if self._len == 0:
            return self
        else:
            first_idx = 0
            cz = z3.SubString(self.z, 0, 1)
            cv = self.v[0]
            zcheck_space = z3.Or([cz == z3.StringVal(char) for char in chars])
            vcheck_space = any(cv == char for char in chars)
            if zbool(self.context, zcheck_space, vcheck_space):
                return zstr(self.context, z3.SubString(
                    self.z, 1, self._len.z), self.v[1:]).lstrip(chars)
            else:
                return self

```

```py
def tstr8(s):
    if s.lstrip(' ') == 'a b':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr8]('   a b')
    print(r)

```

```py
True

```

```py
_.zeval()

```

```py
('sat', {'s': '   a b'})

```

```py
class zstr(zstr):
    def strip(self, chars=None):
        return self.lstrip(chars).rstrip(chars)

```

用法示例。

```py
def tstr9(s):
    if s.strip() == 'a b':
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr9]('    a b  ')
    print(r)

```

```py
True

```

```py
_.zeval()

```

```py
('sat', {'s': '    a b  '})

```

`strip()`已生成正确的约束。

##### 分割字符串

我们如下实现字符串`split()`。

```py
class zstr(zstr):
    def split(self, sep=None, maxsplit=-1):
        assert sep is not None  # default space based split is complicated
        assert maxsplit == -1  # for now.
        zsep = z3.StringVal(sep)
        zl = z3.Length(zsep)
        zi = z3.IndexOf(self.z, zsep, z3.IntVal(0))  # zi would be the length of prefix
        # Z3Bug: There is a bug in the `z3.IndexOf` method which returns
        # `z3.SeqRef` instead of `z3.ArithRef`. So we need to fix it.
        zi = z3.ArithRef(zi.ast, zi.ctx)

        vi = self.v.find(sep)
        if zbool(self.context, zi >= z3.IntVal(0), vi >= 0):
            zprefix = z3.SubString(self.z, z3.IntVal(0), zi)
            zmid = z3.SubString(self.z, zi, zl)
            zsuffix = z3.SubString(self.z, zi + zl,
                                   z3.Length(self.z))
            return [zstr(self.context, zprefix, self.v[0:vi])] + zstr(
                self.context, zsuffix, self.v[vi + len(sep):]).split(
                    sep, maxsplit)
        else:
            return [self]

```

```py
def tstr10(s):
    if s.split(',') == ['a', 'b', 'c']:
        return True
    else:
        return False

```

```py
with ConcolicTracer() as _:
    r = _[tstr10]('a,b,c')
    print(r)

```

```py
True

```

```py
_.zeval()

```

```py
('sat', {'s': 'a,b,c'})

```

##### 跳闸线

为了简化调试，我们中止了`str`中未被`zstr`覆盖的对`str`中方法的所有调用。

```py
def make_str_abort_wrapper(fun):
    def proxy(*args, **kwargs):
        raise Exception( '%s Not implemented in `zstr`' % fun.__name__)
    return proxy

```

```py
strmembers = inspect.getmembers(zstr, callable)
zstrmembers = {m[0] for m in strmembers if len(
    m) == 2 and 'zstr' in m[1].__qualname__}
for name, fn in inspect.getmembers(str, callable):
    # Omitted 'splitlines' as this is needed for formatting output in
    # IPython/Jupyter
    if name not in zstrmembers and name not in [
        'splitlines',
        '__class__',
        '__contains__',
        '__delattr__',
        '__dir__',
        '__format__',
        '__ge__',
        '__getattribute__',
        '__getnewargs__',
        '__gt__',
        '__hash__',
        '__le__',
        '__len__',
        '__lt__',
        '__mod__',
        '__mul__',
        '__ne__',
        '__reduce__',
        '__reduce_ex__',
        '__repr__',
        '__rmod__',
        '__rmul__',
        '__setattr__',
        '__sizeof__',
        '__str__']:
        setattr(zstr, name, make_str_abort_wrapper(fn))

```

## 示例

### 三角形

先前我们展示了如何在`ConcolicTracer`下运行`triangle()`。

```py
with ConcolicTracer() as _:
    print(_[triangle](1, 2, 3))

```

```py
scalene

```

谓词如下：

```py
_.path

```

```py
[Not(triangle_a_int_49 == triangle_b_int_50),
 Not(triangle_b_int_50 == triangle_c_int_51),
 Not(triangle_a_int_49 == triangle_c_int_51)]

```

```py
_.zeval()

```

```py
('sat', {'a': '0', 'b': ['-', '2'], 'c': ['-', '1']})

```

如有必要，我们可以修改谓词。 首先，我们检索符号变量。

```py
za, zb, zc = [z3.Int(s) for s in _.context[0].keys()]

```

然后，我们将修饰的谓词传递给`zeval()`。 密钥确定新谓词将替换哪个谓词。

```py
_.zeval({1: zb == zc})

```

```py
('sat', {'a': -1, 'b': -2, 'c': 0})

```

```py
triangle(1, 0, 1)

```

```py
'isosceles'

```

更新的谓词按预期返回`isosceles`。

### 回合

这是一个为您提供最接近的十进制乘数的函数

```py
def round10(r):
    while r % 10 != 0:
        r += 1
    return r

```

和以前一样，我们在`ConcolicTracer`上下文下执行该函数。

```py
with ConcolicTracer() as _:
    r = _[round10](1)

```

我们验证我们能够捕获所有谓词

```py
_.context

```

```py
({'round10_r_int_52': 'Int'},
 [0 != round10_r_int_52%10,
  0 != (round10_r_int_52 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1 + 1 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1 + 1 + 1 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1 + 1 + 1 + 1 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1 + 1 + 1 + 1 + 1 + 1)%10,
  0 != (round10_r_int_52 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1)%10,
  Not(0 !=
      (round10_r_int_52 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1)%
      10)])

```

我们使用`zeval()`获得结果。

```py
_.zeval()

```

```py
('sat', {'r': '41'})

```

### 绝对最大值

我们的condicolic代理可以跨职能使用吗？ 假设我们有如下功能`max_value()`。

```py
def abs_value(a):
    if a > 0:
        return a
    else:
        return -a

```

由另一个函数`abs_max()`调用

```py
def abs_max(a, b):
    a1 = abs_value(a)
    b1 = abs_value(b)
    if a1 > b1:
        c = a1
    else:
        c = b1
    return c

```

在`abs_max()`上使用`Concolic()`上下文。

```py
with ConcolicTracer() as _:
    _[abs_max](2, 1)

```

不出所料，我们拥有跨函数的谓词。

```py
_.context

```

```py
({'abs_max_a_int_53': 'Int', 'abs_max_b_int_54': 'Int'},
 [0 < abs_max_a_int_53,
  0 < abs_max_b_int_54,
  abs_max_a_int_53 > abs_max_b_int_54])

```

```py
_.zeval()

```

```py
('sat', {'a': '2', 'b': '1'})

```

解决谓词按预期工作。

使用负数作为参数，以便在`abs_value()`中采用不同的分支

```py
with ConcolicTracer() as _:
    _[abs_max](-2, -1)

```

```py
_.context

```

```py
({'abs_max_a_int_55': 'Int', 'abs_max_b_int_56': 'Int'},
 [Not(0 < abs_max_a_int_55),
  Not(0 < abs_max_b_int_56),
  -abs_max_a_int_55 > -abs_max_b_int_56])

```

```py
_.zeval()

```

```py
('sat', {'a': ['-', '1'], 'b': '0'})

```

解决方案反映了我们的谓词。 （我们在`abs_value()`中使用了`a > 0`）。

### 二项式系数

对于使用不同种类变量的较大示例，假设我们要通过以下公式计算二项式系数

$$ ^ nP_k = \ frac {n！} {（n-k）！} $$

$$ \ binom nk = \，^ nC_k = \ frac {^ nP_k} {k！} $$

我们将功能定义如下。

```py
def factorial(n):
    v = 1
    while n != 0:
        v *= n
        n -= 1
    return v

```

```py
def permutation(n, k):
    return factorial(n) / factorial(n - k)

```

```py
def combination(n, k):
    return permutation(n, k) / factorial(k)

```

```py
def binomial(n, k):
    if n < 0 or k < 0 or n < k:
        raise Exception('Invalid values')
    return combination(n, k)

```

和以前一样，我们在`ConcolicTracer`下运行该函数。

```py
with ConcolicTracer() as _:
    v = _[binomial](4, 2)

```

然后调用`zeval()`进行评估。

```py
_.zeval()

```

```py
('sat', {'n': '4', 'k': '2'})

```

### 数据库

对于使用Concolic String类`zstr`的更大示例，我们使用[一章中有关信息流](InformationFlow.html)的DB类。

```py
from [InformationFlow](InformationFlow.html) import DB, sample_db, update_inventory

```

我们首先填充数据库。

```py
from [GrammarMiner](GrammarMiner.html) import VEHICLES  # minor dependency

```

```py
db = sample_db()
for V in VEHICLES:
    update_inventory(db, V)

```

```py
db.db

```

```py
{'inventory': ({'year': int, 'kind': str, 'company': str, 'model': str},
  [{'year': 1997, 'kind': 'van', 'company': 'Ford', 'model': 'E350'},
   {'year': 2000, 'kind': 'car', 'company': 'Mercury', 'model': 'Cougar'},
   {'year': 1999, 'kind': 'car', 'company': 'Chevy', 'model': 'Venture'}])}

```

现在我们准备对`DB`类进行模糊处理。 哈希函数很难直接处理（因为它们依赖于内部C函数）。 因此，我们稍微修改`table()`。

```py
class ConcolicDB(DB):
    def table(self, t_name):
        for k, v in self.db:
            if t_name == k:
                return v
        raise SQLException('Table (%s) was not found' % repr(t_name))

    def column(self, decl, c_name):
        for k in decl:
            if c_name == k:
                return decl[k]
        raise SQLException('Column (%s) was not found' % repr(c_name))

```

为了简单起见，我们定义了一个直接调用`db.sql()`的单个函数`db_select()`。

```py
def db_select(s):
    my_db = ConcolicDB()
    my_db.db = [(k, v) for (k, v) in db.db.items()]
    r = my_db.sql(s)
    return r

```

现在，我们想在`ConcolicTracer`下运行SQL语句，并收集获得的谓词。

```py
with ConcolicTracer() as _:
    _[db_select]('select kind from inventory')

```

执行期间遇到的谓词如下：

```py
_.path

```

```py
[str.indexof(db_select_s_str_59, "select ", 0) == 0,
 str.indexof(db_select_s_str_59, "select ", 0) == 0,
 Not(0 >
     str.indexof(str.substr(db_select_s_str_59, 7, 19),
                 " from ",
                 0)),
 Not(19 <
     str.indexof(str.substr(db_select_s_str_59, 7, 19),
                 " from ",
                 0)),
 Or(0 <
    str.indexof(str.substr(db_select_s_str_59, 7, 19),
                " from ",
                0),
    str.indexof(str.substr(db_select_s_str_59, 7, 19),
                " from ",
                0) ==
    0),
 Not(Or(0 <
        str.indexof(str.substr(db_select_s_str_59, 7, 19),
                    " where ",
                    0),
        str.indexof(str.substr(db_select_s_str_59, 7, 19),
                    " where ",
                    0) ==
        0)),
 Not(19 <
     str.indexof(str.substr(db_select_s_str_59, 7, 19),
                 " from ",
                 0) +
     6),
 Or(19 >
    str.indexof(str.substr(db_select_s_str_59, 7, 19),
                " from ",
                0) +
    6,
    19 ==
    str.indexof(str.substr(db_select_s_str_59, 7, 19),
                " from ",
                0) +
    6),
 str.substr(str.substr(db_select_s_str_59, 7, 19), 10, 9) ==
 "inventory"]

```

我们可以像以前一样使用`zeval()`来解决约束。

```py
_.zeval()

```

```py
('Gave up', None)

```

**注意：**由于SMT求解器中字符串的*理论的状态，您在执行`zeval()`时大多会放弃*放弃*。 很好 这只是意味着SMT求解器在得到答案之前就放弃了。*

## 带约束的模糊测试

在本节中，我们将展示如何使用为进行conolicly执行而构建的基础结构来指导模糊测试。

### SimpleConcolicFuzzer

`SimpleConcolicFuzzer`从其他一些模糊器生成的样本输入开始。 然后，它运行`ConcolicTracer`下正在测试的功能，并收集路径谓词。 然后，它对路径内的随机谓词求反，并使用 *z3* 对其进行求解，以产生新的输出，该输出可保证采用与原始路径不同的路径。

首先，我们导入`Fuzzer`接口和示例程序`hang_if_no_space()`

```py
from [Fuzzer](Fuzzer.html) import Fuzzer, hang_if_no_space

```

```py
from [ExpectError](ExpectError.html) import ExpectTimeout

```

```py
import [random](https://docs.python.org/3/library/random.html)

```

`SimpleConcolicFuzzer`是通过`Fuzzer`接口定义的。

```py
class SimpleConcolicFuzzer(Fuzzer):
    def __init__(self):
        self.ct = []
        self.seen = set()
        self.seen_strs = set()
        self.max_tries = 1000
        self.last = None
        self.last_idx = None

```

`add_trace()`方法提供了一种添加新迹线的方法。 它与初始化保持分离，因为我们可能想从同一函数中添加多个跟踪。

```py
class SimpleConcolicFuzzer(SimpleConcolicFuzzer):
    def add_trace(self, tracer):
        self.ct.append(tracer)
        self.last = tracer
        self.last_idx = len(tracer.context[1]) - 1

```

`to_num()`方法将一系列谓词转换为与每个谓词所采用的决策相对应的位模式。 如果采用`if`分支，则模式为`1`，而`else`分支由`0`指示。 这使我们可以将任何执行路径表示为单个整数。

```py
class SimpleConcolicFuzzer(SimpleConcolicFuzzer):
    def to_num(self, arr):
        return int(
            ''.join(
                reversed([
                    '0' if z3.simplify(i).decl().name() == 'not' else '1'
                    for i in arr
                ] + ['1'])), 2)

```

它的用法如下。

```py
scf = SimpleConcolicFuzzer()
a, b = z3.Ints('a b')
print(bin(scf.to_num([z3.Not(a == b)])))
print(bin(scf.to_num([z3.Not(a == b), a == b])))
print(bin(scf.to_num([z3.Not(a == b), a == b, z3.Not(a == b)])))

```

```py
0b10
0b110
0b1010

```

从左到右读取位模式。 第一个`0`表示第一个`else`分支（对应于`Not`），而最后一个`1`作为标记添加。 模式`0b1010`表示已采取两个`else`分支。

`get_newpath()`函数在谓词列表中选择一个要取反的随机点。 也就是说，它将创建一个新列表，该列表具有随机长度的前缀（与原始谓词具有相同的谓词），并在末尾添加单个否定值。 它还确保了永远不会重复看到的值。

```py
class SimpleConcolicFuzzer(SimpleConcolicFuzzer):
    def get_newpath(self):
        switch = random.randint(0, self.last_idx)
        if self.seen:
            param = list(self.last.fn_args.values())[0]
            sparam = z3.String(param)
            seen = [sparam != z3.StringVal(i) for i in self.seen_strs]
        else:
            seen = []
        new_path = self.last.path[0:switch] + \
            [z3.Not(self.last.path[switch])] + seen
        return self.to_num(new_path), new_path

```

`fuzz()`方法仅生成谓词的新列表，并将其求解以产生新的输入。

```py
class SimpleConcolicFuzzer(SimpleConcolicFuzzer):
    def fuzz(self):
        for i in range(self.max_tries):
            pattern, path = self.get_newpath()
            if pattern in self.seen:
                continue
            self.seen.add(pattern)
            s, v = zeval_smt(path, self.last, log=False)
            if s != 'sat':
                continue
            s = list(v.values())[0]
            self.seen_strs.add(s)
            return s
        return None

```

`SimpleConcolicFuzzer`的用法如下。 首先，我们使用随机字符串生成圆锥曲线。

```py
with ExpectTimeout(2):
    with ConcolicTracer() as _:
        _[hang_if_no_space]('abcd')

```

```py
Traceback (most recent call last):
  File "<ipython-input-204-ff2b46f545ec>", line 3, in <module>
    _[hang_if_no_space]('abcd')
  File "<ipython-input-33-635d2b1b13c2>", line 3, in __call__
    self.result = self.fn(*self.concolic(args))
  File "<string>", line 4, in hang_if_no_space
  File "<string>", line 4, in hang_if_no_space
  File "<string>", line 16, in check_time
TimeoutError (expected)

```

接下来，我们初始化并将此跟踪添加到模糊器。

```py
scf = SimpleConcolicFuzzer()
scf.add_trace(_)

```

最后，我们起毛了。

```py
for i in range(10):
    v = scf.fuzz()
    if v is None:
        break
    print(v)

```

```py
 \x00
\x00\x00 \x00
 \x02
 \x80
 @
\x00 \x00
 \x10
\x00\x00 \x08
\x00\x00\x00 \x00
\x00  

```

这是使用`cgi_decode()`的另一个示例

```py
from [Coverage](Coverage.html) import cgi_decode

```

```py
with ConcolicTracer() as _:
    _[cgi_decode]('abcd')

```

```py
scf = SimpleConcolicFuzzer()
scf.add_trace(_)

```

```py
for i in range(10):
    v = scf.fuzz()
    if v is None:
        break
    print(v)

```

```py
%\x00
\x00\x00\x00+\x00
%\x02
\x00\x00%\x00
\x00+\x00
\x00\x00\x00+\x02
\x00\x00\x00%\x00
\x00\x00% 
\x00\x00+\x00
\x00\x00\x00+\x80

```

`SimpleConcolicFuzzer`在探索给定样本输入所遵循的路径附近的路径方面相当有效。 但是，在选择要遵循的路径时并不是很聪明。 我们看另一个模糊器，它将获得的谓词提升为语法并获得更好的模糊性。

### ConcolicGrammarFuzzer

框架式框架可以直接用于模糊测试。 首先，我们使用辅助方法`tree_to_string()`扩展`GrammarFuzzer`，以便我们可以检索模糊输出的派生树。 我们还定义了`prune_tree()`和`coalesce()`以减少子树的深度。 这些方法接受令牌类型的列表，以便通过调用`tree_to_string()`将属于令牌类型的节点从树转换为叶节点。

```py
from [InformationFlow](InformationFlow.html) import INVENTORY_GRAMMAR, SQLException

```

```py
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer

```

```py
class ConcolicGrammarFuzzer(GrammarFuzzer):
    def tree_to_string(self, tree):
        symbol, children, *_ = tree
        e = ''
        if children:
            return e.join([self.tree_to_string(c) for c in children])
        else:
            return e if symbol in self.grammar else symbol

    def prune_tree(self, tree, tokens):
        name, children = tree
        children = self.coalesce(children)
        if name in tokens:
            return (name, [(self.tree_to_string(tree), [])])
        else:
            return (name, [self.prune_tree(c, tokens) for c in children])

    def coalesce(self, children):
        last = ''
        new_lst = []
        for cn, cc in children:
            if cn not in self.grammar:
                last += cn
            else:
                if last:
                    new_lst.append((last, []))
                    last = ''
                new_lst.append((cn, cc))
        if last:
            new_lst.append((last, []))
        return new_lst

```

现在，我们可以使用模糊器为数据库生成输入。

```py
tgf = ConcolicGrammarFuzzer(INVENTORY_GRAMMAR)
while True:
    qtree = tgf.fuzz_tree()
    query = str(tgf.tree_to_string(qtree))
    if query.startswith('select'):
        break

```

```py
from [ExpectError](ExpectError.html) import ExpectError

```

```py
with ExpectError():
    print(repr(query))
    with ConcolicTracer() as _:
        res = _[db_select](str(query))
    print(repr(res))

```

```py
'select z,(k(w)+N) from W0 where T9(e)*i+b-E!=Yt'

```

```py
Traceback (most recent call last):
  File "<ipython-input-216-6cbadb7bcf98>", line 4, in <module>
    res = _[db_select](str(query))
  File "<ipython-input-33-635d2b1b13c2>", line 3, in __call__
    self.result = self.fn(*self.concolic(args))
  File "<ipython-input-191-26b52b86ed9e>", line 4, in db_select
    r = my_db.sql(s)
  File "<string>", line 18, in sql
  File "<string>", line 17, in do_select
  File "<ipython-input-190-d1ea3580462a>", line 6, in table
    raise SQLException('Table (%s) was not found' % repr(t_name))
InformationFlow.SQLException: Table ('W0') was not found (expected)

```

我们的模糊器异常返回。 无法找到指定的表。 让我们检查一下它遇到的谓词。

```py
for i, p in enumerate(_.path):
    print(i, p)

```

```py
0 str.indexof(db_select_s_str_62, "select ", 0) == 0
1 str.indexof(db_select_s_str_62, "select ", 0) == 0
2 Not(0 >
    str.indexof(str.substr(db_select_s_str_62, 7, 40),
                " from ",
                0))
3 Not(40 <
    str.indexof(str.substr(db_select_s_str_62, 7, 40),
                " from ",
                0))
4 Or(0 <
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " from ",
               0),
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " from ",
               0) ==
   0)
5 Or(0 <
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " where ",
               0),
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " where ",
               0) ==
   0)
6 Not(40 <
    str.indexof(str.substr(db_select_s_str_62, 7, 40),
                " from ",
                0) +
    6)
7 Not(40 <
    str.indexof(str.substr(db_select_s_str_62, 7, 40),
                " where ",
                0))
8 Or(str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " from ",
               0) +
   6 <
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " where ",
               0),
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " where ",
               0) ==
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " from ",
               0) +
   6)
9 Not(40 <
    str.indexof(str.substr(db_select_s_str_62, 7, 40),
                " where ",
                0) +
    7)
10 Or(40 >
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " where ",
               0) +
   7,
   40 ==
   str.indexof(str.substr(db_select_s_str_62, 7, 40),
               " where ",
               0) +
   7)
11 Not(str.substr(str.substr(db_select_s_str_62, 7, 40), 16, 2) ==
    "inventory")

```

注意，我们可以通过使用`ConcolicTracer`获得语法中不存在的约束。 特别要看的是，我们如何能够获得表必须为`inventory`（谓词11）的条件才能使模糊测试成功。

我们如何将它们提升为语法？ 特别是我们如何自动做到这一点？ 我们有一个选择是简单地切换获得的最后一个谓词。 在我们的例子中，最后一个谓词是（11）。 我们可以简单地颠倒谓词并再次求解吗？

```py
new_path = _.path[0:-1] + [z3.Not(_.path[-1])]

```

```py
new_ = ConcolicTracer((_.decls, new_path))
new_.fn = _.fn
new_.fn_args = _.fn_args

```

```py
new_.zeval()

```

```py
('No Solutions', None)

```

实际上，由于要比较的字符串长度不同，因此这将不起作用。

```py
print(_.path[-1])
z3.solve(z3.Not(_.path[-1]))

```

```py
Not(str.substr(str.substr(db_select_s_str_62, 7, 40), 16, 2) ==
    "inventory")
no solution

```

一个更好的主意是研究正在进行哪些*字符串*比较，并将其与语法中的相应节点相关联。 让我们检查一下派生树（为避免递归结构而进行修剪，并着重于重要部分）。

```py
from [GrammarFuzzer](GrammarFuzzer.html) import display_tree

```

```py
prune_tokens = [
    '<value>', '<table>', '<column>', '<literals>', '<exprs>', '<bexpr>'
]
dt = tgf.prune_tree(qtree, prune_tokens)
display_tree(dt)

```

<svg height="176pt" viewBox="0.00 0.00 374.50 176.00" width="375pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 172)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="165" y="-156.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="165" y="-105.8"><query></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="18" y="-54.8">select</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="78" y="-54.8"><exprs></text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="137" y="-54.8">from</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="194" y="-54.8"><table></text></g> <g class="edge" id="edge6"><title>1->6</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="255" y="-54.8">where</text></g> <g class="edge" id="edge8"><title>1->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="318" y="-54.8"><bexpr></text></g> <g class="edge" id="edge9"><title>1->9</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="78" y="-3.8">z,(k(w)+N)</text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="194" y="-3.8">W0</text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="318" y="-3.8">T9(e)*i+b-E!=Yt</text></g> <g class="edge" id="edge10"><title>9->10</title></g></g></svg>

我们能否确定输入的哪一部分由语法的哪一部分提供？ 我们定义了`span()`，它可以从派生树中恢复此信息。 对于给定的节点，让我们假设起点是已知的。 然后，为了处理这些子项，我们进行如下操作：我们从左到右一次选择一个子项，并计算该子项的长度。 除了我们的起点之外，当前孩子之前的孩子的长度也给出了当前孩子的起点。 每个节点的终点只是其最后一个子节点的终点（如果是叶子，则为节点的长度）。

```py
from [GrammarFuzzer](GrammarFuzzer.html) import START_SYMBOL

```

```py
def span(node, g, node_start=0):
    hm = {}
    k, cs = node
    end_i = node_start
    new_cs = []
    for c in cs:
        chm, (ck, child_start, child_end, gcs) = span(c, g, end_i)
        new_cs.append((ck, child_start, child_end, gcs))
        end_i = child_end
        hm.update(chm)
    node_end = end_i if cs else node_start + len(k)
    if k in g and k != START_SYMBOL:
        hm[k] = (node_start, node_end - node_start)
    return hm, (k, node_start, node_end, new_cs)

```

我们按如下方式使用它：

```py
span_hm, _n = span(dt, INVENTORY_GRAMMAR)

```

```py
span_hm

```

```py
{'<exprs>': (7, 10),
 '<table>': (23, 2),
 '<bexpr>': (32, 15),
 '<query>': (0, 47)}

```

我们可以检查是否获得了正确的值，如下所示。

```py
print("query:", query)
for k in span_hm:
    start, l = span_hm[k]
    print(k, query[start:start + l])

```

```py
query: select z,(k(w)+N) from W0 where T9(e)*i+b-E!=Yt
<exprs> z,(k(w)+N)
<table> W0
<bexpr> T9(e)*i+b-E!=Yt
<query> select z,(k(w)+N) from W0 where T9(e)*i+b-E!=Yt

```

接下来，我们需要获取每个谓词中的所有比较。 为此，我们定义了两个辅助函数。 第一个是`unwrap_substrings()`，它将多个调用转换为`z3.SubString`，并返回给定z3字符串表达式的开始和长度。

```py
def unwrap_substrings(s):
    assert s.decl().name() == 'str.substr'
    cs, frm, l = s.children()
    fl = frm.as_long()
    ll = l.as_long()
    if cs.decl().name() == 'str.substr':
        newfrm, _l = unwrap_substrings(cs)
        return (fl + newfrm, ll)
    else:
        return (fl, ll)

```

我们定义了遍历给定z3字符串表达式的`traverse_z3()`，并将所有直接的字符串比较收集到原始参数的子字符串中。

```py
def traverse_z3(p, hm):
    def z3_as_string(v):
        s = v.as_string()
        # Z3 bug: Python z3 API returns quoted strings for as_string
        assert s[0] == '"' and s[-1] == '"'
        return s[1:-1]

    n = p.decl().name()
    if n == 'not':
        return traverse_z3(p.children()[0], hm)
    elif n == '=':
        i, j = p.children()
        if isinstance(i, (int, z3.IntNumRef)):
            return traverse_z3(j, hm)
        elif isinstance(j, (int, z3.IntNumRef)):
            return traverse_z3(i, hm)
        else:
            if i.is_string() and j.is_string():
                if i.is_string_value():
                    cs, frm, l = j.children()
                    if (isinstance(frm, z3.IntNumRef)
                            and isinstance(l, z3.IntNumRef)):
                        hm[z3_as_string(i)] = unwrap_substrings(j)
                elif j.is_string_value():
                    cs, frm, l = i.children()
                    if (isinstance(frm, z3.IntNumRef)
                            and isinstance(l, z3.IntNumRef)):
                        hm[z3_as_string(j)] = unwrap_substrings(i)
            else:
                assert False  # for now
    elif n == '<' or n == '>':
        i, j = p.children()
        if isinstance(i, (int, z3.IntNumRef)):
            return traverse_z3(j, hm)
        elif isinstance(j, (int, z3.IntNumRef)):
            return traverse_z3(i, hm)
        else:
            assert False
    return p

```

```py
comparisons = {}
for p in _.path:
    traverse_z3(p, comparisons)
comparisons

```

```py
{'inventory': (23, 2)}

```

现在我们需要做的是声明与`comparisons`中的子字符串匹配的字符串变量，并为路径中的每个项目求解它们。 为此，我们定义`find_alternatives()`。

```py
def find_alternatives(spans, cmp):
    alts = {}
    for key in spans:
        start, l = spans[key]
        rset = set(range(start, start + l))
        for ckey in cmp:
            cstart, cl = cmp[ckey]
            cset = set(range(cstart, cstart + cl))
            # if rset.issubset(cset): <- ignoring subsets for now.
            if rset == cset:
                if key not in alts:
                    alts[key] = set()
                alts[key].add(ckey)
    return alts

```

我们如下使用它。

```py
alternatives = find_alternatives(span_hm, comparisons)
alternatives

```

```py
{'<table>': {'inventory'}}

```

因此，对于语法中的每个键，我们都有替代方法。 现在，我们可以按以下方式更新语法。

```py
INVENTORY_GRAMMAR_NEW = dict(INVENTORY_GRAMMAR)

```

```py
for k in alternatives:
    INVENTORY_GRAMMAR_NEW[k] = INVENTORY_GRAMMAR_NEW[k] + list(alternatives[k])

```

我们在这里做出了选择。 我们可以完全覆盖`<table>`的定义。 相反，我们在现有定义中添加了新的替代方法。 这样，我们的模糊器也会偶尔尝试`<table>`的其他值。

```py
INVENTORY_GRAMMAR_NEW['<table>']

```

```py
['<word>', 'inventory']

```

让我们尝试用我们的新语法模糊测试。

```py
cgf = ConcolicGrammarFuzzer(INVENTORY_GRAMMAR_NEW)

```

```py
for i in range(10):
    qtree = cgf.fuzz_tree()
    query = cgf.tree_to_string(qtree)
    print(query)
    with ExpectError():
        try:
            with ConcolicTracer() as _:
                res = _[db_select](query)
            print(repr(res))
        except SQLException as e:
            print(e)
        print()

```

```py
select (A*S*l(e)+h) from inventory where l/j<G(G)<P
Invalid WHERE ('(l/j<G(G)<P)')

update W5RhS set TT8=P7 where E!=b+e
Table ('W5RhS') was not found

delete from inventory where Z(F)/y*W/v<R/w/L(Y)/R
Invalid WHERE ('Z(F)/y*W/v<R/w/L(Y)/R')

delete from Y where -02==:
Table ('Y') was not found

insert into ZQ (C3y,h4,o3) values ('D','.{Y')
Table ('ZQ') was not found

select oy((r42),K==_==D!=(:),((h>N/W)),(k!=p)) from inventory
Invalid WHERE ('(oy((r42),K==_==D!=(:),((h>N/W)),(k!=p)))')

select X==(r),Y,l from inventory where J/x*f>M(h)/B
Invalid WHERE ('(J/x*f>M(h)/B)')

select _6(U,K5)>a-N-n+C*e+s-n from inventory
Invalid WHERE ('(_6(U,K5)>a-N-n+C*e+s-n)')

select n-V/X/X<e(s),fqw(f+v) from inventory
Invalid WHERE ('(n-V/X/X<e(s),fqw(f+v))')

select (d769R090x1Of),(b9S9G89) from inventory
Invalid WHERE ('((d769R090x1Of),(b9S9G89))')

```

也就是说，我们能够达到危险的方法`my_eval()`。 实际上，我们所做的是将谓词的一部分提升为语法。 新语法可以生成比以前更深入程序的输入。 注意，我们只处理了相等谓词。 如果需要，还可以将'<'和'>'比较运算符转换为语法。

将我们的模糊器的输出与下面的原始`GrammarFuzzer`进行比较。

```py
gf = GrammarFuzzer(INVENTORY_GRAMMAR)
for i in range(10):
    query = gf.fuzz()
    print(query)
    with ExpectError():
        try:
            res = db_select(query)
            print(repr(res))
        except SQLException as e:
            print(e)
        print()

```

```py
select (D),j from _4 where N/d/I*o>h
Table ('_4') was not found

select r-u*Z/d*S/L-h,qN,T!=r from t
Table ('t') was not found

delete from _04e where -5!=(X)*p(x)+w*l(J,i,b)/Y/d
Table ('_04e') was not found

delete from c3 where --6!=o8M!=o==79156.443
Table ('c3') was not found

insert into F (Lu9g:917s) values ('B')
Table ('F') was not found

delete from W where j/S*S+P/d/x>a(a)+M(V)
Table ('W') was not found

delete from NQ where D(u)-R+X(X,X)==x/w*s/R
Table ('NQ') was not found

delete from C3 where A*q+u/M(z,X)<v<8+X*I(A)==821.2
Table ('C3') was not found

delete from Tg06M6 where t+V/S<s+.+S
Table ('Tg06M6') was not found

select 5,G from z3 where W>Z==l(W)/g*D-T
Table ('z3') was not found

```

可以看出，原始的语法模糊检查器无法进行表格验证以外的操作。

#### 在一起

我们在`ConcolicGrammarFuzzer`中实现这些方法。 方法`update_grammar()`允许`ConcolicGrammarFuzzer`收集来自圆锥模糊测试的反馈，并相应地更新用于模糊测试的语法。

```py
class ConcolicGrammarFuzzer(ConcolicGrammarFuzzer):
    def prune_tokens(self, tokens):
        self.prune_tokens = tokens

    def update_grammar(self, trace):
        self.comparisons = {}
        for p in trace.path:
            traverse_z3(p, comparisons)
        alternatives = find_alternatives(self.span_range, comparisons)
        if self.log:
            print('Alternatives:', alternatives, 'Span:', self.span_range)
        new_grammar = dict(self.grammar)
        for k in alternatives:
            new_grammar[k] = list(set(new_grammar[k] + list(alternatives[k])))
        self.grammar = new_grammar

```

`fuzz()`方法仅生成派生树，计算跨度范围，并返回从派生树生成的字符串。

```py
class ConcolicGrammarFuzzer(ConcolicGrammarFuzzer):
    def fuzz(self):
        qtree = self.fuzz_tree()
        self.pruned_tree = self.prune_tree(qtree, self.prune_tokens)
        query = self.tree_to_string(qtree)
        self.span_range, _n = span(self.pruned_tree, self.grammar)
        return query

```

为了确保我们的方法可行，让我们稍微更新一下表。

```py
inventory = db.db.pop('inventory', None)

```

```py
db.db['vehicles'] = inventory
db.db['months'] = ({
    'month': int,
    'name': str
}, [{
    'month': i + 1,
    'name': m
} for i, m in enumerate([
    'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct',
    'nov', 'dec'
])])
db.db

```

```py
{'vehicles': ({'year': int, 'kind': str, 'company': str, 'model': str},
  [{'year': 1997, 'kind': 'van', 'company': 'Ford', 'model': 'E350'},
   {'year': 2000, 'kind': 'car', 'company': 'Mercury', 'model': 'Cougar'},
   {'year': 1999, 'kind': 'car', 'company': 'Chevy', 'model': 'Venture'}]),
 'months': ({'month': int, 'name': str},
  [{'month': 1, 'name': 'jan'},
   {'month': 2, 'name': 'feb'},
   {'month': 3, 'name': 'mar'},
   {'month': 4, 'name': 'apr'},
   {'month': 5, 'name': 'may'},
   {'month': 6, 'name': 'jun'},
   {'month': 7, 'name': 'jul'},
   {'month': 8, 'name': 'aug'},
   {'month': 9, 'name': 'sep'},
   {'month': 10, 'name': 'oct'},
   {'month': 11, 'name': 'nov'},
   {'month': 12, 'name': 'dec'}])}

```

`ConcolicGrammarFuzzer`的用法如下。

```py
cgf = ConcolicGrammarFuzzer(INVENTORY_GRAMMAR)
cgf.prune_tokens(prune_tokens)
for i in range(10):
    query = cgf.fuzz()
    print(query)
    with ConcolicTracer() as _:
        with ExpectError():
            try:
                res = _[db_select](query)
                print(repr(res))
            except SQLException as e:
                print(e)
        cgf.update_grammar(_)
        print()

```

```py
delete from b where m/C!=L-K*Q<H+c(O)+-0*H/G
Table ('b') was not found

update O8F set E=g,p=Z where y>p
Table ('O8F') was not found

update months set wz=w5 where _*E/O-K(U)<Z+v/G/r/c
Column ('wz') was not found

delete from months where r-z-Q-d*d-h-P!=r1R
Invalid WHERE ('r-z-Q-d*d-h-P!=r1R')

insert into muc (n23f2W6sOP) values ('y')
Table ('muc') was not found

insert into months (.9) values ('T|gi','O',1.86,-501.9,0.1,48199462.8)
Column ('.9') was not found

select w<O/X-S from months where Y/b>Z*K*Z*B
Invalid WHERE ('(Y/b>Z*K*Z*B)')

insert into vehicles (name) values (5.0,25,4.0,'/')
Column ('name') was not found

update months set kind=kN,company=a,kind=O where (S<S(Q))==I+t<z
Column ('kind') was not found

update vehicles set month=kU7,company=k,company=M,name=X where (((b+L)==0.4))==4
Column ('month') was not found

```

可以看出，模糊器在不了解表`vehicles`，`months`和`years`的情况下开始，但从主旨执行中识别出该表，并将其提升为语法。 这使我们可以提高模糊测试的效率。

## 限制

与动态污点分析一样，隐式控制流可以掩盖在执行执行策略时遇到的谓词。 但是，可以通过在源中使用其各自的代理对象包装任何常量来在某种程度上克服此限制。 类似地，对内部C函数的调用可能导致符号信息被丢弃，并且只能获取部分信息。

## 经验教训

*   在程序行为方面，共谋执行通常可以提供比污点分析更多的信息。 但是，这需要更大的运行时成本。 因此，与污点分析不同，实时分析通常是不可能的。

*   与污点分析类似，concord执行也受到诸如间接控制流和内部函数调用之类的限制。

*   礼节性执行的谓词可以与模糊结合使用，以提供比污点更可靠的错误行为指示，并且可以用于创建更能产生有效输入的语法。

## 后续步骤

[象征性模糊](SymbolicFuzzer.html)是一种更昂贵但更强大的替代方法。 同样，基于[搜索的模糊检测](SearchBasedFuzzer.html)通常可以提供比依赖SMT求解器来提供与当前路径稍有不同的输入便宜的探索策略。

## 背景

调和执行技术最初用于通知和扩展*符号执行* [ [King *等人*，1976年。](https://doi.org/10.1145/360248.360252)]，一种程序的静态分析技术。 分析。 Laron等。 cite {Larson2003}是第一个使用调和执行技术的人。

Cadar等人提出了使用代理对象来收集约束的想法。 [Cadar *等人*，2005。]。 PeerCheck [ [A. Bruni *等人*，2011\.](https://hoheinzollern.files.wordpress.com/2008/04/seer1.pdf) ]和Python错误查找器[[DamiánBarsotti *]等人*，2018。](https://doi.org/https://doi.org/10.1016/j.entcs.2018.06.003)。

## 练习

### 练习1：仿照浮点代理类

在实现`zint`二进制运算符时，我们断言结果是`int`。 但是，不必如此。 例如，除法会导致`float`。 因此，我们需要`float`的代理对象。 您可以为`float`实现类似的代理对象并修复`zint`二进制运算符定义吗？

**解决方案。** 解决方案如下。

与`zint`一样，我们首先打开`zfloat`进行扩展。

```py
class zfloat(float):
    def __new__(cls, context, zn, v, *args, **kw):
        return float.__new__(cls, v, *args, **kw)

```

然后，我们实现初始化方法。

```py
class zfloat(zfloat):
    @classmethod
    def create(cls, context, zn, v=None):
        return zproxy_create(cls, 'Real', z3.Real, context, zn, v)

    def __init__(self, context, z, v=None):
        self.z, self.v = z, v
        self.context = context

```

当二进制操作中的参数之一不是`float`时的帮助器。

```py
class zfloat(zfloat):
    def _zv(self, o):
        return (o.z, o.v) if isinstance(o, zfloat) else (z3.RealVal(o), o)

```

将`float`强制转换为bool值，以便在有条件的情况下使用。

```py
class zfloat(zfloat):
    def __bool__(self):
        # force registering boolean condition
        if self != 0.0:
            return True
        return False

```

定义比较方法的通用代理方法

```py
def make_float_bool_wrapper(fname, fun, zfun):
    def proxy(self, other):
        z, v = self._zv(other)
        z_ = zfun(self.z, z)
        v_ = fun(self.v, v)
        return zbool(self.context, z_, v_)

    return proxy

```

我们将比较方法应用于已定义的`zfloat`类。

```py
FLOAT_BOOL_OPS = [
    '__eq__',
    # '__req__',
    '__ne__',
    # '__rne__',
    '__gt__',
    '__lt__',
    '__le__',
    '__ge__',
]

```

```py
for fname in FLOAT_BOOL_OPS:
    fun = getattr(float, fname)
    zfun = getattr(z3.ArithRef, fname)
    setattr(zfloat, fname, make_float_bool_wrapper(fname, fun, zfun))

```

同样，我们为二进制运算符定义了通用代理方法。

```py
def make_float_binary_wrapper(fname, fun, zfun):
    def proxy(self, other):
        z, v = self._zv(other)
        z_ = zfun(self.z, z)
        v_ = fun(self.v, v)
        return zfloat(self.context, z_, v_)

    return proxy

```

并将它们应用于`zfloat`

```py
FLOAT_BINARY_OPS = [
    '__add__',
    '__sub__',
    '__mul__',
    '__truediv__',
    # '__div__',
    '__mod__',
    # '__divmod__',
    '__pow__',
    # '__lshift__',
    # '__rshift__',
    # '__and__',
    # '__xor__',
    # '__or__',
    '__radd__',
    '__rsub__',
    '__rmul__',
    '__rtruediv__',
    # '__rdiv__',
    '__rmod__',
    # '__rdivmod__',
    '__rpow__',
    # '__rlshift__',
    # '__rrshift__',
    # '__rand__',
    # '__rxor__',
    # '__ror__',
]

```

```py
for fname in FLOAT_BINARY_OPS:
    fun = getattr(float, fname)
    zfun = getattr(z3.ArithRef, fname)
    setattr(zfloat, fname, make_float_binary_wrapper(fname, fun, zfun))

```

这些用途如下。

```py
with ConcolicTracer() as _:
    za = zfloat.create(_.context, 'float_a', 1.0)
    zb = zfloat.create(_.context, 'float_b', 0.0)
    if za * zb:
        print(1)

```

```py
_.context

```

```py
({'float_a': 'Real', 'float_b': 'Real'}, [Not(float_a*float_b != 0)])

```

最后，我们修复了`zint`二进制包装程序，以便在需要时正确创建`zfloat`。

```py
def make_int_binary_wrapper(fname, fun, zfun):
    def proxy(self, other):
        z, v = self._zv(other)
        z_ = zfun(self.z, z)
        v_ = fun(self.v, v)
        if isinstance(v_, float):
            return zfloat(self.context, z_, v_)
        elif isinstance(v_, int):
            return zint(self.context, z_, v_)
        else:
            assert False

    return proxy

```

```py
for fname in INT_BINARY_OPS:
    fun = getattr(int, fname)
    zfun = getattr(z3.ArithRef, fname)
    setattr(zint, fname, make_int_binary_wrapper(fname, fun, zfun))

```

检查它是否按预期工作。

```py
with ConcolicTracer() as _:
    v = _[binomial](4, 2)

```

```py
_.zeval()

```

```py
('sat', {'n': '4', 'k': '2'})

```

### 练习2：位操作

与浮点运算类似，实现位处理功能（例如`xor`）涉及将`int`转换为其等效的位向量，对其进行操作，然后将其转换回原始类型。 您可以为`zint`实现位操作操作吗？

**Solution.** The solution is as follows.

我们首先像以前一样定义代理方法。

```py
def make_int_bit_wrapper(fname, fun, zfun):
    def proxy(self, other):
        z, v = self._zv(other)
        z_ = z3.BV2Int(
            zfun(
                z3.Int2BV(
                    self.z, num_bits=64), z3.Int2BV(
                    z, num_bits=64)))
        v_ = fun(self.v, v)
        return zint(self.context, z_, v_)

    return proxy

```

然后将其应用于`zint`类。

```py
BIT_OPS = [
    '__lshift__',
    '__rshift__',
    '__and__',
    '__xor__',
    '__or__',
    '__rlshift__',
    '__rrshift__',
    '__rand__',
    '__rxor__',
    '__ror__',
]

```

```py
for fname in BIT_OPS:
    fun = getattr(int, fname)
    zfun = getattr(z3.BitVecRef, fname)
    setattr(zint, fname, make_int_bit_wrapper(fname, fun, zfun))

```

反转是唯一的一元位操作方法。

```py
class zint(zint):
    def __invert__(self):
        return zint(self.context, z3.BV2Int(
            ~z3.Int2BV(self.z, num_bits=64)), ~self.v)

```

如果`xor`得出非零值，则`my_fn()`计算`xor`并返回`True`。

```py
def my_fn(a, b):
    o_ = (a | b)
    a_ = (a & b)
    if o_ & ~a_:
        return True
    else:
        return False

```

在`ConcolicTracer`下使用

```py
with ConcolicTracer() as _:
    print(_[my_fn](2, 1))

```

```py
True

```

我们记录计算出的SMT表达式以验证一切正常。

```py
_.zeval(log=True)

```

```py
(declare-const my_fn_a_int_96 Int)
(declare-const my_fn_b_int_97 Int)
(assert (let ((a!1 (bvnot (bvor (bvnot ((_ int2bv 64) my_fn_a_int_96))
                        (bvnot ((_ int2bv 64) my_fn_b_int_97))))))
(let ((a!2 (bvor (bvnot (bvor ((_ int2bv 64) my_fn_a_int_96)
                              ((_ int2bv 64) my_fn_b_int_97)))
                 a!1)))
  (not (= 0 (bv2int (bvnot a!2)))))))
(check-sat)
(get-model)
sat
(model 
  (define-fun my_fn_a_int_96 () Int
    (- 18446744073709551617))
  (define-fun my_fn_b_int_97 () Int
    (- 2))
)

```

```py
('sat', {'a': ['-', '18446744073709551617'], 'b': ['-', '2']})

```

我们可以从生成的公式中确认位操作功能可以正常工作。

### 练习3：字符串转换函数

我们已经看到了如何定义`upper()`和`lower()`。 您可以定义`capitalize()`，`title()`和`swapcase()`方法吗？

**解决方案。** 解决方案尚不可用。