草庐IT

angr_ctf——从0学习angr(四):库操作和溢出漏洞利用

YOASOBI 2023-03-28 原文

angr_ctf项目中后面13~17题没有新的成块的有关angr的知识了,只是对之前题目使用到的模块的扩展和补充,因此就不先列知识点和使用方式了,直接在实战中边讲解边说明

库操作

13_angr_static_binary:静态编译库函数替换

此题的代码与第1题没有区别,但它是静态编译得来的二进制文件,将所有的库函数都写入二进制文件了。

之前在angr_ctf——从0学习angr(三)中对第8题分析时讲到,angr对于库函数只会分出一条路径,而不关心库函数内部是怎样实现的,库函数内部的分支也不会增加angr路径上的分支数量。

这个说法是正确的,但是不太严谨,这是因为angr存在一个符号函数摘要集(symbolic function summaries)

在默认情况下angr会使用SimProcedures里面的符号函数摘要集替换库函数,本质上是在库函数上设置了Hooking,这些hook 函数高效地模仿库函数对状态的影响,就像我们之前在第8题中做的那样。因此angr不进入库函数内部的原因在于,它实际上执行的是hook函数,而hook函数只模仿了库函数对状态的影响,实际内部的操作并没有实现,因此也就不会产生额外分支。

Simprocedures是一个两层结构,第一层表示包名,第二层则是函数名:

 
而此题库函数被静态编译进来了,默认启用的符号函数摘要集作用在动态链接库上,因此此时失效了,在该题中调用的库函数都会进入内部并产生相应的分支,这会大大降低angr的效率。
因此此题的目的在于,手动使用符号函数摘要集替换程序中使用到的库函数
想要获取某个符号函数摘要集中的函数,可以使用下面的代码:
angr.SIM_PROCEDURES['libc']['scanf']()

就可以获取libc包中的scanf函数了,它是一个<SimProcedure scanf>与之前第10题中我们创建的class Hook是同一个类

对于这样的hook函数,可以使用以下两种方式将它hook到目标函数上去:

project.hook(address_of_hooked, angr.SIM_PROCEDURES['libc']['scanf']())

project.hook_symbol('__isoc99_scanf',angr.SIM_PROCEDURES['libc']['scanf']())

一种是传递待hook函数的地址,还有一种是传递函数名。

此外在进入main函数之前,程序会先调用__libc_start_main,它也是库函数,而在创建状态时,如果使用entry_state(),则初始状态就已经经过了__libc_start_main的调用,所以最好也hook掉这个函数,或者使用blank_state手动从main函数开始。

所以此题的解题方式和之前的万能脚本相同,但是需要手动hook一下库函数

angr代码:

import angr
import time
import claripy

time_strat = time.perf_counter()


def good(state):
    tag = b'Good' in state.posix.dumps(1)
    return True if tag else False


def bad(state):
    tag = b'Try' in state.posix.dumps(1)
    return True if tag else False


path_to_binary = './dist/13_angr_static_binary'
p = angr.Project(path_to_binary, auto_load_libs=False)
init_state = p.factory.entry_state()

# 手动hook库函数 p.hook(
0x804ed80, angr.SIM_PROCEDURES['libc']['scanf']()) p.hook(0x804ed40, angr.SIM_PROCEDURES['libc']['printf']()) p.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']()) p.hook(0x8048280, angr.SIM_PROCEDURES['libc']['strcmp']()) p.hook_symbol('__libc_start_main', angr.SIM_PROCEDURES['glibc']['__libc_start_main']()) simgr = p.factory.simgr(init_state) simgr.explore(find=good, avoid=bad) if simgr.found: solution_state = simgr.found[0] flag = solution_state.posix.dumps(0) print(flag)

 

14_angr_shared_library:动态链接库的符号执行

这题不是静态编译了,main函数的逻辑也和13题一样,但是用于混淆输入和比较的函数validate是通过动态链接库调用进来的,因此直接逆向查看动态链接库

此题用万能模板也能暴力破解,但为了练习的目的,我们还是对validate进行符号执行,思路如下:

  1. 模拟validate的函数执行,向它传递参数,参数的类型是一个符号变量
  2. 用explorer()探索路径,直到validate函数返回前
  3. 为状态添加约束,即返回值为1(这样在main函数当中,就能够打印出Good),为状态添加约束可以使用solution_state.add_constraints

模拟validate的函数执行,有两种方法,一种是使用blank_state()手动设定起始位置,并通过布置栈来向validate传递参数,代码如下:

init_state = p.factory.blank_state(addr=validate_addr)

init_state.regs.ebp = init_state.regs.esp
init_state.stack_push(8)
init_state.stack_push(password_addr)
init_state.stack_push(0)

栈的布置需要了解函数调用约定,这里简单解释一下:

  • init_state.regs.ebp = init_state.regs.esp

  这一句是为了初始化ebp,因为采用blank_state来初始化状态的话,大部分寄存器是没有初始化的,处于一个UNINITIALIZED状态,而esp指向栈顶,是有数值的,在第1行代码后打印ebp和esp,结果为:

<BV32 reg_ebp_1_32{UNINITIALIZED}>  <BV32 0x7fff0000>,所以为了使栈结构完整,先让ebp到esp的位置来

  • 先push(8)再push(password_addr)

  这也是函数调用约定决定的,函数的参数从右向左压入栈中,如果不清楚程序采用了哪种函数调用约定,可以通过main函数中,对validate(password, 8)的调用来决定栈的布局

  • 最后push(0)

  实际上这里你随便push啥都可以,这个位置是函数的返回地址。需要这一步的原因是由于,函数返回地址的入栈是在main函数中完成的,也就是call _validate这条指令完成的。而我们设定的初始状态是在动态链接库的validate函数的开始处,也就是跳过了返回地址入栈这一步,因此也要还原回去。

上述方法需要对栈和汇编有一定的了解,angr提供了更方便的从函数处开始执行的方式:

init_state = p.factory.call_state(func_addr, param1, param2)

这样就可以在函数func_addr处开始,传递给该函数的参数则是param1,param2,可以在这里传递保存了符号变量的地址和8

最后还需要注意的一点是,动态链接库在加载时需要重定位,可以在建立项目时用load_options设定重定位的基址,就像这样:

p = angr.Project(path_to_binary,
                 auto_load_libs=False,
                 load_options={'main_opts': {
                     'custom_base_addr': base_addr
                 }})

如果不设立基址,通常angr会默认加载到0x400000处,在IDA中看到的各个指令的地址都只是相对地址,需要加上基址才能找到它们

angr脚本如下:

import angr
import claripy


def good(state):
    tag = b'Good' in state.posix.dumps(1)
    return True if tag else False


def bad(state):
    tag = b'Try' in state.posix.dumps(1)
    return True if tag else False


path_to_binary = './dist/lib14_angr_shared_library.so'
# 设定基址
base_addr = 0x400000
p = angr.Project(path_to_binary,
                 auto_load_libs=False,
                 load_options={'main_opts': {
                     'custom_base_addr': base_addr
                 }})

# validate函数的地址
validate_addr = base_addr + 0x6d7
init_state = p.factory.blank_state(addr=validate_addr)

# 创建符号变量,符号变量保存地址任意,不影响程序运行的地址就行
password = claripy.BVS('password', 8 * 8)
password_addr = base_addr + 0x5000
init_state.memory.store(password_addr, password)

# 布置栈空间
init_state.regs.ebp = init_state.regs.esp
init_state.stack_push(8)
init_state.stack_push(password_addr)
init_state.stack_push(0)

simgr = p.factory.simgr(init_state)
simgr.explore(find=base_addr + 0x783)

if simgr.found:
    solution_state = simgr.found[0]
    # 添加约束并求解,一般函数返回值会保存在eax中,可以通过IDA确认
    solution_state.add_constraints(solution_state.regs.eax == 1)
    print(solution_state.solver.eval(password, cast_to=bytes))
else:
    raise Exception("No solution found")

 

溢出漏洞利用

15_angr_arbitrary_read

题目逻辑很简单,当key等于418108212时执行puts(s),否则puts(try_again),而s的初始值被设定为try_again。

这里有个漏洞,就是scanf没有限制输入的字符个数,且v4的地址比s更低,因此输入字符的长度超过v4的长度时,就可以覆盖s,我们让无敌的chatGPT来分析分析

还是看出来问题了的,当然chatGPT不知道我们想要输出Good,所以没有说出覆盖s这一点

此外还通过shift+F12在地址484f4a47处找到了字符串Good Job,因此直接掏出pwntools

from pwn import *

p = process('./dist/15_angr_arbitrary_read')

Good_addr = 0x484f4a47
payload = b'41810812' + b'a'*0x10 + p32(Good_addr)
p.sendline(payload)
p.interactive()

结果如下:

可以看到打印出了Good Job,解题结束。

 

但又好像没结束,我们是来练习使用angr的,不是来写pwn的。

这题使用angr的解题方式如下:

  1. 首先肯定是让输入符号化,先把scanf函数hook了再说,这里尽管通过逆向能知道key必须等于41810812,但没必要费劲给key传递一个确定的值,因为求解key==41810812只是一眨眼的事,angr的最大敌人是路径太多。v4的长度应该要能够覆盖s,这样实际上s也是一个符号了。
  2. 之后会到puts(s)这里,什么样的状态应该是我们的目标状态吗,是让puts打印出Good吗?传递给puts的参数只是一个符号,puts没办法通过一个符号找到字符串,所以必须在执行puts前停下来
  3. 在puts前停下来如何保证puts能够打印出Good呢?答案是添加约束,让其参数s等于Good的地址。

hook函数的部分不做详细解释,需要注意的是,向一个地址写入字符串时不用管大小端序,也就是不用加上endness=p.arch.memory_endness这个参数。因为尽管字符串的地址在大小端序中的保存方式不同,但是字符串作为一个数组,内部的元素是以大端序保存的,如果当前程序是小端序,添加endness这个参数会导致字符串是反的。

然后,angr在puts前停下时,此时状态对应的地址是多少呢?

在main中,对puts的调用如下

angr是一个基本块一个基本块执行的,因此要么停在0x0804851E要么停在0x08048370,就是不能停在0x08048525这个地址,这个地址不会出现在angr探索的任何一条路径上,因为它不是一个基本块的开始地址。

那么另外两个地址该选哪个呢?0x0804851E处,puts的函数还没有压入栈中,并且puts的参数并不是保存在内存当中的,我们无法获取它在栈中的动态地址,因此只能选0x08048370,然后通过当前状态的esp加上偏移访问参数。

那,偏移是多少?可以看到0x08048370处是jmp指令,此时已经完成了call _puts,也就是说返回地址已经压入栈中了,此时esp应该指向返回地址,所以参数保存在esp + 4当中

angr代码如下:

import angr
import claripy

path_to_binary = './dist/15_angr_arbitrary_read'

p = angr.Project(path_to_binary, auto_load_libs=False)

init_state = p.factory.entry_state()


class Hook(angr.SimProcedure):

    def run(self, str, key_addr, password_addr):
        key_bvs = claripy.BVS('key_bvs', 4 * 8)
        # v4和s相距0x10,再加上s的大小4,一共20个字节
        password_addr_bvs = claripy.BVS('password_addr_bvs', 20 * 8)
        for chr in password_addr_bvs.chop(bits=8):
            self.state.add_constraints(chr >= '0', chr <= 'z')
        self.state.memory.store(key_addr,
                                key_bvs,
                                endness=p.arch.memory_endness)
        # 向地址中写入字符串不用关心大小端序
        self.state.memory.store(password_addr, password_addr_bvs)

        self.state.globals['password'] = password_addr_bvs


p.hook_symbol('__isoc99_scanf', Hook())


def success(state):
    # 此处应为jmp puts的地址
    call_puts_addr = 0x08048370
    if state.addr != call_puts_addr:
        return False

    good_str_addr = 0x484f4a47
    puts_param = state.memory.load(state.regs.esp + 4,
                                   4,
                                   endness=p.arch.memory_endness)

    if state.solver.symbolic(puts_param):
        cp_state = state.copy()
        cp_state.add_constraints(puts_param == good_str_addr)
        # 判断当前状态是否有解,有解就说明找到了目标状态
        if cp_state.satisfiable():
            state.add_constraints(puts_param == good_str_addr)
            return True
        else:
            return False
    else:
        return False


simgr = p.factory.simgr(init_state)
simgr.explore(find=success)

if simgr.found:
    solution_state = simgr.found[0]
    s = solution_state.solver.eval(solution_state.globals['password'],
                                   cast_to=bytes)
    print(s)
else:
    raise Exception("No solution found")

此外对于这段代码:

if state.solver.symbolic(puts_param):
        cp_state = state.copy()
        cp_state.add_constraints(puts_param == good_str_addr)
        # 判断当前状态是否有解,有解就说明找到了目标状态
        if cp_state.satisfiable():
            state.add_constraints(puts_param == good_str_addr)
            return True
        else:
            return False
    else:
        return False

创建一个复制的状态是为了不影响当前状态,如果当前状态还需要进一步执行才能达到目标状态,而你在判断当前状态是否为目标状态时增加了约束,可能会导致错误。

这段代码有一个更简便的写法:

if state.satisfiable(extra_constraints=(is_vulnerable_expression, )):
                
  state.add_constraints(is_vulnerable_expression)
  return True
else:
  return False

在使用satisfiable判断当前状态是否有解时,可以使用extra_constraints添加约束,该约束会跟随状态一起判断是否有解,但该约束不会写入到状态当中。

 

16_angr_arbitrary_write

和15题一模一样,就不解释了

 

17_angr_arbitrary_jump:挟持函数控制流

第17题,先逆向,发现捞的很

一个典中典之scanf("%s"),栈溢出,发现还有一个print_good函数,那么依然是不解释连招

from pwn import *

p = process("./dist/17_angr_arbitrary_jump")
payload = b'a' * 0x20 + p32(0xdeadbeef) + p32(0x42585249)
p.sendline(payload)
p.interactive()

但是我们得用angr来解。

这题的目的是获取一个输入,这个输入能够让某个state的eip变成想要的值,也就是控制程序的控制流。那么就意味着我们要让eip变成某个符号变量,然后求解这个符号变量

eip都变成符号变量了,那么程序该如何执行下一条指令呢?angr对于这种情况,一般会将state放到unconstrained这个stash中,有关stash的介绍可见angr_ctf——从0学习angr(一)。这个stash中的state会被angr默认丢弃以增加效率,而现在我们需要这个stash里面的state。可以在创建SM时保存,就像这样:

simgr = p.factory.simgr(init_state, save_unconstrained=True)

处于unconstrained中的state,如果它在eip等于目标地址(也就是print_good的地址)时,能够有解(满足state.satisfiable为真),这样的状态就是目标状态了,可以将它放入到found这个state中,stash中state的转移也可以参考第一篇内容。

angr在执行时,每次进入新的路径就会有新的state,为了判断这个state是否符合要求,这次不用explorer自动探索了,我们采用step单步执行,然后获取state进行判断。

angr代码如下:

import angr
import claripy


# 下一条指令是print_good的情况下有解的state符合条件
def filter_func(state):
    print_good_addr = 0x42585249
    return state.satisfiable(
        extra_constraints=(state.regs.eip == print_good_addr, ))


path_to_binary = "./dist/17_angr_arbitrary_jump"
proj = angr.Project(path_to_binary)


class SimScanfProcedure(angr.SimProcedure):

    def run(self, fmtstr, input_addr):
        input_bvs = claripy.BVS('input_addr', 200 * 8)
        for chr in input_bvs.chop(bits = 8):
                self.state.add_constraints(chr >= '0', chr <= 'z')
        self.state.memory.store(input_addr, input_bvs)
        self.state.globals['input_val'] = input_bvs


proj.hook_symbol('__isoc99_scanf', SimScanfProcedure())


init_state = proj.factory.entry_state()
# 不丢弃unconstrained中的state
simgr = proj.factory.simgr(init_state,
                           save_unconstrained=True,
                           stashes={
                               'active': [init_state],
                               'unconstrained': [],
                               'found': [],
                           })

while not simgr.found:
    # 如果没有可执行的state,或者没找到unconstrained的state,就退出
    if (not simgr.active) and (not simgr.unconstrained):
        break

    # 把符合filter_func的unconstrained转移到found中
    simgr.move(from_stash='unconstrained',
               to_stash='found',
               filter_func=filter_func)

    simgr.step()

if simgr.found:

    solution_state = simgr.found[0]
    print_good_addr = 0x42585249
    solution_state.add_constraints(solution_state.regs.eip == print_good_addr)
    input_val = solution_state.solver.eval(solution_state.globals['input_val'],
                                           cast_to=bytes)

    print('password: {}'.format(input_val))
else:
    raise Exception('Could not find the solution!')

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

有关angr_ctf——从0学习angr(四):库操作和溢出漏洞利用的更多相关文章

  1. Tomcat AJP 文件包含漏洞(CVE-2020-1938) - 2

    目录1.漏洞简介2、AJP13协议介绍Tomcat主要有两大功能:3.Tomcat远程文件包含漏洞分析4.漏洞复现 5、漏洞分析6.RCE实现的原理1.漏洞简介2020年2月20日,公开CNVD的漏洞公告中发现ApacheTomcat文件包含漏洞(CVE-2020-1938)。ApacheTomcat是Apache开源组织开发的用于处理HTTP服务的项目。ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat上所有webapp目录下的任意文件。该漏洞是一个单独的文件包含漏洞,依赖于Tomcat的AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  4. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  5. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  6. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

    我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

  7. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

    在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

  8. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  9. ruby - 在 Ruby 中是否有一种惯用的方法来操作 2 个数组? - 2

    a=[3,4,7,8,3]b=[5,3,6,8,3]假设数组长度相同,是否有办法使用each或其他一些惯用方法从两个数组的每个元素中获取结果?不使用计数器?例如获取每个元素的乘积:[15,12,42,64,9](0..a.count-1).eachdo|i|太丑了...ruby1.9.3 最佳答案 使用Array.zip怎么样?:>>a=[3,4,7,8,3]=>[3,4,7,8,3]>>b=[5,3,6,8,3]=>[5,3,6,8,3]>>c=[]=>[]>>a.zip(b)do|i,j|c[[3,5],[4,3],[7,6],

  10. ruby-on-rails - 如何让 Rails View 返回其关联的操作名称? - 2

    我有一个非常简单的Controller来管理我的Rails应用程序中的静态页面:classPagesController我怎样才能让View模板返回它自己的名字,这样我就可以做这样的事情:#pricing.html.erb#-->"Pricing"感谢您的帮助。 最佳答案 4.3RoutingParametersTheparamshashwillalwayscontainthe:controllerand:actionkeys,butyoushouldusethemethodscontroller_nameandaction_nam

随机推荐