angr学习[5]--代码整理(总结)

0x01探索模板


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)

0x02获取输入(能运行到这里的输入)(解析结果)

获取满足条件的格式化输入

solution_state.posix.dumps(sys.stdin.fileno())

获取设置变量的值
password1=claripy.BVS('password1',password_size_in_bits)结合使用获取满足条件的设置的变量的值

solution0=solution_state.se.eval(password0)

在hook函数中的变量获取能走到某处的输入

self.state.globals['solution0'] = scanf0			#在hook函数里面声明这是个最终要解析的变量

    stored_solutions0 = solution_state.globals['solution0']
    solution0 = solution_state.se.eval(stored_solutions0)					#最终的解析

解析结果字符串序列化
输出结果为字符串

    solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)

添加限制条件

     constrained_parameter_desired_value = "MRXJKZYRKMKENFZB" # :string   约束这些数据得到符合要求的
    constraint_expression = constrained_parameter_bitvector == constrained_parameter_desired_value
    solution_state.add_constraints(constraint_expression)#添加约束条件
    )

0x03获取输出(运行到这里的时候的状态)
获取运行到某处程序的格式化输出

stdout_output=state.posix.dumps(sys.stdout.fileno())

获取运行到某处的某内存地址的内容

    constrained_parameter_address = 0x0804A050        #获取对应内存区域在运行到这里的时候的值
    constrained_parameter_size_bytes = 0x10
    constrained_parameter_bitvector = solution_state.memory.load(
      constrained_parameter_address,
      constrained_parameter_size_bytes
    )

获取运行到某处寄存器的值
用于判断运行结果是否符合条件,或者添加限制条件

solution_state.regs.eax != 0

运行到某处获取栈的数据

puts_parameter = state.memory.load(state.regs.esp+4,4, endness=project.arch.memory_endness) #情景是刚刚调用了puts,它的参数在esp+4位置处

获取运行到哪个地址
个人理解应该是类似下断点,不知道可不可以继续往下运行程序

 strncpy_address = 0x08048410
    if state.addr == strncpy_address:

0x04探索
设置探索成功与失败运行到的地址
这里开启了pie

  print_good_address = 0x400000+0x000136D
  will_not_succeed_address = 0x400000+0x000137E
  simulation.explore(find=print_good_address, avoid=will_not_succeed_address)

设置成功和失败函数

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job.'.encode() in stdout_output# :boolean
  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again.'.encode() in stdout_output
  simulation.explore(find=is_successful, avoid=should_abort)

复制一份状态去探测看是否有满足限制条件的值

      #运行到这里所有输入得到的状态都已经有了,现在要做的是从这些输入中判断是否有一种输入满足输出的内容为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
      #复制一个执行状态对象用来探路,直到探到正确的路才让真正的执行流走到这里

简单的虚拟探测功能和上一个一样
两个限制条件为and的关系,两个同时成立

     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
        state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address)# 尝试求解成功添加真正的限制
        return True

0x05设置初始化变量

设置加载开始地址

    start_address=0x804890E
    initial_state=project.factory.blank_state(addr=start_address)  #设置加载地址  blank_state

设置变量
是一堆变量

    password_size_in_bits=32
    password0=claripy.BVS('password0',password_size_in_bits)       #设置二维向量三个

设置寄存器

initial_state.regs.eax=password0 

设置栈数据

ida中的代码

  int v1; // [esp+8h] [ebp-10h]
  int v2; // [esp+Ch] [ebp-Ch]

  __isoc99_scanf("%u %u", &v2, &v1);

在这里插入图片描述

  initial_state.regs.ebp = initial_state.regs.esp
  padding_length_in_bytes = 8  # :integer
  initial_state.regs.esp -= padding_length_in_bytes                  #放入栈中
  initial_state.stack_push(password0)  # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
  initial_state.stack_push(password1)

设置bss段内容(已知内存地址)

  password0_address = 0x09FD92A0                       #把数据放到
  initial_state.memory.store(password0_address, password0)

从scanf0_address加载数据存到scanf0_address 最后一个参数应该表示读取多少

self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)

设置堆内容(运行地址未知)

  fake_heap_address0 = 0x40000
  pointer_to_malloc_memory_address0 =0x09FD92AC
  initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness)        #写一个任意的堆地址,把这个地址写入到变量
  initial_state.memory.store(fake_heap_address0, password0)		#设置堆具体内容

设置文件内容

  password_file = angr.storage.SimFile(filename, content=password)       #把内容放入文件
  initial_state.fs.insert(filename, password_file)

0x06hook方法

hook一个地址
def skip_check_equals_(state):下面定义的代码就是要把check_equals_called_address地址处的难书hook成什么

  check_equals_called_address = 0x80486B3        #这个地址是call的地址参数没有改变,只改变call
  instruction_to_skip_length = 0x05     #不同系统下call指令的大小不同
  @project.hook(check_equals_called_address, length=instruction_to_skip_length)
  def skip_check_equals_(state):      #hook成这个函数

hook一组函数
根据函数名字hook函数,直接获取被hook函数的参数

  class ReplacementCheckEquals(angr.SimProcedure):

    def run(self, to_check,check_data_length):     #to_check,check_data_length代表着被hook的函数的两个参数
			......
	project.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) #开始hook

0x07特殊操作

判断条件
claripy.If(条件,条件为True时的返回值,条件为False时的返回值) => 创建条件判断

    state.regs.eax = claripy.If(
      user_input_string == check_against_string, 
      claripy.BVV(1, 32), 
      claripy.BVV(0, 32)
    )

分割数据给claripy.BVS(‘scanf0’, 4*8)生成的二位向量添加限制

      for char in scanf1.chop(bits=8):      #把整个数据分割每8位为一组

        self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf进来的v4的所有字符为可见字符

判断是否为符号化对象(运行结束找出一个数据进行检测要加的条件)

state.se.symbolic(puts_parameter):#检查这个参数是否为符号化对象

0x08解析特殊文件
输入有很多(比如30个字节)

   simulation=project.factory.simgr(initial_state,veritesting=True)

对自己写的c库函数

用angr自带的hook函数hook为库函数

    project.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']())
    project.hook(0x8048d10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

用angr分析动态链接库文件
加载动态链接库,库函数

  base = 0x400000
  project = angr.Project(path_to_binary, load_options={ 
    'main_opts' : { 
      'custom_base_addr' : base 
    } 
  })    #加载动态链接库

  buffer_pointer=claripy.BVV(0x3000000,32)#创造一个32位的指针,值是任意的但这个值指向输入数据
  validate_function_address = base+0x000006D7
  initial_state = project.factory.call_state(validate_function_address, buffer_pointer,claripy.BVV(8,32))#要调用的库函数和他的参数


0x09注意点
用 state.memory.load(strncpy_src, strncpy_len)函数获取到的值
1.是倒序存储需要完全逆过来还原才能进行字符串比较

		src_contents = state.memory.load(strncpy_src, strncpy_len)



      password_string = "KZYRKMKE"# :string

      does_src_hold_password = src_contents[-1:-64] == password_string     #小端字节序存储用这种方式反转数据

2.需要检查是否是符号化对象

if state.se.symbolic(strncpy_dest):
	是符号化对象
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章