C++ 共享内存ShellCode跨进程传输

在计算机安全领域,ShellCode是一段用于利用系统漏洞或执行特定任务的机器码。为了增加攻击的难度,研究人员经常探索新的传递ShellCode的方式。本文介绍了一种使用共享内存的方法,通过该方法,两个本地进程可以相互传递ShellCode,从而实现一种巧妙的本地传输手段。如果你问我为何在本地了还得这样传,那我只能说在某些时候我们可能会将ShellCode打散,而作为客户端也不需要时时刻刻在本地存放ShellCode代码,这能保证客户端的安全性。

服务端部分

CreateFileMapping

用于创建一个文件映射对象,将文件或者其他内核对象映射到进程的地址空间。这个函数通常用于共享内存的创建。

下面是 CreateFileMapping 函数的基本语法:

HANDLE CreateFileMapping(HANDLE                hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD                 flProtect,DWORD                 dwMaximumSizeHigh,DWORD                 dwMaximumSizeLow,LPCTSTR               lpName
);

参数说明:

  • hFile: 文件句柄,可以是一个磁盘文件或者其他内核对象的句柄。如果是 INVALID_HANDLE_VALUE,则表示创建一个只在内存中的映射,而不与文件关联。
  • lpFileMappingAttributes: 安全属性,一般为 NULL,表示使用默认的安全设置。
  • flProtect: 内存保护选项,指定内存页的保护属性,例如读、写、执行等。常见的值有 PAGE_READONLYPAGE_READWRITEPAGE_EXECUTE_READ 等。
  • dwMaximumSizeHighdwMaximumSizeLow: 指定文件映射对象的最大大小。如果映射的是一个文件,可以通过这两个参数指定文件映射的大小。
  • lpName: 文件映射对象的名字,如果是通过共享内存进行跨进程通信,可以通过这个名字在不同的进程中打开同一个文件映射对象。

成功调用 CreateFileMapping 会返回一个文件映射对象的句柄,失败则返回 NULL。通常创建成功后,可以通过 MapViewOfFile 函数将文件映射对象映射到当前进程的地址空间中,进行读写操作。

MapViewOfFile

用于将一个文件映射对象映射到调用进程的地址空间中,使得进程可以直接操作映射区域的内容。

以下是 MapViewOfFile 函数的基本语法:

LPVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD  dwDesiredAccess,DWORD  dwFileOffsetHigh,DWORD  dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap
);

参数说明:

  • hFileMappingObject: 文件映射对象的句柄,这个句柄通常是通过 CreateFileMapping 函数创建得到的。
  • dwDesiredAccess: 映射区域的访问权限,常见的值有 FILE_MAP_READFILE_MAP_WRITEFILE_MAP_EXECUTE
  • dwFileOffsetHighdwFileOffsetLow: 文件映射的起始位置。在这里,通常指定为0,表示从文件的开头开始映射。
  • dwNumberOfBytesToMap: 指定映射的字节数,通常可以设置为 0 表示映射整个文件。

成功调用 MapViewOfFile 会返回映射视图的起始地址,失败则返回 NULL。映射成功后,可以直接通过返回的地址进行读写操作。当不再需要映射时,应该通过 UnmapViewOfFile 函数解除映射。

CreateMutex

用于创建一个互斥体对象。互斥体(Mutex)是一种同步对象,用于确保在多线程或多进程环境中对资源的互斥访问,防止多个线程或进程同时访问共享资源,以避免数据竞争和冲突。

以下是 CreateMutex 函数的基本语法:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL                  bInitialOwner,LPCTSTR               lpName
);

参数说明:

  • lpMutexAttributes: 一个指向 SECURITY_ATTRIBUTES 结构的指针,决定了互斥体的安全性。通常可以设为 NULL,表示使用默认的安全描述符。
  • bInitialOwner: 一个布尔值,指定互斥体的初始状态。如果设置为 TRUE,表示创建互斥体时已经拥有它,这通常用于创建一个已经锁定的互斥体。如果设置为 FALSE,则表示创建互斥体时未拥有它。
  • lpName: 一个指向包含互斥体名称的空终止字符串的指针。如果为 NULL,则创建一个匿名的互斥体;否则,创建一个具有指定名称的互斥体。通过指定相同的名称,可以在多个进程中共享互斥体。

成功调用 CreateMutex 会返回互斥体对象的句柄,失败则返回 NULL。在使用完互斥体后,应该通过 CloseHandle 函数关闭句柄以释放资源。

CreateEvent

用于创建一个事件对象。事件对象是一种同步对象,用于实现多线程或多进程之间的通信和同步。通过事件对象,可以使一个或多个线程等待某个事件的发生,从而协调它们的执行。

以下是 CreateEvent 函数的基本语法:

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL                  bManualReset,BOOL                  bInitialState,LPCTSTR               lpName
);

参数说明:

  • lpEventAttributes: 一个指向 SECURITY_ATTRIBUTES 结构的指针,决定了事件对象的安全性。通常可以设为 NULL,表示使用默认的安全描述符。
  • bManualReset: 一个布尔值,指定事件对象的复位类型。如果设置为 TRUE,则为手动复位;如果设置为 FALSE,则为自动复位。手动复位的事件需要通过 ResetEvent 函数手动将其重置为非触发状态,而自动复位的事件会在一个等待线程被释放后自动复位为非触发状态。
  • bInitialState: 一个布尔值,指定事件对象的初始状态。如果设置为 TRUE,表示创建事件对象时已经处于触发状态;如果设置为 FALSE,则表示创建事件对象时处于非触发状态。
  • lpName: 一个指向包含事件对象名称的空终止字符串的指针。如果为 NULL,则创建一个匿名的事件对象;否则,创建一个具有指定名称的事件对象。通过指定相同的名称,可以在多个进程中共享事件对象。

成功调用 CreateEvent 会返回事件对象的句柄,失败则返回 NULL。在使用完事件对象后,应该通过 CloseHandle 函数关闭句柄以释放资源。

WaitForSingleObject

用于等待一个或多个内核对象的状态变为 signaled。内核对象可以是事件、互斥体、信号量等等。

以下是 WaitForSingleObject 函数的基本语法:

DWORD WaitForSingleObject(HANDLE hHandle,DWORD  dwMilliseconds
);

参数说明:

  • hHandle: 要等待的内核对象的句柄。可以是事件、互斥体、信号量等。
  • dwMilliseconds: 等待的时间,以毫秒为单位。如果设为 INFINITE,表示无限等待,直到内核对象变为 signaled。

WaitForSingleObject 返回一个 DWORD 类型的值,表示等待的结果。可能的返回值包括:

  • WAIT_OBJECT_0:内核对象已经变为 signaled 状态。
  • WAIT_TIMEOUT:等待时间已过,但内核对象仍然没有变为 signaled 状态。
  • WAIT_FAILED:等待出错,可以通过调用 GetLastError 获取详细错误信息。

这个函数是同步函数,调用它的线程会阻塞,直到等待的对象变为 signaled 状态或者等待时间超时。

ReleaseMutex

用于释放之前由 WaitForSingleObjectWaitForMultipleObjects 等函数获取的互斥体对象的所有权。

以下是 ReleaseMutex 函数的基本语法:

BOOL ReleaseMutex(HANDLE hMutex
);

参数说明:

  • hMutex: 要释放的互斥体对象的句柄。

ReleaseMutex 返回一个 BOOL 类型的值,表示释放互斥体对象是否成功。如果函数成功,返回值为非零;如果函数失败,返回值为零。可以通过调用 GetLastError 获取详细错误信息。

互斥体(Mutex)是一种同步对象,用于控制对共享资源的访问。在多线程或者多进程环境中,互斥体可以确保在同一时刻只有一个线程或者进程能够访问被保护的共享资源。当一个线程或者进程成功获取互斥体的所有权后,其他试图获取该互斥体所有权的线程或者进程将会被阻塞,直到拥有互斥体的线程或者进程调用 ReleaseMutex 释放互斥体所有权。

SetEvent

用于将指定的事件对象的状态设置为 signaled(有信号)。该函数通常与等待函数(如 WaitForSingleObjectWaitForMultipleObjects)一起使用,以实现线程之间或进程之间的同步。

以下是 SetEvent 函数的基本语法:

BOOL SetEvent(HANDLE hEvent
);

参数说明:

  • hEvent: 事件对象的句柄。

SetEvent 函数返回一个 BOOL 类型的值,表示设置事件对象状态是否成功。如果函数成功,返回值为非零;如果函数失败,返回值为零。可以通过调用 GetLastError 获取详细错误信息。

事件对象是一种同步对象,用于在线程或者进程之间发信号。通过 SetEvent 可以将事件对象的状态设置为 signaled,表示某个条件已经满足,其他等待该事件对象的线程或者进程可以继续执行。

有了上述API函数的支持,那么实现这个服务端将变得很容易,如下所示则是服务端完整代码,通过创建一个共享内存池,并等待用户按下简单,当键盘被按下时则会自动填充缓冲区为特定内容。

#include <iostream>
#include <Windows.h>
#define BUF_SIZE 1024HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;char ShellCode[] = "此处是ShellCode";using namespace std;int main(int argc,char *argv[])
{// 创建共享文件句柄HANDLE shareFileHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, "SharedMem");if (shareFileHandle == NULL){return 1;}//映射缓冲区视图,得到指向共享内存的指针LPVOID lpBuf = MapViewOfFile(shareFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);if (lpBuf == NULL){CloseHandle(shareFileHandle);return 1;}// 创建互斥器H_Mutex = CreateMutex(NULL, FALSE, "sm_mutex");H_Event = CreateEvent(NULL, FALSE, FALSE, "sm_event");// 操作共享内存while (true){getchar();// 使用互斥体加锁,获得互斥器的拥有权WaitForSingleObject(H_Mutex, INFINITE);memcpy(lpBuf, ShellCode, strlen(ShellCode) + 1);ReleaseMutex(H_Mutex);                           // 放锁SetEvent(H_Event);                               // 激活等待的进程}CloseHandle(H_Mutex);CloseHandle(H_Event);UnmapViewOfFile(lpBuf);CloseHandle(shareFileHandle);return 0;
}

客户端部分

OpenFileMapping

用于打开一个已存在的文件映射对象,以便将它映射到当前进程的地址空间。文件映射对象是一种用于在多个进程间共享内存数据的机制。

以下是 OpenFileMapping 函数的基本语法:

HANDLE OpenFileMapping(DWORD  dwDesiredAccess,BOOL   bInheritHandle,LPCTSTR lpName
);

参数说明:

  • dwDesiredAccess: 指定对文件映射对象的访问权限。可以使用标准的访问权限标志,如 FILE_MAP_READFILE_MAP_WRITE 等。
  • bInheritHandle: 指定句柄是否可以被子进程继承。如果为 TRUE,子进程将继承句柄;如果为 FALSE,子进程不继承句柄。
  • lpName: 指定文件映射对象的名称。此名称在系统内必须是唯一的。如果是 NULL,函数将打开一个不带名称的文件映射对象。

OpenFileMapping 函数返回一个文件映射对象的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。

OpenEvent

用于打开一个已存在的命名事件对象。事件对象是一种同步对象,用于在多个进程间进行通信和同步。

以下是 OpenEvent 函数的基本语法:

HANDLE OpenEvent(DWORD  dwDesiredAccess,BOOL   bInheritHandle,LPCTSTR lpName
);

参数说明:

  • dwDesiredAccess: 指定对事件对象的访问权限。可以使用标准的访问权限标志,如 EVENT_MODIFY_STATEEVENT_QUERY_STATE 等。
  • bInheritHandle: 指定句柄是否可以被子进程继承。如果为 TRUE,子进程将继承句柄;如果为 FALSE,子进程不继承句柄。
  • lpName: 指定事件对象的名称。此名称在系统内必须是唯一的。如果是 NULL,函数将打开一个不带名称的事件对象。

OpenEvent 函数返回一个事件对象的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。

VirtualAlloc

用于在进程的虚拟地址空间中分配一段内存区域。这个函数通常用于动态分配内存,而且可以选择性地将其初始化为零。

以下是 VirtualAlloc 函数的基本语法:

LPVOID VirtualAlloc(LPVOID lpAddress,SIZE_T dwSize,DWORD  flAllocationType,DWORD  flProtect
);

参数说明:

  • lpAddress: 指定欲分配内存的首地址。如果为 NULL,系统将决定分配的地址。
  • dwSize: 指定欲分配内存的大小,以字节为单位。
  • flAllocationType: 指定分配类型。可以是以下常量之一:
    • MEM_COMMIT:将内存提交为物理存储(RAM或磁盘交换文件)中的一页或多页。
    • MEM_RESERVE:为欲保留的内存保留地址空间而不分配任何物理存储。
    • MEM_RESET:将内存区域的内容初始化为零。必须与 MEM_COMMIT 一起使用。
  • flProtect: 指定内存的访问保护。可以是以下常量之一:
    • PAGE_EXECUTE_READ: 允许读取并执行访问。
    • PAGE_READWRITE: 允许读写访问。

VirtualAlloc 函数返回一个指向分配的内存区域的指针。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。

CreateThread

用于创建一个新的线程。线程是执行程序代码的单一路径,一个进程可以包含多个线程,这些线程可以并发执行。

以下是 CreateThread 函数的基本语法:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES   lpThreadAttributes,SIZE_T                  dwStackSize,LPTHREAD_START_ROUTINE  lpStartAddress,LPVOID                  lpParameter,DWORD                   dwCreationFlags,LPDWORD                 lpThreadId
);

参数说明:

  • lpThreadAttributes: 用于设置线程的安全属性,通常设置为 NULL
  • dwStackSize: 指定线程堆栈的大小,可以设置为 0 使用默认堆栈大小。
  • lpStartAddress: 指定线程函数的地址,新线程将从此地址开始执行。
  • lpParameter: 传递给线程函数的参数。
  • dwCreationFlags: 指定线程的创建标志,通常设置为 0。
  • lpThreadId: 接收新线程的标识符。如果为 NULL,则不接收线程标识符。

CreateThread 函数返回一个新线程的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。

客户端同样创建内存映射,使用服务端创建的内存池,并在里面取出ShellCode执行后反弹,完整代码如下所示;

#include <iostream>
#include <Windows.h>
#include <winbase.h>using namespace std;HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;int main(int argc, char* argv[])
{// 打开共享文件句柄HANDLE sharedFileHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "SharedMem");if (sharedFileHandle == NULL){return 1;}// 映射缓存区视图,得到指向共享内存的指针LPVOID lpBuf = MapViewOfFile(sharedFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);if (lpBuf == NULL){CloseHandle(sharedFileHandle);return 1;}H_Event = OpenEvent(EVENT_ALL_ACCESS, FALSE, "sm_event");if (H_Event == NULL){return 1;}char buffer[4096] = {0};while (1){HANDLE hThread;// 互斥体接收数据并加锁WaitForSingleObject(H_Event, INFINITE);WaitForSingleObject(H_Mutex, INFINITE);            // 使用互斥体加锁memcpy(buffer, lpBuf, strlen((char*)lpBuf) + 1);   // 接收数据到内存ReleaseMutex(H_Mutex);                             // 放锁cout << "接收到的ShellCode: " << buffer << endl;// 注入ShellCode并执行void* ShellCode = VirtualAlloc(0, sizeof(buffer), MEM_COMMIT, PAGE_EXECUTE_READWRITE);CopyMemory(ShellCode, buffer, sizeof(buffer));hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ShellCode, 0, 0, 0);WaitForSingleObject(hThread, INFINITE);}CloseHandle(H_Event);CloseHandle(H_Mutex);UnmapViewOfFile(lpBuf);CloseHandle(sharedFileHandle);return 0;
}

潜在风险和安全建议

虽然这种方法在本地攻击场景中有一定的巧妙性,但也存在潜在的风险。以下是一些建议:

  1. 防御共享内存滥用: 操作系统提供了一些机制,如使用 ACL(访问控制列表)和安全描述符,可以限制对共享内存的访问。合理配置这些机制可以减轻潜在的滥用风险。
  2. 加强系统安全策略: 使用强密码、及时更新系统和应用程序、启用防火墙等都是基础的系统安全策略。这些都有助于防止潜在的Shellcode攻击。
  3. 监控和响应: 部署实时监控和响应系统,能够及时检测到异常行为并采取相应措施,对于减缓潜在威胁的影响十分重要。

总结

本文介绍了通过共享内存传递Shellcode的方法,通过这种巧妙的本地攻击方式,两个进程可以在不直接通信的情况下相互传递Shellcode。然而,使用这种技术需要非常谨慎,以免被滥用用于不当用途。在实际应用中,必须谨慎权衡安全性和便利性,同时配合其他防御措施,确保系统的整体安全性。

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

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

相关文章

前端编码中快速填充内容--乱数假文

写前端页面的时候&#xff0c;如果要快速插入图片&#xff0c;可以使用 https://picsum.photos/ 详见笔者这篇博文&#xff1a; 工具网站&#xff1a;随机生成图片的网站-CSDN博客 可是&#xff0c;如果要快速填充文字内容该怎么做呢&#xff1f; 以前&#xff0c;我们都是…

【EI征稿中|SPIE出版】 第四届传感器与信息技术国际学术会议(ICSI 2024)

第四届传感器与信息技术国际学术会议&#xff08;ICSI 2024&#xff09; 2024 4th International Conference on Sensors and Information Technology&#xff08;ICSI 2024&#xff09; 第四届传感器与信息技术国际学术会议&#xff08;ICSI 2024&#xff09;将于2024年1月5…

Windows server 部署iSCSI共享磁盘搭建故障转移群集

在域环境下&#xff0c;在域控制器中配置iSCSI服务&#xff0c;配置共享网络磁盘&#xff0c;在节点服务器使用共享磁盘&#xff0c;并在节点服务器中搭建故障转移群集&#xff0c;实现故障转移 环境准备 准备3台服务器&#xff0c;配置都是8g2核&#xff0c;50g硬盘&#xf…

微服务开发:断路器详解

微服务是目前业界使用的最重要的实现方面。通过使用微服务架构&#xff0c;开发人员可以消除他们以前在单体应用程序中遇到的许多问题。展望未来&#xff0c;人们开始在微服务中搜索和采用各种模式。大多数时候&#xff0c;新模式的产生是为了解决另一个模式中出现的常见问题。…

Python IPO分析的实现

在软件开发过程中&#xff0c;IPO&#xff08;Input-Process-Output&#xff09;分析是一种常用的方法&#xff0c;用于分析软件系统的输入、处理和输出。Python作为一种功能强大的编程语言&#xff0c;提供了许多工具和库来实现IPO分析。本文将介绍如何使用Python进行IPO分析的…

指针(二)

这里写目录标题 字符指针字符指针与常量字符串的区别&#xff1a; 指针数组数组指针两者的区别&#xff1a;&数组名 &#xff0c;sizeof(arr)数组指针的使用数组参数&#xff0c;指针参数一维数组传参整型数组&#xff1a;整型指针数组&#xff1a; 一级指针传参二级指针传…

基于单片机自动饮料混合机控制系统设计

**单片机设计介绍&#xff0c;基于单片机自动饮料混合机控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机自动饮料混合机控制系统设计是一个涉及多个领域的复杂项目&#xff0c;包括单片机技术、传感器技术…

SQL手工注入漏洞测试(Access数据库)-墨者

———靶场专栏——— 声明&#xff1a;文章由作者weoptions学习或练习过程中的步骤及思路&#xff0c;非正式答案&#xff0c;仅供学习和参考。 靶场背景&#xff1a; 来源&#xff1a; 墨者学院 简介&#xff1a; 安全工程师"墨者"最近在练习SQL手工注入漏洞&#…

用例图是什么?用什么工具绘制?

在软件工程中&#xff0c;用例图属于UML&#xff08;Unified Modeling Language&#xff09;的一种图形模型&#xff0c;是一个交流需求和设计的重要工具。 用例图主要用来展示软件系统功能需求和相关角色之间的交互方式。它描述了一个系统如何与外界进行交互&#xff0c;表示…

MYSQL全语法速查(含示例)

文章目录 1.从简单的查询开始查找所有记录(SELECT *)查找记录中的所有登录名(SELECT)查找登录名为admin的密码(WHERE)查找电话号码非空的记录(IS NOT NULL)查找所在城市为北京或者用户名字是李四的记录(OR)查找所在城市为北京并且用户名字是张三的记录(AND)查找用户名字是李四或…

强化学习第1天:强化学习概述

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​​ 文章目录 介绍 强化学习要素 强化学习任务示例 环境搭建&#xff1a;gym 基本用法 环境信息查看 创建智能体 过程可视化 完整代码 结语…

有线传输介质

目录 1、双绞线 &#xff08;1&#xff09;无屏蔽双绞线 UTP(Unshielded Twisted Pair) &#xff08;2&#xff09;屏蔽双绞线 STP (Shielded Twisted Pair) &#xff08;3&#xff09;布线标准EIA/TIA-568-&#xff21; &#xff08;4&#xff09;双绞线的特点 2、同轴电…

力扣:187. 重复的DNA序列(Python3)

题目&#xff1a; DNA序列 由一系列核苷酸组成&#xff0c;缩写为 A, C, G 和 T.。 例如&#xff0c;"ACGAATTCCG" 是一个 DNA序列 。 在研究 DNA 时&#xff0c;识别 DNA 中的重复序列非常有用。 给定一个表示 DNA序列 的字符串 s &#xff0c;返回所有在 DNA 分子中…

变频电机的负载曲线核对

1.问题导入 月初偶然接触到一个很奇怪的现象&#xff0c;一款变频电机在各个档位下的输入线电流&#xff0c;如果负载不变&#xff0c;无论怎样切换变频器输出频率&#xff0c;电流会保持近乎恒定。这个特性让我感觉很好奇。直觉的理解&#xff0c;因为频率变化&#xff0c;而…

Linux指令学习

目录 1.ls指令 2.pwd命令 3.cd 指令 4. touch指令 5.mkdir指令 6.rmdir指令 && rm 指令 7.man指令 8.cp指令 9.mv指令 10.cat指令 11.more指令 12.less指令 13.head指令 14.find指令&#xff1a; -name 15.grep指令 16.zip/unzip指令&#xff1a; 17.tar…

Windows驱动中使用数字签名验证控制设备访问权限

1. 背景 在一般的驱动开发时&#xff0c;创建了符号链接后在应用层就可以访问打开我们的设备并进行通讯。 但我们有时候不希望非自己的进程访问我们的设备并进行交互&#xff0c;虽然可以使用 IoCreateDeviceSecure 来创建有安全描述符的设备&#xff0c;但大数的用户账户为了方…

二叉树的右视图[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定一个二叉树的 根节点root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出…

西工大计算机学院计算机系统基础实验一(函数编写11~14)

稳住心态不要慌&#xff0c;如果考试周冲突的话&#xff0c;可以直接复制这篇博客和上一篇博客西工大计算机学院计算机系统基础实验一&#xff08;函数编写1~10&#xff09;-CSDN博客最后的代码&#xff0c;然后直接提交&#xff0c;等熬过考试周之后回过头再慢慢做也可以。 第…

如何做好软文推广的选题?媒介盒子分享常见套路

选题是软文推广的重中之重&#xff0c;主题选得好&#xff0c;不仅能够戳到用户&#xff0c;提高转化率&#xff0c;还能让各位运营的写作效率大幅度提升&#xff0c;今天媒介盒子就来和大家分享软文选题的常见套路&#xff0c;助力各位品牌进行选题。 一、 根据产品选题 软文…

C语言KR圣经笔记 4.3 外部变量

4.3 外部变量 一个C程序由一系列的外部对象组成&#xff0c;这些外部对象不是变量就是函数。“外部”这个形容词用于区别于“内部”&#xff0c;后者描述的是函数参数及其内部定义的变量。外部变量在所有函数之外定义&#xff0c;这样就可能会被很多函数使用。函数本身总是外部…