全部产品

安全运营管理-SOC(基础版)for RTOS

本文档介绍了安全运营管理-SOC(基础版)如何在RTOS中集成。

SOC(基础版)是一个端到端的服务,为 RTOS/Linux/Android 物联网终端提供安全审计。SOC(基础版)SDK 是SOC 在终端的代理,会实时上报运行状态,定期检查系统的完整性,让管理者随时了解物联网终端的安全状况,及时发现,排查和修复安全问题。

前提条件

SOC (基础版)SDK 是以开源代码方式提供,用户需要根据目标系统的开发要求将 SDK 解压到特定位置。

注意

集成SOC(基础版)后,调用接口“int32_t aiot_das_stepping”会上报MQTT消息。您可以通过控制调用接口的频率来控制设备端SOC(基础版)发送的消息量。

SOC(基础版)SDK简介

SOC(基础版) SDK 分为 Core,Platform,Service 三个层次提供应用集成,平台移植和服务扩展功能,旨在方便各种物联网系统利用 SOC 服务一站式保护物联网终端安全。

SOC基础版SDK简介

1,平台适配

1.1 设置日志开关。

编译时候传入 DAS_DEBUG,即可开启 SOC(基础版) SDK 日志。SOC (基础版)SDK 代码中打印日志接口为 DAS_LOG(...) 定义在 das/include/das/platform.h。

1.2 设置设备身份信息

请适配物联网终端唯一 ID,协助 SOC(基础版)SDK 识别物联网终端,ID 可以是 MEID、CPU ID、MAC 地址等等,形式为字符串即可。

函数原型

适配接口在 das/src/board/ec600s/das_board.c (移远 ec600s 开发板为例)。

size_t das_hal_device_id(char *buf, size_t size);

参数

  • buf (入参):用来存放物联网终端唯一 ID 的缓存。

  • buf (入参):buf 的大小,目前为 96 字节,您可以自行修改 DEVICE_ID_MAX_LEN,但由于物联网终端唯一 ID 会上报到云端,请配合您云端通道(如 MQTT)的单笔消息频宽设定。

返回值

  • 若成功,请返回实际写入 buf 的 ID 大小,以字节为单位。

  • 若失败,请返回 0。

1.3 适配ROM扫描范围

如果您有 text 或者 ROM code,您希望确保完整性,可以适配此接口,SOC(基础版)SDK 将定期帮您扫描计算完整性。

函数原型

适配接口在 das/src/board/ec600s/das_board.c (移远 ec600s 开发板为例)。

int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER]);

参数

  • buf (入参):希望扫描的 bank 范围,您需填写每个 bank 的起始和结束地址,最多可以设定 DAS_ROM_BANK_NUMBER 个段。

返回值

  • 若成功,请返回 DAS_ROM_BANK_NUMBER。

  • 若失败,请返回 0。

示例

举例来说,您有两段代码段想要扫描,且您认为这两段代码段正常情况下不该被修改(除非返厂重烧),如 text、code 等等,则您可在 das_hal_rom_info 设置它。代码段的位置可根据您的 layout file (如 scatter file) 所编写的 symbol 位置去设定。需注意,您要设定的代码段要是您的 task 可读的,以免发生 CPU exception。

示例
extern uint32_t __flash_text_start__;
extern uint32_t __flash_text_end__;

extern uint32_t __ram_image2_text_start__;
extern uint32_t __ram_image2_text_end__;

int das_hal_rom_info(das_rom_bank_t banks[DAS_ROM_BANK_NUMBER])
{
    banks[0].address = &__flash_text_start__;
    banks[0].size = &__flash_text_end__ - &__flash_text_start__;

    banks[1].address = &__ram_image2_text_start__;
    banks[1].size = &__ram_image2_text_end__ - &__ram_image2_text_start__;

    return 2;
}

1.4 互斥锁适配

请在 das/src/service/service_sys.c 和 das/src/service/service_lwip_nfi.c 文档中适配互斥锁,比方说 POSIX 线程 pthread 或者 AliOS-Things 互斥锁 aos_mutex。

1.5 网络流量采集适配

如果您的网路栈是基于 LWIP,且您可以修改到 LWIP 协议栈,请参考 "1.5.1 基于 lwip 的 网路流采集" 篇章做网路监控挂载。

如果没有,可以采用 “1.5.2 自定义网路流采集”,根据您的网路栈做挂载,或者挂载在AT命令负责派送网路的出入口也可以。

说明

您挂载的任务可能和您的 MQTT 任务不同,请将 das/src/core/das_attest.c 和将被挂载的任务一起编译。das_attest.c 会将数据写到一个全局缓存,SOC(基础版)SDK将从此全局缓存读取数据。

1.5.1 基于LWIP的网络流采集

如果您的网路栈是基于 LWIP,且您可以修改到 LWIP 协议栈,您可采用此网路流采集方式。常见 RTOS 如 FreeRTOS 和 AliOS-Things 上,都是采用 LWIP 协议栈。SOC(基础版)SDK 提供 das_attest API 对网络行为进行监控。主要原理是在系统的 LWIP 协议栈中,插入 das_attest 审计代码,从而记录网络流的进程。

TCP网络流量进入

在 tcp_in.c 文件开头引入相关头文件。

#include "lwip/ip.h"
#include <das.h>

在 tcp_in.c 文件的 tcp_input 函数中插入 das_attest 监控代码。

void
tcp_input(struct pbuf *p, struct netif *inp)
{
  ....
      
  /* Convert fields in TCP header to host byte order. */
  tcphdr->src = ntohs(tcphdr->src);
  tcphdr->dest = ntohs(tcphdr->dest);
  seqno = tcphdr->seqno = ntohl(tcphdr->seqno);
  ackno = tcphdr->ackno = ntohl(tcphdr->ackno);
  tcphdr->wnd = ntohs(tcphdr->wnd);

  // 插入如下代码 {
  das_attest("NFI_INPUT", 
    ip_current_src_addr()->addr, 
    ip_current_dest_addr()->addr, 
    tcphdr->src,
    tcphdr->dest,
    p->tot_len, 
    ip_current_header_proto()
  );
  // }
   
  flags = TCPH_FLAGS(tcphdr);
  tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);

  /* Demultiplex an incoming segment. First, we check if it is destined
     for an active connection. */
  prev = NULL;

  ...
}

UDP协议流量进入

在 udp.c 文件开头引入相关头文件。

#include "lwip/ip.h"
#include <das.h>

在 udp.c 文件的 udp_input 函数中插入 das_attest 监控代码。

void
udp_input(struct pbuf *p, struct netif *inp)
{
  ...

  LWIP_DEBUGF(UDP_DEBUG, ("udp_input: received datagram of length %"U16_F"\n", p->tot_len));

  /* convert src and dest ports to host byte order */
  src = ntohs(udphdr->src);
  dest = ntohs(udphdr->dest);

  udp_debug_print(udphdr);
    
  // 插入如下代码 {
  das_attest("NFI_INPUT", 
    ip_current_src_addr()->addr, 
    ip_current_dest_addr()->addr, 
    src,
    dest,
    p->tot_len, 
    ip_current_header_proto()
  );
  // }
  
  /* print the UDP source and destination */
  LWIP_DEBUGF(UDP_DEBUG,
              ("udp (%"U16_F".%"U16_F".%"U16_F".%"U16_F", %"U16_F") <-- "
               "(%"U16_F".%"U16_F".%"U16_F".%"U16_F", %"U16_F")\n",
               ip4_addr1_16(&iphdr->dest), ip4_addr2_16(&iphdr->dest),
               ip4_addr3_16(&iphdr->dest), ip4_addr4_16(&iphdr->dest), ntohs(udphdr->dest),
               ip4_addr1_16(&iphdr->src), ip4_addr2_16(&iphdr->src),
               ip4_addr3_16(&iphdr->src), ip4_addr4_16(&iphdr->src), ntohs(udphdr->src)));

  ...
}

网络流量出方向

在 ip.c 文件开头引入相关头文件。

#include <das.h>

在 ip.c 文件的 ip_output_if_opt 函数中插入 das_attest 监控代码。

err_t ip_output_if_opt(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
       u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options,
       u16_t optlen)
{
  ...

  LWIP_DEBUGF(IP_DEBUG, ("netif->output()"));

  // 插入如下代码 {
  {
    u16_t sport = 0, dport = 0;
    u16_t len = 0;
    u16_t iphdr_hlen = IPH_HL(iphdr);

    iphdr_hlen *= 4;

    if (proto == IP_PROTO_UDP || IPH_PROTO(iphdr) == IP_PROTO_UDP) {
      struct udp_hdr *udphdr = (struct udp_hdr *)((u8_t *)iphdr + iphdr_hlen);
      if (udphdr) {
        sport = lwip_ntohs(udphdr->src);
        dport = lwip_ntohs(udphdr->dest);
        len = (p->tot_len > iphdr_hlen) ? (p->tot_len - iphdr_hlen) : p->tot_len;
      }
    }
    else if (proto == IP_PROTO_TCP || IPH_PROTO(iphdr) == IP_PROTO_TCP) {
      struct tcp_hdr *tcphdr = (struct tcp_hdr *)((u8_t *)iphdr + iphdr_hlen);
      if (tcphdr) {
        sport = lwip_ntohs(tcphdr->src);
        dport = lwip_ntohs(tcphdr->dest);
        len = (p->tot_len > iphdr_hlen) ? (p->tot_len - iphdr_hlen) : p->tot_len;
      }
    }

    if (dport) {
      das_attest("NFI_OUTPUT", 
          src->addr,
          dest->addr, 
          sport,
          dport, 
          len,
          proto
          );
    }
  }
  // }
    
  return netif->output(netif, p, dest);
}

1.5.2 自定义网络流量采集

如果您的网路栈不是基于 LWIP,或者您无法修改 LWIP 协议栈,您可采用自定义网路流采集方式。您需要提供您每笔网路流的源 IP、源端口、目标 IP、目标端口、协议。您可根据您的网路栈做挂载,或者挂载在 AT 命令负责派送网路的出入口等等也行。

挂载TCP网络流量进方向

#include "das.h"
#include "inet.h"

// ...

struct in_addr remote_addr;
struct in_addr local_addr;
inet_aton(remote_ip, &remote_addr);
inet_aton(local_ip, &local_addr);

das_attest("NFI_INPUT", 
    remote_addr.s_addr, // 外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    local_addr.s_addr,  // 本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    remote_port,        // 外部端口,uint32_t。
    local_port,         // 本机端口,uint32_t。
    packet_len,         // 包长度,uint32_t。
    6                   // 填写 6,代表 TCP,uint32_t。
);

// ...

挂载TCP网络流量出方向

#include "das.h"
#include "inet.h"

// ...

struct in_addr remote_addr;
struct in_addr local_addr;
inet_aton(remote_ip, &remote_addr);
inet_aton(local_ip, &local_addr);

das_attest("NFI_OUTPUT", 
    local_addr.s_addr,  // 反过来,本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    remote_addr.s_addr, // 反过来,外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    local_port,         // 反过来,本机端口,uint32_t。
    remote_port,        // 反过来,外部端口,uint32_t。
    packet_len,         // 包长度,uint32_t。
    6                   // 填写 6,代表 TCP,uint32_t。
);

// ...

挂载UDP网络流量进方向

#include "das.h"
#include "inet.h"

// ...

struct in_addr remote_addr;
struct in_addr local_addr;
inet_aton(remote_ip, &remote_addr);
inet_aton(local_ip, &local_addr);

das_attest("NFI_INPUT", 
    remote_addr.s_addr, // 外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    local_addr.s_addr,  // 本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    remote_port,        // 外部端口,uint32_t。
    local_port,         // 本机端口,uint32_t。
    packet_len,         // 包长度,uint32_t。
    17                  // 填写 17,代表 UDP,uint32_t。
);

// ...

挂载UDP网络流量出方向

#include "das.h"
#include "inet.h"

// ...

struct in_addr remote_addr;
struct in_addr local_addr;
inet_aton(remote_ip, &remote_addr);
inet_aton(local_ip, &local_addr);

das_attest("NFI_OUTPUT", 
    local_addr.s_addr,  // 反过来,本机 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    remote_addr.s_addr, // 反过来,外部 IP 位置,需要是 inet_aton 转换后格式,uint32_t。
    local_port,         // 反过来,本机端口,uint32_t。
    remote_port,        // 反过来,外部端口,uint32_t。
    packet_len,         // 包长度,uint32_t。
    17                  // 填写 17,代表 UDP,uint32_t。
);

// ...

2,应用集成

在一般物联网终端的实际开发中,由于资源受限,每个物联网终端网络连接的数目是受限的。SOC(基础版)SDK在核心层只定义了 SOC(基础版)SDK 相关数据的订阅和分发接口,物联网终端可以根据实际的网络会话上对接 SOC(基础版) 服务。

2.1 初始化核心服务

初始化核心服务,并返回服务全局 session 实例。

函数原型

void* das_init(const char *product_name, const char *device_name);

参数

  • product_name(入参):产品名称。

  • device_name(入参):物联网终端名称。

  • P.S. 如果使用阿里云 IoT 平台上云,那么上述两个参数都可以在阿里云 IoT 平台上申请获得;如果自己实现上云通道,那么这两个参数自定义即可。

返回值

  • 初始化成功,返回服务的 session 指针,用于后续安全服务相关函数调用。

  • 初始化失败,返回 NULL 。

2.2 设置固件版本号

函数原型

int das_set_firmware_version(char *ver);

参数

  • ver(入参):固件版本号字串,比如说 “lemo-1.0.0-20191009.1111”。

返回值

  • 初始化成功,返回 0。

  • 初始化失败,返回 -1 。

2.3 设置上下行消息主题

服务器端和客户端根据消息的 topic 类型来确认是否是自己需要处理的数据。

函数原型

  • 设置上行消息主题

const char* das_pub_topic(void *session,const char *topic);
  • 设置下行消息主题

const char* das_sub_topic(void *session, const char *topic);

参数

  • session(入参):由 das_init 函数返回的服务实例。

  • topic(入参):自定义的上下行消息 topic 字符串;如果想使用内置的缺省 topic,则此值为 NULL 。

返回值

  • 设置成功,返回 默认/自定义 topic 字符串。

  • 设置失败,有可能是参数错误,或者 topic 字符串超过 64 字节,则返回 NULL 。

说明

  • 如果 topic 参数为 NULL ,那么返回内置的缺省 topic 字符串,原型如下:/sys/$(product_name)/$(device_name)/security/up(down)stream

  • 自定义 topic 字符串长度不能超过 64 字节,否则设置会失败。

2.4 配置网络连接

SOC (基础版)SDK 采集的数据需要通过业务已有的网络通道进行上报。

函数原型

void das_connection(void *session,
                    publish_handle_t publish_handle,
                    void *channel);

参数

  • session(入参):由 das_init 函数返回的实例。

  • publish_handle(入参):需要用户自己实现,用来发送数据的函数指针。

  • channel(入参):业务创建,用于数据上报的网络通道实例。

返回值

  • 无。

2.5 消息发送回调

用来发送数据的回调函数。如果 SOC(基础版)SDK 有数据需要上报,那么该回调由安全服务内部触发。请将此回调通过 das_connection 注册。

函数原型

typedef int (*publish_handle_t)(const char *topic,
                                const uint8_t *message, size_t msg_size,
                                void *channel);

参数

  • topic(入参):数据上行的 topic 类型。

  • message(入参):需要发送的数据。

  • size(入参):需要发送的数据大小。

  • channel(入参):业务创建,用于数据上报的网络通道实例。

返回值

  • 数据发送成功,返回0。

  • 数据发送失败,返回负数。

注意

该接口需要用户实现。

2.6 更新网络连接状态

当业务的网络状态发生变化时,需要通知 SOC(基础版)SDK。

函数原型

  • 业务网络已连接

void das_on_connected(void *session);
  • 业务网络已断开

void das_on_disconnected(void *session);

参数

  • session(入参):由 das_init 函数返回的实例。

返回值

  • 无。

2.7 处理下行数据

当业务收到服务器下发的指令或数据的时候,可以通过 topic 来区分是否是 SOC 的。如果是,则需要通知 SOC(基础版)SDK 来处理。

函数原型

void das_on_message(void *session, const uint8_t *message, size_t msg_size);

参数

  • session(入参):由 das_init 函数返回的实例。

  • message(入参):由服务器端下发的数据。

  • size(入参):下发数据的字节数。

返回值

  • 无。

2.8 步进驱动取证服务

SOC(基础版)SDK 不会主动执行取证操作,而是需要业务定时来驱动执行。

函数原型

das_result_t das_stepping(void *session, uint64_t now);

参数

  • session(入参):由 das_init 函数返回的实例。

  • now(入参):当前系统时间,以毫秒为单位。

返回值

  • int(das_result_t)类型,成功返回 0;失败返回负数。

2.9 终止核心服务

在程序退出的时候,需要注销核心服务。

函数原型

void das_final(void *session);

参数

  • session:由 das_init 函数返回的实例。

返回值

  • 无。

3,示例代码

完整示例代码请参看$(das_sdk)/example/lv-example/mqtt_das_example.c

#include "iot_import.h"

#define PRODUCT_KEY     "demo_das_product"
#define DEVICE_NAME     "demo_das_device_1"

static void *session = null;

static int _on_publish(const char *topic, uint8_t *msg, uint32_t size, void *mqtt)
{
    iotx_mqtt_topic_info_t topic_msg;
    topic_msg.qos = IOTX_MQTT_QOS1;
    topic_msg.payload = (void *)message;
    topic_msg.payload_len = length;
    return IOT_MQTT_Publish(mqtt, topic, &topic_msg);
}

static void on_message(void *handle, void *pclient, iotx_mqtt_event_msg_pt msg)
{
    das_on_message(session, msg.payload, msg.payload_len);  
}

int main(int argc, const char argv[][])
{
  const char *sub_topic;

  mqtt = IOT_MQTT_Construct(&mqtt_params);
  
  session = das_init(PRODUCT_KEY, DEVICE_NAME);
  
  das_set_firmware_version("lemo-1.0.0-20191009.1111");
    
  sub_topic = das_sub_topic(session, NULL);
    
  das_connection(session, _on_publish, mqtt);
  
  das_on_connected(session);
  
  IOT_MQTT_Subscribe(mqtt,
      sub_topic, IOTX_MQTT_QOS1, on_message, session);

  while (IOT_MQTT_Yield(mqtt, 200) != IOT_MQTT_DISCONNECTED) {
      ...
      das_stepping(session, time(NULL));
      ...
  }

  das_on_disconnected(session);
  das_final(session);
  
  return 0;
}