ctf 格式化字符串漏洞

对printf族函数错误使用导致的漏洞

维基百科

语法:

n$ (unix平台上C拓展的语法)   n是使用此格式说明符显示的参数数,允许使用不同的格式说明符或以不同的顺序多次输出提供的参数。如果任何单个占位符指定了一个参数,则所有其他占位符也必须指定一个参数。

hh  对于整数类型,使printf需要一个整数大小的整型参数,该参数是从char升级而来的。

 对于整数类型,使printf需要一个整数大小的整型参数,该参数是从short升级而来的。

 不打印任何内容,但将迄今为止成功写入的字符数写入整数指针参数。

 

32 位

参数都通过栈传递.从最右开始向左依次入栈,最左侧参数地址最低,最靠近返回地址的位置。最右侧参数的的地址最高。

printf遇到格式化字符串时去栈找参数

 

64位

整型参数从左向右依次放在 rdi rsi rdx rcx r8 r9  
浮点参数从左向右依次放在 xmm0~xmm7
更多的参数通过栈传递.

 

漏洞在哪里

如果格式化字符串和参数列表不符合会怎样?
传参多了会怎样?
传参少了会怎样?

void leak()
{
    char str[128] ;
    printf("%p %p %p %p %p %p %p %p\n");//把栈内容打印出来了
}

 

如果格式化字符串可控?

一般来说,在格式化字符串漏洞中,读取的格式化字符串都是在栈上的。

void vul()
{
    char star[128];         
    fgets(str,128,stdin);
    printf(str);//不安全写法    
}

程序崩溃

%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
泄露栈内存

从栈顶开始泄露

%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p (打印栈里的指针值)

泄露任意地址内容

利用%n$s

格式化字符串放在主调函数栈上,而printf从主调函数栈顶开始向栈底读取参数,所以格式化字符串本身一定在printf的某个参数对应的位置上。

那么格式化字符串是我们自己输入的,因此我们可以控制该格式化字符串。


如果我们知道该格式化字符串的内容在printf调用时对应的是第几个参数,那我们就可以通过如下的
方式来获取某个指定地址addr的内容。这里假设该格式化字符串相对函数调用为第k个参数,其中
addr为地址的机器表示,即p32(address)
"addr%k$s"  //把第k个参数作为地址,输出该地址指向的内容。
但是,并不是说所有的偏移机器字长的整数倍,可以让我们直接相应参数来获取,有时候,我们需要对
我们输入的格式化字符串进行填充,来使得我们想要打印的地址内容的地址位于机器字长整数倍的地址
处。"[padding][addr]"

 

覆盖任意地址内存

利用  %n$n
%n,不输出字符,但是把己经成功输出的字符个数写入对应的整型指针参数所指的变量。

指定任意地址的原理和泄露内存的原理相同,都是在格式化字符串中包含地址然后用$去指定参数偏移。

所以我们需要填充好到达%n时printf输出的字符个数,技巧:%nc其中n为数字,这样可以输出n个字符。

因为需要填充输出字符个数,为了方便生成,一般把地址放到格式化字符串的最后面,这样比较方便填充字符。

(实际攻击中%n$hn和%n$n不常见,因为这样会导致print的输出过长,阻塞网络难以完成攻击,通常都
使用写入一个字节的%n$hhn,然后使用多个单字节写入合用来修改一个机器字长的变量)

 

64位?

64位下前6个参数通过寄存器传递,所以前六个参数要跳过之后,然后才会从主调函数的栈顶开始读取
参数(第一个参数为格式化字符串所以还有5个参数在寄存器里需要跳过才开始从栈中读取参
数 '%p%p%p%p%p'+payload)
64位下地址的高16位一定为0,会把格式化字符串截断。
解决:地址放后面

 

可以做什么?

任意地址读取

泄露栈内存,获取栈上保存的基指针值,计算出栈的地址
泄露栈内存,获取函数返回地址值,计算开启PIE的ELF基址(主调函数为程序ELF中函数,返回地址指
向程序代码段的某个位置)
泄栈内存,获取canary
或者计算libc基址(main的主调函数为_libc_start_main所以main的返回地址指向
_libc_start_main的某个位置)
泄露GOT,获取libc基址

 

任意地址写入:

Partial RELRO 时篡改GOT

结合泄露栈地址,篡改函数栈(修改返回地址ROP,修改保存的基指针栈迁移)

 

利用 : pwntools

生成payload

pwnlib.fmtstr,fmtstr_payload(offset ,writes,numbwritten=0,write_size='byte')    ->str

 

自动利用

class pwnlib.fmstr.FmtStr(execute_fmt,offset=None,padlen=0,numbwritten=0);

 

 

 

 

posted @ 2021-03-07 13:10  KnowledgePorter  阅读(414)  评论(0)    收藏  举报