Skip to content

Commit 601e2e9

Browse files
committed
Adds pytest tests
1 parent 3f50eba commit 601e2e9

23 files changed

+299
-69
lines changed

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ readme = "README.md"
88
packages = [{include = "zeratool_lib"}]
99

1010
[tool.poetry.dependencies]
11-
python = "^3.11"
11+
python = "^3.10"
1212
angr = "^9.2.52"
1313
claripy = "^9.2.52"
1414
ipython = "^8.13.2"
@@ -17,6 +17,9 @@ r2pipe = "^1.8.0"
1717
timeout-decorator = "^0.5.0"
1818
tqdm = "^4.65.0"
1919

20+
[tool.poetry.group.dev.dependencies]
21+
pytest = "^7.3.1"
22+
2023
[build-system]
2124
requires = ["poetry-core"]
2225
build-backend = "poetry.core.masonry.api"

tests/test_zeratool_binaries.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from zeratool_lib import ZeratoolExploit, ZeratoolInputStreams, exploit
2+
3+
4+
def test_bof() -> None:
5+
exploit_data = exploit(
6+
"zeratool_binaries/bin/bof_32.elf",
7+
input_stream=ZeratoolInputStreams.STDIN,
8+
overflow_only=False,
9+
force_shellcode=True,
10+
skip_check=True,
11+
)
12+
13+
assert exploit_data.payload is not None
14+
assert exploit_data.outcome == ZeratoolExploit.Outcomes.SHELL
15+
16+
17+
def test_bof_with_nx() -> None:
18+
exploit_data = exploit(
19+
"zeratool_binaries/bin/bof_nx_32.elf",
20+
input_stream=ZeratoolInputStreams.STDIN,
21+
overflow_only=False,
22+
leak_format=b"{.*}",
23+
skip_check=True,
24+
)
25+
26+
assert exploit_data.payload is not None
27+
assert exploit_data.outcome == ZeratoolExploit.Outcomes.SHELL
28+
29+
30+
def test_bof_with_win_function() -> None:
31+
exploit_data = exploit(
32+
"zeratool_binaries/bin/bof_win_32.elf",
33+
input_stream=ZeratoolInputStreams.STDIN,
34+
overflow_only=True,
35+
win_funcs=["get_secret"],
36+
leak_format=b"{.*}",
37+
skip_check=True,
38+
)
39+
40+
assert exploit_data.payload is not None
41+
assert exploit_data.outcome == ZeratoolExploit.Outcomes.CALL_TO_WIN
42+
43+
44+
def test_format_string_attack_for_leak() -> None:
45+
exploit(
46+
"zeratool_binaries/bin/read_stack_32.elf",
47+
input_stream=ZeratoolInputStreams.STDIN,
48+
format_only=True,
49+
leak_format=b"(.*)BEGIN PRIVATE KEY(.*)",
50+
skip_check=False,
51+
)
52+
53+
# TODO: Check why this is not generating an exploit despite the replication of the
54+
# test from original README.md and tests/format_test.py.
55+
56+
57+
def test_format_string_attack_for_win_call() -> None:
58+
exploit(
59+
"zeratool_binaries/bin/format_pc_write_32.elf",
60+
input_stream=ZeratoolInputStreams.STDIN,
61+
format_only=True,
62+
win_funcs=["secret_function"],
63+
leak_format=b"(.*)BEGIN PRIVATE KEY(.*)",
64+
)
65+
66+
# TODO: Check why this is not generating an exploit despite the replication of the
67+
# test from original README.md and tests/format_test.py.

tests/zeratool_binaries/Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Adapted from Zeratool's tests/Makefile
2+
3+
build_flags := -fno-stack-protector -z execstack \
4+
-Wno-implicit-function-declaration -no-pie \
5+
-Wno-format-security -fcf-protection=none -mno-shstk
6+
build_NX_flags := -fno-stack-protector \
7+
-Wno-implicit-function-declaration -no-pie \
8+
-Wno-format-security -z relro -fcf-protection=none -mno-shstk
9+
10+
CC := gcc
11+
12+
all: build_bof build_format
13+
14+
build_bof:
15+
$(CC) -m32 buffer_overflow.c -o bin/bof_32.elf $(build_flags)
16+
$(CC) -m32 buffer_overflow.c -o bin/bof_nx_32.elf $(build_NX_flags)
17+
$(CC) -m32 buffer_overflow.c -o bin/bof_win_32.elf -Dwin_func $(build_flags)
18+
19+
build_format:
20+
$(CC) -O0 -m32 -fno-stack-protector -o bin/read_stack_32.elf \
21+
format_string.c -DEASY $(build_flags)
22+
$(CC) -O0 -m32 -fno-stack-protector -o bin/format_pc_write_32.elf \
23+
format_string.c -DMEDIUM $(build_flags) -z relro
24+
$(CC) -O0 -m32 -fno-stack-protector -o bin/format_write_and_constrain_32.elf \
25+
format_string.c -DHARD $(build_flags)
26+
27+
clean:
28+
rm -rf bin/*
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Zeratool's tests/buffer_overflow.c
2+
3+
#include <stdio.h>
4+
5+
#ifdef win_func
6+
void get_secret()
7+
{
8+
puts("flag{you_did_it}");
9+
}
10+
#endif
11+
12+
#ifdef srop_func
13+
__attribute__((naked)) void syscall_gad()
14+
{
15+
__asm__("syscall");
16+
__asm__("ret");
17+
}
18+
__attribute__((naked)) void pop_rax()
19+
{
20+
__asm__("pop %rax");
21+
__asm__("ret");
22+
}
23+
__attribute__((naked)) void pop_rdi()
24+
{
25+
__asm__("pop %rdi");
26+
__asm__("ret");
27+
}
28+
#endif
29+
30+
#ifdef dlresolve_read_func
31+
32+
void give_gadgets()
33+
{
34+
__asm__("pop %rdx");
35+
__asm__("ret");
36+
}
37+
38+
int pwn_me()
39+
{
40+
char my_buf[20] = {'\x00'};
41+
printf("Your buffer is at %p\n", my_buf);
42+
read(0, my_buf, 230);
43+
return 0;
44+
}
45+
46+
#else
47+
48+
int pwn_me()
49+
{
50+
char my_buf[20] = {'\x00'};
51+
printf("Your buffer is at %p\n", my_buf);
52+
gets(my_buf);
53+
return 0;
54+
}
55+
56+
#endif
57+
58+
void does_nothing()
59+
{
60+
puts("/bin/sh");
61+
execve(NULL, NULL, NULL);
62+
}
63+
64+
__attribute__((constructor)) void ignore_me()
65+
{
66+
setbuf(stdout, NULL);
67+
setbuf(stdin, NULL);
68+
}
69+
70+
void main()
71+
{
72+
73+
puts("pwn_me:");
74+
pwn_me();
75+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Zeratool's tests/format_string.c
2+
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
7+
/*
8+
* compiled with:
9+
* gcc -O0 -fno-stack-protector -o hard -z execstack -z norelro hard.c
10+
* run with:
11+
* socat TCP4-LISTEN:7803,tcpwrap=script,reuseaddr,fork EXEC:./hard
12+
*/
13+
14+
#define SECRET "---BEGIN PRIVATE KEY---"
15+
16+
#ifdef MEDIUM
17+
/*
18+
* Test for point to win
19+
*/
20+
void secret_function(void)
21+
{
22+
puts(SECRET);
23+
}
24+
#endif
25+
26+
int main(int argc, char *argv[])
27+
{
28+
int i = 0;
29+
30+
#ifdef EASY
31+
char buf[1024];
32+
fgets(buf, 1024, stdin);
33+
/*
34+
* Test for stack reading
35+
*/
36+
char key[64] = {};
37+
strcpy(key, SECRET);
38+
#endif
39+
40+
#ifdef MEDIUM
41+
char buf[256];
42+
read(0, buf, 256);
43+
#endif
44+
45+
#ifdef HARD
46+
char buf[1024];
47+
/* read user input securely */
48+
fgets(buf, 1024, stdin);
49+
/*
50+
* Test for point to shellcode AND
51+
* satisfy constraints
52+
*/
53+
/* convert string to lowercase */
54+
for (i = 0; i < strlen(buf); i++)
55+
if (buf[i] >= 'A' && buf[i] <= 'Z')
56+
buf[i] = buf[i] ^ 0x20;
57+
#endif
58+
59+
/* print out our nice and new lowercase string */
60+
printf(buf);
61+
62+
exit(EXIT_SUCCESS);
63+
return EXIT_FAILURE;
64+
}

zeratool_lib/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
import claripy
99
import timeout_decorator
1010
from angr import sim_options as so
11+
from overflowExploiter import findShellcode, getRegValues
1112
from pwn import *
12-
13+
from simgr_helper import getShellcode
1314
from zeratool import printf_model
1415

15-
from .overflowExploiter import findShellcode, getRegValues
16-
from .simgr_helper import getShellcode
17-
1816
log = logging.getLogger(__name__)
1917

2018

@@ -93,7 +91,9 @@ def exploitFormat(binary_name, properties, leak_format):
9391
log.info("[+] Binary does not have NX")
9492
log.info("[+] Overwriting GOT entry to point to shellcode")
9593

96-
return rediscoverAndExploit(binary_name, properties, stack_position, leak_format)
94+
return rediscoverAndExploit(
95+
binary_name, properties, stack_position, leak_format
96+
)
9797

9898

9999
"""
@@ -107,7 +107,7 @@ def rediscoverAndExploit(binary_name, properties, stack_position, leak_format):
107107

108108
properties["shellcode"] = getShellcode(properties)
109109
properties["stack_position"] = stack_position
110-
properties["leak_format" = leak_format]
110+
properties["leak_format"] = leak_format
111111
inputType = properties["input_type"]
112112

113113
extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY, so.TRACK_CONSTRAINTS}
@@ -176,12 +176,12 @@ def exploreBinary(simgr):
176176
stdin_str = str(end_state.posix.dumps(0))
177177
log.info("[+] Triggerable with STDIN : {}".format(stdin_str))
178178

179-
return stdin_str, end_state["fmt_outcome"]
179+
return stdin_str, end_state.globals["fmt_outcome"]
180180
elif inputType == "ARG" and end_state is not None:
181181
arg_str = str(end_state.solver.eval(arg, cast_to=str))
182182
run_environ["input"] = arg_str
183183

184-
return arg_str, end_state["fmt_outcome"]
184+
return arg_str, end_state.globals["fmt_outcome"]
185185

186186

187187
def get_num_constraints(chop_byte, state):
@@ -317,6 +317,8 @@ def checkExploitable(self, fmt):
317317
if greatest_count > 0:
318318
shellcode = properties["shellcode"]
319319
stack_pos = properties["stack_position"]
320+
state_copy = None
321+
results_n = None
320322

321323
for got_name, got_addr in list(properties["protections"]["got"].items()):
322324
# for got_name,got_addr in [(x,y) for (x,y) in properties['protections']['got'].items() if x in " exit"]: #debug for hard_format
@@ -376,8 +378,8 @@ def checkExploitable(self, fmt):
376378
exploit_results = {}
377379
if results["success"] == True:
378380
exploit_results["success"] = results["success"]
379-
exploit_results["input"] = format_input
380-
self.state["fmt_outcome"] = results["fmt_outcome"]
381+
exploit_results["input"] = user_input
382+
self.state.globals["fmt_outcome"] = results["fmt_outcome"]
381383
else: # Maybe angr still messed up the pointer
382384
log.info("[-] Payload launch failed. Fixing angr stack pointer")
383385

@@ -464,10 +466,7 @@ def checkExploitable(self, fmt):
464466

465467
leak_format = properties["leak_format"]
466468
results_n = sendExploit(
467-
binary_name,
468-
properties,
469-
user_input,
470-
leak_format
469+
binary_name, properties, user_input, leak_format
471470
)
472471
if results_n["success"]:
473472
log.info(
@@ -476,14 +475,19 @@ def checkExploitable(self, fmt):
476475
self.state.globals["type"] = "Format"
477476
self.state.globals["position"] = position
478477
self.state.globals["length"] = greatest_count
479-
self.state["fmt_outcome"] = results_n["fmt_outcome"]
478+
self.state.globals["fmt_outcome"] = results_n["fmt_outcome"]
480479
return True
481480

482481
# exploit_results['success'] = results_n['success']
483482
# exploit_results['input'] = format_input
484483

485484
# Verify solution
486-
if state_copy.globals["inputType"] == "STDIN" and results_n["success"]:
485+
if (
486+
state_copy
487+
and state_copy.globals["inputType"] == "STDIN"
488+
and results_n
489+
and results_n["success"]
490+
):
487491
stdin_str = str(state_copy.posix.dumps(0))
488492
if format_payload in stdin_str or results["success"]:
489493
var_value = self.state.memory.load(var_loc, var_len)
@@ -499,10 +503,10 @@ def checkExploitable(self, fmt):
499503
self.state.globals["type"] = "Format"
500504
self.state.globals["position"] = position
501505
self.state.globals["length"] = greatest_count
502-
self.state["fmt_outcome"] = results_n["fmt_outcome"]
506+
self.state.globals["fmt_outcome"] = results_n["fmt_outcome"]
503507

504508
return True
505-
if state_copy.globals["inputType"] == "ARG":
509+
if state_copy and state_copy.globals["inputType"] == "ARG":
506510
arg = state.globals["arg"]
507511
arg_str = str(state_copy.solver.eval(arg, cast_to=str))
508512
if format_payload in arg_str:
@@ -519,7 +523,7 @@ def checkExploitable(self, fmt):
519523
self.state.globals["type"] = "Format"
520524
self.state.globals["position"] = position
521525
self.state.globals["length"] = greatest_count
522-
self.state["fmt_outcome"] = results_n["fmt_outcome"]
526+
self.state.globals["fmt_outcome"] = results_n["fmt_outcome"]
523527
return True
524528
state_copy = backup_state.copy()
525529

@@ -651,9 +655,7 @@ def sendExploit(binary_name, properties, input_string, leak_format):
651655
# Flag not in stdout, we have a shell
652656
else:
653657

654-
if (
655-
properties["input_type"] == "STDIN"
656-
):
658+
if properties["input_type"] == "STDIN":
657659
proc = process(binary_name)
658660
proc.sendline(input_string)
659661
else:

0 commit comments

Comments
 (0)