第八章 Android 原生程序开发与逆向分析(五)(原生 C 程序逆向分析)
文章目录
- 原生 C 程序逆向分析
- 编译原生 C 程序
- for 循环分支结构
- for1()
- for2()
- while 循环分支结构
- dowhile()
- whiledo()
- if……else 分支结构
- if1()
- if2()
- switch 循环分支结构
- 优化后的 C 程序
原生 C 程序逆向分析
- 一个完整的原生程序中,除了顺序执行的代码流,还有 for 循环、while 循环、if……else 语句、switch 语句等分支语句结构。了解这些语句结构所生成的汇编指令特点,有助于理解反汇编代码的行为
编译原生 C 程序
- 实例:app6(包含 C 语言中常见分支语句结构)
#include <stdio.h>int nums[5] = {1, 2, 3, 4, 5};
int for1(int n)
{int i = 0;int s = 0;for (i = 0; i < n; i++){s += i * 2;}return s;
}
int for2(int n)
{int i = 0;int s = 0;for (i = 0; i < n; i++){s += i * i + nums[n - 1];}return s;
}int dowhile(int n)
{int i = 1;int s = 0;do{s += i;} while (i++ < n);return s;
}
int whiledo(int n)
{int i = 1;int s = 0;while (i <= n){s += i++;}return s;
}void if1(int n)
{if (n < 10){printf("the number less than 10\n");}else{printf("the number greater than or equal to 10\n");}
}
void if2(int n)
{if (n < 16){printf("he is a boy\n");}else if (n < 30){printf("he is a young man\n");}else if (n < 45){printf("he is a strong man\n");}else{printf("he is an old man\n");}
}int switch1(int a, int b, int i)
{switch (i){case 1:return a + b;break;case 2:return a - b;break;case 3:return a * b;break;case 4:return a / b;break;default:return a + b;break;}
}int main(int argc, char const *argv[])
{printf("for1: %d\n", for1(5));printf("for2: %d\n", for2(5));printf("dowhile: %d\n", dowhile(100));printf("while: %d\n", whiledo(100));if1(5);if2(35);printf("switch1: %d\n", switch1(3, 5, 3));return 0;
}
- 编译生成未经优化处理的 app6,查看运行效果:
for 循环分支结构
for1()
- 用 objdump 查看 for1() 的反汇编代码:
- 0x628 处的
sub sp, sp, #0x10
:开辟栈帧(栈空间)指令,用于存放本函数中用的临时变量信息,大小为 16 字节。在栈变量用完,函数返回前,会有一条恢复栈帧(栈寄存器)指令与其对应,即 0x680 处的add sp, sp, #0x10
,此指令执行后,sp 寄存器的值为原来保存的值,这就保证了函数执行后栈不会被破坏。阅读上述汇编代码,发现除了 sp 寄存器,其他的寄存器都以“w”开头,而非“x”,说明 for1() 中用的变量是 32 位的,也印证了 int 类型在 AArch64 上是 32 位的。在开辟的 16 字节栈空间中,可存放 4 个 32 位的整型数 - 0x62c 处的
str w0, [sp, #12]
:将 w0 中的值放到 sp 寄存器加 12(此处为十进制)的地方。因为 w0 ~ w7 用来存放函数的前八个参数,所以这里的 w0 即 for1() 的第一个参数。将其保存到临时的栈地址中,后面可能用到 - 0x630 ~ 0x638 处的三条
str wzr, [sp, #x]
:将 sp 寄存器加一个偏移的栈空间的数据清零,目的为初始化临时变量。wzr 为零寄存器,读它的值会返回 0,写它的值的操作会被忽略。另外可看出,索引栈变量用的是 sp 作为基址寄存器,和 x86 的 bp(ebp)不同。还有,这三条指令的第一条和第三条相同,sp 的值没变化,怎么看都是重复了(可能是编译器生成的多余指令) - 0x63c 处的
ldr w8, [sp, #8]
:将上面被清零的临时变量的值赋给 w8 寄存器(表示要用它了) - 0x640 处的
ldr w9, [sp, #12]
:将函数的第一个参数的值赋给 w9 - 0x644 处的
cmp w8, w9
:比较 w8 和 w9 的值 - 0x648 处的
cset w8, lt
:此指令是CSINC
指令的别名。若条件为 true,则将目标寄存器设置为 1,否则将其设置为 0(见《ARM Architecture Reference Manual》。条件为lt
,即有符号小于。这条指令的意思为若上一条指令比较的结果为小于,则将 w8 设置为 1,否则设置为 0 - 0x64c 处的
tbnz w8, #0, 654 <for1 + 0x2c>
:若 w8 的值不等于 0(即w8 < w9
),则跳转到标签处(循环体),for1 的地址为 0x628,加上 0x2c,为 0x654 处 - 0x650 处的
b 67c <for1 + 0x54>
:若w8 >= w9
,则无条件跳出循环,即 0x628 加上 0x54 的 0x67c 处 - 所以循环体为 0x654 ~ 0x67c(不包括 0x67c)
- 循环体分析:
- 0x654 处的
orr w8, wzr, #0x2
:将 0 和 2 逻辑或,结果为 2,存入 w8 寄存器 - 0x658 处的
ldr w9, [sp, #8]
:sp + 8
现在为 0(初始化为 0),故 w9 为 0。此处获取的是 for 循环中 i 的值,即局部变量 i 的值 - 0x65c 处的
mul w8, w9, w8
:相当于w8 = 2 * i
- 0x660 处的
ldr w9, [sp, #4]
:获取局部变量 s 的值 - 0x664 处的
add w8, w9, w8
:相当于w8 = s + 2 * i
- 0x668 处的
str w8, [sp, #4]
:将计算结果存入sp + 4
处 - 0x66c 处的
ldr w8, [sp, #8]
:获取 i 的值 - 0x670 处的
add w8, w8, #0x1
:即将 i 加 1,相当于i++
- 0x674 处的
str w8, [sp, #8]
:将自增后的 i 存入sp + 8
处 - 0x678 处的
b 63c <for1 + 0x14>
:无条件跳转到循环条件处,即 0x63c,继续下一轮循环
- 0x654 处的
- 0x67c 处的
ldr w0, [sp, #4]
:获取 s 的值存入 w0 寄存器,w0 中的值为返回结果 - 0x680 处的指令前面已分析过,为关闭栈帧
- 0x684 处的
ret
:返回上一层调用处 - 用汇编指令查看分支跳转不够直观,可用支持流程视图的反汇编工具了解 for1() 的分支跳转情况,如 IDA:
- 流程视图下的 for1(),每个跳转指令之前的语句都是一个基本的语句块,语句块间用箭头连接,红色箭头表示跳转条件不满足时程序的走向,绿色表示跳转条件满足时的走向
for2()
- for2() 同样是 for 循环,但在循环中访问了外部的变量
- 用 objdump 查看其反汇编代码:
root@myUbuntu:~# $OBJDUMP -d app6 | grep "<for2>:" -A 37
0000000000000688 <for2>:688: d10083ff sub sp, sp, #0x2068c: b0000088 adrp x8, 11000 <nums>690: 91000108 add x8, x8, #0x0694: b9001fe0 str w0, [sp,#28]698: b9001bff str wzr, [sp,#24]69c: b90017ff str wzr, [sp,#20]6a0: b9001bff str wzr, [sp,#24]6a4: f90007e8 str x8, [sp,#8]6a8: b9401be8 ldr w8, [sp,#24]6ac: b9401fe9 ldr w9, [sp,#28]6b0: 6b09011f cmp w8, w96b4: 1a9fa7e8 cset w8, lt6b8: 37000048 tbnz w8, #0, 6c0 <for2+0x38>6bc: 14000015 b 710 <for2+0x88>6c0: b27e03e8 orr x8, xzr, #0x46c4: b9401be9 ldr w9, [sp,#24]6c8: b9401bea ldr w10, [sp,#24]6cc: 1b0a7d29 mul w9, w9, w106d0: b9401fea ldr w10, [sp,#28]6d4: 7100054a subs w10, w10, #0x16d8: 2a0a03eb mov w11, w106dc: 93407d6b sxtw x11, w116e0: 9b0b7d08 mul x8, x8, x116e4: f94007eb ldr x11, [sp,#8]6e8: 8b080168 add x8, x11, x86ec: b940010a ldr w10, [x8]6f0: 0b0a0129 add w9, w9, w106f4: b94017ea ldr w10, [sp,#20]6f8: 0b090149 add w9, w10, w96fc: b90017e9 str w9, [sp,#20]700: b9401be8 ldr w8, [sp,#24]704: 11000508 add w8, w8, #0x1708: b9001be8 str w8, [sp,#24]70c: 17ffffe7 b 6a8 <for2+0x20>710: b94017e0 ldr w0, [sp,#20]714: 910083ff add sp, sp, #0x20718: d65f03c0 ret
- 快速分析 for 循环法:查看函数尾部的无条件分支指令。此处的是 0x70c 处的
b 6a8 <for2 + 0x20>
,其跳转的目的地址为 0x6a8,即 for 循环的开头 - 除了 0x68c、0x690 处和循环体中的部分指令不一样,其他指令与 for1() 的差不多,不再分析
- 0x68c 处的
adrp x8, 11000 <nums>
:adrp
指令将当前 pc 寄存器的值加上偏移量后赋给 x8 寄存器,下一条的add
指令是直接相加,这两条指令执行后,x8 寄存器的值为 0x11000,用 readelf 查看 Section 信息,发现这个地址指向.data
节区
- 上述两条指令的神奇之处:无论 app6 加载到什么地方,x8 寄存器永远指向
.data
节区中的一个变量值。这即-fPIE -pie
编译参数指定后 ELF 内存中的数据与代码地址无关的原因 - 重新编译 app6(注意:r20 版的 NDK 中的 Clang 貌似默认开启了 pie,要添加编译参数
-no-pie
才能生成位置相关的代码),不指定上述参数,查看 for2() 的反汇编代码:
- 可看出,同样用
adrp
和add
取地址,app_2 中第一个 PT_LOAD 类型的 Segment 指定 ELF 的加载基址为 0x400000,而 app6 没有指定加载基址,链接器可将其加载到任意的内存地址;app_2 的adrp
和add
指令计算出的地址为 0x411000,用 readelf 可知其所处的节区仍为.data
- 0x6e8 处的
add x8, x11, x8
和 0x6ec 处的ldr w10, [x8]
:以 x8 加 x11 寄存器的值为偏移量,取其中的数据。这是种典型的对 x8 寄存器指定地址的查表操作
while 循环分支结构
dowhile()
- while 循环分为 do……while 和 while……do,执行效果和 for 循环类似
- 用 objdump 查看 app6 的 dowhile() 的反汇编代码:
- 0x71c 的
sub sp, sp, #0x10
:开辟栈帧 - 0x720 的
orr w8, wzr, #0x1
:逻辑或,相当于给 w8 初始化(赋值),即w8 = 1
。这是初始化i = 1
- 0x724 的
str w0, [sp, #12]
:将第一个参数放在sp + 12
处 - 0x728 的
str w8, [sp, #8]
:将 w8 的值放在sp + 8
处 - 0x72c 的
str wzr, [sp, #4]
:初始化sp + 4
处为 0。这是初始化s = 0
- 0x730 的
ldr w8, [sp, #8]
:取sp + 8
处的值赋给 w8。这是取 i 的值 - 0x734 的
ldr w9, [sp, #4]
:取 sp + 4 处的值赋给 w9。这是取 s 的值 - 0x738 的
add w8, w9, w8
:相当于s = s + i
- 0x73c 的
str w8, [sp, #4]
:将上面计算的结果存入sp + 4
处。这是存 s 的值 - 0x740 的
ldr w8, [sp, #8]
:再次取 i 的值 - 0x744 的
add w9, w8, #0x1
:相当于i++
- 0x748 的
str w9, [sp, #8]
:存i++
后的值 - 0x74c 的
ldr w9, [sp, #12]
:取第一个参数的值 - 0x750 的
cmp w8, w9
:比较i++
的值和第一个参数的值 - 0x754、0x758 的两条:若
i++
的值小于第一个参数的值,就跳到 0x730,即循环开始处,继续下一轮循环 - 0x75c 的
ldr w0, [sp, #4]
:取 s 的值,即返回结果 - 0x760 的
add sp, sp, #0x10
:关闭栈帧 - 0x764 的
ret
:返回
- 可看出,dowhile() 的特点:先执行一段代码,再判断 while 循环是否成立
- 用 IDA 查看 dowhile() 的流程:
whiledo()
- 查看反汇编:
- 跟 dowhile() 差不多,只在循环体的最后多了一行无条件跳转指令(0x7ac 处),跳到循环判断处(0x77c 处)
- whiledo() 的特点:先判断,后执行
- 流程图:
- 可看出,与 for1() 几乎一样,表示在实际编码时 while……do 语句能和 for 语句替换使用
if……else 分支结构
if1()
- 查看反汇编:
- 0x7bc:开辟栈帧
- 0x7c0 的
stp x29, x30, [sp, #32]
:将 x29 的值保存到sp + 32
处,作用是保存 x29 寄存器原始的值,对应 0x810 的ldp x29, x30, [sp, #32]
(作用是函数返回时还原 x29 的原始值) - 0x7c4 的
add x29, sp, #0x20
:将 sp 寄存器加 32 后的值赋给 x29,目的是将 x29 作为基址寄存器用 - 0x7c8 的
mov w8, #0xa
:将 10 赋给 w8 - 0x7cc 的
adrp x9, 0 <note_android_ident-0x218>
:将当前 pc 寄存器的值加上偏移量后赋给 x9 - 0x7d0 的
add x9, x9, #0xb1d
:执行后,x9 的值为 0xb1d,位于.rodata
。这两条指令用于获取一个字符串的地址,然后由 0x800 的bl 580 <printf@plt>
调用 printf() 输出这个字符串 - 0x7d4 的
adrp x10, 0 <note_android_ident-0x218>
:将当前 pc 寄存器的值加上偏移量后赋给 x10 - 0x7d8 的
add x10, x10, #0xb04
:执行后,x10 的值为 0xb04,位于.rodata
。这两条指令用于获取另一个字符串的地址,然后由 0x80c 的bl 580 <printf@plt>
调用 printf() 输出这个字符串 - 0x7dc 的
stur w0, [x29,#-4]
:保存第一个参数的值 - 0x7e0 的
ldur w0, [x29,#-4]
:获取上面保存的第一个参数的值 - 0x7e4 的
cmp w0, w8
:比较第一个参数的值与 10 的大小 - 0x7e8 的
cset w8, lt
:判断上述比较结果是否为 lt(小于),若条件为 true(即小于),则将 w8 设为 1,否则将其设为 0 - 0x7ec、0x7f0 的
str
指令:将获取的两个要打印的字符串保存到栈中 - 0x7f4 的
tbnz w8, #0, 7fc <if1+0x40>
:若w8 != 0
则跳转到 0x7fc 的判断分支一(即 if 语句块) - 0x7f8:若上一条不成立,则执行这条无条件跳转指令,跳转到 0x808 的判断分支二(即 else 语句块)
- 0x7fc ~ 0x804:获取前面保存的字符串,调用 printf() 将其输出(下面的 0x808、0x80c 同此,不再分析),然后无条件跳出判断分支
- 0x814:关闭栈帧
- 0x818:返回
- 查看流程图:
if2()
- 查看反汇编:
root@myUbuntu:~# $OBJDUMP -d app6 | grep "<if2>:" -A 48
000000000000081c <if2>:81c: d10103ff sub sp, sp, #0x40820: a9037bfd stp x29, x30, [sp,#48]824: 9100c3fd add x29, sp, #0x30828: 321c03e8 orr w8, wzr, #0x1082c: 90000009 adrp x9, 0 <note_android_ident-0x218>830: 912de529 add x9, x9, #0xb79834: 9000000a adrp x10, 0 <note_android_ident-0x218>838: 912d954a add x10, x10, #0xb6583c: 9000000b adrp x11, 0 <note_android_ident-0x218>840: 912d496b add x11, x11, #0xb52844: 9000000c adrp x12, 0 <note_android_ident-0x218>848: 912d158c add x12, x12, #0xb4584c: b81fc3a0 stur w0, [x29,#-4]850: b85fc3a0 ldur w0, [x29,#-4]854: 6b08001f cmp w0, w8858: 1a9fa7e8 cset w8, lt85c: f81f03a9 stur x9, [x29,#-16]860: f9000fea str x10, [sp,#24]864: f9000beb str x11, [sp,#16]868: f90007ec str x12, [sp,#8]86c: 37000048 tbnz w8, #0, 874 <if2+0x58>870: 14000004 b 880 <if2+0x64>874: f94007e0 ldr x0, [sp,#8]878: 97ffff42 bl 580 <printf@plt>87c: 14000015 b 8d0 <if2+0xb4>880: 321f0fe8 orr w8, wzr, #0x1e884: b85fc3a9 ldur w9, [x29,#-4]888: 6b08013f cmp w9, w888c: 1a9fa7e8 cset w8, lt890: 37000048 tbnz w8, #0, 898 <if2+0x7c>894: 14000004 b 8a4 <if2+0x88>898: f9400be0 ldr x0, [sp,#16]89c: 97ffff39 bl 580 <printf@plt>8a0: 1400000c b 8d0 <if2+0xb4>8a4: 528005a8 mov w8, #0x2d // #458a8: b85fc3a9 ldur w9, [x29,#-4]8ac: 6b08013f cmp w9, w88b0: 1a9fa7e8 cset w8, lt8b4: 37000048 tbnz w8, #0, 8bc <if2+0xa0>8b8: 14000004 b 8c8 <if2+0xac>8bc: f9400fe0 ldr x0, [sp,#24]8c0: 97ffff30 bl 580 <printf@plt>8c4: 14000003 b 8d0 <if2+0xb4>8c8: f85f03a0 ldur x0, [x29,#-16]8cc: 97ffff2d bl 580 <printf@plt>8d0: a9437bfd ldp x29, x30, [sp,#48]8d4: 910103ff add sp, sp, #0x408d8: d65f03c0 ret
- 可看出,跟 for1() 差不多,只不过多了几次
cmp
的比较 - 查看流程图:
switch 循环分支结构
- 条件判断语句数量较多时,用 switch 分支会更加方便,生成的代码执行效率也较高
- 查看反汇编:
root@myUbuntu:~# $OBJDUMP -d app6 | grep "<switch1>:" -A 57
00000000000008dc <switch1>:8dc: d10083ff sub sp, sp, #0x208e0: 320003e8 orr w8, wzr, #0x18e4: b9001be0 str w0, [sp,#24]8e8: b90017e1 str w1, [sp,#20]8ec: b90013e2 str w2, [sp,#16]8f0: b94013e0 ldr w0, [sp,#16]8f4: 6b00011f cmp w8, w08f8: 1a9f17e8 cset w8, eq8fc: b9000fe0 str w0, [sp,#12]900: 370002a8 tbnz w8, #0, 954 <switch1+0x78>904: 14000001 b 908 <switch1+0x2c>908: 321f03e8 orr w8, wzr, #0x290c: b9400fe9 ldr w9, [sp,#12]910: 6b09011f cmp w8, w9914: 1a9f17e8 cset w8, eq918: 37000288 tbnz w8, #0, 968 <switch1+0x8c>91c: 14000001 b 920 <switch1+0x44>920: 320007e8 orr w8, wzr, #0x3924: b9400fe9 ldr w9, [sp,#12]928: 6b09011f cmp w8, w992c: 1a9f17e8 cset w8, eq930: 37000268 tbnz w8, #0, 97c <switch1+0xa0>934: 14000001 b 938 <switch1+0x5c>938: 321e03e8 orr w8, wzr, #0x493c: b9400fe9 ldr w9, [sp,#12]940: 6b09011f cmp w8, w9944: 1a9f17e8 cset w8, eq948: 37000248 tbnz w8, #0, 990 <switch1+0xb4>94c: 14000001 b 950 <switch1+0x74>950: 14000015 b 9a4 <switch1+0xc8>954: b9401be8 ldr w8, [sp,#24]958: b94017e9 ldr w9, [sp,#20]95c: 0b090108 add w8, w8, w9960: b9001fe8 str w8, [sp,#28]964: 14000014 b 9b4 <switch1+0xd8>968: b9401be8 ldr w8, [sp,#24]96c: b94017e9 ldr w9, [sp,#20]970: 6b090108 subs w8, w8, w9974: b9001fe8 str w8, [sp,#28]978: 1400000f b 9b4 <switch1+0xd8>97c: b9401be8 ldr w8, [sp,#24]980: b94017e9 ldr w9, [sp,#20]984: 1b097d08 mul w8, w8, w9988: b9001fe8 str w8, [sp,#28]98c: 1400000a b 9b4 <switch1+0xd8>990: b9401be8 ldr w8, [sp,#24]994: b94017e9 ldr w9, [sp,#20]998: 1ac90d08 sdiv w8, w8, w999c: b9001fe8 str w8, [sp,#28]9a0: 14000005 b 9b4 <switch1+0xd8>9a4: b9401be8 ldr w8, [sp,#24]9a8: b94017e9 ldr w9, [sp,#20]9ac: 0b090108 add w8, w8, w99b0: b9001fe8 str w8, [sp,#28]9b4: b9401fe0 ldr w0, [sp,#28]9b8: 910083ff add sp, sp, #0x209bc: d65f03c0 ret
0x8dc、0x9b8:开辟、关闭栈帧
0x8e0 的
orr w8, wzr, #0x1
:执行后,w8 = 1
(即case 1
的条件)0x8e4 ~ 0x9ec:将三个参数保存在栈中
0x8f0 的
ldr w0, [sp, #16]
:获取第三个参数(即 i)的值0x8f4 的
cmp w8, w0
:比较第三个参数是否为 w8(即 1)0x8f8 的
cset w8, eq
:判断上述比较结果是否为 eq(等于),若条件为 true(即等于),则将 w8 设为 1,否则将其设为 00x8fc 的
str w0, [sp, #12]
:保存第三个参数的值到sp + 12
处0x900 的
tbnz w8, #0, 954 <switch1+0x78>
:若w8 != 0
则跳转到 0x954 处(即case 1
的语句块)0x904 的
b 908 <switch1+0x2c>
:若上一条不满足,则跳转到 0x908(即case 2
的判断处)0x908 的
orr w8, wzr, #0x2
:执行后,w8 = 2
(即case 2
的条件)0x90c 的
ldr w9, [sp, #12]
:获取第三个参数(即 i)的值0x910 的
cmp w8, w9
:比较第三个参数是否为 w8(即 2)0x910 ~ 0x91c:同上,若满足条件,则跳转到 0x968 处(即
case 2
的语句块);若不满足,无条件跳到 0x920 处0x920 ~ 0x934:同上,
case 3
的判断0x938 ~ 0x94c:同上,
case 4
的判断0x950:若上述 case 都不满足,则跳转到 0x9a4(即 default 语句块)
0x954 ~ 0x964:
case 1
语句块。获取第一、二个参数的值,将其相加,存入sp + 28
处,然后无条件跳转到 0x9b4(即获取返回结果处)0x968 ~ 0x978:
case 2
语句块。获取第一、二个参数的值,将其相减,存入sp + 28
处,然后无条件跳转到 0x9b4(即获取返回结果处)0x97c ~ 0x98c:
case 3
语句块。获取第一、二个参数的值,将其相乘,存入sp + 28
处,然后无条件跳转到 0x9b4(即获取返回结果处)0x990 ~0x9a0:
case 4
语句块。获取第一、二个参数的值,将其相除,存入sp + 28
处,然后无条件跳转到 0x9b4(即获取返回结果处)0x9a4 ~ 0x9b0:
default
语句块。获取第一、二个参数的值,将其相加,存入sp + 28
处0x9b4:获取函数返回结果
0x9bc:返回
查看流程图:
优化后的 C 程序
- 原生程序的优化分为编译时优化和后期 strip 优化
- 优化后的原生程序的指令和优化前的指令有很大不同
- 后期优化:
- 用
file
查看文件信息,可看出程序已经过 stripped 处理 - 用 objdump 查看反汇编代码,可看出,反汇编代码中只剩
.plt
节区的外部符号和.text
节区的main@@Base-0x410
和main@@Base
(即 main()),之前的 for1()、if1()、switch1() 等函数都不见了
- 对原来的 app6 执行上述命令:
- IDA 查看,发现那些“消失”的函数都已变成以“sub_”开头的函数:
- 可见,这样处理的原生程序,函数名被去除,逆向分析难度增加
- 还可在编译时指定优化参数,如添加“-O3”参数,开启等级 3 的优化,编译器会对代码进行常量优化。对于可计算出结果的代码,编译器会直接插入计算结果而非原来的计算语句,这样程序的执行效率会提高
- 重新编译生成 app6,查看 for1() 的汇编指令:
- 可看出,不仅指令块变短,而且原来的 for 循环被优化成了
b.lt
执行的分支指令。优化后的分支代码:
__int64 __fastcall for1(int a1)
{__int64 result; // x0if ( a1 < 1 )result = 0LL;elseresult = ((a1 - 1) * (a1 - 2) & 0xFFFFFFFE) + 2 * a1 - 2;return result;
}
- 查看 main() 中关于 for1() 的调用:
- 可看出,对 for1() 的调用被优化,传入 printf() 打印的是 0x7f8 处给出的值 0x14。对 for1() 传入的常量参数 5,编译器在编译时通过计算得到它的值固定为 0x14。可见,是编译器帮我们完成了函数调用优化工作,直接将执行结果传入 printf()。类似的还有 app6 中的其他分支函数,它们都被传入常量值,并在编译时计算出结果。用 IDA 查看这些分支函数的代码交叉引用,可发现没有代码引用它们,即优化后的程序中这些代码永远不会被执行
第八章 Android 原生程序开发与逆向分析(五)(原生 C 程序逆向分析)相关推荐
- 全栈工程师之路-中级篇之小程序开发-第二章第五节小程序中的Promise
上一节课最后,我们遇到了一个警告. 说我们太过频繁的调用serData了,因为我们这个页面的三部分数据是通过三个接口获取的. 所以我们分别在三个接口返回的时候调用了数据绑定. 但是过于频繁的调用set ...
- 了解微信小程序、掌握微信小程序开发工具的使用、了解小程序的目录以及文件结构、掌握小程序中常用的组件、掌握WXML、WXSS、WXS的基本使用
1 微信小程序介绍以及开发准备 1.1 了解微信小程序 百度百科: 微信小程序,简称小程序,英文名Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用"触手可及&quo ...
- 开放下载 | 《Knative 云原生应用开发指南》开启云原生时代 Serverless 之门
点击下载<Knative 云原生应用开发指南> 自 2018 年 Knative 项目开源后,就得到了广大开发者的密切关注.Knative 在 Kubernetes 之上提供了一套完整的应 ...
- 小程序开发代码_快速学会微信小程序开发,无需懂代码!
现在想要制作自己的小程序的人越来越多,但大多数都不懂任何代码知识,不知该如何制作.其实随着各种第三方开发工具的出现,无需微信小程序开发源代码,小白也能顺利生成一个自己的小程序了.下面我就跟大家说下该如 ...
- GTK+图形化应用程序开发学习笔记(五)—组装盒、组合表、固定容器构件
GTK+图形化应用程序开发学习笔记(五)-组装盒.组合表.固定容器构件 一.组装盒 组装盒(GtkBox)也称为组合构件.使用组装盒可以将多个构件放在一个容器中.容器可以把组装盒看作是一个构件.不像按 ...
- 基于小程序开发的宝可梦图鉴小程序源码课程设计毕业设计
源码地址:基于小程序开发的宝可梦图鉴小程序源码课程设计毕业设计 宝可梦是一款备受喜爱的游戏,其丰富的剧情和可爱的角色深受玩家们的喜欢.而对于宝可梦爱好者来说,一款好的宝可梦图鉴是必不可少的.今天,我来 ...
- 企业小程序开发步骤【教你创建小程序】
随着移动互联网的兴起,微信已经成为了很多企业和商家必备的平台,而其中,微信小程序是一个非常重要的工具.本文将为大家介绍小程序开发步骤,教你创建小程序. 步骤一.注册小程序账号 先准备一个小程序账号,在 ...
- 小程序开发运营必看:微信小程序平台运营规范
一.原则及相关说明 微信最核心的价值,就是连接--提供一对一.一对多和多对多的连接方式,从而实现人与人.人与智能终端.人与社交化娱乐.人与硬件设备的连接,同时连接服务.资讯.商业. 微信团队一 ...
- 微信小程序开发学习笔记001--认识微信小程序,第一个微信小程序
第一天,认识微信小程序,第一个微信小程序 1.什么是微信小程序? 是h5网页嘛?不是 微信张小龙说: 小程序是一种不需要下载安装即可使用的应用, 它实现了应用"触手可及"的梦想,用 ...
- 微信开发者工具无法选择预览和真机调试_小程序开发 第二篇:使用微信小程序开发者工具、wepy框架初始化项目...
1.微信小程序开发者工具 使用: 小程序原生开发:直接使用小程序开发者工具打开项目即可 小程序框架开发:首选官方提供类vue.js开发框架 wepy.js ,备选 mpVue.我们选择的是 wepy ...
最新文章
- 剑指offer:二维数组中的查找python实现
- PHP ftp_mkdir 函数
- 远程桌面解决(面对不同问题)连接办法
- 网络宣传推广浅谈关键词排名好却没有流量的原因解决方法!
- linux存储--文件描述符fd与FILE结构体(二)
- java并发编程实战学习笔记之基础知识与对象的共享
- 橱柜高度与身高对照表_下一套房子装修,橱柜就照这样打,布局尺寸这么详细,不信不好用...
- [转]十五分钟介绍 Redis数据结构
- java jnlp_java – 调试JNLP启动应用程序
- uva 784 Maze Exploration
- Elipse 、Idea配置 Java-Code-Formatter
- java 线程同步的list_java线程生产者与消费者实例(使用List实现同步)
- Valgrind的使用方法
- 强化学习:2 马尔科夫决策过程
- 简述UTF-8编码原理及其文本文件的读写技术 【转】
- Unity3d UGUI 通用Confirm确认对话框实现(Inventory Pro学习总结)
- java web fileupload_javaweb 文件上传(fileupload) 下载
- 电子书下载:Pro ASP.NET MVC2 Framework 2nd
- wxWindows一些网文
- vc2013控件第一个程序