fprintf_chk 绕过

admin 2023年10月9日17:52:08评论53 views字数 3573阅读11分54秒阅读模式
fprintf_chk会检查%n,如果%n不是只读区域的地址就会报错,相对来说可以安全的克制格式化字符串任意写,但是没有绝对安全的代码。

前置知识:

1、标准输入输出的流

这个文件句柄,是每个线程创建的时候,自动创建的,自动绑定到文件描述符0,1,2上的,定义如下:

#define stdin (&sched_getstreams()->sl_streams[0])
#define stdout (&sched_getstreams()->sl_streams[1])
#define stderr (&sched_getstreams()->sl_streams[2])

2、fopen会使用IO_FILE结构体,返回值是文件读写的首地址或NULL,open返回文件描述符

FAR FILE *fopen(FAR const char *path, FAR const char *mode)
//打开文件描述符
fd = open(path, oflags, 0666);

//把文件句柄绑定到os中
-> ret = fs_fdopen(fd, oflags, NULL);

//动态文件缓冲区, 确定start和end的位置,
stream->fs_bufstart = group_malloc(tcb->group, CONFIG_STDIO_BUFFER_SIZE);
stream->fs_bufend = &stream->fs_bufstart[CONFIG_STDIO_BUFFER_SIZE];
stream->fs_bufpos = stream->fs_bufstart;
stream->fs_bufread = stream->fs_bufstart;

//绑定文件描述到os的流中
stream->fs_fd = fd;
};

在2.23下检测逻辑如下:

LABEL(form_number) : if (s->_flags2 & _IO_FLAGS2_FORTIFY) {
if (!readonly_format) {

extern int __readonly_area(const void *, size_t) attribute_hidden;

readonly_format = __readonly_area(format, ((STR_LEN(format) + 1) * sizeof(CHAR_T)));

}
if (readonly_format < 0)

__libc_fatal("*** %n in writable segment detected ***n");

}



可以看到如果readonly_format小于0就会出错,而readonly_format是 __readonly_area这个函数的返回值,这个函数字面意思就是读取只读区域,就是判断格式化字符串的地址是不是只读位置。我们再来看看这个函数源代码:


__readonly_area:

int __readonly_area(const char *ptr, size_t size) {

const void *ptr_end = ptr + size;

FILE *fp = fopen("/proc/self/maps", "rce");

if (fp == NULL) {

/* It is the system administrator's choice to not have /proc

available to this process (e.g., because it runs in a chroot

environment. Don't fail in this case. */

if (errno == ENOENT

/* The kernel has a bug in that a process is denied access

to the /proc filesystem if it is set[ug]id. There has

been no willingness to change this in the kernel so

far. */

|| errno == EACCES)

return 1;

return -1;

}

/* We need no locking. */

__fsetlocking(fp, FSETLOCKING_BYCALLER);

char *line = NULL;

size_t linelen = 0;

while (!feof_unlocked(fp)) {

if (_IO_getdelim(&line, &linelen, 'n', fp) <= 0)

break;

char *p;

uintptr_t from = strtoul(line, &p, 16);

if (p == line || *p++ != '-')

break;

char *q;

uintptr_t to = strtoul(p, &q, 16);

if (q == p || *q++ != ' ')

break;

if (from < (uintptr_t) ptr_end && to > (uintptr_t) ptr) {

/* Found an entry that at least partially covers the area. */

if (*q++ != 'r' || *q++ != '-')

break;

if (from <= (uintptr_t) ptr && to >= (uintptr_t) ptr_end) {

size = 0;

break;

} else if (from <= (uintptr_t) ptr)

size -= to - (uintptr_t) ptr;

else if (to >= (uintptr_t) ptr_end)

size -= (uintptr_t) ptr_end - from;

else

size -= to - from;

if (!size)

break;

}

}

fclose(fp);

free(line);

/* If the whole area between ptr and ptr_end is covered by read-only

VMAs, return 1. Otherwise return -1. */

return size == 0 ? 1 : -1;

}


首先FILE *fp = fopen("/proc/self/maps", "rce");,通过fopen读取maps文件,后续通过文件来和格式化字符串的地址做判断,是否在只读区域,也就是说在可写区域的段上是不能写入%n的,否则会返回-1,导致readonly_format于0。

那么怎么去绕过呢?
fopen的源代码可以发现,也是调用了open,而open返回的文件描述符,如果我们把文件描述符改为0,就成了标准输入。看下__IO_FILE结构体源码:

_IO_FILE * _IO_file_open (_IO_FILE *fp, const char *filename, int posix_mode, int prot, int read_write, int is32not64) {

int fdesc;
#ifdef _LIBC

if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))

fdesc = open_not_cancel (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);

else

fdesc = open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
#else

fdesc = open (filename, posix_mode, prot);
#endif

if (fdesc < 0)

return NULL;

fp->_fileno = fdesc;

_IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);

if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS)) == (_IO_IS_APPENDING | _IO_NO_READS)) {

_IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);

if (new_pos == _IO_pos_BAD && errno != ESPIPE) {

close_not_cancel (fdesc);

return NULL;

}

}

_IO_link_in ((struct _IO_FILE_plus *) fp);

return fp;

}

libc_hidden_def (_IO_file_open)


首先open将文件描述符给了fdesc,然后赋值给了file结构体的_fileno( fp->_fileno = fdesc;),也就是说让 open 函数返回 0 就会使 __readonly_area 程序从标准输入中读取数据,这时候我们只需要从标准输入中传入 000000000000-7fffffffffff r-xp 00000000 00:00 0 /bin/vm 即可绕过 %n 检测。

原文始发于微信公众号(由由学习吧):fprintf_chk 绕过

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月9日17:52:08
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   fprintf_chk 绕过http://cn-sec.com/archives/2096976.html

发表评论

匿名网友 填写信息