0%

在C语言里一种有效组织数据的方式就是结构体,除了储存多种类型的数据外,结构体也是使用一个协议的数据格式最好的办法。这样做还有一个好处就是上位机采用同样的结构体即可通信,在代码层面的移植相当容易。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct{
int a,b;
char c;
float f;
}data_t;

data_t bundle;
//给各个数值输入值
bundle.a = 1;
bundle.b = 2;
bundle.c = 'a';
bundle.f = 0.0f;
//此处采用转换指针类型的形式调用如串口等发送数据
uart_send((unsigned char*)&bundle);

然而有个问题,结构体内部的变量并不是紧密的排在一起,他们中间默认是有空隙的。在上面的代码中sizeof(data_t)的值并不是4+4+1+4,而是在char c后面增加了三个字节的空位。这就是内存对齐的问题,本质的原因是硬件上为了减少访问次数,通常会将变量按照4字节对齐。

通俗一点的解释办法,这个原则就是要求单个变量不能跨4字节(当然双精度对于这个原则不适用,但对于四字节及四字节以下的变量,这个原则却是正确的)。那么有没有变量连续储存的结构体呢?由于内存对齐实际上是编译器的优化,而不是硬件的强制要求,所以我们可以配置编译器让它在以牺牲访问次数的代价来换取这种对齐模式。参照编译器手册,我们可以利用#pragma pack(1)来完成或者在struct前加__PACKED

参考:https://zhuanlan.zhihu.com/p/30007037

阅读全文 »

串口接收

串口是一种直接接收串行数据的方法,而通常固定数据帧的结构为多个字节,在处理的时候实际上是针对整帧。因此需要通过某种方法将串行数据转换为相当于同一时刻连续出现的数据处理。评定这种转换方法的优劣有以下方面

  • 正确性
    能正确识别数据帧的帧头和帧尾,总是可以将接收到的各种数据正确转换,不论发送的间隔,发送的时机如何,甚至也可以处理不定长的数据
  • 可靠性
    当发送数据出现不符合协议规定的数据时能够判断出数据错误,并返回错误信息,同时处理该错误时不会影响到接下来的接收过程
  • 完整性
    在保证以上两点的情况下尽可能减少数据丢失

实际上,在STM32的串口中,我们接收数据通常会采用以下两种方法

  • 串口中断接收
    当每接收到一个字节时发生一次中断,处理一个字节,通常会编写一个有限状态机(FSM)去识别和处理一帧数据。这种方法是最原始的,最通用的接收方法,但是每接收一个字节都需要占用内核时间,同时不容易编写发现和处理错误的代码段,因此目前没有被我们使用
  • 串口DMA接收
    DMA是直接内存访问(Direct Memory Access)的缩写。顾名思义,它是一个直接访问处理内存数据的外设,通过预先配置可以让该外设自动传输内存中的数据而无需内核干预。在串口的应用中,在接收到一个字节后DMA将串口数据寄存器中的数据直接复制到指定内存地址中,这个内存地址可以预先设定,并且可以配置DMA在每次完成传输后地址自增1,直到达到预设的数据长度为止。于是我们可以设定一块缓冲区,在正确配置DMA的情况下即可实现将接收到的数据连续填入这个缓冲区内,整个过程由DMA控制器完成。DMA本身可以配置在传输数据过半(完成预先设定的数据长度的一半)或者完成所有传输后发出中断,以便内核及时处理缓冲区内的数据。
阅读全文 »

工具集

​ IDA, gdb & pwndbg, checksec, pwntools, LibcSearcher

漏洞点

拿到程序后用checksec看,64位ELF,发现没有主程序canary,NX以及ALSR

1
2
3
4
5
6
7
8
9
10
11
12
[*] '/home/ctf/pwn/pwn1'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/ctf/pwn/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
阅读全文 »

simple

x64的ELF,主函数反编译

xor

xor函数是将两个数组异或后放回第二个数组arr内,同时判断flag是否正确有两个条件,一个是长度为26字节,一个是异或后数组与src相等。因此将src和arr异或即可解出原来的字符串。

arr

阅读全文 »

看门狗使用

独立看门狗(IWDG):通常检测硬件导致的运行出错,独立时钟运行
窗口看门狗(WWDG):通常检测软件漏洞导致的死机死循环,早喂狗晚喂狗都会复位,复位前可产生中断以便保存数据

如何喂狗

​ 正确使用看门狗定时器,但是它并不像重新载入计数器那样简单(通常被称为喂狗或者踢狗)。在其系统中运行看门狗定时器时,开发人员必须仔细选择看门狗的超时时间,以便看门狗在发生故障的系统可以执行任何不可逆转的恶意动作之前进行干预。

在简单的应用中,特别是没有使用RTOS,开发人员通常会从主循环(main loop)中提供看门狗。该方法仅需要配置适当的初始计数器值,它可以简单地选择任何超过整个主循环最坏的执行时间的值,至少有一个计时器周期。这通常是一个非常有效的方法,虽然有一些系统需要立即恢复,但更多系统只需要确保它们不会被无限期地挂起,这一方法能很好的实现之一目的。

调试技巧

  1. 上电后(或初始化完成后)点亮一枚LED可判断上述过程是否有误(复位或初始化代码问题排查),运行main函数的死循环时让LED闪烁可以判断系统此时运行状况(有效判断操作执行是否异常)
  2. 完成前期模块测试后先使用模拟器检查软件运行是否正确,后再烧写上机调试
  3. 通过对某个GPIO引脚产生逻辑电平作为调试信号,辅之以示波器可以调试某些对时序性较高的过程代码
  4. Keil的调试技巧(内存读写断点,条件/次数断点,运行时在断点用Command输出字符)

优化提示

  1. 在本机仿真或用某些调试器时可能有Profiler工具查看执行时间与调用次数,方便确定优化目标
  2. 在开发阶段务必将编译器优化关掉,因为某些编译器优化会使代码执行结果异常(必要时使用volatile变量)
  3. 使用查询表(函数指针的数列)代替switch,化判断为寻址
  4. 使用全局变量来传入参数(但程序模块化下降),特别是浮点数等较大的数据类型;使用register修饰变量可针对频繁读取的变量优化
  5. 使用内联函数优化某些微小的函数,降低堆栈传参带来的额外开销
  6. 适配堆栈大小,使程序在正常运行的情况下不浪费内存(但注意堆栈溢出会带来致命的后果)
    做法:在函数内定义变量如int a[n];并赋一个特殊值,调整n的大小直到程序完整运行的时候崩溃(崩溃后应该进行跟踪),以此估算所需堆栈大小
    减小堆栈大小:避免递归算法,减小局部变量的大小,内存对齐问题
  7. 避免浮点运算或用硬件指令代替;手动写汇编

可靠性改进

阅读全文 »

现象:使用CubeMX多次Generate Code发现串口初始化出现问题,DMA的CR寄存器除了中断位和使能位其他配置未能写入。调试时发现似乎写入寄存器CR时无效。此问题在仿真情况下不会发生

img

原因:在先配置UART后Generate Code回头再修改DMA配置,第二次Generate Code后会出现该问题。MX_DMA_Init()初始化函数中负责启动DMA2的时钟,而UART初始化里面也包含了部分DMA2的初始化配置。观察例程可判断正确顺序应该是先MX_DMA_Init()再MX_USART1_UART_Init(),但是在这种情况下,CubeMX的MX_USART1_UART_Init()和MX_DMA_Init()的顺序就会按照Generate Code的顺序。个人推测根本原因应该是在这种顺序下,DMA的时钟未启动而提前写入配置导致配置丢失。参见这篇文章

解决方案:一定可行的办法是一次配置重建整个项目。在不推倒重来的前提下,可以在CubeMX中取消勾选UART和DMA,然后Generate Code,再按照原先配置勾选UART和DMA然后再Generate Code即可。或者暴力修改.ioc文件中的ProjectManager.functionlistsort,调换UART和DMA的顺序。在Project Manager下的Advanced Setting里面可以看到顺序。

现象:打开调试/仿真Command窗口出现“*** error 65: access violation at 0x40021000 : no ‘read’ permission”,程序停在了system_stm32f10x.c中的一行

原因:该仿真器对只保证CMSIS兼容性,并没有对STM32进行专门配置

解决方案:修改仿真器/调试器加载的DLL,如图

img