首页
论坛
课程
招聘
[原创]Python栈溢出CVE-2021-3177分析
2021-2-10 11:37 7769

[原创]Python栈溢出CVE-2021-3177分析

2021-2-10 11:37
7769

问题背景

我正在学习缓冲区溢出,同时又对各个语言的安全问题感兴趣。最近看到 Python CVE-2021-3177 ,所以分析一下。

 

从描述上看,这是一个sprintf函数引发的栈溢出漏洞。

分析过程

因为本文主要分析的和Python执行流程关系不大,所以也可以忽略第一步调试环境搭建,直接看调试分析。

  1. 搭建调试环境

    我用的IDE是Clion

    先搭建Python调试环境,编译出可调试的Python二进制程序。这一步可以参考官网 Guide

    准备好漏洞测试的Python代码

    1
    2
    3
    4
    from ctypes import *
    x = c_double.from_param(1e300)
     
    print(x)
  2. 调试分析

    _ctypes/callproc.c下面代码打好断点

    1
    2
    3
    4
    5
    6
    ...
    case 'd':
        sprintf(buffer, "<cparam '%c' (%f)>",
            self->tag, self->value.d);  // 1e300
        break;
    ...

    在IDE中可以看到 self->value.d 是double类型,且值就是我们传入的参数。

    从这里可以看出来,这个CVE漏洞和下面这段C代码漏洞是一样的

    1
    2
    3
    4
    5
    6
    7
    #include <stdio.h>
    int main(){
      char buf[2];
      double s=1e300//s值用户可控
     
      sprintf(buf, "%f", s);
    }

    所以接下来我就去研究学习上面的C代码中漏洞是怎么利用的。

  3. sprintf引起的栈溢出分析

    网上文章分析sprintf溢出都以sprintf(buf, "%s", s);举例

    对于 "%s" 格式符这种覆盖很容易理解,如果s="a...a" 很多a字符时,就可以将rip覆盖成"\x6161616161616161"。

    对于 "%f" 这种用浮点数字去覆盖buf变量,我就有一个疑问,浮点数怎么控制rip指针。比如s=11111111111111112222222233333333.0时,rip会被覆盖成什么?

    先说自己的结论:

    1
    2
    3
    4
    5
    6
    在漏洞代码中,double浮点数可以用来向很大内存中写入 `\x30-\x39`、`\x2e` 的字节。
     
    * 只能写入`\x30-\x39`、`\x2e`,而不能写其他字节,是因为 %f 转换成小数只能有`[0-9.]`,数字1-9对应的十六进制就是 `\x30`-`\x39`
    * 能覆盖多大的内存?
        * 因为double类型,表示的小数位数能很大,所以至少可以覆盖300个字节
        * 如果是float类型,就要小的多了

    结论是怎么来的呢?

    首先我们要知道浮点数在计算机并不总是能够精确的被表示,只能被近似表示。更具体的细节需要去了解"浮点数在计算机中的存储",可以看文末的参考资料。

    所以 double s=11111111111111112222222233333333.0 在内存中,s的值并不一定是 11111111111111112222222233333333.0

    我在代码中加了一行printf("%f\n",s);打印s变量实际的值。

    从下面的分析来看,在64位系统上 11111111111111112222222233333333.0 会把rip覆盖成"\x30\x38\x30\x32\x34\x33\x35\x33"。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include <stdio.h>
    int main(){
      char buf[2];
      double s=11111111111111112222222233333333.0; //1618283
     
      // s实际的值是 1111111111111111 19575513 35342080.000000
      //            buf栈            rbp      rip
      printf("%f\n",s);
     
      /*
     
      print("".join([hex(ord(i)).replace("0x","\\x") for i in "35342080"]))
      "35342080"字符串对应 \x33\x35\x33\x34\x32\x30\x38\x30
     
      >>> x=[hex(ord(i)).replace("0x","\\x") for i in "35342080"]
      >>> x.reverse()
      >>> print("".join(x))
      \x30\x38\x30\x32\x34\x33\x35\x33
     
      */
      // 所以rip会被覆盖成 \x30\x38\x30\x32\x34\x33\x35\x33
      // gdb验证后,符合预期
     
      sprintf(buf, "%f", s);
    }
  4. _ctypes/callproc.c其他代码有没有相同的栈溢出问题?
    self->value.d在_ctypes/ctypes.h文件有定义,是一个union数据类型。

    这个union类型中看着好像还有几个字段也能用在sprintf栈溢出中,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    union {
        char c;
        char b;
        short h;
        int i;
        long l;
        long long q;
        long double D; // 可以利用
        double d;  // 可以利用
        float f;  // 能覆盖的内存有限
        void *p;  // 可能可以
    } value;

    在 _ctypes/callproc.c 文件看了看,结论是只有d变量可以利用。

    1
    2
    3
    4
    5
    6
    7
    union {
        ...
        long double D; // 没有用到这个变量
        double d;  // 可以利用
        float f;  // 能覆盖的内存有限
        void *p;  // 因为只用到了 %p 打印地址,没有 %s ,所以也无法利用
    } value;

    _ctypes/callproc.c文件中,也只有下面的代码sprintf d变量。所以只有这一处是存在栈溢出的。

    1
    2
    3
    4
    5
    6
    ...
    case 'd':
        sprintf(buffer, "<cparam '%c' (%f)>",
            self->tag, self->value.d);  // 1e300
        break;
    ...

总结

本文仅仅分析了参数怎么控制rip指针,其中我学习到的点是"浮点数在内存中的表示"。

 

关于漏洞还有其他很多方面都没有分析,比如:

  • 怎么自动化检测这种漏洞?
  • 漏洞的实际影响是什么?
    • 有多少Python应用存在漏洞?
  • 漏洞触发的调用链是什么?

参考资料

C语言float、double的内存表示


第五届安全开发者峰会(SDC 2021)10月23日上海召开!限时2.5折门票(含自助午餐1份)

收藏
点赞1
打赏
分享
最新回复 (6)
雪    币: 22
活跃值: 活跃值 (134)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
doomday 活跃值 2021-2-11 03:06
2
0
很有意思的是,有些看起来是”漏洞“的漏洞,确是开发者故意为之。
雪    币: 713
活跃值: 活跃值 (66)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
xyy吸氧羊 活跃值 2021-3-15 01:32
3
0
请教一下,我是不是能通过:把OD设置为实时调试器,再执行POC,也能看到触发时的上下文环境?有没有试过?
雪    币: 62
活跃值: 活跃值 (138)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
爱大萌 活跃值 2021-3-29 15:43
4
0
xyy吸氧羊 请教一下,我是不是能通过:把OD设置为实时调试器,再执行POC,也能看到触发时的上下文环境?有没有试过?
没有试过。已经忘了怎么用OD了,不太清楚是否能用来调试这个漏洞。
雪    币: 713
活跃值: 活跃值 (66)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
xyy吸氧羊 活跃值 2021-4-5 02:35
5
0
爱大萌 没有试过。已经忘了怎么用OD了,不太清楚是否能用来调试这个漏洞。

我试了一下,是可以的,不过好像是因为有GS保护,会直接跳到stack buffer overrun里面去

雪    币: 713
活跃值: 活跃值 (66)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
xyy吸氧羊 活跃值 2021-4-5 02:42
6
0

然后还有一个问题:你文章里说"浮点数实际在内存中的值是不一样的" 我试了一下你的验证程序,确实正如你说的,

但是我发现,我调试这个漏洞的时候,poc里的1e300,在溢出的时候,在内存中的值还是1E300,retn地址也是3030303030303030,这是为什么呢?

如果内存中的值没有改变的话,还是能像”%s“一样利用吧?

雪    币: 62
活跃值: 活跃值 (138)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
爱大萌 活跃值 2021-5-15 14:49
7
0
xyy吸氧羊 然后还有一个问题:你文章里说&quot;浮点数实际在内存中的值是不一样的&quot; 我试了一下你的验证程序,确实正如你说的,但是我发现,我调试这个漏洞的时候,poc里的1e300, ...
`在溢出的时候,在内存中的值还是1E300`,这句话里,你是怎么看内存的值还是1e300的呢?
游客
登录 | 注册 方可回帖
返回