diff --git a/.gitignore b/.gitignore index 0eb245fd8..5faab21d1 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,4 @@ coverage.xml docs/_build/ target/ site -tests/bins tests/pylint*.html diff --git a/.travis.yml b/.travis.yml index 6c222c97b..0d2f3cc56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,20 +11,24 @@ branches: addons: apt: packages: - - python + - python3 - gcc + - g++ + - pkg-config - gdb - - python-pip + - python3-pip - pylint + - git + - cmake + - libglib2.0-dev before_script: + - pip3 install --user --upgrade ropper retdec-python keystone-engine capstone unicorn - echo "source `pwd`/gef.py" > ~/.gdbinit script: - gdb -q -ex 'gef help' -ex 'gef config' -ex 'quit' /bin/ls - -after_script: - - python ./tests/test-runner.py + - python3 ./tests/test-runner.py notifications: email: diff --git a/gef.py b/gef.py index c451c689c..5fd4830b7 100644 --- a/gef.py +++ b/gef.py @@ -53,7 +53,6 @@ # # -# x86 aggregate selectors from __future__ import print_function, division, absolute_import @@ -616,7 +615,8 @@ def __int__(self): return self.__addr def dereference_as_long(self, addr): - return long(dereference(addr).address) + derefed = dereference(addr) + return long(derefed.address) if derefed is not None else 0 def fastbin(self, i): addr = self.dereference_as_long(self.fastbinsY[i]) @@ -2832,7 +2832,11 @@ def gef_get_auxiliary_values(): for line in gdb.execute("info auxv", to_string=True).splitlines(): tmp = line.split() _type = tmp[1] - res[_type] = int(tmp[-2], base=0) if _type in ("AT_PLATFORM", "AT_EXECFN") else int(tmp[-1], base=0) + if _type in ("AT_PLATFORM", "AT_EXECFN"): + idx = line[:-1].rfind('"') - 1 + tmp = line[:idx].split() + + res[_type] = int(tmp[-1], base=0) return res @@ -2848,10 +2852,12 @@ def gef_read_canary(): canary &= ~0xff return canary, canary_location + def gef_get_pie_breakpoint(num): global __pie_breakpoints__ return __pie_breakpoints__[num] + @lru_cache() def gef_getpagesize(): """Get the page size from auxiliary values.""" @@ -4693,8 +4699,17 @@ def do_invoke(self, argv): else: perm = Permission.READ | Permission.WRITE | Permission.EXECUTE - loc = long(gdb.parse_and_eval(argv[0])) + loc = safe_parse_and_eval(argv[0]) + if loc is None: + err("Invalid address") + return + + loc = long(loc) sect = process_lookup_address(loc) + if sect is None: + err("Unmapped address") + return + size = sect.page_end - sect.page_start original_pc = current_arch.pc @@ -5941,6 +5956,7 @@ def pre_load(self): return + @only_if_gdb_running def do_invoke(self, argv): ropper = sys.modules["ropper"] if "--file" not in argv: @@ -7119,7 +7135,17 @@ def do_invoke(self, argv): elif len(argv) == 2 and argv[0] == "$sp" and argv[1].isdigit(): nb = int(argv[1]) - start_address = align_address(long(gdb.parse_and_eval(argv[0]))) + addr = safe_parse_and_eval(argv[0]) + if addr is None: + err("Invalid address") + return + + addr = long(addr) + if process_lookup_address(addr) is None: + err("Unmapped address") + return + + start_address = align_address(addr) largest_addresss_to_be_shown = start_address + (current_arch.ptrsize * nb) stackoffs = range(0, nb) diff --git a/tests/binaries/Makefile b/tests/binaries/Makefile new file mode 100644 index 000000000..34ecaf62a --- /dev/null +++ b/tests/binaries/Makefile @@ -0,0 +1,42 @@ +CC = gcc +DEBUG = 1 +CFLAGS += -Wall +SOURCES = $(wildcard *.c) +COMPILED = $(SOURCES:.c=.o) +LINKED = $(SOURCES:.c=.out) +LDFLAGS = +EXTRA_FLAGS = + +ifeq ($(TARGET), x86) +CFLAGS += -m32 +endif + +ifeq ($(DEBUG), 1) +CFLAGS += -DDEBUG=1 -ggdb -O0 +else +CFLAGS += -O1 +endif + + +.PHONY : all clean + +all: $(LINKED) + + +%.out : %.c + @echo "[+] Linking C file '$@'" + @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -o $@ $? $(LDFLAGS) + +clean : + @echo "[+] Cleaning stuff" + @rm -f $(COMPILED) $(LINKED) + +checksec-no-canary.out: EXTRA_FLAGS := -fno-stack-protector + +checksec-no-pie.out: EXTRA_FLAGS := -fno-pie + +checksec-no-nx.out: EXTRA_FLAGS := -z execstack + +pattern.out: EXTRA_FLAGS := -D_FORTIFY_SOURCE=0 -fno-stack-protector + +canary.out: EXTRA_FLAGS := -fstack-protector-all diff --git a/tests/binaries/canary.c b/tests/binaries/canary.c new file mode 100644 index 000000000..1c0f809e3 --- /dev/null +++ b/tests/binaries/canary.c @@ -0,0 +1,29 @@ +/** + * canary.c + * -*- mode: c -*- + * -*- coding: utf-8 -*- + */ + +#include +#include +#include +#include +#include + + +void greetz(char* buf) +{ + char name[8] = {0,}; + strcpy(name, buf); + printf("Hello %s\n", name); +} + + +int main(int argc, char** argv, char** envp) +{ + if(argc < 2) + return EXIT_FAILURE; + + greetz(argv[1]); + return EXIT_SUCCESS; +} diff --git a/tests/binaries/checksec-no-canary.c b/tests/binaries/checksec-no-canary.c new file mode 100644 index 000000000..99d54c4b8 --- /dev/null +++ b/tests/binaries/checksec-no-canary.c @@ -0,0 +1,20 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * checksec-no-canary.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + return EXIT_SUCCESS; +} diff --git a/tests/binaries/checksec-no-nx.c b/tests/binaries/checksec-no-nx.c new file mode 100644 index 000000000..99d54c4b8 --- /dev/null +++ b/tests/binaries/checksec-no-nx.c @@ -0,0 +1,20 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * checksec-no-canary.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + return EXIT_SUCCESS; +} diff --git a/tests/binaries/checksec-no-pie.c b/tests/binaries/checksec-no-pie.c new file mode 100644 index 000000000..426d69305 --- /dev/null +++ b/tests/binaries/checksec-no-pie.c @@ -0,0 +1,20 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * checksec-no-pie.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + return EXIT_SUCCESS; +} diff --git a/tests/binaries/format-string-helper.c b/tests/binaries/format-string-helper.c new file mode 100644 index 000000000..8bcae1a26 --- /dev/null +++ b/tests/binaries/format-string-helper.c @@ -0,0 +1,31 @@ +/** + * bof.c + * -*- mode: c -*- + * -*- coding: utf-8 -*- + */ + +#include +#include +#include +#include +#include + + +void greetz(char* buf) +{ + char name[256] = {0,}; + strcpy(name, buf); + printf("Hello"); + printf(name); + printf("\n"); +} + + +int main(int argc, char** argv, char** envp) +{ + if(argc < 2) + return EXIT_FAILURE; + + greetz(argv[1]); + return EXIT_SUCCESS; +} diff --git a/tests/binaries/heap-fastbins.c b/tests/binaries/heap-fastbins.c new file mode 100644 index 000000000..7ccee9235 --- /dev/null +++ b/tests/binaries/heap-fastbins.c @@ -0,0 +1,27 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * heap.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + void* p1 = malloc(0x10); + void* p2 = malloc(0x10); + void* p3 = malloc(0x10); + free(p2); + __asm__ volatile("int3;" : : : ); + (void)p1; + (void)p3; + return EXIT_SUCCESS; +} diff --git a/tests/binaries/heap.c b/tests/binaries/heap.c new file mode 100644 index 000000000..3dd53bd60 --- /dev/null +++ b/tests/binaries/heap.c @@ -0,0 +1,23 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * heap.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + void* p1 = malloc(0x10); + __asm__ volatile("int3;" : : : ); + (void)p1; + return EXIT_SUCCESS; +} diff --git a/tests/binaries/pattern.c b/tests/binaries/pattern.c new file mode 100644 index 000000000..43f1a07be --- /dev/null +++ b/tests/binaries/pattern.c @@ -0,0 +1,29 @@ +/** + * pattern.c + * -*- mode: c -*- + * -*- coding: utf-8 -*- + */ + +#include +#include +#include +#include +#include + + +void greetz(char* buf) +{ + char name[8] = {0,}; + strcpy(name, buf); + printf("Hello %s\n", name); +} + + +int main(int argc, char** argv, char** envp) +{ + if(argc < 2) + return EXIT_FAILURE; + + greetz(argv[1]); + return EXIT_SUCCESS; +} diff --git a/tests/binaries/retdec.c b/tests/binaries/retdec.c new file mode 100644 index 000000000..b23db83da --- /dev/null +++ b/tests/binaries/retdec.c @@ -0,0 +1,21 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * retdec.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + printf("Hello World!\n"); + return EXIT_SUCCESS; +} diff --git a/tests/binaries/set-permission.c b/tests/binaries/set-permission.c new file mode 100644 index 000000000..c86956547 --- /dev/null +++ b/tests/binaries/set-permission.c @@ -0,0 +1,32 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * set-permission.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv, char** envp) +{ + void *p = mmap((void *)0x1337000, + getpagesize(), + PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, + -1, + 0); + + if( p == (void *)-1) + return EXIT_FAILURE; + + __asm__ volatile("int3 ;" : : :); + + return EXIT_SUCCESS; +} diff --git a/tests/helpers.py b/tests/helpers.py index 7f4660bf1..00aa1659b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,9 +1,14 @@ import subprocess -def gdb_run_command(cmd, before=[], after=[]): +def gdb_run_command(cmd, before=[], after=[], target="/bin/ls"): """Execute a command inside GDB. `before` and `after` are lists of commands to be executed before (resp. after) the command to test.""" - command = ["gdb", "-q", "-nx", "-ex", "source /tmp/gef.py", "-ex", "gef config gef.debug True"] + command = [ + "gdb", "-q", "-nx", + "-ex", "source /tmp/gef.py", + "-ex", "gef config gef.disable_color True", + "-ex", "gef config gef.debug True" + ] if len(before): for _ in before: command+= ["-ex", _] @@ -13,33 +18,41 @@ def gdb_run_command(cmd, before=[], after=[]): if len(after): for _ in after: command+= ["-ex", _] - command+= ["-ex", "quit", "--", "/bin/ls"] + command+= ["-ex", "quit", "--", target] lines = subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() - return "\n".join(lines) + return b"\n".join(lines) -def gdb_run_command_last_line(cmd, before=[], after=[]): +def gdb_run_silent_command(cmd, before=[], after=[], target="/bin/ls"): + """Disable the output and run entirely the `target` binary.""" + before += ["gef config context.clear_screen False", + "gef config context.layout ''", + "run"] + return gdb_run_command(cmd, before, after, target) + + +def gdb_run_command_last_line(cmd, before=[], after=[], target="/bin/ls"): """Execute a command in GDB, and return only the last line of its output.""" - return gdb_run_command(cmd, before, after).splitlines()[-1] + return gdb_run_command(cmd, before, after, target).splitlines()[-1] -def gdb_start_silent_command(cmd, before=[], after=[]): +def gdb_start_silent_command(cmd, before=[], after=[], target="/bin/ls"): """Execute a command in GDB by starting an execution context. This command disables the `context` and set a tbreak at the most convenient entry point.""" before += ["gef config context.clear_screen False", "gef config context.layout ''", "entry-break"] - return gdb_run_command(cmd, before, after) + return gdb_run_command(cmd, before, after, target) -def gdb_start_silent_command_last_line(cmd, before=[], after=[]): +def gdb_start_silent_command_last_line(cmd, before=[], after=[], target="/bin/ls"): """Execute `gdb_start_silent_command()` and return only the last line of its output.""" before += ["gef config context.clear_screen False", "gef config context.layout ''", "entry-break"] - return gdb_start_silent_command(cmd, before, after).splitlines()[-1] + return gdb_start_silent_command(cmd, before, after, target).splitlines()[-1] -def gdb_test_python_method(meth, before="", after=""): +def gdb_test_python_method(meth, before="", after="", target="/bin/ls"): cmd = "pi {}print({});{}".format(before+";" if len(before)>0 else "", meth, after) - return gdb_start_silent_command(cmd) + return gdb_start_silent_command(cmd, target=target) diff --git a/tests/test-runner.py b/tests/test-runner.py index 8d61c84ce..fe69e2705 100755 --- a/tests/test-runner.py +++ b/tests/test-runner.py @@ -1,34 +1,173 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Run tests by spawning a gdb instance for every command. # from __future__ import print_function -import unittest, sys, shutil, os +import difflib +import os +import shutil +import subprocess +import sys +import unittest -from helpers import * +from helpers import gdb_run_command, \ + gdb_run_silent_command, \ + gdb_run_command_last_line, \ + gdb_start_silent_command, \ + gdb_start_silent_command_last_line, \ + gdb_test_python_method -class TestGefCommands(unittest.TestCase): - """Global class for command testing""" - ### unittest helpers +class GefUnitTestGeneric(unittest.TestCase): + """Generic class for command testing, that defines all helpers""" def assertNoException(self, buf): - return "Python Exception <" not in buf or "'gdb.error'" in buf + return b"Python Exception <" not in buf \ + or b"'gdb.error'" in buf \ + or b"failed to execute properly, reason:" in buf def assertFailIfInactiveSession(self, buf): - return "No debugging session active" in buf + return b"No debugging session active" in buf - ### testing GEF commands +class TestGefCommands(GefUnitTestGeneric): + """Tests GEF GDB commands.""" + + def test_command_canary(self): + self.assertFailIfInactiveSession(gdb_run_command("canary")) + res = gdb_start_silent_command("canary", target="tests/binaries/canary.out") + self.assertNoException(res) + self.assertTrue(b"Found AT_RANDOM at" in res) + self.assertTrue(b"The canary of process " in res) + return + + + def test_command_capstone_disassemble(self): + self.assertFailIfInactiveSession(gdb_run_command("capstone-disassemble")) + res = gdb_start_silent_command("capstone-disassemble") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 1) + return + + + def test_command_checksec(self): + cmd = "checksec" + res = gdb_run_command(cmd) + self.assertNoException(res) + + target = "tests/binaries/checksec-no-canary.out" + res = gdb_run_command(cmd, target=target) + self.assertTrue("Canary : No") + + target = "tests/binaries/checksec-no-nx.out" + res = gdb_run_command(cmd, target=target) + self.assertTrue("NX : No") + + target = "tests/binaries/checksec-no-pie.out" + res = gdb_run_command(cmd, target=target) + self.assertTrue("PIE : No") + return + + + def test_command_dereference(self): + self.assertFailIfInactiveSession(gdb_run_command("dereference")) + + res = gdb_start_silent_command("dereference $sp") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 2) + self.assertTrue(b"$rsp" in res) + + res = gdb_start_silent_command("dereference 0") + self.assertNoException(res) + self.assertTrue(b"Unmapped address" in res) + return + + + def test_command_edit_flags(self): + self.assertFailIfInactiveSession(gdb_run_command("edit-flags")) + # force enable flag + res = gdb_start_silent_command_last_line("edit-flags +carry") + self.assertNoException(res) + self.assertTrue(b"CARRY " in res) + # force disable flag + res = gdb_start_silent_command_last_line("edit-flags -carry") + self.assertNoException(res) + self.assertTrue(b"carry " in res) + # toggle flag + before = gdb_start_silent_command_last_line("edit-flags") + self.assertNoException(before) + after = gdb_start_silent_command_last_line("edit-flags ~carry") + self.assertNoException(after) + s = difflib.SequenceMatcher(None, before, after) + self.assertTrue(s.ratio() > 0.90) + return + + + def test_command_elf_info(self): + res = gdb_run_command("elf-info") + self.assertNoException(res) + self.assertTrue(b"7f 45 4c 46" in res) + return + def test_command_entry_break(self): res = gdb_run_command("entry-break") self.assertNoException(res) return + + def test_command_format_string_helper(self): + cmd = "format-string-helper" + target = "tests/binaries/format-string-helper.out" + res = gdb_run_command(cmd, + after=["set args testtest", + "run",], + target=target) + self.assertNoException(res) + self.assertTrue(b"Possible insecure format string:" in res) + return + + + def test_command_heap_arenas(self): + cmd = "heap arenas" + target = "tests/binaries/heap.out" + self.assertFailIfInactiveSession(gdb_run_command(cmd, target=target)) + res = gdb_start_silent_command(cmd, target=target) + self.assertNoException(res) + self.assertTrue(b"Arena (base=" in res) + return + + def test_command_heap_chunk(self): + cmd = "heap chunk p1" + target = "tests/binaries/heap.out" + self.assertFailIfInactiveSession(gdb_run_command(cmd, target=target)) + res = gdb_run_silent_command(cmd, target=target) + self.assertNoException(res) + self.assertTrue(b"NON_MAIN_ARENA flag: " in res) + return + + def test_command_heap_bins_fast(self): + cmd = "heap bins fast" + target = "tests/binaries/heap-fastbins.out" + self.assertFailIfInactiveSession(gdb_run_command(cmd, target=target)) + res = gdb_run_silent_command(cmd, target=target) + self.assertNoException(res) + self.assertTrue(b"Fastbins[idx=0, size=0x10]" in res) + self.assertTrue(b"Chunk(addr=" in res) + return + + + def test_command_heap_analysis(self): + cmd = "heap-analysis-helper" + self.assertFailIfInactiveSession(gdb_run_command(cmd)) + res = gdb_start_silent_command(cmd) + self.assertNoException(res) + return + + def test_command_hexdump(self): self.assertFailIfInactiveSession(gdb_run_command("hexdump $pc")) res = gdb_start_silent_command("hexdump qword $pc") @@ -41,104 +180,201 @@ def test_command_hexdump(self): self.assertNoException(res) return - def test_command_vmmap(self): - self.assertFailIfInactiveSession(gdb_run_command("vmmap")) - res = gdb_start_silent_command("vmmap") + + def test_command_keystone_assemble(self): + valid_cmds = [ + "assemble nop; xor eax, eax; int 0x80", + "assemble -a arm -m arm add r0, r1, r2", + "assemble -a mips -m mips32 add $v0, 1", + "assemble -a sparc -m sparc32 set 0, %o0", + "assemble -a arm64 -m little_endian add x29, sp, 0; mov w0, 0; ret" + ] + for cmd in valid_cmds: + res = gdb_start_silent_command(cmd) + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 1) + return + + + def test_command_patch(self): + self.assertFailIfInactiveSession(gdb_run_command("patch")) + return + + def test_command_patch_byte(self): + before = gdb_start_silent_command_last_line("x/8bx $pc") + after = gdb_start_silent_command_last_line("patch byte $pc 0x42", after=["x/8bx $pc",]) + self.assertNoException(after) + r = difflib.SequenceMatcher(None, before, after).ratio() + self.assertTrue( 0.90 < r < 1.0 ) + return + + def test_command_patch_word(self): + before = gdb_start_silent_command_last_line("x/8bx $pc") + after = gdb_start_silent_command_last_line("patch word $pc 0x4242", after=["x/8bx $pc",]) + self.assertNoException(after) + r = difflib.SequenceMatcher(None, before, after).ratio() + self.assertTrue( 0.90 < r < 1.0 ) + return + + def test_command_patch_dword(self): + before = gdb_start_silent_command_last_line("x/8bx $pc") + after = gdb_start_silent_command_last_line("patch dword $pc 0x42424242", after=["x/8bx $pc",]) + self.assertNoException(after) + r = difflib.SequenceMatcher(None, before, after).ratio() + self.assertTrue( 0.80 < r < 0.90 ) + return + + def test_command_patch_qword(self): + before = gdb_start_silent_command_last_line("x/8bx $pc") + after = gdb_start_silent_command_last_line("patch qword $pc 0x4242424242424242", after=["x/8bx $pc",]) + self.assertNoException(after) + r = difflib.SequenceMatcher(None, before, after).ratio() + self.assertTrue( 0.50 < r < 0.70 ) + return + + def test_command_patch_string(self): + res = gdb_start_silent_command_last_line("patch string $sp \"Gef!Gef!Gef!Gef!\"", after=["grep Gef!Gef!Gef!Gef!",]) self.assertNoException(res) - self.assertTrue(res.splitlines() > 1) + self.assertTrue(b"\"Gef!Gef!Gef!Gef!\"" in res) return - def test_command_xinfo(self): - self.assertFailIfInactiveSession(gdb_run_command("xinfo $sp")) - res = gdb_start_silent_command("xinfo") - self.assertTrue("At least one valid address must be specified" in res) - res = gdb_start_silent_command("xinfo $sp") + + def test_command_pattern(self): + cmd = "pattern create 32" + target = "tests/binaries/pattern.out" + res = gdb_run_command(cmd, target=target) self.assertNoException(res) - self.assertTrue(res.splitlines() >= 7) + self.assertTrue(b"aaaaaaaabaaaaaaacaaaaaaadaaaaaaa" in res) + + cmd = "pattern search $rbp" + target = "tests/binaries/pattern.out" + res = gdb_run_command(cmd, before=["set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run"], target=target) + self.assertNoException(res) + self.assertTrue(b"Found at offset" in res) return - def test_command_process_search(self): - self.assertFailIfInactiveSession(gdb_run_command("grep /bin/sh")) - res = gdb_start_silent_command("grep /bin/sh") + + def test_command_process_status(self): + self.assertFailIfInactiveSession(gdb_run_command("process-status")) + res = gdb_start_silent_command("process-status") self.assertNoException(res) - self.assertTrue("0x" in res) + self.assertTrue(b"Process Information" in res \ + and b"No child process" in res \ + and b"No open connections" in res) return + def test_command_registers(self): self.assertFailIfInactiveSession(gdb_run_command("registers")) res = gdb_start_silent_command("registers") self.assertNoException(res) - self.assertTrue("$rax" in res and "$eflags" in res) + self.assertTrue(b"$rax" in res) + self.assertTrue(b"$eflags" in res) return - def test_command_process_status(self): - self.assertFailIfInactiveSession(gdb_run_command("process-status")) - res = gdb_start_silent_command("process-status") + + def test_command_reset_cache(self): + res = gdb_start_silent_command("reset-cache") self.assertNoException(res) - self.assertTrue("Process Information" in res \ - and "No child process" in res \ - and "No open connections" in res) return - def test_command_xor_memory(self): - cmd = "xor-memory display 0x555555774000 0x10 0x41" + + def test_command_retdec(self): + cmd = "retdec -s main" + target = "tests/binaries/retdec.out" + res = gdb_start_silent_command(cmd, target=target) + if b"No RetDec API key provided" in res: + api_key = os.getenv("GEF_RETDEC_API_KEY") + if api_key is None: + return + before = ["gef config retdec.key {}".format(api_key),] + res = gdb_start_silent_command(cmd, before=before, target=target) + + self.assertNoException(res) + self.assertTrue(b"Saved as" in res) + return + + + def test_command_ropper(self): + cmd = "ropper" self.assertFailIfInactiveSession(gdb_run_command(cmd)) - res = gdb_start_silent_command(cmd) + cmd = "ropper --search \"pop %; pop %; ret\"" + res = gdb_run_silent_command(cmd) self.assertNoException(res) - self.assertTrue("Original block" in res and "XOR-ed block" in res) + self.assertFalse(b": error:" in res) + self.assertTrue(len(res.splitlines()) > 2) + return - cmd = "xor-memory patch 0x555555774000 0x10 0x41" - res = gdb_start_silent_command(cmd) + + def test_command_search_pattern(self): + self.assertFailIfInactiveSession(gdb_run_command("grep /bin/sh")) + res = gdb_start_silent_command("grep /bin/sh") self.assertNoException(res) - self.assertTrue("Patching XOR-ing 0x555555774000-0x555555774010 with '0x41'") + self.assertTrue(b"0x" in res) return - def test_command_elf_info(self): - res = gdb_run_command("elf-info") + + def test_command_set_permission(self): + self.assertFailIfInactiveSession(gdb_run_command("set-permission")) + target = "tests/binaries/set-permission.out" + + res = gdb_run_silent_command("set-permission 0x1337000", after=["vmmap",], target=target) self.assertNoException(res) - self.assertTrue("7f 45 4c 46" in res) + line = [ l for l in res.splitlines() if b"0x0000000001337000" in l ][0] + line = line.split() + self.assertEqual(line[0], b"0x0000000001337000") + self.assertEqual(line[1], b"0x0000000001338000") + self.assertEqual(line[2], b"0x0000000000000000") + self.assertEqual(line[3], b"rwx") + + res = gdb_run_silent_command("set-permission 0x1338000", target=target) + self.assertNoException(res) + self.assertTrue(b"Unmapped address") return - def test_command_checksec(self): - res = gdb_run_command("checksec") + + def test_command_shellcode(self): + res = gdb_start_silent_command("shellcode") self.assertNoException(res) - # todo: add more granular tests (with specific binaries (no canary, no pic, etc.)) + self.assertTrue(b"Missing sub-command " in res) return - def test_command_stub(self): - cmd = "stub printf" - self.assertFailIfInactiveSession(gdb_run_command(cmd)) + def test_command_shellcode_search(self): + cmd = "shellcode search execve /bin/sh" res = gdb_start_silent_command(cmd) self.assertNoException(res) + self.assertTrue(b"setuid(0) + execve(/bin/sh) 49 bytes" in res) return - def test_command_heap_analysis(self): - cmd = "heap-analysis-helper" - self.assertFailIfInactiveSession(gdb_run_command(cmd)) - res = gdb_start_silent_command(cmd) + def test_command_shellcode_get(self): + res = gdb_start_silent_command("shellcode get 77") self.assertNoException(res) + self.assertTrue(b"Shellcode written to " in res) return - def test_command_pattern_create(self): - res = gdb_run_command("pattern create 16") + + def test_command_stub(self): + cmd = "stub printf" + self.assertFailIfInactiveSession(gdb_run_command(cmd)) + res = gdb_start_silent_command(cmd) self.assertNoException(res) - self.assertTrue("aaaaaaaabaaaaaaa" in res) return + def test_command_theme(self): res = gdb_run_command("theme") self.assertNoException(res) possible_themes = [ - "context_title_line" - "dereference_base_address" - "context_title_message" - "disable_color" - "dereference_code" - "dereference_string" - "default_title_message", - "default_title_line" - "dereference_register_value", - "xinfo_title_message", + "context_title_line" + "dereference_base_address" + "context_title_message" + "disable_color" + "dereference_code" + "dereference_string" + "default_title_message", + "default_title_line" + "dereference_register_value", + "xinfo_title_message", ] for t in possible_themes: # testing command viewing @@ -152,50 +388,150 @@ def test_command_theme(self): return - def test_command_capstone_disassemble(self): - self.assertFailIfInactiveSession(gdb_run_command("capstone-disassemble")) - res = gdb_start_silent_command("capstone-disassemble") + def test_command_trace_run(self): + cmd = "trace-run" + res = gdb_run_command(cmd) + self.assertFailIfInactiveSession(res) + + cmd = "trace-run $pc+1" + res = gdb_start_silent_command(cmd, + before=["gef config trace-run.tracefile_prefix /tmp/gef-trace-"]) self.assertNoException(res) - self.assertTrue(res.splitlines() > 1) + self.assertTrue(b"Tracing from" in res) return - def test_command_keystone_assemble(self): - valid_cmds = [ - "assemble nop; nop; nop", - "assemble -a arm -m arm add r0, r1, r2", - "assemble -a mips -m mips32 add $v0, 1", - "asm -a sparc -m sparc32 set 0, %o0", - ] - for cmd in valid_cmds: - res = gdb_start_silent_command(cmd) - self.assertNoException(res) - self.assertTrue(res.splitlines() > 1) + def test_command_unicorn_emulate(self): + cmd = "emu -n 1" + res = gdb_run_command(cmd) + self.assertFailIfInactiveSession(res) + + res = gdb_start_silent_command(cmd) + self.assertNoException(res) + self.assertTrue(b"Final registers" in res) return - ### testing GEF methods - def test_which(self): - res = gdb_test_python_method("which('gdb')") - self.assertTrue(res.splitlines()[-1].startswith("/")) - res = gdb_test_python_method("which('__IDontExist__')") - self.assertTrue("Missing file `__IDontExist__`" in res) + def test_command_vmmap(self): + self.assertFailIfInactiveSession(gdb_run_command("vmmap")) + res = gdb_start_silent_command("vmmap") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 1) + + res = gdb_start_silent_command("vmmap stack") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 1) return - def test_get_memory_alignment(self): + + def test_command_xfiles(self): + self.assertFailIfInactiveSession(gdb_run_command("xfiles")) + res = gdb_start_silent_command("xfiles") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) >= 3) + return + + + def test_command_xinfo(self): + self.assertFailIfInactiveSession(gdb_run_command("xinfo $sp")) + res = gdb_start_silent_command("xinfo") + self.assertTrue(b"At least one valid address must be specified" in res) + + res = gdb_start_silent_command("xinfo $sp") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) >= 7) + return + + + def test_command_xor_memory(self): + cmd = "xor-memory display $sp 0x10 0x41" + self.assertFailIfInactiveSession(gdb_run_command(cmd)) + res = gdb_start_silent_command(cmd) + self.assertNoException(res) + self.assertTrue(b"Original block" in res) + self.assertTrue(b"XOR-ed block" in res) + + cmd = "xor-memory patch $sp 0x10 0x41" + res = gdb_start_silent_command(cmd) + self.assertNoException(res) + self.assertTrue(b"Patching XOR-ing ") + return + + +class TestGefFunctions(GefUnitTestGeneric): + """Tests GEF internal functions.""" + + + def test_function_get_memory_alignment(self): res = gdb_test_python_method("get_memory_alignment(in_bits=False)") - self.assertTrue(res.splitlines()[-1] in ("4", "8")) + self.assertTrue(res.splitlines()[-1] in (b"4", b"8")) return - def test_set_arch(self): + + def test_function_set_arch(self): res = gdb_test_python_method("current_arch.arch, current_arch.mode", before="set_arch()") res = (res.splitlines()[-1]) - self.assertTrue('X86' in res) + self.assertTrue(b"X86" in res) return -if __name__ == "__main__": + def test_function_which(self): + res = gdb_test_python_method("which('gdb')") + lines = res.splitlines() + self.assertTrue(b"/gdb" in lines[-1]) + res = gdb_test_python_method("which('__IDontExist__')") + self.assertTrue(b"Missing file `__IDontExist__`" in res) + return + + + def test_function_get_filepath(self): + res = gdb_test_python_method("get_filepath()", target="/bin/ls") + self.assertNoException(res) + subprocess.call(["cp", "/bin/ls", "/tmp/foo bar"]) + res = gdb_test_python_method("get_filepath()", target="/tmp/foo bar") + self.assertNoException(res) + subprocess.call(["rm", "/tmp/foo bar"]) + return + + + def test_function_get_pid(self): + res = gdb_test_python_method("get_pid()", target="/bin/ls") + self.assertNoException(res) + self.assertTrue(int(res.splitlines()[-1])) + return + + + +def setup(): + subprocess.call(["make","-C", "tests/binaries", "all"]) shutil.copy2("./gef.py", "/tmp/gef.py") - suite = unittest.TestLoader().loadTestsFromTestCase(TestGefCommands) - unittest.TextTestRunner(verbosity=3).run(suite) + return + + +def cleanup(): os.unlink("/tmp/gef.py") + subprocess.call(["make","-C", "tests/binaries", "clean"]) + return + + +def run_tests(): + test_instances = [ + TestGefCommands, + TestGefFunctions, + ] + + runner = unittest.TextTestRunner(verbosity=3) + total_errors = 0 + + for test in [ unittest.TestLoader().loadTestsFromTestCase(x) for x in test_instances ]: + res = runner.run(test) + total_errors += len(res.errors) + len(res.failures) + + return total_errors + + +if __name__ == "__main__": + setup() + errnum = run_tests() + cleanup() + sys.exit(errnum)