这次的几道题主要是hook相关的,有些题目甚至不用去反汇编找地址,真是非常amazing!也说明angr这个工具真的很强。
另外,还发现了一个大佬写的关于这方面的教程,贴在这里(因为我写的太垃圾了):
https://github.com/ZERO-A-ONE/AngrCTF_FITM
文章目录
08_angr_constraints
这道题是让我们手动添加约束。如果直接用符号执行的话,由于字符串的长度有16位,所以会导致2^16个分支,造成路径爆炸的问题。
起始地址还是从scanf后面开始,password的地址是缓冲区的地址,address_to_check_constraint这个是函数check_equals_xxx的地址。目的就是用我们手动添加的约束来替代模拟执行一个一个字节导致的分支。
# The binary asks for a 16 character password to which is applies a complex
# function and then compares with a reference string with the function
# check_equals_[reference string]. (Decompile the binary and take a look at it!)
# The source code for this function is provided here. However, the reference
# string in your version will be different than AABBCCDDEEFFGGHH:
#
# #define REFERENCE_PASSWORD = "AABBCCDDEEFFGGHH";
# int check_equals_AABBCCDDEEFFGGHH(char* to_check, size_t length) {
# uint32_t num_correct = 0;
# for (int i=0; i<length; ++i) {
# if (to_check[i] == REFERENCE_PASSWORD[i]) {
# num_correct += 1;
# }
# }
# return num_correct == length;
# }
#
# ...
#
# char* input = user_input();
# char* encrypted_input = complex_function(input);
# if (check_equals_AABBCCDDEEFFGGHH(encrypted_input, 16)) {
# puts("Good Job.");
# } else {
# puts("Try again.");
# }
#
# The function checks if *to_check == "AABBCCDDEEFFGGHH". Verify this yourself.
# While you, as a human, can easily determine that this function is equivalent
# to simply comparing the strings, the computer cannot. Instead the computer
# would need to branch every time the if statement in the loop was called (16
# times), resulting in 2^16 = 65,536 branches, which will take too long of a
# time to evaluate for our needs.
#
# We do not know how the complex_function works, but we want to find an input
# that, when modified by complex_function, will produce the string:
# AABBCCDDEEFFGGHH.
#
# In this puzzle, your goal will be to stop the program before this function is
# called and manually constrain the to_check variable to be equal to the
# password you identify by decompiling the binary. Since, you, as a human, know
# that if the strings are equal, the program will print "Good Job.", you can
# be assured that if the program can solve for an input that makes them equal,
# the input will be the correct password.
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
start_address = 0x08048640
initial_state = project.factory.blank_state(addr=start_address)
password = claripy.BVS('password', 16*8)
password_address = 0x0804A050
initial_state.memory.store(password_address, password)
simulation = project.factory.simgr(initial_state)
# Angr will not be able to reach the point at which the binary prints out
# 'Good Job.'. We cannot use that as the target anymore.
# (!)
address_to_check_constraint = 0x08048580
simulation.explore(find=address_to_check_constraint)
if simulation.found:
solution_state = simulation.found[0]
# Recall that we need to constrain the to_check parameter (see top) of the
# check_equals_ function. Determine the address that is being passed as the
# parameter and load it into a bitvector so that we can constrain it.
# (!)
constrained_parameter_address = password_address
constrained_parameter_size_bytes = 16
constrained_parameter_bitvector = solution_state.memory.load(
constrained_parameter_address,
constrained_parameter_size_bytes
)
# We want to constrain the system to find an input that will make
# constrained_parameter_bitvector equal the desired value.
# (!)
constrained_parameter_desired_value = "LUZZTFWZLYZOOQNR" # :string
# Specify a claripy expression (using Pythonic syntax) that tests whether
# constrained_parameter_bitvector == constrained_parameter_desired_value.
# We will let z3 attempt to find an input that will make this expression
# true.
constraint_expression = constrained_parameter_bitvector == constrained_parameter_desired_value
# Add the constraint to the state to instruct z3 to include it when solving
# for input.
solution_state.add_constraints(constrained_parameter_bitvector == constrained_parameter_desired_value)
# Solve for the constrained_parameter_bitvector.
solution = solution_state.solver.eval(password,cast_to=bytes)
print(solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
solved~
10_angr_simprocedures
这道题主要是写了一个类来代替程序里的check_equal_xxx函数。从而避免符号执行模拟执行原始的check_equal_xxx函数导致路径爆炸问题。其他和之前的挺像。
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, to_check, length ):
user_input_buffer_address = to_check
user_input_buffer_length = length
user_input_string = self.state.memory.load(
user_input_buffer_address,
user_input_buffer_length
)
check_against_string = "UMXLDQWFUTURPCTU"
return claripy.If(user_input_string == check_against_string, claripy.BVV(1, 32), claripy.BVV(0, 32))
check_equals_symbol = "check_equals_UMXLDQWFUTURPCTU" # :string
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Good Job.' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again' in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno())
print (solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
solved~
11_angr_sim_scanf
这道题和前面的那个有点像,就是用hook的方式替代了有2个参数的scanf函数。然后需要注意的是,这里用了state.globals来保存scanf的符号化输入。
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
class ReplacementScanf(angr.SimProcedure):
def run(self, format_string, scanf0_address, scanf1_address):
scanf0 = claripy.BVS('scanf0', 32)
scanf1 = claripy.BVS('scanf1', 32)
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)
self.state.globals['solution0'] = scanf0
self.state.globals['solution1'] = scanf1
scanf_symbol = "__isoc99_scanf"
project.hook_symbol(scanf_symbol, ReplacementScanf())
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Good Job.' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.' in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
stored_solutions0 = solution_state.globals['solution0']
stored_solutions1 = solution_state.globals['solution1']
solution0 = solution_state.solver.eval(stored_solutions0)
solution1 = solution_state.solver.eval(stored_solutions1)
solution = (solution0,solution1)
print(solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
solved~
12_angr_veritesting
这道题其实就是find_condition那个在加载工程的时候加了一个路径归并的选项。这样就不用手动给字符串添加约束了。
import angr
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
#在这里改动了!!
simulation = project.factory.simgr(initial_state,veritesting=True)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return state.solver.is_true("Good Job." in str(stdout_output)) # :boolean
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return state.solver.is_true("Try again." in str(stdout_output)) # :boolean
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
print (solution_state.posix.dumps(sys.stdin.fileno()))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
13_angr_static_binary
angr里头写好了很多内置的libc函数,从而让我们用hook来提高符号执行的速度。
要知道更多的函数,可以访问下面的网址:
https://github.com/angr/angr/tree/master/angr/procedures/libc
import angr
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state,veritesting=True)
project.hook_symbol('printf',angr.SIM_PROCEDURES['libc']['printf']())
project.hook_symbol('__isoc99_scanf',angr.SIM_PROCEDURES['libc']['scanf']())
project.hook_symbol('puts',angr.SIM_PROCEDURES['libc']['puts']())
project.hook_symbol('__libc_start_main',angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
def is_successful(state):
# Dump whatever has been printed out by the binary so far into a string.
stdout_output = state.posix.dumps(sys.stdout.fileno())
return state.solver.is_true("Good Job." in str(stdout_output)) # :boolean
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return state.solver.is_true("Try again." in str(stdout_output)) # :boolean
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
print (solution_state.posix.dumps(sys.stdin.fileno()))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
14_angr_shared_library
这道题是针对共享库分析。通过分析源程序14_angr_shared_library,我们知道里头调用了一个validate函数。但是这个函数是在共享库里的。如果想知道更多的关于共享库的知识,可以查阅《程序员的自我修养——链接、装载与库》。
import angr
import claripy
import sys
def main(argv):
path_to_binary = "lib14_angr_shared_library.so"
# The shared library is compiled with position-independent code. You will need
# to specify the base address. All addresses in the shared library will be
# base + offset, where offset is their address in the file.
# (!)
base = 0x4000000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {
'custom_base_addr' : base
}
})
# Initialize any symbolic values here; you will need at least one to pass to
# the validate function.
password = claripy.BVS("password",8*8)
buffer_pointer = claripy.BVV(0xffffff00,32)
# Begin the state at the beginning of the validate function, as if it was
# called by the program. Determine the parameters needed to call validate and
# replace 'parameters...' with bitvectors holding the values you wish to pass.
# Recall that 'claripy.BVV(value, size_in_bits)' constructs a bitvector
# initialized to a single value.
# Remember to add the base value you specified at the beginning to the
# function address!
# Hint: int validate(char* buffer, int length) { ...
# Another hint: the password is 8 bytes long.
# (!)
validate_function_address = base + 0x674
initial_state = project.factory.call_state(validate_function_address, buffer_pointer,claripy.BVV(8,32))
initial_state.memory.store(buffer_pointer,password)
# You will need to add code to inject a symbolic value into the program at the
# end of the function that constrains eax to equal true (value of 1) just
# before the function returns. There are multiple ways to do this:
# 1. Use a hook.
# 2. Search for the address just before the function returns and then
# constrain eax (this may require putting code elsewhere)
simulation = project.factory.simgr(initial_state)
success_address = base+0x720
simulation.explore(find=success_address)
if simulation.found:
solution_state = simulation.found[0]
solution_state.add_constraints(solution_state.regs.eax != 0)
# Determine where the program places the return value, and constrain it so
# that it is true. Then, solve for the solution and print it.
# (!)
solution = solution_state.solver.eval(password,cast_to=bytes)
print (solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)