├── www ├── saved_users ├── 00_angr_find ├── __init__.py ├── 00_angr_find.c.jinja ├── generate.py └── scaffold00.py ├── 01_angr_avoid ├── __init__.py ├── scaffold01.py ├── 01_angr_avoid.c.jinja └── generate.py ├── 09_angr_hooks ├── __init__.py ├── generate.py ├── 09_angr_hooks.c.jinja └── scaffold09.py ├── xx_angr_segfault ├── __init__.py ├── a ├── generate.py └── xx_angr_segfault.c.templite ├── 07_angr_symbolic_file ├── __init__.py ├── generate.py ├── 07_angr_symbolic_file.c.jinja └── scaffold07.py ├── 08_angr_constraints ├── __init__.py ├── generate.py ├── 08_angr_constraints.c.jinja └── scaffold08.py ├── 10_angr_simprocedures ├── __init__.py ├── 10_angr_simprocedures.c.jinja └── generate.py ├── 11_angr_sim_scanf ├── __init__.py ├── 11_angr_sim_scanf ├── 11_angr_sim_scanf.c.jinja ├── scaffold11.py └── generate.py ├── 12_angr_veritesting ├── __init__.py ├── scaffold12.py ├── generate.py └── 12_angr_veritesting.c.jinja ├── 13_angr_static_binary ├── __init__.py ├── generate.py ├── 13_angr_static_binary.c.jinja └── scaffold13.py ├── 02_angr_find_condition ├── __init__.py ├── 02_angr_find_condition.c.jinja ├── generate.py └── scaffold02.py ├── 03_angr_symbolic_registers ├── __init__.py ├── 03_angr_symbolic_registers.c.jinja ├── generate.py └── scaffold03.py ├── 04_angr_symbolic_stack ├── __init__.py ├── 04_angr_symbolic_stack.c.jinja └── generate.py ├── 05_angr_symbolic_memory ├── __init__.py ├── generate.py ├── 05_angr_symbolic_memory.c.jinja └── scaffold05.py ├── 14_angr_shared_library ├── __init__.py ├── 14_angr_shared_library.c ├── 14_angr_shared_library_so.c.jinja ├── generate.py └── scaffold14.py ├── 15_angr_arbitrary_read ├── __init__.py ├── 15_angr_arbitrary_read.c.jinja └── generate.py ├── 16_angr_arbitrary_write ├── __init__.py ├── 16_angr_arbitrary_write.c.jinja └── generate.py ├── 17_angr_arbitrary_jump ├── __init__.py ├── 17_angr_arbitrary_jump.c.jinja └── generate.py ├── 06_angr_symbolic_dynamic_memory ├── __init__.py ├── generate.py ├── 06_angr_symbolic_dynamic_memory.c.jinja └── scaffold06.py ├── requirements.txt ├── .gitignore ├── dist ├── 00_angr_find ├── 01_angr_avoid ├── 09_angr_hooks ├── 11_angr_sim_scanf ├── 08_angr_constraints ├── 12_angr_veritesting ├── 02_angr_find_condition ├── 04_angr_symbolic_stack ├── 05_angr_symbolic_memory ├── 07_angr_symbolic_file ├── 10_angr_simprocedures ├── 13_angr_static_binary ├── 14_angr_shared_library ├── 15_angr_arbitrary_read ├── 16_angr_arbitrary_write ├── 17_angr_arbitrary_jump ├── 03_angr_symbolic_registers ├── lib14_angr_shared_library.so ├── 06_angr_symbolic_dynamic_memory ├── scaffold12.py ├── scaffold01.py ├── scaffold13.py ├── scaffold05.py ├── scaffold11.py ├── scaffold02.py ├── scaffold06.py ├── scaffold14.py ├── scaffold03.py ├── scaffold00.py ├── scaffold09.py └── scaffold08.py ├── SymbolicExecution.pptx ├── solutions ├── 00_angr_find │ ├── 00_angr_find │ ├── scaffold00.py │ └── solve00.py ├── 01_angr_avoid │ ├── 01_angr_avoid │ ├── scaffold01.py │ └── solve01.py ├── 09_angr_hooks │ ├── 09_angr_hooks │ ├── scaffold09.py │ └── solve09.py ├── 11_angr_sim_scanf │ ├── 11_angr_sim_scanf │ ├── scaffold11.py │ └── solve11.py ├── 08_angr_constraints │ ├── 08_angr_constraints │ └── scaffold08.py ├── 12_angr_veritesting │ ├── 12_angr_veritesting │ ├── scaffold12.py │ └── solve12.py ├── 07_angr_symbolic_file │ ├── 07_angr_symbolic_file │ ├── scaffold07.py │ └── solve07.py ├── 10_angr_simprocedures │ └── 10_angr_simprocedures ├── 13_angr_static_binary │ ├── 13_angr_static_binary │ ├── scaffold13.py │ └── solve13.py ├── 02_angr_find_condition │ ├── 02_angr_find_condition │ ├── scaffold02.py │ └── solve02.py ├── 04_angr_symbolic_stack │ └── 04_angr_symbolic_stack ├── 14_angr_shared_library │ ├── 14_angr_shared_library │ ├── lib14_angr_shared_library.so │ ├── scaffold14.py │ └── solve14.py ├── 15_angr_arbitrary_read │ └── 15_angr_arbitrary_read ├── 17_angr_arbitrary_jump │ └── 17_angr_arbitrary_jump ├── 05_angr_symbolic_memory │ ├── 05_angr_symbolic_memory │ ├── scaffold05.py │ └── solve05.py ├── 16_angr_arbitrary_write │ └── 16_angr_arbitrary_write ├── 03_angr_symbolic_registers │ ├── 03_angr_symbolic_registers │ ├── scaffold03.py │ └── solve03.py ├── 06_angr_symbolic_dynamic_memory │ ├── 06_angr_symbolic_dynamic_memory │ ├── scaffold06.py │ └── solve06.py └── run-all.sh ├── users ├── solve.py ├── NOTES ├── Makefile ├── README ├── wwwusers.py └── package.py /www: -------------------------------------------------------------------------------- 1 | ../www/ -------------------------------------------------------------------------------- /saved_users: -------------------------------------------------------------------------------- 1 | foo bar 2 | -------------------------------------------------------------------------------- /00_angr_find/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01_angr_avoid/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09_angr_hooks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /xx_angr_segfault/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /07_angr_symbolic_file/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /08_angr_constraints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /10_angr_simprocedures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /11_angr_sim_scanf/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /12_angr_veritesting/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /13_angr_static_binary/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02_angr_find_condition/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03_angr_symbolic_registers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /04_angr_symbolic_stack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /05_angr_symbolic_memory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /14_angr_shared_library/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /15_angr_arbitrary_read/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /16_angr_arbitrary_write/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /17_angr_arbitrary_jump/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /06_angr_symbolic_dynamic_memory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jinja2 2 | angr ~= 6.7 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.swp 3 | obj/ 4 | *.pyc 5 | env 6 | obj 7 | users 8 | env 9 | -------------------------------------------------------------------------------- /dist/00_angr_find: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/00_angr_find -------------------------------------------------------------------------------- /dist/01_angr_avoid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/01_angr_avoid -------------------------------------------------------------------------------- /dist/09_angr_hooks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/09_angr_hooks -------------------------------------------------------------------------------- /xx_angr_segfault/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/xx_angr_segfault/a -------------------------------------------------------------------------------- /SymbolicExecution.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/SymbolicExecution.pptx -------------------------------------------------------------------------------- /dist/11_angr_sim_scanf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/11_angr_sim_scanf -------------------------------------------------------------------------------- /dist/08_angr_constraints: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/08_angr_constraints -------------------------------------------------------------------------------- /dist/12_angr_veritesting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/12_angr_veritesting -------------------------------------------------------------------------------- /dist/02_angr_find_condition: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/02_angr_find_condition -------------------------------------------------------------------------------- /dist/04_angr_symbolic_stack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/04_angr_symbolic_stack -------------------------------------------------------------------------------- /dist/05_angr_symbolic_memory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/05_angr_symbolic_memory -------------------------------------------------------------------------------- /dist/07_angr_symbolic_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/07_angr_symbolic_file -------------------------------------------------------------------------------- /dist/10_angr_simprocedures: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/10_angr_simprocedures -------------------------------------------------------------------------------- /dist/13_angr_static_binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/13_angr_static_binary -------------------------------------------------------------------------------- /dist/14_angr_shared_library: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/14_angr_shared_library -------------------------------------------------------------------------------- /dist/15_angr_arbitrary_read: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/15_angr_arbitrary_read -------------------------------------------------------------------------------- /dist/16_angr_arbitrary_write: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/16_angr_arbitrary_write -------------------------------------------------------------------------------- /dist/17_angr_arbitrary_jump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/17_angr_arbitrary_jump -------------------------------------------------------------------------------- /dist/03_angr_symbolic_registers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/03_angr_symbolic_registers -------------------------------------------------------------------------------- /dist/lib14_angr_shared_library.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/lib14_angr_shared_library.so -------------------------------------------------------------------------------- /11_angr_sim_scanf/11_angr_sim_scanf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/11_angr_sim_scanf/11_angr_sim_scanf -------------------------------------------------------------------------------- /solutions/00_angr_find/00_angr_find: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/00_angr_find/00_angr_find -------------------------------------------------------------------------------- /dist/06_angr_symbolic_dynamic_memory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/dist/06_angr_symbolic_dynamic_memory -------------------------------------------------------------------------------- /solutions/01_angr_avoid/01_angr_avoid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/01_angr_avoid/01_angr_avoid -------------------------------------------------------------------------------- /solutions/09_angr_hooks/09_angr_hooks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/09_angr_hooks/09_angr_hooks -------------------------------------------------------------------------------- /solutions/11_angr_sim_scanf/11_angr_sim_scanf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/11_angr_sim_scanf/11_angr_sim_scanf -------------------------------------------------------------------------------- /solutions/08_angr_constraints/08_angr_constraints: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/08_angr_constraints/08_angr_constraints -------------------------------------------------------------------------------- /solutions/12_angr_veritesting/12_angr_veritesting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/12_angr_veritesting/12_angr_veritesting -------------------------------------------------------------------------------- /solutions/07_angr_symbolic_file/07_angr_symbolic_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/07_angr_symbolic_file/07_angr_symbolic_file -------------------------------------------------------------------------------- /solutions/10_angr_simprocedures/10_angr_simprocedures: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/10_angr_simprocedures/10_angr_simprocedures -------------------------------------------------------------------------------- /solutions/13_angr_static_binary/13_angr_static_binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/13_angr_static_binary/13_angr_static_binary -------------------------------------------------------------------------------- /solutions/02_angr_find_condition/02_angr_find_condition: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/02_angr_find_condition/02_angr_find_condition -------------------------------------------------------------------------------- /solutions/04_angr_symbolic_stack/04_angr_symbolic_stack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/04_angr_symbolic_stack/04_angr_symbolic_stack -------------------------------------------------------------------------------- /solutions/14_angr_shared_library/14_angr_shared_library: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/14_angr_shared_library/14_angr_shared_library -------------------------------------------------------------------------------- /solutions/15_angr_arbitrary_read/15_angr_arbitrary_read: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/15_angr_arbitrary_read/15_angr_arbitrary_read -------------------------------------------------------------------------------- /solutions/17_angr_arbitrary_jump/17_angr_arbitrary_jump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/17_angr_arbitrary_jump/17_angr_arbitrary_jump -------------------------------------------------------------------------------- /solutions/05_angr_symbolic_memory/05_angr_symbolic_memory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/05_angr_symbolic_memory/05_angr_symbolic_memory -------------------------------------------------------------------------------- /solutions/16_angr_arbitrary_write/16_angr_arbitrary_write: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/16_angr_arbitrary_write/16_angr_arbitrary_write -------------------------------------------------------------------------------- /solutions/03_angr_symbolic_registers/03_angr_symbolic_registers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/03_angr_symbolic_registers/03_angr_symbolic_registers -------------------------------------------------------------------------------- /solutions/14_angr_shared_library/lib14_angr_shared_library.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/14_angr_shared_library/lib14_angr_shared_library.so -------------------------------------------------------------------------------- /solutions/06_angr_symbolic_dynamic_memory/06_angr_symbolic_dynamic_memory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakespringer/angr_ctf/HEAD/solutions/06_angr_symbolic_dynamic_memory/06_angr_symbolic_dynamic_memory -------------------------------------------------------------------------------- /users: -------------------------------------------------------------------------------- 1 | admin 9999 2 | wuchang 9999 3 | demo0 malware 4 | demo1 malware 5 | demo2 malware 6 | demo3 malware 7 | demo4 malware 8 | demo5 malware 9 | demo6 malware 10 | demo7 malware 11 | demo8 malware 12 | demo9 malware 13 | -------------------------------------------------------------------------------- /dist/scaffold12.py: -------------------------------------------------------------------------------- 1 | # When you construct a simulation manager, you will want to enable Veritesting: 2 | # project.factory.simgr(initial_state, veritesting=True) 3 | # Hint: use one of the first few levels' solutions as a reference. 4 | -------------------------------------------------------------------------------- /12_angr_veritesting/scaffold12.py: -------------------------------------------------------------------------------- 1 | # When you construct a simulation manager, you will want to enable Veritesting: 2 | # project.factory.simgr(initial_state, veritesting=True) 3 | # Hint: use one of the first few levels' solutions as a reference. 4 | -------------------------------------------------------------------------------- /solutions/12_angr_veritesting/scaffold12.py: -------------------------------------------------------------------------------- 1 | # When you construct a simulation manager, you will want to enable Veritesting: 2 | # project.factory.simgr(initial_state, veritesting=True) 3 | # Hint: use one of the first few levels' solutions as a reference. 4 | -------------------------------------------------------------------------------- /solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import angr 4 | import sys 5 | import os 6 | 7 | def main(): 8 | proj = angr.Project(sys.argv[1]) 9 | initial_state = proj.factory.entry_state() 10 | pg = proj.factory.simgr(initial_state, veritesting=False) 11 | pg.explore(find=lambda p: 'Good Job.'.encode('utf8') in p.posix.dumps(1)) 12 | print(pg) 13 | print(repr(pg.found[0].posix.dumps(0))) 14 | 15 | if __name__ == '__main__': 16 | main() 17 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | call complex function on password in level 8 in description (scaffold) 2 | 3 | - add gcc-32 to requirements 4 | - document package.py to explain how it works 5 | - add something that uses unicorn engine 6 | 7 | Replace read/write levels with angr.Project.inspect (see documentation) 8 | can hook all memory reads/writes!!! 9 | instead of replace, add new levels that use these 10 | 11 | add checks to ensure everything is printed nicely (trailing \0) 12 | for all arbitrary x, ensure solutions are capital letters 13 | 14 | for 17, add an exit() after print good job 15 | -------------------------------------------------------------------------------- /14_angr_shared_library/14_angr_shared_library.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef __14_ANGR_SHARED_LIBRARY 5 | #define __14_ANGR_SHARED_LIBRARY 6 | 7 | extern void print_msg(void); 8 | extern int validate(char*, int); 9 | 10 | #endif 11 | 12 | int main(int argc, char* argv[]) { 13 | char buffer[16]; 14 | memset(buffer, 0, 16); 15 | //print_msg(); 16 | printf("Enter the password: "); 17 | scanf("%8s", buffer); 18 | if (validate(buffer, 8)) { 19 | printf("Good Job.\n"); 20 | } else { 21 | printf("Try again.\n"); 22 | } 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | WWWDIR=../www/static/obj 2 | 3 | .PHONY: all web local clean local_clean web_clean 4 | 5 | all: 6 | 7 | env: 8 | ( \ 9 | virtualenv -p python3 env; \ 10 | env/bin/pip install jinja2; \ 11 | ) 12 | 13 | web: env 14 | $(foreach user,$(USERS), mkdir -p $(WWWDIR)/$(user)/angr/solved; env/bin/python package.py $(WWWDIR)/$(user)/angr;) 15 | 16 | local: env 17 | $(foreach user,$(USERS), env/bin/python package.py obj/$(user)/angr;) 18 | 19 | clean: local_clean web_clean 20 | 21 | local_clean: 22 | rm -rf obj 23 | rm -rf env 24 | 25 | web_clean: 26 | rm -rf $(WWWDIR)/* 27 | -------------------------------------------------------------------------------- /17_angr_arbitrary_jump/17_angr_arbitrary_jump.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | char msg[] = "{{ description }}"; 7 | 8 | void print_msg() { 9 | printf("%s", msg); 10 | } 11 | 12 | void print_good() { 13 | printf("Good Job.\n"); 14 | exit(0); 15 | } 16 | 17 | void read_input() { 18 | char padding0[{{ padding0 }}]; 19 | char buffer[8]; 20 | char padding1[{{ padding1 }}]; 21 | 22 | scanf("%s", buffer); 23 | } 24 | 25 | int main(int argc, char* argv[]) { 26 | uint32_t key = 0; 27 | 28 | //print_msg(); 29 | 30 | printf("Enter the password: "); 31 | read_input(); 32 | 33 | printf("Try again.\n"); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /15_angr_arbitrary_read/15_angr_arbitrary_read.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | char msg[] = "{{ description }}"; 8 | char* try_again = "Try again."; 9 | char* good_job = "Good Job."; 10 | uint32_t key; 11 | 12 | void print_msg() { 13 | printf("%s", msg); 14 | } 15 | 16 | struct overflow_me { 17 | char buffer[16]; 18 | char* to_print; 19 | }; 20 | 21 | int main(int argc, char* argv[]) { 22 | struct overflow_me locals; 23 | locals.to_print = try_again; 24 | 25 | //print_msg(); 26 | 27 | printf("Enter the password: "); 28 | scanf("%u %20s", &key, locals.buffer); 29 | 30 | {{ expanded_switch_statement }} 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Eventually, this README will have information about building, installing, and 2 | playing the levels. 3 | 4 | Currently, if you want to play around with them, take a look at package.py, 5 | which builds the levels, and dist/ which generally has an up-to-date build 6 | of each of the levels. 7 | 8 | A Makefile is included that performs an automated build for both a local 9 | installation and for the MetaCTF web installation. A list of users 10 | is passed in via the USERS environment variable which will then build 11 | the binaries for each user listed. 12 | 13 | Build binaries in obj/{foo,bar}/angr 14 | make USERS='foo bar' local 15 | 16 | Build binaries in upper-level MetaCTF repo ../www/static/obj/{foo,bar}/angr 17 | make USERS='foo bar' web 18 | -------------------------------------------------------------------------------- /wwwusers.py: -------------------------------------------------------------------------------- 1 | import json 2 | try: 3 | usersfile=open("users","r") 4 | d = dict([line.split() for line in usersfile]) 5 | usersfile.close() 6 | except: 7 | print("Error opening users file") 8 | exit() 9 | 10 | try: 11 | usersfile=open("saved_users","r") 12 | sd = dict([line.split() for line in usersfile]) 13 | usersfile.close() 14 | except: 15 | print("Error opening newusers file") 16 | exit() 17 | 18 | if sd: 19 | d.update(sd) 20 | 21 | try: 22 | with open("www/users.py","w") as pyusers: 23 | pyusers.write("users = {}\n".format(json.dumps(d))) 24 | pyusers.close() 25 | except: 26 | print("Error opening www/users.py") 27 | exit() 28 | 29 | #for k,v in d.items(): 30 | #print("{} {}".format(k,v)) 31 | -------------------------------------------------------------------------------- /09_angr_hooks/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef = ''.join([random.choice(userdef_charset) for _ in range(16)]) 15 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '09_angr_hooks.c.jinja'), 'r').read() 16 | t = jinja2.Template(template) 17 | c_code = t.render(description='', userdef=userdef) 18 | 19 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 20 | temp.write(c_code) 21 | temp.seek(0) 22 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 23 | 24 | if __name__ == '__main__': 25 | generate(sys.argv) 26 | -------------------------------------------------------------------------------- /08_angr_constraints/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef = ''.join([random.choice(userdef_charset) for _ in range(16)]) 15 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '08_angr_constraints.c.jinja'), 'r').read() 16 | t = jinja2.Template(template) 17 | c_code = t.render(description='', userdef=userdef) 18 | 19 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 20 | temp.write(c_code) 21 | temp.seek(0) 22 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 23 | 24 | if __name__ == '__main__': 25 | generate(sys.argv) 26 | -------------------------------------------------------------------------------- /00_angr_find/00_angr_find.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define USERDEF "{{ userdef }}" 6 | #define LEN_USERDEF {{ len_userdef }} 7 | 8 | char msg[] = 9 | "{{ description }}"; 10 | 11 | void print_msg() { 12 | printf("%s", msg); 13 | } 14 | 15 | int complex_function(int value, int i) { 16 | #define LAMBDA 3 17 | if (!('A' <= value && value <= 'Z')) { 18 | printf("Try again.\n"); 19 | exit(1); 20 | } 21 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 22 | } 23 | 24 | int main(int argc, char* argv[]) { 25 | char buffer[9]; 26 | 27 | //print_msg(); 28 | 29 | printf("Enter the password: "); 30 | scanf("%8s", buffer); 31 | 32 | for (int i=0; i 2 | #include 3 | #include 4 | 5 | #define USERDEF "{{ userdef }}" 6 | #define LEN_USERDEF {{ len_userdef }} 7 | 8 | char msg[] = "{{ description }}"; 9 | 10 | void print_msg() { 11 | printf("%s", msg); 12 | } 13 | 14 | int complex_function(int value, int i) { 15 | #define LAMBDA 41 16 | if (!('A' <= value && value <= 'Z')) { 17 | printf("Try again.\n"); 18 | exit(1); 19 | } 20 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 21 | } 22 | 23 | int validate(char* buffer, int length) { 24 | if (length < 8) { 25 | return 0; 26 | } 27 | 28 | char password[20]; 29 | 30 | for (int i=0; i < 20; ++i) { 31 | password[i] = 0; 32 | } 33 | 34 | strncpy(password, USERDEF, LEN_USERDEF); 35 | 36 | for (int i=0; i<8; ++i) { 37 | buffer[i] = complex_function(buffer[i], i); 38 | } 39 | 40 | return !strcmp(buffer, password); 41 | } 42 | -------------------------------------------------------------------------------- /00_angr_find/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | 12 | random.seed(seed) 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 15 | 16 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '00_angr_find.c.jinja'), 'r').read() 17 | t = jinja2.Template(template) 18 | c_code = t.render(userdef=userdef, len_userdef=len(userdef), description = '') 19 | 20 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 21 | temp.write(c_code) 22 | temp.seek(0) 23 | os.system('gcc -fno-pie -no-pie -fcf-protection=none -m32 -o ' + output_file + ' ' + temp.name) 24 | 25 | if __name__ == '__main__': 26 | generate(sys.argv) 27 | -------------------------------------------------------------------------------- /13_angr_static_binary/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 15 | 16 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '13_angr_static_binary.c.jinja'), 'r').read() 17 | t = jinja2.Template(template) 18 | c_code = t.render(description='', userdef=userdef, len_userdef=len(userdef)) 19 | 20 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 21 | temp.write(c_code) 22 | temp.seek(0) 23 | os.system('gcc -static -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 24 | 25 | if __name__ == '__main__': 26 | generate(sys.argv) 27 | -------------------------------------------------------------------------------- /dist/scaffold01.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import sys 3 | 4 | def main(argv): 5 | path_to_binary = ??? 6 | project = angr.Project(path_to_binary) 7 | initial_state = project.factory.entry_state() 8 | simulation = project.factory.simgr(initial_state) 9 | 10 | # Explore the binary, but this time, instead of only looking for a state that 11 | # reaches the print_good_address, also find a state that does not reach 12 | # will_not_succeed_address. The binary is pretty large, to save you some time, 13 | # everything you will need to look at is near the beginning of the address 14 | # space. 15 | # (!) 16 | print_good_address = ??? 17 | will_not_succeed_address = ??? 18 | simulation.explore(find=print_good_address, avoid=will_not_succeed_address) 19 | 20 | if simulation.found: 21 | solution_state = simulation.found[0] 22 | print(solution_state.posix.dumps(sys.stdin.fileno())) 23 | else: 24 | raise Exception('Could not find the solution') 25 | 26 | if __name__ == '__main__': 27 | main(sys.argv) 28 | -------------------------------------------------------------------------------- /16_angr_arbitrary_write/16_angr_arbitrary_write.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define USERDEF "{{ userdef }}" 8 | 9 | char msg[] = "{{ description }}"; 10 | char unimportant_buffer[16]; 11 | char password_buffer[16]; 12 | uint32_t key; 13 | 14 | void print_msg() { 15 | printf("%s", msg); 16 | } 17 | 18 | struct overflow_me { 19 | char buffer[16]; 20 | char* to_copy_to; 21 | }; 22 | 23 | int main(int argc, char* argv[]) { 24 | struct overflow_me locals; 25 | locals.to_copy_to = unimportant_buffer; 26 | 27 | memset(locals.buffer, 0, 16); 28 | strncpy(password_buffer, "PASSWORD", 12); 29 | 30 | //print_msg(); 31 | 32 | printf("Enter the password: "); 33 | scanf("%u %20s", &key, locals.buffer); 34 | 35 | {{ expanded_switch_statement }} 36 | 37 | if (strncmp(password_buffer, USERDEF, 8)) { 38 | printf("Try again.\n"); 39 | } else { 40 | printf("Good Job.\n"); 41 | } 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /07_angr_symbolic_file/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef0 = ''.join(random.choice(userdef_charset) for _ in range(8)) 15 | userdef1 = ''.join(random.choice(userdef_charset) for _ in range(8)) 16 | 17 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '07_angr_symbolic_file.c.jinja'), 'r').read() 18 | t = jinja2.Template(template) 19 | c_code = t.render(description='', userdef0=userdef0, userdef1=userdef1) 20 | 21 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 22 | temp.write(c_code) 23 | temp.seek(0) 24 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 25 | 26 | if __name__ == '__main__': 27 | generate(sys.argv) 28 | -------------------------------------------------------------------------------- /xx_angr_segfault/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys, random, os, tempfile 4 | from templite import Templite 5 | 6 | def generate(argv): 7 | if len(argv) != 3: 8 | print('Usage: ./generate.py [seed] [output_file]') 9 | sys.exit() 10 | 11 | seed = argv[1] 12 | output_file = argv[2] 13 | 14 | random.seed(seed) 15 | 16 | description = '' 17 | with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'description.txt'), 'r') as desc_file: 18 | description = desc_file.read().encode('unicode_escape') 19 | 20 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'xx_angr_segfault.c.templite'), 'r').read() 21 | c_code = Templite(template).render(description=description) 22 | 23 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 24 | temp.write(c_code) 25 | temp.seek(0) 26 | os.system('gcc -fno-pie -no-pie -m32 -fno-stack-protector -o ' + output_file + ' ' + temp.name) 27 | 28 | if __name__ == '__main__': 29 | generate(sys.argv) 30 | -------------------------------------------------------------------------------- /12_angr_veritesting/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | letter0 = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") 14 | integer = random.randint(0, 256) 15 | lamb = random.choice([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]) 16 | 17 | 18 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '12_angr_veritesting.c.jinja'), 'r').read() 19 | t = jinja2.Template(template) 20 | c_code = t.render(description='', integer=integer, letter0=letter0, lamb=lamb) 21 | 22 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 23 | temp.write(c_code) 24 | temp.seek(0) 25 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 26 | 27 | if __name__ == '__main__': 28 | generate(sys.argv) 29 | -------------------------------------------------------------------------------- /04_angr_symbolic_stack/04_angr_symbolic_stack.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define USERDEF0 {{ userdef0 }} 7 | #define USERDEF1 {{ userdef1 }} 8 | 9 | char msg[] = "{{ description }}"; 10 | 11 | void print_msg() { 12 | printf("%s", msg); 13 | } 14 | 15 | uint32_t complex_function0(uint32_t value) { 16 | {{ complex_function0 }} 17 | return value; 18 | } 19 | 20 | uint32_t complex_function1(uint32_t value) { 21 | {{ complex_function1 }} 22 | return value; 23 | } 24 | 25 | void handle_user() { 26 | uint32_t user_int0; 27 | uint32_t user_int1; 28 | scanf("%u %u", &user_int0, &user_int1); 29 | user_int0 = complex_function0(user_int0); 30 | user_int1 = complex_function1(user_int1); 31 | if ((user_int0 ^ USERDEF0) || (user_int1 ^ USERDEF1)) { 32 | printf("Try again.\n"); 33 | } else { 34 | printf("Good Job.\n"); 35 | } 36 | } 37 | 38 | int main(int argc, char* argv[]) { 39 | //print_msg(); 40 | printf("Enter the password: "); 41 | handle_user(); 42 | } 43 | -------------------------------------------------------------------------------- /02_angr_find_condition/02_angr_find_condition.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define USERDEF "{{ userdef }}" 6 | #define LEN_USERDEF {{ len_userdef }} 7 | 8 | char msg[] = "{{ description }}"; 9 | 10 | void print_msg() { 11 | printf("%s", msg); 12 | } 13 | 14 | int complex_function(int value, int i) { 15 | #define LAMBDA 31 16 | if (!('A' <= value && value <= 'Z')) { 17 | printf("Try again.\n"); 18 | exit(1); 19 | } 20 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 21 | } 22 | 23 | int main(int argc, char* argv[]) { 24 | char buffer[20]; 25 | char password[20]; 26 | unsigned int x = 0xDEADBEEF; 27 | 28 | //print_msg(); 29 | 30 | for (int i=0; i < 20; ++i) { 31 | password[i] = 0; 32 | } 33 | 34 | strncpy(password, USERDEF, LEN_USERDEF); 35 | 36 | printf("Enter the password: "); 37 | scanf("%8s", buffer); 38 | 39 | for (int i=0; i 2 | #include 3 | #include 4 | #include 5 | 6 | char msg[] = "{{ description }}"; 7 | 8 | void print_msg() { 9 | printf("%s", msg); 10 | } 11 | 12 | int complex_function(int value, int i) { 13 | #define LAMBDA {{ lamb }} 14 | if (!('A' <= value && value <= 'Z')) { 15 | printf("Try again.\n"); 16 | exit(1); 17 | } 18 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 19 | } 20 | 21 | int main(int argc, char* argv[]) { 22 | char buffer[33]; 23 | 24 | //print_msg(); 25 | 26 | memset(buffer, 0, 33); 27 | 28 | printf("Enter the password: "); 29 | scanf("%32s", buffer); 30 | 31 | int counter0 = 0; 32 | int counter1 = 0; 33 | for (int i=0; i<32; ++i) { 34 | if (buffer[i] == complex_function('{{ letter0 }}', i + {{ integer }}) ) { 35 | counter0++; 36 | } 37 | } 38 | 39 | if (counter0 == 32 && buffer[33] == 0x0) { 40 | printf("Good Job.\n"); 41 | } else { 42 | printf("Try again.\n"); 43 | } 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /05_angr_symbolic_memory/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef = repr(''.join(random.choice(userdef_charset) for _ in range(32)))[1:-1].replace('\"', '\\\"') 15 | padding0 = random.randint(0, 2**26) 16 | padding1 = random.randint(0, 2**26) 17 | 18 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '05_angr_symbolic_memory.c.jinja'), 'r').read() 19 | t = jinja2.Template(template) 20 | c_code = t.render(description='', padding0=padding0, padding1=padding1, userdef=userdef) 21 | 22 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 23 | temp.write(c_code) 24 | temp.seek(0) 25 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 26 | 27 | if __name__ == '__main__': 28 | generate(sys.argv) 29 | -------------------------------------------------------------------------------- /13_angr_static_binary/13_angr_static_binary.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define USERDEF "{{ userdef }}" 6 | #define LEN_USERDEF {{ len_userdef }} 7 | 8 | char msg[] = 9 | "{{ description }}"; 10 | 11 | void print_msg() { 12 | printf("%s", msg); 13 | } 14 | 15 | int complex_function(int value, int i) { 16 | #define LAMBDA 37 17 | if (!('A' <= value && value <= 'Z')) { 18 | printf("Try again.\n"); 19 | exit(1); 20 | } 21 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 22 | } 23 | int main(int argc, char* argv[]) { 24 | char buffer[20]; 25 | char password[20]; 26 | 27 | //print_msg(); 28 | 29 | for (int i=0; i < 20; ++i) { 30 | password[i] = 0; 31 | } 32 | 33 | strncpy(password, USERDEF, LEN_USERDEF); 34 | 35 | printf("Enter the password: "); 36 | scanf("%8s", buffer); 37 | 38 | for (int i=0; i<8; ++i) { 39 | buffer[i] = complex_function(buffer[i], i); 40 | } 41 | 42 | if (strcmp(buffer, password)) { 43 | printf("Try again.\n"); 44 | } else { 45 | printf("Good Job.\n"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /06_angr_symbolic_dynamic_memory/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 13 | userdef0 = ''.join([random.choice(userdef_charset) for _ in range(8)]) 14 | userdef1 = ''.join([random.choice(userdef_charset) for _ in range(8)]) 15 | padding = random.randint(0, 2**26) 16 | 17 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '06_angr_symbolic_dynamic_memory.c.jinja'), 'r').read() 18 | t = jinja2.Template(template) 19 | c_code = t.render(description='', padding=padding, userdef0=userdef0, userdef1=userdef1) 20 | 21 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 22 | temp.write(c_code) 23 | temp.seek(0) 24 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 25 | 26 | if __name__ == '__main__': 27 | generate(sys.argv) 28 | -------------------------------------------------------------------------------- /05_angr_symbolic_memory/05_angr_symbolic_memory.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define USERDEF "{{ userdef }}" 5 | 6 | char padding0[ {{ padding0 }} ]; 7 | char user_input[32+1]; 8 | char padding1[ {{ padding1 }} ]; 9 | 10 | char msg[] = "{{ description }}"; 11 | 12 | void print_msg() { 13 | printf("%s", msg); 14 | } 15 | 16 | int complex_function(int value, int i) { 17 | #define LAMBDA 9 18 | if (!('A' <= value && value <= 'Z')) { 19 | printf("Try again.\n"); 20 | exit(1); 21 | } 22 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 23 | } 24 | 25 | int main(int argc, char* argv[]) { 26 | memset(user_input, 0, sizeof(user_input)); 27 | 28 | //print_msg(); 29 | printf("Enter the password: "); 30 | scanf("%8s %8s %8s %8s", user_input, &user_input[8], &user_input[16], &user_input[24]); 31 | 32 | for (int i=0; i<32; ++i) { 33 | user_input[i] = (char) complex_function(user_input[i], i); 34 | } 35 | 36 | if (strncmp(user_input, USERDEF, 32)) { 37 | printf("Try again.\n"); 38 | } else { 39 | printf("Good Job.\n"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /01_angr_avoid/scaffold01.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import sys 3 | 4 | def main(argv): 5 | path_to_binary = ??? 6 | project = angr.Project(path_to_binary) 7 | initial_state = project.factory.entry_state( 8 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 9 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 10 | ) 11 | simulation = project.factory.simgr(initial_state) 12 | 13 | # Explore the binary, but this time, instead of only looking for a state that 14 | # reaches the print_good_address, also find a state that does not reach 15 | # will_not_succeed_address. The binary is pretty large, to save you some time, 16 | # everything you will need to look at is near the beginning of the address 17 | # space. 18 | # (!) 19 | print_good_address = ??? 20 | will_not_succeed_address = ??? 21 | simulation.explore(find=print_good_address, avoid=will_not_succeed_address) 22 | 23 | if simulation.found: 24 | solution_state = simulation.found[0] 25 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 26 | else: 27 | raise Exception('Could not find the solution') 28 | 29 | if __name__ == '__main__': 30 | main(sys.argv) 31 | -------------------------------------------------------------------------------- /solutions/01_angr_avoid/scaffold01.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import sys 3 | 4 | def main(argv): 5 | path_to_binary = ??? 6 | project = angr.Project(path_to_binary) 7 | initial_state = project.factory.entry_state( 8 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 9 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 10 | ) 11 | simulation = project.factory.simgr(initial_state) 12 | 13 | # Explore the binary, but this time, instead of only looking for a state that 14 | # reaches the print_good_address, also find a state that does not reach 15 | # will_not_succeed_address. The binary is pretty large, to save you some time, 16 | # everything you will need to look at is near the beginning of the address 17 | # space. 18 | # (!) 19 | print_good_address = ??? 20 | will_not_succeed_address = ??? 21 | simulation.explore(find=print_good_address, avoid=will_not_succeed_address) 22 | 23 | if simulation.found: 24 | solution_state = simulation.found[0] 25 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 26 | else: 27 | raise Exception('Could not find the solution') 28 | 29 | if __name__ == '__main__': 30 | main(sys.argv) 31 | -------------------------------------------------------------------------------- /solutions/01_angr_avoid/solve01.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import sys 3 | 4 | def main(argv): 5 | path_to_binary = argv[1] 6 | project = angr.Project(path_to_binary) 7 | initial_state = project.factory.entry_state( 8 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 9 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 10 | ) 11 | simulation = project.factory.simgr(initial_state) 12 | 13 | # Explore the binary, but this time, instead of only looking for a state that 14 | # reaches the print_good_address, also find a state that does not reach 15 | # will_not_succeed_address. The binary is pretty large, to save you some time, 16 | # everything you will need to look at is near the beginning of the address 17 | # space. 18 | # (!) 19 | print_good_address = 0x80485f7 20 | will_not_succeed_address = 0x80485bf 21 | simulation.explore(find=print_good_address, avoid=will_not_succeed_address) 22 | 23 | if simulation.found: 24 | solution_state = simulation.found[0] 25 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 26 | else: 27 | raise Exception('Could not find the solution') 28 | 29 | if __name__ == '__main__': 30 | main(sys.argv) 31 | -------------------------------------------------------------------------------- /04_angr_symbolic_stack/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef0 = random.randint(0, 0xFFFFFFFF) 14 | userdef1 = random.randint(0, 0xFFFFFFFF) 15 | complex_function0_string = ''.join([ (f'value ^= {random.randint(0,0xFFFFFFFF)};') for _ in range(32) ]) 16 | complex_function1_string = ''.join([ (f'value ^= {random.randint(0,0xFFFFFFFF)};') for _ in range(32) ]) 17 | 18 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '04_angr_symbolic_stack.c.jinja'), 'r').read() 19 | t = jinja2.Template(template) 20 | c_code = t.render(description = '', complex_function0=complex_function0_string, complex_function1=complex_function1_string, userdef0=userdef0, userdef1=userdef1) 21 | 22 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 23 | temp.write(c_code) 24 | temp.seek(0) 25 | os.system('gcc -fno-stack-protector -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name + ' 2>/dev/null') 26 | 27 | if __name__ == '__main__': 28 | generate(sys.argv) 29 | 30 | 31 | -------------------------------------------------------------------------------- /03_angr_symbolic_registers/03_angr_symbolic_registers.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | register int eax asm("eax"); 5 | register int ebx asm("ebx"); 6 | register int edx asm("edx"); 7 | 8 | char msg[] = "{{ description }}"; 9 | 10 | void print_msg() { 11 | printf("%s", msg); 12 | } 13 | 14 | int complex_function_1(int input) { 15 | {{ complex_function_1 }} 16 | return input; 17 | } 18 | 19 | int complex_function_2(int input) { 20 | {{ complex_function_2 }} 21 | return input; 22 | } 23 | 24 | int complex_function_3(int input) { 25 | {{ complex_function_3 }} 26 | return input; 27 | } 28 | 29 | void get_user_input() { 30 | int first, second, third; 31 | scanf("%x %x %x", &first, &second, &third); 32 | eax = first; 33 | ebx = second; 34 | edx = third; 35 | } 36 | 37 | int main(int argc, char* argv[]) { 38 | //print_msg(); 39 | printf("Enter the password: "); 40 | get_user_input(); 41 | int non_eax = eax; 42 | int non_ebx = ebx; 43 | int non_edx = edx; 44 | non_eax = complex_function_1(non_eax); 45 | non_ebx = complex_function_2(non_ebx); 46 | non_edx = complex_function_3(non_edx); 47 | 48 | if (non_eax || non_ebx || non_edx) { 49 | printf("Try again.\n"); 50 | } else { 51 | printf("Good Job.\n"); 52 | } 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /11_angr_sim_scanf/11_angr_sim_scanf.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define USERDEF0 "{{ userdef }}" 7 | #define LEN_USERDEF {{ len_userdef }} 8 | 9 | char msg[] = "{{ description }}"; 10 | 11 | char padding0[{{ padding0 }}]; 12 | char buffer1[5]; 13 | char padding1[{{ padding1 }}]; 14 | char buffer0[5]; 15 | char padding2[{{ padding2 }}]; 16 | 17 | void print_msg() { 18 | printf("%s", msg); 19 | } 20 | 21 | int complex_function(int value, int i) { 22 | #define LAMBDA 29 23 | if (!('A' <= value && value <= 'Z')) { 24 | printf("Try again.\n"); 25 | exit(1); 26 | } 27 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 28 | } 29 | 30 | int main(int argc, char* argv[]) { 31 | char password[20]; 32 | int keep_going = 1; 33 | unsigned int x = 0xDEADBEEF; 34 | 35 | //print_msg(); 36 | 37 | memset(password, 0, 20); 38 | strncpy(&password[0], USERDEF0, LEN_USERDEF); 39 | 40 | /* complex function on password */ 41 | for (int j=0; j<8; ++j) { 42 | password[j] = complex_function(password[j], j); 43 | } 44 | 45 | printf("Enter the password: "); 46 | 47 | {{ recursive_if_else }} 48 | 49 | if (!keep_going) { 50 | printf("Try again.\n"); 51 | } else { 52 | printf("Good Job.\n"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /08_angr_constraints/08_angr_constraints.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define USERDEF "{{ userdef }}" 7 | 8 | char msg[] = "{{ description }}"; 9 | 10 | char buffer[17]; 11 | char password[16]; 12 | 13 | void print_msg() { 14 | printf("%s", msg); 15 | } 16 | 17 | int complex_function(int value, int i) { 18 | #define LAMBDA 53 19 | if (!('A' <= value && value <= 'Z')) { 20 | printf("Try again.\n"); 21 | exit(1); 22 | } 23 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 24 | } 25 | 26 | int check_equals_{{ userdef }}(char* to_check, size_t length) { 27 | uint32_t num_correct = 0; 28 | for (int i=0; i 2 | #include 3 | #include 4 | #define USERDEF0 "{{ userdef0 }}" 5 | #define USERDEF1 "{{ userdef1 }}" 6 | 7 | char padding2[{{ padding }}]; 8 | char* buffer0; 9 | char* buffer1; 10 | char* buffer2; 11 | char* buffer3; 12 | 13 | char msg[] = "{{ description }}"; 14 | 15 | void print_msg() { 16 | printf("%s", msg); 17 | } 18 | 19 | int complex_function(int value, int i) { 20 | #define LAMBDA 13 21 | if (!('A' <= value && value <= 'Z')) { 22 | printf("Try again.\n"); 23 | exit(1); 24 | } 25 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 26 | } 27 | 28 | int main(int argc, char* argv[]) { 29 | buffer0 = malloc(9); 30 | buffer1 = malloc(9); 31 | 32 | memset(buffer0, 0, 9); 33 | memset(buffer1, 0, 9); 34 | 35 | //print_msg(); 36 | printf("Enter the password: "); 37 | scanf("%8s %8s", buffer0, buffer1); 38 | 39 | for (int i=0; i<8; ++i) { 40 | buffer0[i] = complex_function(buffer0[i], i); 41 | buffer1[i] = complex_function(buffer1[i], i+32); 42 | } 43 | 44 | if (strncmp(buffer0, USERDEF0, 8) 45 | || strncmp(buffer1, USERDEF1, 8)) { 46 | printf("Try again.\n"); 47 | } else { 48 | printf("Good Job.\n"); 49 | } 50 | 51 | free(buffer0); 52 | free(buffer1); 53 | } 54 | -------------------------------------------------------------------------------- /10_angr_simprocedures/10_angr_simprocedures.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define USERDEF "{{ userdef }}" 7 | 8 | char msg[] = "{{ description }}"; 9 | 10 | char password[17]; 11 | 12 | void print_msg() { 13 | printf("%s", msg); 14 | } 15 | 16 | int complex_function(int value, int i) { 17 | #define LAMBDA 29 18 | if (!('A' <= value && value <= 'Z')) { 19 | printf("Try again.\n"); 20 | exit(1); 21 | } 22 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 23 | } 24 | 25 | int check_equals_{{ userdef }}(char* to_check, size_t length) { 26 | uint32_t num_correct = 0; 27 | for (int i=0; i 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | char msg[] = "${ description }$"; 21 | char* try_again = "Try again."; 22 | uint32_t key; 23 | 24 | void print_msg() { 25 | printf("%s", msg); 26 | } 27 | 28 | struct overflow_me { 29 | char buffer[16]; 30 | char* arbitrary_pointer; 31 | char allocated_memory; 32 | char random_char; 33 | }; 34 | 35 | int main(int argc, char* argv[]) { 36 | struct overflow_me locals; 37 | memset(&locals, 0, sizeof(locals)); 38 | locals.arbitrary_pointer = &locals.allocated_memory; 39 | 40 | print_msg(); 41 | 42 | printf("Enter the password: "); 43 | scanf("%u %20s", &key, locals.buffer); 44 | 45 | ${ 46 | hit_statement = 'locals.random_char = *(&locals.allocated_memory);' 47 | miss_statement = 'locals.random_char = *locals.arbitrary_pointer;' 48 | expanded_switch_statement('key', miss_statement, hit_statement, random.sample(range(2**26-1), 2)) 49 | }$ 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /09_angr_hooks/09_angr_hooks.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define USERDEF "{{ userdef }}" 7 | 8 | char msg[] = "{{ description }}"; 9 | 10 | char buffer[17]; 11 | char password[16]; 12 | int equals; 13 | 14 | void print_msg() { 15 | printf("%s", msg); 16 | } 17 | 18 | int complex_function(int value, int i) { 19 | #define LAMBDA 23 20 | if (!('A' <= value && value <= 'Z')) { 21 | printf("Try again.\n"); 22 | exit(1); 23 | } 24 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 25 | } 26 | 27 | int check_equals_{{ userdef }}(char* to_check, size_t length) { 28 | uint32_t num_correct = 0; 29 | for (int i=0; i 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define USERDEF "{{ userdef }}" 9 | #define LEN_USERDEF {{ len_userdef }} 10 | 11 | // return true if nth bit of array is 1 12 | #define CHECK_BIT(array, bit_index) (!!(((uint8_t*) array)[bit_index / 8] & (((uint8_t) 0x1) << (bit_index % 8)))) 13 | 14 | char msg[] = 15 | "{{ description }}"; 16 | 17 | uint8_t should_succeed = 1; 18 | 19 | void print_msg() { 20 | printf("%s", msg); 21 | } 22 | 23 | int complex_function(int value, int i) { 24 | #define LAMBDA 5 25 | if (!('A' <= value && value <= 'Z')) { 26 | printf("Try again.\n"); 27 | exit(1); 28 | } 29 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 30 | } 31 | 32 | void avoid_me() { 33 | should_succeed = 0; 34 | } 35 | 36 | void maybe_good(char* compare0, char* compare1) { 37 | if (should_succeed && !strncmp(compare0, compare1, 8)) { 38 | printf("Good Job.\n"); 39 | } else { 40 | printf("Try again.\n"); 41 | } 42 | } 43 | 44 | int main(int argc, char* argv[]) { 45 | char buffer[20]; 46 | char password[20]; 47 | 48 | //print_msg(); 49 | 50 | for (int i=0; i < 20; ++i) { 51 | password[i] = 0; 52 | } 53 | 54 | strncpy(password, USERDEF, LEN_USERDEF); 55 | 56 | printf("Enter the password: "); 57 | scanf("%8s", buffer); 58 | 59 | for (int i=0; i/dev/null') 36 | 37 | if __name__ == '__main__': 38 | generate(sys.argv) 39 | -------------------------------------------------------------------------------- /dist/scaffold13.py: -------------------------------------------------------------------------------- 1 | # This challenge is the exact same as the first challenge, except that it was 2 | # compiled as a static binary. Normally, Angr automatically replaces standard 3 | # library functions with SimProcedures that work much more quickly. 4 | # 5 | # Here are a few SimProcedures Angr has already written for you. They implement 6 | # standard library functions. You will not need all of them: 7 | # angr.SIM_PROCEDURES['libc']['malloc'] 8 | # angr.SIM_PROCEDURES['libc']['fopen'] 9 | # angr.SIM_PROCEDURES['libc']['fclose'] 10 | # angr.SIM_PROCEDURES['libc']['fwrite'] 11 | # angr.SIM_PROCEDURES['libc']['getchar'] 12 | # angr.SIM_PROCEDURES['libc']['strncmp'] 13 | # angr.SIM_PROCEDURES['libc']['strcmp'] 14 | # angr.SIM_PROCEDURES['libc']['scanf'] 15 | # angr.SIM_PROCEDURES['libc']['printf'] 16 | # angr.SIM_PROCEDURES['libc']['puts'] 17 | # angr.SIM_PROCEDURES['libc']['exit'] 18 | # angr.SIM_PROCEDURES['glibc']['__libc_start_main'] 19 | # 20 | # As a reminder, you can hook functions with something similar to: 21 | # project.hook(malloc_address, angr.SIM_PROCEDURES['libc']['malloc']()) 22 | # 23 | # There are many more, see: 24 | # https://github.com/angr/angr/tree/master/angr/procedures/libc 25 | # 26 | # Additionally, note that, when the binary is executed, the main function is not 27 | # the first piece of code called. In the _start function, __libc_start_main is 28 | # called to start your program. The initialization that occurs in this function 29 | # can take a long time with Angr, so you should replace it with a SimProcedure. 30 | # angr.SIM_PROCEDURES['glibc']['__libc_start_main'] 31 | # Note 'glibc' instead of 'libc'. 32 | -------------------------------------------------------------------------------- /07_angr_symbolic_file/07_angr_symbolic_file.c.jinja: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #define USERDEF0 "{{ userdef0 }}" 6 | #define USERDEF1 "{{ userdef1 }}.txt" 7 | #define FILESIZE 64 8 | 9 | char msg[] = "{{ description }}"; 10 | 11 | char buffer[FILESIZE]; 12 | FILE* fp; 13 | 14 | void print_msg() { 15 | printf("%s", msg); 16 | } 17 | 18 | int complex_function(int value, int i) { 19 | #define LAMBDA 17 20 | if (!('A' <= value && value <= 'Z')) { 21 | printf("Try again.\n"); 22 | exit(1); 23 | } 24 | return ((value - 'A' + (LAMBDA * i)) % ('Z' - 'A' + 1)) + 'A'; 25 | } 26 | 27 | void ignore_me(char* buffer, int length) { 28 | char buffer_break_angr[length]; 29 | int i; 30 | 31 | memset(buffer_break_angr, 0, length); 32 | unlink(USERDEF1); 33 | FILE* file = fopen(USERDEF1, "a+b"); 34 | fwrite(buffer, 1, length, file); 35 | fseek(file, 0, SEEK_SET); 36 | fscanf(file, "%64s", buffer_break_angr); 37 | fseek(file, 0, SEEK_SET); 38 | fwrite(buffer_break_angr, 1, length, file); 39 | fclose(file); 40 | } 41 | 42 | int main(int argc, char* argv[]) { 43 | memset(buffer, 0, FILESIZE); 44 | 45 | //print_msg(); 46 | printf("Enter the password: "); 47 | scanf("%64s", buffer); 48 | ignore_me(buffer, FILESIZE); 49 | memset(buffer, 0, FILESIZE); 50 | fp = fopen(USERDEF1, "rb"); 51 | fread(buffer, 1, FILESIZE, fp); 52 | fclose(fp); 53 | unlink(USERDEF1); 54 | 55 | for (int i=0; i<8; ++i) { 56 | buffer[i] = complex_function(buffer[i], i); 57 | } 58 | 59 | if (strncmp(buffer, USERDEF0, 9)) { 60 | printf("Try again.\n"); 61 | exit(1); 62 | } else { 63 | printf("Good Job.\n"); 64 | exit(0); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /dist/scaffold05.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = ??? 10 | initial_state = project.factory.blank_state(addr=start_address) 11 | 12 | # The binary is calling scanf("%8s %8s %8s %8s"). 13 | # (!) 14 | password0 = claripy.BVS('password0', ???) 15 | ... 16 | 17 | # Determine the address of the global variable to which scanf writes the user 18 | # input. The function 'initial_state.memory.store(address, value)' will write 19 | # 'value' (a bitvector) to 'address' (a memory location, as an integer.) The 20 | # 'address' parameter can also be a bitvector (and can be symbolic!). 21 | # (!) 22 | password0_address = ??? 23 | initial_state.memory.store(password0_address, password0) 24 | ... 25 | 26 | simulation = project.factory.simgr(initial_state) 27 | 28 | def is_successful(state): 29 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 30 | return ??? 31 | 32 | def should_abort(state): 33 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 34 | return ??? 35 | 36 | simulation.explore(find=is_successful, avoid=should_abort) 37 | 38 | if simulation.found: 39 | solution_state = simulation.found[0] 40 | 41 | # Solve for the symbolic values. We are trying to solve for a string. 42 | # Therefore, we will use eval, with named parameter cast_to=str 43 | # which returns a string instead of an integer. 44 | # (!) 45 | solution0 = solution_state.se.eval(password0,cast_to=str) 46 | ... 47 | solution = ??? 48 | 49 | print(solution) 50 | else: 51 | raise Exception('Could not find the solution') 52 | 53 | if __name__ == '__main__': 54 | main(sys.argv) 55 | -------------------------------------------------------------------------------- /13_angr_static_binary/scaffold13.py: -------------------------------------------------------------------------------- 1 | # This challenge is the exact same as the first challenge, except that it was 2 | # compiled as a static binary. Normally, Angr automatically replaces standard 3 | # library functions with SimProcedures that work much more quickly. 4 | # 5 | # To solve the challenge, manually hook any standard library c functions that 6 | # are used. Then, ensure that you begin the execution at the beginning of the 7 | # main function. Do not use entry_state. 8 | # 9 | # Here are a few SimProcedures Angr has already written for you. They implement 10 | # standard library functions. You will not need all of them: 11 | # angr.SIM_PROCEDURES['libc']['malloc'] 12 | # angr.SIM_PROCEDURES['libc']['fopen'] 13 | # angr.SIM_PROCEDURES['libc']['fclose'] 14 | # angr.SIM_PROCEDURES['libc']['fwrite'] 15 | # angr.SIM_PROCEDURES['libc']['getchar'] 16 | # angr.SIM_PROCEDURES['libc']['strncmp'] 17 | # angr.SIM_PROCEDURES['libc']['strcmp'] 18 | # angr.SIM_PROCEDURES['libc']['scanf'] 19 | # angr.SIM_PROCEDURES['libc']['printf'] 20 | # angr.SIM_PROCEDURES['libc']['puts'] 21 | # angr.SIM_PROCEDURES['libc']['exit'] 22 | # 23 | # As a reminder, you can hook functions with something similar to: 24 | # project.hook(malloc_address, angr.SIM_PROCEDURES['libc']['malloc']()) 25 | # 26 | # There are many more, see: 27 | # https://github.com/angr/angr/tree/master/angr/procedures/libc 28 | # 29 | # Additionally, note that, when the binary is executed, the main function is not 30 | # the first piece of code called. In the _start function, __libc_start_main is 31 | # called to start your program. The initialization that occurs in this function 32 | # can take a long time with Angr, so you should replace it with a SimProcedure. 33 | # angr.SIM_PROCEDURES['glibc']['__libc_start_main'] 34 | # Note 'glibc' instead of 'libc'. 35 | -------------------------------------------------------------------------------- /solutions/13_angr_static_binary/scaffold13.py: -------------------------------------------------------------------------------- 1 | # This challenge is the exact same as the first challenge, except that it was 2 | # compiled as a static binary. Normally, Angr automatically replaces standard 3 | # library functions with SimProcedures that work much more quickly. 4 | # 5 | # To solve the challenge, manually hook any standard library c functions that 6 | # are used. Then, ensure that you begin the execution at the beginning of the 7 | # main function. Do not use entry_state. 8 | # 9 | # Here are a few SimProcedures Angr has already written for you. They implement 10 | # standard library functions. You will not need all of them: 11 | # angr.SIM_PROCEDURES['libc']['malloc'] 12 | # angr.SIM_PROCEDURES['libc']['fopen'] 13 | # angr.SIM_PROCEDURES['libc']['fclose'] 14 | # angr.SIM_PROCEDURES['libc']['fwrite'] 15 | # angr.SIM_PROCEDURES['libc']['getchar'] 16 | # angr.SIM_PROCEDURES['libc']['strncmp'] 17 | # angr.SIM_PROCEDURES['libc']['strcmp'] 18 | # angr.SIM_PROCEDURES['libc']['scanf'] 19 | # angr.SIM_PROCEDURES['libc']['printf'] 20 | # angr.SIM_PROCEDURES['libc']['puts'] 21 | # angr.SIM_PROCEDURES['libc']['exit'] 22 | # 23 | # As a reminder, you can hook functions with something similar to: 24 | # project.hook(malloc_address, angr.SIM_PROCEDURES['libc']['malloc']()) 25 | # 26 | # There are many more, see: 27 | # https://github.com/angr/angr/tree/master/angr/procedures/libc 28 | # 29 | # Additionally, note that, when the binary is executed, the main function is not 30 | # the first piece of code called. In the _start function, __libc_start_main is 31 | # called to start your program. The initialization that occurs in this function 32 | # can take a long time with Angr, so you should replace it with a SimProcedure. 33 | # angr.SIM_PROCEDURES['glibc']['__libc_start_main'] 34 | # Note 'glibc' instead of 'libc'. 35 | -------------------------------------------------------------------------------- /14_angr_shared_library/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate(argv): 5 | if len(argv) != 3: 6 | print('Usage: ./generate.py [seed] [output_file]') 7 | sys.exit() 8 | 9 | seed = argv[1] 10 | output_file = argv[2] 11 | random.seed(seed) 12 | 13 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 14 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 15 | 16 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '14_angr_shared_library_so.c.jinja'), 'r').read() 17 | t = jinja2.Template(template) 18 | c_code = t.render(description='', userdef=userdef, len_userdef=len(userdef)) 19 | 20 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 21 | temp.write(c_code) 22 | temp.seek(0) 23 | os.system('gcc -I' + os.path.dirname(os.path.realpath(__file__)) + ' -fno-stack-protector -fpic -m32 -c -o 14_angr_shared_library.o ' + temp.name) 24 | os.system('gcc -shared -m32 -o ' + os.path.join('/'.join(output_file.split('/')[0:-1]), 'lib' + output_file.split('/')[-1] + '.so') + ' 14_angr_shared_library.o') 25 | os.system('rm 14_angr_shared_library.o') 26 | os.system('chmod -x ' + os.path.join('/'.join(output_file.split('/')[0:-1]), 'lib' + output_file.split('/')[-1] + '.so')) 27 | 28 | c_code = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '14_angr_shared_library.c'), 'r').read() 29 | 30 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 31 | temp.write(c_code) 32 | temp.seek(0) 33 | os.system('gcc -fno-pie -no-pie -m32 -Wl,-R . -I . -L ' + '/'.join(output_file.split('/')[0:-1]) + ' -o ' + output_file + ' ' + temp.name + ' -l' + output_file.split('/')[-1]) 34 | 35 | if __name__ == '__main__': 36 | generate(sys.argv) 37 | -------------------------------------------------------------------------------- /01_angr_avoid/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def check_string_recursive(array0, array1, random_list, bit): 5 | if bit < 0: 6 | return f'maybe_good({array0}, {array1});' 7 | else: 8 | if random_list[0]: 9 | ret_str = f'if (CHECK_BIT({array0}, {bit}) == CHECK_BIT({array1}, {bit}))' + '{' + check_string_recursive(array0, array1, random_list[1:], bit-1) + '} else { avoid_me(); ' + check_string_recursive(array0, array1, random_list[1:], bit-1) + '}' 10 | else: 11 | ret_str = f'if (CHECK_BIT({array0}, {bit}) != CHECK_BIT({array1}, {bit}))' + '{ avoid_me();' + check_string_recursive(array0, array1, random_list[1:], bit-1) + '} else { ' + check_string_recursive(array0, array1, random_list[1:], bit-1) + '}' 12 | return ret_str 13 | 14 | def generate(argv): 15 | if len(argv) != 3: 16 | print('Usage: ./generate.py [seed] [output_file]') 17 | sys.exit() 18 | 19 | seed = argv[1] 20 | output_file = argv[2] 21 | random.seed(seed) 22 | 23 | description = '' 24 | 25 | random_list = [random.choice([True, False]) for _ in range(64)] 26 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 27 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 28 | random_list = [random.choice([True, False]) for _ in range(64)] 29 | check_string = check_string_recursive('buffer', 'password', random_list, 12) 30 | 31 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '01_angr_avoid.c.jinja'), 'r').read() 32 | t = jinja2.Template(template) 33 | c_code = t.render(userdef=userdef, len_userdef=len(userdef), description = '', check_string=check_string) 34 | 35 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 36 | temp.write(c_code) 37 | temp.seek(0) 38 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 39 | 40 | if __name__ == '__main__': 41 | generate(sys.argv) 42 | -------------------------------------------------------------------------------- /05_angr_symbolic_memory/scaffold05.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = ??? 10 | initial_state = project.factory.blank_state( 11 | addr=start_address, 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | 16 | # The binary is calling scanf("%8s %8s %8s %8s"). 17 | # (!) 18 | password0 = claripy.BVS('password0', ???) 19 | ... 20 | 21 | # Determine the address of the global variable to which scanf writes the user 22 | # input. The function 'initial_state.memory.store(address, value)' will write 23 | # 'value' (a bitvector) to 'address' (a memory location, as an integer.) The 24 | # 'address' parameter can also be a bitvector (and can be symbolic!). 25 | # (!) 26 | password0_address = ??? 27 | initial_state.memory.store(password0_address, password0) 28 | ... 29 | 30 | simulation = project.factory.simgr(initial_state) 31 | 32 | def is_successful(state): 33 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 34 | return ??? 35 | 36 | def should_abort(state): 37 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 38 | return ??? 39 | 40 | simulation.explore(find=is_successful, avoid=should_abort) 41 | 42 | if simulation.found: 43 | solution_state = simulation.found[0] 44 | 45 | # Solve for the symbolic values. We are trying to solve for a string. 46 | # Therefore, we will use eval, with named parameter cast_to=bytes 47 | # which returns bytes that can be decoded to a string instead of an integer. 48 | # (!) 49 | solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode() 50 | ... 51 | solution = ??? 52 | 53 | print(solution) 54 | else: 55 | raise Exception('Could not find the solution') 56 | 57 | if __name__ == '__main__': 58 | main(sys.argv) 59 | -------------------------------------------------------------------------------- /solutions/12_angr_veritesting/solve12.py: -------------------------------------------------------------------------------- 1 | # When you construct a simulation manager, you will want to enable Veritesting: 2 | # project.factory.simgr(initial_state, veritesting=True) 3 | # Hint: use one of the first few levels' solutions as a reference. 4 | 5 | import angr 6 | import sys 7 | 8 | def main(argv): 9 | path_to_binary = argv[1] 10 | project = angr.Project(path_to_binary) 11 | initial_state = project.factory.entry_state( 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | simulation = project.factory.simgr(initial_state, veritesting=True) 16 | 17 | # Define a function that checks if you have found the state you are looking 18 | # for. 19 | def is_successful(state): 20 | # Dump whatever has been printed out by the binary so far into a string. 21 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 22 | 23 | # Return whether 'Good Job.' has been printed yet. 24 | # (!) 25 | return 'Good Job.'.encode() in stdout_output # :boolean 26 | 27 | # Same as above, but this time check if the state should abort. If you return 28 | # False, Angr will continue to step the state. In this specific challenge, the 29 | # only time at which you will know you should abort is when the program prints 30 | # "Try again." 31 | def should_abort(state): 32 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 33 | return 'Try again.'.encode() in stdout_output # :boolean 34 | 35 | # Tell Angr to explore the binary and find any state that is_successful identfies 36 | # as a successful state by returning True. 37 | simulation.explore(find=is_successful, avoid=should_abort) 38 | 39 | if simulation.found: 40 | solution_state = simulation.found[0] 41 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 42 | else: 43 | raise Exception('Could not find the solution') 44 | 45 | if __name__ == '__main__': 46 | main(sys.argv) 47 | -------------------------------------------------------------------------------- /solutions/05_angr_symbolic_memory/scaffold05.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = ??? 10 | initial_state = project.factory.blank_state( 11 | addr=start_address, 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | 16 | # The binary is calling scanf("%8s %8s %8s %8s"). 17 | # (!) 18 | password0 = claripy.BVS('password0', ???) 19 | ... 20 | 21 | # Determine the address of the global variable to which scanf writes the user 22 | # input. The function 'initial_state.memory.store(address, value)' will write 23 | # 'value' (a bitvector) to 'address' (a memory location, as an integer.) The 24 | # 'address' parameter can also be a bitvector (and can be symbolic!). 25 | # (!) 26 | password0_address = ??? 27 | initial_state.memory.store(password0_address, password0) 28 | ... 29 | 30 | simulation = project.factory.simgr(initial_state) 31 | 32 | def is_successful(state): 33 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 34 | return ??? 35 | 36 | def should_abort(state): 37 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 38 | return ??? 39 | 40 | simulation.explore(find=is_successful, avoid=should_abort) 41 | 42 | if simulation.found: 43 | solution_state = simulation.found[0] 44 | 45 | # Solve for the symbolic values. We are trying to solve for a string. 46 | # Therefore, we will use eval, with named parameter cast_to=bytes 47 | # which returns bytes that can be decoded to a string instead of an integer. 48 | # (!) 49 | solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode() 50 | ... 51 | solution = ??? 52 | 53 | print(solution) 54 | else: 55 | raise Exception('Could not find the solution') 56 | 57 | if __name__ == '__main__': 58 | main(sys.argv) 59 | -------------------------------------------------------------------------------- /10_angr_simprocedures/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate_true_statement(variable, value): 5 | random_int = random.randint(0, 0xFFFFFFFF) 6 | value_xor_int = value ^ random_int 7 | return '(!(' + variable + ' ^ ' + str(random_int) + ' ^ ' + str(value_xor_int) + '))' 8 | 9 | def recursive_if_else(variable, value, end_statement, depth): 10 | if depth == 0: 11 | return end_statement 12 | else: 13 | if_true = random.choice([True, False]) 14 | if (if_true): 15 | ret_str = 'if (' + generate_true_statement(variable, value) + ') {' + recursive_if_else(variable, value, end_statement, depth - 1) + '} else {' + recursive_if_else(variable, value, end_statement, depth - 1) + '}' 16 | else: 17 | ret_str = 'if (!' + generate_true_statement(variable, value) + ') {' + recursive_if_else(variable, value, end_statement, depth - 1) + '} else {' + recursive_if_else(variable, value, end_statement, depth - 1) + '}' 18 | return ret_str 19 | 20 | def generate(argv): 21 | if len(argv) != 3: 22 | print('Usage: ./generate.py [seed] [output_file]') 23 | sys.exit() 24 | 25 | seed = argv[1] 26 | output_file = argv[2] 27 | random.seed(seed) 28 | 29 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 30 | userdef = ''.join([random.choice(userdef_charset) for _ in range(16)]) 31 | statement = f"equals = check_equals_{userdef}(buffer, 16);" 32 | recursive_if_else_string = recursive_if_else('x', 0xDEADBEEF, statement, 8) 33 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '10_angr_simprocedures.c.jinja'), 'r').read() 34 | t = jinja2.Template(template) 35 | c_code = t.render(description='', userdef=userdef, recursive_if_else=recursive_if_else_string) 36 | 37 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 38 | temp.write(c_code) 39 | temp.seek(0) 40 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 41 | 42 | if __name__ == '__main__': 43 | generate(sys.argv) 44 | -------------------------------------------------------------------------------- /15_angr_arbitrary_read/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import binascii, sys, random, os, tempfile, jinja2 3 | 4 | def expanded_switch_statement(variable, miss_statement, hit_statement, samples): 5 | target = random.choice(samples) 6 | 7 | ret_str = 'switch (%s) {' % (variable,) 8 | for sample in samples: 9 | ret_str += 'case %d: %s; break;' % (sample, hit_statement if sample == target else miss_statement) 10 | ret_str += 'default: %s; break; }' % (miss_statement,) 11 | return ret_str 12 | 13 | def generate(argv): 14 | if len(argv) != 3: 15 | print('Usage: ./generate.py [seed] [output_file]') 16 | sys.exit() 17 | 18 | seed = argv[1] 19 | output_file = argv[2] 20 | 21 | random.seed(seed) 22 | 23 | # cs492 branch 24 | # rodata_tail_modifier = 0x30 25 | # rodata_parts = ''.join([ chr(random.randint(ord('A'), ord('Z'))) for _ in xrange(3) ] + [ chr(random.randint(0,16) + rodata_tail_modifier) ]) 26 | rodata_tail_modifier = 0x14 27 | rodata_parts = ''.join([ chr(random.randint(ord('A'), ord('Z'))) for _ in range(3) ] 28 | + [ chr(random.randint(ord('A') - rodata_tail_modifier, ord('Z') - rodata_tail_modifier)) ]) 29 | rodata_address = '0x' + binascii.hexlify(rodata_parts.encode('utf8')).decode('utf8') 30 | 31 | hit_statement = 'puts(locals.to_print);' 32 | miss_statement = 'puts(try_again);' 33 | expanded_switch_statement_string = expanded_switch_statement('key', miss_statement, hit_statement, random.sample(range(2**26-1), 2)) 34 | 35 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '15_angr_arbitrary_read.c.jinja'), 'r').read() 36 | t = jinja2.Template(template) 37 | c_code = t.render(description='', expanded_switch_statement=expanded_switch_statement_string) 38 | 39 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 40 | temp.write(c_code) 41 | temp.seek(0) 42 | os.system('gcc -fno-pie -no-pie -m32 -fno-stack-protector -Wl,--section-start=.rodata=' + rodata_address + ' -o ' + output_file + ' ' + temp.name) 43 | 44 | if __name__ == '__main__': 45 | generate(sys.argv) 46 | -------------------------------------------------------------------------------- /02_angr_find_condition/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate_true_statement(variable, value): 5 | random_int = random.randint(0, 0xFFFFFFFF) 6 | value_xor_int = value ^ random_int 7 | return '(!(' + variable + ' ^ ' + str(random_int) + ' ^ ' + str(value_xor_int) + '))' 8 | 9 | def recursive_if_else(variable, value, depth): 10 | if depth == 0: 11 | return 'if (strcmp(buffer, password)) { printf(\"Try again.\\n\"); } else { printf(\"Good Job.\\n\"); }' 12 | else: 13 | if_true = random.choice([True, False]) 14 | if (if_true): 15 | ret_str = 'if (' + generate_true_statement(variable, value) + ') {' + recursive_if_else(variable, value, depth - 1) + '} else {' + recursive_if_else(variable, value, depth - 1) + '}' 16 | else: 17 | ret_str = 'if (!' + generate_true_statement(variable, value) + ') {' + recursive_if_else(variable, value, depth - 1) + '} else {' + recursive_if_else(variable, value, depth - 1) + '}' 18 | return ret_str 19 | 20 | def generate(argv): 21 | if len(argv) != 3: 22 | print('Usage: ./generate.py [seed] [output_file]') 23 | sys.exit() 24 | 25 | seed = argv[1] 26 | output_file = argv[2] 27 | random.seed(seed) 28 | 29 | random_list = [random.choice([True, False]) for _ in range(64)] 30 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 31 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 32 | random_list = [random.choice([True, False]) for _ in range(64)] 33 | recursive_if_else_string = recursive_if_else('x', 0xDEADBEEF, 8) 34 | 35 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '02_angr_find_condition.c.jinja'), 'r').read() 36 | t = jinja2.Template(template) 37 | c_code = t.render(userdef=userdef, len_userdef=len(userdef), description = '', recursive_if_else=recursive_if_else_string) 38 | 39 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 40 | temp.write(c_code) 41 | temp.seek(0) 42 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 43 | 44 | if __name__ == '__main__': 45 | generate(sys.argv) 46 | -------------------------------------------------------------------------------- /dist/scaffold11.py: -------------------------------------------------------------------------------- 1 | # This time, the solution involves simply replacing scanf with our own version, 2 | # since Angr does not support requesting multiple parameters with scanf. 3 | 4 | import angr 5 | import claripy 6 | import sys 7 | 8 | def main(argv): 9 | path_to_binary = argv[1] 10 | project = angr.Project(path_to_binary) 11 | 12 | initial_state = project.factory.entry_state() 13 | 14 | class ReplacementScanf(angr.SimProcedure): 15 | # Finish the parameters to the scanf function. Hint: 'scanf("%u %u", ...)'. 16 | # (!) 17 | def run(self, format_string, scanf0_address, ...): 18 | scanf0 = claripy.BVS('scanf0', ???) 19 | ... 20 | 21 | # The scanf function writes user input to the buffers to which the 22 | # parameters point. 23 | self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) 24 | ... 25 | 26 | # Now, we want to 'set aside' references to our symbolic values in the 27 | # globals plugin included by default with a state. You will need to 28 | # store multiple bitvectors. You can either use a list, tuple, or multiple 29 | # keys to reference the different bitvectors. 30 | # (!) 31 | self.state.globals['solution0'] = ??? 32 | self.state.globals['solution1'] = ??? 33 | 34 | scanf_symbol = ??? 35 | project.hook_symbol(scanf_symbol, ReplacementScanf()) 36 | 37 | simulation = project.factory.simgr(initial_state) 38 | 39 | def is_successful(state): 40 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 41 | return ??? 42 | 43 | def should_abort(state): 44 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 45 | return ??? 46 | 47 | simulation.explore(find=is_successful, avoid=should_abort) 48 | 49 | if simulation.found: 50 | solution_state = simulation.found[0] 51 | 52 | # Grab whatever you set aside in the globals dict. 53 | stored_solutions0 = solution_state.globals['solution0'] 54 | ... 55 | solution = ??? 56 | 57 | print(solution) 58 | else: 59 | raise Exception('Could not find the solution') 60 | 61 | if __name__ == '__main__': 62 | main(sys.argv) 63 | -------------------------------------------------------------------------------- /dist/scaffold02.py: -------------------------------------------------------------------------------- 1 | # It is very useful to be able to search for a state that reaches a certain 2 | # instruction. However, in some cases, you may not know the address of the 3 | # specific instruction you want to reach (or perhaps there is no single 4 | # instruction goal.) In this challenge, you don't know which instruction 5 | # grants you success. Instead, you just know that you want to find a state where 6 | # the binary prints "Good Job." 7 | # 8 | # Angr is powerful in that it allows you to search for a states that meets an 9 | # arbitrary condition that you specify in Python, using a predicate you define 10 | # as a function that takes a state and returns True if you have found what you 11 | # are looking for, and False otherwise. 12 | 13 | import angr 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | initial_state = project.factory.entry_state() 20 | simulation = project.factory.simgr(initial_state) 21 | 22 | # Define a function that checks if you have found the state you are looking 23 | # for. 24 | def is_successful(state): 25 | # Dump whatever has been printed out by the binary so far into a string. 26 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 27 | 28 | # Return whether 'Good Job.' has been printed yet. 29 | # (!) 30 | return ??? # :boolean 31 | 32 | # Same as above, but this time check if the state should abort. If you return 33 | # False, Angr will continue to step the state. In this specific challenge, the 34 | # only time at which you will know you should abort is when the program prints 35 | # "Try again." 36 | def should_abort(state): 37 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 38 | return ??? # :boolean 39 | 40 | # Tell Angr to explore the binary and find any state that is_successful identfies 41 | # as a successful state by returning True. 42 | simulation.explore(find=is_successful, avoid=should_abort) 43 | 44 | if simulation.found: 45 | solution_state = simulation.found[0] 46 | print(solution_state.posix.dumps(sys.stdin.fileno())) 47 | else: 48 | raise Exception('Could not find the solution') 49 | 50 | if __name__ == '__main__': 51 | main(sys.argv) 52 | -------------------------------------------------------------------------------- /dist/scaffold06.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = ??? 10 | initial_state = project.factory.blank_state(addr=start_address) 11 | 12 | # The binary is calling scanf("%8s %8s"). 13 | # (!) 14 | password0 = claripy.BVS('password0', ???) 15 | ... 16 | 17 | # Instead of telling the binary to write to the address of the memory 18 | # allocated with malloc, we can simply fake an address to any unused block of 19 | # memory and overwrite the pointer to the data. This will point the pointer 20 | # with the address of pointer_to_malloc_memory_address0 to fake_heap_address. 21 | # Be aware, there is more than one pointer! Analyze the binary to determine 22 | # global location of each pointer. 23 | # Note: by default, Angr stores integers in memory with big-endianness. To 24 | # specify to use the endianness of your architecture, use the parameter 25 | # endness=project.arch.memory_endness. On x86, this is little-endian. 26 | # (!) 27 | fake_heap_address0 = ??? 28 | pointer_to_malloc_memory_address0 = ??? 29 | initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness) 30 | ... 31 | 32 | # Store our symbolic values at our fake_heap_address. Look at the binary to 33 | # determine the offsets from the fake_heap_address where scanf writes. 34 | # (!) 35 | initial_state.memory.store(fake_heap_address0, password0) 36 | ... 37 | 38 | simulation = project.factory.simgr(initial_state) 39 | 40 | def is_successful(state): 41 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 42 | return ??? 43 | 44 | def should_abort(state): 45 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 46 | return ??? 47 | 48 | simulation.explore(find=is_successful, avoid=should_abort) 49 | 50 | if simulation.found: 51 | solution_state = simulation.found[0] 52 | 53 | solution0 = solution_state.se.eval(password0,cast_to=str) 54 | ... 55 | solution = ??? 56 | 57 | print(solution) 58 | else: 59 | raise Exception('Could not find the solution') 60 | 61 | if __name__ == '__main__': 62 | main(sys.argv) 63 | -------------------------------------------------------------------------------- /16_angr_arbitrary_write/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import binascii, sys, random, os, tempfile, jinja2 3 | 4 | def expanded_switch_statement(variable, miss_statement, hit_statement, samples): 5 | target = random.choice(samples) 6 | 7 | ret_str = 'switch (%s) {' % (variable,) 8 | for sample in samples: 9 | ret_str += 'case %d: %s; break;' % (sample, hit_statement if sample == target else miss_statement) 10 | ret_str += 'default: %s; break; }' % (miss_statement,) 11 | return ret_str 12 | 13 | def generate(argv): 14 | if len(argv) != 3: 15 | print('Usage: ./generate.py [seed] [output_file]') 16 | sys.exit() 17 | 18 | seed = argv[1] 19 | output_file = argv[2] 20 | random.seed(seed) 21 | 22 | # cs492 23 | # rodata_tail_modifier = 0x15 24 | # rodata_tail_modifier = 0x10 25 | # rodata_parts = ''.join([ chr(random.randint(ord('A'), ord('Z'))) for _ in xrange(3) ] + [ chr(random.randint(0,4) + rodata_tail_modifier) ]) 26 | rodata_tail_modifier = 0x2c 27 | rodata_parts = ''.join([ chr(random.randint(ord('A'), ord('Z'))) for _ in range(3) ] 28 | + [ chr(random.randint(ord('A') - rodata_tail_modifier, ord('Z') - rodata_tail_modifier)) ]) 29 | rodata_address = '0x' + binascii.hexlify(rodata_parts.encode('utf8')).decode('utf8') 30 | 31 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 32 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 33 | hit_statement = 'strncpy(locals.to_copy_to, locals.buffer, 16)' 34 | miss_statement = 'strncpy(unimportant_buffer, locals.buffer, 16)' 35 | expanded_switch_statement_string = expanded_switch_statement('key', miss_statement, hit_statement, random.sample(range(2**26-1), 2)) 36 | 37 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '16_angr_arbitrary_write.c.jinja'), 'r').read() 38 | t = jinja2.Template(template) 39 | c_code = t.render(description='', userdef=userdef, expanded_switch_statement=expanded_switch_statement_string) 40 | 41 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 42 | temp.write(c_code) 43 | temp.seek(0) 44 | os.system('gcc -fno-pie -no-pie -m32 -fno-stack-protector -Wl,--section-start=.data=' + rodata_address + ' -o ' + output_file + ' ' + temp.name) 45 | 46 | if __name__ == '__main__': 47 | generate(sys.argv) 48 | -------------------------------------------------------------------------------- /11_angr_sim_scanf/scaffold11.py: -------------------------------------------------------------------------------- 1 | # This time, the solution involves simply replacing scanf with our own version, 2 | # since Angr does not support requesting multiple parameters with scanf. 3 | 4 | import angr 5 | import claripy 6 | import sys 7 | 8 | def main(argv): 9 | path_to_binary = argv[1] 10 | project = angr.Project(path_to_binary) 11 | 12 | initial_state = project.factory.entry_state( 13 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 14 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 15 | ) 16 | 17 | class ReplacementScanf(angr.SimProcedure): 18 | # Finish the parameters to the scanf function. Hint: 'scanf("%u %u", ...)'. 19 | # (!) 20 | def run(self, format_string, scanf0_address, ...): 21 | # Hint: scanf0_address is passed as a parameter, isn't it? 22 | scanf0 = claripy.BVS('scanf0', ???) 23 | ... 24 | 25 | # The scanf function writes user input to the buffers to which the 26 | # parameters point. 27 | self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) 28 | ... 29 | 30 | # Now, we want to 'set aside' references to our symbolic values in the 31 | # globals plugin included by default with a state. You will need to 32 | # store multiple bitvectors. You can either use a list, tuple, or multiple 33 | # keys to reference the different bitvectors. 34 | # (!) 35 | self.state.globals['solution0'] = ??? 36 | ... 37 | 38 | scanf_symbol = ??? 39 | project.hook_symbol(scanf_symbol, ReplacementScanf()) 40 | 41 | simulation = project.factory.simgr(initial_state) 42 | 43 | def is_successful(state): 44 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 45 | return ??? 46 | 47 | def should_abort(state): 48 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 49 | return ??? 50 | 51 | simulation.explore(find=is_successful, avoid=should_abort) 52 | 53 | if simulation.found: 54 | solution_state = simulation.found[0] 55 | 56 | # Grab whatever you set aside in the globals dict. 57 | stored_solutions0 = solution_state.globals['solution0'] 58 | ... 59 | solution = ??? 60 | 61 | print(solution) 62 | else: 63 | raise Exception('Could not find the solution') 64 | 65 | if __name__ == '__main__': 66 | main(sys.argv) 67 | -------------------------------------------------------------------------------- /solutions/11_angr_sim_scanf/scaffold11.py: -------------------------------------------------------------------------------- 1 | # This time, the solution involves simply replacing scanf with our own version, 2 | # since Angr does not support requesting multiple parameters with scanf. 3 | 4 | import angr 5 | import claripy 6 | import sys 7 | 8 | def main(argv): 9 | path_to_binary = argv[1] 10 | project = angr.Project(path_to_binary) 11 | 12 | initial_state = project.factory.entry_state( 13 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 14 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 15 | ) 16 | 17 | class ReplacementScanf(angr.SimProcedure): 18 | # Finish the parameters to the scanf function. Hint: 'scanf("%u %u", ...)'. 19 | # (!) 20 | def run(self, format_string, scanf0_address, ...): 21 | # Hint: scanf0_address is passed as a parameter, isn't it? 22 | scanf0 = claripy.BVS('scanf0', ???) 23 | ... 24 | 25 | # The scanf function writes user input to the buffers to which the 26 | # parameters point. 27 | self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) 28 | ... 29 | 30 | # Now, we want to 'set aside' references to our symbolic values in the 31 | # globals plugin included by default with a state. You will need to 32 | # store multiple bitvectors. You can either use a list, tuple, or multiple 33 | # keys to reference the different bitvectors. 34 | # (!) 35 | self.state.globals['solution0'] = ??? 36 | ... 37 | 38 | scanf_symbol = ??? 39 | project.hook_symbol(scanf_symbol, ReplacementScanf()) 40 | 41 | simulation = project.factory.simgr(initial_state) 42 | 43 | def is_successful(state): 44 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 45 | return ??? 46 | 47 | def should_abort(state): 48 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 49 | return ??? 50 | 51 | simulation.explore(find=is_successful, avoid=should_abort) 52 | 53 | if simulation.found: 54 | solution_state = simulation.found[0] 55 | 56 | # Grab whatever you set aside in the globals dict. 57 | stored_solutions0 = solution_state.globals['solution0'] 58 | ... 59 | solution = ??? 60 | 61 | print(solution) 62 | else: 63 | raise Exception('Could not find the solution') 64 | 65 | if __name__ == '__main__': 66 | main(sys.argv) 67 | -------------------------------------------------------------------------------- /11_angr_sim_scanf/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, random, os, tempfile, jinja2 3 | 4 | def generate_true_statement(variable, value): 5 | random_int = random.randint(0, 0xFFFFFFFF) 6 | value_xor_int = value ^ random_int 7 | return '(!(' + variable + ' ^ ' + str(random_int) + ' ^ ' + str(value_xor_int) + '))' 8 | 9 | def recursive_if_else(variable, value, end_statement, depth): 10 | if depth == 0: 11 | return end_statement 12 | else: 13 | if_true = random.choice([True, False]) 14 | if (if_true): 15 | ret_str = 'if (' + generate_true_statement(variable, value) + ') {' + recursive_if_else(variable, value, end_statement, depth - 1) + '} else {' + recursive_if_else(variable, value, end_statement, depth - 1) + '}' 16 | else: 17 | ret_str = 'if (!' + generate_true_statement(variable, value) + ') {' + recursive_if_else(variable, value, end_statement, depth - 1) + '} else {' + recursive_if_else(variable, value, end_statement, depth - 1) + '}' 18 | return ret_str 19 | 20 | def generate(argv): 21 | if len(argv) != 3: 22 | print('Usage: ./generate.py [seed] [output_file]') 23 | sys.exit() 24 | 25 | seed = argv[1] 26 | output_file = argv[2] 27 | random.seed(seed) 28 | 29 | userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 30 | userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) 31 | padding0 = random.randint(0, 2**16) 32 | padding1 = random.randint(0, 2**16) 33 | padding2 = random.randint(0, 2**16) 34 | 35 | statement = """ 36 | scanf("%u %u", (uint32_t*) buffer0, (uint32_t*) buffer1); 37 | keep_going = keep_going && !strncmp(buffer0, &password[0], 4) && !strncmp(buffer1, &password[4], 4); 38 | """ 39 | recursive_if_else_string = recursive_if_else('x', 0xDEADBEEF, statement, 8) 40 | 41 | template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '11_angr_sim_scanf.c.jinja'), 'r').read() 42 | t = jinja2.Template(template) 43 | c_code = t.render(description='', userdef=userdef, len_userdef=len(userdef), padding0=padding0, padding1=padding1, padding2=padding2, recursive_if_else=recursive_if_else_string) 44 | 45 | with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp: 46 | temp.write(c_code) 47 | temp.seek(0) 48 | os.system('gcc -fno-pie -no-pie -m32 -o ' + output_file + ' ' + temp.name) 49 | 50 | if __name__ == '__main__': 51 | generate(sys.argv) 52 | -------------------------------------------------------------------------------- /02_angr_find_condition/scaffold02.py: -------------------------------------------------------------------------------- 1 | # It is very useful to be able to search for a state that reaches a certain 2 | # instruction. However, in some cases, you may not know the address of the 3 | # specific instruction you want to reach (or perhaps there is no single 4 | # instruction goal.) In this challenge, you don't know which instruction 5 | # grants you success. Instead, you just know that you want to find a state where 6 | # the binary prints "Good Job." 7 | # 8 | # Angr is powerful in that it allows you to search for a states that meets an 9 | # arbitrary condition that you specify in Python, using a predicate you define 10 | # as a function that takes a state and returns True if you have found what you 11 | # are looking for, and False otherwise. 12 | 13 | import angr 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | initial_state = project.factory.entry_state( 20 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 21 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 22 | ) 23 | simulation = project.factory.simgr(initial_state) 24 | 25 | # Define a function that checks if you have found the state you are looking 26 | # for. 27 | def is_successful(state): 28 | # Dump whatever has been printed out by the binary so far into a string. 29 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 30 | 31 | # Return whether 'Good Job.' has been printed yet. 32 | # (!) 33 | return ??? # :boolean 34 | 35 | # Same as above, but this time check if the state should abort. If you return 36 | # False, Angr will continue to step the state. In this specific challenge, the 37 | # only time at which you will know you should abort is when the program prints 38 | # "Try again." 39 | def should_abort(state): 40 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 41 | return ??? # :boolean 42 | 43 | # Tell Angr to explore the binary and find any state that is_successful identfies 44 | # as a successful state by returning True. 45 | simulation.explore(find=is_successful, avoid=should_abort) 46 | 47 | if simulation.found: 48 | solution_state = simulation.found[0] 49 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 50 | else: 51 | raise Exception('Could not find the solution') 52 | 53 | if __name__ == '__main__': 54 | main(sys.argv) 55 | -------------------------------------------------------------------------------- /solutions/02_angr_find_condition/scaffold02.py: -------------------------------------------------------------------------------- 1 | # It is very useful to be able to search for a state that reaches a certain 2 | # instruction. However, in some cases, you may not know the address of the 3 | # specific instruction you want to reach (or perhaps there is no single 4 | # instruction goal.) In this challenge, you don't know which instruction 5 | # grants you success. Instead, you just know that you want to find a state where 6 | # the binary prints "Good Job." 7 | # 8 | # Angr is powerful in that it allows you to search for a states that meets an 9 | # arbitrary condition that you specify in Python, using a predicate you define 10 | # as a function that takes a state and returns True if you have found what you 11 | # are looking for, and False otherwise. 12 | 13 | import angr 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | initial_state = project.factory.entry_state( 20 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 21 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 22 | ) 23 | simulation = project.factory.simgr(initial_state) 24 | 25 | # Define a function that checks if you have found the state you are looking 26 | # for. 27 | def is_successful(state): 28 | # Dump whatever has been printed out by the binary so far into a string. 29 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 30 | 31 | # Return whether 'Good Job.' has been printed yet. 32 | # (!) 33 | return ??? # :boolean 34 | 35 | # Same as above, but this time check if the state should abort. If you return 36 | # False, Angr will continue to step the state. In this specific challenge, the 37 | # only time at which you will know you should abort is when the program prints 38 | # "Try again." 39 | def should_abort(state): 40 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 41 | return ??? # :boolean 42 | 43 | # Tell Angr to explore the binary and find any state that is_successful identfies 44 | # as a successful state by returning True. 45 | simulation.explore(find=is_successful, avoid=should_abort) 46 | 47 | if simulation.found: 48 | solution_state = simulation.found[0] 49 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 50 | else: 51 | raise Exception('Could not find the solution') 52 | 53 | if __name__ == '__main__': 54 | main(sys.argv) 55 | -------------------------------------------------------------------------------- /06_angr_symbolic_dynamic_memory/scaffold06.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = ??? 10 | initial_state = project.factory.blank_state( 11 | addr=start_address, 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | 16 | # The binary is calling scanf("%8s %8s"). 17 | # (!) 18 | password0 = claripy.BVS('password0', ???) 19 | ... 20 | 21 | # Instead of telling the binary to write to the address of the memory 22 | # allocated with malloc, we can simply fake an address to any unused block of 23 | # memory and overwrite the pointer to the data. This will point the pointer 24 | # with the address of pointer_to_malloc_memory_address0 to fake_heap_address. 25 | # Be aware, there is more than one pointer! Analyze the binary to determine 26 | # global location of each pointer. 27 | # Note: by default, Angr stores integers in memory with big-endianness. To 28 | # specify to use the endianness of your architecture, use the parameter 29 | # endness=project.arch.memory_endness. On x86, this is little-endian. 30 | # (!) 31 | fake_heap_address0 = ??? 32 | pointer_to_malloc_memory_address0 = ??? 33 | initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness) 34 | ... 35 | 36 | # Store our symbolic values at our fake_heap_address. Look at the binary to 37 | # determine the offsets from the fake_heap_address where scanf writes. 38 | # (!) 39 | initial_state.memory.store(fake_heap_address0, password0) 40 | ... 41 | 42 | simulation = project.factory.simgr(initial_state) 43 | 44 | def is_successful(state): 45 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 46 | return ??? 47 | 48 | def should_abort(state): 49 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 50 | return ??? 51 | 52 | simulation.explore(find=is_successful, avoid=should_abort) 53 | 54 | if simulation.found: 55 | solution_state = simulation.found[0] 56 | 57 | solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode() 58 | ... 59 | solution = ??? 60 | 61 | print(solution) 62 | else: 63 | raise Exception('Could not find the solution') 64 | 65 | if __name__ == '__main__': 66 | main(sys.argv) 67 | -------------------------------------------------------------------------------- /solutions/06_angr_symbolic_dynamic_memory/scaffold06.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = ??? 10 | initial_state = project.factory.blank_state( 11 | addr=start_address, 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | 16 | # The binary is calling scanf("%8s %8s"). 17 | # (!) 18 | password0 = claripy.BVS('password0', ???) 19 | ... 20 | 21 | # Instead of telling the binary to write to the address of the memory 22 | # allocated with malloc, we can simply fake an address to any unused block of 23 | # memory and overwrite the pointer to the data. This will point the pointer 24 | # with the address of pointer_to_malloc_memory_address0 to fake_heap_address. 25 | # Be aware, there is more than one pointer! Analyze the binary to determine 26 | # global location of each pointer. 27 | # Note: by default, Angr stores integers in memory with big-endianness. To 28 | # specify to use the endianness of your architecture, use the parameter 29 | # endness=project.arch.memory_endness. On x86, this is little-endian. 30 | # (!) 31 | fake_heap_address0 = ??? 32 | pointer_to_malloc_memory_address0 = ??? 33 | initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness) 34 | ... 35 | 36 | # Store our symbolic values at our fake_heap_address. Look at the binary to 37 | # determine the offsets from the fake_heap_address where scanf writes. 38 | # (!) 39 | initial_state.memory.store(fake_heap_address0, password0) 40 | ... 41 | 42 | simulation = project.factory.simgr(initial_state) 43 | 44 | def is_successful(state): 45 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 46 | return ??? 47 | 48 | def should_abort(state): 49 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 50 | return ??? 51 | 52 | simulation.explore(find=is_successful, avoid=should_abort) 53 | 54 | if simulation.found: 55 | solution_state = simulation.found[0] 56 | 57 | solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode() 58 | ... 59 | solution = ??? 60 | 61 | print(solution) 62 | else: 63 | raise Exception('Could not find the solution') 64 | 65 | if __name__ == '__main__': 66 | main(sys.argv) 67 | -------------------------------------------------------------------------------- /solutions/02_angr_find_condition/solve02.py: -------------------------------------------------------------------------------- 1 | # It is very useful to be able to search for a state that reaches a certain 2 | # instruction. However, in some cases, you may not know the address of the 3 | # specific instruction you want to reach (or perhaps there is no single 4 | # instruction goal.) In this challenge, you don't know which instruction 5 | # grants you success. Instead, you just know that you want to find a state where 6 | # the binary prints "Good Job." 7 | # 8 | # Angr is powerful in that it allows you to search for a states that meets an 9 | # arbitrary condition that you specify in Python, using a predicate you define 10 | # as a function that takes a state and returns True if you have found what you 11 | # are looking for, and False otherwise. 12 | 13 | import angr 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | initial_state = project.factory.entry_state( 20 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 21 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 22 | ) 23 | simulation = project.factory.simgr(initial_state) 24 | 25 | # Define a function that checks if you have found the state you are looking 26 | # for. 27 | def is_successful(state): 28 | # Dump whatever has been printed out by the binary so far into a string. 29 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 30 | 31 | # Return whether 'Good Job.' has been printed yet. 32 | # (!) 33 | return 'Good Job.'.encode() in stdout_output # :boolean 34 | 35 | # Same as above, but this time check if the state should abort. If you return 36 | # False, Angr will continue to step the state. In this specific challenge, the 37 | # only time at which you will know you should abort is when the program prints 38 | # "Try again." 39 | def should_abort(state): 40 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 41 | return 'Try again.'.encode() in stdout_output # :boolean 42 | 43 | # Tell Angr to explore the binary and find any state that is_successful identfies 44 | # as a successful state by returning True. 45 | simulation.explore(find=is_successful, avoid=should_abort) 46 | 47 | if simulation.found: 48 | solution_state = simulation.found[0] 49 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 50 | else: 51 | raise Exception('Could not find the solution') 52 | 53 | if __name__ == '__main__': 54 | main(sys.argv) 55 | -------------------------------------------------------------------------------- /solutions/11_angr_sim_scanf/solve11.py: -------------------------------------------------------------------------------- 1 | # This time, the solution involves simply replacing scanf with our own version, 2 | # since Angr does not support requesting multiple parameters with scanf. 3 | 4 | import angr 5 | import claripy 6 | import sys 7 | 8 | def main(argv): 9 | path_to_binary = argv[1] 10 | project = angr.Project(path_to_binary) 11 | 12 | initial_state = project.factory.entry_state( 13 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 14 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 15 | ) 16 | 17 | class ReplacementScanf(angr.SimProcedure): 18 | # Finish the parameters to the scanf function. Hint: 'scanf("%u %u", ...)'. 19 | # (!) 20 | def run(self, format_string, scanf0_address, scanf1_address): 21 | # Hint: scanf0_address is passed as a parameter, isn't it? 22 | scanf0 = claripy.BVS('scanf0', 32) 23 | scanf1 = claripy.BVS('scanf1', 32) 24 | 25 | # The scanf function writes user input to the buffers to which the 26 | # parameters point. 27 | self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) 28 | self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness) 29 | 30 | # Now, we want to 'set aside' references to our symbolic values in the 31 | # globals plugin included by default with a state. You will need to 32 | # store multiple bitvectors. You can either use a list, tuple, or multiple 33 | # keys to reference the different bitvectors. 34 | # (!) 35 | self.state.globals['solution0'] = scanf0 36 | self.state.globals['solution1'] = scanf1 37 | 38 | scanf_symbol = '__isoc99_scanf' 39 | project.hook_symbol(scanf_symbol, ReplacementScanf()) 40 | 41 | simulation = project.factory.simgr(initial_state) 42 | 43 | def is_successful(state): 44 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 45 | return 'Good Job.'.encode() in stdout_output 46 | 47 | def should_abort(state): 48 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 49 | return 'Try again.'.encode() in stdout_output 50 | 51 | simulation.explore(find=is_successful, avoid=should_abort) 52 | 53 | if simulation.found: 54 | solution_state = simulation.found[0] 55 | 56 | # Grab whatever you set aside in the globals dict. 57 | stored_solutions0 = solution_state.globals['solution0'] 58 | stored_solutions1 = solution_state.globals['solution1'] 59 | solution = f'{solution_state.solver.eval(stored_solutions0)} {solution_state.solver.eval(stored_solutions1)}' 60 | print(solution) 61 | else: 62 | raise Exception('Could not find the solution') 63 | 64 | if __name__ == '__main__': 65 | main(sys.argv) 66 | -------------------------------------------------------------------------------- /solutions/05_angr_symbolic_memory/solve05.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = 0x8048618 10 | initial_state = project.factory.blank_state( 11 | addr=start_address, 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | 16 | # The binary is calling scanf("%8s %8s %8s %8s"). 17 | # (!) 18 | password0 = claripy.BVS('password0', 8*8) 19 | password1 = claripy.BVS('password1', 8*8) 20 | password2 = claripy.BVS('password2', 8*8) 21 | password3 = claripy.BVS('password3', 8*8) 22 | 23 | # Determine the address of the global variable to which scanf writes the user 24 | # input. The function 'initial_state.memory.store(address, value)' will write 25 | # 'value' (a bitvector) to 'address' (a memory location, as an integer.) The 26 | # 'address' parameter can also be a bitvector (and can be symbolic!). 27 | # (!) 28 | password0_address = 0xab232c0 29 | initial_state.memory.store(password0_address, password0) 30 | password1_address = 0xab232c8 31 | initial_state.memory.store(password1_address, password1) 32 | password2_address = 0xab232d0 33 | initial_state.memory.store(password2_address, password2) 34 | password3_address = 0xab232d8 35 | initial_state.memory.store(password3_address, password3) 36 | 37 | 38 | simulation = project.factory.simgr(initial_state) 39 | 40 | def is_successful(state): 41 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 42 | return 'Good Job.'.encode() in stdout_output 43 | 44 | def should_abort(state): 45 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 46 | return 'Try again.'.encode() in stdout_output 47 | 48 | simulation.explore(find=is_successful, avoid=should_abort) 49 | 50 | if simulation.found: 51 | solution_state = simulation.found[0] 52 | 53 | # Solve for the symbolic values. We are trying to solve for a string. 54 | # Therefore, we will use eval, with named parameter cast_to=bytes 55 | # which returns bytes that can be decoded to a string instead of an integer. 56 | # (!) 57 | solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode() 58 | solution1 = solution_state.solver.eval(password1,cast_to=bytes).decode() 59 | solution2 = solution_state.solver.eval(password2,cast_to=bytes).decode() 60 | solution3 = solution_state.solver.eval(password3,cast_to=bytes).decode() 61 | 62 | solution = ' '.join([ solution0, solution1, solution2, solution3 ]) 63 | 64 | print(solution) 65 | else: 66 | raise Exception('Could not find the solution') 67 | 68 | if __name__ == '__main__': 69 | main(sys.argv) 70 | -------------------------------------------------------------------------------- /dist/scaffold14.py: -------------------------------------------------------------------------------- 1 | # The shared library has the function validate, which takes a string and returns 2 | # either true (1) or false (0). The binary calls this function. If it returns 3 | # true, the program prints "Good Job." otherwise, it prints "Try again." 4 | # 5 | # Note: When you run this script, make sure you run it on 6 | # lib14_angr_shared_library.so, not the executable. This level is intended to 7 | # teach how to analyse binary formats that are not typical executables. 8 | 9 | import angr 10 | import claripy 11 | import sys 12 | 13 | def main(argv): 14 | path_to_binary = ??? 15 | 16 | # The shared library is compiled with position-independent code. You will need 17 | # to specify the base address. All addresses in the shared library will be 18 | # base + offset, where offset is their address in the file. 19 | # (!) 20 | base = ??? 21 | project = angr.Project(path_to_binary, load_options={ 22 | 'main_opts' : { 23 | 'custom_base_addr' : base 24 | } 25 | }) 26 | 27 | # Initialize any symbolic values here; you will need at least one to pass to 28 | # the validate function. 29 | ... 30 | 31 | # Begin the state at the beginning of the validate function, as if it was 32 | # called by the program. Determine the parameters needed to call validate and 33 | # replace 'parameters...' with bitvectors holding the values you wish to pass. 34 | # Recall that 'claripy.BVV(value, size_in_bits)' constructs a bitvector 35 | # initialized to a single value. 36 | # Remember to add the base value you specified at the beginning to the 37 | # function address! 38 | # Hint: int validate(char* buffer, int length) { ... 39 | # Another hint: the password is 8 bytes long. 40 | # (!) 41 | validate_function_address = ??? 42 | initial_state = project.factory.call_state(validate_function_address, parameters...) 43 | 44 | # You will need to add code to inject a symbolic value into the program at the 45 | # end of the function that constrains eax to equal true (value of 1) just 46 | # before the function returns. There are multiple ways to do this: 47 | # 1. Use a hook. 48 | # 2. Search for the address just before the function returns and then 49 | # constrain eax (this may require putting code elsewhere) 50 | ... 51 | 52 | simulation = project.factory.simgr(initial_state) 53 | 54 | success_address = ??? 55 | simulation.explore(find=success_address) 56 | 57 | if simulation.found: 58 | solution_state = simulation.found[0] 59 | 60 | # Determine where the program places the return value, and constrain it so 61 | # that it is true. Then, solve for the solution and print it. 62 | # (!) 63 | solution = ??? 64 | print(solution) 65 | else: 66 | raise Exception('Could not find the solution') 67 | 68 | if __name__ == '__main__': 69 | main(sys.argv) 70 | -------------------------------------------------------------------------------- /solutions/06_angr_symbolic_dynamic_memory/solve06.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | import sys 4 | 5 | def main(argv): 6 | path_to_binary = argv[1] 7 | project = angr.Project(path_to_binary) 8 | 9 | start_address = 0x80486af 10 | initial_state = project.factory.blank_state( 11 | addr=start_address, 12 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 13 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 14 | ) 15 | 16 | # The binary is calling scanf("%8s %8s"). 17 | # (!) 18 | password0 = claripy.BVS('password0', 8*8) 19 | password1 = claripy.BVS('password0', 8*8) 20 | 21 | # Instead of telling the binary to write to the address of the memory 22 | # allocated with malloc, we can simply fake an address to any unused block of 23 | # memory and overwrite the pointer to the data. This will point the pointer 24 | # with the address of pointer_to_malloc_memory_address0 to fake_heap_address. 25 | # Be aware, there is more than one pointer! Analyze the binary to determine 26 | # global location of each pointer. 27 | # Note: by default, Angr stores integers in memory with big-endianness. To 28 | # specify to use the endianness of your architecture, use the parameter 29 | # endness=project.arch.memory_endness. On x86, this is little-endian. 30 | # size=number of bytes being stored (e.g. 32-bit address = 4 bytes) 31 | # (!) 32 | fake_heap_address0 = 0x4444444 33 | pointer_to_malloc_memory_address0 = 0xa2def74 34 | initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness, size=4) 35 | fake_heap_address1 = 0x4444454 36 | pointer_to_malloc_memory_address1 = 0xa2def7c 37 | initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness, size=4) 38 | 39 | # Store our symbolic values at our fake_heap_address. Look at the binary to 40 | # determine the offsets from the fake_heap_address where scanf writes. 41 | # (!) 42 | initial_state.memory.store(fake_heap_address0, password0) 43 | initial_state.memory.store(fake_heap_address1, password1) 44 | 45 | simulation = project.factory.simgr(initial_state) 46 | 47 | def is_successful(state): 48 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 49 | return 'Good Job.'.encode() in stdout_output 50 | 51 | def should_abort(state): 52 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 53 | return 'Try again.'.encode() in stdout_output 54 | 55 | simulation.explore(find=is_successful, avoid=should_abort) 56 | 57 | if simulation.found: 58 | solution_state = simulation.found[0] 59 | 60 | solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode() 61 | solution1 = solution_state.solver.eval(password1,cast_to=bytes).decode() 62 | 63 | solution = ' '.join([ solution0, solution1 ]) 64 | 65 | print(solution) 66 | else: 67 | raise Exception('Could not find the solution') 68 | 69 | if __name__ == '__main__': 70 | main(sys.argv) 71 | -------------------------------------------------------------------------------- /14_angr_shared_library/scaffold14.py: -------------------------------------------------------------------------------- 1 | # The shared library has the function validate, which takes a string and returns 2 | # either true (1) or false (0). The binary calls this function. If it returns 3 | # true, the program prints "Good Job." otherwise, it prints "Try again." 4 | # 5 | # Note: When you run this script, make sure you run it on 6 | # lib14_angr_shared_library.so, not the executable. This level is intended to 7 | # teach how to analyse binary formats that are not typical executables. 8 | 9 | import angr 10 | import claripy 11 | import sys 12 | 13 | def main(argv): 14 | path_to_binary = ??? 15 | 16 | # The shared library is compiled with position-independent code. You will need 17 | # to specify the base address. All addresses in the shared library will be 18 | # base + offset, where offset is their address in the file. 19 | # (!) 20 | base = ??? 21 | project = angr.Project(path_to_binary, load_options={ 22 | 'main_opts' : { 23 | 'base_addr' : base 24 | } 25 | }) 26 | 27 | # Initialize any symbolic values here; you will need at least one to pass to 28 | # the validate function. 29 | # (!) 30 | buffer_pointer = claripy.BVV(???, ???) 31 | 32 | # Begin the state at the beginning of the validate function, as if it was 33 | # called by the program. Determine the parameters needed to call validate and 34 | # replace 'parameters...' with bitvectors holding the values you wish to pass. 35 | # Recall that 'claripy.BVV(value, size_in_bits)' constructs a bitvector 36 | # initialized to a single value. 37 | # Remember to add the base value you specified at the beginning to the 38 | # function address! 39 | # Hint: int validate(char* buffer, int length) { ... 40 | # (!) 41 | validate_function_address = base + ??? 42 | initial_state = project.factory.call_state( 43 | validate_function_address, 44 | buffer_pointer, 45 | ??? 46 | ) 47 | 48 | # Inject a symbolic value for the password buffer into the program and 49 | # instantiate the simulation. Another hint: the password is 8 bytes long. 50 | # (!) 51 | password = claripy.BVS( ???, ??? ) 52 | initial_state.memory.store( ??? , ???) 53 | 54 | simulation = project.factory.simgr(initial_state) 55 | 56 | # We wish to reach the end of the validate function and constrain the 57 | # return value of the function (stored in eax) to equal true (value of 1) 58 | # just before the function returns. We could use a hook, but instead we 59 | # can search for the address just before the function returns and then 60 | # constrain eax 61 | # (!) 62 | check_constraint_address = base + ??? 63 | simulation.explore(find=check_constraint_address) 64 | 65 | if simulation.found: 66 | solution_state = simulation.found[0] 67 | 68 | # Determine where the program places the return value, and constrain it so 69 | # that it is true. Then, solve for the solution and print it. 70 | # (!) 71 | solution_state.add_constraints( ??? ) 72 | solution = ??? 73 | print(solution) 74 | else: 75 | raise Exception('Could not find the solution') 76 | 77 | if __name__ == '__main__': 78 | main(sys.argv) 79 | -------------------------------------------------------------------------------- /solutions/14_angr_shared_library/scaffold14.py: -------------------------------------------------------------------------------- 1 | # The shared library has the function validate, which takes a string and returns 2 | # either true (1) or false (0). The binary calls this function. If it returns 3 | # true, the program prints "Good Job." otherwise, it prints "Try again." 4 | # 5 | # Note: When you run this script, make sure you run it on 6 | # lib14_angr_shared_library.so, not the executable. This level is intended to 7 | # teach how to analyse binary formats that are not typical executables. 8 | 9 | import angr 10 | import claripy 11 | import sys 12 | 13 | def main(argv): 14 | path_to_binary = ??? 15 | 16 | # The shared library is compiled with position-independent code. You will need 17 | # to specify the base address. All addresses in the shared library will be 18 | # base + offset, where offset is their address in the file. 19 | # (!) 20 | base = ??? 21 | project = angr.Project(path_to_binary, load_options={ 22 | 'main_opts' : { 23 | 'base_addr' : base 24 | } 25 | }) 26 | 27 | # Initialize any symbolic values here; you will need at least one to pass to 28 | # the validate function. 29 | # (!) 30 | buffer_pointer = claripy.BVV(???, ???) 31 | 32 | # Begin the state at the beginning of the validate function, as if it was 33 | # called by the program. Determine the parameters needed to call validate and 34 | # replace 'parameters...' with bitvectors holding the values you wish to pass. 35 | # Recall that 'claripy.BVV(value, size_in_bits)' constructs a bitvector 36 | # initialized to a single value. 37 | # Remember to add the base value you specified at the beginning to the 38 | # function address! 39 | # Hint: int validate(char* buffer, int length) { ... 40 | # (!) 41 | validate_function_address = base + ??? 42 | initial_state = project.factory.call_state( 43 | validate_function_address, 44 | buffer_pointer, 45 | ??? 46 | ) 47 | 48 | # Inject a symbolic value for the password buffer into the program and 49 | # instantiate the simulation. Another hint: the password is 8 bytes long. 50 | # (!) 51 | password = claripy.BVS( ???, ??? ) 52 | initial_state.memory.store( ??? , ???) 53 | 54 | simulation = project.factory.simgr(initial_state) 55 | 56 | # We wish to reach the end of the validate function and constrain the 57 | # return value of the function (stored in eax) to equal true (value of 1) 58 | # just before the function returns. We could use a hook, but instead we 59 | # can search for the address just before the function returns and then 60 | # constrain eax 61 | # (!) 62 | check_constraint_address = base + ??? 63 | simulation.explore(find=check_constraint_address) 64 | 65 | if simulation.found: 66 | solution_state = simulation.found[0] 67 | 68 | # Determine where the program places the return value, and constrain it so 69 | # that it is true. Then, solve for the solution and print it. 70 | # (!) 71 | solution_state.add_constraints( ??? ) 72 | solution = ??? 73 | print(solution) 74 | else: 75 | raise Exception('Could not find the solution') 76 | 77 | if __name__ == '__main__': 78 | main(sys.argv) 79 | -------------------------------------------------------------------------------- /dist/scaffold03.py: -------------------------------------------------------------------------------- 1 | # Angr doesn't currently support reading multiple things with scanf (Ex: 2 | # scanf("%u %u).) You will have to tell the simulation engine to begin the 3 | # program after scanf is called and manually inject the symbols into registers. 4 | 5 | import angr 6 | import claripy 7 | import sys 8 | 9 | def main(argv): 10 | path_to_binary = argv[1] 11 | project = angr.Project(path_to_binary) 12 | 13 | # Sometimes, you want to specify where the program should start. The variable 14 | # start_address will specify where the symbolic execution engine should begin. 15 | # Note that we are using blank_state, not entry_state. 16 | # (!) 17 | start_address = ??? # :integer (probably hexadecimal) 18 | initial_state = project.factory.blank_state(addr=start_address) 19 | 20 | # Create a symbolic bitvector (the datatype Angr uses to inject symbolic 21 | # values into the binary.) The first parameter is just a name Angr uses 22 | # to reference it. 23 | # You will have to construct multiple bitvectors. Copy the two lines below 24 | # and change the variable names. To figure out how many (and of what size) 25 | # you need, dissassemble the binary and determine the format parameter passed 26 | # to scanf. 27 | # (!) 28 | password0_size_in_bits = ??? # :integer 29 | password0 = claripy.BVS('password0', password0_size_in_bits) 30 | ... 31 | 32 | # Set a register to a symbolic value. This is one way to inject symbols into 33 | # the program. 34 | # initial_state.regs stores a number of convenient attributes that reference 35 | # registers by name. For example, to set eax to password0, use: 36 | # 37 | # initial_state.regs.eax = password0 38 | # 39 | # You will have to set multiple registers to distinct bitvectors. Copy and 40 | # paste the line below and change the register. To determine which registers 41 | # to inject which symbol, dissassemble the binary and look at the instructions 42 | # immediately following the call to scanf. 43 | # (!) 44 | initial_state.regs.??? = password0 45 | ... 46 | 47 | simulation = project.factory.simgr(initial_state) 48 | 49 | def is_successful(state): 50 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 51 | return ??? 52 | 53 | def should_abort(state): 54 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 55 | return ??? 56 | 57 | simulation.explore(find=is_successful, avoid=should_abort) 58 | 59 | if simulation.found: 60 | solution_state = simulation.found[0] 61 | 62 | # Solve for the symbolic values. If there are multiple solutions, we only 63 | # care about one, so we can use eval, which returns any (but only one) 64 | # solution. Pass eval the bitvector you want to solve for. 65 | # (!) 66 | solution0 = solution_state.se.eval(password0) 67 | ... 68 | 69 | # Aggregate and format the solutions you computed above, and then print 70 | # the full string. Pay attention to the order of the integers, and the 71 | # expected base (decimal, octal, hexadecimal, etc). 72 | solution = ??? # :string 73 | print(solution) 74 | else: 75 | raise Exception('Could not find the solution') 76 | 77 | if __name__ == '__main__': 78 | main(sys.argv) 79 | -------------------------------------------------------------------------------- /03_angr_symbolic_registers/scaffold03.py: -------------------------------------------------------------------------------- 1 | # Angr doesn't currently support reading multiple things with scanf (Ex: 2 | # scanf("%u %u).) You will have to tell the simulation engine to begin the 3 | # program after scanf is called and manually inject the symbols into registers. 4 | 5 | import angr 6 | import claripy 7 | import sys 8 | 9 | def main(argv): 10 | path_to_binary = argv[1] 11 | project = angr.Project(path_to_binary) 12 | 13 | # Sometimes, you want to specify where the program should start. The variable 14 | # start_address will specify where the symbolic execution engine should begin. 15 | # Note that we are using blank_state, not entry_state. 16 | # (!) 17 | start_address = ??? # :integer (probably hexadecimal) 18 | initial_state = project.factory.blank_state( 19 | addr=start_address, 20 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 21 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 22 | ) 23 | 24 | # Create a symbolic bitvector (the datatype Angr uses to inject symbolic 25 | # values into the binary.) The first parameter is just a name Angr uses 26 | # to reference it. 27 | # You will have to construct multiple bitvectors. Copy the two lines below 28 | # and change the variable names. To figure out how many (and of what size) 29 | # you need, dissassemble the binary and determine the format parameter passed 30 | # to scanf. 31 | # (!) 32 | password0_size_in_bits = ??? # :integer 33 | password0 = claripy.BVS('password0', password0_size_in_bits) 34 | ... 35 | 36 | # Set a register to a symbolic value. This is one way to inject symbols into 37 | # the program. 38 | # initial_state.regs stores a number of convenient attributes that reference 39 | # registers by name. For example, to set eax to password0, use: 40 | # 41 | # initial_state.regs.eax = password0 42 | # 43 | # You will have to set multiple registers to distinct bitvectors. Copy and 44 | # paste the line below and change the register. To determine which registers 45 | # to inject which symbol, dissassemble the binary and look at the instructions 46 | # immediately following the call to scanf. 47 | # (!) 48 | initial_state.regs.??? = password0 49 | ... 50 | 51 | simulation = project.factory.simgr(initial_state) 52 | 53 | def is_successful(state): 54 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 55 | return ??? 56 | 57 | def should_abort(state): 58 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 59 | return ??? 60 | 61 | simulation.explore(find=is_successful, avoid=should_abort) 62 | 63 | if simulation.found: 64 | solution_state = simulation.found[0] 65 | 66 | # Solve for the symbolic values. If there are multiple solutions, we only 67 | # care about one, so we can use eval, which returns any (but only one) 68 | # solution. Pass eval the bitvector you want to solve for. 69 | # (!) 70 | solution0 = solution_state.solver.eval(password0) 71 | ... 72 | 73 | # Aggregate and format the solutions you computed above, and then print 74 | # the full string. Pay attention to the order of the integers, and the 75 | # expected base (decimal, octal, hexadecimal, etc). 76 | solution = ??? # :string 77 | print(solution) 78 | else: 79 | raise Exception('Could not find the solution') 80 | 81 | if __name__ == '__main__': 82 | main(sys.argv) 83 | -------------------------------------------------------------------------------- /solutions/03_angr_symbolic_registers/scaffold03.py: -------------------------------------------------------------------------------- 1 | # Angr doesn't currently support reading multiple things with scanf (Ex: 2 | # scanf("%u %u).) You will have to tell the simulation engine to begin the 3 | # program after scanf is called and manually inject the symbols into registers. 4 | 5 | import angr 6 | import claripy 7 | import sys 8 | 9 | def main(argv): 10 | path_to_binary = argv[1] 11 | project = angr.Project(path_to_binary) 12 | 13 | # Sometimes, you want to specify where the program should start. The variable 14 | # start_address will specify where the symbolic execution engine should begin. 15 | # Note that we are using blank_state, not entry_state. 16 | # (!) 17 | start_address = ??? # :integer (probably hexadecimal) 18 | initial_state = project.factory.blank_state( 19 | addr=start_address, 20 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 21 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 22 | ) 23 | 24 | # Create a symbolic bitvector (the datatype Angr uses to inject symbolic 25 | # values into the binary.) The first parameter is just a name Angr uses 26 | # to reference it. 27 | # You will have to construct multiple bitvectors. Copy the two lines below 28 | # and change the variable names. To figure out how many (and of what size) 29 | # you need, dissassemble the binary and determine the format parameter passed 30 | # to scanf. 31 | # (!) 32 | password0_size_in_bits = ??? # :integer 33 | password0 = claripy.BVS('password0', password0_size_in_bits) 34 | ... 35 | 36 | # Set a register to a symbolic value. This is one way to inject symbols into 37 | # the program. 38 | # initial_state.regs stores a number of convenient attributes that reference 39 | # registers by name. For example, to set eax to password0, use: 40 | # 41 | # initial_state.regs.eax = password0 42 | # 43 | # You will have to set multiple registers to distinct bitvectors. Copy and 44 | # paste the line below and change the register. To determine which registers 45 | # to inject which symbol, dissassemble the binary and look at the instructions 46 | # immediately following the call to scanf. 47 | # (!) 48 | initial_state.regs.??? = password0 49 | ... 50 | 51 | simulation = project.factory.simgr(initial_state) 52 | 53 | def is_successful(state): 54 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 55 | return ??? 56 | 57 | def should_abort(state): 58 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 59 | return ??? 60 | 61 | simulation.explore(find=is_successful, avoid=should_abort) 62 | 63 | if simulation.found: 64 | solution_state = simulation.found[0] 65 | 66 | # Solve for the symbolic values. If there are multiple solutions, we only 67 | # care about one, so we can use eval, which returns any (but only one) 68 | # solution. Pass eval the bitvector you want to solve for. 69 | # (!) 70 | solution0 = solution_state.solver.eval(password0) 71 | ... 72 | 73 | # Aggregate and format the solutions you computed above, and then print 74 | # the full string. Pay attention to the order of the integers, and the 75 | # expected base (decimal, octal, hexadecimal, etc). 76 | solution = ??? # :string 77 | print(solution) 78 | else: 79 | raise Exception('Could not find the solution') 80 | 81 | if __name__ == '__main__': 82 | main(sys.argv) 83 | -------------------------------------------------------------------------------- /solutions/14_angr_shared_library/solve14.py: -------------------------------------------------------------------------------- 1 | # The shared library has the function validate, which takes a string and returns 2 | # either true (1) or false (0). The binary calls this function. If it returns 3 | # true, the program prints "Good Job." otherwise, it prints "Try again." 4 | # 5 | # Note: When you run this script, make sure you run it on 6 | # lib14_angr_shared_library.so, not the executable. This level is intended to 7 | # teach how to analyse binary formats that are not typical executables. 8 | 9 | import angr 10 | import claripy 11 | import sys 12 | 13 | def main(argv): 14 | path_to_binary = argv[1] 15 | 16 | # The shared library is compiled with position-independent code. You will need 17 | # to specify the base address. All addresses in the shared library will be 18 | # base + offset, where offset is their address in the file. 19 | # (!) 20 | base = 0x4000000 21 | project = angr.Project(path_to_binary, load_options={ 22 | 'main_opts' : { 23 | 'base_addr' : base 24 | } 25 | }) 26 | 27 | # Initialize any symbolic values here; you will need at least one to pass to 28 | # the validate function. 29 | # (!) 30 | buffer_pointer = claripy.BVV(0x3000000, 32) 31 | 32 | # Begin the state at the beginning of the validate function, as if it was 33 | # called by the program. Determine the parameters needed to call validate and 34 | # replace 'parameters...' with bitvectors holding the values you wish to pass. 35 | # Recall that 'claripy.BVV(value, size_in_bits)' constructs a bitvector 36 | # initialized to a single value. 37 | # Remember to add the base value you specified at the beginning to the 38 | # function address! 39 | # Hint: int validate(char* buffer, int length) { ... 40 | # (!) 41 | validate_function_address = base + 0x670 42 | initial_state = project.factory.call_state( 43 | validate_function_address, 44 | buffer_pointer, 45 | claripy.BVV(8, 32), 46 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 47 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 48 | ) 49 | 50 | # Inject a symbolic value for the password buffer into the program and 51 | # instantiate the simulation. Another hint: the password is 8 bytes long. 52 | # (!) 53 | password = claripy.BVS('password', 8*8) 54 | initial_state.memory.store(buffer_pointer, password) 55 | 56 | simulation = project.factory.simgr(initial_state) 57 | 58 | # We wish to reach the end of the validate function and constrain the 59 | # return value of the function (stored in eax) to equal true (value of 1) 60 | # just before the function returns. We could use a hook, but instead we 61 | # can search for the address just before the function returns and then 62 | # constrain eax 63 | # (!) 64 | check_constraint_address = base + 0x71c 65 | simulation.explore(find=check_constraint_address) 66 | 67 | if simulation.found: 68 | solution_state = simulation.found[0] 69 | 70 | # Determine where the program places the return value, and constrain it so 71 | # that it is true. Then, solve for the solution and print it. 72 | # (!) 73 | solution_state.add_constraints(solution_state.regs.eax != 0) 74 | solution = solution_state.solver.eval(password,cast_to=bytes).decode() 75 | print(solution) 76 | else: 77 | raise Exception('Could not find the solution') 78 | 79 | if __name__ == '__main__': 80 | main(sys.argv) 81 | -------------------------------------------------------------------------------- /package.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import datetime, os, sys, shutil 3 | 4 | def level_generate_module(level_name): 5 | return __import__(level_name + '.generate') 6 | 7 | def package_level(level_name, output_base_directory, num_binaries, user, salt, extra_files): 8 | seed = level_name + user + salt 9 | generate_module = level_generate_module(level_name) 10 | output_directory = output_base_directory 11 | binary_file_output_prefix = os.path.join(output_directory, level_name) 12 | suffix_format_str = '{:0' + str(len(str(num_binaries - 1))) + '}' if (num_binaries - 1 > 0) else '' 13 | 14 | if not os.path.exists(output_directory): 15 | os.makedirs(output_directory) 16 | 17 | for i in range(num_binaries): 18 | suffix = suffix_format_str.format(i) 19 | binary_file_output = binary_file_output_prefix + suffix 20 | generate_module.generate.generate([None, seed, binary_file_output]) 21 | 22 | for extra_file in extra_files: 23 | extra_file_abs = os.path.join('.', level_name, extra_file) 24 | extra_file_target = os.path.join(output_base_directory, extra_file) 25 | shutil.copyfile(extra_file_abs, extra_file_target) 26 | name_candidates = user.split('/') 27 | if len(name_candidates) >= 2: 28 | name = name_candidates[-2] 29 | else: 30 | name = name_candidates[-1] 31 | print('Compiled %s for user %s.' % (level_name, name)) 32 | 33 | def package_all(root_folder): 34 | num_binaries = 1 35 | year = str(datetime.datetime.now().year) 36 | package_level('00_angr_find', root_folder, num_binaries, root_folder, year, ['scaffold00.py']) 37 | package_level('01_angr_avoid', root_folder, num_binaries, root_folder, year, ['scaffold01.py']) 38 | package_level('02_angr_find_condition', root_folder, num_binaries, root_folder, year, ['scaffold02.py']) 39 | package_level('03_angr_symbolic_registers', root_folder, num_binaries, root_folder, year, ['scaffold03.py']) 40 | package_level('04_angr_symbolic_stack', root_folder, num_binaries, root_folder, year, ['scaffold04.py']) 41 | package_level('05_angr_symbolic_memory', root_folder, num_binaries, root_folder, year, ['scaffold05.py']) 42 | package_level('06_angr_symbolic_dynamic_memory', root_folder, num_binaries, root_folder, year, ['scaffold06.py']) 43 | package_level('07_angr_symbolic_file', root_folder, num_binaries, root_folder, year, ['scaffold07.py']) 44 | package_level('08_angr_constraints', root_folder, num_binaries, root_folder, year, ['scaffold08.py']) 45 | package_level('09_angr_hooks', root_folder, num_binaries, root_folder, year, ['scaffold09.py']) 46 | package_level('10_angr_simprocedures', root_folder, num_binaries, root_folder, year, ['scaffold10.py']) 47 | package_level('11_angr_sim_scanf', root_folder, num_binaries, root_folder, year, ['scaffold11.py']) 48 | package_level('12_angr_veritesting', root_folder, num_binaries, root_folder, year, ['scaffold12.py']) 49 | package_level('13_angr_static_binary', root_folder, num_binaries, root_folder, year, ['scaffold13.py']) 50 | package_level('14_angr_shared_library', root_folder, num_binaries, root_folder, year, ['scaffold14.py']) 51 | package_level('15_angr_arbitrary_read', root_folder, num_binaries, root_folder, year, ['scaffold15.py']) 52 | package_level('16_angr_arbitrary_write', root_folder, num_binaries, root_folder, year, ['scaffold16.py']) 53 | package_level('17_angr_arbitrary_jump', root_folder, num_binaries, root_folder, year, ['scaffold17.py']) 54 | 55 | if __name__ == '__main__': 56 | if len(sys.argv) != 2: 57 | print('Usage: python package.py [base_directory]') 58 | sys.exit() 59 | 60 | if not os.path.exists(sys.argv[1]): 61 | os.makedirs(sys.argv[1]) 62 | package_all(sys.argv[1]) 63 | -------------------------------------------------------------------------------- /dist/scaffold00.py: -------------------------------------------------------------------------------- 1 | # Before you begin, here are a few notes about these capture-the-flag 2 | # challenges. 3 | # 4 | # Each binary, when run, will ask for a password, which can be entered via stdin 5 | # (typing it into the console.) Many of the levels will accept many different 6 | # passwords. Your goal is to find a single password that works for each binary. 7 | # 8 | # If you enter an incorrect password, the program will print "Try again." If you 9 | # enter a correct password, the program will print "Good Job." 10 | # 11 | # Each challenge will be accompanied by a file like this one, named 12 | # "scaffoldXX.py". It will offer guidance as well as the skeleton of a possible 13 | # solution. You will have to edit each file. In some cases, you will have to 14 | # edit it significantly. While use of these files is recommended, you can write 15 | # a solution without them, if you find that they are too restrictive. 16 | # 17 | # Places in the scaffoldXX.py that require a simple substitution will be marked 18 | # with three question marks (???). Places that require more code will be marked 19 | # with an ellipsis (...). Comments will document any new concepts, but will be 20 | # omitted for concepts that have already been covered (you will need to use 21 | # previous scaffoldXX.py files as a reference to solve the challenges.) If a 22 | # comment documents a part of the code that needs to be changed, it will be 23 | # marked with an exclamation point at the end, on a separate line (!). 24 | 25 | import angr 26 | import sys 27 | 28 | def main(argv): 29 | # Create an Angr project. 30 | # If you want to be able to point to the binary from the command line, you can 31 | # use argv[1] as the parameter. Then, you can run the script from the command 32 | # line as follows: 33 | # python ./scaffold00.py [binary] 34 | # (!) 35 | path_to_binary = ??? # :string 36 | project = angr.Project(path_to_binary) 37 | 38 | # Tell Angr where to start executing (should it start from the main() 39 | # function or somewhere else?) For now, use the entry_state function 40 | # to instruct Angr to start from the main() function. 41 | initial_state = project.factory.entry_state() 42 | 43 | # Create a simulation manager initialized with the starting state. It provides 44 | # a number of useful tools to search and execute the binary. 45 | simulation = project.factory.simgr(initial_state) 46 | 47 | # Explore the binary to attempt to find the address that prints "Good Job." 48 | # You will have to find the address you want to find and insert it here. 49 | # This function will keep executing until it either finds a solution or it 50 | # has explored every possible path through the executable. 51 | # (!) 52 | print_good_address = ??? # :integer (probably in hexadecimal) 53 | simulation.explore(find=print_good_address) 54 | 55 | # Check that we have found a solution. The simulation.explore() method will 56 | # set simulation.found to a list of the states that it could find that reach 57 | # the instruction we asked it to search for. Remember, in Python, if a list 58 | # is empty, it will be evaluated as false, otherwise true. 59 | if simulation.found: 60 | # The explore method stops after it finds a single state that arrives at the 61 | # target address. 62 | solution_state = simulation.found[0] 63 | 64 | # Print the string that Angr wrote to stdin to follow solution_state. This 65 | # is our solution. 66 | print(solution_state.posix.dumps(sys.stdin.fileno())) 67 | else: 68 | # If Angr could not find a path that reaches print_good_address, throw an 69 | # error. Perhaps you mistyped the print_good_address? 70 | raise Exception('Could not find the solution') 71 | 72 | if __name__ == '__main__': 73 | main(sys.argv) 74 | -------------------------------------------------------------------------------- /solutions/13_angr_static_binary/solve13.py: -------------------------------------------------------------------------------- 1 | # This challenge is the exact same as the first challenge, except that it was 2 | # compiled as a static binary. Normally, Angr automatically replaces standard 3 | # library functions with SimProcedures that work much more quickly. 4 | # 5 | # To solve the challenge, manually hook any standard library c functions that 6 | # are used. Then, ensure that you begin the execution at the beginning of the 7 | # main function. Do not use entry_state. 8 | # 9 | # Here are a few SimProcedures Angr has already written for you. They implement 10 | # standard library functions. You will not need all of them: 11 | # angr.SIM_PROCEDURES['libc']['malloc'] 12 | # angr.SIM_PROCEDURES['libc']['fopen'] 13 | # angr.SIM_PROCEDURES['libc']['fclose'] 14 | # angr.SIM_PROCEDURES['libc']['fwrite'] 15 | # angr.SIM_PROCEDURES['libc']['getchar'] 16 | # angr.SIM_PROCEDURES['libc']['strncmp'] 17 | # angr.SIM_PROCEDURES['libc']['strcmp'] 18 | # angr.SIM_PROCEDURES['libc']['scanf'] 19 | # angr.SIM_PROCEDURES['libc']['printf'] 20 | # angr.SIM_PROCEDURES['libc']['puts'] 21 | # angr.SIM_PROCEDURES['libc']['exit'] 22 | # 23 | # As a reminder, you can hook functions with something similar to: 24 | # project.hook(malloc_address, angr.SIM_PROCEDURES['libc']['malloc']()) 25 | # 26 | # There are many more, see: 27 | # https://github.com/angr/angr/tree/master/angr/procedures/libc 28 | # 29 | # Additionally, note that, when the binary is executed, the main function is not 30 | # the first piece of code called. In the _start function, __libc_start_main is 31 | # called to start your program. The initialization that occurs in this function 32 | # can take a long time with Angr, so you should replace it with a SimProcedure. 33 | # angr.SIM_PROCEDURES['glibc']['__libc_start_main'] 34 | # Note 'glibc' instead of 'libc'. 35 | 36 | import angr 37 | import sys 38 | 39 | def main(argv): 40 | path_to_binary = argv[1] 41 | project = angr.Project(path_to_binary) 42 | 43 | initial_state = project.factory.entry_state( 44 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 45 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 46 | ) 47 | 48 | project.hook(0x804fab0, angr.SIM_PROCEDURES['libc']['printf']()) 49 | project.hook(0x804fb10, angr.SIM_PROCEDURES['libc']['scanf']()) 50 | project.hook(0x80503f0, angr.SIM_PROCEDURES['libc']['puts']()) 51 | project.hook(0x8048d60, angr.SIM_PROCEDURES['glibc']['__libc_start_main']()) 52 | 53 | simulation = project.factory.simgr(initial_state) 54 | 55 | # Define a function that checks if you have found the state you are looking 56 | # for. 57 | def is_successful(state): 58 | # Dump whatever has been printed out by the binary so far into a string. 59 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 60 | 61 | # Return whether 'Good Job.' has been printed yet. 62 | # (!) 63 | return 'Good Job.'.encode() in stdout_output # :boolean 64 | 65 | # Same as above, but this time check if the state should abort. If you return 66 | # False, Angr will continue to step the state. In this specific challenge, the 67 | # only time at which you will know you should abort is when the program prints 68 | # "Try again." 69 | def should_abort(state): 70 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 71 | return 'Try again.'.encode() in stdout_output # :boolean 72 | 73 | # Tell Angr to explore the binary and find any state that is_successful identfies 74 | # as a successful state by returning True. 75 | simulation.explore(find=is_successful, avoid=should_abort) 76 | 77 | if simulation.found: 78 | solution_state = simulation.found[0] 79 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 80 | else: 81 | raise Exception('Could not find the solution') 82 | 83 | if __name__ == '__main__': 84 | main(sys.argv) 85 | -------------------------------------------------------------------------------- /00_angr_find/scaffold00.py: -------------------------------------------------------------------------------- 1 | # Before you begin, here are a few notes about these capture-the-flag 2 | # challenges. 3 | # 4 | # Each binary, when run, will ask for a password, which can be entered via stdin 5 | # (typing it into the console.) Many of the levels will accept many different 6 | # passwords. Your goal is to find a single password that works for each binary. 7 | # 8 | # If you enter an incorrect password, the program will print "Try again." If you 9 | # enter a correct password, the program will print "Good Job." 10 | # 11 | # Each challenge will be accompanied by a file like this one, named 12 | # "scaffoldXX.py". It will offer guidance as well as the skeleton of a possible 13 | # solution. You will have to edit each file. In some cases, you will have to 14 | # edit it significantly. While use of these files is recommended, you can write 15 | # a solution without them, if you find that they are too restrictive. 16 | # 17 | # Places in the scaffoldXX.py that require a simple substitution will be marked 18 | # with three question marks (???). Places that require more code will be marked 19 | # with an ellipsis (...). Comments will document any new concepts, but will be 20 | # omitted for concepts that have already been covered (you will need to use 21 | # previous scaffoldXX.py files as a reference to solve the challenges.) If a 22 | # comment documents a part of the code that needs to be changed, it will be 23 | # marked with an exclamation point at the end, on a separate line (!). 24 | 25 | import angr 26 | import sys 27 | 28 | def main(argv): 29 | # Create an Angr project. 30 | # If you want to be able to point to the binary from the command line, you can 31 | # use argv[1] as the parameter. Then, you can run the script from the command 32 | # line as follows: 33 | # python ./scaffold00.py [binary] 34 | # (!) 35 | path_to_binary = ??? # :string 36 | project = angr.Project(path_to_binary) 37 | 38 | # Tell Angr where to start executing (should it start from the main() 39 | # function or somewhere else?) For now, use the entry_state function 40 | # to instruct Angr to start from the main() function. 41 | initial_state = project.factory.entry_state( 42 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 43 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 44 | ) 45 | 46 | # Create a simulation manager initialized with the starting state. It provides 47 | # a number of useful tools to search and execute the binary. 48 | simulation = project.factory.simgr(initial_state) 49 | 50 | # Explore the binary to attempt to find the address that prints "Good Job." 51 | # You will have to find the address you want to find and insert it here. 52 | # This function will keep executing until it either finds a solution or it 53 | # has explored every possible path through the executable. 54 | # (!) 55 | print_good_address = ??? # :integer (probably in hexadecimal) 56 | simulation.explore(find=print_good_address) 57 | 58 | # Check that we have found a solution. The simulation.explore() method will 59 | # set simulation.found to a list of the states that it could find that reach 60 | # the instruction we asked it to search for. Remember, in Python, if a list 61 | # is empty, it will be evaluated as false, otherwise true. 62 | if simulation.found: 63 | # The explore method stops after it finds a single state that arrives at the 64 | # target address. 65 | solution_state = simulation.found[0] 66 | 67 | # Print the string that Angr wrote to stdin to follow solution_state. This 68 | # is our solution. 69 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 70 | else: 71 | # If Angr could not find a path that reaches print_good_address, throw an 72 | # error. Perhaps you mistyped the print_good_address? 73 | raise Exception('Could not find the solution') 74 | 75 | if __name__ == '__main__': 76 | main(sys.argv) 77 | -------------------------------------------------------------------------------- /solutions/00_angr_find/scaffold00.py: -------------------------------------------------------------------------------- 1 | # Before you begin, here are a few notes about these capture-the-flag 2 | # challenges. 3 | # 4 | # Each binary, when run, will ask for a password, which can be entered via stdin 5 | # (typing it into the console.) Many of the levels will accept many different 6 | # passwords. Your goal is to find a single password that works for each binary. 7 | # 8 | # If you enter an incorrect password, the program will print "Try again." If you 9 | # enter a correct password, the program will print "Good Job." 10 | # 11 | # Each challenge will be accompanied by a file like this one, named 12 | # "scaffoldXX.py". It will offer guidance as well as the skeleton of a possible 13 | # solution. You will have to edit each file. In some cases, you will have to 14 | # edit it significantly. While use of these files is recommended, you can write 15 | # a solution without them, if you find that they are too restrictive. 16 | # 17 | # Places in the scaffoldXX.py that require a simple substitution will be marked 18 | # with three question marks (???). Places that require more code will be marked 19 | # with an ellipsis (...). Comments will document any new concepts, but will be 20 | # omitted for concepts that have already been covered (you will need to use 21 | # previous scaffoldXX.py files as a reference to solve the challenges.) If a 22 | # comment documents a part of the code that needs to be changed, it will be 23 | # marked with an exclamation point at the end, on a separate line (!). 24 | 25 | import angr 26 | import sys 27 | 28 | def main(argv): 29 | # Create an Angr project. 30 | # If you want to be able to point to the binary from the command line, you can 31 | # use argv[1] as the parameter. Then, you can run the script from the command 32 | # line as follows: 33 | # python ./scaffold00.py [binary] 34 | # (!) 35 | path_to_binary = ??? # :string 36 | project = angr.Project(path_to_binary) 37 | 38 | # Tell Angr where to start executing (should it start from the main() 39 | # function or somewhere else?) For now, use the entry_state function 40 | # to instruct Angr to start from the main() function. 41 | initial_state = project.factory.entry_state( 42 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 43 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 44 | ) 45 | 46 | # Create a simulation manager initialized with the starting state. It provides 47 | # a number of useful tools to search and execute the binary. 48 | simulation = project.factory.simgr(initial_state) 49 | 50 | # Explore the binary to attempt to find the address that prints "Good Job." 51 | # You will have to find the address you want to find and insert it here. 52 | # This function will keep executing until it either finds a solution or it 53 | # has explored every possible path through the executable. 54 | # (!) 55 | print_good_address = ??? # :integer (probably in hexadecimal) 56 | simulation.explore(find=print_good_address) 57 | 58 | # Check that we have found a solution. The simulation.explore() method will 59 | # set simulation.found to a list of the states that it could find that reach 60 | # the instruction we asked it to search for. Remember, in Python, if a list 61 | # is empty, it will be evaluated as false, otherwise true. 62 | if simulation.found: 63 | # The explore method stops after it finds a single state that arrives at the 64 | # target address. 65 | solution_state = simulation.found[0] 66 | 67 | # Print the string that Angr wrote to stdin to follow solution_state. This 68 | # is our solution. 69 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 70 | else: 71 | # If Angr could not find a path that reaches print_good_address, throw an 72 | # error. Perhaps you mistyped the print_good_address? 73 | raise Exception('Could not find the solution') 74 | 75 | if __name__ == '__main__': 76 | main(sys.argv) 77 | -------------------------------------------------------------------------------- /solutions/03_angr_symbolic_registers/solve03.py: -------------------------------------------------------------------------------- 1 | # Angr doesn't currently support reading multiple things with scanf (Ex: 2 | # scanf("%u %u).) You will have to tell the simulation engine to begin the 3 | # program after scanf is called and manually inject the symbols into registers. 4 | 5 | import angr 6 | import claripy 7 | import sys 8 | 9 | def main(argv): 10 | path_to_binary = argv[1] 11 | project = angr.Project(path_to_binary) 12 | 13 | # Sometimes, you want to specify where the program should start. The variable 14 | # start_address will specify where the symbolic execution engine should begin. 15 | # Note that we are using blank_state, not entry_state. 16 | # (!) 17 | start_address = 0x80488c7 # :integer (probably hexadecimal) 18 | initial_state = project.factory.blank_state( 19 | addr=start_address, 20 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 21 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 22 | ) 23 | 24 | # Create a symbolic bitvector (the datatype Angr uses to inject symbolic 25 | # values into the binary.) The first parameter is just a name Angr uses 26 | # to reference it. 27 | # You will have to construct multiple bitvectors. Copy the two lines below 28 | # and change the variable names. To figure out how many (and of what size) 29 | # you need, dissassemble the binary and determine the format parameter passed 30 | # to scanf. 31 | # (!) 32 | password0_size_in_bits = 32 # :integer 33 | password0 = claripy.BVS('password0', password0_size_in_bits) 34 | 35 | password1_size_in_bits = 32 # :integer 36 | password1 = claripy.BVS('password1', password1_size_in_bits) 37 | 38 | password2_size_in_bits = 32 # :integer 39 | password2 = claripy.BVS('password2', password2_size_in_bits) 40 | 41 | # Set a register to a symbolic value. This is one way to inject symbols into 42 | # the program. 43 | # initial_state.regs stores a number of convenient attributes that reference 44 | # registers by name. For example, to set eax to password0, use: 45 | # 46 | # initial_state.regs.eax = password0 47 | # 48 | # You will have to set multiple registers to distinct bitvectors. Copy and 49 | # paste the line below and change the register. To determine which registers 50 | # to inject which symbol, dissassemble the binary and look at the instructions 51 | # immediately following the call to scanf. 52 | # (!) 53 | initial_state.regs.eax = password0 54 | initial_state.regs.ebx = password1 55 | initial_state.regs.edx = password2 56 | 57 | simulation = project.factory.simgr(initial_state) 58 | 59 | def is_successful(state): 60 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 61 | return 'Good Job.'.encode() in stdout_output 62 | 63 | def should_abort(state): 64 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 65 | return 'Try again.'.encode() in stdout_output 66 | 67 | simulation.explore(find=is_successful, avoid=should_abort) 68 | 69 | if simulation.found: 70 | solution_state = simulation.found[0] 71 | 72 | # Solve for the symbolic values. If there are multiple solutions, we only 73 | # care about one, so we can use eval, which returns any (but only one) 74 | # solution. Pass eval the bitvector you want to solve for. 75 | # (!) 76 | solution0 = solution_state.solver.eval(password0) 77 | solution1 = solution_state.solver.eval(password1) 78 | solution2 = solution_state.solver.eval(password2) 79 | 80 | # Aggregate and format the solutions you computed above, and then print 81 | # the full string. Pay attention to the order of the integers, and the 82 | # expected base (decimal, octal, hexadecimal, etc). 83 | solution = ' '.join(map('{:x}'.format, [ solution0, solution1, solution2 ])) # :string 84 | print(solution) 85 | else: 86 | raise Exception('Could not find the solution') 87 | 88 | if __name__ == '__main__': 89 | main(sys.argv) 90 | -------------------------------------------------------------------------------- /solutions/00_angr_find/solve00.py: -------------------------------------------------------------------------------- 1 | # Before you begin, here are a few notes about these capture-the-flag 2 | # challenges. 3 | # 4 | # Each binary, when run, will ask for a password, which can be entered via stdin 5 | # (typing it into the console.) Many of the levels will accept many different 6 | # passwords. Your goal is to find a single password that works for each binary. 7 | # 8 | # If you enter an incorrect password, the program will print "Try again." If you 9 | # enter a correct password, the program will print "Good Job." 10 | # 11 | # Each challenge will be accompanied by a file like this one, named 12 | # "scaffoldXX.py". It will offer guidance as well as the skeleton of a possible 13 | # solution. You will have to edit each file. In some cases, you will have to 14 | # edit it significantly. While use of these files is recommended, you can write 15 | # a solution without them, if you find that they are too restrictive. 16 | # 17 | # Places in the scaffoldXX.py that require a simple substitution will be marked 18 | # with three question marks (???). Places that require more code will be marked 19 | # with an ellipsis (...). Comments will document any new concepts, but will be 20 | # omitted for concepts that have already been covered (you will need to use 21 | # previous scaffoldXX.py files as a reference to solve the challenges.) If a 22 | # comment documents a part of the code that needs to be changed, it will be 23 | # marked with an exclamation point at the end, on a separate line (!). 24 | 25 | import angr 26 | import sys 27 | 28 | def main(argv): 29 | # Create an Angr project. 30 | # If you want to be able to point to the binary from the command line, you can 31 | # use argv[1] as the parameter. Then, you can run the script from the command 32 | # line as follows: 33 | # python ./scaffold00.py [binary] 34 | # (!) 35 | path_to_binary = argv[1] # :string 36 | project = angr.Project(path_to_binary) 37 | 38 | # Tell Angr where to start executing (should it start from the main() 39 | # function or somewhere else?) For now, use the entry_state function 40 | # to instruct Angr to start from the main() function. 41 | initial_state = project.factory.entry_state( 42 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 43 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 44 | ) 45 | 46 | # Create a simulation manager initialized with the starting state. It provides 47 | # a number of useful tools to search and execute the binary. 48 | simulation = project.factory.simgr(initial_state) 49 | 50 | # Explore the binary to attempt to find the address that prints "Good Job." 51 | # You will have to find the address you want to find and insert it here. 52 | # This function will keep executing until it either finds a solution or it 53 | # has explored every possible path through the executable. 54 | # (!) 55 | print_good_address = 0x804868c # :integer (probably in hexadecimal) 56 | simulation.explore(find=print_good_address) 57 | 58 | # Check that we have found a solution. The simulation.explore() method will 59 | # set simulation.found to a list of the states that it could find that reach 60 | # the instruction we asked it to search for. Remember, in Python, if a list 61 | # is empty, it will be evaluated as false, otherwise true. 62 | if simulation.found: 63 | # The explore method stops after it finds a single state that arrives at the 64 | # target address. 65 | solution_state = simulation.found[0] 66 | 67 | # Print the string that Angr wrote to stdin to follow solution_state. This 68 | # is our solution. 69 | print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) 70 | else: 71 | # If Angr could not find a path that reaches print_good_address, throw an 72 | # error. Perhaps you mistyped the print_good_address? 73 | raise Exception('Could not find the solution') 74 | 75 | if __name__ == '__main__': 76 | main(sys.argv) 77 | -------------------------------------------------------------------------------- /dist/scaffold09.py: -------------------------------------------------------------------------------- 1 | # This level performs the following computations: 2 | # 3 | # 1. Get 16 bytes of user input and encrypt it. 4 | # 2. Save the result of check_equals_AABBCCDDEEFFGGHH (or similar) 5 | # 3. Get another 16 bytes from the user and encrypt it. 6 | # 4. Check that it's equal to a predefined password. 7 | # 8 | # The ONLY part of this program that we have to worry about is #2. We will be 9 | # replacing the call to check_equals_ with our own version, using a hook, since 10 | # check_equals_ will run too slowly otherwise. 11 | 12 | import angr 13 | import claripy 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | 20 | # Since Angr can handle the initial call to scanf, we can start from the 21 | # beginning. 22 | initial_state = project.factory.entry_state() 23 | 24 | # Hook the address of where check_equals_ is called. 25 | # (!) 26 | check_equals_called_address = ??? 27 | 28 | # The length parameter in angr.Hook specifies how many bytes the execution 29 | # engine should skip after completing the hook. This will allow hooks to 30 | # replace certain instructions (or groups of instructions). Determine the 31 | # instructions involved in calling check_equals_, and then determine how many 32 | # bytes are used to represent them in memory. This will be the skip length. 33 | # (!) 34 | instruction_to_skip_length = ??? 35 | @project.hook(check_equals_called_address, length=instruction_to_skip_length) 36 | def skip_check_equals_(state): 37 | # Determine the address where user input is stored. It is passed as a 38 | # parameter ot the check_equals_ function. Then, load the string. Reminder: 39 | # int check_equals_(char* to_check, int length) { ... 40 | user_input_buffer_address = ??? # :integer, probably hexadecimal 41 | user_input_buffer_length = ??? 42 | 43 | # Reminder: state.memory.load will read the stored value at the address 44 | # user_input_buffer_address of byte length user_input_buffer_length. 45 | # It will return a bitvector holding the value. This value can either be 46 | # symbolic or concrete, depending on what was stored there in the program. 47 | user_input_string = state.memory.load( 48 | user_input_buffer_address, 49 | user_input_buffer_length 50 | ) 51 | 52 | # Determine the string this function is checking the user input against. 53 | # It's encoded in the name of this function; decompile the program to find 54 | # it. 55 | check_against_string = ??? # :string 56 | 57 | # gcc uses eax to store the return value, if it is an integer. We need to 58 | # set eax to 1 if check_against_string == user_input_string and 0 otherwise. 59 | # However, since we are describing an equation to be used by z3 (not to be 60 | # evaluated immediately), we cannot use Python if else syntax. Instead, we 61 | # have to use claripy's built in function that deals with if statements. 62 | # claripy.If(expression, ret_if_true, ret_if_false) will output an 63 | # expression that evaluates to ret_if_true if expression is true and 64 | # ret_if_false otherwise. 65 | # Think of it like the Python "value0 if expression else value1". 66 | state.regs.eax = claripy.If( 67 | user_input_string == check_against_string, 68 | claripy.BVV(1, 32), 69 | claripy.BVV(0, 32) 70 | ) 71 | 72 | simulation = project.factory.simgr(initial_state) 73 | 74 | def is_successful(state): 75 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 76 | return ??? 77 | 78 | def should_abort(state): 79 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 80 | return ??? 81 | 82 | simulation.explore(find=is_successful, avoid=should_abort) 83 | 84 | if simulation.found: 85 | solution_state = simulation.found[0] 86 | 87 | # Since we are allowing Angr to handle the input, retrieve it by printing 88 | # the contents of stdin. Use one of the early levels as a reference. 89 | solution = ??? 90 | print(solution) 91 | else: 92 | raise Exception('Could not find the solution') 93 | 94 | if __name__ == '__main__': 95 | main(sys.argv) 96 | -------------------------------------------------------------------------------- /09_angr_hooks/scaffold09.py: -------------------------------------------------------------------------------- 1 | # This level performs the following computations: 2 | # 3 | # 1. Get 16 bytes of user input and encrypt it. 4 | # 2. Save the result of check_equals_AABBCCDDEEFFGGHH (or similar) 5 | # 3. Get another 16 bytes from the user and encrypt it. 6 | # 4. Check that it's equal to a predefined password. 7 | # 8 | # The ONLY part of this program that we have to worry about is #2. We will be 9 | # replacing the call to check_equals_ with our own version, using a hook, since 10 | # check_equals_ will run too slowly otherwise. 11 | 12 | import angr 13 | import claripy 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | 20 | # Since Angr can handle the initial call to scanf, we can start from the 21 | # beginning. 22 | initial_state = project.factory.entry_state( 23 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 24 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 25 | ) 26 | 27 | # Hook the address of where check_equals_ is called. 28 | # (!) 29 | check_equals_called_address = ??? 30 | 31 | # The length parameter in angr.Hook specifies how many bytes the execution 32 | # engine should skip after completing the hook. This will allow hooks to 33 | # replace certain instructions (or groups of instructions). Determine the 34 | # instructions involved in calling check_equals_, and then determine how many 35 | # bytes are used to represent them in memory. This will be the skip length. 36 | # (!) 37 | instruction_to_skip_length = ??? 38 | @project.hook(check_equals_called_address, length=instruction_to_skip_length) 39 | def skip_check_equals_(state): 40 | # Determine the address where user input is stored. It is passed as a 41 | # parameter ot the check_equals_ function. Then, load the string. Reminder: 42 | # int check_equals_(char* to_check, int length) { ... 43 | user_input_buffer_address = ??? # :integer, probably hexadecimal 44 | user_input_buffer_length = ??? 45 | 46 | # Reminder: state.memory.load will read the stored value at the address 47 | # user_input_buffer_address of byte length user_input_buffer_length. 48 | # It will return a bitvector holding the value. This value can either be 49 | # symbolic or concrete, depending on what was stored there in the program. 50 | user_input_string = state.memory.load( 51 | user_input_buffer_address, 52 | user_input_buffer_length 53 | ) 54 | 55 | # Determine the string this function is checking the user input against. 56 | # It's encoded in the name of this function; decompile the program to find 57 | # it. 58 | check_against_string = ??? # :string 59 | 60 | # gcc uses eax to store the return value, if it is an integer. We need to 61 | # set eax to 1 if check_against_string == user_input_string and 0 otherwise. 62 | # However, since we are describing an equation to be used by z3 (not to be 63 | # evaluated immediately), we cannot use Python if else syntax. Instead, we 64 | # have to use claripy's built in function that deals with if statements. 65 | # claripy.If(expression, ret_if_true, ret_if_false) will output an 66 | # expression that evaluates to ret_if_true if expression is true and 67 | # ret_if_false otherwise. 68 | # Think of it like the Python "value0 if expression else value1". 69 | state.regs.eax = claripy.If( 70 | user_input_string == check_against_string, 71 | claripy.BVV(1, 32), 72 | claripy.BVV(0, 32) 73 | ) 74 | 75 | simulation = project.factory.simgr(initial_state) 76 | 77 | def is_successful(state): 78 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 79 | return ??? 80 | 81 | def should_abort(state): 82 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 83 | return ??? 84 | 85 | simulation.explore(find=is_successful, avoid=should_abort) 86 | 87 | if simulation.found: 88 | solution_state = simulation.found[0] 89 | 90 | # Since we are allowing Angr to handle the input, retrieve it by printing 91 | # the contents of stdin. Use one of the early levels as a reference. 92 | solution = ??? 93 | print(solution) 94 | else: 95 | raise Exception('Could not find the solution') 96 | 97 | if __name__ == '__main__': 98 | main(sys.argv) 99 | -------------------------------------------------------------------------------- /solutions/09_angr_hooks/scaffold09.py: -------------------------------------------------------------------------------- 1 | # This level performs the following computations: 2 | # 3 | # 1. Get 16 bytes of user input and encrypt it. 4 | # 2. Save the result of check_equals_AABBCCDDEEFFGGHH (or similar) 5 | # 3. Get another 16 bytes from the user and encrypt it. 6 | # 4. Check that it's equal to a predefined password. 7 | # 8 | # The ONLY part of this program that we have to worry about is #2. We will be 9 | # replacing the call to check_equals_ with our own version, using a hook, since 10 | # check_equals_ will run too slowly otherwise. 11 | 12 | import angr 13 | import claripy 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | 20 | # Since Angr can handle the initial call to scanf, we can start from the 21 | # beginning. 22 | initial_state = project.factory.entry_state( 23 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 24 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 25 | ) 26 | 27 | # Hook the address of where check_equals_ is called. 28 | # (!) 29 | check_equals_called_address = ??? 30 | 31 | # The length parameter in angr.Hook specifies how many bytes the execution 32 | # engine should skip after completing the hook. This will allow hooks to 33 | # replace certain instructions (or groups of instructions). Determine the 34 | # instructions involved in calling check_equals_, and then determine how many 35 | # bytes are used to represent them in memory. This will be the skip length. 36 | # (!) 37 | instruction_to_skip_length = ??? 38 | @project.hook(check_equals_called_address, length=instruction_to_skip_length) 39 | def skip_check_equals_(state): 40 | # Determine the address where user input is stored. It is passed as a 41 | # parameter ot the check_equals_ function. Then, load the string. Reminder: 42 | # int check_equals_(char* to_check, int length) { ... 43 | user_input_buffer_address = ??? # :integer, probably hexadecimal 44 | user_input_buffer_length = ??? 45 | 46 | # Reminder: state.memory.load will read the stored value at the address 47 | # user_input_buffer_address of byte length user_input_buffer_length. 48 | # It will return a bitvector holding the value. This value can either be 49 | # symbolic or concrete, depending on what was stored there in the program. 50 | user_input_string = state.memory.load( 51 | user_input_buffer_address, 52 | user_input_buffer_length 53 | ) 54 | 55 | # Determine the string this function is checking the user input against. 56 | # It's encoded in the name of this function; decompile the program to find 57 | # it. 58 | check_against_string = ??? # :string 59 | 60 | # gcc uses eax to store the return value, if it is an integer. We need to 61 | # set eax to 1 if check_against_string == user_input_string and 0 otherwise. 62 | # However, since we are describing an equation to be used by z3 (not to be 63 | # evaluated immediately), we cannot use Python if else syntax. Instead, we 64 | # have to use claripy's built in function that deals with if statements. 65 | # claripy.If(expression, ret_if_true, ret_if_false) will output an 66 | # expression that evaluates to ret_if_true if expression is true and 67 | # ret_if_false otherwise. 68 | # Think of it like the Python "value0 if expression else value1". 69 | state.regs.eax = claripy.If( 70 | user_input_string == check_against_string, 71 | claripy.BVV(1, 32), 72 | claripy.BVV(0, 32) 73 | ) 74 | 75 | simulation = project.factory.simgr(initial_state) 76 | 77 | def is_successful(state): 78 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 79 | return ??? 80 | 81 | def should_abort(state): 82 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 83 | return ??? 84 | 85 | simulation.explore(find=is_successful, avoid=should_abort) 86 | 87 | if simulation.found: 88 | solution_state = simulation.found[0] 89 | 90 | # Since we are allowing Angr to handle the input, retrieve it by printing 91 | # the contents of stdin. Use one of the early levels as a reference. 92 | solution = ??? 93 | print(solution) 94 | else: 95 | raise Exception('Could not find the solution') 96 | 97 | if __name__ == '__main__': 98 | main(sys.argv) 99 | -------------------------------------------------------------------------------- /solutions/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Binaries generated via python package.py obj/wuchang/angr 3 | echo "Solving all of the levels... This could take a while." 4 | ANGR_OUT_00="$(python3 00_angr_find/solve00.py 00_angr_find/00_angr_find 2> /dev/null)" 5 | echo -n "." 6 | ANGR_OUT_01="$(python3 01_angr_avoid/solve01.py 01_angr_avoid/01_angr_avoid 2> /dev/null)" 7 | echo -n "." 8 | ANGR_OUT_02="$(python3 02_angr_find_condition/solve02.py 02_angr_find_condition/02_angr_find_condition 2> /dev/null)" 9 | echo -n "." 10 | ANGR_OUT_03="$(python3 03_angr_symbolic_registers/solve03.py 03_angr_symbolic_registers/03_angr_symbolic_registers 2> /dev/null)" 11 | echo -n "." 12 | ANGR_OUT_04="$(python3 04_angr_symbolic_stack/solve04.py 04_angr_symbolic_stack/04_angr_symbolic_stack 2> /dev/null)" 13 | echo -n "." 14 | ANGR_OUT_05="$(python3 05_angr_symbolic_memory/solve05.py 05_angr_symbolic_memory/05_angr_symbolic_memory 2> /dev/null)" 15 | echo -n "." 16 | ANGR_OUT_06="$(python3 06_angr_symbolic_dynamic_memory/solve06.py 06_angr_symbolic_dynamic_memory/06_angr_symbolic_dynamic_memory 2> /dev/null)" 17 | echo -n "." 18 | ANGR_OUT_07="$(python3 07_angr_symbolic_file/solve07.py 07_angr_symbolic_file/07_angr_symbolic_file 2> /dev/null | awk '{print $1}')" 19 | echo -n "." 20 | ANGR_OUT_08="$(python3 08_angr_constraints/solve08.py 08_angr_constraints/08_angr_constraints 2> /dev/null)" 21 | echo -n "." 22 | ANGR_OUT_09="$(python3 09_angr_hooks/solve09.py 09_angr_hooks/09_angr_hooks 2> /dev/null)" 23 | echo -n "." 24 | ANGR_OUT_10="$(python3 10_angr_simprocedures/solve10.py 10_angr_simprocedures/10_angr_simprocedures 2> /dev/null)" 25 | echo -n "." 26 | ANGR_OUT_11="$(python3 11_angr_sim_scanf/solve11.py 11_angr_sim_scanf/11_angr_sim_scanf 2> /dev/null)" 27 | echo -n "." 28 | ANGR_OUT_12="$(python3 12_angr_veritesting/solve12.py 12_angr_veritesting/12_angr_veritesting 2> /dev/null | tail -1)" 29 | echo -n "." 30 | ANGR_OUT_13="$(python3 13_angr_static_binary/solve13.py 13_angr_static_binary/13_angr_static_binary 2> /dev/null)" 31 | echo -n "." 32 | ANGR_OUT_14="$(python3 14_angr_shared_library/solve14.py 14_angr_shared_library/lib14_angr_shared_library.so 2> /dev/null)" 33 | echo -n "." 34 | ANGR_OUT_15="$(python3 15_angr_arbitrary_read/solve15.py 15_angr_arbitrary_read/15_angr_arbitrary_read 2> /dev/null)" 35 | echo -n "." 36 | ANGR_OUT_16="$(python3 16_angr_arbitrary_write/solve16.py 16_angr_arbitrary_write/16_angr_arbitrary_write 2> /dev/null)" 37 | echo -n "." 38 | ANGR_OUT_17="$(python3 17_angr_arbitrary_jump/solve17.py 17_angr_arbitrary_jump/17_angr_arbitrary_jump 2> /dev/null)" 39 | echo -n "." 40 | echo "" 41 | echo "-- Solutions --" 42 | echo "00: $ANGR_OUT_00" 43 | echo $ANGR_OUT_00 | 00_angr_find/00_angr_find 44 | echo "01: $ANGR_OUT_01" 45 | echo $ANGR_OUT_01 | 01_angr_avoid/01_angr_avoid 46 | echo "02: $ANGR_OUT_02" 47 | echo $ANGR_OUT_02 | 02_angr_find_condition/02_angr_find_condition 48 | echo "03: $ANGR_OUT_03" 49 | echo $ANGR_OUT_03 | 03_angr_symbolic_registers/03_angr_symbolic_registers 50 | echo "04: $ANGR_OUT_04" 51 | echo $ANGR_OUT_04 | 04_angr_symbolic_stack/04_angr_symbolic_stack 52 | echo "05: $ANGR_OUT_05" 53 | echo $ANGR_OUT_05 | 05_angr_symbolic_memory/05_angr_symbolic_memory 54 | echo "06: $ANGR_OUT_06" 55 | echo $ANGR_OUT_06 | 06_angr_symbolic_dynamic_memory/06_angr_symbolic_dynamic_memory 56 | echo "07: $ANGR_OUT_07" 57 | echo $ANGR_OUT_07 | 07_angr_symbolic_file/07_angr_symbolic_file 58 | echo "08: $ANGR_OUT_08" 59 | echo $ANGR_OUT_08 | 08_angr_constraints/08_angr_constraints 60 | echo "09: $ANGR_OUT_09" 61 | echo $ANGR_OUT_09 | 09_angr_hooks/09_angr_hooks 62 | echo "10: $ANGR_OUT_10" 63 | echo $ANGR_OUT_10 | 10_angr_simprocedures/10_angr_simprocedures 64 | echo "11: $ANGR_OUT_11" 65 | echo $ANGR_OUT_11 | 11_angr_sim_scanf/11_angr_sim_scanf 66 | echo "12: $ANGR_OUT_12" 67 | echo $ANGR_OUT_12 | 12_angr_veritesting/12_angr_veritesting 68 | echo "13: $ANGR_OUT_13" 69 | echo $ANGR_OUT_13 | 13_angr_static_binary/13_angr_static_binary 70 | echo "14: $ANGR_OUT_14" 71 | BACKUP_LD_LIBRARY_PATH=$LD_LIBRARY_PATH 72 | export LD_LIBRARY_PATH=./14_angr_shared_library 73 | echo $ANGR_OUT_14 | 14_angr_shared_library/14_angr_shared_library 74 | echo "15: $ANGR_OUT_15" 75 | export LD_LIBRARY_PATH=$BACKUP_LD_LIBRARY_PATH 76 | echo $ANGR_OUT_15 | 15_angr_arbitrary_read/15_angr_arbitrary_read 77 | echo "16: $ANGR_OUT_16" 78 | echo $ANGR_OUT_16 | 16_angr_arbitrary_write/16_angr_arbitrary_write 79 | echo "17: $ANGR_OUT_17" 80 | echo $ANGR_OUT_17 | 17_angr_arbitrary_jump/17_angr_arbitrary_jump 81 | -------------------------------------------------------------------------------- /07_angr_symbolic_file/scaffold07.py: -------------------------------------------------------------------------------- 1 | # This challenge could, in theory, be solved in multiple ways. However, for the 2 | # sake of learning how to simulate an alternate filesystem, please solve this 3 | # challenge according to structure provided below. As a challenge, once you have 4 | # an initial solution, try solving this in an alternate way. 5 | # 6 | # Problem description and general solution strategy: 7 | # The binary loads the password from a file using the fread function. If the 8 | # password is correct, it prints "Good Job." In order to keep consistency with 9 | # the other challenges, the input from the console is written to a file in the 10 | # ignore_me function. As the name suggests, ignore it, as it only exists to 11 | # maintain consistency with other challenges. 12 | # We want to: 13 | # 1. Determine the file from which fread reads. 14 | # 2. Use Angr to simulate a filesystem where that file is replaced with our own 15 | # simulated file. 16 | # 3. Initialize the file with a symbolic value, which will be read with fread 17 | # and propogated through the program. 18 | # 4. Solve for the symbolic input to determine the password. 19 | 20 | import angr 21 | import claripy 22 | import sys 23 | 24 | def main(argv): 25 | path_to_binary = argv[1] 26 | project = angr.Project(path_to_binary) 27 | 28 | start_address = ??? 29 | initial_state = project.factory.blank_state( 30 | addr=start_address, 31 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 32 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 33 | ) 34 | 35 | # Specify some information needed to construct a simulated file. For this 36 | # challenge, the filename is hardcoded, but in theory, it could be symbolic. 37 | # Note: to read from the file, the binary calls 38 | # 'fread(buffer, sizeof(char), 64, file)'. 39 | # (!) 40 | filename = ??? # :string 41 | symbolic_file_size_bytes = ??? 42 | 43 | # Construct a bitvector for the password and then store it in the file's 44 | # backing memory. For example, imagine a simple file, 'hello.txt': 45 | # 46 | # Hello world, my name is John. 47 | # ^ ^ 48 | # ^ address 0 ^ address 24 (count the number of characters) 49 | # In order to represent this in memory, we would want to write the string to 50 | # the beginning of the file: 51 | # 52 | # hello_txt_contents = claripy.BVV('Hello world, my name is John.', 30*8) 53 | # 54 | # Perhaps, then, we would want to replace John with a 55 | # symbolic variable. We would call: 56 | # 57 | # name_bitvector = claripy.BVS('symbolic_name', 4*8) 58 | # 59 | # Then, after the program calls fopen('hello.txt', 'r') and then 60 | # fread(buffer, sizeof(char), 30, hello_txt_file), the buffer would contain 61 | # the string from the file, except four symbolic bytes where the name would be 62 | # stored. 63 | # (!) 64 | password = claripy.BVS('password', symbolic_file_size_bytes * 8) 65 | 66 | # Construct the symbolic file. The file_options parameter specifies the Linux 67 | # file permissions (read, read/write, execute etc.) The content parameter 68 | # specifies from where the stream of data should be supplied. If content is 69 | # an instance of SimSymbolicMemory (we constructed one above), the stream will 70 | # contain the contents (including any symbolic contents) of the memory, 71 | # beginning from address zero. 72 | # Set the content parameter to our BVS instance that holds the symbolic data. 73 | # (!) 74 | password_file = angr.storage.SimFile(filename, content=???) 75 | 76 | # Add the symbolic file we created to the symbolic filesystem. 77 | initial_state.fs.insert(filename, password_file) 78 | 79 | simulation = project.factory.simgr(initial_state) 80 | 81 | def is_successful(state): 82 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 83 | return ??? 84 | 85 | def should_abort(state): 86 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 87 | return ??? 88 | 89 | simulation.explore(find=is_successful, avoid=should_abort) 90 | 91 | if simulation.found: 92 | solution_state = simulation.found[0] 93 | 94 | solution = solution_state.solver.eval(password,cast_to=bytes).decode() 95 | 96 | print(solution) 97 | else: 98 | raise Exception('Could not find the solution') 99 | 100 | if __name__ == '__main__': 101 | main(sys.argv) 102 | -------------------------------------------------------------------------------- /solutions/07_angr_symbolic_file/scaffold07.py: -------------------------------------------------------------------------------- 1 | # This challenge could, in theory, be solved in multiple ways. However, for the 2 | # sake of learning how to simulate an alternate filesystem, please solve this 3 | # challenge according to structure provided below. As a challenge, once you have 4 | # an initial solution, try solving this in an alternate way. 5 | # 6 | # Problem description and general solution strategy: 7 | # The binary loads the password from a file using the fread function. If the 8 | # password is correct, it prints "Good Job." In order to keep consistency with 9 | # the other challenges, the input from the console is written to a file in the 10 | # ignore_me function. As the name suggests, ignore it, as it only exists to 11 | # maintain consistency with other challenges. 12 | # We want to: 13 | # 1. Determine the file from which fread reads. 14 | # 2. Use Angr to simulate a filesystem where that file is replaced with our own 15 | # simulated file. 16 | # 3. Initialize the file with a symbolic value, which will be read with fread 17 | # and propogated through the program. 18 | # 4. Solve for the symbolic input to determine the password. 19 | 20 | import angr 21 | import claripy 22 | import sys 23 | 24 | def main(argv): 25 | path_to_binary = argv[1] 26 | project = angr.Project(path_to_binary) 27 | 28 | start_address = ??? 29 | initial_state = project.factory.blank_state( 30 | addr=start_address, 31 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 32 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 33 | ) 34 | 35 | # Specify some information needed to construct a simulated file. For this 36 | # challenge, the filename is hardcoded, but in theory, it could be symbolic. 37 | # Note: to read from the file, the binary calls 38 | # 'fread(buffer, sizeof(char), 64, file)'. 39 | # (!) 40 | filename = ??? # :string 41 | symbolic_file_size_bytes = ??? 42 | 43 | # Construct a bitvector for the password and then store it in the file's 44 | # backing memory. For example, imagine a simple file, 'hello.txt': 45 | # 46 | # Hello world, my name is John. 47 | # ^ ^ 48 | # ^ address 0 ^ address 24 (count the number of characters) 49 | # In order to represent this in memory, we would want to write the string to 50 | # the beginning of the file: 51 | # 52 | # hello_txt_contents = claripy.BVV('Hello world, my name is John.', 30*8) 53 | # 54 | # Perhaps, then, we would want to replace John with a 55 | # symbolic variable. We would call: 56 | # 57 | # name_bitvector = claripy.BVS('symbolic_name', 4*8) 58 | # 59 | # Then, after the program calls fopen('hello.txt', 'r') and then 60 | # fread(buffer, sizeof(char), 30, hello_txt_file), the buffer would contain 61 | # the string from the file, except four symbolic bytes where the name would be 62 | # stored. 63 | # (!) 64 | password = claripy.BVS('password', symbolic_file_size_bytes * 8) 65 | 66 | # Construct the symbolic file. The file_options parameter specifies the Linux 67 | # file permissions (read, read/write, execute etc.) The content parameter 68 | # specifies from where the stream of data should be supplied. If content is 69 | # an instance of SimSymbolicMemory (we constructed one above), the stream will 70 | # contain the contents (including any symbolic contents) of the memory, 71 | # beginning from address zero. 72 | # Set the content parameter to our BVS instance that holds the symbolic data. 73 | # (!) 74 | password_file = angr.storage.SimFile(filename, content=???) 75 | 76 | # Add the symbolic file we created to the symbolic filesystem. 77 | initial_state.fs.insert(filename, password_file) 78 | 79 | simulation = project.factory.simgr(initial_state) 80 | 81 | def is_successful(state): 82 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 83 | return ??? 84 | 85 | def should_abort(state): 86 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 87 | return ??? 88 | 89 | simulation.explore(find=is_successful, avoid=should_abort) 90 | 91 | if simulation.found: 92 | solution_state = simulation.found[0] 93 | 94 | solution = solution_state.solver.eval(password,cast_to=bytes).decode() 95 | 96 | print(solution) 97 | else: 98 | raise Exception('Could not find the solution') 99 | 100 | if __name__ == '__main__': 101 | main(sys.argv) 102 | -------------------------------------------------------------------------------- /solutions/09_angr_hooks/solve09.py: -------------------------------------------------------------------------------- 1 | # This level performs the following computations: 2 | # 3 | # 1. Get 16 bytes of user input and encrypt it. 4 | # 2. Save the result of check_equals_AABBCCDDEEFFGGHH (or similar) 5 | # 3. Get another 16 bytes from the user and encrypt it. 6 | # 4. Check that it's equal to a predefined password. 7 | # 8 | # The ONLY part of this program that we have to worry about is #2. We will be 9 | # replacing the call to check_equals_ with our own version, using a hook, since 10 | # check_equals_ will run too slowly otherwise. 11 | 12 | import angr 13 | import claripy 14 | import sys 15 | 16 | def main(argv): 17 | path_to_binary = argv[1] 18 | project = angr.Project(path_to_binary) 19 | 20 | # Since Angr can handle the initial call to scanf, we can start from the 21 | # beginning. 22 | initial_state = project.factory.entry_state( 23 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 24 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 25 | ) 26 | 27 | # Hook the address of where check_equals_ is called. 28 | # (!) 29 | check_equals_called_address = 0x80486ca 30 | 31 | # The length parameter in angr.Hook specifies how many bytes the execution 32 | # engine should skip after completing the hook. This will allow hooks to 33 | # replace certain instructions (or groups of instructions). Determine the 34 | # instructions involved in calling check_equals_, and then determine how many 35 | # bytes are used to represent them in memory. This will be the skip length. 36 | # (!) 37 | instruction_to_skip_length = 5 38 | @project.hook(check_equals_called_address, length=instruction_to_skip_length) 39 | def skip_check_equals_(state): 40 | # Determine the address where user input is stored. It is passed as a 41 | # parameter ot the check_equals_ function. Then, load the string. Reminder: 42 | # int check_equals_(char* to_check, int length) { ... 43 | user_input_buffer_address = 0x804a044 # :integer, probably hexadecimal 44 | user_input_buffer_length = 16 45 | 46 | # Reminder: state.memory.load will read the stored value at the address 47 | # user_input_buffer_address of byte length user_input_buffer_length. 48 | # It will return a bitvector holding the value. This value can either be 49 | # symbolic or concrete, depending on what was stored there in the program. 50 | user_input_string = state.memory.load( 51 | user_input_buffer_address, 52 | user_input_buffer_length 53 | ) 54 | 55 | # Determine the string this function is checking the user input against. 56 | # It's encoded in the name of this function; decompile the program to find 57 | # it. 58 | check_against_string = 'OSIWHBXIFOQVSBZB'.encode() # :string 59 | 60 | # gcc uses eax to store the return value, if it is an integer. We need to 61 | # set eax to 1 if check_against_string == user_input_string and 0 otherwise. 62 | # However, since we are describing an equation to be used by z3 (not to be 63 | # evaluated immediately), we cannot use Python if else syntax. Instead, we 64 | # have to use claripy's built in function that deals with if statements. 65 | # claripy.If(expression, ret_if_true, ret_if_false) will output an 66 | # expression that evaluates to ret_if_true if expression is true and 67 | # ret_if_false otherwise. 68 | # Think of it like the Python "value0 if expression else value1". 69 | state.regs.eax = claripy.If( 70 | user_input_string == check_against_string, 71 | claripy.BVV(1, 32), 72 | claripy.BVV(0, 32) 73 | ) 74 | 75 | simulation = project.factory.simgr(initial_state) 76 | 77 | def is_successful(state): 78 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 79 | return 'Good Job.'.encode() in stdout_output 80 | 81 | def should_abort(state): 82 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 83 | return 'Try again.'.encode() in stdout_output 84 | 85 | simulation.explore(find=is_successful, avoid=should_abort) 86 | 87 | if simulation.found: 88 | solution_state = simulation.found[0] 89 | 90 | # Since we are allowing Angr to handle the input, retrieve it by printing 91 | # the contents of stdin. Use one of the early levels as a reference. 92 | solution = solution_state.posix.dumps(sys.stdin.fileno()).decode() 93 | print(solution) 94 | else: 95 | raise Exception('Could not find the solution') 96 | 97 | if __name__ == '__main__': 98 | main(sys.argv) 99 | -------------------------------------------------------------------------------- /solutions/07_angr_symbolic_file/solve07.py: -------------------------------------------------------------------------------- 1 | # This challenge could, in theory, be solved in multiple ways. However, for the 2 | # sake of learning how to simulate an alternate filesystem, please solve this 3 | # challenge according to structure provided below. As a challenge, once you have 4 | # an initial solution, try solving this in an alternate way. 5 | # 6 | # Problem description and general solution strategy: 7 | # The binary loads the password from a file using the fread function. If the 8 | # password is correct, it prints "Good Job." In order to keep consistency with 9 | # the other challenges, the input from the console is written to a file in the 10 | # ignore_me function. As the name suggests, ignore it, as it only exists to 11 | # maintain consistency with other challenges. 12 | # We want to: 13 | # 1. Determine the file from which fread reads. 14 | # 2. Use Angr to simulate a filesystem where that file is replaced with our own 15 | # simulated file. 16 | # 3. Initialize the file with a symbolic value, which will be read with fread 17 | # and propogated through the program. 18 | # 4. Solve for the symbolic input to determine the password. 19 | 20 | import angr 21 | import claripy 22 | import sys 23 | 24 | def main(argv): 25 | path_to_binary = argv[1] 26 | project = angr.Project(path_to_binary) 27 | 28 | start_address = 0x80488bc 29 | initial_state = project.factory.blank_state( 30 | addr=start_address, 31 | add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 32 | angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} 33 | ) 34 | 35 | # Specify some information needed to construct a simulated file. For this 36 | # challenge, the filename is hardcoded, but in theory, it could be symbolic. 37 | # Note: to read from the file, the binary calls 38 | # 'fread(buffer, sizeof(char), 64, file)'. 39 | # (!) 40 | filename = 'FOQVSBZB.txt' # :string 41 | symbolic_file_size_bytes = 8 42 | 43 | # Construct a bitvector for the password and then store it in the file's 44 | # backing memory. For example, imagine a simple file, 'hello.txt': 45 | # 46 | # Hello world, my name is John. 47 | # ^ ^ 48 | # ^ address 0 ^ address 24 (count the number of characters) 49 | # In order to represent this in memory, we would want to write the string to 50 | # the beginning of the file: 51 | # 52 | # hello_txt_contents = claripy.BVV('Hello world, my name is John.', 30*8) 53 | # 54 | # Perhaps, then, we would want to replace John with a 55 | # symbolic variable. We would call: 56 | # 57 | # name_bitvector = claripy.BVS('symbolic_name', 4*8) 58 | # 59 | # Then, after the program calls fopen('hello.txt', 'r') and then 60 | # fread(buffer, sizeof(char), 30, hello_txt_file), the buffer would contain 61 | # the string from the file, except four symbolic bytes where the name would be 62 | # stored. 63 | # (!) 64 | password = claripy.BVS('password', symbolic_file_size_bytes * 8) 65 | 66 | # Construct the symbolic file. The file_options parameter specifies the Linux 67 | # file permissions (read, read/write, execute etc.) The content parameter 68 | # specifies from where the stream of data should be supplied. If content is 69 | # an instance of SimSymbolicMemory (we constructed one above), the stream will 70 | # contain the contents (including any symbolic contents) of the memory, 71 | # beginning from address zero. 72 | # Set the content parameter to our BVS instance that holds the symbolic data. 73 | # (!) 74 | password_file = angr.storage.SimFile(filename, content=password) 75 | 76 | # Add the symbolic file we created to the symbolic filesystem. 77 | initial_state.fs.insert(filename, password_file) 78 | 79 | simulation = project.factory.simgr(initial_state) 80 | 81 | def is_successful(state): 82 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 83 | return 'Good Job.'.encode() in stdout_output 84 | 85 | def should_abort(state): 86 | stdout_output = state.posix.dumps(sys.stdout.fileno()) 87 | return 'Try again.'.encode() in stdout_output 88 | 89 | simulation.explore(find=is_successful, avoid=should_abort) 90 | 91 | if simulation.found: 92 | solution_state = simulation.found[0] 93 | 94 | solution = solution_state.solver.eval(password,cast_to=bytes).decode() 95 | 96 | print(solution) 97 | else: 98 | raise Exception('Could not find the solution') 99 | 100 | if __name__ == '__main__': 101 | main(sys.argv) 102 | -------------------------------------------------------------------------------- /dist/scaffold08.py: -------------------------------------------------------------------------------- 1 | # The binary asks for a 16 character password to which is applies a complex 2 | # function and then compares with a reference string with the function 3 | # check_equals_[reference string]. (Decompile the binary and take a look at it!) 4 | # The source code for this function is provided here. However, the reference 5 | # string in your version will be different than AABBCCDDEEFFGGHH: 6 | # 7 | # #define REFERENCE_PASSWORD = "AABBCCDDEEFFGGHH"; 8 | # int check_equals_AABBCCDDEEFFGGHH(char* to_check, size_t length) { 9 | # uint32_t num_correct = 0; 10 | # for (int i=0; i