北京深盾科技股份有限公司 https://shell.virbox.com 加壳工具,加壳软件,软件加密,加密工具,应用加固,IOT应用加固 Tue, 30 Dec 2025 02:45:43 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.9.4 https://shell.virbox.com/wp-content/uploads/2025/07/cropped-icon-1-32x32.png 北京深盾科技股份有限公司 https://shell.virbox.com 32 32 如何使用 C++ error_code 处理错误 https://shell.virbox.com/2025/12/30/%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8-c-error_code-%e5%a4%84%e7%90%86%e9%94%99%e8%af%af/ https://shell.virbox.com/2025/12/30/%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8-c-error_code-%e5%a4%84%e7%90%86%e9%94%99%e8%af%af/#respond Tue, 30 Dec 2025 02:45:41 +0000 https://shell.virbox.com/?p=3151 概述

std::error_code 是 C++ 标准库提供的一种不依赖异常的错误表示与传播机制。它以值类型的方式封装错误信息,适合在禁用异常、系统级编程或跨库边界的场景中使用。通过 std::error_code,函数可以在不抛出异常的情况下,将失败原因安全、明确地返回给调用方。

在现代 C++ 中,std::error_code 已经成为标准库中无异常接口的基础设施,并且被广泛认为是未来 expected<T, E> 等结果类型中默认的错误表示形式。

std::error_code 基本知识

在 C++ 程序中,错误处理通常有三种方式:通过返回值表示失败、通过异常抛出错误、通过额外的错误对象传递错误信息。返回值方式往往表达能力有限,异常在某些工程环境中不可用或不可取,而 std::error_code 正是为第三种方式提供的标准化方案。

std::error_code 最早在 C++11 中引入,目的是在保持类型安全和可组合性的同时,提供一种不使用异常的错误处理机制。

从概念上看,一个 std::error_code 对象由两部分组成:一个整数值和一个错误类别。整数值表示具体的错误编号,而错误类别用于说明这个编号属于哪个错误域。错误域可以理解为错误编号的来源或语义空间,例如 POSIX 错误、文件系统错误或某个库私有的错误集合。

这种设计解决了单纯使用整数错误码时的歧义问题。相同的整数值在不同错误域中可以表示完全不同的错误含义,而 std::error_code 可以同时携带这两方面的信息。

std::error_code 是一个轻量级的值类型,可以被拷贝、赋值和按值传递。默认构造的 std::error_code 表示“无错误”状态,其错误值为 0。

std::error_code 提供了显式的 operator bool(),用于判断是否存在错误。当错误值不为 0 时,该对象在布尔上下文中为 true,表示发生了错误;当错误值为 0 时,表示成功。

此外,还可以通过 value() 获取原始错误值,通过 category() 获取错误类别,通过 message() 获取面向用户的错误描述字符串。需要注意的是,message() 仅用于展示,不应参与程序逻辑判断。

为什么需要 std::error_code

尽管异常是 C++ 语言内建的错误处理机制,但在实际工程中,并非所有环境都适合使用异常。某些系统级项目会在编译时禁用异常,以减少二进制体积或避免运行时开销;某些库需要与 C 接口或其他语言交互,异常无法跨越语言边界;还有一些代码需要明确地表达每一步可能失败的结果。

在这些场景中,异常并不是合适的工具,而 std::error_code 提供了一种标准、可移植的替代方案。

与传统的返回整数错误码相比,std::error_code 具备更强的类型信息和更清晰的语义。错误值不再是孤立的整数,而是与错误域绑定,从而避免了不同模块之间的冲突。错误对象可以被直接传递和存储,不依赖全局状态,也不需要线程局部存储。

C++17 标准库中的 <filesystem> 提供了大量同时支持抛异常和不抛异常的接口。非抛异常版本通常通过额外的 std::error_code& 参数来报告错误。此外,std::from_chars 等低层工具函数也采用了类似的设计思想。

这些接口的存在,使得 std::error_code 成为现代 C++ 程序中不可忽视的一部分。

如何使用 std::error_code

最常见的使用方式,是调用那些接受 std::error_code& 参数的函数。调用方在调用前提供一个 std::error_code 对象,函数在执行完成后会根据结果对其进行设置。如果操作成功,错误码会被清空;如果失败,错误码会被设置为对应的错误值。

调用完成后,调用方通过检查该对象即可判断是否发生错误,并据此采取相应处理逻辑。

std::error_code 采用一个重要的约定:错误值为 0 表示成功,任何非 0 值表示失败。基于这一约定,operator bool() 的实现非常直接,判断条件也十分清晰。

这一约定在使用时必须被严格遵守。自定义错误码时,必须确保不会将 0 分配给任何失败情况,否则将导致判断逻辑出现错误。

在编写库或模块时,往往需要定义属于自身的错误集合。推荐的做法是使用 enum class 定义错误码枚举类型,并明确指定一个表示成功的枚举值,其底层值为 0。

这些枚举值仅用于表示“发生了哪种错误”,而不包含错误域信息。错误域由后续的 error_category 提供。

为了让自定义的错误枚举能够自动转换为 std::error_code,需要完成两步工作。第一步是为该枚举类型特化 std::is_error_code_enum,将其标记为错误码枚举。第二步是提供一个名为 make_error_code 的自由函数,用于根据枚举值构造对应的 std::error_code 对象。

完成这两步之后,该枚举类型就可以在需要 std::error_code 的地方被隐式使用。这种设计依赖于参数相关查找机制,使得错误码的构造过程对调用方透明。

一个完整示例

假设需要为一个简单的文件处理模块定义错误码。首先定义一个错误码枚举类型,其中明确保留一个成功值:

enum class FileError {
   success = 0,
   not_found = 1,
   permission_denied = 2
};

接下来,需要定义一个对应的错误类别。该类别继承自 std::error_category,并提供类别名称和错误消息描述:

class FileErrorCategory : public std::error_category {
public:
   const char* name() const noexcept override {
       return "file_error";
  }

   std::string message(int ev) const override {
       switch (static_cast<FileError>(ev)) {
       case FileError::success:
           return "success";
       case FileError::not_found:
           return "file not found";
       case FileError::permission_denied:
           return "permission denied";
       default:
           return "unknown file error";
      }
  }
};

然后提供一个返回该类别实例的函数,并确保该实例在程序中唯一存在:

const std::error_category& file_error_category() {
   static FileErrorCategory instance;
   return instance;
}

接下来,将枚举类型标记为错误码枚举,并提供构造函数:

namespace std {
template <>
struct is_error_code_enum<FileError> : true_type {};
}

std::error_code make_error_code(FileError e) {
   return std::error_code(static_cast<int>(e), file_error_category());
}

完成上述步骤后,就可以在接口中自然地使用 std::error_code 了。例如:

std::error_code open_file(const std::string& path) {
   if (path.empty()) {
       return FileError::not_found;
  }
   return FileError::success;
}

调用方只需要检查返回的错误码即可判断结果:

std::error_code ec = open_file("data.txt");
if (ec) {
   std::cerr << ec.message() << std::endl;
}

使用 std::error_code 的基本原则

在实际使用中,std::error_code 应当仅用于表示“发生了什么错误”,而不是携带复杂的上下文信息。错误码的比较应当基于枚举值或布尔语义,而不应依赖错误消息字符串。

此外,错误码的设计应当保持稳定性。一旦错误值和错误类别对外暴露,就应避免随意更改其含义,以免破坏调用方的判断逻辑。

总结

std::error_code 提供了一种标准化、类型安全且不依赖异常的错误处理方式。它通过将错误值与错误域绑定,解决了传统错误码的歧义问题,并在标准库中得到了实际应用。

在需要无异常接口的场景中,正确地定义错误枚举、错误类别,并遵循“0 表示成功”的约定,可以使 std::error_code 成为一种可靠而清晰的错误传递工具。随着 C++ 标准库向结果类型演进,std::error_code 仍将在很长一段时间内扮演重要角色。

无论是错误处理机制的设计,还是核心业务逻辑的实现,C++ 代码的安全性始终是商业项目的重要考量。在交付可执行程序或SDK时,除了保证功能正确性,还需要防范逆向工程和代码篡改的风险。对于需要加强保护的 C++ 应用程序,推荐使用 Virbox Protector 进行专业的代码加密和混淆,它能有效防止反编译分析,保护知识产权,为您的软件加上一道坚固的安全防线。

]]>
https://shell.virbox.com/2025/12/30/%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8-c-error_code-%e5%a4%84%e7%90%86%e9%94%99%e8%af%af/feed/ 0
文件属性篡改:一种容易被忽视却极具风险的安全问题 https://shell.virbox.com/2025/12/29/%e6%96%87%e4%bb%b6%e5%b1%9e%e6%80%a7%e7%af%a1%e6%94%b9%ef%bc%9a%e4%b8%80%e7%a7%8d%e5%ae%b9%e6%98%93%e8%a2%ab%e5%bf%bd%e8%a7%86%e5%8d%b4%e6%9e%81%e5%85%b7%e9%a3%8e%e9%99%a9%e7%9a%84%e5%ae%89%e5%85%a8/ https://shell.virbox.com/2025/12/29/%e6%96%87%e4%bb%b6%e5%b1%9e%e6%80%a7%e7%af%a1%e6%94%b9%ef%bc%9a%e4%b8%80%e7%a7%8d%e5%ae%b9%e6%98%93%e8%a2%ab%e5%bf%bd%e8%a7%86%e5%8d%b4%e6%9e%81%e5%85%b7%e9%a3%8e%e9%99%a9%e7%9a%84%e5%ae%89%e5%85%a8/#respond Mon, 29 Dec 2025 03:26:16 +0000 https://shell.virbox.com/?p=3149 在日常安全分析和运维管理中,文件通常被视为可信的对象。我们习惯通过文件的创建时间、修改时间、所有者信息以及权限配置来判断文件是否异常,甚至据此还原安全事件的发生过程。然而,在真实攻击场景中,这些看似客观的文件属性,本身就可能成为被操纵的对象。

文件属性篡改,指的是攻击者在未经授权的情况下,对文件的元数据进行修改。这些元数据并不影响文件能否被打开或执行,却深刻影响着系统、安全工具以及分析人员对文件的认知。一旦这些信息被恶意更改,文件就可能在保持原有功能的同时,呈现出完全虚假的历史和身份。

文件属性为何会成为攻击目标

从攻击者角度看,直接删除日志或大规模破坏系统,往往会迅速触发警告。而对文件属性的修改则更加隐蔽,也更具迷惑性。

通过篡改文件的创建时间或修改时间,攻击者可以掩盖真实的入侵时间和地点,使恶意文件看起来像是系统早期就已存在的组件,降低在时间线分析中的异常程度。通过调整文件所有者或权限,恶意文件可以在不引起注意的情况下获得执行或访问能力,同时避免触发权限异常告警。

更进一步,文件属性还隐含着文件来源信息。当这些信息被伪造后,即便文件内容本身存在风险,其表面特征也可能让分析人员误判为正常文件,从而延误处置时机。

被篡改的文件属性如何被恶意利用

在实际攻击中,文件属性篡改往往不是单独出现,而是服务于更复杂的攻击目标。

恶意软件在落地系统后,常会主动修改自身文件的时间属性,使其与系统中其他正常文件保持一致,从而规避基于时间异常的安全检测。一些攻击还会利用文件属性来绕过安全检查逻辑,例如让文件看起来并非「新更新的文件」,从而避开只关注新增或近期修改文件的扫描策略。

在网络传输场景中,文件属性的伪造同样具有价值。通过改变文件类型标识、扩展属性或访问权限,攻击者可以让恶意文件在传输和存储过程中被识别为普通业务文件,降低被防火墙或入侵检测系统拦截的概率。

这些行为的共同点在于,它们并不依赖复杂漏洞,而是利用安全体系对文件元数据的信任假设。

文件内容篡改与属性篡改的配合

文件属性篡改并不等同于文件内容篡改,但两者在攻击中经常配合使用。

例如,在对可执行文件进行二进制层面的修改后,如果文件属性仍然显示为近期变更,这本身就可能暴露异常。因此,攻击者往往会同步调整时间戳、版本信息等属性,让被修改过的文件在外观上保持合理状态。这种配合并不是为了炫技,而是为了减少被发现的概率。

从防御角度看,这意味着仅关注文件是否被修改是不够的,还需要警惕修改后的文件是否在属性层面被刻意隐藏差异。

对安全分析和取证的实际影响

文件属性一旦失真,其影响会贯穿整个安全流程。

在事件响应阶段,错误的时间信息可能导致调查人员对攻击顺序产生误判,进而影响处置决策。在自动化防御系统中,被篡改的属性可能直接导致检测逻辑失效,使恶意文件绕过扫描或清理机制。

更隐蔽的问题在于,这类篡改往往不会立刻显现危害,而是在后续分析或复盘时才暴露出矛盾。一旦关键判断建立在错误的文件属性之上,修正成本将大幅提高。

应对文件属性篡改的现实思路

在防止文件被篡改方面,关键在于减少其被静默修改的空间。通过部署文件完整性校验机制,可以在文件落盘后建立可信基线,使后续任何未授权的内容变更都难以被忽略。数字签名则从来源层面限制了篡改的可行性,一旦文件内容被修改,其签名状态就会失效,从而阻断被篡改文件继续以合法文件身份存在的可能。配合严格的版本控制管理,可以明确文件的发布和更新路径,避免文件在脱离管理流程的情况下被替换或修改。

在系统层面,这些措施的价值并不在于完全杜绝攻击,而在于提高篡改行为的成本和暴露概率。当文件的内容、来源和版本都受到约束时,攻击者即使具备修改能力,也很难在不留下痕迹的情况下完成篡改,从而显著降低文件被长期滥用的风险。

在高级攻击面前,一个更根本的挑战在于:攻击者首先需要获得修改文件的能力。他们往往通过逆向分析、内存篡改或利用软件漏洞,先瓦解程序本身的保护机制。

因此,一个更为主动和彻底的防御策略,是将保护措施前置到软件分发包本身。通过对关键业务逻辑进行代码混淆、虚拟化保护,以及对核心二进制文件进行高强度加密与完整性绑定,可以从根源上大幅提升攻击者进行静态分析、动态调试和内存篡改的难度。即使攻击者突破了系统层的部分防线,面对一个被牢固保护的应用程序,他们想要精准定位并篡改特定文件属性或函数逻辑,其成本和时间将变得难以承受。

Virbox Protector 正是为此而生的专业级应用安全解决方案。它不仅能实现传统的文件完整性校验,更能为您的 Windows、Linux 平台的可执行程序(EXE)、动态库(DLL/SO)、以及 .NET/Java 程序提供深度的代码保护,包括:

  • 高级代码混淆与虚拟化:将关键代码转换为难以理解的虚拟指令,有效防止逆向工程,保护算法和业务逻辑。
  • 二进制加密与压缩:加固程序本身,防止被非法脱壳、调试和内存Dump。
  • 反调试与反篡改:实时检测调试器、代码注入等攻击行为,一旦发现异常即触发保护动作。
  • 灵活的授权与访问控制:可与许可管理结合,控制谁、在何种条件下可以运行程序,从使用源头杜绝非法拷贝和分发。

将 Virbox Protector 集成到软件发布流程中,许多时候就不需要再去被动地检测篡改,而是在主动地预防篡改发生,让文件属性篡改这类依赖于深度分析和修改的攻击,在第一步就困难重重。

总结

文件属性篡改之所以危险,并不在于技术门槛有多高,而在于它精准利用了安全体系中的信任盲区。攻击者并不需要制造明显破坏,只需让文件在错误的信息基础上显得合理,就足以干扰检测和分析。

在当前的攻防环境下,重新审视文件属性的可信性,是提升整体安全能力不可回避的一步。只有当我们不再默认元数据天然可信,才能更早识别那些隐藏在虚假表象之下的真实风险。

借助一些专业的加固工具,如 Virbox Protector,也可以预防文件属性被恶意篡改。

]]>
https://shell.virbox.com/2025/12/29/%e6%96%87%e4%bb%b6%e5%b1%9e%e6%80%a7%e7%af%a1%e6%94%b9%ef%bc%9a%e4%b8%80%e7%a7%8d%e5%ae%b9%e6%98%93%e8%a2%ab%e5%bf%bd%e8%a7%86%e5%8d%b4%e6%9e%81%e5%85%b7%e9%a3%8e%e9%99%a9%e7%9a%84%e5%ae%89%e5%85%a8/feed/ 0
docker常用命令总结 https://shell.virbox.com/2025/12/25/docker%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4%e6%80%bb%e7%bb%93/ https://shell.virbox.com/2025/12/25/docker%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4%e6%80%bb%e7%bb%93/#respond Thu, 25 Dec 2025 05:50:17 +0000 https://shell.virbox.com/?p=3146 由于docker轻便的特性,其广泛应用于软件生命周期的各个环节,软件的开发、测试甚至产品发布都会使用到docker,这里便总结一些docker经常使用的一些命令,方便应对使用docker时需要的的各个场景。

镜像管理

1.拉取镜像

docker pull 镜像名:标签

# 下载特定版本的 Ubuntu
docker pull ubuntu:22.04        

2.删除镜像

docker rmi 镜像名:标签 根据镜像名删除镜像

docker rmi 镜像ID 根据进行ID删除镜像

3.构建镜像

docker build 镜像名

# 使用当前路径下的Dockerfile构建镜像名为my-app:v1的镜像
docker build -t my-app:v1.0 .  

4.tar包操作

  • docker load xxx.tar# 将my-app-v1.0.tar文件导入为镜像
    docker load -i my-app-v1.0.tar
  • docker save -o xxx.tar 镜像名# 将my-app:v1的镜像导出为tar文件
    docker save -o my-app-v1.0.tar my-app:v1  

容器管理

1.创建容器

docker run [选项] 镜像名 [命令]

# 创建Ubuntu22.04容器并启动容器终端
docker run -it ubuntu:22.04 /bin/bash  

[核心选项]

-d:后台运行容器(守护进程模式)

-it:交互模式运行容器(通常与 /bin/bash连用)

--name:为容器指定一个名称

-p:端口映射(格式:宿主机端口:容器端口

-v:挂载数据卷(格式:宿主机目录:容器目录

--rm:容器退出时自动删除(常用于临时测试)

2.启动/停止/重启容器

docker start 容器名

docker stop 容器名

docker restart 容器名

3.进入容器

以下是两种与docker容器交互的命令,推荐使用docker exec进入容器:

  • docker exec 容器名 命令# 进入容器名为ubuntu的命令行终端
    docker exec -it ubuntu /bin/sh
  • docker attach 容器名# 进入容器名为ubuntu的命令行终端
    docker attach ubuntu

4.删除容器

docker rm 容器名

# 删除已停止的ubuntu容器
docker rm web      
# 删除所有已停止的容器
docker rm $(docker ps -aq)

信息查看

1.查看容器信息

docker ps

# 查看正在运行的容器
docker ps
# 查看所有容器
docker ps -a

2.查看镜像信息

docker images 查看本地所有镜像

docker images nginx 只列出nginx相关镜像

3.查看容器日志

docker logs [选项] 容器名

# 查看ubuntu容器的所有日志
docker logs ubuntu
# 实时查看查看ubuntu容器最后50行日志
docker logs -f --tail 50 ubuntu

4.查看容器/镜像的详情信息

docker inspect 容器名/镜像名

5.查看容器端口映射情况

docker port 容器名

6.查看容器占用资源

docker stats

# 查看ubuntu容器的资源占用
docker stats ubuntu
# 查看全部运行容器的资源占用
docker stats

容器程序安全防护

在Docker容器中部署应用程序时,安全因素至关重要,尤其在通过容器进行交付的场景下,保障代码不被窃取或泄露更是重中之重。

Virbox Protector工具可以全面保护docker中的程序,运行在docker容器中的java、python以及本地的elf和so文件,都可以使用Virbox Protector工具进行保护,针对不同的文件类型会提供对应的安全策略来保护程序中的代码,确保程序在docker容器中安全运行。

]]>
https://shell.virbox.com/2025/12/25/docker%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4%e6%80%bb%e7%bb%93/feed/ 0
bk7258固件的介绍和安全防范 https://shell.virbox.com/2025/12/25/bk7258%e5%9b%ba%e4%bb%b6%e7%9a%84%e4%bb%8b%e7%bb%8d%e5%92%8c%e5%ae%89%e5%85%a8%e9%98%b2%e8%8c%83/ https://shell.virbox.com/2025/12/25/bk7258%e5%9b%ba%e4%bb%b6%e7%9a%84%e4%bb%8b%e7%bb%8d%e5%92%8c%e5%ae%89%e5%85%a8%e9%98%b2%e8%8c%83/#respond Thu, 25 Dec 2025 05:47:16 +0000 https://shell.virbox.com/?p=3141 了解下BK7258的使用场景和安全问题

基本介绍

BK7258是一款集成Wi-Fi、蓝牙、强大多媒体和AI处理能力的芯片,其核心特点是超低功耗、丰富的外设接口和强大的音视频处理能力,这使得它特别适合对功耗和算力有高要求的智能设备。

适用场景

基于其硬件特性,BK7258目前主要应用于以下前沿领域:

应用领域典型产品关键支撑特性
AIoT与智能穿戴AI智能眼镜、机器人等产品Wi-Fi和蓝牙提供稳定连接;低功耗延长续航;AI算力支持本地智能交互。
智能家居与安防智能可视门锁、智能音箱等H.264/JPEG编解码器处理视频流;显示控制器驱动屏幕;麦克风/音频接口支持语音。
交互界面与家电智能家电、HMI(人机交互)面板丰富外设(LCD, CAN, USB, 触摸传感器等)支持复杂控制与显示。

简单示例

本文主要是在Ubuntu 24.04上为BK7258开发板编译和运行程序,主要需要配置编译环境、获取SDK代码、编译工程几个步骤。

环境配置

安装必要的工具和依赖库 打开终端,执行以下命令来安装基础的编译工具和Python依赖库。

sudo apt update
sudo apt install make cmake python3 python3-pip ninja-build -y
sudo pip3 install pycryptodome click future click_option_group cryptography jinja2 PyYAML cbor2 intelhex

获取代码

* 安装BK7258芯片的程序需要ARM架构的交叉编译工具链。

  • 从博通集成(Beken)的官方渠道下载BK7258工具链。
  • 下载后,将其解压到系统的 /opt/ 目录下。
sudo tar -xvjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt/

* 获取Armino SDK代码,需要从官方仓库获取软件开发套件(SDK)源代码,比如:

mkdir -p ~/armino
cd ~/armino
git clone https://github.com/bekencorp/bk_idk.git

编译工程

  1. 环境准备好后,进入SDK目录,就可以开始编译项目了;
  2. 执行编译命令,使用 make 命令进行编译,比如make bk7258则默认执行默认的app项目;
  1. 如果想指定具体项目进行编译时,需要添加PROJECT 参数,其中PROJECT 的值应该是从 bk_idk/projects/ 目录开始的相对路径;make bk7258 PROJECT=你的项目名称
    命令参考:
    make bk7258 PROJECT=bluetooth/bt_hidd编译成功后,生成的固件文件(例如 all-app.bin)通常会位于 build/bt_hidd/bk7258 这样的目录下。

编译输出位置

编译成功后,固件文件通常位于:

# 固件文件
build/app/bk7258/app.elf
build/app/bk7258/app.bin

# 静态库文件
bk_idk/build/app/bk7258/armino/main/libmain.a

安全防范

安全问题

以默认项目为例,编译成固件的流程大概为:

  1. make bk7258编译的时候会对app_main.c进行编译;
  1. 根据配置可以推断出app_main.c编译成为中间文件(.a静态库文件);
  2. 然后再编译集成app.elf,最后是固件app.bin文件。

通过反编译工具还是可以对app.elf进行反编译分析,最终导致核心逻辑和敏感信息泄露。

防范措施

因此,我们可以对编译中的核心静态库文件(.a文件)进行加壳(Virbox Protector工具)等保护处理,增加反编译和逆向工程的难度。

编译方式如下:

  1. make bk7258先对app项目编译成功后产生静态库bk_idk\build\app\bk7258\armino\main\libmain.a;

2. 然后对libmain.a进行加壳,并将名字改为和原文件相同,然后在使用make bk7258进行编译,可以看到编译链接是保护后的静态库;

  • 注意:只要不是make clean,重新make bk7258不会覆盖libmain.a,但会覆盖app.elf和app.bin。

    3. 编译的app.bin和app.elf里集成的都是保护后的静态库,使用ida分析也能看到代码保护上了。

      ]]>
      https://shell.virbox.com/2025/12/25/bk7258%e5%9b%ba%e4%bb%b6%e7%9a%84%e4%bb%8b%e7%bb%8d%e5%92%8c%e5%ae%89%e5%85%a8%e9%98%b2%e8%8c%83/feed/ 0
      C++与Python混合编程 https://shell.virbox.com/2025/12/23/c%e4%b8%8epython%e6%b7%b7%e5%90%88%e7%bc%96%e7%a8%8b/ https://shell.virbox.com/2025/12/23/c%e4%b8%8epython%e6%b7%b7%e5%90%88%e7%bc%96%e7%a8%8b/#respond Tue, 23 Dec 2025 01:03:21 +0000 https://shell.virbox.com/?p=3111 混合编程的必要性

      在软件开发中,我们经常面临一个问题:开发效率和运行性能很难兼顾。Python和C++正好代表了这两个极端方向。

      Python的优势与局限

      Python凭借简洁易学的语法、高效的开发体验、优秀的跨平台特性和强大的生态系统,赢得了广大开发者的青睐。它广泛应用于自动化脚本、Web开发、机器学习与AI、科学计算与数据分析等领域,并拥有庞大的开发者社区。

      然而,Python的解释执行特性和全局解释器锁(GIL)也带来了性能瓶颈。在计算密集型任务中,特别是涉及大量循环计算和数值运算的场景下,Python的执行效率往往比编译型语言慢10-100倍,成为高性能应用的制约因素。

      C++的强大与挑战

      C++作为编译型语言,其核心设计赋予了开发者对硬件和内存的底层控制能力,并始终坚持“零开销抽象”理念,这使得它在执行速度上通常拥有绝对优势。在图形渲染、物理模拟、大规模数值计算等CPU密集型任务中,C++的性能表现往往是Python的数十倍乃至上百倍。但这种极致的性能是以显著的开发复杂性为代价的。C++复杂的语法规则、需要手动处理的内存管理等,都导致了更长的开发周期和对开发者更高的专业门槛。

      混合编程

      Python和C++各有优缺点,单独使用一种语言很难同时满足开发效率和运行性能的要求。好在Python支持与C/C++互操作,可以把两种语言的优势结合起来:

      • 用Python构建主体框架:处理业务逻辑、用户界面、数据流程等高层抽象,充分发挥其开发效率高、代码简洁的优势
      • 用C++优化性能瓶颈:将计算密集型的核心算法用C++实现,获得接近原生的执行速度

      这种混合编程模式已成为工业界的主流实践。例如:

      • NumPy/SciPy:科学计算库的底层用C/Fortran实现
      • PyTorch/TensorFlow:深度学习框架的核心算子用C++/CUDA编写
      • OpenCV:计算机视觉库提供Python接口,底层为高度优化的C++代码

      通过混合编程,我们既保留了Python快速开发和易于维护的特点,又在关键部分获得了C++级别的性能,同时兼顾了效率和速度。

      小试牛刀

      本节我们会使用Pybind11进行C++与Python混合编程来分别实现 递归版的斐波那契数列,注意测试代码本身没有实际意义,目标是 体验混合编程与性能对比。

      下面代码是Python C拓展库(pyd)代码

      #include "Python.h"
      #include "pybind11/pybind11.h"


      namespace py = pybind11;

      long long fib_cpp(int n) {
       if (n <= 1) return n;
       return fib_cpp(n - 1) + fib_cpp(n - 2);
      }

      PYBIND11_MODULE(test, m) {
       m.doc() = u8"Python C扩展模块";


       m.def("fibonacci", &fib_cpp,
         "Calculate the Fibonacci sequence",
         py::arg("n"));
      }

      下面代码是python代码

      import time
      import test

      def fib_py(n):
         """纯Python递归实现"""
         if n <= 1:
             return n
         return fib_py(n-1) + fib_py(n-2)

      if __name__ == "__main__":
         n = 38

         start_time = time.time()
         result = fib_py(n)
         elapsed_time = time.time() - start_time

         print(f"Python fibonacci({n}) 计算完成")
         print(f"执行时间: {elapsed_time:.6f} 秒")

         input()
         start_time = time.time()
         result =  test.fibonacci(n)
         elapsed_time = time.time() - start_time

         print(f"C++ fibonacci({n}) 计算完成")
         print(f"执行时间: {elapsed_time:.6f} 秒")

      执行结果如下

      Python fibonacci(38) 计算完成
      执行时间: 3.389794 秒

      C++ fibonacci(38) 计算完成
      执行时间: 0.116832 秒

      程序安全

      混合编程在带来性能优势的同时,也面临着代码安全的挑战。无论是Python脚本还是编译后的C++扩展模块,都可能成为逆向分析的目标。核心算法、业务逻辑一旦被破解,将造成严重的知识产权损失。

      VirboxProtector 是一款同时支持Python脚本和c++程序的保护工具,提供从静态分析到动态分析的整体保护方案,从多个方面提升程序安全性。

      对于C++程序,VirboxProtector会对函数进行混淆和虚拟化,达到扰乱逆向者分析的目的,从而保护代码安全。

      对于Python脚本,VirboxProtector整个脚本中的字节码进行加密,并对脚本编译后的代码对象实现整体加密,由Python C扩展模块用来动态解密,从而保护脚本安全。

      ]]>
      https://shell.virbox.com/2025/12/23/c%e4%b8%8epython%e6%b7%b7%e5%90%88%e7%bc%96%e7%a8%8b/feed/ 0
      Windows 动态链接库查找机制介绍 https://shell.virbox.com/2025/12/12/windows-%e5%8a%a8%e6%80%81%e9%93%be%e6%8e%a5%e5%ba%93%e6%9f%a5%e6%89%be%e6%9c%ba%e5%88%b6%e4%bb%8b%e7%bb%8d/ https://shell.virbox.com/2025/12/12/windows-%e5%8a%a8%e6%80%81%e9%93%be%e6%8e%a5%e5%ba%93%e6%9f%a5%e6%89%be%e6%9c%ba%e5%88%b6%e4%bb%8b%e7%bb%8d/#respond Fri, 12 Dec 2025 08:51:23 +0000 https://shell.virbox.com/?p=3108 概述

      Windows 操作系统通过动态链接库(Dynamic Link Library, DLL)机制将常用逻辑抽离为模块,以实现代码复用、功能扩展和内存占用优化。当应用程序或系统组件需要加载 DLL 时,可以显式提供绝对路径,也可以只提供文件名。若未指定路径,系统将按照一套复杂而明确的搜索顺序在多个目录中寻找目标 DLL。

      DLL 搜索机制由 Windows 加载器(Loader)负责,其行为受到应用程序类型(打包应用或传统桌面应用)、系统版本、安全配置、应用程序清单(manifest)、加载函数参数以及注册表配置等因素影响。因此理解 DLL 搜索路径对于软件开发、部署安全、故障排查均至关重要。

      影响搜索的关键因素

      在正式开始搜索 DLL 之前,Windows 会根据内部规则对 DLL 名称进行解释和重定向。以下因素按顺序影响加载过程。

      第一,DLL 重定向(Side-by-Side / .local 文件)。

      如果应用程序目录中存在一个与主程序同名的 .exe.local 文件,或者存在一个与 DLL 文件同名的 .local 文件,系统将优先从应用程序目录加载该 DLL,而不从系统目录或其他位置加载。这是早期解决 DLL Hell 问题的兼容机制,但现代 Windows 不再推荐使用,主要用于兼容旧程序。

      第二,API 集合解析。

      Windows 自 Windows 7 起引入 API Set Schema,将 Win32 API 按逻辑功能划分为 “API 集合”(如 api-ms-win-core-synch-l1-2-0.dll)。这些 DLL 是虚拟的,实际会映射到系统核心 DLL(如 kernel32.dllntdll.dll)。因此,API Set 名称不会按照常规路径搜索,而是由系统重定向。

      第三,SxS Manifest 重定向。

      应用程序可通过 manifest 清单明确声明依赖 DLL 的特定版本。加载器会根据清单信息定位这些版本,通常用于支持多个版本的通用控件(如 comctl32.dll v5 与 v6)共存。

      第四,已加载模块列表。

      Windows 会先检查当前进程地址空间。如果 DLL 已经加载,则返回现有模块句柄,这减少了重复加载,提高性能,并防止多个版本的 DLL 同时存在导致冲突。

      第五,KnownDLLs。

      系统注册表中 KnownDLLs 项列出了部分核心 DLL,这些 DLL 总是从系统目录加载,并通过内存映射共享到所有进程中。这样提高了安全性与性能,并避免 DLL 覆盖攻击问题。

      打包应用的 DLL 搜索顺序

      所谓打包应用通常指 UWP 应用、MSIX 打包应用、Windows App SDK 程序等。这类应用的运行环境受到操作系统沙盒机制保护,应用的文件系统视图受到限制。

      打包应用的 DLL 搜索顺序比桌面应用更严格,主要来源只有:

      • 应用包本身
      • 声明的依赖包(如 Framework 包)
      • 与应用同层级安装的包
      • 系统目录中的已知 DLL

      扩展的标准搜索顺序如下:

      1. DLL 重定向
      2. API 集合解析
      3. Manifest 并行清单解析(桌面桥应用可能触发)
      4. 已加载模块检查
      5. 已知 DLL
      6. 应用包依赖图(Package Dependency Graph)
        • 按照依赖顺序,从 Framework 包、Resource 包、可选包中查找。
        • 每个包内部的 System32Redirected 目录和 VFS 虚拟文件系统在此阶段参与。
      7. 可执行文件所在目录(仅桌面桥应用适用)
      8. 系统目录(%SystemRoot%\system32)

      未打包应用的 DLL 搜索顺序

      传统 Win32 桌面应用可以访问更广泛的文件系统目录,因此 DLL 搜索机制更复杂,历史兼容性也要求保留许多老旧路径。

      启用安全 DLL 搜索模式是默认行为,此模式改善了早期系统容易被 DLL 劫持的弱点(例如将恶意 DLL 放在当前目录)。搜索顺序为:

      1. DLL 重定向
      2. API 集合解析
      3. Manifest 清单解析
      4. 已加载模块检查
      5. 已知 DLL
      6. Windows 11 21H2 起:检查包依赖图(即使程序未打包)
      7. 应用程序加载目录(exe 所在目录)
      8. 系统目录(system32)
      9. 16 位系统目录(system)
      10. Windows 根目录
      11. 当前工作目录(CWD)
      12. PATH 环境变量

      如果禁用了安全模式,当前工作目录会提升到第 8 位,紧挨着应用程序目录之后,这可能造成严重的 DLL 劫持风险。

      修改搜索顺序的方法

      开发者可通过一系列 API 影响加载器行为,常用于插件加载、便携式软件或安全强化。

      比如 LoadLibraryEx 函数,它支持多种标志位,LOAD_WITH_ALTERED_SEARCH_PATH 会优先搜索目标 DLL 所在目录(Full Path 指定时),用于加载同目录依赖项;LOAD_LIBRARY_SEARCH_* 系列标志位可精准控制参与搜索的目录集,彻底排除 PATH 或当前目录等不安全路径。

      还有 SetDllDirectory 函数,它可以将一个目录插入默认搜索路径中,仅次于应用程序目录,传入空字符串 "" 会移除当前目录从搜索路径中,这是安全加固的推荐做法。

      SetDefaultDllDirectories 函数可覆盖进程的默认搜索策略,一旦设置,系统将忽略 PATH 环境变量除非明确允许。常与 AddDllDirectory 组合使用。

      典型安全用法:

      SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS);
      AddDllDirectory(L"C:\\MyApp\\Plugins");

      LOAD_LIBRARY_SEARCH 标志的搜索顺序

      使用此类标志后,系统将完全忽略传统目录(如 PATH)。

      搜索顺序固定:

      1. LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
        • 若主 DLL 通过绝对路径加载,该标志允许系统在主 DLL 所在目录搜索其依赖项。
      2. LOAD_LIBRARY_SEARCH_APPLICATION_DIR
        • 程序主 exe 的目录。
      3. LOAD_LIBRARY_SEARCH_USER_DIRS
        • 通过 AddDllDirectory 添加的目录,对顺序不做保证。
      4. LOAD_LIBRARY_SEARCH_SYSTEM32
        • 系统目录。

      这些标志应尽量使用,以增强 DLL 加载的确定性和安全性。

      依赖项加载规则

      即使主 DLL 是通过绝对路径加载的,它的依赖项也不会自动从同一目录加载。

      除非启用特定标志(如 LOAD_WITH_ALTERED_SEARCH_PATHLOAD_LIBRARY_SEARCH_DLL_LOAD_DIR),依赖项将:

      • 按名称解析(名称不能与路径结合)
      • 使用当前进程设置的全部搜索路径
      • 可能从系统目录加载旧的或不兼容版本

      这可能导致所谓的“DLL Hell”或版本冲突,因此插件系统或便携式应用应谨慎处理依赖项路径。

      安全考虑

      DLL 搜索路径是攻击者常用的入口,典型攻击包括DLL 劫持(DLL Hijacking)目录遍历注入未签名模块替换等。

      以下是一些降低风险的方法:

      1. 采用打包应用(MSIX)部署:天然阻断搜索到用户可写目录,DLL 必须来自包依赖图,提高可靠性。
      2. 保持安全 DLL 搜索模式启用:减少当前目录攻击风险,Windows 默认是开启的,尽量不要关闭。
      3. 使用 SetDefaultDllDirectories + AddDllDirectory 进行目录白名单机制:明确控制 DLL 可加载的目录,可将搜索限制到仅系统目录与私有插件目录。
      4. 检查 DLL 数字签名:关键模块应经过签名验证(如 Authenticode),尤其在加载第三方插件时。
      5. 避免将用户可写目录(如 %TEMP%,当前目录)置于搜索前列,禁止从不可信路径加载 DLL 是必须的安全策略。

      除了理解 Windows 本身的 DLL 加载机制,商业软件往往还需要保护自身 DLL 不被逆向分析或替换。在这方面,可借助诸如 Virbox Protector 的专业保护工具实现 DLL 加密、防调试和完整性校验,减少在复杂环境中的攻击面,提高软件安全性。

      总结

      Windows 的 DLL 搜索机制既复杂又灵活。它需要兼顾:

      • 兼容旧应用
      • 提供安全沙箱机制(打包应用)
      • 提供精确可控的加载路径(LOAD_LIBRARY_SEARCH)
      • 支持 Side-by-Side 版本共存
      • 提升性能(KnownDLLs)

      开发者应理解不同应用模型的搜索路径差异,并采用现代 API(如 SetDefaultDllDirectories、AddDllDirectory)构建稳定、安全、可预测的模块加载机制。

      合理利用打包应用模型或使用 MSIX 能显著减少加载器面临的不确定性,进一步提升软件的部署与运行安全性。

      ]]>
      https://shell.virbox.com/2025/12/12/windows-%e5%8a%a8%e6%80%81%e9%93%be%e6%8e%a5%e5%ba%93%e6%9f%a5%e6%89%be%e6%9c%ba%e5%88%b6%e4%bb%8b%e7%bb%8d/feed/ 0
      Python mmdet模块介绍及应用保护方案 https://shell.virbox.com/2025/12/12/python-mmdet%e6%a8%a1%e5%9d%97%e4%bb%8b%e7%bb%8d%e5%8f%8a%e5%ba%94%e7%94%a8%e4%bf%9d%e6%8a%a4%e6%96%b9%e6%a1%88/ https://shell.virbox.com/2025/12/12/python-mmdet%e6%a8%a1%e5%9d%97%e4%bb%8b%e7%bb%8d%e5%8f%8a%e5%ba%94%e7%94%a8%e4%bf%9d%e6%8a%a4%e6%96%b9%e6%a1%88/#respond Fri, 12 Dec 2025 08:47:57 +0000 https://shell.virbox.com/?p=3105 Python mmdet模块介绍及应用保护方案

      mmdet简介

      Python的mmdet模块(MMDetection)是一个基于PyTorch的开源目标检测工具箱,用于AI模型的推理。

      mmdet的使用

      安装mmdet

      1.安装cuda,在cuda的官网下载环境对应版本进行安装:https://developer.nvidia.com/cuda-downloads

      2.安装PyTorch,在PyTorch官网下载系统对应版本进行安装:https://pytorch.org/get-started/locally/

      3.使用mim安装mmengine和mmcv:

      pip install -U openmim
      mim install mmengine
      mim install "mmcv>=2.0.0"

      4.安装mmdetection,有两个方案:

      方案一:如果你开发并直接运行mmdet,从源码安装它:

      git clone https://github.com/open-mmlab/mmdetection.git
      cd mmdetection
      pip install -v -e .
      # "-v" 指详细说明,或更多的输出
      # "-e" 表示在可编辑模式下安装项目,因此对代码所做的任何本地修改都会生效,从而无需重新安装。

      方案二:如果将mmdet作为依赖或第三方 Python包,使用MIM安装:

      mim install mmdet

      运行mmdet

      mmdet源码项目中带有示例代码,可以直接验证操作

      1.下载配置文件和模型权重文件:

      mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest .

      2.通过源码安装的 MMDetection,那么直接运行以下命令进行验证:

      python demo/image_demo.py demo/demo.jpg rtmdet_tiny_8xb32-300e_coco.py --weights rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth --device cpu

      3.推理完成后会在outputs/vis输出推理完成的图片

      安全防护

      mmdet模块的使用还是使用源码的场景更多,会针对不同的AI模型编写对应的python代码,此时对自己的代码肯定会考虑到安全性,不仅是自身的代码,还有AI模型的推理数据等等。

      而Virbox Protector工具可以更好的保障python代码的安全,在字节码层次保障代码不会被破解,同时还具备保护AI模型的能力,非常符合mmdet模块的使用场景。

      ]]>
      https://shell.virbox.com/2025/12/12/python-mmdet%e6%a8%a1%e5%9d%97%e4%bb%8b%e7%bb%8d%e5%8f%8a%e5%ba%94%e7%94%a8%e4%bf%9d%e6%8a%a4%e6%96%b9%e6%a1%88/feed/ 0
      认识下XCFramework格式 https://shell.virbox.com/2025/12/11/%e8%ae%a4%e8%af%86%e4%b8%8bxcframework%e6%a0%bc%e5%bc%8f/ https://shell.virbox.com/2025/12/11/%e8%ae%a4%e8%af%86%e4%b8%8bxcframework%e6%a0%bc%e5%bc%8f/#respond Thu, 11 Dec 2025 09:03:44 +0000 https://shell.virbox.com/?p=3087 基础介绍

      XCFramework 是苹果在 Xcode 11 中引入的一种二进制框架分发格式,专门设计用于支持多种平台和架构。开发者能将不同平台(如 iOS、macOS、tvOS 等)和不同架构(如 arm64、x64 等)的二进制文件打包在一个单一的 bundle 中。

      使用XCFramework时,Xcode 能自动选择匹配当前架构使用的指令集Framework,避免手动操作指令集的工作。

      主要特点

      支持多平台

      普通 Framework 通常只为单一平台和架构设计,而XCFramework 支持包含多种架构的framework,包含iOS (arm64) 、iOS Simulator 、macOS (x86_64, arm64)、watchOS等架构。

      兼容性

      普通 Framework 在不同架构(如真机和模拟器)之间可能存在兼容性问题,而XCFramework 能够同时包含多个架构的二进制文件,解决了跨架构兼容性问题

      分发便利性

      使用普通 Framework 时,可能需要为不同平台提供不同的版本,XCFramework 将所有平台和架构的版本打包在一起,简化了分发和集成流程。

      灵活性

      XCFramework 提供了更好的灵活性和可扩展性,特别适合 iOS 开发中的多平台需求,并且支持静态和动态库类型,提供更高的灵活性。

      简单示例

      创建项目

      1. 使用xcode工具,按需求编译各平台framework框架,可以是静态库(.a或.framework)或动态库(.dylib);
      2. 使用xcodebuild将多个不同平台的框架合并为一个,命令参考如下:
      xcodebuild -create-xcframework \
          -framework archives/ios.xcarchive/Products/Library/Frameworks/MyFramework.framework 
          -framework archives/simulator.xcarchive/Products/Library/Frameworks/MyFramework.framework 
          -output test.xcframework
      1. 最终合并后的文件如下图所示:

      简单使用

      在Xcode项目中添加即可,如下:

      • .xcframework 放到项目目录下;
      • Build Phases 中添加依赖;
      • Framework&Libraries中选择Embed & Sign即可使用。

      安全防范

      安全问题

      XCFramework相当于是一个包含多个平台和多个架构的Framework集合,里面的framework框架里的可执行文件属于machO文件格式,如果文件不被保护,则关键代码可能会被泄露,比如:

      被逆向风险

      虽然编译生成的二进制文件逆向分析难度较高,但由于相应的反编译工具(比如ida、Ghidra)也成熟强大,依然可以反编译为类C伪代码;

      被调试风险

      攻击者可通过调试工具附加应用进程进行调试,在调试过程中可能暴露敏感信息(如密钥、算法逻辑);

      程序被篡改风险

      攻击者可以通过修改应用内存改变程序行为,绕过安全检查或实现恶意功能,导致数据泄露;

      调试符号暴漏

      dylib库中包含调试符号(如函数名、变量名、函数地址),可能暴露敏感信息,则攻击者可以更容易地理解代码逻辑,对代码进行分析。

      防范措施

      Virbox Protector工具(简称加壳工具)在对machO格式文件的保护上有着成熟的方案,可以实现对macho格式文件进行函数级和整体保护,具体方案参考官网文档Native程序保护最佳实践。;

      目前加壳工具无法直接支持对XCFramework文件夹进行加壳,但支持对framework或可执行性文件加壳,所以建议可以先对framework或可执行性文件进行保护,然后在将其打包为一个XCFramework文件。

      ]]>
      https://shell.virbox.com/2025/12/11/%e8%ae%a4%e8%af%86%e4%b8%8bxcframework%e6%a0%bc%e5%bc%8f/feed/ 0
      Linux 跨进程内存操作 https://shell.virbox.com/2025/12/09/linux-%e8%b7%a8%e8%bf%9b%e7%a8%8b%e5%86%85%e5%ad%98%e6%93%8d%e4%bd%9c/ https://shell.virbox.com/2025/12/09/linux-%e8%b7%a8%e8%bf%9b%e7%a8%8b%e5%86%85%e5%ad%98%e6%93%8d%e4%bd%9c/#respond Tue, 09 Dec 2025 02:01:05 +0000 https://shell.virbox.com/?p=3011 本文将会介绍三种Linux系统下跨进程内存操作的方法,用于读取修改其他进程的内存数据,恶意程序会通过这些操作来破环程序的正常执行逻辑,以到达窃取/篡改密钥,修改代码和数据,严重危害程序安全。在文章末尾我会聊聊相应的对抗方案来保护程序安全。

      ptrace

      ptrace 全称叫 Process trace,ptrace是linux系统上的一个系统调用(syscall),它为一个进程提供观察和控制另一个进程执行过程的能力。

      我们可以使用 ptrace来实现内存数据的读取和写入,还有一些知名的工具也是基于ptrace实现,比如:gdb, strace, ltrace等。

      函数签名

      #include <sys/ptrace.h>
      long ptrace(enum __ptrace_request op, pid_t pid, void *addr, void *data);
      • op: ptrace操作类型
      • pid: 被追踪的进程id
      • addr: 目标内存地址, 使用方式取决于参数op。
      • data: 操作数据, 使用方式取决于参数op。

      基于ptrace实现内存读取和写入

      通过ptrace操作需要通过PTRACE_PEEKDATA/PTRACE_POKEDATA操作来读取和写入, 这种方式实现大数据内存的读取和写入操作效率还是比较低。

      需要注意的是 我们只能当程序处于SIGSTOP状态是才可以去操作被追踪进程。

      Tracer.h

      #ifndef _TRACER_12312421_H
      #define _TRACER_12312421_H

      #include <stdint.h>
      #include <stddef.h>
      #include <sys/ptrace.h>
      #include <asm/ptrace.h>  

      class Tracer
      {
      public:
         
         Tracer(/* args */);
         ~Tracer();

         bool attach(int pid);
         void detach();
         bool continueRun();
         void wait(int *status);
         void stop();

         size_t readMemory(uintptr_t address, void* buffer, size_t size);
         size_t writeMemory(uintptr_t address, void* buffer, size_t size);

      private:
         int pid_;
      };


      #endif _TRACER_12312421_H

      Tracer.cpp

      #include "Tracer.h"

      #include <errno.h>
      #include <memory.h>
      #include <sys/types.h>
      #include <sys/wait.h>

      #define INVALID_PID (-1)

      Tracer::Tracer()
      :pid_(INVALID_PID)
      {
      }

      Tracer::~Tracer()
      {
      }

      bool Tracer::attach(int pid)
      {
         long ret = ptrace(PTRACE_ATTACH, pid, nullptr,nullptr);
         if( ret == -1)
             return false;

         pid_ = pid;
      int status = 0;
         wait(&status);
         return true;
      }

      void Tracer::detach()
      {
         ptrace(PTRACE_DETACH, pid_, NULL, 0);
      }

      bool Tracer::continueRun()
      {
         return ptrace(PTRACE_CONT, pid_, NULL, 0) != -1;
      }

      void Tracer::wait(int *status)
      {
         waitpid(pid_, status, 0);
      }

      void Tracer::stop()
      {
         kill(pid_, SIGSTOP);
      }

      size_t Tracer::readMemory(uintptr_t address, void *buffer, size_t size)
      {
         size_t read_count = size / sizeof(long);
         size_t remain_bytes = size % sizeof(long);
         uint8_t* _buffer = (uint8_t*)buffer;
         size_t readsize = 0;

         long tmp;
         for(size_t i = 0; i < read_count; ++i)
        {
             errno = 0;
             tmp = ptrace(PTRACE_PEEKDATA, pid_, (void*)(address + readsize), nullptr);
             if(tmp == -1 &&  errno != 0)
            {
                 return readsize;
            }

             memcpy(_buffer + i * sizeof(long), &tmp, sizeof(tmp));
             readsize += sizeof(tmp);
        }

         if(remain_bytes)
        {
             errno = 0;
             tmp = ptrace(PTRACE_PEEKDATA, pid_, (void*)(address + readsize), nullptr);
             if(tmp == -1 &&  errno != 0)
            {
                 return readsize;
            }

             memcpy(_buffer + read_count * sizeof(long), &tmp, remain_bytes);
             readsize += remain_bytes;
        }

         return readsize;
      }

      size_t Tracer::writeMemory(uintptr_t address, void *buffer, size_t size)
      {
         size_t read_count = size / sizeof(long);
         size_t remain_bytes = size % sizeof(long);
         uint8_t* _buffer = (uint8_t*)buffer;
         size_t writesize = 0;

         long tmp, result;
         for(size_t i = 0; i < read_count; ++i)
        {
             memcpy(&tmp, _buffer + i * sizeof(long), sizeof(tmp));

             errno = 0;
             result = ptrace(PTRACE_POKEDATA, pid_, (void*)(address + writesize ), tmp);
             if(result == -1 &&  errno != 0)
            {
                 return writesize;
            }
             writesize += sizeof(tmp);
        }

         if(remain_bytes)
        {
             long original = ptrace(PTRACE_PEEKDATA, pid_, (void*)(address + writesize), nullptr);
             if(original == -1 &&  errno != 0)
            {
                 return writesize;
            }

             long new_data = 0;
             memcpy(&new_data, _buffer + writesize, remain_bytes);
             long mask = (1UL << (remain_bytes * 8)) - 1;
             long merged = (original & ~mask) | (new_data & mask);

             result = ptrace(PTRACE_POKEDATA, pid_, (void*)(address + writesize ), merged);
             if(original == -1 &&  errno != 0)
            {
                 return writesize;
            }
             writesize += remain_bytes;
        }

         return writesize;
      }

      下面是具体的实现示例。


      int tracer_memory(int pid)
      {

         Tracer tracer;
         if(!tracer.attach(pid))
        {
             printf("failed to ptrace %d\n", pid);
             return 1;
        }
         tracer.continueRun();

         intptr_t address;
         std::string content;
         std::string inputLine;
         int statue = 0;

         std::cout <<"input address: ";
         std::getline(std::cin, inputLine);
         std::stringstream(inputLine) >>  std::hex >>address;

         content.resize(256);
         tracer.stop();
         tracer.wait(&statue);
         size_t read_size = tracer.readMemory(address, (void*)content.c_str(), content.size());
         tracer.continueRun();
         printf("%p context:%s size:%d\n", address, content.c_str(), read_size);

         std::cout <<"input context: ";
         std::getline(std::cin, content);
         tracer.stop();
         tracer.wait(&statue);
         tracer.writeMemory(address, (void*)content.c_str(), content.size() + 1);
         tracer.continueRun();

         read_size = tracer.readMemory(address, (void*)content.c_str(), content.size());
         tracer.continueRun();
         printf("%p context:%s size:%d\n", address, content.c_str(), read_size);

         return 0;
      }

      mem进程虚拟文件

      /proc/[pid]/mem 是系统内核生成的虚拟文件,可以通过这个文件文件直接访问进程整个虚拟内存空间,并允许读取和修改内存数据。我们可以通过文件操作api来操作指定进程的内存数据,如:open, lseek, read,write, close。这种方式实现大数据内存的读取和写入操作效率较高。

      需要注意的是 我们只能当程序处于SIGSTOP状态是才可以去操作被追踪进程。

      ProcFile.h

      #ifndef _PROCFILE_12312421_H
      #define _PROCFILE_12312421_H

      #include <stdint.h>
      #include <stddef.h>

      class ProcFile
      {
      public:
         ProcFile(int pid);
         ~ProcFile();

         bool openMemory();
         void closeMemory();

         size_t readMemory(intptr_t address, void* buffer, size_t size);
         size_t writeMemory(intptr_t address, const void* buffer, size_t size);

      private:
         int pid_;
         int mem_fd_ = -1;
      };



      #endif _PROCFILE_12312421_H

      ProcFile.cpp

      #include "ProcFile.h"
      #include <string.h>
      #include <unistd.h>
      #include <fcntl.h>
      #include <string>
      #include <signal.h>
      #include <wait.h>
      #include <errno.h>

      ProcFile::ProcFile(int pid)
      : pid_(pid)
      {
      }

      ProcFile::~ProcFile()
      {
         closeMemory();
      }

      bool ProcFile::openMemory()
      {
         char path[64];
         snprintf(path, sizeof(path), "/proc/%d/mem", pid_);
         mem_fd_ = open(path, O_RDWR); // 需要读写权限
         return mem_fd_ != -1;
      }

      void ProcFile::closeMemory()
      {
         if (mem_fd_ != -1)
        {
             close(mem_fd_);
             mem_fd_ = -1;
        }
      }

      size_t ProcFile::readMemory(intptr_t address, void *buffer, size_t size)
      {
         if (mem_fd_ == -1)
             return 0;
             
         off_t offset = lseek(mem_fd_, static_cast<off_t>(address), SEEK_SET);
         if (offset != static_cast<off_t>(address))
        {
             return 0;
        }
             
         size_t read_size  = 0;

         if (kill(pid_, SIGSTOP) == -1)
        {
             return 0;
        }

         read_size = read(mem_fd_, buffer, size);

         if (kill(pid_, SIGCONT) == -1)
        {
             abort();
        }

         return read_size;
      }

      size_t ProcFile::writeMemory(intptr_t address, const void *buffer, size_t size)
      {
         if (mem_fd_ == -1)
             return 0;
         
         off_t offset = lseek(mem_fd_, static_cast<off_t>(address), SEEK_SET);
         if (offset != static_cast<off_t>(address))
        {
             return 0;
        }
         
         size_t write_size  = 0;

         if (kill(pid_, SIGSTOP) == -1)
        {
             return 0;
        }

         write_size = write(mem_fd_, buffer, size);

         if (kill(pid_, SIGCONT) == -1)
        {
             abort();
        }

         return write_size;
      }

      下面是具体的实现示例。


      int proc_memory(int pid)
      {
         ProcFile proc(pid);

         if(!proc.openMemory())
        {
             printf("failed to open memory %d\n", pid);
             return 1;
        }


         intptr_t address;
         std::string content;
         std::string inputLine;
         int statue = 0;

         std::cout <<"input address: ";
         std::getline(std::cin, inputLine);
         std::stringstream(inputLine) >>  std::hex >>address;

         content.resize(256);
         size_t read_size = proc.readMemory(address, (void*)content.c_str(), content.size());
         printf("%p context:%s size:%d\n", address, content.c_str(), read_size);


         std::cout <<"input context: ";
         std::getline(std::cin, content);
         proc.writeMemory(address, (void*)content.c_str(), content.size() + 1);

         read_size = proc.readMemory(address, (void*)content.c_str(), content.size());
         printf("%p context:%s size:%d\n", address, content.c_str(), read_size);

         return 0;
      }

      系统调用

      linux 直接提供两个api专门用于读取和写入其他进程内存api。

      函数声明

      #include <sys/uio.h>

      ssize_t process_vm_readv(pid_t pid,
                           const struct iovec *local_iov,
                           unsigned long liovcnt,
                           const struct iovec *remote_iov,
                           unsigned long riovcnt,
                           unsigned long flags);
      ssize_t process_vm_writev(pid_t pid,
                           const struct iovec *local_iov,
                           unsigned long liovcnt,
                           const struct iovec *remote_iov,
                           unsigned long riovcnt,
                           unsigned long flags);

      基于系统调用实现内存读取和写入


      int api_memory(int pid)
      {
      intptr_t address;
      std::string content;
      std::string inputLine;
      int statue = 0;

      std::cout <<"input address: ";
      std::getline(std::cin, inputLine);
      std::stringstream(inputLine) >> std::hex >>address;

      content.resize(256);
      iovec local_iov = {content.data(), content.size()}; // 本地缓冲区
      iovec remote_iov = {(void*)address, content.size()}; // 远程进程地址
      ssize_t nread = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0);

      printf("%p context:%s size:%d\n", address, content.c_str(), nread);


      std::cout <<"input context: ";
      std::getline(std::cin, content);
      process_vm_writev(pid, &local_iov, 1, &remote_iov, 1, 0 );


      nread = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0);

      printf("%p context:%s size:%d\n", address, content.c_str(), nread);
      return 0;
      }

      安全防范

      恶意人员要去攻击一个程序,第一步就是先去通过静态分析工具和动态分析工具分析出程序可以工具的点。然后才通过读取或修改内存等一系列攻击方式去实现目的。

      Virbox Protector 提供从静态分析和动态分析整体保护方案, 从多个方面提升Linux程序安全。

      防止静态分析:代码虚拟化将核心函数转换为专有虚拟机指令集,代码混淆通过控制流平坦化与虚假分支将执行逻辑打散为复杂的跳转网络,代码加密则对代码段进行加密存储并在运行时按需解密,三者结合使反汇编工具无法还原程序的真实逻辑。与此同时,导入表保护隐藏了程序对外部库函数的依赖关系,移除调试信息清除了符号表与函数名称等关键信息。让逆向者无法分析出程序里可被攻击代码和内存。

      防止动态调试:调试器检测能够识别基于ptrace实现调试行为。内存校验可以防止程序代码被修改。

      ]]>
      https://shell.virbox.com/2025/12/09/linux-%e8%b7%a8%e8%bf%9b%e7%a8%8b%e5%86%85%e5%ad%98%e6%93%8d%e4%bd%9c/feed/ 0
      搞懂数字签名与证书 https://shell.virbox.com/2025/12/01/%e6%90%9e%e6%87%82%e6%95%b0%e5%ad%97%e7%ad%be%e5%90%8d%e4%b8%8e%e8%af%81%e4%b9%a6/ https://shell.virbox.com/2025/12/01/%e6%90%9e%e6%87%82%e6%95%b0%e5%ad%97%e7%ad%be%e5%90%8d%e4%b8%8e%e8%af%81%e4%b9%a6/#respond Mon, 01 Dec 2025 10:14:39 +0000 https://shell.virbox.com/?p=3005 前言

      在之前的文章中,我们介绍了什么是对称加密什么是非对称加密,同时我们在文章中我们留下了一个疑问,那就是如何确认公钥和数据就是对方的而不是伪造的,今天这篇文章我们就聊聊相关的技术,签名与证书,非常有用,注意听讲哈

      签名

      平时我们说的签名更多是签个名字,比如有个文件需要经过我的确认,我再确认后需要拿起笔签上名字,证明这个文件是我已经同意了的。这里的关键点就是笔迹。

      那么在数字世界中,如果我需要对方发送一个文件给我,我如何确认我所接收到的文件就是对方发送给我的而不是有人恶意伪造的呢?或者我该如何确认对方发送给我的文件不是经过有人篡改后的呢?

      答案就是数字签名,数字签名使用的核心技术是哈希算法和非对称加密,如果大家对非对称加密不了解可以参考之前的文章

      哈希算法(HASH)

      本篇文章不对哈希算法做详细的说明,只做一个简单的介绍,让大家知道什么是哈希算法

      所谓的哈希算法也称为“散列函数”或“哈希函数”,听名字是不是被吓到了,其实它就是一种能够输入任意长度的数据,通过计算,转换为一个固定长度字符串,这个转换出的字符串被称为哈希值、散列值或信息摘要,可以把它想象为一个数据的数字指纹

      一个优秀的哈希算法具有以下几个特征

      1. 输入相同的数据,无论什么时候计算出的哈希值都必须是完全相同的
      2. 输入数据哪怕仅发生极其微小的变化,哪怕是1个bit,输出的哈希值都会发生巨大变化,不可预测的改变
      3. 无法从哈希值反向推导出原始数据,也就是单向性
      4. 几乎不可能找到两个不同的输入具有相同的哈希值

      数字签名

      数字签名的作用就是让别人相信内容没有被改过和确实是来自指定的人,同时签名者也无法抵赖说不是自己签的名

      有了哈希算法和非对称加密我们就能够实现数字签名了

      其实数字签名的流程很简单,可以分为以下几步

      1. 对需要签名的数据进行哈希运算,计算数据的哈希值
      2. 使用私钥对哈希值进行加密

      使用数字签名也很简单,进行确认签名的流程叫做验签,可以分为以下几步

      1. 从数据中提取签名数据和内容
      2. 使用公钥对签名数据进行解密得到哈希值
      3. 使用相同的哈希算法计算内容的哈希值
      4. 对比计算出的哈希值和解密出的哈希值是否一致,一致代表验签成功

      数字证书

      刚刚说了数字签名,那就有一个问题,验签时使用了公钥,我又如何知道我所获得的公钥是正确的呢?这就是所谓的CA体系

      数字证书也称为公钥证书,是一个电子文档,它遵循国际标准(X.509),这个问的那个就像是一个数字世界的身份证一样。

      它的核心作用就是将一个公钥与一个特定的实体(个人、组织)的身份信息绑定在一起,并由一个可信的第三方机构对这个绑定关系进行担保和签名

      比如用驾照来类比数字证书:

      要素驾照数字证书
      持有者信息姓名、地址、身份证号主题:持有者的名称、组织信息等
      核心凭证驾照号码公钥:证书持有者的公钥
      颁发机构车管所证书颁发机构
      机构印章车管所的官方盖章颁发者的数字签名:CA用自己的私钥对证书内容进行签名
      有效期签发日期和到期时间证书生效和失效的时间

      你相信驾照上的信息,是因为你信任车管所这个权威机构,并且驾照上有它的防伪签名和印章。

      同样,你相信一个数字证书里的公钥属于某个人或组织是因为你信任证书的颁发机构,并且证书上有颁发机构的数字签名

      总结来说就是:假如我要将我的公钥发送给对方,那我就找证书颁发机构,提供我的公钥,让证书颁发机构帮我进行签名并制作证书,然后我将制作好的证书发送给对方。

      对方拿到我的证书后需要进行验签,确认证书的颁发机构,那我们凭什么就要信任颁发机构呢?答案就是不相 信,颁发机构又会有它的证书,颁发机构的证书是由更上层的颁发机构颁发的,而更上层的颁发机构又有更更上层的颁发机构颁发,这套体系叫做CA体系,这套证书叫做证书链,一直到最上层是根证书,根证书仅有几个企业可以办法,这些跟证书已经早早的保存在我们的设备中了,只需要进行一下确认就可以了

      安全问题

      好了签名和证书已经为大家介绍完了,大家是不是已经觉的我们的程序只要使用了这套签名和证书体系就很安全了呢?哈哈哈,别太自信,你想想如果有人通过逆向的手段,直接修改我们的程序,绕过了验签流程,那再安全的方案也没有用啊

      那怎么办呢?

      程序加壳保护

      现在我们已经有了足够安全的保障体系了,那要防止的也就是我们的程序被其他人逆向分析或篡改,这时可以使用Virbox Protector工具,对我们的程序进行加壳保护,加壳时会使用混淆,虚拟化,反调试等各种手段保护我们的程序,程序经过保护后我们就不需要在为此担心啦

      ]]>
      https://shell.virbox.com/2025/12/01/%e6%90%9e%e6%87%82%e6%95%b0%e5%ad%97%e7%ad%be%e5%90%8d%e4%b8%8e%e8%af%81%e4%b9%a6/feed/ 0