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函數的參數值,把它改爲需要的內容


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