freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

流程控制语句底层实现 | switch | 1 | GCC
Deutsh 2022-12-11 13:09:07 153534
所属地 江西省
#include <stdio.h>

int main()
{
        int nIndex = 1;
        scanf("%d",&nIndex);

        switch(nIndex)
        {
                case 1:
                        printf("nIndex == 1");
                        break;
                case 2:
                        printf("nIndex == 2");
                        break;
                case 3:
                        printf("nIndex == 3");
                        break;
                case 4:
                        printf("nIndex == 4");
                        break;
                case 5:
                        printf("nIndex == 5");
                        break;
        }
}

经测试,在分支超过4条时在一定分支范围内,GCC会采用跳表这种优化的方式来处理swicth,代替低于4条时,与if一样的分支结构 ,从而加速其访问速度

GCC对于跳表的处理与VC6.0的处理方式有所不同,先来说GCC的处理方式

动态调试以上代码:

scanf输入后开始调试:

1670735083_639564eb0be18f9e65e83.png!small?1670735074051

由于我们的case是从1...5依次增加的,所以从图中516c处开始的3条语句负责判断当前的输入是否超过了swicth分支语句中case的最大值

此处我们输入的值为2,经scanf输入后该值就被存储在rbp-0xc所指的栈空间中,然后通过mov将输入的值取出放入eax中,并与分支的最大值5作比较,并可能产生两种结果:

  • 若输入的值> 5:执行ja指令继续进行跳转

    1670735088_639564f0c65e00718ab19.png!small?1670735082238

根据其反汇编代码可以看出,由于我们没有设置default语句,所以直接跳转到结尾,将返回值0放入eax中,释放栈空间进行返回

  • 若输入的值< 5:接着向下执行

跳表

1670735096_639564f880ff99e4d7de6.png!small?1670735087779

接着向下执行便涉及到跳表的介入了,一个跳表的表项占4字节,且为连续存储,所以可直接通过case后跟的值就相当于索引号,跳表首地址 + 索引号 * 4就是对应表项的位置,所以 索引号 * 4其实就是对应调表表项的偏移地址

lea rdx, [rax*4]

该指令计算的便是偏移地址,由于我们的输入的case值为2,所以其跳表表项相对于跳表首地址的偏移应该为2 * 4 = 8该值被放入RDX

1670735124_63956514951a4f28c4350.png!small?1670735115466

那么接下来便是取出跳表的首地址,并将其放入RAX仅存其中

lea rax, [rip + 0xe61]0x51e3 + 0x61e = 0x6044

动态调试看跳表的地址为:0x555555556044

1670735128_63956518d31b16f374b7a.png!small?1670735119793

4字节为一个单位,查看该内存区域:

1670735132_6395651c66de274b11f72.png!small?1670735123529

VC中的跳表不同,此处并不是每一个case代码块的地址,而更像是偏移

mov eax, dword ptr [rdx + rax]

解析来便是取出当前case索引对应的跳表表项*(0x555555556044 + 2 * 4)也就是跳表中的第二项0xfffff1c7

1670735139_639565234dd71e3c3f481.png!small?1670735130606

接下来的cdqe的作用是:将EAX中的32位数值的符号位扩展到64RAX寄存器中高32位的每一位,所以扩展后为

1670735143_63956527b9b81badc87d8.png!small?1670735134712

这时候精髓的部分就要到了

1670735148_6395652cb5c446ced6212.png!small?1670735139838

来细看这三步

  • lea rdx, [rip + 0xe55]

    此处又将跳表的首地址放入了 RDX 中,为啥是跳表的首地址?可以来计算一下:

    刚刚在0x5555555551dc处执行了lea rax, [rip + 0xe61]获取到了跳表的首地址,也就是说跳表的首地址为0x5555555551e3 + 0xe61

    而在此处计算出的地址为:0x5555555551ef + 0xe55=0x5555555551e3 + 0xc + 0xe55=0x5555555551e3 + 0xe61由此推出执行完该语句rdx中将存放跳表的首地址,也就是0x555555556044

    1670735159_63956537b0365f30e0aa7.png!small?1670735150922

  • add rax, rdx

    这一句的含义我想不用过多说明,但这一句就表示其实跳表中的每个表项存储的就是偏移量,而且是每个case代码块相对于跳表的偏移量,而且这个偏移量是有符号的

    由于RAX中存储的有符号数,所以:

    RAX + RDX = 0xfffffffffffff1c7 + 0x555555556044 = 0x55555555520B

    1670735168_6395654031025e45bddf5.png!small

    回看其反汇编代码,这正是第二个case的代码块

    1670735190_6395655609e7a2e8b78ca.png!small?1670735181292

  • 接下来便jmp到目标位置即可

但我们再跟过去看一眼

1670735198_6395655ec5631642cc065.png!small?1670735190198

lea rax, [rip + 0xe01]的作用在于将我们要输出字符串的首地址放入EAX中,准备被printf输出,这没什么稀奇的

1670735204_639565641e4112bf3173b.png!small?1670735196509

但注意这个字符串所在的位置,也是rip+0xe什么什么,那么如果在当前位置想要索引到跳表的首地址应该是怎么样的呢,应该是:

0x555555556044 - 0x555555555212 = E32

也就是[rip + 0xe32]这也足以表明跳表所处区域与常量字符串所处的区域基本为同一区域(图中红色方框中的虚拟地址范围中)

1670735212_6395656cb9a1295553920.png!small?1670735204753

此时还有一个问题,我们可以发现跳表中的第0项是不会被使用到的,那为什么不将第一项放到0号索引处依次类推呢?

这是为了方便直接根据case后所跟序号计算偏移地址,所以才会空出第0项,转而从第1项开始执行

# 系统安全
本文为 Deutsh 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
PWN与GCC相关
Deutsh LV.5
欢迎来我的博客 www.shtwo.top
  • 45 文章数
  • 36 关注者
File Structure 基础 && PUTS 流程分析
2023-01-30
Linux 堆内存结构分析(中)| PWN
2022-12-30
Canary补充 - TLS && Canary原始值在内存中的位置
2022-12-21
文章目录