ARM逆向-表达式求值
Posted 嘻嘻兮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARM逆向-表达式求值相关的知识,希望对你有一定的参考价值。
表达式求值也就是把各类加减乘除的运算都看一遍,这里主要记录的是Release版中的一些优化,各种优化手段我感觉有x86的逆向基础还是会比较好理解的,因为很多都很类似。
首先看一下加减的运算,这里主要可能会出现常量折叠和常量传播等优化手段
int main(int argc,char* argv[])
int n1 = argc;
int n2 = 0;
scanf("%d", &n2);
n1 = n1 - 100;
n1 = n1 + 5 - n2 ;
printf("n1 = %d \\r\\n", n1);
return 0;
汇编代码如下(armeabi-v7a)
.text:0000060C MOV R4, R0 ;n1 = argc
.text:00000610 MOV R1, SP ;scanf的参数二
.text:0000061A MOVS R0, #0
.text:0000061C STR R0, [SP] ;n2=0 初始化
.text:0000061E LDR R0, =(unk_768 - 0x624)
.text:00000620 ADD R0, PC ; unk_768 ; format
.text:00000622 BLX scanf
.text:00000626 LDR R0, [SP] ;r0 = n2
.text:00000628 SUBS R0, R4, R0 ;r0 = argc - n2
.text:0000062A SUB.W R1, R0, #0x5F //r1 = r0 - 0x5F = argc - n2 - 95
.text:0000062E LDR R0, =(aN1D - 0x634)
.text:00000630 ADD R0, PC ; "n1 = %d \\r\\n"
.text:00000632 BLX printf
通过上面可以看到,对于最后的n1的结果是可以优化的
n1 = n1 - 100
n1 = n1 + 5 - n2
= n1 - 100 + 5 - n2
= n1 - 95 - n2
= n1 - n2 - 95
而n1的值就是argc,所以上面的计算结果就使用argc减去n2,最后再减去95获取结果。
下面再来看乘法
int main(int argc,char* argv[])
int n1 = argc;
int n2 = 0;
scanf("%d", &n2);
printf("n1 * 15 = %d\\n", n1 * 15); //变量乘常量 ( 常量值为非 2 的幂 )
printf("n1 * 16 = %d\\n", n1 * 16); //变量乘常量 ( 常量值为 2 的幂 )
printf("2 * 2 = %d\\n", 2 * 2); //两常量相乘
printf("n2 * 4 + 5 = %d\\n", n2 * 4 + 5); //混合运算
printf("n1 * n2 = %d\\n", n1 * n2); //两变量相乘
return 0;
汇编代码如下(armeabi-v7a)
.text:00000620 LDR R0, =(printf_ptr - 0x62A)
.text:00000622 RSB.W R1, R4, R4,LSL#4 //r1 = r4 << 4 - r4 = r4 * 16 - r4 = r4 * 15
.text:00000626 ADD R0, PC ; printf_ptr
.text:00000628 LDR R6, [R0] ; printf
.text:0000062A LDR R0, =(aN115D - 0x630)
.text:0000062C ADD R0, PC ; "n1 * 15 = %d\\n"
.text:0000062E BLX R6 ; printf
.text:00000630 LDR R0, =(aN116D - 0x638)
.text:00000632 LSLS R1, R4, #4 //左移4位,相当于直接乘以16
.text:00000634 ADD R0, PC ; "n1 * 16 = %d\\n"
.text:00000636 BLX R6 ; printf
.text:00000638 LDR R0, =(a22D - 0x640)
.text:0000063A MOVS R1, #4 //这里常量折叠直接得出结果4
.text:0000063C ADD R0, PC ; "2 * 2 = %d\\n"
.text:0000063E BLX R6 ; printf
.text:00000640 LDR R1, [SP] ;获取n2
.text:00000642 MOVS R0, #5
.text:00000644 ADD.W R1, R0, R1,LSL#2 //r1 = 5 + n2 << 2 = 5 + n2*4
.text:00000648 LDR R0, =(aN245D - 0x64E)
.text:0000064A ADD R0, PC ; "n2 * 4 + 5 = %d\\n"
.text:0000064C BLX R6 ; printf
.text:0000064E LDR R0, [SP]
.text:00000650 MUL.W R1, R0, R4 //n1*n2
.text:00000654 LDR R0, =(aN1N2D - 0x65A)
.text:00000656 ADD R0, PC ; "n1 * n2 = %d\\n"
.text:00000658 BLX R6 ; printf
这里主要说的就是第一个表达式,使用了一个反向减法,也就是结果等于操作数三减去操作数二的值。
下面就是除法运算了,对于除数是变量的情况,这里就直接调用除法函数了
.text:0000063A LDR R1, [SP]
.text:0000063C MOV R0, R4
.text:0000063E BL __divsi3 ;除法函数 r0/r1
所以下面主要就探讨一下除数为常量的情况,这里一共有九种情况,首先这九种情况是分被除数是有无符号的,无符号的情况三种,有符号的情况六种。对于除法优化而言,下面就直接给出一些公式了,也就是说这个公式是对应着汇编的表现形式,至于需要深入了解原因的可以参考x86的逆向部分,对于除法有细讲了两篇,其优化原理是一致的。
先来看无符号情况下的三种
int main(int argc,char* argv[])
printf("argc / 16 = %u", (unsigned int)argc / 16); //变量除以常量,常量为无符号2的幂
printf("argc / 3 = %u", (unsigned int)argc / 3); //变量除以常量,常量为无符号非2的幂(上)
printf("argc / 7 = %u", (unsigned int)argc / 7); //变量除以常量,常量为无符号非2的幂(下)
return 0;
首先在我armeabi-v7a这个版本中,只有除以2的幂时才出现了优化,也许除以非2的幂出现了Magic Number就不太划算了直接调用了上面的除法函数,所以对于后面两种就使用arm64-v8a来观察优化
首先来看常量为无符号2的幂
.text:00000600 MOV R4, R0
.text:00000602 LSRS R1, R0, #4 //无符号右移n位(2^n)
.text:00000604 LDR R0, =(printf_ptr - 0x60A)
.text:00000606 ADD R0, PC ; printf_ptr
.text:00000608 LDR R5, [R0] ; printf
.text:0000060A LDR R0, =(aArgc16U - 0x610)
.text:0000060C ADD R0, PC ; "argc / 16 = %u"
.text:0000060E BLX R5 ; printf
这一种情况比较简单,公式如下,直接右移n位即可
x >> n
再来看常量为无符号非2的幂,这里是上
.text:00000000000006C8 MOV W8, #0xAAAB
.text:00000000000006CC MOVK W8, #0xAAAA,LSL#16 //这里和上面拼接位 0xAAAAAAAB
.text:00000000000006D0 UMADDL X8, W19, W8, XZR //w19就是argc
.text:00000000000006D4 ADRP X0, #aArgc3U@PAGE ; "argc / 3 = %u"
.text:00000000000006D8 LSR X1, X8, #33 //最后结果右移33位
.text:00000000000006DC ADD X0, X0, #aArgc3U@PAGEOFF ; "argc / 3 = %u"
.text:00000000000006E0 BL .printf
这一种情况是最基础的情况,也就是将除法运算转为乘法运算,公式如下:(原因可参考这)
x * M >> 32 >> n
这里的x表示被除数,也就是argc,M表示Magic Number,也就上面的0xAAAAAAAB,最后的右移n位需要根据具体情况判断
那么这里的话很明显,因为最后右移了33位,所以这里的n值为1。还原公式如下
2^(32+n) / M
对于上例的还原
2 ^ 33 / 0xAAAAAAAB = 2.9999999996507540345598531381041 -> 3
注意,这里的还原说的是如何还原出常量值,因为常量值确定,被除数在代码中是有体现的,这样子就能还原出除法了。还有这里最终的结果都需要向上取整,而且一般结果小数点都是.9999...,如果不是这个结果,那么需要注意哦,是不是有可能求错了。
再来看上面的那种情况二,这种情况是Magic Number会溢出的情况,而Magic Number一旦溢出则这个乘法可能就是一个大数乘法了,所以需要再次优化
.text:00000000000006E4 MOV W8, #0x4925
.text:00000000000006E8 MOVK W8, #0x2492,LSL#16 //0x24924925
.text:00000000000006EC UMADDL X8, W19, W8, XZR //argc * M
.text:00000000000006F0 LSR X8, X8, #32 //x8 = (argc * M) >> 32
.text:00000000000006F4 SUB W9, W19, W8 //使用argc再去减去上面值
.text:00000000000006F8 ADD W8, W8, W9,LSR#1 //w8 + w9 >> 1
.text:00000000000006FC ADRP X0, #aArgc7U@PAGE ; "argc / 7 = %u"
.text:0000000000000700 LSR W1, W8, #2 //最终结果右移2位
.text:0000000000000704 ADD X0, X0, #aArgc7U@PAGEOFF ; "argc / 7 = %u"
.text:0000000000000708 BL .printf
可以看出来,这个公式就复杂很多了,公式如下
((x - (x * M) >> 32) >> n1) + ((x * M) >> 32) >> n2
注意上面公式中的加减运算的优先级是高于移位运算的,别看花了,还有个比较好记的口诀就是乘减移加移。对于上面的例子而言,n1的值就是1了,而n2的值就是2了
还原公式如下,因为上面有说了M值有进位的情况,所以这里的还原需要将M值进一(编译器只会控制进一,也就是加上2^32)
2^(32 + n1 + n2) / (2^32 + M)
对于上例的还原
2 ^ (32 + 1 + 2) / (2^32 + 0x24924925)
= 2^35 / 0x124924925
= 6.999999999388819560461955299793
-> 7
OK,下面再来看一下有符号的情况了,这里共有六种
int main(int argc,char* argv[])
printf("argc / 8 = %d", argc / 8); //变量除以常量,常量为 2 的幂
printf("argc / 9 = %d", argc / 9); //变量除以常量,常量为非2的幂上
printf("argc / 7 = %d", argc / 7); //变量除以常量,常量为非2的幂下
printf("argc / -8 = %d", argc / -8); //变量除以常量,常量为负2的幂
printf("argc / -5 = %d", argc / -5); //变量除以常量,常量为负非2的幂上
printf("argc / -7 = %d", argc / -7); //变量除以常量,常量为负非2的幂下
return 0;
先来看第一种,常量为 2 的幂,对于有符号数而言,他是需要考虑正负数的,其中正数其实就是上面无符号的情况,而对于负数的情况就是需要做一些调整,先来看公式,再来看对于的汇编代码
x >= 0
x >> n
x < 0
(x + 2^n-1) >> n //调整值需要加上2^n-1
汇编代码如下
.text:00000604 MOV R4, R0
.text:00000606 ASRS R0, R0, #31 //有符号右移32位 0 || -1
.text:00000608 ADD.W R6, R4, R0,LSR#29 //argc + r0 >> 29 (无分支调整 +0 || +7)
.text:0000060C LDR R0, =(printf_ptr - 0x612)
.text:0000060E ADD R0, PC ; printf_ptr
.text:00000610 ASRS R1, R6, #3 //最后有符号右移3位
.text:00000612 LDR R5, [R0] ; printf
.text:00000614 LDR R0, =(aArgc8D - 0x61A)
.text:00000616 ADD R0, PC ; "argc / 8 = %d"
.text:00000618 BLX R5 ; printf
对于上面的代码,开始做了一个无分支的优化,首先如果是负数,那么这里需要加上2^n-1(也就是7) ,所以如果是负数,那么开始R0 = 0xFFFFFFFF,然后R0又无符号右移29位(高位填0),此时这个值就是7了。
因为这里相对于无符号而言,就是多了一个负数的调整,所以也没什么公式可言,根据最后右移多少位来计算常量值即可(2^n)
再来看常量为非2的幂上的情况,这里又需要看arm64-v8a了,v7a中调用除法函数
这种情况相对于无符号而言也是一样的,对于负数需要做一个调整(最终的结果+1),公式如下
x >= 0
x * M >> 32 >> n
x < 0
(x * M >> 32 >> n) + 1
汇编代码如下
.text:00000000000006D8 MOV W8, #0x8E39
.text:00000000000006DC MOVK W8, #0x38E3,LSL#16 //0x38E38E39
.text:00000000000006E0 SMADDL X8, W19, W8, XZR //argc * M
.text:00000000000006E4 LSR X9, X8, #32 //右移32位
.text:00000000000006E8 LSR X8, X8, #63 //这里是无分支,如果结果为正,那么x8=0,否则x8=1
.text:00000000000006EC ADRP X0, #aArgc9D@PAGE ; "argc / 9 = %d"
.text:00000000000006F0 ADD W1, W8, W9,ASR#1 //最终右移后再加上调整值
.text:00000000000006F4 ADD X0, X0, #aArgc9D@PAGEOFF ; "argc / 9 = %d"
.text:00000000000006F8 BL .printf
上面的汇编代码就是前面公式的具体体现,里面也用到了一个无分支,右移63位,如果原本值是负的,那么结果会为1。
对于还原的公式,和前面是一样的,因为这里还是只多了一个负数的情况判断而言,还原公式如下
2^(32+n) / M
对于上例的还原
2^(32+1) / 0x38E38E39
= 2^33 / 0x38E38E39
= 8.9999999989522621036795594143123
-> 9
再来看常量为非2的幂下的情况
对于这种情况,其实是因为出现了Magic Number为负的情况,但是由于Magic Number本身的定义就是一个无符号数值,那么也就是说我们原本需要乘以一个正数(大于0x7FFFFFFF),此时变为了乘以一个负数,因为这样子其值肯定变小了,所以需要调整肯定是加上一个值,公式如下
x >= 0
(x * M >> 32) + x >> n
x < 0
((x * M >> 32) + x >> n) + 1
对于的汇编代码如下
.text:00000000000006FC MOV W8, #0x2493
.text:0000000000000700 MOVK W8, #0x9249,LSL#16 //0x92492493 负数值
.text:0000000000000704 SMADDL X8, W19, W8, XZR //先乘以M
.text:0000000000000708 LSR X8, X8, #32
.text:000000000000070C ADD W8, W8, W19 //这里就是调整的值 +x
.text:0000000000000710 ASR W9, W8, #2 //调整后再右移2位
.text:0000000000000714 ADRP X0, #aArgc7D@PAGE ; "argc / 7 = %d"
.text:0000000000000718 ADD W1, W9, W8,LSR#31 //W8 >> 31这里是用于判断是不是负数,负数需要最终结果+1
.text:000000000000071C ADD X0, X0, #aArgc7D@PAGEOFF ; "argc / 7 = %d"
.text:0000000000000720 BL .printf
所以这种情况其实本质是解决的是乘以无符号的数如何转化为有符号的数,其本质原理不变,所以其还原的公式也没有变化
2^(32+n) / M
对于上例的还原
2^(32+2) / 0x92492493
= 2^34 / 0x92492493
= 6.9999999979627318686215638099758
-> 7
注意对于公式中的Magic Number值,我们需要还是需要将其看成一个无符号的数,而不是一个负数。
好了,下面可以看负数的三种情况了,先看2的幂的情况,对于这种情况,其实也就是最终结果求个负即可
a/-b = -(a/b)
x / -2^n = -(x / 2^n)
所以对于负数的这三种情况,其实本质都是最终结果求个负即可,汇编代码如下
.text:00000604 MOV R4, R0
.text:00000606 ASRS R0, R0, #31
.text:00000608 ADD.W R6, R4, R0,LSR#29 //这里就是无分支调整 +0 || +7
.text:0000063A MOVS R0, #0
.text:0000063C SUB.W R1, R0, R6,ASR#3 //0 - r6>>3
.text:00000640 LDR R0, =(aArgc8D_0 - 0x646)
.text:00000642 ADD R0, PC ; "argc / -8 = %d"
.text:00000644 BLX R5 ; printf
可以发现最终使用0去减结果,也就是相当于最后取了一个负数,这里的还原也就简单了,直接看右移多少位,如果最后有取反的操作,那么说明此时的常量值是一个负数。
再来看常量为负非2的幂上的情况,这里也就是最终结果求个负,因为在非2的幂中涉及到了Magic Number(常量值),所以可以直接将这个求反操作放在常量值中即可,公式如下
x >= 0
x * -M >> 32 >> n
x < 0
(x * -M >> 32 >> n) + 1
此时的M值就是一个已经求反过后的值。汇编代码如下
.text:0000000000000734 MOV W8, #0x99999999
.text:0000000000000738 SMADDL X8, W19, W8, XZR
.text:000000000000073C LSR X9, X8, #32 //右移32位
.text:0000000000000740 LSR X8, X8, #63 //无分支求 0 || 1,最终结果的调整
.text:0000000000000744 ADRP X0, #aArgc5D@PAGE ; "argc / -5 = %d"
.text:0000000000000748 ADD W1, W8, W9,ASR#1 //右移一位后加上调整值
.text:000000000000074C ADD X0, X0, #aArgc5D@PAGEOFF ; "argc / -5 = %d"
.text:0000000000000750 BL .printf
可以发现,这里的代码和上面的非2的幂情况是一摸一样的,不过注意看,此时的M值是一个负数,因为取反了(原先是正数)
还原的公式如下,因为这里的M是一个取反值,所以在还原中,我们还是需要使用原本的值来计算(取反前的值,也就是2^32 - M)
2^(32+n) / (2^32-M)
对于上例的还原
2^(32+1) / (0 - 0x99999999)
=2^33 / 0x66666667
=4.9999999982537701732058415050132
-> -5
好了,下面就是最后一种情况,对于上面正数的情况中,会出现一种Magic Number为负数的情况,那么此时如果在对其取反,那么这个M值就是一个正数了(取反后M值是负数为正常情况,就是需要乘以一个负数),此时相乘肯定结果会变大,所以此时需要调整减去一个值。公式如下
x >= 0
(x * -M >> 32) - x >> n
x < 0
((x * -M >> 32) - x >> n) + 1
对应的汇编代码如下
.text:0000000000000754 MOV W8, #0xDB6D
.text:0000000000000758 MOVK W8, #0x6DB6,LSL#16 //0x6DB6DB6D
.text:000000000000075C SMADDL X8, W19, W8, XZR
.text:0000000000000760 LSR X8, X8, #32
.text:0000000000000764 SUB W8, W8, W19 //这里的调整就是需要减去argc
.text:0000000000000768 ASR W9, W8, #2 //再右移2位
.text:000000000000076C ADRP X0, #aArgc7D_0@PAGE ; "argc / -7 = %d"
.text:0000000000000770 ADD W1, W9, W8,LSR#31 //最后需要加上一个调整值
.text:0000000000000774 ADD X0, X0, #aArgc7D_0@PAGEOFF ; "argc / -7 = %d"
可以发现,上面的Magic Number的确是一个正数值,对于还原公式而言,和上面是一样的,因为本质,没有变化
2^(32+n) / (2^32-M)
对于上例的还原
2^(32+2) / (2^32 - 0x6DB6DB6D)
=2^34 / 0x92492493
=6.9999999979627318686215638099758
-> -7
除法整理完就看下面的取模运算,对于取模运算而言,首先其结果的符号位是跟着被除数走的,所以就没有正负数的情况考虑了,主要就以下两种
int main(int argc,char* argv[])
printf("argc % 8 = %d", argc % 8); //变量模常量,常量为2的幂
printf("argc % 9 = %d", argc % 9); //变量模常量,常量为非2的幂
return 0;
先来看第一种,常量为2的幂的情况,公式如下
x > 0
x - (x & ~(2^n-1))
x < 0
x - (x + (2^n-1) & ~(2^n-1))
这个公式的本质其实就是取低几位,如模8,那么就是取低三位即可,一般情况下我们可以直接and 7就是结果了(正数)
假设x的二进制值为YYYYYYYYYXXX
x & ~7
YYYYYYYYYXXX
111111111000
-------------
YYYYYYYYY000
x - (x & ~7)
YYYYYYYYYXXX
YYYYYYYYY000
-------------
000000000XXX
因为在x86的逆向中没有遇到过这种公式,所以这里就稍微详细的记录一下,本质上就是求最后的低n位,当然上面只是举一个简单例子(Y的位数略有不足)。汇编代码如下
.text:0000060C MOV R4, R0
.text:0000060E ASRS R0, R0, #31 // 无分支 -1 || 0
.text:00000610 ADD.W R0, R4, R0,LSR#29 // if argc > 0 r0 = argc + 0
//else r0 = argc + 7
.text:00000614 BIC.W R0, R0, #7 //下面开始取低n位,先 &~7
.text:00000618 SUBS R1, R4, R0 //然后减去即可得到结果
.text:0000061A LDR R0, =(aArgc8D - 0x620)
.text:0000061C ADD R0, PC ; "argc % 8 = %d"
.text:0000061E BLX printf
首先开始先做了一个无分支的优化,因为对于负数需要先加上一个2^n-1,毕竟符号需要跟着被除数走,那么这里加一个2^n-1,最终减去肯定是一个负数值。
对于BIC是位清零指令,也就是最后的n位清零,相当于 and ~n的操作,后面的两行代码就是求最后的n位了。
下面再来看非2的幂情况,公式如下,这个公式还是很好理解的,不理解可以参考取模运算
x - (x / n * n)
那么可以看到,上面的公式出现了除法,所以此时就和上面的除法优化又有关系了,汇编代码如下
.text:00000000000006D8 MOV W8, #0x8E39
.text:00000000000006DC MOVK W8, #0x38E3,LSL#16 //0x8E3938E3
.text:00000000000006E0 SMADDL X8, W19, W8, XZR
.text:00000000000006E4 LSR X9, X8, #0x3F
.text:00000000000006E8 ASR X8, X8, #0x21
.text:00000000000006EC ADD W8, W8, W9 //上面都是除法优化,此时w8就是商
.text:00000000000006F0 ADD W8, W8, W8,LSL#3 //w8 = w8 + w8*8 = w8*9 也就是*n
.text:00000000000006F4 ADRP X0, #aArgc9D@PAGE ; "argc % 9 = %d"
.text:00000000000006F8 SUB W1, W19, W8 //最后argc减去w8获取余数
.text:00000000000006FC ADD X0, X0, #aArgc9D@PAGEOFF ; "argc % 9 = %d"
.text:0000000000000700 BL .printf
最后简单记录下三目运算表达式而言,因为ARM汇编中提供了条件执行,那么此时很容易使用汇编来表达
printf("%d\\n", argc == 5 ? 5 : 6);
汇编代码如下
.text:000005F8 MOV R1, R0 //r1 = argc,也就是等于5
.text:000005FA CMP R0, #5
.text:000005FC IT NE //这里表示下一条指令NE条件执行
.text:000005FE MOVNE R1, #6 //不相等则执行 - r1=6
.text:00000600 LDR R0, =(unk_7E7 - 0x606)
.text:00000602 ADD R0, PC ; unk_7E7 ; format
.text:00000604 BLX printf
以上是关于ARM逆向-表达式求值的主要内容,如果未能解决你的问题,请参考以下文章