格式化字符串(fmt)的利用
些占位符大部分在c语言里学过了我们漏洞利用常用的就是%s%p%hn%hhn%n%c以及格式化修饰符$与*及一些数字我就只讲这些常用的其他的如果不知道那就去看c语言吧。占位符首先是%s它的作用就是把栈中值当作指针进行解引用取出那个指针指向的值出来到\x00为止%p则是把栈中的值直接打印出来比如我现在栈中是一个printf的got表的地址如果用%s就会把printf的真实地址打印出来因为got表是指向plt表的延迟绑定机制而我用%p的话就会把printf的got表的地址打印出来这就是他们的区别。具体的利用的话比如%s加入got表可以泄露真实地址而%p可以把canary打印出来。接下来是%n%hn,%hhn这三个就是用格式化字符串写入区别是%n一次写四个字节%hn一次写两个字节%hhn一次写一个字节都经常配合%c一起用。%c直接使用就会输出一个空格。%n%hn,%hhn都是把输出的字符数写入栈中某个指针所指向的地址比如说我栈里放的还是printf的got表的地址我就可以用%n%hn,%hhn把printf的got表指向printf的真实地址给改了比如改成system这样当系统想再次调用printf的时候call它就不会调用printf了因为他call一个函数也要利用一个二级指针我们修改后printf的got表指向的不是printf的真实地址了而是system函数的地址那么此时他就会调用system而不是printf。修饰符$是参数索引分隔符用于指定参数的位置。正常32位系统通过栈传参也就是第一个参数的位置就是我们输入的位置而64位系统线通过六个寄存器传参也就是正常我们的输入在第六个位置此时就可以用6$去对我们输入的值进行相关操作而*就是滞后赋值。比如说printf(%*d,5,10);就会把5放回*的位置实际效果其实等于printf(%5d,10);而数字在$前就是指定参数相对于第一个参数的偏移在%c中就是指定输出的宽度如果直接%100c就会打印100个空格。至于输出于修饰符排列的顺序我就不细讲了不知道可以去看c语言。漏洞利用原理一般存在格式化字符串漏洞就是如printf(buf)其中本来需要有格式化占位符但却没有反而其中的内容还是我们的变量名那此时假如我们输入一个占位符进去系统不会判断这个占位符是不是我们输入的还是自带的他只会直接根据对应的说明符进行对应的操作这就给我们很大的利用空间。漏洞利用姿势栈上下面我们来看格式化字符串具体如何利用先看泄露canaryPCTF2025的week1-ret2bzdr首先checksec一下发现开了nx和canary保护然后放ida看一眼这里我们发现存在格式化字符串漏洞而且也存在栈溢出同时fgets限制了我们格式化字符串输入的大小那么我们就打栈溢出而栈溢出关键就是要泄露出canary所以我们nc进去输入AAAA%n$p先找到变量s在栈上的位置发现在n6时找到了我们输入的AAAA而s到v2(canary)的距离为0x88即17个偏移所以canary就在第23个位置所以用%23$p就能泄露出canary接下来我们找是否有后门函数在ida看见这是一个后门函数虽然存在黑名单过滤但注意到他只过滤了bin没有过滤sh所以直接再输入sh即可getshell完整exp如下from pwn import * premote(challenge1.pctf.top,30959) p.recvuntil(can u solve canary?) p.sendline(b%23$p) leakp.recvline().strip() canaryint(leak,16) print(fCanary: 0x{canary:x}) ret0x40101a payload136*bap64(canary)ba*8p64(ret)p64(0x4013AD) p.sendline(payload) p.sendlineafter(OH,NO!!!HACKER!!!DONT COME!!!,bsh) p.interactive()BaseCTF2024新生赛format_string_level0这里我们先chekcsec保护全开大概是看题目本身了接下来放ida看看这里看见首先分配了一个堆然后用一个指针接收其起始地址然后open打开了flag文件接下来用read把他写入了ptr指向的处or都有了w就交给我们用格式化字符串完成了。我们gdb调试一下看看我们可以看见这个测试flag就写在距我们rsp偏移为2的位置,而且是一个指针指向的值所以我们可以用%s读出来而我们%6$s距rsp偏移就为0所以我们用%8$s就可以把flag读出来了我们nc上去看看BaseCTF2024新生赛format_string_level1首先还是checksec开了canary与nxrelro没全开放ida看看明显的格式化字符串漏洞我们看看后面的函数是什么orw都来了相当于只要进了这个函数flag就会自动出现在我们的屏幕上我们看看这个target在哪在bss段上那就很简单了我们先输入aa%7$hhnp64bss段的地址就可以了为什么因为前面要输两个字符因为要确保前面把栈的单元空间填满64位是8字节32位是4字节否则我们bss的地址就不能完整的放在栈上就不能写入了而且这个target的值没有限制的只要不是0那if判断就会为真就会执行后面的orw而且注意不能把bss段地址放前面因为其高位两字节是00printf读完bss的地址就直接终止了这样我们格式化字符串就不能发挥作用了。exp如下from pwn import * import sys from ctypes import * context.log_leveldebug context.archamd64 elfELF(./pwn) libc ELF(./libc.so.6) flag 1 if flag: p remote(challenge.imxbt.cn,30337) else: p process(./pwn) sa lambda s,n : p.sendafter(s,n) sla lambda s,n : p.sendlineafter(s,n) sl lambda s : p.sendline(s) sd lambda s : p.send(s) rc lambda n : p.recv(n) ru lambda s : p.recvuntil(s) ti lambda : p.interactive() leak lambda name,addr :log.success(name---hex(addr)) def dbg(): gdb.attach(p) pause() bss0x4040B0 paybaa%7$hhnp64(bss) sd(pay) ti()效果如下BaseCTF2024新生赛format_string_level2其实这题才是重点前面的只是一些基础这题我会讲的非常非常细因为只要明白了这个后面的基本可以自己写了哪怕是非栈上格式化字符串也万变不离其宗。下面我们开始先checksec