0x15测试流程
设初始状态: 设置hook函数,声明要获取输入的变量
设置成功失败状态: 检查运行到某一地址时内存数据的值,可以用copied_state = state.copy() 探测之后添加限制条件来检查最终结果是否符合要求
解析成功数据:对声明的变量解析得到输入,解析得到格式化输入,添加限制条件解析出来最终的值
#检测程序漏洞,并且检测这个漏洞能否被利用
import angr
import claripy
import sys
def main(argv):
path_to_binary = "15_angr_arbitrary_read"
project = angr.Project(path_to_binary)
# You can either use a blank state or an entry state; just make sure to start
# at the beginning of the program.
# (!)
initial_state = project.factory.entry_state()
# Again, scanf needs to be replaced.
class ReplacementScanf(angr.SimProcedure): #钩取scanf函数重新定义,获取到它的参数
#定义的输入第二个输入的数据为可见字符
# Hint: scanf("%u %20s")
def run(self, format_string, checksec_key_address,input_address):
scanf0 = claripy.BVS('scanf0', 4*8)
scanf1 = claripy.BVS('scanf1', 20*8)
for char in scanf1.chop(bits=8): #把整个数据分割每8位为一组
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf进来的v4的所有字符为可见字符
scanf0_address = checksec_key_address
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
self.state.memory.store(input_address, scanf1, endness=project.arch.memory_endness) #保存求解变量到内存中
self.state.globals['solution0'] = scanf0
self.state.globals['solution1'] = scanf1 #保存这两个变量到state,结束的时候可以查看这里对应的输入
scanf_symbol = '__isoc99_scanf'# :string
project.hook_symbol(scanf_symbol, ReplacementScanf())#在这里执行hook
# We will call this whenever puts is called. The goal of this function is to
# determine if the pointer passed to puts is controllable by the user, such
# that we can rewrite it to point to the string "Good Job."
def check_puts(state): #执行到这里是刚执行完puts函数 获取现在puts函数的参数1.检查符号化 2.复制一份执行流添加限制判断在这个限制之下是否有解(添加的限制不能取消)
puts_parameter = state.memory.load(state.regs.esp+4,4, endness=project.arch.memory_endness) #情景是刚刚调用了puts,它的参数在esp+4位置处
if state.se.symbolic(puts_parameter):#检查这个参数是否为符号化对象
good_job_string_address = 0x4D52584B # :integer, probably hexadecimal Good Job.的地址
is_vulnerable_expression = puts_parameter==good_job_string_address # :boolean bitvector expression
#运行到这里所有输入得到的状态都已经有了,现在要做的是从这些输入中判断是否有一种输入满足输出的内容为Good job.
copied_state = state.copy() #复制执行状态的上下文
copied_state.add_constraints(is_vulnerable_expression) #复制的新进程添加限制为 puts参数为Good job.的地址
if copied_state.satisfiable():# 判断添加了上面这个约束是否有解
state.add_constraints(is_vulnerable_expression) # 如果有解的话就保存到我们执行的那个状态对象
return True
#复制一个执行状态对象用来探路,直到探到正确的路才让真正的执行流走到这里
else:
return False
else: # not state.se.symbolic(???)
return False
simulation = project.factory.simgr(initial_state)
def is_successful(state): #到这里刚刚执行完puts函数 相当于下断点
puts_address = 0x8048370 #puts@plt的地址(已经执行过了call)
if state.addr == puts_address: #程序执行到这里(也就是路径探测到这里)
# Return True if we determine this call to puts is exploitable.
return check_puts(state)
else:
# We have not yet found a call to puts; we should continue!
return False
simulation.explore(find=is_successful)
if simulation.found: #如果找到说明满足条件1.能够走到puts函数 2.走到puts函数的参数为格式化对象 3.判断输出的内容是否为Good job.
solution_state = simulation.found[0]
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
solution1 = solution_state.se.eval(solution_state.globals['solution1'], cast_to=bytes) # 输出字符串序列化的内容
print(solution0, solution1)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
核心代码
把数据分成8bit为一组过滤
for char in scanf1.chop(bits=8): #把整个数据分割每8位为一组
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf进来的v4的所有字符为可见字符
在successful函数里面下断点
def is_successful(state): #到这里刚刚执行完puts函数 相当于下断点
puts_address = 0x8048370 #puts@plt的地址(已经执行过了call)
if state.addr == puts_address: #程序执行到这里(也就是路径探测到这里)
# Return True if we determine this call to puts is exploitable.
return check_puts(state)
if state.se.symbolic(puts_parameter):#检查这个参数是否为符号化对象
用来探测加上条件之后是否有解
copied_state = state.copy() #复制执行状态的上下文
copied_state.add_constraints(is_vulnerable_expression) #复制的新进程添加限制为 puts参数为Good job.的地址
if copied_state.satisfiable():# 判断添加了上面这个约束是否有解
state.add_constraints(is_vulnerable_expression) # 如果有解的话就保存到我们执行的那个状态对象
return True
加入cast_to=bytes选项输出的结果是字符串
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
0x16漏洞测试2
# Essentially, the program does the following:
#
# scanf("%d %20s", &key, user_input);
# ...
# // if certain unknown conditions are true...
# strncpy(random_buffer, user_input);
# ...
# if (strncmp(secure_buffer, reference_string)) {
# // The secure_buffer does not equal the reference string.
# puts("Try again.");
# } else {
# // The two are equal.
# puts("Good Job.");
# }
#
# If this program has no bugs in it, it would _always_ print "Try again." since
# user_input copies into random_buffer, not secure_buffer.
#
# The question is: can we find a buffer overflow that will allow us to overwrite
# the random_buffer pointer to point to secure_buffer? (Spoiler: we can, but we
# will need to use Angr.)
#
# We want to identify a place in the binary, when strncpy is called, when we can:
# 1) Control the source contents (not the source pointer!)
# * This will allow us to write arbitrary data to the destination.
# 2) Control the destination pointer
# * This will allow us to write to an arbitrary location.
# If we can meet both of those requirements, we can write arbitrary data to an
# arbitrary location. Finally, we need to contrain the source contents to be
# equal to the reference_string and the destination pointer to be equal to the
# secure_buffer.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "16_angr_arbitrary_write"
project = angr.Project(path_to_binary)
# You can either use a blank state or an entry state; just make sure to start
# at the beginning of the program.
initial_state = project.factory.entry_state()
class ReplacementScanf(angr.SimProcedure):
# Hint: scanf("%u %20s")
def run(self, format_string, check_key,input_buffer):
# %u
scanf0 = claripy.BVS('scanf0', 4*8)
# %20s
scanf1 = claripy.BVS('scanf1', 20*8)
for char in scanf1.chop(bits=8):
self.state.add_constraints(char >= "0", char <= "z")
scanf0_address = check_key
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = input_buffer
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' # :string
project.hook_symbol(scanf_symbol, ReplacementScanf())
# In this challenge, we want to check strncpy to determine if we can control
# both the source and the destination. It is common that we will be able to
# control at least one of the parameters, (such as when the program copies a
# string that it received via stdin).
def check_strncpy(state):
# The stack will look as follows:
# ... ________________
# esp + 15 -> / \
# esp + 14 -> | param2 |
# esp + 13 -> | len |
# esp + 12 -> \________________/
# esp + 11 -> / \
# esp + 10 -> | param1 |
# esp + 9 -> | src |
# esp + 8 -> \________________/
# esp + 7 -> / \
# esp + 6 -> | param0 |
# esp + 5 -> | dest |
# esp + 4 -> \________________/
# esp + 3 -> / \
# esp + 2 -> | return |
# esp + 1 -> | address |
# esp -> \________________/
# (!)
strncpy_src = state.memory.load(state.regs.esp+8,4,endness=project.arch.memory_endness)
strncpy_dest = state.memory.load(state.regs.esp+4,4,endness=project.arch.memory_endness)
strncpy_len = state.memory.load(state.regs.esp+12,4,endness=project.arch.memory_endness)
# We need to find out if src is symbolic, however, we care about the
# contents, rather than the pointer itself. Therefore, we have to load the
# the contents of src to determine if they are symbolic.
# Hint: How many bytes is strncpy copying?
# (!)
src_contents = state.memory.load(strncpy_src, strncpy_len)
# Our goal is to determine if we can write arbitrary data to an arbitrary
# location. This means determining if the source contents are symbolic
# (arbitrary data) and the destination pointer is symbolic (arbitrary
# destination).
# (!)
if state.se.symbolic(strncpy_dest) and state.se.symbolic(src_contents): #dest指针和src的内容为符号化对象
# Use ltrace to determine the reference string. Decompile the binary to
# determine the address of the buffer it checks the password against. Our
# goal is to overwrite that buffer to store the password.
# (!)
password_string = "KZYRKMKE"# :string
buffer_address = 0x4D52584C # :integer, probably in hexadecimal
# Create an expression that tests if the first n bytes is length. Warning:
# while typical Python slices (array[start:end]) will work with bitvectors,
# they are indexed in an odd way. The ranges must start with a high value
# and end with a low value. Additionally, the bits are indexed from right
# to left. For example, let a bitvector, b, equal 'ABCDEFGH', (64 bits).
# The following will read bit 0-7 (total of 1 byte) from the right-most
# bit (the end of the string).
# b[7:0] == 'H'
# To access the beginning of the string, we need to access the last 16
# bits, or bits 48-63:
# b[63:48] == 'AB'
# In this specific case, since we don't necessarily know the length of the
# contents (unless you look at the binary), we can use the following:
# b[-1:-16] == 'AB', since, in Python, -1 is the end of the list, and -16
# is the 16th element from the end of the list. The actual numbers should
# correspond with the length of password_string.
# (!)
does_src_hold_password = src_contents[-1:-64] == password_string #小端字节序存储用这种方式反转数据
# Create an expression to check if the dest parameter can be set to
# buffer_address. If this is true, then we have found our exploit!
# (!)
does_dest_equal_buffer_address = buffer_address==strncpy_dest
# In the previous challenge, we copied the state, added constraints to the
# copied state, and then determined if the constraints of the new state
# were satisfiable. Since that pattern is so common, Angr implemented a
# parameter 'extra_constraints' for the satisfiable function that does the
# exact same thing:
if state.satisfiable(extra_constraints=(does_src_hold_password, does_dest_equal_buffer_address)): #尝试求解满足两个条件1.strncpy函数的目的地址为
#if ( !strncmp(password_buffer, "KZYRKMKE", 8u) )中password_buffer的地址 2.strncpy中的前8位数据的内容为KZYRKMKE
#这样执行完strncpy函数strncpy函数一定返回非零
state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address)# 尝试求解成功添加真正的限制
return True
else:
return False
else: # not state.se.symbolic(???)
return False
simulation = project.factory.simgr(initial_state)
def is_successful(state):
strncpy_address = 0x08048410
if state.addr == strncpy_address:
return check_strncpy(state)
else:
return False
simulation.explore(find=is_successful)
if simulation.found:
solution_state = simulation.found[0]
solution0=solution_state.se.eval(solution_state.globals['solution0'])
solution1=solution_state.se.eval(solution_state.globals["solution1"],cast_to=bytes)
print (hex(solution0),solution1)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
#strncmp(password_buffer, "KZYRKMKE", 8u) 作为判断函数没有不可控的,只有从它的变量入手
#真正不可控的是strncpy函数,测试这个函数是否能改变strncpy函数的参数值,把它改为需要的内容
核心代码
添加限制尝试求解
if state.satisfiable(extra_constraints=(does_src_hold_password, does_dest_equal_buffer_address)): #尝试求解满足两个条件1.strncpy函数的目的地址为
#if ( !strncmp(password_buffer, "KZYRKMKE", 8u) )中password_buffer的地址 2.strncpy中的前8位数据的内容为KZYRKMKE
#strncmp(password_buffer, “KZYRKMKE”, 8u) 作为判断函数没有不可控的,只有从它的变量入手
#真正不可控的是strncpy函数,测试这个函数是否能改变strncpy函数的参数值,把它改为需要的内容