Pwn 乱刷合集

3※ [SHCTF 2024] No stack overflow2

  • 考点:ret2libc,libc 库查询,整数溢出

  • 在 linux 下使用 checksec 查看该程序开启的保护,发现 Archamd64-64-little,这说明这是一个 64位 的程序,并且采用了 小端 存储,即低位对应低地址,高位对应高地址。

  • 下方的 RELRO ,这是一种通过设置 重定位相关表 的权限为 只读 来防止其被修改的安全机制,我们只关注其对 got 表的影响。Partial RELRO指的是部分开启,此时 got 表被设置为:每个表项只有在未解析过该函数地址前是可写,载入地址后改为只读:

  • 不过我们暂时不关心,把 vuln 文件拖进 IDA64 打开,点击左侧 main,按 F5 反编译,发现程序主要功能是先读入一个长度,接着检测长度小于等于 256 时再读入最多这么长的字节:

  • 我们看 IDA64 提示的存储地址,发现 nbytes_4 长度也是 0x100 即 256 个字节,难道无法溢出吗:

  • 我们发现关键在于传入参数时,将长度转为了 unsigned int 无符号整数,这就使得首位的符号位被当做了数据,那么如果原先输入长度为 -1,二进制对应的 0xFFFFFFFF,那么转为无符号整数后就变为了 2147483647,就可以绕过上方的长度检测了:

  • 那么接下来就是要构造我们的 payload1 了,但是一个个查看左侧函数发现没有 system 相关的,按 Shift+F12 也没有发现 /bin/sh 字符串。

  • 但是发现左侧有 puts 函数,那么我们可以考虑使用和上一题相同的方法:将 puts函数的 got 表地址泄露出来,然后查询其在动态库中的位置,二者相减得到偏移量。 接着就可以先查询动态库中的 system 函数和字符串 /bin/sh的位置,来计算出它们在程序中的位置,然后便可以构造 payload 模拟执行 system("/bin/sh")了。

  • 那么我们先打开 ELF 文件,查询puts函数 plt 表,got表所在的地址,以及 puts 结束后返回的 main 函数的地址:

  • 这里的 process('./vuln') 指的是先不连接服务器,而是用本地文件来模拟,方便我们调试:

  • 然后在第一处输入 -1 绕过长度检测:

  • 但是我们忽然发现一个问题,本题与上一题不同的是,上一题是 32位 程序,所有参数均通过栈来传递,我们只需要将函数的传参压入栈中即可模拟函数执行。但是这题是 64位 程序,函数的前 6个 参数是依次通过 6个 寄存器来传递:rdirsirdxrcxr8r9,之后的更多的参数才用栈来传递。这样使得程序运行的速度提升了不少,但是对于我们栈溢出攻击就不能直接通过栈来传参了,需要修改寄存器的值。


  • 我们可以想到,pop rdi 指令可以将栈内写入的内容传给 rdi这样就可以修改它了,那么如果我们将 pop rdi指令所在的地址,放在函数返回的 ret处,就可以修改 rip下一条指令执行 pop rdi 了!

  • 可是仅仅是这样,rip 跳转之后就回不来了,程序流程整个被打乱,所以我们需要找一个后面紧跟着 ret 指令的 pop rdi,这样下一句还会执行 ret,将此时 rsp 自加过后的栈顶的内容给 rip,不就仍然等同于继续进行栈溢出攻击了吗。

  • 同理,如果想要修改 rsirdx,也需要在程序中找到 pop rsi 随后 ret 的代码片段的地址,以此来传递参数模拟函数执行。这个过程就是所谓的构造 ROP 链。


  • 我们可以通过使用 ropper 工具或是 ROPgadget 工具,在 linux 下快速查找一个文件中出现指定字符串的位置,我们通过使用管道符来查找所有pop 开头到 ret 结尾的字符串,再要求其中含有 rdi,写出以下命令查询:ropper --file vuln --search "pop|ret" | grep "rdi",发现成功找到一个:

  • 记录下这段程序所在的地址 pop_rdi_ret = 0x401223,接下来就可以使用了。由于 puts 函数只需要一个参数,即输出的字符串的地址,我们只需要 rdi 来传参,所以现在可以开始构造 payload1 了:

  • 先填充 0x100即 256个字节 给 nbytes_4,然后因为这是 64位 程序,要填充 8个字节 给 srbp,接下来在 r 处填入我们的 pop rdi;ret 程序的地址:p64(pop_rdi_ret),然后填入要修改的 rid 数据,即 puts 的传参,也就是要输出的字符串的地址:puts_got

  • 然后填充 pop rdi;ret 返回回来后要执行的程序的地址,我们要输出 puts 函数的 got 表里的内容,所以这里填调用的输出函数 puts 的地址:puts_plt

  • 最后填充 puts函数输出完返回回来后要执行的下一个程序的地址,由于我们需要再次利用这里的栈溢出来执行 system("/bin/sh"),所以填 main 函数的首地址。

    image

  • 可以看到此时已经将地址输出出来了,不过都是 \x开头的 16 进制 bytes 数据。由于 64位 程序的地址都是以 \x7f 开头的,并且由于这是个 小端程序,字符串地位置存储在低位置,所以输出出来是倒序的,所以我们可以用 .recvuntil(b'\x7f') 读到 \x7f 为止。

  • 又由于虽然我们是 64位 程序虽然应该有 8 位,但使用时的编码都是以 \x7f 开头的 6位 编码地址,所以我们只要读进来的最后 6个字节:

    image

  • 然后我们要对这个 16进制 的 bytes 数据用 u64() 进行解包,但是如果直接使用的话程序会报错:

    image

  • 这是因为 u64() 每次解包需要输入 \(8个字节\),而刚才的地址不足 8位,我们需要在左侧用.ljust(8,b'\x00')\x00 将其补满 8位:

    image

  • 此时再输出发现就是正确的一个整数地址了:

    image

  • 那么拿到了一个 puts 函数的 got 表的所在地址,接下来我们就可以通过查询动态库中 puts 函数的地址然后计算出偏移量啦。

  • 不过这道题题目并没有把动态库文件直接给我们,我们需要根据泄露出来的 puts 函数的 got 表的所在地址来查询到系统所使用的动态库版本。


  • 我们可以下载使用 LibcSearcher 这个 python 库来打开对应的动态库,只需要提供某个已知函数的具体地址即可:

    image

  • 接下来就和上一题一样了,查询 puts 函数在动态库的地址,计算出偏移量,然后查询 system 函数和 /bin/sh 字符串在动态库的地址,计算出在程序内的地址。不过要注意的是此时使用 LibcSearcher 指令,需要用 .dump("xxx") 来查询某个函数的地址,用 .dump("str_bin_sh") 来查询字符串 /bin/sh 的位置:

    image

  • 此时运行时我们会发现,查找到多个匹配的动态库,程序询问我们要使用哪一个版本的,这是因为先前我们用的是 process('./vuln') 在本地调试。而如果连接到服务器上的时候就不需要我们进行选择了。

  • 不过现在我们需要根据自己的 ubuntu 版本来选择对应的动态库,我们可以先按 Ctrl+C 退出程序,在 linux 中输入 ldd --version 来查询版本:

    image

  • 可以看到第二行,我的是 2.39-0ubuntu8.3,那么再次运行程序,这次就选择这个版本的动态库,填入程序提示的版本前方的编号 0 按回车,可以看到下方有一句该版本 be choosed就成功选择了:

    image

  • 那么完事具备,我们现在已经执行到第二次 main 函数要求我们输入长度的位置了,再次输入 -1,然后开始构造我们第二次的 payload2:

  • 先填充前面 0x108 个字符到 r 处与 payload1 一样,然后通过 pop di;ret 来传递 system 的参数:bin_sh_addr

  • 然后填充要执行的 system 函数的地址: system_addr

  • 最后的返回地址在哪里都无所谓,因为马上要得到系统权限进入交互模式了,并不会返回回来用上,直接不填。

    image

  • 但是!此时运行会发现并没有得到系统权限,反而报错退出了。这是因为这是采用了新的高版本的 gcc 编译器的 64位 系统,其在调用动态库中的 system 函数时,对 rsp 有额外的要求:

  • 在准备进入 system 函数时,会对此时的 rsp 也就是栈顶进行一次检验,要求此时指向的地址必须能被 16 整除,也就是必须以 0 结尾,否则报错退出不予调用。

  • 我们进入 IDA64 的 main 函数,点击 nbytes_4 查看栈空间,发现我们填充到 r 的位置以 8 结尾:

    image

  • 所以此时放在 r 中的 pop rdi;ret 的地址以 8 结尾,接下来 /bin/sh 字符串的地址以 0 结尾,而 system函数的地址以 8 结尾,就无法通过高版本的 rsp 检验。

  • 那么我们需要再调用 system 函数之前额外填充一个 某段程序的地址,这样在执行 system 函数时 rsp就以 0 结尾了。

  • 最简单的就是找一个只有一句 ret 指令的地址,rip 执行原先函数的ret 跳转到这里后,下一句还是将执行 ret,没有区别,但是此时 rsp 已经自加了一次。

  • 所以我们用 ropper 指令寻找一个只有一句 ret 的程序,在 linux 下输入 ropper --file vuln --search "ret" 查找:

    image

  • 记录下程序的位置 ret = 0x40101a,接下来只需要在调用 system 之前多填充一个 ret 的地址即可:

    image

  • 此时运行完程序,在选择动态库版本输入 0 后,我们发现已经进入了交互模式,输入 ls 可以看到当前目录下的文件,大功告成:

    image

  • 最后调整为远程连接服务器,ls 一下发现有 flagcat flag 获取 flag:

    image

  • 最后放上完整 exp(调整了一下顺序):

    image

  • 除了使用 LibcSearcher 在线查询动态库之外,我们还可以使用一个在线网站将服务器所使用的动态库下载下来:https://libc.rip/,使用的时候只需要输入,泄露的函数的名称,和泄露出来的函数的地址的后三位(16进制)即可:

    image

  • 当然,如果用 puts 函数查不到对应的版本的话,可以试着用别的函数查询,网站的内容有时候明没有更新到最新(这里就是,我换成了 read函数 ):

    image

  • 然后可以下载下来,本地进行调试(当然我们不知道服务器用的是哪一个,这只限于本地调试代码用的下载)。



3※ No stack overflow2 pro

  • 考点:libc 静态链接

  • 这题题目首先提示了,使用了静态链接,也就是将动态链接直接写入了程序中,这样就没有 plt 表和 got 表供我们使用了。

  • 首先在 linux 下用 checksec vuln 查看文件保护情况:

    image

  • 发现是 64位 小端程序,开了 Partial RELRO,开了 NX 保护,这个就是不允许执行存放在数据段的代码,也就是为什么我们之前,都要费尽心思往栈里面写别的程序的地址的原因:代码直接放在栈里面不允许执行。

  • 接着是 Stack:Canary found ,这是指开启了 Canary保护:在进入函数前生成一个校验码压入栈中,在函数返回时检测校验码是否被修改,若被修改则判断栈发生了改变收到了溢出攻击,自动结束程序。这是对栈溢出攻击的防护。

  • 那么接下来我们拖入 IDA64 中,发现左边乱七八糟一大堆,这正是因为静态链接引起的,将所有动态库里的函数全写进来了,如果查看过这个文件的大小的话,会发现它远远大于我们之前使用动态库连接技术的文件的大小:

    image

  • 我们找到加黑了的 main,点击进入,F5 反编译,发现和上一题的代码一模一样,都是输入一个长度,然后转化为有符号的 int来进行判断大小,接着往 v9 中存入不超过先前读入的长度的字节。很明显这里存在着和前几题一样的栈溢出:

    image

  • 那么我们记得先前有提到 Canary保护,点开 v9 查看栈结构找找 校验值 存在哪里,但是发现 v9 下面直接就是 sr 了,并没有找到 Canary保护的校验值存储的位置,那么就不需要理会了,直接正常溢出即可执行我们想执行的程序,也就是所谓的劫持程序。

  • 我们需要模拟 system("/bin/sh") ,这在动态库里本质是输入指令syscall,所以我们就需要一个写着 syscall指令的地址,用 ropper --file vuln --search "syscall" 进行查找:

  • 发现很多个,我们随便选哪个地址都可以,因为执行完 syscall 指令后我们会获得系统权限进入交互模式,就不用管 syscall指令之后还有什么了,可以选最后一个 syscall_addr = 0x41cbf6

  • 接下来我们要找字符串 /bin/sh,按 Shift+F12,按 alt+T 查找字符串 /bin/sh,发现并没有跳转,不存在现成的字符串:

    image

  • 所以我们只能自己找一个地址,往里面写入字符串 /bin/sh。首先我们需要找一个有读和写权限的段,因为既要写进去也要读出来使用。我们按 Shift+F7 打开段视图,一般使用 .bss 段,BSS 段通常是指用来存放程序中 未初始化 的或者 \(初始化为0\) 的 全局变量 和 静态变量 也就是说,只要初始值为 0 的类型,都会先放在这里,等到再次赋值时才会被取出。所以写在这里面可以全局使用。

  • 我们点开 .bss 段,随便复制一个起始位置,bss_addr = 0x4E72C0

  • 那么接下来我们要往里面写数据,可以调用 read 函数,在左侧下方输入 read 查找函数位置:

  • 点进去,复制函数入口位置,read_addr = 0x44FD90

  • 我们发现 read 函数需要三个参数,由于这是 64位 程序通过寄存器传参,所以和上一题一样我们要去寻找 pop rdi;retpop rsi;retpop rdx;ret 的程序的存放位置,来改变寄存器的值为 read函数传参:

  • 在 linux 下用 ropper --file vuln --search "pop|ret" | grep "rdi" 来找与 rdi 相关的指令,在一大堆结果中找到紧挨着 ret 的程序,pop_rdi_ret = 0x4022bf

  • 在 linux 下用 ropper --file vuln --search "pop|ret" | grep "rsi" 来找与 rsi 相关的指令,同理找紧挨着 ret的程序,pop_rsi_ret = 0x40a32e

  • 在 linux 下用 ropper --file vuln --search "pop|ret" | grep "rdx" 来找与 rsi 相关的指令时,发现没有紧挨着 ret的程序,我们找一个离 ret最近的程序,中间仍然多了一个 pop rbx,不过也可以用,每次多传一个 0 给 rbx 即可,pop_rdx_rbx_ret = 0x49D06B

  • 最后在程序内找到 main的起始地址,因为第一次溢出后我们输入字符串 /bin/sh 还需要第二次溢出来执行 system("/bin/sh")main_addr = 0x401B7A

  • 那么我们可以开始构造第一次溢出的 payload1 了,需要注意的是,这一个程序输入长度的时候用 (unsigned int) 输入,判断的时候转为 (int) 判断,所以我们需要输入 2147483679,对应的二进制转化为 (int) 就是 -1:

  • 然后构造 payload1,先用 0x100 + 0x08 个字节填充到 r 处,然后用寄存器为 read 函数传参:

  • 第一个参数表示读取的文件,为 0 表示从控制台读入,我们 pop_rdi_ret 后传 0

  • 第二个参数为存放的地址,我们 pop_rsi_ret 后传 bss_addr

  • 第三个参数为最大的写入长度,可以大一点,我们 pop_rdx_rbx_ret 后传 0x100,然后传 0 给多的 pop rbx

  • 最后填充函数结束后返回的地址 main_addr

  • 且慢,这是 64 程序,需要检验一下调用 main 函数之前,rsp 是否指向的地址末尾为 0,可以简单数一下 main 函数是第 9 条指令,第一条指令以 8 结尾,此时 main 也以 8 结尾,无法通过检验。我们需要再填充一条指令进去,这就是所谓的平衡栈操作。

  • 同上一题,我们再用 ropper --file vuln --search "ret" 找一下 ret 指令的位置,取单独的指令,ret = 0x454257

  • 那么我们此时在进入 main之前加一个 ret 指令的地址来平衡栈,构造出 payload1:

  • 运行可以看到此时程序成功执行了 read 函数,在输入一串字符后重新开始执行 main 函数了:

    image

  • 接下来我们可以往这个 .bss 段里面写入 /bin/sh 字符串了,需要注意的是字符串需要一个结束标识符 \x00,所以我们往里面写的应该是 b'/bin/sh\x00'

  • 接着重新再来一遍 main 函数,还是先输入 2147483649 绕过长度判断,然后开始构造 payload2 来执行我们的 system("/bin/sh")


  • syscall 的本质是系统通过调用这条指令时 rax 里面的值,来执行不同的函数,我们执行 system("/bin/sh")时需要令 rax = 0x3b 来执行 execve语句。(在 32位 系统中,是用 int 80h 代替 syscall,同时令 eax = 0x0b),所以我们需要找到修改 rax 寄存器的代码段 pop rax;ret,和之前的那些一样,都是所谓的 gadget。

  • 在 linux 中用 ropper --file vuln --search "pop|ret" | grep "rax" 来查找,pop_rax_ret = 0x4507f7

  • 那么接下来就可以继续构造我们的 payload2 了,先填充 0x108 个字符到 r,然后在 pop_rax_ret 后传 0x3b 修改 rax

  • 接着为 syscall 传参数,一共有三个参数:

    第一个参数是字符串地址,pop_rdi_ret 后传 bss_addr

    第二和第三个参数涉及系统内核取参方式,系统空间与用户空间之间的协议,都设为 0 默认即可。

  • pop_rsi_ret 后传 0,pop_rdx_rbx_ret 后传 0 ,再传 0 给 rbx

  • 最后填入 syscall_addr 的地址,来调用系统的 syscall 功能。

  • 此时算一下是否栈平衡,我们数一下 syscall的地址为第 10 个指令,此时 rsp 末位为 0,无需调整。

  • 运行成功获得系统权限,进入交互模式,输入 ls 成功输出当前目录下的内容:

  • 转为远程连接服务器再次运行,输入 cat flag 获得 flag:

  • 最后附上完整 exp(调整了下顺序):

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/841096.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL原理简介—7.redo日志的底层原理

大纲 1.redo日志对事务提交后数据不丢失的意义 2.redo日志文件的构成 3.redo日志会写入到Redo Log Blcok中 4.redo日志如何写入到Redo Log Blcok中 5.Redo Log Buffer解析 6.Redo Log Buffer的刷盘时机 7.undo log回滚日志原理1.redo日志对事务提交后数据不丢失的意义 (1)redo …

MySQL原理简介—8.MySQL并发事务处理

大纲 1.简单总结增删改SQL语句的实现原理 2.多个事务同时执行的场景遇到的问题 3.多个事务并发更新或查询时可能出现的问题 4.SQL标准中对事务的4个隔离级别 5.MySQL是如何支持4种事务隔离级别的 6.Spring事务注解了如何设置隔离级别 7.uodo log多版本链介绍 8.基于undo log多版…

python复习笔记——2024.11.25

2024.11.25 一、类的定义二、类与实例的关系# 定义一个猫类,age, name, color 是属性,或者称为成员变量 class Cat:age=Nonename=Nonecolor=Nonecat1=Cat() #通过对象名.属性名,可以给各个属性赋值 cat1.name="小白" cat2,age=2 cat3.color="白色"print…

Logisim-019-4位先行进位电路

仓库地址 https://gitee.com/gitliang/logisim-to-cpu

Java学习笔记——2024.11.25

2024.11.25 一、Java_DOS原理 1.DOS基本原理 创建文件夹=>md d:\\xxx 消除文件夹=>rd d:\\xxx2.相对路径和绝对路径=>相对路径:从当前目录开始定位,形成的一个路径 =>绝对路径:从顶级目录d,开始定位,形成的路径举例子:相对路径:..\ ..\abc2 \ test200\hello…

Jenkins实现CICD之邮箱告警

作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 目录一.Jenkins配置邮箱告警1 安装支持邮件配置的Mailer插件2 配置QQ邮箱3 配置系统管理员邮箱地址4 发送测试邮件二.Jenkins配置钉钉告警 一.Jenkins配置邮箱告警 1 安装支持邮件配置的Mailer插件如上图所示…

MATLAB贝叶斯优化混合Bayes-CNN-RNN分析股票市场数据与浅层网络超参数优化

全文链接:https://tecdat.cn/?p=38354 原文出处:拓端数据部落公众号 本文旨在介绍一种利用贝叶斯优化方法来优化混合 CNN - RNN 和浅层网络超参数的简单方法,并展示了如何使贝叶斯优化器考虑离散值。通过对股票市场数据的模拟与分析,阐述了网络构建、数据预处理、超参数优…

银河英雄传说

[NOI2002] 银河英雄传说 题目背景 公元 \(5801\) 年,地球居民迁至金牛座 \(\alpha\) 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。 宇宙历 \(799\) 年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱…

Linux系统进程

系统进程 【1】、进程基本概述 当我们运行一个程序,那么我们将运行的程序叫进程 ​ PS1:当程序运行为进程后,系统会为该进程分配内存,以及进程运行的身份和权限 ​ PS2:在进程运行的过程中,服务器上会有各种状态来表示当前进程的指标信息 程序和进程的区别 ​ 程序是数…

桌面软件识别截图区域识别实例

first :1、使用pyautogui截图+拼接 2、使用lackey 截图+识别import pyautogui """整凭截图""" im1 = pyautogui.screenshot() im2 = pyautogui.screenshot(my_screenshot.png) """区域截图""" import pyautogui…

WEB攻防-XSS跨站CSP策略HttpOnly属性Filter过滤器标签闭合事件触发

1.CSP(Content Security Policy 内容安全策略) 内容安全策略是一种可信白名单机制,来限制网站中是否可以包含某来源内容。 该制度明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单, 它的实现和执行全部由浏览器完成,开发者只需提供配置。 禁止加载外域代码,防…