PIC代码的移位

qc1iu published this page · Last modified:

对编译器已经生成的PIC代码进行混淆,其实还是很有意思的。关于什么是PIC代码可以看这里。我用ARM架构的thumb指令集来举例子。PIC代码与普通代码不同的地方在于,其代码中有很多是相对寻址。比如:

 0x00    ldr r1, [pc, #4]    ①
 ...
 0x08    .word   0x0001      ②

上面的代码①和②之间的距离必须是固定的8byte,因为语句①使用了pc做相对寻址,所以两条语句的相对位置一旦改变会导致语句①无法拿到语句②位置的常数1。

对于这种pc相关的代码,我们只要做相应的处理就可以。比如ldr指令,如果用pc相对寻址,那么所要寻找的常数是可以静态计算出来的,我们可以把上面的指令稍作变换

     push {lr}                   ①
     bl G_0                      ②
     .byte   0                   ③
     .word   0x0001              ④
     .byte   0                   ⑤
 G_0:
     ldr r1, [lr, #0]            ⑥
     pop {lr}                    ⑦

语句①和语句⑦用于保存和恢复lr,因语句②会改变bl,并使lr寄存器为语句④的地址。语句③和⑤在此处略显奇怪,但仔细阅读ARM的指令集手册,可以看出这里是为了迎合bl指令的语义。在编译器生成的PIC代码中,经常出现的pc相对寻址指令还有

    add r1, pc

这种类型的指令明显也与pc的值相关,与ldr指令不同的是,add指令并没有读取常数,所以我们无法静态确定r1的值。如若改变指令运行时的位置,则由于pc的改变而导致add指令得出错误的r1。一个解决的思路是修复r1的值。虽然我们静态无法得到r1该为何值,但是我们静态计算出移位前后pc的差值δ。修复后的指令变为

     push    {lr}                ①
     bl  G_1                     ②
     .byte   0                   ③
     .word   0xffff7777          ④
     .byte   0                   ⑤
 G_1:
     ldr lr, [lr, #0]            ⑥
     add r1, lr                  ⑦
     add r1, pc                  ⑧
     pop {lr}                    ⑨

其中语句④即为移位前后pc的δ。⑦用于修复r1的值。