I2C总线上的MITM


9

我一直在尝试设计一个模块,该模块将允许我修改I2C总线上的选定从属响应。这是原始总线配置(为清楚起见,未显示上拉和电源连接:

在此处输入图片说明 该总线上只有2个设备,并且只有100kHz。控制器MCU(I2C主设备)和RFID读卡器(I2C从设备)NXP PN512。我无法修改控制器固件或更改I2C总线事务。好消息是控制器仅发送两种交易类型:

Master (Write Register) - <s><address+W><register number><data><p> Master (Read Register) - <s><address+W><register number><p><s><address+R><data><p>

我想做的是用我自己的字节替换在主寄存器读取期间选择的数据字节。我可以通过UART(921.6kbaud)将MCU希望读取的寄存器号发送到我的PC。我可以在那里用C / C ++或Python处理它们。当我收到需要替换其值的寄存器号时,可以将假字节发送回我的设备,它将负责将其发送回控制器,以替换原始卡的响应。

首先,我将I2C总线分为两部分: 在此处输入图片说明

我尝试了Arduino Nano,后来尝试了使用时钟扩展的CPLD。面对MCU控制器的ATmega328硬件I2C不能跟上,因为有时启动序列的产生早于上一个停止周期后的5us。因此,AVR时不时要进行NAK读取事务。CPLD可以处理停止/启动速度,结果证明在MCU中禁用了总线扩展。

我想出了一个可以通过检测单字节写入来“预测”主寄存器读取的想法,因为我确信它后面会进行读取。在下一个读周期地址写入期间,似乎有足够的时间从从设备引入字节。那不是很有效。总线事务在开始时(大约前5秒)似乎还不错,但随后控制器停止了总线上的所有通信,就好像它检测到不是在直接与标签读取对话一样。

读卡器还可以产生对主机的中断。IRQ是基于计时器或事件的。我将问题归因于本来是在总线上引入的延迟。我可能错了,但我想出了另一种“零延迟”设计。 在此处输入图片说明

我的想法是,我只能断开SDA线,而使SCL线保持连接在主机和从机之间。这样,我仍然可以在任一方向上替换数据线上的字节。由于我必须根据总线周期来控制SDA线的方向,因此设计变得更加复杂。这是处理总线事务并通过UART将十六进制字节发送到计算机的VHDL代码。从计算机接收字节尚未实现:

library ieee; 
use ieee.std_logic_1164.all; 
use ieee.numeric_std.all; 

entity I2C_Sniffer is 
port ( 
 clk : in std_logic;

 scl_master : in std_logic; 
 sda_master : inout std_logic;
 sda_slave  : inout std_logic;

 tx : out std_logic

); 
end entity I2C_Sniffer; 

architecture arch of I2C_Sniffer is
 signal clkDiv: std_logic_vector(7 downto 0) := (others => '0');

 type I2C_STATE is (I2C_IDLE, I2C_MASTER_WRITE, I2C_SLAVE_ACK, I2C_MASTER_READ, I2C_MASTER_ACK);
 signal i2cState: I2C_STATE := I2C_IDLE;

 type I2C_BUS_DIR is (MASTER_TO_SLAVE, SLAVE_TO_MASTER);
 signal i2cBusDir: I2C_BUS_DIR := MASTER_TO_SLAVE;

 signal i2cRxData: std_logic_vector(7 downto 0);
 signal i2cCntr: integer range 0 to 8 := 0;

 signal i2cAddr: std_logic := '1';
 signal i2cCmd: std_logic := '0';

 signal scl_d: std_logic := '1';
 signal scl: std_logic := '1';
 signal sda_d: std_logic := '1';
 signal sda: std_logic := '1';

 --Strobes for SCL edges and Start/Stop bits
 signal start_strobe : std_logic := '0';
 signal stop_strobe : std_logic := '0';
 signal scl_rising_strobe : std_logic := '0';
 signal scl_falling_strobe : std_logic := '0';

 type UART_STATE is (UART_IDLE, UART_START, UART_DATA, UART_STOP);
 signal uartState: UART_STATE := UART_IDLE;

 signal uartTxRdy: std_logic := '0';
 signal uartTxData: std_logic_vector(7 downto 0);
 signal uartCntr: integer range 0 to 8 := 0;

begin

 CLK_DIV: process (clk)
 begin
   if rising_edge(clk) then
     clkDiv <= std_logic_vector(unsigned(clkDiv) + 1);
   end if;
 end process;

I2C_STROBES: process (clk)
begin
  if rising_edge(clk) then
    --Pipelined SDA and SCL signals

    scl_d <= scl_master;
    scl <= scl_d;

    scl_rising_strobe <= '0';
    if scl = '0' and scl_d = '1' then
      scl_rising_strobe <= '1';
    end if;

    scl_falling_strobe <= '0';
    if scl = '1' and scl_d = '0' then
      scl_falling_strobe <= '1';
    end if;

    if i2cBusDir = MASTER_TO_SLAVE then
      sda_d <= sda_master;
      sda <= sda_d;
    else
      sda_d <= sda_slave;
      sda <= sda_d;
    end if;

    start_strobe <= '0';
    if sda_d = '0' and sda = '1' and scl = '1' and scl_d = '1' then
      start_strobe <= '1';
    end if;

    stop_strobe <= '0';
    if sda_d = '1' and sda = '0' and scl = '1' and scl_d = '1' then
      stop_strobe <= '1';
    end if;
  end if;
end process;

BUS_DIR: process(sda_master, sda_slave, i2cBusDir)
begin 
  if i2cBusDir = MASTER_TO_SLAVE then
    sda_slave <= sda_master;
    sda_master <= 'Z';
  else
    sda_master <= sda_slave;
    sda_slave <= 'Z';
  end if;
end process;

I2C: process(clk)
begin
    if rising_edge(clk) then
        uartTxRdy <= '0';

        case i2cState is
            when I2C_IDLE =>
                i2cBusDir <= MASTER_TO_SLAVE;

                if start_strobe = '1' then
                    i2cAddr <= '1';
                    i2cCntr <= 0;
                    i2cState <= I2C_MASTER_WRITE;
                end if;

            -- Master Write (Address/Data)
            when I2C_MASTER_WRITE =>
                i2cBusDir <= MASTER_TO_SLAVE;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                        uartTxData <= "00001010";
                        uartTxRdy <= '1';
                end if;

                if scl_rising_strobe = '1' then
                    if i2cCntr <= 7 then
                        i2cRxData(7 - i2cCntr) <= sda;
                        i2cCntr <= i2cCntr + 1;
                    end if;
                end if;

                if i2cCntr = 4 then
                    case i2cRxData(7 downto 4) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    case i2cRxData(3 downto 0) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    if scl_falling_strobe = '1' then
                        i2cState <= I2C_SLAVE_ACK;

                        if i2cAddr = '1' then
                            i2cCmd <= i2cRxData(0);
                            i2cAddr <= '0';
                        end if;
                    end if;
                end if;

            when I2C_SLAVE_ACK =>
                i2cBusDir <= SLAVE_TO_MASTER;

                if scl_falling_strobe = '1' then
                    i2cCntr <= 0;

                    if i2cCmd = '0' then
                        i2cState <= I2C_MASTER_WRITE;
                    else
                        i2cState <= I2C_MASTER_READ;
                    end if;
                end if;

            when I2C_MASTER_READ =>
                i2cBusDir <= SLAVE_TO_MASTER;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                        uartTxData <= "00001010";
                        uartTxRdy <= '1';
                end if;

                if scl_rising_strobe = '1' then
                    if i2cCntr <= 7 then
                        i2cRxData(7 - i2cCntr) <= sda;
                        i2cCntr <= i2cCntr + 1;
                    end if;
                end if;

                if i2cCntr = 4 then
                    case i2cRxData(7 downto 4) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    case i2cRxData(3 downto 0) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 and scl_falling_strobe = '1' then
                    i2cState <= I2C_MASTER_ACK;
                end if;

            when I2C_MASTER_ACK =>
                i2cBusDir <= MASTER_TO_SLAVE;
                if scl_falling_strobe = '1' then
                    i2cCntr <= 0;
                end if;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                    uartTxData <= "00001010"; -- \n
                    uartTxRdy <= '1';
                end if;
        end case;
    end if;
end process;


UART: process (clk, clkDiv(1), uartTxRdy)
begin
    if rising_edge(clk) then
        case uartState is
            when UART_IDLE =>
                if uartTxRdy = '1' then
                    uartState <= UART_START;
                end if;

            when UART_START =>
                if clkDiv(1 downto 0) = "00" then
                    tx <= '0';
                    uartState <= UART_DATA;
                    uartCntr <= 0;
                end if;

            when UART_DATA =>
                if clkDiv(1 downto 0) = "00" then
                    if uartCntr <= 7 then
                        uartCntr <= uartCntr + 1;
                        tx <= uartTxData(uartCntr);
                    else
                        tx <= '1';
                        uartState <= UART_STOP;
                    end if;
                end if;

            when UART_STOP =>
                if clkDiv(1 downto 0) = "00" then
                    tx <= '1';
                    uartState <= UART_IDLE;
                end if;
        end case;
    end if;
  end process;
end architecture arch;

以下是CPLD控制SDA线捕获的总线转换。

注册写:

在此处输入图片说明

注册阅读:

在此处输入图片说明

当总线方向改变时,您会看到一些故障。这是由于CPLD更改总线方向与读卡器生成ACK之间的时序差异引起的。ACK电平在SCL的上升沿似乎稳定。据我所知,这就是您所需要的。

有了这个东西,控制器的行为就与拆分总线在几秒钟内暂停任何总线活动的方式相同。我还测试了可以模拟我的MCU并为我生成总线流量的Arduino,而且Arduino有时也会冻结。所以我想我在VHDL状态机上可能会遇到某种问题,在某些情况下,我陷入了一种状态而没有出路。有任何想法吗?


无论如何,对我来说,你的问题不是很清楚。首先你说There's only 2 devices on this bus running at 100kHz然后The hardware I2C was a slave and a bit banged I2C was a master on the card reader bus at 1Mbps。为什么有两辆公共汽车?为什么需要高速巴士?提供初始设计的草图,并尝试阐明您的问题。
SoreDakeNoKoto 2016年

是的,对不起。原始总线仅具有控制器(i2c主设备)和RFID卡/标签读取器(i2c从设备)。因此,我真的不必担心I2C寻址是点对点的(主机发送的每个数据包都是针对该从机的)。我的第一种方法是将总线分为2条总线,并在控制器侧充当i2c从设备,在RFID读取器侧充当主机。
Alexxx '16

RIFD读取器具有更快的速度(1MHz甚至更快),所以我想我可能会用到它,所以在我从RFID读取器读取数据时,不要将总线(总线扩展)放在控制器端的时间太长寄存器。另外,当检测到单个字节写入时总线没有伸展时,在下一个读取周期中,我只有很少的时间从RIFD读取器读取字节并将其发送回控制器。
Alexxx '16

总线拉长是指I2C时钟延展,其中从机将SCL线保持为低电平,以使主机知道尚未准备好处理数据。当从机准备就绪时,它将释放SCL线,并且主机继续读取从机发送的位。
Alexxx '16

1
最好改为编辑问题。您仍然没有解释为什么需要2辆巴士。如果您只需要从使用I2C的读卡器中读取数据,那为什么不将它们连接到同一总线上并让您的MCU读取数据呢?时钟延长仅在从站方面有意义,通常是在其对主机的响应速度较慢时。如果从站没有准备好,您将无能为力。100-400kHz通常足以满足大多数应用的需求。如果您必须对读取的数据或类似数据执行一些对时间敏感的操作,那么您可能想要更快的速度的唯一原因。
SoreDakeNoKoto

Answers:


6

我认为尝试像您一样的过激黑客正在问麻烦,而您遇到的正是这种症状。您基本上是在试图作弊,希望您不要被抓住。

根据您的描述,您尚未尝试的一件事就是对该读卡器事物的完整仿真。您还没有真正解释它的确切功能以及它的复杂性,但是从主机发送的内容来看并没有那么复杂。

使用具有硬件IIC从属功能的微控制器。即已连接到主机。固件模拟读卡器。由于主控制器唯一读取的是一系列寄存器,因此固件的另一部分与读卡器完全异步通信,以从中获取信息并进行控制。这也意味着复位和IRQ线也分开。

如果做得对,这必须奏效,因为没有作弊行为。读卡器会看到控制器向其发送命令,并准确地读取其预期用途。这包括响应IRQ事件。

主机认为它直接与真实的读卡器对话,因为您可以像真实物品一样模拟其所有操作,包括重置和IRQ行为。

这听起来可能比快速而又肮脏的将一个不同的字节阻塞到总线hack上要花更多的时间,但是正如您所发现的那样,它不是那么快,并且可能总是存在一些时序问题。通过完全仿真,所有时序约束都将得到消除。如果您的仿真尚未赶上读卡器所做的事情,那么它将像尚未发生那样对主机起作用。您基本上会假装没有发生任何新的事情,直到您的仿真准备好全方位地响应该事件为止。

这意味着您确实有固件的两个异步部分:呈现给主机的读取器的IIC仿真,以及一个完整的读卡器驱动程序,可让您将其所有状态保持在内部存储器中。

由于您没有作弊,因此必须正确操作。唯一的系统级问题是,与现有系统相比,主控机在看到和引起读卡器动作方面会有一些延迟。对于“读卡器”来说,这听起来没什么大不了的,考虑到这种延迟,最坏的情况可能是10毫秒。在人类时间尺度上,它当然不应该引起注意。

请注意,仿真器和读卡器之间的通信不限于当前使用的100 kbit / s。您应该以读卡器和硬件允许的速度运行。毕竟,在该链接上,您将成为主人,因此您拥有时钟。同样,通过适当的固件体系结构和异步任务,这无关紧要。实际上,与主设备从模拟器获得的驱动程序相比,驱动程序可能会更频繁地进行通信并从读卡器获取更多数据。


感谢您的回答。当我第一次说看时,我确实有一些想法。我很快放弃了这个想法,因为这似乎很复杂。如果MCU只写和读寄存器,那很容易,但是读取器与具有自己的协议(多字节命令和响应)的RFID通信。最重要的是,MCU正在为阅读器中的IRQ设置一些标志并回读雕像。因此,似乎只将几个字节作为目标,而其余的留给读者则容易得多。
Alexxx '16

另外,如果我将整个总线分成2条总线,我的确可以更快地与读卡器通信。但是,在最新的设计中,我只切断了SDA线,所以我必须坚持MCU在SCL线上提供的时序,即100KHz。
Alexxx '16

0

我建议您将Arduino Nano作为MITM放在正确的轨道上,尽管我认为两个最好。

在此处输入图片说明

NXP-PN512将以3.4 Mhz的时钟速度运行,因此我建议您可以使用1.5-2 MHz的数量级,以使右边的MCU与阅读器通信。
由于左手MCU设置为100 kHz,一旦识别出任何事务字节(地址/寄存器WR),就可以通过8位并行总线(或更宽)在MCU之间复制它,并将命令发送到读取器中。在慢速I2C通道上少于一个时钟时间。在较慢的总线上,只需不到一个时钟时间就可以从阅读器中平均接收到一个字节,从而为建立应答字节提供了足够的时间。

我在这里假设您实际上可能需要将多个字节转换为NFC ID,而不仅仅是一个字节一个字节的转换(这需要更少的时间)。

那时我会看到的主要问题是,如果您确实需要将多个字节串行化到PC或从PC串行化以映射您的更改,则时序变得更加关键。如果有一种方法可以将映射更改算法/表构建到左手MCU中,那似乎是一个更好的方法,尽管解决多路标识符映射仍然是最大的挑战。

如果我错了,而您只需要映射说一个卡标识符字节,那么这可能会起作用。

在使用Arduino进行的早期测试中,您是否确保所有中断均已关闭(至少仅使用了TWI)?如果您没有这样做,那么这可能会弄乱您的时间安排。


1
我不明白为什么需要两个单独的微镜。大量的微控制器可以同时处理两条IIC总线。尽管使用硬件即使成为主服务器也很方便,但实际上您只需要硬件即可作为从服务器。与在同一微型计算机中运行的两个任务相比,在两个微型计算机之间进行通信似乎不必要且复杂且缓慢。我看不到两个微米解决的问题。
奥林·拉斯特罗普

@Olin Lathrop。它简化了软件开发。使得调试变得更加简单等。这就像为什么汽车中装有100个微处理器,而不是一个(可能会建议的那样简单)大型多进程处理器。我使用多个MCU绝对没有问题,这些MCU的成本大多低于单功能逻辑芯片的成本,并且功能更易于定义和开发。在这种情况下,ATMega328中只有一个TWI中断向量,因此要支持两个I2C通道就比较困难。..但这当然是个人选择。
杰克·克雷西

在这种情况下,多个处理器增加了复杂性,并引入了额外的通信需求。当您是总线主控器时,无需为IIC使用中断或完全没有硬件。但是,有很多处理器可以在硬件中独立处理两条IIC总线。如果ATMega不能,并且您要使用两个硬件IIC,则不要使用ATMega。
奥林·拉斯罗普

@Olin Lathrop。让我们接收有差异的看法。超过100 kHz的IMO比特扑灭是无法启动的。OP的问题在于,将数据串行化发送到PC以执行映射算法的成本充满了时序问题。
杰克·克雷西

感谢您的回答和评论。由于MCU I2C时序,ATMega328 / Arduino无法在MCU端使用。该MCU能够在先前停止后生成比4.7us更快的启动序列。请查看ATMega328数据表中的表32-10(tBUF参数)。发生的事情是,Arduino在对i2c写入之后的所有i2c读取进行NACK处理。这显然是一个已知问题。我把所有的头发都拉过去之后,在网上某个地方找到了有关该信息的信息。这就是为什么我切换到CPLD的原因。
Alexxx '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.