angr使用[4]--用angr测试漏洞是否可用

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函数的参数值,把它改为需要的内容


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章