文章目录

  • 原生 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,继续下一轮循环
  • 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() 的反汇编代码:
  • 可看出,同样用 adrpadd 取地址,app_2 中第一个 PT_LOAD 类型的 Segment 指定 ELF 的加载基址为 0x400000,而 app6 没有指定加载基址,链接器可将其加载到任意的内存地址;app_2 的 adrpadd 指令计算出的地址为 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,否则将其设为 0

  • 0x8fc 的 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-0x410main@@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 程序逆向分析)相关推荐

  1. 全栈工程师之路-中级篇之小程序开发-第二章第五节小程序中的Promise

    上一节课最后,我们遇到了一个警告. 说我们太过频繁的调用serData了,因为我们这个页面的三部分数据是通过三个接口获取的. 所以我们分别在三个接口返回的时候调用了数据绑定. 但是过于频繁的调用set ...

  2. 了解微信小程序、掌握微信小程序开发工具的使用、了解小程序的目录以及文件结构、掌握小程序中常用的组件、掌握WXML、WXSS、WXS的基本使用

    1 微信小程序介绍以及开发准备 1.1 了解微信小程序 百度百科: 微信小程序,简称小程序,英文名Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用"触手可及&quo ...

  3. 开放下载 | 《Knative 云原生应用开发指南》开启云原生时代 Serverless 之门

    点击下载<Knative 云原生应用开发指南> 自 2018 年 Knative 项目开源后,就得到了广大开发者的密切关注.Knative 在 Kubernetes 之上提供了一套完整的应 ...

  4. 小程序开发代码_快速学会微信小程序开发,无需懂代码!

    现在想要制作自己的小程序的人越来越多,但大多数都不懂任何代码知识,不知该如何制作.其实随着各种第三方开发工具的出现,无需微信小程序开发源代码,小白也能顺利生成一个自己的小程序了.下面我就跟大家说下该如 ...

  5. GTK+图形化应用程序开发学习笔记(五)—组装盒、组合表、固定容器构件

    GTK+图形化应用程序开发学习笔记(五)-组装盒.组合表.固定容器构件 一.组装盒 组装盒(GtkBox)也称为组合构件.使用组装盒可以将多个构件放在一个容器中.容器可以把组装盒看作是一个构件.不像按 ...

  6. 基于小程序开发的宝可梦图鉴小程序源码课程设计毕业设计

    源码地址:基于小程序开发的宝可梦图鉴小程序源码课程设计毕业设计 宝可梦是一款备受喜爱的游戏,其丰富的剧情和可爱的角色深受玩家们的喜欢.而对于宝可梦爱好者来说,一款好的宝可梦图鉴是必不可少的.今天,我来 ...

  7. 企业小程序开发步骤【教你创建小程序】

    随着移动互联网的兴起,微信已经成为了很多企业和商家必备的平台,而其中,微信小程序是一个非常重要的工具.本文将为大家介绍小程序开发步骤,教你创建小程序. 步骤一.注册小程序账号 先准备一个小程序账号,在 ...

  8. 小程序开发运营必看:微信小程序平台运营规范

    一.原则及相关说明 ​ 微信最核心的价值,就是连接--提供一对一.一对多和多对多的连接方式,从而实现人与人.人与智能终端.人与社交化娱乐.人与硬件设备的连接,同时连接服务.资讯.商业. ​ 微信团队一 ...

  9. 微信小程序开发学习笔记001--认识微信小程序,第一个微信小程序

    第一天,认识微信小程序,第一个微信小程序 1.什么是微信小程序? 是h5网页嘛?不是 微信张小龙说: 小程序是一种不需要下载安装即可使用的应用, 它实现了应用"触手可及"的梦想,用 ...

  10. 微信开发者工具无法选择预览和真机调试_小程序开发 第二篇:使用微信小程序开发者工具、wepy框架初始化项目...

    1.微信小程序开发者工具 使用: 小程序原生开发:直接使用小程序开发者工具打开项目即可 小程序框架开发:首选官方提供类vue.js开发框架 wepy.js ,备选 mpVue.我们选择的是 wepy ...

最新文章

  1. 剑指offer:二维数组中的查找python实现
  2. PHP ftp_mkdir 函数
  3. 远程桌面解决(面对不同问题)连接办法
  4. 网络宣传推广浅谈关键词排名好却没有流量的原因解决方法!
  5. linux存储--文件描述符fd与FILE结构体(二)
  6. java并发编程实战学习笔记之基础知识与对象的共享
  7. 橱柜高度与身高对照表_下一套房子装修,橱柜就照这样打,布局尺寸这么详细,不信不好用...
  8. [转]十五分钟介绍 Redis数据结构
  9. java jnlp_java – 调试JNLP启动应用程序
  10. uva 784 Maze Exploration
  11. Elipse 、Idea配置 Java-Code-Formatter
  12. java 线程同步的list_java线程生产者与消费者实例(使用List实现同步)
  13. Valgrind的使用方法
  14. 强化学习:2 马尔科夫决策过程
  15. 简述UTF-8编码原理及其文本文件的读写技术 【转】
  16. Unity3d UGUI 通用Confirm确认对话框实现(Inventory Pro学习总结)
  17. java web fileupload_javaweb 文件上传(fileupload) 下载
  18. 电子书下载:Pro ASP.NET MVC2 Framework 2nd
  19. wxWindows一些网文
  20. vc2013控件第一个程序

热门文章

  1. 获取本机所有网卡的网卡名、网卡描述、网卡MAC地址、网卡IP、网卡类型等信息及网线是否插入状态
  2. Neural Style Transfer解读与实现
  3. 怎么用计算机工作,电脑备忘录软件怎么用
  4. 司铭宇讲师:如何做好大客户销售沟通与谈判技巧
  5. unity 3D炫酷开场动画
  6. 站在风口的猪-TaskList
  7. DELL R720硬件故障
  8. 睡眠辅助软件行业调研报告 - 市场现状分析与发展前景预测
  9. 抖音测试美甲软件,抖音美甲大师游戏
  10. 打开计算机窗口抖动是什么原因,Win7电脑屏幕一直抖动的解决方法