概念:
printf("格式化字符"变量(指针、整形等变量));
printf(变量);
C语言格式化字符:
输出字符,配上%n可用于向指定地址写数据。
输出十进制整数,配上%n可用于向指定地址写数据。
输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要偏移i处8字节长的16进制数据。32bit和64bit环境下一样。
输出16进制数据,与%x基本一样,只是附加了前级0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32biti不是64bit。
输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串。在32bit和64bit环境下一样,可用于读取GOT表等信息。
将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100x%10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节。
表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
文件解析:
获取到的文件样本。
可以丢到kali下尝试运行,并查看文件信息。
1、./pwn7运行
2、查看文件类型命令:
file pwn7
可见是32位下的Linux文件。
3、查看保护机制:
checksec --file=pwn7
-
RELRO:开启则无法修改got表。
-
Stack:开启则无法直接覆盖EIP让程序任意跳转,跳转后会进行cookie校验;但这项保护可以被绕过。
-
NX:开启则shellcode无法被执行。
-
PIE:开启在每次程序运行地址都会变化,未开启则返回值括号内是程序的基址。
4、用32位编辑器查看:
获取到两个信息,一个是.ELF格式的文件,即Linux平台下的可执行文件。另一个是文件的编译信息,即GCC,C语言的文件。那么就可能存在有格式化字符串的溢出。
5、readelf -h 可读取 elf 文件头。头文件包含如下:
readelf -h
-
ELF 魔数。
-
文件机器字节长度。
-
数据存储方式。
-
版本。
-
运行平台。
-
ABI 版本。
-
ELF 重定位类型。
-
硬件平台。
-
硬件平台版本。
-
入口地址(目标文件入口地址为 0,只有相对位置)。
-
程序的入口和长度。
-
段表的位置和长度。
可见程序入口地址为0x8048450。
IDA逆向:
定位到入口地址,向下翻找到了main函数。
空格切换到图形模式分析。
可见输入输出套在一个死循环里,基本可以判断为格式化字符串的溢出。
Got表修改与Libc基址偏移:
Got表与PLT表:
还是使用gdb来进行观察。(详细gdb安装与使用教程在文章末尾)
第一个jmp跳转的就是函数对应的Got表。
类似于一个间接寻址的过程。
我们就把获取数据段存放函数地址的那一小段代码称为PLT(过程链接表)
存放函数地址的数据段称为GOT(全局偏移表)
所以如果我们可以把printf的Got表指向改为system地址,那么就可以执行命令,反弹shell。但是我们不知道system的地址。
因为Got表中的内容,会在_dl_runtimw_resolve之前和之后,将真正的函数地址,也就是glibc运行库中的函数的地址,回写到代码段,就是got[n](n>=3)中。
也就是说在函数第一次调用时,才通过连接器动态解析并加载到.got.plt中,而这个过程称之为延时加载或者惰性加载。
Libc基址偏移获取system Got表地址
glibc是linux下面c标准库的实现,即GNU C Library。glibc本身是GNU旗下的C标准库,后来逐渐成为了Linux的标准c库,而Linux下原来的标准c库Linux libc逐渐不再被维护。
Linux下面的标准c库不仅有这一个,如uclibc、klibc,但是glibc无疑是用得最多的。glibc在/lib目录下的.so文件为libc.so.6。
获取函数在libc中的偏移量:
libc已知:
libc已知的情况,可以通过反编译libc获取地址。利用radare分析libc文件,可以获取libc中write的偏移地址
也可以通过pwntools的ELF类,加载libc文件来获取目标函数的偏移地址。
libc未知:
libc未知的情况下,需要确定libc的版本号。
同一个版本的libc对应的函数的实际地址是一样的,因此通过收集所有libc库的实际函数的地址,就利用泄露的函数的实际地址确定libc版本,从而进一步获取libc中函数的偏移地址。
PY中可以使用LibSearcher库。
因为函数的实际地址=libc基址+函数在libc中偏移量,而在Got表的介绍中我们可以拿到函数的实际地址,就可以计算得到libc的基址,从而计算得到libc的偏移量,最后推算出system的实际地址。
GDB+pwndbg+fmtarg获取libc偏移量:
1、安装GDB用于程序的调试:
apt install gdb
2、pwndbg安装:
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
如果是手动下载的话需要初始化git仓库,git init 。
3、pwdgdb安装:
fmtarg是属于pwdgdb中的一个偏移计算的工具。
cd ~/
git clone https://github.com/scwuaptx/Pwngdb.git
cp ~/Pwngdb/.gdbinit ~/
4、配置dbg与gdb共存环境
vim ~/.gdbinit
5、执行gdb pwn7调试程序
6、使用b printf在printf处下断点
7、输入r执行程序
8、程序提示用户输入,输入123456,此时程序运行到printf语句停止
9、输入stack 60命令查看栈情况
10、使用fmtarg 0xffffd17c算出偏移:
poc编写的是获取的输入的Got表实际地址,所以这里计算的是输入的偏移
11、编写poc获取shell
详细说明:
12、python3执行脚本
由于库pwn仅存在于3.0以上版本,所以需要使用python3
成功拿到shell
原文始发于微信公众号(黑熊安全):格式化字符串溢出原理详解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论