angr/tests/procedures/libc/test_string.py
Kevin Phoenix f939c5b88c
Enable ruff isort rule (#6452)
* Enable ruff isort rule

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-02 14:48:07 -07:00

1106 lines
44 KiB
Python
Executable file

#!/usr/bin/env python3
# pylint: disable=missing-class-docstring,no-self-use,line-too-long
from __future__ import annotations
__package__ = __package__ or "tests.procedures.libc" # pylint:disable=redefined-builtin
import logging
import random
import unittest
from itertools import combinations
import claripy
import angr
from angr import SIM_LIBRARIES, SimState
from tests.common import broken
log = logging.getLogger("angr.tests.string")
def make_state_with_stdin(content):
s = SimState(arch="AMD64", mode="symbolic")
stdin_storage = angr.storage.file.SimFile("stdin", content=content)
stdin = angr.storage.file.SimFileDescriptor(stdin_storage)
s.register_plugin("posix", angr.state_plugins.SimSystemPosix(stdin=stdin_storage, fd={0: stdin}))
return s
def make_func(name):
return lambda state, arguments: (
SIM_LIBRARIES["libc.so.6"][0].get(name, "AMD64").execute(state, arguments=arguments).ret_expr
)
strstr = make_func("strstr")
strtok_r = make_func("strtok_r")
strcmp = make_func("strcmp")
strchr = make_func("strchr")
strncmp = make_func("strncmp")
strlen = make_func("strlen")
strncpy = make_func("strncpy")
strcpy = make_func("strcpy")
memset = make_func("memset")
memcpy = make_func("memcpy")
memcmp = make_func("memcmp")
sprintf = make_func("sprintf")
getc = make_func("_IO_getc")
fgetc = make_func("fgetc")
getchar = make_func("getchar")
scanf = make_func("scanf")
wcscmp = make_func("wcscmp")
class TestStringSimProcedures(unittest.TestCase):
def test_strlen(self):
s = SimState(arch="AMD64", mode="symbolic")
log.info("fully concrete string")
a_str = claripy.BVV(0x41414100, 32)
a_addr = claripy.BVV(0x10, 64)
s.memory.store(a_addr, a_str, endness="Iend_BE")
a_len = strlen(s, arguments=[a_addr])
assert s.solver.unique(a_len)
assert s.solver.eval(a_len) == 3
log.info("concrete-terminated string")
b_str = claripy.Concat(claripy.BVS("mystring", 24), claripy.BVV(0, 8))
b_addr = claripy.BVV(0x20, 64)
s.memory.store(b_addr, b_str, endness="Iend_BE")
b_len = strlen(s, arguments=[b_addr])
assert s.solver.max_int(b_len) == 3
assert tuple(sorted(s.solver.eval_upto(b_len, 10))) == (0, 1, 2, 3)
log.info("fully unconstrained")
u_addr = claripy.BVV(0x50, 64)
u_len_sp = strlen(s, arguments=[u_addr])
u_len = u_len_sp
assert len(s.solver.eval_upto(u_len, 100)) == s.libc.buf_symbolic_bytes
assert s.solver.max_int(u_len) == s.libc.buf_symbolic_bytes - 1
# print u_len_sp.solver.maximum_null
# s.add_constraints(u_len < 16)
assert s.solver.eval_upto(s.memory.load(0x50 + u_len, 1), 300) == [0]
#
# This tests if a strlen can influence a symbolic str.
#
log.info("Trying to influence length.")
s = SimState(arch="AMD64", mode="symbolic")
str_c = claripy.BVS("some_string", 8 * 16)
c_addr = claripy.BVV(0x10, 64)
s.memory.store(c_addr, str_c, endness="Iend_BE")
c_len = strlen(s, arguments=[c_addr])
assert len(s.solver.eval_upto(c_len, 100)) == s.libc.buf_symbolic_bytes
assert s.solver.max_int(c_len) == s.libc.buf_symbolic_bytes - 1
one_s = s.copy()
one_s.add_constraints(c_len == 1)
assert one_s.solver.eval(str_c, cast_to=bytes).index(b"\x00") == 1
str_test = one_s.memory.load(c_addr, 2, endness="Iend_BE")
assert len(one_s.solver.eval_upto(str_test, 300, cast_to=bytes)) == 255
for i in range(16):
test_s = s.copy()
test_s.add_constraints(c_len == i)
str_test = test_s.memory.load(c_addr, i + 1, endness="Iend_BE")
assert test_s.solver.eval(str_test, cast_to=bytes).index(b"\x00") == i
for j in range(i):
assert not test_s.solver.unique(test_s.memory.load(c_addr + j, 1))
def test_strcmp(self):
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(0x41414100, 32)
str_b = claripy.BVS("mystring", 32)
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s.memory.store(a_addr, str_a, endness="Iend_BE")
s.memory.store(b_addr, str_b, endness="Iend_BE")
s_cmp = s.copy()
cmpres = strcmp(s_cmp, arguments=[a_addr, b_addr])
s_match = s_cmp.copy()
s_nomatch = s_cmp.copy()
s_match.add_constraints(cmpres == 0)
s_nomatch.add_constraints(cmpres != 0)
assert s_match.solver.unique(str_b)
assert not s_nomatch.solver.unique(str_b)
assert s_match.solver.eval(str_b, cast_to=bytes) == b"AAA\x00"
s_ncmp = s.copy()
ncmpres = strncmp(s_ncmp, arguments=[a_addr, b_addr, claripy.BVV(2, s.arch.bits)])
s_match = s_ncmp.copy()
s_nomatch = s_ncmp.copy()
s_match.add_constraints(ncmpres == 0)
s_nomatch.add_constraints(ncmpres != 0)
assert not s_match.solver.unique(str_b)
assert s_match.solver.unique(s_match.memory.load(b_addr, 2))
assert len(s_match.solver.eval_upto(s_match.memory.load(b_addr, 3), 300)) == 256
assert not s_nomatch.solver.unique(str_b)
log.info("concrete a, symbolic b")
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(0x41424300, 32)
str_b = claripy.BVS("mystring", 32)
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s.memory.store(a_addr, str_a, endness="Iend_BE")
s.memory.store(b_addr, str_b, endness="Iend_BE")
s_cmp = s.copy()
cmpres = strncmp(s_cmp, arguments=[a_addr, b_addr, claripy.BVV(2, s_cmp.arch.bits)])
s_match = s_cmp.copy()
s_nomatch = s_cmp.copy()
s_match.add_constraints(cmpres == 0)
s_nomatch.add_constraints(cmpres != 0)
assert s_match.solver.solution(str_b, 0x41420000)
assert s_match.solver.solution(str_b, 0x41421234)
assert s_match.solver.solution(str_b, 0x41424300)
assert not s_nomatch.solver.solution(str_b, 0x41420000)
assert not s_nomatch.solver.solution(str_b, 0x41421234)
assert not s_nomatch.solver.solution(str_b, 0x41424300)
log.info("symbolic a, symbolic b")
s = SimState(arch="AMD64", mode="symbolic")
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s_cmp = s.copy()
cmpres = strcmp(s_cmp, arguments=[a_addr, b_addr])
s_match = s_cmp.copy()
s_nomatch = s_cmp.copy()
s_match.add_constraints(cmpres == 0)
s_nomatch.add_constraints(cmpres != 0)
m_res = strcmp(s_match, arguments=[a_addr, b_addr])
s_match.add_constraints(m_res != 0)
nm_res = strcmp(s_nomatch, arguments=[a_addr, b_addr])
s_nomatch.add_constraints(nm_res == 0)
assert not s_match.satisfiable()
assert not s_match.satisfiable()
def test_strncmp(self):
log.info("symbolic left, symbolic right, symbolic len")
s = SimState(arch="AMD64", mode="symbolic")
left = claripy.BVS("left", 32)
left_addr = claripy.BVV(0x1000, 64)
right = claripy.BVS("right", 32)
right_addr = claripy.BVV(0x2000, 64)
maxlen = claripy.BVS("len", 64)
s.memory.store(left_addr, left)
s.memory.store(right_addr, right)
s.add_constraints(strlen(s, arguments=[left_addr]) == 3)
s.add_constraints(strlen(s, arguments=[right_addr]) == 0)
s.add_constraints(maxlen != 0)
c = strncmp(s, arguments=[left_addr, right_addr, maxlen])
s_match = s.copy()
s_match.add_constraints(c == 0)
assert not s_match.satisfiable()
# assert s_match.solver.min_int(maxlen) == 3
s_nomatch = s.copy()
s_nomatch.add_constraints(c != 0)
assert s_nomatch.satisfiable()
# assert s_nomatch.solver.max_int(maxlen) == 2
log.info("zero-length")
s = SimState(arch="AMD64", mode="symbolic")
left = claripy.BVS("left", 32)
left_addr = claripy.BVV(0x1000, 64)
right = claripy.BVS("right", 32)
right_addr = claripy.BVV(0x2000, 64)
maxlen = claripy.BVS("len", 64)
left_len = strlen(s, arguments=[left_addr])
right_len = strlen(s, arguments=[right_addr])
c = strncmp(s, arguments=[left_addr, right_addr, maxlen])
s.add_constraints(right_len == 0)
s.add_constraints(left_len == 0)
# s.add_constraints(c == 0)
s.add_constraints(maxlen == 0)
assert s.satisfiable()
def test_strncmp_longer_limit(self):
log.info("concrete a, concrete b, concrete n")
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(b"ABC\0")
str_b = claripy.BVV(b"AB\0")
addr_a = claripy.BVV(0x10, 64)
addr_b = claripy.BVV(0xB0, 64)
s.memory.store(addr_a, str_a, endness="Iend_BE")
s.memory.store(addr_b, str_b, endness="Iend_BE")
ss_res = strncmp(s, arguments=[addr_a, addr_b, claripy.BVV(3, 64)])
assert s.solver.unique(ss_res)
assert s.solver.eval(ss_res) != 0
def test_strncmp_find_limits(self):
log.info("concrete a, concrete b, symbolic n")
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(b"ABCD\0")
str_b = claripy.BVV(b"ABCE\0")
addr_a = claripy.BVV(0x10, 64)
addr_b = claripy.BVV(0xB0, 64)
s.memory.store(addr_a, str_a, endness="Iend_BE")
s.memory.store(addr_b, str_b, endness="Iend_BE")
n = claripy.BVS("n", 64)
ss_res = strncmp(s, arguments=[addr_a, addr_b, n])
s.add_constraints(ss_res == 0)
assert set(s.solver.eval_upto(n, 10)) == {0, 1, 2, 3}
def test_strncmp_find_prefix(self):
log.info("concrete a, symbolic b, symbolic n")
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(b"ABCD\0")
str_b = claripy.BVS("str_b", len(str_a))
addr_a = claripy.BVV(0x10, 64)
addr_b = claripy.BVV(0xB0, 64)
s.memory.store(addr_a, str_a, endness="Iend_BE")
s.memory.store(addr_b, str_b, endness="Iend_BE")
# All prefixes should be valid
n = strlen(s, arguments=[addr_b])
ss_res = strncmp(s, arguments=[addr_a, addr_b, n])
s.add_constraints(ss_res == 0)
assert set(s.solver.eval_upto(n, 10)) == {0, 1, 2, 3, 4}
def test_strncmp_find_prefix_unsat(self):
log.info("concrete a, concrete b, symbolic n")
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(b"\0\0\0\0\0")
str_b = claripy.BVV(b"ABCE\0")
addr_a = claripy.BVV(0x10, 64)
addr_b = claripy.BVV(0xB0, 64)
s.memory.store(addr_a, str_a, endness="Iend_BE")
s.memory.store(addr_b, str_b, endness="Iend_BE")
# No prefix should be valid
n = strlen(s, arguments=[addr_b])
ss_res = strncmp(s, arguments=[addr_a, addr_b, n])
s.add_constraints(ss_res == 0)
assert not s.satisfiable()
def test_strncmp_find_input_for_limit(self):
log.info("concrete a, symbolic b, symbolic n")
s = SimState(arch="AMD64", mode="symbolic")
str_a = claripy.BVV(b"ABCD\0")
str_b = claripy.BVS("str_b", len(str_a))
addr_a = claripy.BVV(0x10, 64)
addr_b = claripy.BVV(0xB0, 64)
s.memory.store(addr_a, str_a, endness="Iend_BE")
s.memory.store(addr_b, str_b, endness="Iend_BE")
n = claripy.BVS("n", 64)
ss_res = strncmp(s, arguments=[addr_a, addr_b, n])
s.add_constraints(ss_res == 0)
# Check constrained limit produces only expected input
for i in range(1, len(str_b) // 8):
s2 = s.copy()
s2.add_constraints(n == i)
hi, lo = 5 * 8 - 1, (5 - i) * 8
substr_a, substr_b = str_a[hi:lo], str_b[hi:lo]
assert s2.solver.unique(substr_b)
assert s2.solver.solution(substr_b, substr_a)
def test_strstr_conc_haystack_conc_needle(self):
log.info("concrete haystack and needle")
s = SimState(arch="AMD64", mode="symbolic")
str_haystack = claripy.BVV(0x41424300, 32)
str_needle = claripy.BVV(0x42430000, 32)
addr_haystack = claripy.BVV(0x10, 64)
addr_needle = claripy.BVV(0xB0, 64)
s.memory.store(addr_haystack, str_haystack, endness="Iend_BE")
s.memory.store(addr_needle, str_needle, endness="Iend_BE")
ss_res = strstr(s, arguments=[addr_haystack, addr_needle])
assert s.solver.unique(ss_res)
assert s.solver.eval(ss_res) == 0x11
def test_strstr_conc_haystack_sym_needle(self):
log.info("concrete haystack, symbolic needle")
s = SimState(arch="AMD64", mode="symbolic")
s.libc.max_symbolic_strstr = 20
haystack = b"ABCD"
str_haystack = claripy.BVV(haystack + b"\0")
str_needle = claripy.BVS("needle", 32)
addr_haystack = claripy.BVV(0x10, 64)
addr_needle = claripy.BVV(0xB0, 64)
s.memory.store(addr_haystack, str_haystack, endness="Iend_BE")
s.memory.store(addr_needle, str_needle, endness="Iend_BE")
s.memory.store(addr_needle + 4, claripy.BVV(0, 8))
ss_res = strstr(s, arguments=[addr_haystack, addr_needle])
results = set(s.solver.eval_upto(ss_res, len(haystack) * 2))
expected = {(addr_haystack.concrete_value + i) for i in range(len(haystack))} | {0}
assert results == expected
s_match = s.copy()
s_nomatch = s.copy()
s_match.add_constraints(ss_res != 0)
s_nomatch.add_constraints(ss_res == 0)
# Check all substrings
substrings = [haystack[l:h] for l, h in combinations(range(len(haystack) + 1), 2)]
needle_len = len(str_needle) // 8
for substring in substrings:
solution = claripy.BVV(substring.ljust(needle_len, b"\0"))
assert s_match.solver.solution(str_needle, solution)
assert not s_match.solver.solution(str_needle, claripy.BVV(b"AC\0\0"))
assert not s_match.solver.solution(str_needle, claripy.BVV(b"AD\0\0"))
assert s_nomatch.solver.satisfiable()
def test_strstr_sym_haystack_conc_needle(self):
log.info("symbolic haystack, concrete needle")
s = SimState(arch="AMD64", mode="symbolic")
s.libc.max_symbolic_strstr = 20
str_haystack = claripy.BVS("haystack", 5 * 8).concat(claripy.BVV(0, 8))
str_needle = claripy.BVV(b"ABC\0")
addr_haystack = claripy.BVV(0x10, 64)
addr_needle = claripy.BVV(0xB0, 64)
s.memory.store(addr_haystack, str_haystack, endness="Iend_BE")
s.memory.store(addr_needle, str_needle, endness="Iend_BE")
ss_res = strstr(s, arguments=[addr_haystack, addr_needle])
s_match = s.copy()
s_match.add_constraints(ss_res != 0)
s_nomatch = s.copy()
s_nomatch.add_constraints(ss_res == 0)
num_possible = min(s.libc.max_symbolic_strstr, 1 + (len(str_haystack) - len(str_needle)) // 8)
expected = set(
range(
addr_haystack.concrete_value,
addr_haystack.concrete_value + num_possible,
)
)
results = set(s_match.solver.eval_exact(ss_res, num_possible))
assert results == expected
assert s_nomatch.solver.satisfiable()
def test_strstr_sym_haystack_sym_needle(self):
log.info("symbolic haystack, symbolic needle")
s = SimState(arch="AMD64", mode="symbolic")
s.libc.max_symbolic_strstr = 20
s.libc.buf_symbolic_bytes = 10
str_haystack = claripy.BVS("haystack", s.libc.buf_symbolic_bytes * 8)
str_needle = claripy.BVS("needle", s.libc.buf_symbolic_bytes * 8)
addr_haystack = claripy.BVV(0x10, 64)
addr_needle = claripy.BVV(0xB0, 64)
s.memory.store(addr_haystack, str_haystack, endness="Iend_BE")
s.memory.store(addr_needle, str_needle, endness="Iend_BE")
ss_res = strstr(s, arguments=[addr_haystack, addr_needle])
s_match = s.copy()
s_nomatch = s.copy()
s_match.add_constraints(ss_res != 0)
s_nomatch.add_constraints(ss_res == 0)
s_i = s_match.copy()
s_i.add_constraints(str_needle == claripy.BVV(b"123".ljust(s.libc.buf_symbolic_bytes, b"\0")))
# Check needle is not found after end of haystack
s_ez = s_i.copy()
s_ez.add_constraints(str_haystack[len(str_haystack) - 1 : len(str_haystack) - 8] == 0)
assert not s_ez.satisfiable()
# Check target of specific offset
for i in range(5):
s_n = s_i.copy()
s_n.add_constraints(ss_res == addr_haystack + i)
h = s_n.solver.eval(str_haystack, cast_to=bytes)
assert h.find(b"123") == i
len_needle = strlen(s_match, arguments=[addr_needle])
match_cmp = strncmp(s_match, arguments=[ss_res, addr_needle, len_needle])
assert s_match.solver.eval_upto(match_cmp, 10) == [0]
r_mm = strstr(s_match, arguments=[addr_haystack, addr_needle])
s_match.add_constraints(r_mm == 0)
assert not s_match.satisfiable()
assert s_nomatch.satisfiable()
s_nss = s_nomatch.copy()
nomatch_ss = strstr(s_nss, arguments=[addr_haystack, addr_needle])
s_nss.add_constraints(nomatch_ss != 0)
assert not s_nss.satisfiable()
def test_strstr_inconsistency(self):
log.info("symbolic haystack, symbolic needle")
s = SimState(arch="AMD64", mode="symbolic")
s.libc.buf_symbolic_bytes = 2
addr_haystack = claripy.BVV(0x10, 64)
addr_needle = claripy.BVV(0xB0, 64)
# len_needle = strlen(s, inline=True, arguments=[addr_needle])
ss_res = strstr(s, arguments=[addr_haystack, addr_needle])
# slh_res = strlen(s, inline=True, arguments=[addr_haystack])
# sln_res = strlen(s, inline=True, arguments=[addr_needle])
# print "LENH:", s.solver.eval_upto(slh_res, 100)
# print "LENN:", s.solver.eval_upto(sln_res, 100)
assert not s.solver.unique(ss_res)
assert sorted(s.solver.eval_upto(ss_res, 100)) == [0, *list(range(16, 16 + s.libc.buf_symbolic_bytes - 1))]
s.add_constraints(ss_res != 0)
ss2 = strstr(s, arguments=[addr_haystack, addr_needle])
s.add_constraints(ss2 == 0)
assert not s.satisfiable()
def test_memcpy(self):
log.info("concrete src, concrete dst, concrete len")
log.debug("... full copy")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVV(0x42424242, 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
memcpy(s, arguments=[dst_addr, src_addr, claripy.BVV(4, 64)])
new_dst = s.memory.load(dst_addr, 4, endness="Iend_BE")
assert s.solver.eval_upto(new_dst, 2, cast_to=bytes) == [b"BBBB"]
log.info("giant copy")
s = SimState(arch="AMD64", mode="symbolic", remove_options=angr.options.simplification)
s.memory._maximum_symbolic_size = 0x2000000
size = claripy.BVV(0x1000000, 64)
data = claripy.BVS("giant", 8 * 0x1_000_000)
dst_addr = claripy.BVV(0x2000000, 64)
src_addr = claripy.BVV(0x4000000, 64)
s.memory.store(src_addr, data)
memcpy(s, arguments=[dst_addr, src_addr, size])
assert s.memory.load(dst_addr, size) is s.memory.load(src_addr, size)
log.debug("... partial copy")
s = SimState(arch="AMD64", mode="symbolic")
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
memcpy(s, arguments=[dst_addr, src_addr, claripy.BVV(2, 64)])
new_dst = s.memory.load(dst_addr, 4, endness="Iend_BE")
assert s.solver.eval_upto(new_dst, 2, cast_to=bytes) == [b"BBAA"]
log.info("symbolic src, concrete dst, concrete len")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
# make sure it copies it all
memcpy(s, arguments=[dst_addr, src_addr, claripy.BVV(4, 64)])
assert s.satisfiable()
s.add_constraints(src != s.memory.load(dst_addr, 4))
assert not s.satisfiable()
log.info("symbolic src, concrete dst, symbolic len")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
cpylen = claripy.BVS("len", 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
s.add_constraints(cpylen < 10)
memcpy(s, arguments=[dst_addr, src_addr, cpylen])
result = s.memory.load(dst_addr, 4, endness="Iend_BE")
# make sure it copies it all
s1 = s.copy()
s1.add_constraints(cpylen == 1)
assert s1.solver.unique(s1.memory.load(dst_addr + 1, 3))
assert len(s1.solver.eval_upto(s1.memory.load(dst_addr, 1), 300)) == 256
s2 = s.copy()
s2.add_constraints(cpylen == 2)
assert len(s2.solver.eval_upto(result[31:24], 300)) == 256
assert len(s2.solver.eval_upto(result[23:16], 300)) == 256
assert s2.solver.eval_upto(result[15:0], 300, cast_to=bytes) == [b"AA"]
log.info("concrete src, concrete dst, symbolic len")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVV(0x42424242, 32)
src_addr = claripy.BVV(0x2000, 64)
s = SimState(arch="AMD64", mode="symbolic")
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
cpylen = claripy.BVS("len", 64)
s.add_constraints(claripy.ULE(cpylen, 4))
memcpy(s, arguments=[dst_addr, src_addr, cpylen])
new_dst = s.memory.load(dst_addr, 4, endness="Iend_BE")
assert sorted(s.solver.eval_upto(new_dst, 300, cast_to=bytes)) == [b"AAAA", b"BAAA", b"BBAA", b"BBBA", b"BBBB"]
def test_memcmp(self):
log.info("concrete src, concrete dst, concrete len")
log.debug("... full cmp")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVV(0x42424242, 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
r = memcmp(s, arguments=[dst_addr, src_addr, claripy.BVV(4, 64)])
assert s.satisfiable()
s_pos = s.copy()
s_pos.add_constraints(r.SGE(0))
assert not s_pos.satisfiable()
s_neg = s.copy()
s_neg.add_constraints(r.SLT(0))
assert s_neg.satisfiable()
log.debug("... zero cmp")
s = SimState(arch="AMD64", mode="symbolic")
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
r = memcmp(s, arguments=[dst_addr, src_addr, claripy.BVV(0, 64)])
assert s.solver.eval_upto(r, 2) == [0]
log.info("symbolic src, concrete dst, concrete len")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
# make sure it copies it all
r = memcmp(s, arguments=[dst_addr, src_addr, claripy.BVV(4, 64)])
s_match = s.copy()
s_match.add_constraints(r == 0)
m = s_match.memory.load(src_addr, 4)
assert s_match.solver.eval_upto(m, 2) == [0x41414141]
s_nomatch = s.copy()
s_nomatch.add_constraints(r != 0)
m = s_nomatch.memory.load(src_addr, 4)
assert not s_nomatch.solver.solution(m, 0x41414141)
log.info("symbolic src, concrete dst, symbolic len")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414141, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
cmplen = claripy.BVS("len", 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
r = memcmp(s, arguments=[dst_addr, src_addr, cmplen])
# look at effects of different lengths
s1 = s.copy()
s1.add_constraints(cmplen == 1)
s1.add_constraints(r == 0)
log.debug("... simplifying")
s1.solver._solver.simplify()
log.debug("... solving")
assert s1.solver.eval_upto(src[31:24], 2) == [0x41]
assert not s1.solver.unique(src[31:16])
log.debug("... solved")
s2 = s.copy()
s2.add_constraints(cmplen == 2)
s2.add_constraints(r == 0)
assert s2.solver.eval_upto(s2.memory.load(src_addr, 2), 2) == [0x4141]
assert not s2.solver.unique(s2.memory.load(src_addr, 3))
s2u = s.copy()
s2u.add_constraints(cmplen == 2)
s2u.add_constraints(r == 1)
assert not s2u.solver.solution(s2u.memory.load(src_addr, 2), 0x4141)
def test_strncpy(self):
log.info("concrete src, concrete dst, concrete len")
log.debug("... full copy")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414100, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVV(0x42420000, 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
strncpy(s, arguments=[dst_addr, src_addr, claripy.BVV(3, 64)])
new_dst = s.memory.load(dst_addr, 4, endness="Iend_BE")
assert s.solver.eval(new_dst, cast_to=bytes) == b"BB\x00\x00"
log.debug("... partial copy")
s = SimState(arch="AMD64", mode="symbolic")
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
strncpy(s, arguments=[dst_addr, src_addr, claripy.BVV(2, 64)])
new_dst = s.memory.load(dst_addr, 4, endness="Iend_BE")
assert s.solver.eval_upto(new_dst, 2, cast_to=bytes) == [b"BBA\x00"]
log.info("symbolic src, concrete dst, concrete len")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414100, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
# make sure it copies it all
s.add_constraints(strlen(s, arguments=[src_addr]) == 2)
# sanity check
s_false = s.copy()
s_false.add_constraints(strlen(s_false, arguments=[src_addr]) == 3)
assert not s_false.satisfiable()
strncpy(s, arguments=[dst_addr, src_addr, 3])
assert s.satisfiable()
c = strcmp(s, arguments=[dst_addr, src_addr])
assert s.solver.eval_upto(c, 10) == [0]
log.info("symbolic src, concrete dst, symbolic len")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414100, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
maxlen = claripy.BVS("len", 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
# make sure it copies it all
s.add_constraints(strlen(s, arguments=[src_addr]) == 2)
strncpy(s, arguments=[dst_addr, src_addr, maxlen])
c = strcmp(s, arguments=[dst_addr, src_addr])
s_match = s.copy()
s_match.add_constraints(c == 0)
assert s_match.solver.min_int(maxlen) == 3
s_nomatch = s.copy()
s_nomatch.add_constraints(c != 0)
assert s_nomatch.solver.max_int(maxlen) == 2
log.info("concrete src, concrete dst, symbolic len")
log.debug("... full copy")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414100, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVV(0x42420000, 32)
src_addr = claripy.BVV(0x2000, 64)
maxlen = claripy.BVS("len", 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
strncpy(s, arguments=[dst_addr, src_addr, maxlen])
r = s.memory.load(dst_addr, 4, endness="Iend_BE")
# print repr(r.solver.eval_upto(r, 10, cast_to=bytes))
assert sorted(s.solver.eval_upto(r, 10, cast_to=bytes)) == [b"AAA\x00", b"BAA\x00", b"BB\x00\x00", b"BBA\x00"]
def test_strcpy(self):
log.info("concrete src, concrete dst")
log.debug("... full copy")
s = SimState(arch="AMD64", mode="symbolic")
dst = claripy.BVV(0x41414100, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVV(0x42420000, 32)
src_addr = claripy.BVV(0x2000, 64)
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
strcpy(s, arguments=[dst_addr, src_addr])
new_dst = s.memory.load(dst_addr, 4, endness="Iend_BE")
assert s.solver.eval(new_dst, cast_to=bytes) == b"BB\x00\x00"
log.info("symbolic src, concrete dst")
dst = claripy.BVV(0x41414100, 32)
dst_addr = claripy.BVV(0x1000, 64)
src = claripy.BVS("src", 32)
src_addr = claripy.BVV(0x2000, 64)
s = SimState(arch="AMD64", mode="symbolic")
s.memory.store(dst_addr, dst)
s.memory.store(src_addr, src)
ln = strlen(s, arguments=[src_addr])
strcpy(s, arguments=[dst_addr, src_addr])
cm = strcmp(s, arguments=[dst_addr, src_addr])
s.add_constraints(cm == 0)
s.add_constraints(ln == 15)
# readsize = 16
# both_strs = claripy.Concat(
# *[
# s.memory.load(dst_addr, readsize, endness="Iend_BE"),
# s.memory.load(src_addr, readsize, endness="Iend_BE"),
# ]
# )
# for i in s.solver.eval_upto(both_strs, 50, cast_to=bytes):
# assert s.solver.eval_upto(10) == [0]
# assert s.solver.solution(s.memory.load(dst_addr, 4, endness="Iend_BE"), 0x42434400)
# assert s.solver.solution(s.memory.load(dst_addr, 4, endness="Iend_BE"), 0x42434445)
# assert s.solver.solution(s.memory.load(dst_addr, 4, endness="Iend_BE"), 0x00414100)
# assert not s.solver.solution(s.memory.load(dst_addr, 4, endness="Iend_BE"), 0x00010203)
@broken
def test_sprintf(self):
log.info("concrete src, concrete dst, concrete len")
s = SimState(mode="symbolic", arch="PPC32")
format_str = claripy.BVV(0x25640000, 32)
format_addr = claripy.BVV(0x2000, 32)
# dst = claripy.BVV("destination", 128)
dst_addr = claripy.BVV(0x1000, 32)
arg = claripy.BVS("some_number", 32)
s.memory.store(format_addr, format_str)
sprintf(s, arguments=[dst_addr, format_addr, arg])
for i in range(9):
j = random.randint(10**i, 10 ** (i + 1))
s2 = s.copy()
s2.add_constraints(arg == j)
# print s2.solver.eval_upto(s2.memory.load(dst_addr, i+2), 2, cast_to=bytes), repr(b"%d\x00" % j)
assert s2.solver.eval_upto(s2.memory.load(dst_addr, i + 2), 2, cast_to=bytes) == [b"%d\x00" % j]
s2 = s.copy()
s2.add_constraints(arg == 0)
# print s2.solver.eval_upto(s2.memory.load(dst_addr, 2), 2, cast_to=bytes), repr(b"%d\x00" % 0)
assert s2.solver.eval_upto(s2.memory.load(dst_addr, 2), 2, cast_to=bytes) == [b"%d\x00" % 0]
def test_memset(self):
log.info("concrete src, concrete dst, concrete len")
s = SimState(arch="PPC32", mode="symbolic")
dst = claripy.BVV(0, 128)
dst_addr = claripy.BVV(0x1000, 32)
char = claripy.BVV(0x00000041, 32)
char2 = claripy.BVV(0x50505050, 32)
length = claripy.BVS("some_length", 32)
s.memory.store(dst_addr, dst)
memset(s, arguments=[dst_addr, char, claripy.BVV(3, 32)])
assert s.solver.eval(s.memory.load(dst_addr, 4)) == 0x41414100
log.debug("Symbolic length")
s = SimState(arch="PPC32", mode="symbolic")
s.memory.store(dst_addr, dst)
length = claripy.BVS("some_length", 32)
s.add_constraints(length < 10)
memset(s, arguments=[dst_addr, char2, length])
log.debug("Trying 2")
s_two = s.copy()
s_two.add_constraints(length == 2)
assert s_two.solver.eval(s_two.memory.load(dst_addr, 4)) == 0x50500000
log.debug("Trying 0")
s_zero = s.copy()
s_zero.add_constraints(length == 0)
assert s_zero.solver.eval(s_zero.memory.load(dst_addr, 4)) == 0x00000000
log.debug("Trying 5")
s_five = s.copy()
s_five.add_constraints(length == 5)
assert s_five.solver.eval(s_five.memory.load(dst_addr, 6)) == 0x505050505000
def test_strchr(self):
log.info("concrete haystack and needle")
s = SimState(arch="AMD64", mode="symbolic")
str_haystack = claripy.BVV(0x41424300, 32)
str_needle = claripy.BVV(0x42, 64)
addr_haystack = claripy.BVV(0x10, 64)
s.memory.store(addr_haystack, str_haystack, endness="Iend_BE")
ss_res = strchr(s, arguments=[addr_haystack, str_needle])
assert s.solver.unique(ss_res)
assert s.solver.eval(ss_res) == 0x11
log.info("concrete haystack, symbolic needle")
s = SimState(arch="AMD64", mode="symbolic")
str_haystack = claripy.BVV(0x41424300, 32)
str_needle = claripy.BVS("wtf", 64)
chr_needle = str_needle[7:0]
addr_haystack = claripy.BVV(0x10, 64)
s.memory.store(addr_haystack, str_haystack, endness="Iend_BE")
ss_res = strchr(s, arguments=[addr_haystack, str_needle])
assert not s.solver.unique(ss_res)
assert len(s.solver.eval_upto(ss_res, 10)) == 5
s_match = s.copy()
s_nomatch = s.copy()
s_match.add_constraints(ss_res != 0)
s_nomatch.add_constraints(ss_res == 0)
assert s_match.satisfiable()
assert s_nomatch.satisfiable()
assert len(s_match.solver.eval_upto(chr_needle, 300)) == 4
assert len(s_nomatch.solver.eval_upto(chr_needle, 300)) == 252
assert sorted(s_match.solver.eval_upto(ss_res, 300)) == [0x10, 0x11, 0x12, 0x13]
assert sorted(s_match.solver.eval_upto(chr_needle, 300)) == [0x00, 0x41, 0x42, 0x43]
s_match.memory.store(ss_res, claripy.BVV(0x44, 8))
assert sorted(s_match.solver.eval_upto(s_match.memory.load(0x10, 1), 300)) == [0x41, 0x44]
assert sorted(s_match.solver.eval_upto(s_match.memory.load(0x11, 1), 300)) == [0x42, 0x44]
assert sorted(s_match.solver.eval_upto(s_match.memory.load(0x12, 1), 300)) == [0x43, 0x44]
assert sorted(s_match.solver.eval_upto(s_match.memory.load(0x13, 1), 300)) == [0x00, 0x44]
# l.info("symbolic haystack, symbolic needle")
# s = SimState(arch="AMD64", mode="symbolic")
# s.libc.buf_symbolic_bytes = 5
# addr_haystack = claripy.BVV(0x10, 64)
# addr_needle = claripy.BVV(0xb0, 64)
# len_needle = strlen(s, inline=True, arguments=[addr_needle])
# ss_res = strstr(s, inline=True, arguments=[addr_haystack, addr_needle])
# ss_val = s.expr_value(ss_res)
# assert not ss_val.is_unique()
# assert len(ss_val.solver.eval_upto(100)) == s.libc.buf_symbolic_bytes
# s_match = s.copy()
# s_nomatch = s.copy()
# s_match.add_constraints(ss_res != 0)
# s_nomatch.add_constraints(ss_res == 0)
# match_cmp = strncmp(s_match, inline=True, arguments=[ss_res, addr_needle, len_needle])
# match_cmp_val = s_match.expr_value(match_cmp)
# assert match_cmp_val.solver.eval_upto(10) == [0]
# r_mm = strstr(s_match, inline=True, arguments=[addr_haystack, addr_needle])
# s_match.add_constraints(r_mm == 0)
# assert not s_match.satisfiable()
# assert s_nomatch.satisfiable()
# s_nss = s_nomatch.copy()
# nomatch_ss = strstr(s_nss, inline=True, arguments=[addr_haystack, addr_needle])
# s_nss.add_constraints(nomatch_ss != 0)
# assert not s_nss.satisfiable()
@broken
def test_strtok_r(self):
log.debug("CONCRETE MODE")
s = SimState(arch="AMD64", mode="symbolic")
s.memory.store(100, claripy.BVV(0x4141414241414241424300, 88), endness="Iend_BE")
s.memory.store(200, claripy.BVV(0x4200, 16), endness="Iend_BE")
str_ptr = claripy.BVV(100, s.arch.bits)
delim_ptr = claripy.BVV(200, s.arch.bits)
state_ptr = claripy.BVV(300, s.arch.bits)
st1 = strtok_r(s, arguments=[str_ptr, delim_ptr, state_ptr])
assert s.solver.eval_upto(st1, 10) == [104]
assert s.solver.eval_upto(s.memory.load(st1 - 1, 1), 10) == [0]
assert s.solver.eval_upto(s.memory.load(200, 2), 10) == [0x4200]
st2 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st2, 10) == [107]
assert s.solver.eval_upto(s.memory.load(st2 - 1, 1), 10) == [0]
st3 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st3, 10) == [109]
assert s.solver.eval_upto(s.memory.load(st3 - 1, 1), 10) == [0]
st4 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st4, 10) == [0]
assert s.solver.eval_upto(s.memory.load(300, s.arch.bytes, endness=s.arch.memory_endness), 10) == [109]
st5 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st5, 10) == [0]
assert s.solver.eval_upto(s.memory.load(300, s.arch.bytes, endness=s.arch.memory_endness), 10) == [109]
s.memory.store(1000, claripy.BVV(0x4141414241414241424300, 88), endness="Iend_BE")
s.memory.store(2000, claripy.BVV(0x4200, 16), endness="Iend_BE")
str_ptr = claripy.BVV(1000, s.arch.bits)
delim_ptr = claripy.BVV(2000, s.arch.bits)
state_ptr = claripy.BVV(3000, s.arch.bits)
st1 = strtok_r(s, arguments=[str_ptr, delim_ptr, state_ptr])
assert s.solver.eval_upto(st1, 10) == [1004]
assert s.solver.eval_upto(s.memory.load(st1 - 1, 1), 10) == [0]
assert s.solver.eval_upto(s.memory.load(2000, 2), 10) == [0x4200]
st2 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st2, 10) == [1007]
assert s.solver.eval_upto(s.memory.load(st2 - 1, 1), 10) == [0]
st3 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st3, 10) == [1009]
assert s.solver.eval_upto(s.memory.load(st3 - 1, 1), 10) == [0]
st4 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st4, 10) == [0]
assert s.solver.eval_upto(s.memory.load(3000, s.arch.bytes, endness=s.arch.memory_endness), 10) == [1009]
st5 = strtok_r(s, arguments=[claripy.BVV(0, s.arch.bits), delim_ptr, state_ptr])
assert s.solver.eval_upto(st5, 10) == [0]
assert s.solver.eval_upto(s.memory.load(3000, s.arch.bytes, endness=s.arch.memory_endness), 10) == [1009]
s = SimState(arch="AMD64", mode="symbolic")
str_ptr = claripy.BVV(100, s.arch.bits)
delim_ptr = claripy.BVV(200, s.arch.bits)
state_ptr = claripy.BVV(300, s.arch.bits)
s.add_constraints(s.memory.load(delim_ptr, 1) != 0)
st1 = strtok_r(s, arguments=[str_ptr, delim_ptr, state_ptr])
s.add_constraints(st1 != 0)
assert s.solver.eval_upto(s.memory.load(st1 - 1, 1), 10) == [0]
def test_getc(self):
s = make_state_with_stdin(b"1234")
stdin = s.posix.get_fd(0)
s.mem[0x1000 + 0x70].int = 0
assert s.solver.eval_one(stdin.tell()) == 0
# The argument of getc should be a FILE *
c = getc(s, [0x1000])
assert s.solver.eval_upto(c, 300) == [0x31]
assert s.solver.eval_upto(stdin.tell(), 300) == [1]
c = getc(s, [0x1000])
assert s.solver.eval_upto(c, 300) == [0x32]
assert s.solver.eval_upto(stdin.tell(), 300) == [2]
c = getc(s, [0x1000])
assert s.solver.eval_upto(c, 300) == [0x33]
assert s.solver.eval_upto(stdin.tell(), 300) == [3]
c = getc(s, [0x1000])
assert s.solver.eval_upto(c, 300) == [0x34]
assert s.solver.eval_upto(stdin.tell(), 300) == [4]
def test_getchar(self):
s = make_state_with_stdin(b"1234")
stdin = s.posix.get_fd(0)
assert s.solver.eval_upto(stdin.tell(), 300) == [0]
c = getchar(s, arguments=[])
assert s.solver.eval_upto(c, 300) == [0x31]
assert s.solver.eval_upto(stdin.tell(), 300) == [1]
c = getchar(s, arguments=[])
assert s.solver.eval_upto(c, 300) == [0x32]
assert s.solver.eval_upto(stdin.tell(), 300) == [2]
c = getchar(s, arguments=[])
assert s.solver.eval_upto(c, 300) == [0x33]
assert s.solver.eval_upto(stdin.tell(), 300) == [3]
c = getchar(s, arguments=[])
assert s.solver.eval_upto(c, 300) == [0x34]
assert s.solver.eval_upto(stdin.tell(), 300) == [4]
def test_scanf(self):
s = make_state_with_stdin(b"Hello\n")
s.memory.store(0x2000, b"%1s\0")
scanf(s, [0x2000, 0x1000])
assert s.solver.eval_upto(s.memory.load(0x1000, 2), 2, cast_to=bytes) == [b"H\x00"]
def test_strcmp_concrete(self):
log.info("concrete a, concrete b")
s = SimState(arch="AMD64", mode="symbolic")
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s.memory.store(a_addr, b"heck\x00")
s.memory.store(b_addr, b"heck\x00")
r = strcmp(s, arguments=[a_addr, b_addr])
assert s.solver.eval_upto(r, 2) == [0]
log.info("concrete a, empty b")
s = SimState(arch="AMD64", mode="symbolic")
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s.memory.store(a_addr, b"heck\x00")
s.memory.store(b_addr, b"\x00")
r = strcmp(s, arguments=[a_addr, b_addr])
assert s.solver.eval_upto(r, 2) == [1]
log.info("empty a, concrete b")
s = SimState(arch="AMD64", mode="symbolic")
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s.memory.store(a_addr, b"\x00")
s.memory.store(b_addr, b"heck\x00")
r = strcmp(s, arguments=[a_addr, b_addr])
assert s.solver.eval_upto(r, 2) == [0xFFFFFFFF]
log.info("empty a, empty b")
s = SimState(arch="AMD64", mode="symbolic")
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
s.memory.store(a_addr, b"\x00")
s.memory.store(b_addr, b"\x00")
r = strcmp(s, arguments=[a_addr, b_addr])
assert s.solver.eval_upto(r, 2) == [0]
def test_wcscmp(self):
# concrete cases for the wide char version sufficiently overlap with strcmp and friends
log.info("concrete a, symbolic b")
s = SimState(arch="AMD64", mode="symbolic")
heck = "heck\x00".encode("utf-16")[2:] # remove encoding prefix
a_addr = claripy.BVV(0x10, 64)
b_addr = claripy.BVV(0xB0, 64)
b_bvs = claripy.BVS("b", len(heck) * 8)
s.memory.store(a_addr, heck)
s.memory.store(b_addr, b_bvs)
r = wcscmp(s, arguments=[a_addr, b_addr])
solutions = s.solver.eval_upto(b_bvs, 2, cast_to=bytes, extra_constraints=(r == 0,))
assert solutions == [heck]
def test_string_without_null(self):
s = SimState(arch="AMD64", mode="symbolic")
str_ = b"abcd"
str_addr = claripy.BVV(0x10, 64)
s.memory.store(str_addr, str_)
assert s.solver.eval(s.mem[str_addr].string.resolved, cast_to=bytes) == b"abcd"
if __name__ == "__main__":
unittest.main()