使用Z3解算器的ord()函数或字符串的ASCII字符代码

时间:2018-12-14 06:57:29

标签: python-3.4 z3 z3py

如何将z3.String转换为ASCII值序列?

例如,下面是一些我认为可以检查字符串中所有字符的ASCII值是否总计为100的代码:

import z3

def add_ascii_values(password):
    return sum(ord(character) for character in password)

password = z3.String("password")
solver = z3.Solver()
ascii_sum = add_ascii_values(password)
solver.add(ascii_sum == 100)

print(solver.check())
print(solver.model())

不幸的是,我收到此错误:

TypeError: ord() expected string of length 1, but SeqRef found

很明显ordz3.String不兼容。 Z3中有什么功能吗?

1 个答案:

答案 0 :(得分:1)

您正在混合Python字符串和Z3字符串;不幸的是,两者是完全不同的类型。

在Z3py中,String只是8位值的序列。 Z3所能做的实际上非常有限。例如,您不能像在add_ascii_values函数中那样遍历字符。请参见本页以了解允许的功能:https://rise4fun.com/z3/tutorialcontent/sequences(此页面以SMTLib的术语列出;但是等效的功能可从z3py界面获得。)

使用Z3序列和字符串时,需要记住一些重要的限制/事项:

  • 您必须非常明确地说明长度;特别是,您不能sum遍历任意符号长度的字符串。您可以执行一些操作而无需显式指定长度,但这是有限的。 (例如正则表达式匹配,子字符串提取等)

  • 您不能从字符串中提取字符。我认为这是一个疏忽,但SMTLib暂时无法这样做。相反,您会得到一个长度为1的列表。这在编程中引起很多麻烦,但是有解决方法。见下文。

  • 每当您遍历字符串/序列时,都必须上升到固定范围。有多种编程方法,因此您可以将“ N为止的所有字符串都覆盖为一个常数” N,但是它们确实很毛。

牢记所有这些,我将像下面这样对您的示例进行编码;将password的长度限制为10个字符:

from z3 import *

s = Solver()

# Work around the fact that z3 has no way of giving us an element at an index. Sigh.
ordHelperCounter = 0
def OrdAt(inp, i):
    global ordHelperCounter
    v = BitVec("OrdAtHelper_%d_%d" % (i, ordHelperCounter), 8)
    ordHelperCounter += 1
    s.add(Unit(v) == SubString(inp, i, 1))
    return v

# Your original function, but note the addition of len parameter and use of Sum
def add_ascii_values(password, len):
    return Sum([OrdAt(password, i) for i in range(len)])

# We'll have to force a constant length
length = 10
password = String("password")
s.add(Length(password) == 10)
ascii_sum = add_ascii_values(password, length)
s.add(ascii_sum == 100)

# Also require characters to be printable so we can view them:
for i in range(length):
  v = OrdAt(password, i)
  s.add(v >= 0x20)
  s.add(v <= 0x7E)

print(s.check())
print(s.model()[password])

OrdAt函数可解决无法提取字符的问题。还要注意我们如何使用Sum而不是sum,以及所有“循环”如何具有固定的迭代计数。为了方便起见,我还添加了一些约束以使所有ascii代码都可打印。

运行此命令,您将得到:

sat
":X|@`y}@@@"

让我们检查一下它的确不错:

>>> len(":X|@`y}@@@")
10
>>> sum(ord(character) for character in ":X|@`y}@@@")
868

因此,我们确实得到了长度为10的字符串;但是ord的总和为什么不等于100?现在,您必须记住序列是由8位值组成的,因此该算术以256为模进行。因此,总和实际上是:

>>> sum(ord(character) for character in ":X|@`y}@@@") % 256
100

为避免溢出,可以使用更大的位向量,或者更简单地使用Z3的无界整数类型Int。为此,只需将BV2Int更改为:即可使用add_ascii_values函数:

def add_ascii_values(password, len):
    return Sum([BV2Int(OrdAt(password, i)) for i in range(len)])

现在我们将得到:

unsat

那是因为我们每个字符的值都至少为0x20,而我们需要10个字符;所以没有办法使它们的总和等于100。而z3正是在告诉我们。如果将总和目标提高到更合理的水平,您将开始获得适当的价值。

使用z3py进行编程与使用Python进行常规编程不同,并且z3 String对象与Python本身完全不同。请注意,SMTLib人员甚至尚未对序列/字符串逻辑进行标准化,因此情况可能会发生变化。 (特别是,我希望它们会添加用于在索引处提取元素的功能!)。

说了这么多,遍历https://rise4fun.com/z3/tutorialcontent/sequences是熟悉它们的好开始,并可以随时提出进一步的问题。