格式化字符串函数:格式化字符串函数就是将计算机内存中表示的数据转化为我们人类可读的字符串格式
以 printf() 为例,它的第一个参数就是格式化字符串 :"Color %s,Number %d,Float %4.2f"
然后 printf 函数会根据这个格式化字符串来解析对应的其他参数
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数
这种攻击方法最简单,只需要输入一串 %s 就可以
%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
对于每一个 %s,printf() 都会从栈上取一个数字,把该数字视为地址,然后打印出该地址指向的内存内容,由于不可能获取的每一个数字都是地址,所以数字对应的内容可能不存在,或者这个地址是被保护的,那么便会使程序崩溃
在 Linux 中,存取无效的指针会引起进程收到 SIGSEGV 信号,从而使程序非正常终止并产生核心转储
编译一下:gcc -m32 -fno-stack-protector -no-pie -o fs1 1.c
在 printf 函数上面下个断点,然后 r 运行,输入 %08x.%08x.%08x
可以看一下此时的栈空间
可以来看一下栈上的地址
首先是红色的,这是 printf 的返回地址,然后是那一串绿色的,可以看到这后面是之前的一串 %08x.%08x.%08x.%s\n 这是 printf 函数的第一个参数:格式化字符串,printf 函数会根据这个字符串来解析后面的参数
第一个 %08x 解析的是 0x1,也就是源码里面的 a,第二个 %08x 解析的是 0x22222222,peda 显示的有些问题,但是通过 x/wx 0xffffce18 看出来的确实是之前源码里面的 b,第三个 %08x 解析的是 0xffffffff,也就是参数 c:-1,后面的 %s 会把我们输入的内容,也就是 %08x.%08x.%08x 给打印出来
我们执行 c 让程序继续运行,看一下结果
结果跟我们想的一样,同时程序断在了第二个 printf 这里,把我们之前输入的内容作为 格式化字符串,但是这一次没有给他提供其他的参数,但是他同样会在栈上找临近的三个参数,根据 格式化字符串 给打印出来,这样就把他后面三个栈上的值给输出出来了
但是上面的都是获取临近的内容进行输出,我们不可能只要这几个东西,可以通过 %n$x 来获取被视作第 n+1 个参数的值(格式化字符串是第一个参数)
那上个例子来说,如果使用 %3$x 就会打印出第四个参数对应的值
另外也可以通过 %s 来获取栈变量对应的字符串
小技巧总结
之前的方法还只是泄露栈上变量值,没法泄露变量的地址,但是如果我们知道格式化字符串在输出函数调用时是第几个参数,这里假设格式化字符串相对函数调用是第 k 个参数,那我们就可以通过如下方法来获取指定地址 addr 的内容 addr%k$x
下面就是确定格式化字符串是第几个参数了,一般可以通过 [tag]%p%p%p%p%p%p%p%p%p 来实现,如果输出的内容跟我们前面的 tag 重复了,那就说明我们找到了,但是不排除栈上有些其他变量也是这个值,所以可以用一些其他的字符进行再次尝试
比如之前那个例子,输入:AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p
AAAA 对应后面到 0x41414141,也就是格式化字符串的第四个参数
也可以用 AAAA%4$p 来达到同样的效果,通过这种方法,如果我们传入的是 一个函数的 GOT 地址,那么他就可以给我们打印出来函数在内存中的真实地址
使用 objdump -R fs1 查看一下 got 表
通过这段代码,可以把 scanf 的地址给打印出来,%s 是把地址指向的内存内容给打印出来
需要注意的是不能直接在命令行输入 \x14\xa0\x0%4$s 否则 scanf 会把它识别成:\,x,1,4....
%n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量
只要变量对应的地址可写,就可以利用格式化字符串来改变其对应的值
一般来说,利用分为以下的步骤:
关于覆盖偏移的话可以通过测试得出来:
AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p
可以看到,是格式化字符串的第 6 个参数
那下面,通过 %n 来进行覆盖,c_addr+%012d+%6$n
c_addr 再加上 12 之后才能凑够 16,这样就可以把 c 改成 16
如果想要将一个地方改为一个较小的数字,只需要 %n 是 数字 就可以了,如果想改成 2,可以用 aa%k$n,但是有个问题,之前我们是把地址放在前面,加上地址(4或8字节)之后就成了一个至少比 4 大的数
aa%k$nxx,如果用这样的方式,前面 aa%k 是第六个参数,$nxx 是第七个参数,后面在跟一个 我们想要修改的地址,那么这个地址就是第八个参数,只需要把 k 改成 8 就可以把这第八个参数改成 2,aa%8$nxx
这里掌握的小技巧:没有必要把地址放在最前面,只需要找到它对应的偏移就可以
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-4-13 10:49
被yichen115编辑
,原因: 改错