Angr-CTF学习笔记15-17

15_angr_arbitrary_read

汇编代码:

.text:080484C9 main            proc near               ; DATA XREF: _start+17↑o
.text:080484C9
.text:080484C9 input_buffer    = byte ptr -1Ch         ;  注意这个buffer 的大小是16 字节
.text:080484C9 try_again_string_point= dword ptr -0Ch
.text:080484C9 var_4           = dword ptr -4
.text:080484C9 argc            = dword ptr  8
.text:080484C9 argv            = dword ptr  0Ch
.text:080484C9 envp            = dword ptr  10h
.text:080484C9
.text:080484C9 ; __unwind {
.text:080484C9                 lea     ecx, [esp+4]
.text:080484CD                 and     esp, 0FFFFFFF0h
.text:080484D0                 push    dword ptr [ecx-4]
.text:080484D3                 push    ebp
.text:080484D4                 mov     ebp, esp
.text:080484D6                 push    ecx
.text:080484D7                 sub     esp, 24h
.text:080484DA                 mov     eax, try_again 
.text:080484DF                 mov     [ebp+try_again_string_point], eax  ;  把字符串try_again 的指针保存的局部变量try_again_string_point
.text:080484E2                 sub     esp, 0Ch
.text:080484E5                 push    offset aEnterThePasswo ; "Enter the password: "
.text:080484EA                 call    _printf
.text:080484EF                 add     esp, 10h
.text:080484F2                 sub     esp, 4
.text:080484F5                 lea     eax, [ebp+input_buffer]
.text:080484F8                 push    eax
.text:080484F9                 push    offset check_key
.text:080484FE                 push    offset aU20s    ; "%u %20s"
.text:08048503                 call    ___isoc99_scanf  ;  用户input 两个输入:check_key 和20 字节的input_buffer
.text:08048508                 add     esp, 10h
.text:0804850B                 mov     eax, ds:check_key
.text:08048510                 cmp     eax, 228BF7Eh
.text:08048515                 jz      short loc_8048531
.text:08048517                 cmp     eax, 3AD516Ah
.text:0804851C                 jnz     short loc_8048542  ;  这里根据check_key 的输入来进行跳转到不同的puts 中
.text:0804851E                 mov     eax, try_again
.text:08048523                 sub     esp, 0Ch
.text:08048526                 push    eax             ; s
.text:08048527                 call    _puts
.text:0804852C                 add     esp, 10h
.text:0804852F                 jmp     short loc_8048553
.text:08048531 ; ---------------------------------------------------------------------------
.text:08048531
.text:08048531 loc_8048531:                            ; CODE XREF: main+4C↑j
.text:08048531                 mov     eax, [ebp+try_again_string_point]  ;  我们知道,input_buffer 的大小为16 字节,但是scanf() 输入时是20 字节,所以可以导致try_again_string_point 可以被覆盖,于是需要满足条件input_buffer = 0x228BF7E ,我们就可以控制puts 的输出了.
.text:08048534                 sub     esp, 0Ch
.text:08048537                 push    eax             ; s
.text:08048538                 call    _puts
.text:0804853D                 add     esp, 10h
.text:08048540                 jmp     short loc_8048553
.text:08048542 ; ---------------------------------------------------------------------------
.text:08048542
.text:08048542 loc_8048542:                            ; CODE XREF: main+53↑j
.text:08048542                 mov     eax, try_again
.text:08048547                 sub     esp, 0Ch
.text:0804854A                 push    eax             ; s
.text:0804854B                 call    _puts
.text:08048550                 add     esp, 10h
.text:08048553
.text:08048553 loc_8048553:                            ; CODE XREF: main+66↑j
.text:08048553                                         ; main+77↑j
.text:08048553                 nop

从代码主要逻辑可以知道,我们关键的一点在于检查puts() 函数是否接受到了可控的输入.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  initial_state = project.factory.entry_state()

  class ReplacementScanf(angr.SimProcedure):  #  实现Scanf Hook 函数

    def run(self, format_string, check_key_address,input_buffer_address):
      scanf0 = claripy.BVS('scanf0', 4 * 8)   # check_key
      scanf1 = claripy.BVS('scanf1', 20 * 8)  # input_buffer

      for char in scanf1.chop(bits=8):
        self.state.add_constraints(char >= '0', char <= 'z')  #  对input_buffer 的输入约束

      self.state.memory.store(check_key_address, scanf0, endness=project.arch.memory_endness)
      self.state.memory.store(input_buffer_address, scanf1,endness=project.arch.memory_endness)  #  保存求解变量到指定的内存中

      self.state.globals['solution0'] = scanf0  #  保存这两个变量到state 中,后续求解需要用到
      self.state.globals['solution1'] = scanf1

  scanf_symbol = '__isoc99_scanf'
  project.hook_symbol(scanf_symbol, ReplacementScanf())  #  Hook scanf 函数

  def check_puts(state):
    puts_parameter = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness)  #  获取puts() 函数的参数

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

      copied_state = state.copy()  #  复制执行状态上下文进行约束求解,不影响原理的执行上下文

      copied_state.add_constraints(puts_parameter == good_job_string_address)  #  puts 的参数地址是否可以被指定为0x4D525854B ,如果可以的话,那就证明这个值是可控的

      if copied_state.satisfiable():  #  判断添加了上面这个约束是否有解
        state.add_constraints(puts_parameter == good_job_string_address)  #  如果有解的话就保存到我们执行的那个状态对象
        return True 
      else:
        return False
    else:
      return False

  simulation = project.factory.simgr(initial_state)
    
  def is_successful(state):
    puts_address = 0x8048370  #  当程序执行到puts() 函数时,我们就认为路径探索到了这里,然后再去通过check_puts() 判断这里是否存在漏洞,告诉Angr这是不是我们需要找的那条执行路径
    
    if state.addr == puts_address:
      return check_puts(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(solution0,solution1)

Angr函数使用总结:

state.copy() => 复制状态上下文

state.satisfiable() => 判断当前的所有约束是否有解

solution_state.se.eval(求解变量,cast_to=bytes) => 序列化变量内容为字符串

16_angr_arbitrary_write

汇编代码:

.text:08048569 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:08048569                 public main
.text:08048569 main            proc near               ; DATA XREF: _start+17↑o
.text:08048569
.text:08048569 input_buffer    = byte ptr -1Ch
.text:08048569 target_buffer   = dword ptr -0Ch
.text:08048569 var_4           = dword ptr -4
.text:08048569 argc            = dword ptr  8
.text:08048569 argv            = dword ptr  0Ch
.text:08048569 envp            = dword ptr  10h
.text:08048569
.text:08048569 ; __unwind {
.text:08048569                 lea     ecx, [esp+4]
.text:0804856D                 and     esp, 0FFFFFFF0h
.text:08048570                 push    dword ptr [ecx-4]
.text:08048573                 push    ebp
.text:08048574                 mov     ebp, esp
.text:08048576                 push    ecx
.text:08048577                 sub     esp, 24h
.text:0804857A                 mov     [ebp+target_buffer], offset unimportant_buffer
.text:08048581                 sub     esp, 4
.text:08048584                 push    10h             ; n
.text:08048586                 push    0               ; c
.text:08048588                 lea     eax, [ebp+input_buffer]
.text:0804858B                 push    eax             ; s
.text:0804858C                 call    _memset         ;  清空input_buffer 的内容
.text:08048591                 add     esp, 10h
.text:08048594                 sub     esp, 4
.text:08048597                 push    0Ch             ; n
.text:08048599                 push    offset src      ; "PASSWORD"
.text:0804859E                 push    offset password_buffer ; dest
.text:080485A3                 call    _strncpy        ;  复制PASSWORD 到全局内存password_buffer
.text:080485A8                 add     esp, 10h
.text:080485AB                 sub     esp, 0Ch
.text:080485AE                 push    offset aEnterThePasswo ; "Enter the password: "
.text:080485B3                 call    _printf
.text:080485B8                 add     esp, 10h
.text:080485BB                 sub     esp, 4
.text:080485BE                 lea     eax, [ebp+input_buffer]
.text:080485C1                 push    eax
.text:080485C2                 push    offset check_key
.text:080485C7                 push    offset aU20s    ; "%u %20s"
.text:080485CC                 call    ___isoc99_scanf  ;  scanf("%u %20s",check_key,input_buffer) .注意input_buffer 的大小是20 字节,栈上的input_buffer 默认的大小是16 字节,最后4 字节可以覆盖target_buffer .
.text:080485D1                 add     esp, 10h
.text:080485D4                 mov     eax, ds:check_key
.text:080485D9                 cmp     eax, 1A25D71h
.text:080485DE                 jz      short loc_80485E9
.text:080485E0                 cmp     eax, 1CB7D43h
.text:080485E5                 jz      short loc_8048601  ;  根据check_key 的输入来跳转到不同的_strncpy
.text:080485E7                 jmp     short loc_8048618
.text:080485E9 ; ---------------------------------------------------------------------------
.text:080485E9
.text:080485E9 loc_80485E9:                            ; CODE XREF: main+75↑j
.text:080485E9                 sub     esp, 4
.text:080485EC                 push    10h             ; n
.text:080485EE                 lea     eax, [ebp+input_buffer]
.text:080485F1                 push    eax             ; src
.text:080485F2                 push    offset unimportant_buffer ; dest
.text:080485F7                 call    _strncpy
.text:080485FC                 add     esp, 10h
.text:080485FF                 jmp     short loc_804862E
.text:08048601 ; ---------------------------------------------------------------------------
.text:08048601
.text:08048601 loc_8048601:                            ; CODE XREF: main+7C↑j
.text:08048601                 mov     eax, [ebp+target_buffer]  ;  注意这个是MOV 指令,意思是获取EBP + target_buffer 这个地址的内容保存到EAX 中
.text:08048604                 sub     esp, 4
.text:08048607                 push    10h             ; n
.text:08048609                 lea     edx, [ebp+input_buffer]  ;  注意这个是LEA 指令,意思是计算出EBP + input_buffer 的地址保存到EBX 中
.text:0804860C                 push    edx             ; src
.text:0804860D                 push    eax             ; dest
.text:0804860E                 call    _strncpy  ;  漏洞点在这里,strncpy(*target_buffer,input_buffer) ,也就是说input_buffer 最后四字节可以控制对任意地址的_strncpy() .总结起来就是strncpy(input_buffer[ -4 : ],input_buffer,0x10) .
.text:08048613                 add     esp, 10h
.text:08048616                 jmp     short loc_804862E
.text:08048618 ; ---------------------------------------------------------------------------
.text:08048618
.text:08048618 loc_8048618:                            ; CODE XREF: main+7E↑j
.text:08048618                 sub     esp, 4
.text:0804861B                 push    10h             ; n
.text:0804861D                 lea     eax, [ebp+input_buffer]
.text:08048620                 push    eax             ; src
.text:08048621                 push    offset unimportant_buffer ; dest
.text:08048626                 call    _strncpy
.text:0804862B                 add     esp, 10h
.text:0804862E
.text:0804862E loc_804862E:                            ; CODE XREF: main+96↑j
.text:0804862E                                         ; main+AD↑j
.text:0804862E                 nop
.text:0804862F                 sub     esp, 4
.text:08048632                 push    8               ; n
.text:08048634                 push    offset key_string       ; "KZYRKMKE"
.text:08048639                 push    offset password_buffer ; s1
.text:0804863E                 call    _strncmp        ;  我们知道了上面有一个任意地址写之后,我们就需要改写key_string 或者password_buffer 一致,让_strncmp() 返回0 ,跳转到puts("Good Job")
.text:08048643                 add     esp, 10h
.text:08048646                 test    eax, eax
.text:08048648                 jz      short loc_804865C
.text:0804864A                 sub     esp, 0Ch
.text:0804864D                 push    offset s        ; "Try again."
.text:08048652                 call    _puts
.text:08048657                 add     esp, 10h
.text:0804865A                 jmp     short loc_804866C
.text:0804865C ; ---------------------------------------------------------------------------
.text:0804865C
.text:0804865C loc_804865C:                            ; CODE XREF: main+DF↑j
.text:0804865C                 sub     esp, 0Ch
.text:0804865F                 push    offset aGoodJob ; "Good Job."
.text:08048664                 call    _puts
.text:08048669                 add     esp, 10h

汇编代码中的注释已经把整体的逻辑和漏洞原理讲解得差不多了,那么我们就需要做两个判断:一是判断input_buffer 后四字节是否可控;二是前八字节是否可以控制内容为"KZYRKMKE" 或者"PASSWORD" .那么得到的solver.py 代码如下:

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()

  class ReplacementScanf(angr.SimProcedure):

    def run(self, format_string, check_key ,input_buffer):
      scanf0 = claripy.BVS('scanf0', 4 * 8)
      scanf1 = claripy.BVS('scanf1', 20 * 8)

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

      self.state.memory.store(check_key, scanf0, endness=project.arch.memory_endness)
      self.state.memory.store(input_buffer, scanf1, endness=project.arch.memory_endness)

      self.state.globals['solution0'] = scanf0
      self.state.globals['solution1'] = scanf1

  scanf_symbol = '__isoc99_scanf'
  project.hook_symbol(scanf_symbol, ReplacementScanf())

  def check_strncpy(state):
    strncpy_dest = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness)  #  获取strncpy() 的参数,strncpy_dest ..
    strncpy_src  = state.memory.load(state.regs.esp + 8, 4, endness=project.arch.memory_endness)
    strncpy_len  = state.memory.load(state.regs.esp + 12, 4, endness=project.arch.memory_endness)
    src_contents = state.memory.load(strncpy_src, strncpy_len)  #  因为参数中只保存了地址,我们需要根据这个地址去获取内容

    if state.se.symbolic(strncpy_dest) and state.se.symbolic(src_contents) :  #  判断dest 和src 的内容是不是符号化对象
      if state.satisfiable(extra_constraints=(src_contents[ -1 : -64 ] == 'KZYRKMKE' ,strncpy_dest == 0x4D52584C)):  #  尝试求解,其中strncpy_dest == 0x4D52584C 的意思是判断dest 是否可控为password 的地址;src_contents[ -1 : -64 ] == 'KZYRKMKE' 是判断input_buffer 的内容是否可控为'KZYRKMKE' ,因为这块内存是倒序,所以需要通过[ -1 : -64 ] 倒转(contentes 的内容是比特,获取8 字节的大小为:8*8 = 64),然后判断该值是否为字符串'KZYRKMKE'
        state.add_constraints(src_contents[ -1 : -64 ] == 'KZYRKMKE',strncpy_dest == 0x4D52584C)
        return True
      else:
        return False
    else:
      return False

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    strncpy_address = 0x8048410

    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(solution0,solution1)

Angr函数使用总结:

state.satisfiable(extra_constraints=(条件1,条件2)) => 合并多个条件计算是否存在满足约束的解(注意两个或多个条件之间是And 合并判断,不是Or )

17_angr_arbitrary_jump

汇编代码:

.text:4D525886 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:4D525886                 public main
.text:4D525886 main            proc near               ; DATA XREF: _start+17↑o
.text:4D525886
.text:4D525886 var_C           = dword ptr -0Ch
.text:4D525886 var_4           = dword ptr -4
.text:4D525886 argc            = dword ptr  8
.text:4D525886 argv            = dword ptr  0Ch
.text:4D525886 envp            = dword ptr  10h
.text:4D525886
.text:4D525886 ; __unwind {
.text:4D525886                 lea     ecx, [esp+4]
.text:4D52588A                 and     esp, 0FFFFFFF0h
.text:4D52588D                 push    dword ptr [ecx-4]
.text:4D525890                 push    ebp
.text:4D525891                 mov     ebp, esp
.text:4D525893                 push    ecx
.text:4D525894                 sub     esp, 14h
.text:4D525897                 mov     [ebp+var_C], 0
.text:4D52589E                 sub     esp, 0Ch
.text:4D5258A1                 push    offset aEnterThePasswo ; "Enter the password: "
.text:4D5258A6                 call    _printf
.text:4D5258AB                 add     esp, 10h
.text:4D5258AE                 call    read_input  ;  小细节,注意read_input 是stdcall 的调用方法
.text:4D5258B3                 sub     esp, 0Ch
.text:4D5258B6                 push    offset aTryAgain ; "Try again."
.text:4D5258BB                 call    _puts
.text:4D5258C0                 add     esp, 10h
.text:4D5258C3                 mov     eax, 0
.text:4D5258C8                 mov     ecx, [ebp+var_4]
.text:4D5258CB                 leave
.text:4D5258CC                 lea     esp, [ecx-4]
.text:4D5258CF                 retn

main() 函数的逻辑很简单,printf() 输出Enter the password: 然后调用read_input() 函数.继续阅读read_input() 函数的代码:

.text:4D525869 read_input      proc near               ; CODE XREF: main+28↓p
.text:4D525869
.text:4D525869 input_buffer    = byte ptr -2Bh   ;  input_buffer 大小为0x2B
.text:4D525869
.text:4D525869 ; __unwind {
.text:4D525869                 push    ebp
.text:4D52586A                 mov     ebp, esp
.text:4D52586C                 sub     esp, 38h  ;  栈空间在这里分配
.text:4D52586F                 sub     esp, 8
.text:4D525872                 lea     eax, [ebp+input_buffer]  
.text:4D525875                 push    eax
.text:4D525876                 push    offset format   ; "%s"
.text:4D52587B                 call    ___isoc99_scanf ; 注意scanf() 的输入长度是没有限制的
.text:4D525880                 add     esp, 10h
.text:4D525883                 nop
.text:4D525884                 leave
.text:4D525885                 retn

看完read_input() 的代码之后,我们知道这是一个典型的栈溢出覆盖RET 地址的题目,最后要让RET 地址返回到这个位置

.text:4D525849 print_good      proc near
.text:4D525849 ; __unwind {
.text:4D525849                 push    ebp
.text:4D52584A                 mov     ebp, esp
.text:4D52584C                 sub     esp, 8
.text:4D52584F                 sub     esp, 0Ch
.text:4D525852                 push    offset s        ; "Good Job."
.text:4D525857                 call    _puts
.text:4D52585C                 add     esp, 10h
.text:4D52585F                 sub     esp, 0Ch
.text:4D525862                 push    0               ; status
.text:4D525864                 call    _exit

Angr-CTF 解题脚本已经不能在当前的Angr 版本中正常执行了,修改的方法是Hook scanf() 在input_buffer 中构造Vector 进行求解.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()

  simulation = project.factory.simgr(
    initial_state,
    save_unconstrained=True,
    stashes={
      'active' : [initial_state],
      'unconstrained' : [],
      'found' : [],
      'not_needed' : []
    }
  )

  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 >= '0', 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())  #  对scanf() 做Hook

  while (simulation.active or simulation.unconstrained) and (not simulation.found):  #  
    for unconstrained_state in simulation.unconstrained:
      def should_move(s):
        return s is unconstrained_state
      
      simulation.move('unconstrained', 'found', filter_func=should_move)  #  保存

    simulation.step()  #  步进执行

  if simulation.found:
    solution_state = simulation.found[0]

    solution_state.add_constraints(solution_state.regs.eip == 0x4D525849)  #  判断EIP 地址是否可控

    solution = solution_state.se.eval(solution_state.globals['solution'],cast_to = bytes)  #  生成Payload
    print(solution)

点击关注,共同学习!
安全狗的自我修养

github haidragon

https://github.com/haidragon

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值