├── EternalBlue ├── dump.bin ├── sbmv1.md └── test.cpp ├── Etneralromance ├── 3.1.png ├── 5.png ├── 6.png ├── Eternalromance (永恒浪漫) 漏洞分析.md ├── andx.JPG ├── exploit.pcapng ├── wireshark.JPG └── wirshark1.PNG └── README.md /EternalBlue/dump.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/EternalBlue/dump.bin -------------------------------------------------------------------------------- /EternalBlue/sbmv1.md: -------------------------------------------------------------------------------- 1 | # Eternalblue SMBv1 SrvOs2FeaToNt OOB 2 | # NSA Eternalblue SMB 漏洞分析 3 | ## 背景 4 | 5 | __EXPLOIT__: 6 | Eternalblue-2.2.0.exe 7 | 8 | __TARGET__: 9 | win7 sp1 32bits 10 | srv.sys 6.1.7601.17514 11 | srvnet.sys 6.1.7601.17514 12 | 13 | __PATCH__: 14 | MS17-010 15 | 16 | 17 | ## 漏洞原理 18 | srv.sys在处理SrvOs2FeaListSizeToNt的时候逻辑不正确导致越界拷贝。漏洞触发点如下: 19 | ```cpp 20 | unsigned int __fastcall SrvOs2FeaToNt(int a1, int a2) 21 | { 22 | int v4; // edi@1 23 | _BYTE *v5; // edi@1 24 | unsigned int result; // eax@1 25 | 26 | v4 = a1 + 8; 27 | *(_BYTE *)(a1 + 4) = *(_BYTE *)a2; 28 | *(_BYTE *)(a1 + 5) = *(_BYTE *)(a2 + 1); 29 | *(_WORD *)(a1 + 6) = *(_WORD *)(a2 + 2); 30 | _memmove((void *)(a1 + 8), (const void *)(a2 + 4), *(_BYTE *)(a2 + 1)); 31 | v5 = (_BYTE *)(*(_BYTE *)(a1 + 5) + v4); 32 | *v5++ = 0; 33 | _memmove(v5, (const void *)(a2 + 5 + *(_BYTE *)(a1 + 5)), *(_WORD *)(a1 + 6)); //这里产生的越界写 34 | result = (unsigned int)&v5[*(_WORD *)(a1 + 6) + 3] & 0xFFFFFFFC; 35 | *(_DWORD *)a1 = result - a1; 36 | return result; 37 | } 38 | ``` 39 | 发生越界的地方见上面第二个memmove。调试的时候可以这样下断点: 40 | ``` 41 | kd> u srv!SrvOs2FeaToNt+0x4d 42 | srv!SrvOs2FeaToNt+0x4d: 43 | 9877b278 ff15e0a07698 call dword ptr [srv!_imp__memmove (9876a0e0)] 44 | 9877b27e 0fb74606 movzx eax,word ptr [esi+6] 45 | 9877b282 8d441803 lea eax,[eax+ebx+3] 46 | 9877b286 83e0fc and eax,0FFFFFFFCh 47 | 9877b289 83c418 add esp,18h 48 | 9877b28c 8bc8 mov ecx,eax 49 | 9877b28e 2bce sub ecx,esi 50 | 9877b290 5f pop edi 51 | 52 | //最后一次越界的拷贝的长度是0xa8 53 | ba e1 srv!SrvOs2FeaToNt+0x4d ".if(poi(esp+8) != a8){gc} .else {}" 54 | ``` 55 | 这么设断点的原因是最后一次越界的拷贝的长度是0xa8,断下来后可以发现: 56 | ``` 57 | kd> dd esp 58 | 99803b38 88c8dff9 a3fc203a 000000a8 88c8dff8 59 | 99803b48 a3fc2039 00000000 a3fb20d8 a3fc2035 60 | 99803b58 a3fd2030 99803b7c 9877b603 88c8dff0 61 | 99803b68 a3fc2035 88307360 a3fb20b4 a3fb2008 62 | 99803b78 a3fc2035 99803bb4 98794602 88c8dff0 63 | 99803b88 99803bbc 99803ba8 99803bac 88307360 64 | 99803b98 a3fb2008 00000002 a3fb20b4 a3fb20d8 65 | 99803ba8 00010fe8 00000000 00000000 99803c00 66 | 67 | kd> !pool 88c8dff9 68 | Pool page 88c8dff9 region is Nonpaged pool 69 | *88c7d000 : large page allocation, tag is LSdb, size is 0x11000 bytes 70 | Pooltag LSdb : SMB1 data buffer, Binary : srv.sys 71 | kd> !pool 88c8e009 72 | Pool page 88c8e009 region is Nonpaged pool 73 | 88c8e000 size: 8 previous size: 0 (Free) .... 74 | 75 | 88c8e008 doesn't look like a valid small pool allocation, checking to see 76 | if the entire page is actually part of a large page allocation... 77 | 78 | *88c8e000 : large page allocation, tag is LSbf, size is 0x11000 bytes 79 | Pooltag LSbf : SMB1 buffer descriptor or srvnet allocation, Binary : srvnet.sys 80 | 81 | kd> ? 88c7d000 +11000 82 | Evaluate expression: -2000101376 = 88c8e000 83 | kd> ? 88c8dff9 +a8 84 | Evaluate expression: -2000101215 = 88c8e0a1 //这里明显越界了。 85 | ``` 86 | 我们可以从上面的调试记录看到明显的越写拷贝操作。可以看到被覆盖的是SMB1的buffer是有srvnet.sys分配的。这里exploit精心布局好的,是通过pool喷射的将两个pool连接在一起的。覆盖后面的这个pool有啥用后面会提到。 87 | 有同学会说这只是现象还没有看到漏洞真正的成因。 88 | ```cpp 89 | unsigned int __fastcall SrvOs2FeaListSizeToNt(int pOs2Fea) 90 | { 91 | unsigned int v1; // edi@1 92 | int Length; // ebx@1 93 | int pBody; // esi@1 94 | unsigned int v4; // ebx@1 95 | int v5; // ecx@3 96 | int v8; // [sp+10h] [bp-8h]@3 97 | unsigned int v9; // [sp+14h] [bp-4h]@1 98 | 99 | v1 = 0; 100 | Length = *(_DWORD *)pOs2Fea; 101 | pBody = pOs2Fea + 4; 102 | v9 = 0; 103 | v4 = pOs2Fea + Length; 104 | while ( pBody < v4 ) 105 | { 106 | if ( pBody + 4 >= v4 107 | || (v5 = *(_BYTE *)(pBody + 1) + *(_WORD *)(pBody + 2), 108 | v8 = *(_BYTE *)(pBody + 1) + *(_WORD *)(pBody + 2), 109 | v5 + pBody + 5 > v4) ) 110 | { 111 | // 112 | // 注意这里修改了Os2Fea的Length,自动适应大小 113 | // 本来是一个DWORD为什么强制转换成WORD????? 114 | // 初始值是0x10000,最终变成了0x1ff5d 115 | // 116 | *(_WORD *)pOs2Fea = pBody - pOs2Fea; 117 | return v1; 118 | } 119 | if ( RtlULongAdd(v1, (v5 + 0xC) & 0xFFFFFFFC, &v9) < 0 ) 120 | return 0; 121 | v1 = v9; 122 | pBody += v8 + 5; 123 | } 124 | return v1; 125 | } 126 | 127 | unsigned int __fastcall SrvOs2FeaListToNt(int pOs2Fea, int *pArgNtFea, int *a3, _WORD *a4) 128 | { 129 | __int16 v5; // bx@1 130 | unsigned int Size; // eax@1 131 | NTFEA *pNtFea; // ecx@3 132 | int pOs2FeaBody; // esi@9 133 | int v10; // edx@9 134 | unsigned int v11; // esi@14 135 | int v12; // [sp+Ch] [bp-Ch]@11 136 | unsigned int v14; // [sp+20h] [bp+8h]@9 137 | 138 | v5 = 0; 139 | Size = SrvOs2FeaListSizeToNt(pOs2Fea); 140 | *a3 = Size; 141 | if ( !Size ) 142 | { 143 | *a4 = 0; 144 | return 0xC098F0FF; 145 | } 146 | pNtFea = (NTFEA *)SrvAllocateNonPagedPool(Size, 0x15); 147 | *pArgNtFea = (int)pNtFea; 148 | if ( pNtFea ) 149 | { 150 | pOs2FeaBody = pOs2Fea + 4; 151 | v10 = (int)pNtFea; 152 | v14 = pOs2Fea + *(_DWORD *)pOs2Fea - 5; 153 | if ( pOs2Fea + 4 > v14 ) 154 | { 155 | LABEL_13: 156 | if ( pOs2FeaBody == pOs2Fea + *(_DWORD *)pOs2Fea ) 157 | { 158 | *(_DWORD *)v10 = 0; 159 | return 0; 160 | } 161 | v11 = 0xC0000001; 162 | *a4 = v5 - pOs2Fea; 163 | } 164 | else 165 | { 166 | while ( !(*(_BYTE *)pOs2FeaBody & 0x7F) ) 167 | { 168 | v12 = (int)pNtFea; 169 | v5 = pOs2FeaBody; 170 | pNtFea = (NTFEA *)SrvOs2FeaToNt(pNtFea, pOs2FeaBody); 171 | pOs2FeaBody += *(_BYTE *)(pOs2FeaBody + 1) + *(_WORD *)(pOs2FeaBody + 2) + 5; 172 | 173 | // 174 | // 由于SrvOs2FeaListSizeToNt将pOs2Fea的Length改大了。 175 | // 而且变得大了不少,所以这里的判读就没有什么意义了。最终导致越界的产生。 176 | // 177 | 178 | if ( pOs2FeaBody > v14 ) 179 | { 180 | v10 = v12; 181 | goto LABEL_13; 182 | } 183 | } 184 | *a4 = pOs2FeaBody - pOs2Fea; 185 | v11 = 0xC000000D; 186 | } 187 | SrvFreeNonPagedPool(*pArgNtFea); 188 | return v11; 189 | } 190 | if ( BYTE1(WPP_GLOBAL_Control->Flags) >= 2u && WPP_GLOBAL_Control->Characteristics & 1 && KeGetCurrentIrql() < 2u ) 191 | { 192 | _DbgPrint("SrvOs2FeaListToNt: Unable to allocate %d bytes from nonpaged pool.", *a3, 0); 193 | _DbgPrint("\n"); 194 | } 195 | return 0xC0000205; 196 | } 197 | ``` 198 | 首先`SrvOs2FeaListToNt`首先调用`SrvOs2FeaListSizeToNt`计算pNtFea的大小。这里注意了`SrvOs2FeaListSizeToNt`函数会修改原始的pOs2Fea中的Length大小,本来Length是一个DWORD, 代码还强制转换成了WORD,不能不让人联想一些事。然后以计算出来的Length来分配pNtFea.最后调用`SrvOs2FeaToNt`来实现转换。`SrvOs2FeaToNt`后面的判断就有问题了。这里还不止一个问题。 199 | 1. 转换完成后,增加pOs2FeaBody然后比较。正确的逻辑难道不应该是先判断再转换吗? 200 | 2. 由于`SrvOs2FeaListSizeToNt`中改变了pOs2Fea的`length`的值,这里使用变大后的值做比较,肯定会越界。 201 | 202 | 为了方便同学们调试,我把代码扣出来了。大家可以在环3围观下这段代码。 203 | ```cpp 204 | #include 205 | 206 | signed int RtlULongAdd(unsigned int a1, int a2, unsigned int *a3) 207 | { 208 | unsigned int v3; // edx@1 209 | signed int result; // eax@2 210 | 211 | v3 = a1 + a2; 212 | if (v3 < a1) 213 | { 214 | *a3 = -1; 215 | result = -1073741675; 216 | } else 217 | { 218 | *a3 = v3; 219 | result = 0; 220 | } 221 | return result; 222 | } 223 | 224 | unsigned int SrvOs2FeaListSizeToNt(PUCHAR pOs2Fea) 225 | { 226 | unsigned int v1; // edi@1 227 | int Length; // ebx@1 228 | PUCHAR pBody; // esi@1 229 | PUCHAR v4; // ebx@1 230 | int v5; // ecx@3 231 | int v8; // [sp+10h] [bp-8h]@3 232 | unsigned int v9; // [sp+14h] [bp-4h]@1 233 | 234 | v1 = 0; 235 | Length = *(DWORD*)pOs2Fea; 236 | pBody = pOs2Fea + 4; 237 | v9 = 0; 238 | v4 = pOs2Fea + Length; 239 | while (pBody < v4) 240 | { 241 | if (pBody + 4 >= v4 242 | || (v5 = *(BYTE *)(pBody + 1) + *(WORD *)(pBody + 2), 243 | v8 = *(BYTE *)(pBody + 1) + *(WORD *)(pBody + 2), 244 | v5 + pBody + 5 > v4)) 245 | { 246 | *(WORD *)pOs2Fea = pBody - pOs2Fea; 247 | return v1; 248 | } 249 | if (RtlULongAdd(v1, (v5 + 0xC) & 0xFFFFFFFC, &v9) < 0) 250 | return 0; 251 | v1 = v9; 252 | pBody += v8 + 5; 253 | } 254 | return v1; 255 | } 256 | 257 | PUCHAR gpBuffer = NULL; 258 | ULONG guSize = 0; 259 | 260 | PUCHAR SrvOs2FeaToNt(PUCHAR pNtFea, PUCHAR pOs2FeaBody) 261 | { 262 | PUCHAR pBody; // edi@1 263 | BYTE *pNtBodyStart; // edi@1 264 | PUCHAR result; // eax@1 265 | 266 | pBody = pNtFea + 8; 267 | *(BYTE *)(pNtFea + 4) = *(BYTE *)pOs2FeaBody; 268 | *(BYTE *)(pNtFea + 5) = *(BYTE *)(pOs2FeaBody + 1); 269 | *(WORD *)(pNtFea + 6) = *(WORD *)(pOs2FeaBody + 2); 270 | 271 | memcpy((void *)(pNtFea + 8), (const void *)(pOs2FeaBody + 4), *(BYTE *)(pOs2FeaBody + 1)); 272 | pNtBodyStart = (BYTE *)(*(BYTE *)(pNtFea + 5) + pBody); 273 | *pNtBodyStart++ = 0; 274 | 275 | if ((pNtBodyStart + *(WORD *)(pNtFea + 6)) > (gpBuffer + guSize)){ 276 | __debugbreak(); 277 | } 278 | memcpy(pNtBodyStart, (const void *)(pOs2FeaBody + 5 + *(BYTE *)(pNtFea + 5)), *(WORD *)(pNtFea + 6)); 279 | result = (PUCHAR)((ULONG_PTR)&pNtBodyStart[*(WORD *)(pNtFea + 6) + 3] & 0xFFFFFFFC); 280 | *(DWORD *)pNtFea = result - pNtFea; 281 | static int j = 0; 282 | printf("j=%d\n", j++); 283 | return result; 284 | } 285 | 286 | int main() 287 | { 288 | FILE* pFile = fopen("dump.bin", "r+b"); 289 | fseek(pFile, 0, SEEK_END); 290 | ULONG uSize = (ULONG)ftell(pFile); 291 | fseek(pFile, 0, SEEK_SET); 292 | PUCHAR pOs2Fea = (PUCHAR)malloc(uSize); 293 | fread(pOs2Fea, 1, uSize, pFile); 294 | fclose(pFile); 295 | 296 | ULONG uFixSize = SrvOs2FeaListSizeToNt(pOs2Fea); 297 | 298 | PUCHAR pOs2FeaBody; 299 | PUCHAR pNtFea = (PUCHAR)malloc(uFixSize); 300 | PUCHAR v10; 301 | PUCHAR v14; 302 | PUCHAR v12; 303 | PUCHAR v5; 304 | LONG v11; 305 | 306 | PUCHAR pNtFeaEnd = pNtFea + uFixSize; 307 | 308 | gpBuffer = pNtFea; 309 | guSize = uFixSize; 310 | 311 | if (pNtFea) 312 | { 313 | pOs2FeaBody = pOs2Fea + 4; 314 | v10 = pNtFea; 315 | v14 = pOs2Fea + *(DWORD *)pOs2Fea - 5; 316 | if (pOs2Fea + 4 > v14) 317 | { 318 | LABEL_13: 319 | if (pOs2FeaBody == pOs2Fea + *(DWORD *)pOs2Fea) 320 | { 321 | *(DWORD *)v10 = 0; 322 | return 0; 323 | } 324 | v11 = 0xC0000001; 325 | //*a4 = v5 - pOs2Fea; 326 | } else{ 327 | while (!(*(BYTE *)pOs2FeaBody & 0x7F)) 328 | { 329 | v12 = pNtFea; 330 | v5 = pOs2FeaBody; 331 | pNtFea = SrvOs2FeaToNt(pNtFea, pOs2FeaBody); 332 | pOs2FeaBody += *(BYTE *)(pOs2FeaBody + 1) + *(WORD *)(pOs2FeaBody + 2) + 5; 333 | if (pOs2FeaBody > v14) 334 | { 335 | v10 = v12; 336 | goto LABEL_13; 337 | } 338 | } 339 | //*a4 = pOs2FeaBody - pOs2Fea; 340 | v11 = 0xC000000D; 341 | } 342 | return v11; 343 | } 344 | 345 | return 0; 346 | } 347 | ``` 348 | 看到我加了个`__debugbreak`的地方,断在那里就说明溢出了 349 | dump.bin的内容最后我会给大家带上。 350 | 大家也可以自己抓dump.bin的内容,方法如下: 351 | ``` 352 | kd> u SrvOs2FeaListToNt 353 | srv!SrvOs2FeaListToNt: 354 | 9877b565 8bff mov edi,edi 355 | 9877b567 55 push ebp 356 | 9877b568 8bec mov ebp,esp 357 | 9877b56a 51 push ecx 358 | 9877b56b 8365fc00 and dword ptr [ebp-4],0 359 | 9877b56f 56 push esi 360 | 9877b570 57 push edi 361 | 9877b571 8b7d08 mov edi,dword ptr [ebp+8] 362 | 9877b574 57 push edi 363 | 9877b575 e82effffff call srv!SrvOs2FeaListSizeToNt (9877b4a8) 364 | 365 | kd> ba e1 9877b575 366 | 367 | kd> g 368 | Breakpoint 0 hit 369 | srv!SrvOs2FeaListToNt+0x10: 370 | 9877b575 e82effffff call srv!SrvOs2FeaListSizeToNt (9877b4a8) 371 | 372 | kd> !pool edi 373 | Pool page a3fd10d8 region is Paged pool 374 | *a3fd1000 : large page allocation, tag is LStr, size is 0x11000 bytes 375 | Pooltag LStr : SMB1 transaction, Binary : srv.sys 376 | kd> .writemem 1.bin a3fd10d8 l0x11000-d8 377 | ``` 378 | 379 | ## 漏洞利用 380 | ``` 381 | 382 | 覆盖前 383 | 8d1aa000 00 10 01 00 00 00 00 00 58 00 00 00 70 ........X...p 384 | 8d1aa00d 09 11 95 08 00 00 00 08 2f 1f 9f 08 2f ......../.../ 385 | 8d1aa01a 1f 9f 60 a1 1a 8d a0 0e 01 00 80 00 00 ..`.......... 386 | 8d1aa027 00 3c a0 1a 8d 00 00 00 00 f7 ff 00 00 .<........... 387 | 8d1aa034 10 a0 1a 8d a4 a0 1a 8d 00 00 00 00 60 ............` 388 | 8d1aa041 00 04 10 00 00 00 00 60 a1 1a 8d 00 a0 .......`..... 389 | 8d1aa04e 1a 8d a0 0e 01 00 60 01 00 00 d5 8e 01 ......`...... 390 | 8d1aa05b 00 d4 8e 01 00 13 8d 01 00 92 6f 00 00 ..........o.. 391 | 8d1aa068 11 36 01 00 10 6a 00 00 4f 9a 03 00 8e .6...j..O.... 392 | 8d1aa075 4d 01 00 4d d6 00 00 0c 6f 00 00 4b 71 M..M....o..Kq 393 | 8d1aa082 00 00 8a 99 03 00 c9 6d 00 00 c8 70 00 .......m...p. 394 | 8d1aa08f 00 c7 69 00 00 86 35 01 00 05 94 03 00 ..i...5...... 395 | 8d1aa09c 70 09 11 95 28 00 00 00 00 00 00 00 64 p...(.......d 396 | 8d1aa0a9 00 00 00 70 09 11 95 38 00 00 00 00 00 ...p...8..... 397 | 8d1aa0b6 00 00 a0 0e 01 00 ff 0f 00 00 28 a5 00 ..........(.. 398 | 8d1aa0c3 00 70 09 11 95 30 a5 00 00 70 09 11 95 .p...0...p... 399 | 8d1aa0d0 38 a5 00 00 70 09 11 95 40 a5 00 00 70 8...p...@...p 400 | 8d1aa0dd 09 11 95 48 a5 00 00 70 09 11 95 50 a5 ...H...p...P. 401 | 8d1aa0ea 00 00 70 09 11 95 58 a5 00 00 70 09 11 ..p...X...p.. 402 | 8d1aa0f7 95 f0 04 00 00 70 09 11 95 f8 04 00 00 .....p....... 403 | 8d1aa104 70 09 11 95 00 05 00 00 70 09 11 95 4e p.......p...N 404 | 8d1aa111 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 405 | 8d1aa11e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 406 | 8d1aa12b 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 407 | 8d1aa138 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 408 | 8d1aa145 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 409 | 8d1aa152 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 410 | 8d1aa15f 4e fe 53 4d 42 00 00 00 00 00 00 00 00 N.SMB........ 411 | 412 | 覆盖后 413 | 8d1aa000 00 00 00 00 00 00 00 00 ff ff 00 00 00 ............. 414 | 8d1aa00d 00 00 00 ff ff 00 00 00 00 00 00 00 00 ............. 415 | 8d1aa01a 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. 416 | 8d1aa027 00 00 f1 df ff 00 00 00 00 00 00 00 00 ............. 417 | 8d1aa034 20 f0 df ff 00 f1 df ff ff ff ff ff 60 ...........` 418 | 8d1aa041 00 04 10 00 00 00 00 80 ef df ff 00 00 ............. 419 | 8d1aa04e 00 00 10 00 d0 ff ff ff ff ff 18 01 d0 ............. 420 | 8d1aa05b ff ff ff ff ff 00 00 00 00 00 00 00 00 ............. 421 | 8d1aa068 00 00 00 00 00 00 00 00 60 00 04 10 00 ........`.... 422 | 8d1aa075 00 00 00 00 00 00 00 00 00 00 00 90 ff ............. 423 | 8d1aa082 cf ff ff ff ff ff 00 00 00 00 00 00 00 ............. 424 | 8d1aa08f 00 80 10 00 00 00 00 00 00 00 00 00 00 ............. 425 | 8d1aa09c 00 00 00 00 4b 00 00 00 00 00 00 00 64 ....K.......d 426 | 8d1aa0a9 00 00 00 70 09 11 95 38 00 00 00 00 00 ...p...8..... 427 | 8d1aa0b6 00 00 a0 0e 01 00 ff 0f 00 00 28 a5 00 ..........(.. 428 | 8d1aa0c3 00 70 09 11 95 30 a5 00 00 70 09 11 95 .p...0...p... 429 | 8d1aa0d0 38 a5 00 00 70 09 11 95 40 a5 00 00 70 8...p...@...p 430 | 8d1aa0dd 09 11 95 48 a5 00 00 70 09 11 95 50 a5 ...H...p...P. 431 | 8d1aa0ea 00 00 70 09 11 95 58 a5 00 00 70 09 11 ..p...X...p.. 432 | 8d1aa0f7 95 f0 04 00 00 70 09 11 95 f8 04 00 00 .....p....... 433 | 8d1aa104 70 09 11 95 00 05 00 00 70 09 11 95 4e p.......p...N 434 | 8d1aa111 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 435 | 8d1aa11e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 436 | 8d1aa12b 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 437 | 8d1aa138 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 438 | 8d1aa145 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 439 | 8d1aa152 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e 4e NNNNNNNNNNNNN 440 | 8d1aa15f 4e fe 53 4d 42 00 00 00 00 00 00 00 00 N.SMB........ 441 | ``` 442 | 再将下面SrvNet分配的pool覆盖前面的a8字节后。由于覆盖了里面用于接收数据的buffer的指针。像上面描述的0x8d1aa034这个地方的指针(笔者发懒了没有去确定)。srvnet在接收包的时候就会在固定ffdff000这个地址存入客户端发送来的数据。 443 | 0xffdff0000这个地址是什么?wrk中有定义,如下: 444 | ```c 445 | // addressed from 0xffdf0000 - 0xffdfffff are reserved for the system 446 | // begin_ntddk begin_ntosp 447 | 448 | #define KI_USER_SHARED_DATA 0xffdf0000 449 | #define SharedUserData ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA) 450 | ``` 451 | 这块内存是系统预留的,里面保存了系统的一些信息,像时钟,版本,配置之类。注意这个地址在win10下是不可以执行的。所以这个利用方法在windows10下是不行的。 452 | ```cpp 453 | win7 454 | kd> !pte ffdff000 455 | VA ffdff000 456 | PDE at C0603FF0 PTE at C07FEFF8 457 | contains 000000000018A063 contains 00000000001E3163 458 | pfn 18a ---DA--KWEV pfn 1e3 -G-DA--KWEV 459 | 460 | win10 461 | kd> !pte ffd0f000 462 | VA ffd0f000 463 | PDE at C0603FF0 PTE at C07FE878 464 | contains 0000000000616063 contains 800000000000E963 465 | pfn 616 ---DA--KWEV pfn e -G-DA--KW-V 466 | 467 | ``` 468 | 覆盖完之后是这样。 469 | ``` 470 | kd> db ffdff000 l1000 471 | ffdff000 00 00 00 00 00 00 00 00-03 00 00 00 00 00 00 00 ................ 472 | ffdff010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 473 | ffdff020 00 00 00 00 00 00 00 00-03 00 00 00 00 00 00 00 ................ 474 | ffdff030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 475 | ffdff040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 476 | ffdff050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 477 | ffdff060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 478 | ffdff070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 479 | ffdff080 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 480 | ffdff090 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 481 | ffdff0a0 b0 00 d0 ff ff ff ff ff-b0 00 d0 ff ff ff ff ff ................ 482 | ffdff0b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 483 | ffdff0c0 c0 f0 df ff c0 f0 df ff-00 00 00 00 00 00 00 00 ................ 484 | ffdff0d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 485 | ffdff0e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 486 | ffdff0f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 487 | ffdff100 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 488 | ffdff110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 489 | ffdff120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 490 | ffdff130 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 491 | ffdff140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 492 | ffdff150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 493 | ffdff160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 494 | ffdff170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 495 | ffdff180 00 00 00 00 00 00 00 00-00 00 00 00 90 f1 df ff ................ 496 | ffdff190 00 00 00 00 f0 f1 df ff-00 00 00 00 00 00 00 00 ................ 497 | ffdff1a0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 498 | ffdff1b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 499 | ffdff1c0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 500 | ffdff1d0 00 00 00 00 00 00 00 00-f0 01 d0 ff ff ff ff ff ................ 501 | ffdff1e0 00 00 00 00 00 00 00 00-00 02 d0 ff ff ff ff ff ................ 502 | ffdff1f0 00 31 c0 40 90 74 08 e8-09 00 00 00 c2 24 00 e8 .1.@.t.......$.. 503 | ffdff200 a7 00 00 00 c3 e8 01 00-00 00 eb 90 5b b9 76 01 ............[.v. 504 | ffdff210 00 00 0f 32 a3 fc ff df-ff 8d 43 17 31 d2 0f 30 ...2......C.1..0 505 | ffdff220 c3 b9 23 00 00 00 6a 30-0f a1 8e d9 8e c1 64 8b ..#...j0......d. 506 | ffdff230 0d 40 00 00 00 8b 61 04-ff 35 fc ff df ff 60 9c .@....a..5....`. 507 | ffdff240 6a 23 52 9c 6a 02 83 c2-08 9d 80 4c 24 01 02 6a j#R.j......L$..j 508 | ffdff250 1b ff 35 04 03 df ff 6a-00 55 53 56 57 64 8b 1d ..5....j.USVWd.. 509 | ffdff260 1c 00 00 00 6a 3b 8b b3-24 01 00 00 ff 33 31 c0 ....j;..$....31. 510 | ffdff270 48 89 03 8b 6e 28 6a 01-83 ec 48 81 ed 9c 02 00 H...n(j...H..... 511 | ffdff280 00 a1 fc ff df ff b9 76-01 00 00 31 d2 0f 30 fb .......v...1..0. 512 | ffdff290 e8 11 00 00 00 fa 64 8b-0d 40 00 00 00 8b 61 04 ......d..@....a. 513 | ffdff2a0 83 ec 28 9d 61 c3 e9 ef-00 00 00 b9 82 00 00 c0 ..(.a........... 514 | ffdff2b0 0f 32 48 bb f8 0f d0 ff-ff ff ff ff 89 53 04 89 .2H..........S.. 515 | ffdff2c0 03 48 8d 05 0a 00 00 00-48 89 c2 48 c1 ea 20 0f .H......H..H.. . 516 | ffdff2d0 30 c3 0f 01 f8 65 48 89-24 25 10 00 00 00 65 48 0....eH.$%....eH 517 | ffdff2e0 8b 24 25 a8 01 00 00 50-53 51 52 56 57 55 41 50 .$%....PSQRVWUAP 518 | ffdff2f0 41 51 41 52 41 53 41 54-41 55 41 56 41 57 6a 2b AQARASATAUAVAWj+ 519 | ffdff300 65 ff 34 25 10 00 00 00-41 53 6a 33 51 4c 89 d1 e.4%....ASj3QL.. 520 | ffdff310 48 83 ec 08 55 48 81 ec-58 01 00 00 48 8d ac 24 H...UH..X...H..$ 521 | ffdff320 80 00 00 00 48 89 9d c0-00 00 00 48 89 bd c8 00 ....H......H.... 522 | ffdff330 00 00 48 89 b5 d0 00 00-00 48 a1 f8 0f d0 ff ff ..H......H...... 523 | ffdff340 ff ff ff 48 89 c2 48 c1-ea 20 48 31 db ff cb 48 ...H..H.. H1...H 524 | ffdff350 21 d8 48 31 c9 b9 82 00-00 c0 0f 30 fb e8 38 00 !.H1.......0..8. 525 | ffdff360 00 00 fa 65 48 8b 24 25-a8 01 00 00 48 83 ec 78 ...eH.$%....H..x 526 | ``` 527 | 0xffdff1f1处为shellcode.最后在接收完成后,最终调到srvnet!SrvNetWskReceiveComplete.可以这么下断点。 528 | ``` 529 | srvnet!SrvNetWskReceiveComplete: 530 | 986e9569 8bff mov edi,edi 531 | 986e956b 55 push ebp 532 | 986e956c 8bec mov ebp,esp 533 | 986e956e 8b450c mov eax,dword ptr [ebp+0Ch] 534 | 986e9571 8b4818 mov ecx,dword ptr [eax+18h] 535 | 986e9574 53 push ebx 536 | 986e9575 8b581c mov ebx,dword ptr [eax+1Ch] 537 | 986e9578 56 push esi 538 | 986e9579 8b7510 mov esi,dword ptr [ebp+10h] 539 | 986e957c 57 push edi 540 | 986e957d 8b7e24 mov edi,dword ptr [esi+24h] 541 | 986e9580 50 push eax 542 | 986e9581 894d0c mov dword ptr [ebp+0Ch],ecx 543 | 986e9584 c6451300 mov byte ptr [ebp+13h],0 544 | 986e9588 ff1518106f98 call dword ptr [srvnet!_imp__IoFreeIrp (986f1018)] 545 | 986e958e 33c0 xor eax,eax 546 | 986e9590 39450c cmp dword ptr [ebp+0Ch],eax 547 | 986e9593 7553 jne srvnet!SrvNetWskReceiveComplete+0x7f (986e95e8) 548 | 549 | kd> .reload srvnet.sys 550 | kd> ba e1 srvnet!SrvNetWskReceiveComplete+0x13 ".if(poi(esi+0x24) == ffdff020) {} .else {gc}" 551 | ``` 552 | 最终调用到shellcode的调用栈为: 553 | ``` 554 | # ChildEBP RetAddr Args to Child 555 | WARNING: Frame IP not in any known module. Following frames may be wrong. 556 | 00 83f6c2e0 986ea290 00000000 00000000 00000420 0xffdff1f1 557 | 01 83f6c330 986e8204 ffdff020 00001068 00001068 srvnet!SrvNetCommonReceiveHandler+0x94 (FPO: [Non-Fpo]) 558 | 02 83f6c370 986e95db ffdff020 00000001 870cb26b srvnet!SrvNetIndicateData+0x73 (FPO: [Non-Fpo]) 559 | 03 83f6c38c 83ebaf83 00000000 02000000 019bd010 srvnet!SrvNetWskReceiveComplete+0x72 (FPO: [Non-Fpo]) 560 | 04 83f6c3d0 8998db8c 865fd020 83f6c454 89661c8a nt!IopfCompleteRequest+0x128 561 | 05 83f6c3dc 89661c8a 870cb1f8 00000000 00001068 afd!WskProTLReceiveComplete+0x5e (FPO: [Non-Fpo]) 562 | 06 83f6c454 8964d839 865fd020 00000000 8841a608 tcpip!TcpCompleteClientReceiveRequest+0x1c (FPO: [Non-Fpo]) 563 | 07 83f6c4c0 8964d8be 8841a608 8841a700 00000000 tcpip!TcpFlushTcbDelivery+0x1f6 (FPO: [Non-Fpo]) 564 | 08 83f6c4dc 8965af7f 8841a608 00000000 83f6c5d0 tcpip!TcpFlushRequestReceive+0x1c (FPO: [Non-Fpo]) 565 | 09 83f6c518 8965ae47 8841a608 8841a608 83f6c5a8 tcpip!TcpDeliverFinToClient+0x37 (FPO: [Non-Fpo]) 566 | 0a 83f6c528 896abfc1 8841a608 83f6c688 8841a608 tcpip!TcpAllowFin+0x86 (FPO: [Non-Fpo]) 567 | 0b 83f6c5a8 896aa5a5 86f8c078 8841a608 83f6c5d0 tcpip!TcpTcbCarefulDatagram+0x16f2 (FPO: [Non-Fpo]) 568 | 0c 83f6c614 8968da38 86f8c078 8841a608 00f6c688 tcpip!TcpTcbReceive+0x22d (FPO: [Non-Fpo]) 569 | 0d 83f6c67c 8968e23a 8656b9b8 86f9657c 86f965f0 tcpip!TcpMatchReceive+0x237 (FPO: [Non-Fpo]) 570 | 0e 83f6c6cc 8965dd90 86f8c078 86f9600c 00003d08 tcpip!TcpPreValidatedReceive+0x263 (FPO: [Non-Fpo]) 571 | 0f 83f6c6e0 89693396 83f6c6fc 00000011 86f96008 tcpip!TcpNlClientReceivePreValidatedDatagrams+0x15 (FPO: [Non-Fpo]) 572 | 10 83f6c704 896938dd 83f6c710 00000000 00000011 tcpip!IppDeliverPreValidatedListToProtocol+0x33 (FPO: [Non-Fpo]) 573 | 11 83f6c7a0 89698a7b 8665d918 00000000 83f79480 tcpip!IpFlcReceivePreValidatedPackets+0x479 (FPO: [Non-Fpo]) 574 | 12 83f6c7c8 83ecbb95 00000000 ee2bb116 865bab48 tcpip!FlReceiveNetBufferListChainCalloutRoutine+0xfc (FPO: [Non-Fpo]) 575 | 13 83f6c830 89698c0b 8969897f 83f6c858 00000000 nt!KeExpandKernelStackAndCalloutEx+0x132 576 | 14 83f6c86c 8951f18d 8665d002 87773900 00000000 tcpip!FlReceiveNetBufferListChain+0x7c (FPO: [Non-Fpo]) 577 | 15 83f6c8a4 8950d5be 8665eaa8 87773988 00000000 ndis!ndisMIndicateNetBufferListsToOpen+0x188 (FPO: [Non-Fpo]) 578 | 16 83f6c8cc 8950d4b2 00000000 87773988 871650e0 ndis!ndisIndicateSortedNetBufferLists+0x4a (FPO: [Non-Fpo]) 579 | 17 83f6ca48 894b8c1d 871650e0 00000000 00000000 ndis!ndisMDispatchReceiveNetBufferLists+0x129 (FPO: [Non-Fpo]) 580 | 18 83f6ca64 8950d553 871650e0 87773988 00000000 ndis!ndisMTopReceiveNetBufferLists+0x2d (FPO: [Non-Fpo]) 581 | 19 83f6ca8c 894b8c78 871650e0 87773988 00000000 ndis!ndisMIndicateReceiveNetBufferListsInternal+0x62 (FPO: [Non-Fpo]) 582 | 1a 83f6cab4 903ab7f4 871650e0 87773988 00000000 ndis!NdisMIndicateReceiveNetBufferLists+0x52 (FPO: [Non-Fpo]) 583 | 1b 83f6cafc 903aa77e 00000000 87792660 00000001 E1G60I32!RxProcessReceiveInterrupts+0x108 (FPO: [Non-Fpo]) 584 | 1c 83f6cb14 8950d89a 011e9138 00000000 83f6cb40 E1G60I32!E1000HandleInterrupt+0x80 (FPO: [Non-Fpo]) 585 | 1d 83f6cb50 894b8a0f 87792674 00792660 00000000 ndis!ndisMiniportDpc+0xe2 (FPO: [Non-Fpo]) 586 | 1e 83f6cb78 83eba696 87792674 87792660 00000000 ndis!ndisInterruptDpc+0xaf (FPO: [Non-Fpo]) 587 | 1f 83f6cbd4 83eba4f8 83f6fe20 83f79480 00000000 nt!KiExecuteAllDpcs+0xfa 588 | 20 83f6cc20 83eba318 00000000 0000000e 00000000 nt!KiRetireDpcList+0xd5 589 | 21 83f6cc24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x38 (FPO: [0,0,0]) 590 | ``` 591 | 592 | ## 关于补丁 593 | 修补前: 594 | ```cpp 595 | int __fastcall SrvOs2FeaListSizeToNt(_DWORD *a1) 596 | { 597 | int v1; // edi@1 598 | int v2; // ebx@1 599 | unsigned int v3; // esi@1 600 | unsigned int v4; // ebx@1 601 | _DWORD *v6; // [sp+Ch] [bp-Ch]@1 602 | int v7; // [sp+10h] [bp-8h]@3 603 | int v8; // [sp+14h] [bp-4h]@1 604 | 605 | v1 = 0; 606 | v6 = a1; 607 | v2 = *a1; 608 | v3 = (unsigned int)(a1 + 1); 609 | v8 = 0; 610 | v4 = (unsigned int)a1 + v2; 611 | while (v3 < v4) { 612 | if (v3 + 4 >= v4 || (v7 = *(_BYTE *)(v3 + 1) + *(_WORD *)(v3 + 2), v7 + v3 + 5 > v4)) 613 | { 614 | 615 | *(WORD*)v6 = v3 - (_DWORD)v6; 616 | return v1; 617 | } 618 | if (RtlULongAdd(&v8) < 0) return 0; v1 = v8; v3 += v7 + 5; 619 | } return v1; 620 | } 621 | 622 | int __thiscall ExecuteTransaction(int this) { 623 | //略... 624 | if ( *(_DWORD *)(v3 + 0x50) >= 1u && v10 <= 0x11 ) 625 | { 626 | v2 = SrvTransaction2DispatchTable[v10](this); //这里进入派发函数 627 | if ( BYTE1(WPP_GLOBAL_Control->Flags) >= 2u 628 | && WPP_GLOBAL_Control->Characteristics & 1 629 | && KeGetCurrentIrql() < 2u && v2 && (PDEVICE_OBJECT *)WPP_GLOBAL_Control != &WPP_GLOBAL_Control ) { WPP_SF_(WPP_GLOBAL_Control->AttachedDevice, WPP_GLOBAL_Control->CurrentIrp); 630 | } 631 | goto LABEL_104; 632 | } 633 | //goto error 634 | //略... 635 | } 636 | ``` 637 | 修复后: 638 | ```cpp 639 | int __fastcall SrvOs2FeaListSizeToNt(_DWORD *a1) 640 | { 641 | int v1; // edi@1 642 | int v2; // ebx@1 643 | unsigned int v3; // esi@1 644 | unsigned int v4; // ebx@1 645 | _DWORD *v6; // [sp+Ch] [bp-Ch]@1 646 | int v7; // [sp+10h] [bp-8h]@3 647 | int v8; // [sp+14h] [bp-4h]@1 648 | 649 | v1 = 0; 650 | v6 = a1; 651 | v2 = *a1; 652 | v3 = (unsigned int)(a1 + 1); 653 | v8 = 0; 654 | v4 = (unsigned int)a1 + v2; 655 | while (v3 < v4) { 656 | if (v3 + 4 >= v4 || (v7 = *(_BYTE *)(v3 + 1) + *(_WORD *)(v3 + 2), v7 + v3 + 5 > v4)) 657 | { 658 | 659 | *(DWORD*)v6 = v3 - (_DWORD)v6; 660 | return v1; 661 | } 662 | if (RtlULongAdd(&v8) < 0) 663 | return 0; 664 | v1 = v8; 665 | v3 += v7 + 5; 666 | } 667 | return v1; 668 | } 669 | int __thiscall ExecuteTransaction(int this) 670 | { 671 | //略... 672 | if (*(_DWORD *)(v3 + 0x50) < 2u) 673 | { 674 | _SrvSetSmbError2(0, 464, "onecore\\base\\fs\\remotefs\\smb\\srv\\srv.downlevel\\smbtrans.c"); 675 | SrvLogInvalidSmbDirect(v1, v10); 676 | goto LABEL_109; 677 | } 678 | 679 | if (v11 <= 0x11) { 680 | v2 = SrvTransaction2DispatchTable[v11](v1); if (BYTE1(WPP_GLOBAL_Control->Flags) >= 2u 681 | && WPP_GLOBAL_Control->Characteristics & 1 682 | && KeGetCurrentIrql() < 2u && v2 && (PDEVICE_OBJECT *)WPP_GLOBAL_Control != &WPP_GLOBAL_Control) { 683 | WPP_SF_(WPP_GLOBAL_Control->AttachedDevice, WPP_GLOBAL_Control->CurrentIrp); 684 | } 685 | goto LABEL_108; 686 | } 687 | //goto error 688 | //略... 689 | } 690 | ``` 691 | 修补的方法就是将修补`\*(WORD\*)v6 ==> \*(DWORD\*)v6`; 还有就是`\*(_DWORD \*)(v3 + 0x50) >= 1` 变成了 `\*(_DWORD \*)(v3 + 0x50) >= 2u` 笔者在调试的时候发现触发漏洞的正好是1。 692 | 693 | 由于作者水平有限,有什么错误欢迎大家指出 694 | 联系作者:[pgboy1988](http://weibo.com/pgboy1988) 695 | 696 | [dump.bin](https://pastebin.com/RAgsEEiZ) 697 | 698 | 699 | 700 | 701 | -------------------------------------------------------------------------------- /EternalBlue/test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | signed int RtlULongAdd(unsigned int a1, int a2, unsigned int *a3) 6 | { 7 | unsigned int v3; // edx@1 8 | signed int result; // eax@2 9 | 10 | v3 = a1 + a2; 11 | if (v3 < a1) 12 | { 13 | *a3 = -1; 14 | result = -1073741675; 15 | } else 16 | { 17 | *a3 = v3; 18 | result = 0; 19 | } 20 | return result; 21 | } 22 | 23 | unsigned int SrvOs2FeaListSizeToNt(PUCHAR pOs2Fea) 24 | { 25 | unsigned int v1; // edi@1 26 | int Length; // ebx@1 27 | PUCHAR pBody; // esi@1 28 | PUCHAR v4; // ebx@1 29 | int v5; // ecx@3 30 | int v8; // [sp+10h] [bp-8h]@3 31 | unsigned int v9; // [sp+14h] [bp-4h]@1 32 | 33 | v1 = 0; 34 | Length = *(DWORD*)pOs2Fea; 35 | pBody = pOs2Fea + 4; 36 | v9 = 0; 37 | v4 = pOs2Fea + Length; 38 | while (pBody < v4) 39 | { 40 | if (pBody + 4 >= v4 41 | || (v5 = *(BYTE *)(pBody + 1) + *(WORD *)(pBody + 2), 42 | v8 = *(BYTE *)(pBody + 1) + *(WORD *)(pBody + 2), 43 | v5 + pBody + 5 > v4)) 44 | { 45 | *(WORD *)pOs2Fea = pBody - pOs2Fea; 46 | return v1; 47 | } 48 | if (RtlULongAdd(v1, (v5 + 0xC) & 0xFFFFFFFC, &v9) < 0) 49 | return 0; 50 | v1 = v9; 51 | pBody += v8 + 5; 52 | } 53 | return v1; 54 | } 55 | 56 | PUCHAR gpBuffer = NULL; 57 | ULONG guSize = 0; 58 | 59 | PUCHAR SrvOs2FeaToNt(PUCHAR pNtFea, PUCHAR pOs2FeaBody) 60 | { 61 | PUCHAR pBody; // edi@1 62 | BYTE *pNtBodyStart; // edi@1 63 | PUCHAR result; // eax@1 64 | 65 | pBody = pNtFea + 8; 66 | *(BYTE *)(pNtFea + 4) = *(BYTE *)pOs2FeaBody; 67 | *(BYTE *)(pNtFea + 5) = *(BYTE *)(pOs2FeaBody + 1); 68 | *(WORD *)(pNtFea + 6) = *(WORD *)(pOs2FeaBody + 2); 69 | 70 | 71 | memcpy((void *)(pNtFea + 8), (const void *)(pOs2FeaBody + 4), *(BYTE *)(pOs2FeaBody + 1)); 72 | pNtBodyStart = (BYTE *)(*(BYTE *)(pNtFea + 5) + pBody); 73 | *pNtBodyStart++ = 0; 74 | 75 | if ((pNtBodyStart + *(WORD *)(pNtFea + 6)) > (gpBuffer + guSize)){ 76 | __debugbreak(); 77 | } 78 | memcpy(pNtBodyStart, (const void *)(pOs2FeaBody + 5 + *(BYTE *)(pNtFea + 5)), *(WORD *)(pNtFea + 6)); 79 | result = (PUCHAR)((ULONG_PTR)&pNtBodyStart[*(WORD *)(pNtFea + 6) + 3] & 0xFFFFFFFC); 80 | *(DWORD *)pNtFea = result - pNtFea; 81 | return result; 82 | } 83 | 84 | int main() 85 | { 86 | 87 | FILE* pFile = fopen("d:\\test\\dump.bin", "r+b"); 88 | fseek(pFile, 0, SEEK_END); 89 | ULONG uSize = (ULONG)ftell(pFile); 90 | fseek(pFile, 0, SEEK_SET); 91 | PUCHAR pOs2Fea = (PUCHAR)malloc(uSize); 92 | fread(pOs2Fea, 1, uSize, pFile); 93 | fclose(pFile); 94 | 95 | ULONG uSaveSize = *(DWORD*)pOs2Fea; 96 | ULONG uFixSize = SrvOs2FeaListSizeToNt(pOs2Fea); 97 | 98 | PUCHAR pOs2FeaBody; 99 | PUCHAR pNtFea = (PUCHAR)malloc(uFixSize); 100 | PUCHAR v10; 101 | PUCHAR v14; 102 | PUCHAR v12; 103 | PUCHAR v5; 104 | LONG v11; 105 | 106 | PUCHAR pNtFeaEnd = pNtFea + uFixSize; 107 | 108 | gpBuffer = pNtFea; 109 | guSize = uFixSize; 110 | 111 | if (pNtFea) 112 | { 113 | pOs2FeaBody = pOs2Fea + 4; 114 | v10 = pNtFea; 115 | v14 = pOs2Fea + *(DWORD *)pOs2Fea - 5; 116 | if (pOs2Fea + 4 > v14) 117 | { 118 | LABEL_13: 119 | if (pOs2FeaBody == pOs2Fea + *(DWORD *)pOs2Fea) 120 | { 121 | *(DWORD *)v10 = 0; 122 | return 0; 123 | } 124 | v11 = 0xC0000001; 125 | //*a4 = v5 - pOs2Fea; 126 | } else{ 127 | while (!(*(BYTE *)pOs2FeaBody & 0x7F)) 128 | { 129 | v12 = pNtFea; 130 | v5 = pOs2FeaBody; 131 | pNtFea = SrvOs2FeaToNt(pNtFea, pOs2FeaBody); 132 | pOs2FeaBody += *(BYTE *)(pOs2FeaBody + 1) + *(WORD *)(pOs2FeaBody + 2) + 5; 133 | if (pOs2FeaBody > v14) 134 | { 135 | v10 = v12; 136 | goto LABEL_13; 137 | } 138 | } 139 | //*a4 = pOs2FeaBody - pOs2Fea; 140 | v11 = 0xC000000D; 141 | } 142 | return v11; 143 | } 144 | 145 | return 0; 146 | } 147 | 148 | -------------------------------------------------------------------------------- /Etneralromance/3.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/3.1.png -------------------------------------------------------------------------------- /Etneralromance/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/5.png -------------------------------------------------------------------------------- /Etneralromance/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/6.png -------------------------------------------------------------------------------- /Etneralromance/Eternalromance (永恒浪漫) 漏洞分析.md: -------------------------------------------------------------------------------- 1 | # Eternalromance (永恒浪漫) 漏洞分析 2 | 3 | ------------------------------ 4 | 5 | [TOC] 6 | 7 | ## 1 环境 8 | 9 | **EXPLOIT:** 10 | Eternalromance-1.3.0 11 | 12 | **TARGET:** 13 | windows xp sp3 14 | 15 | **FILE:** 16 | srv.sys 5.1.2600.5512 17 | 18 | ## 2 Exploit使用 19 | 我们可以发现工具包中有两个Eternalromance, 一个1.4.0, 另外一个是1.3.0。经过我一翻折腾也没有把1.4.0跑起来。无奈试了下1.3.0发现竟然能成功运行。因此便有了这篇分析。大家可能都会用fuzzbunch这个命令行了。但是我们调试的时候总不能调一次都要重新输入TargetIp等那些配置吧。告诉大家一个省劲的方法。首先fuzzbunch按正常流程走一遍。在最后跑起exp的时候,在Log目录下会生成一个xml的配置文件。然后大家就可以用Eternalromance.1.3.0 --inconfig "xml路径" 来调用了。还有就是你也可以改exploit下的那个配置文件不过你得填非常多的参数。 20 | 21 | ## 3 基础知识 22 | 23 | 不想看的同学可以直接跳到 [漏洞相关的重点](#jump_zzz)那里 24 | 25 | ### 3.1 SMB Message structure 26 | SMB Messages are divisible into three parts: 27 | * fixed-length header 28 | * variable length parameter block 29 | * variable length data block 30 | 31 | header的结构如下: 32 | ```cpp 33 | SMB_Header 34 | { 35 | UCHAR Protocol[4]; 36 | UCHAR Command; 37 | SMB_ERROR Status; 38 | UCHAR Flags; 39 | USHORT Flags2; 40 | USHORT PIDHigh; 41 | UCHAR SecurityFeatures[8]; 42 | USHORT Reserved; 43 | USHORT TID; 44 | USHORT PIDLow; 45 | USHORT UID; 46 | USHORT MID; 47 | } 48 | ``` 49 | 更详细见 (https://msdn.microsoft.com/en-us/library/ee441702.aspx) 50 | 51 | ### 3.2 SMB_COM_TRANSACTION (0x25) 52 | 为事务处理协议的传输子协议服务。这些命令可以用于CIFS文件系统内部通信的邮箱和命名管道。如果出书的数据超过了会话建立时规定的MaxBufferSize,必须使用SMB_COM_TRANSACTION_SECONDARY命令来传输超出的部分:SMB_Data.Trans_Data和SMB_Data.Trans_Parameter。这两部分在初始化消息中没有固定。 53 | 如果客户端没有发送完所有的SMB_Data.Trans_Data,会将DataCount设置为小于TotalDataCount的一个值。同样的,如果SMB_Data.Trans_Parameters没有发送完,会设置ParameterCount为一个小于TotalParameterCount的值。参数部分优先级高于数据部分,客户端在每个消息中应该尽量多的发送数据。服务器应该可以接收无序到达的SMB_Data.Trans_Parameters 和 SMB_Data.Trans_Data,不论是大量还是少量的数据。 54 | 在请求和响应消息中,SMB_Data.Trans_Parameters和SMB_Data.Trans_Data的位置和长度都是由SMB_Parameters.ParameterOffset、SMB_Parameters.ParameterCount,SMB_Parameters.DataOffset和SMB_Parameters.DataCount决定。另外需要说明的是,SMB_Parameters.ParameterDisplacement和SMB_Parameters.DataDisplacement可以用来改变发送数据段的序号。服务器应该优先发送SMB_Data.Trans_Parameters。客户端应该准备好组装收到的SMB_Data.Trans_Parameters和SMB_Data.Trans_Data,即使它们是乱序到达的。 55 | The PID, MID, TID, and UID MUST be the same for all requests and responses that are part of the same transaction. 56 | 57 | 更详细看 (https://msdn.microsoft.com/en-us/library/ee441489.aspx) 58 | 59 | ### 3.3 SMB_COM_TRANSACTION_SECONDARY(0x26) 60 | 此命令用来完成SMB_COM_TRANSACTION中未传输完毕数据的传输。在请求和响应消息中,SMB_Data.Trans_Parameters和SMB_Data.Trans_Data的位置和长度都是由SMB_Parameters.ParameterOffset、SMB_Parameters.ParameterCount,SMB_Parameters.DataOffset和SMB_Parameters.DataCount决定。另外需要说明的是,SMB_Parameters.ParameterDisplacement和SMB_Parameters.DataDisplacement可以用来改变发送数据段的序号。服务器应该优先发送SMB_Data.Trans_Parameters。客户端应该准备好组装收到的SMB_Data.Trans_Parameters和SMB_Data.Trans_Data,即使它们是乱序到达的。 61 | 62 | 更详细看 (https://msdn.microsoft.com/en-us/library/ee441949.aspx) 63 | 64 | ### 3.4 SMB_COM_WRITE_ANDX (0x2F) 65 | 66 | 结构如图: 67 | ![ANDX_STRUCTURE](.//andx.JPG) 68 | 69 | 更详细看 (https://msdn.microsoft.com/en-us/library/ee441848.aspx) 70 | 71 | ### 3.5 总结下漏洞相关的重点 72 | 73 | 1. 客户端处理SMB_COM_TRANSACTION命令的时候如果数据大小超过MaxBufferSize,则需要使用SMB_COM_TRANSACTION_SECONDARY传输剩下的数据。 74 | 2. 对于作为同一Transcation部分的所有请求和响应,PID,MID,TID和UID必须相同。 75 | 76 | ## 4 漏洞分析 77 | 78 | ### 4.1 SrvSmbTransactionSecondary 79 | 结合上一节的重点中提到的。 80 | 如果我们先发送了一个数据大小大于MaxBufferSize的SMB_COM_TRANSACTION数据包。那么接下来肯定是要发送SMB_COM_TRANSACTION_SECONDARY来传输剩余的数据,那么服务器在收到处理SMB_COM_TRANSACTION_SECONDARY这个命令的时候肯定会找他对应的那个Transcation。同时服务器也可能同时收到很多个包含SMB_COM_TRANSACTION_SECONDARY命令的数据包。怎么定位某个MB_COM_TRANSACTION_SECONDARY数据包对应的SMB_COM_TRANSACTION数据包呢?重点里也提到了。如果MB_COM_TRANSACTION_SECONDARY数据包中的PID,MID,TID和UID和SMB_COM_TRANSACTION数据包中的相同,那么就认为是同一部分的请求。 81 | 代码是这么实现的。 82 | 83 | ```cpp 84 | int __thiscall SrvSmbTransactionSecondary(int this) 85 | { 86 | int v1; // ebx@1 87 | int pSmbParamter; // edi@1 88 | TRANSCATION *pTransation; // eax@1 MAPDST 89 | unsigned int ParameterDisplayment; // ecx@10 MAPDST 90 | size_t ParameterSize; // eax@10 MAPDST 91 | size_t DataSize; // edx@10 MAPDST 92 | int pTransList; // [sp+Ch] [bp-24h]@1 93 | PERESOURCE Resource; // [sp+18h] [bp-18h]@26 94 | struct _ERESOURCE *Resourcea; // [sp+18h] [bp-18h]@30 95 | int pSmbHeader; // [sp+1Ch] [bp-14h]@1 96 | int ParameterOffset; // [sp+20h] [bp-10h]@10 97 | int DataOffset; // [sp+28h] [bp-8h]@10 98 | 99 | v1 = this; 100 | pSmbParamter = *(_DWORD *)(this + 0x6C); 101 | pSmbHeader = *(_DWORD *)(this + 0x68); 102 | pTransList = *(_DWORD *)(this + 0x4C); 103 | 104 | // 105 | // 首先查找SMB_COM_TRANSACTION_SECONDARY对应的SMB_COM_TRANSACTION 106 | // 107 | 108 | pTransation = (TRANSCATION *)SrvFindTransaction(pTransList, pSmbHeader, 0); 109 | if ( !pTransation ) 110 | { 111 | return 2; 112 | } 113 | if ( !*(_BYTE *)(pTransation->field_10 + 0x98) ) 114 | { 115 | ParameterDisplayment = *(_WORD *)(pSmbParamter + 9); 116 | ParameterOffset = *(_WORD *)(pSmbParamter + 7); 117 | ParameterSize = *(_WORD *)(pSmbParamter + 5); 118 | DataOffset = *(_WORD *)(pSmbParamter + 0xD); 119 | DataSize = *(_WORD *)(pSmbParamter + 0xB); 120 | DataDisplayment = *(_WORD *)(pSmbParamter + 0xF); 121 | if ( pTransation->field_93 ) 122 | { 123 | //... 124 | } 125 | else 126 | { 127 | //Check 128 | Resource = *(PERESOURCE *)(*(_DWORD *)(v1 + 0x60) + 0x10); 129 | if ( ParameterSize + ParameterOffset > *(_DWORD *)(*(_DWORD *)(v1 + 0x60) + 0x10) 130 | || DataOffset + DataSize > (unsigned int)Resource 131 | || ParameterSize + ParameterDisplayment > pTransation->TotalParameterCount 132 | || DataSize + DataDisplayment > pTransation->TotalDataCount ) 133 | { 134 | //CheckFaild 135 | } 136 | else 137 | { 138 | if ( pTransation->field_94 != 1 ) 139 | { 140 | // 141 | // 这里将SMB_COM_TRANSACTION_SECONDARY传过来的Parameter和Data都保存 142 | // 到Transaction中 143 | // 144 | 145 | //拷贝Parameter Buffer 146 | if ( ParameterSize ) 147 | _memmove( 148 | (void *)(ParameterDisplayment + pTransation->ParameterBuffer), 149 | (const void *)(pSmbHeader + ParameterOffset), 150 | ParameterSize); // parameter 151 | 152 | //复制Data Buffer,这里注意下,下面会提到!! 153 | if ( DataSize ) 154 | _memmove( 155 | (void *)(DataDisplayment + pTransation->DataBuffer), 156 | (const void *)(pSmbHeader + DataOffset), 157 | DataSize); // data 158 | 159 | return 2; 160 | } 161 | } 162 | } 163 | } 164 | return 1; 165 | } 166 | ``` 167 | 168 | 这个SMB_COM_TRANSACTION_SECONDARY命令的处理函数的大体流程就是首先查找SMB_COM_TRANSACTION_SECONDARY对应的SMB_COM_TRANSACTION如果找到就将SMB_COM_TRANSACTION_SECONDARY中的Parameter和Data都复制到SMB_COM_TRANSACTION中去。 169 | 170 | ### 4.2 SrvFindTransaction 171 | 下面来看下查找的逻辑`SrvFindTransaction`: 172 | 173 | ```cpp 174 | int __stdcall SrvFindTransaction(int a1, int pSmbHeaderOrMid, int a3) 175 | { 176 | SMB_HEADER *pSmbHeader_; // edi@1 177 | struct _ERESOURCE *v4; // ebx@4 178 | _DWORD **pTransList; // edx@4 179 | _DWORD *i; // ecx@4 180 | TRANSCATION *v7; // eax@5 181 | int v9; // esi@14 182 | 183 | pSmbHeader_ = (SMB_HEADER *)pSmbHeaderOrMid; 184 | 185 | // 186 | // command 0x2f is SMB_CMD_WRITE_ANDX 187 | // a3 = SMB_CMD_WRITE_ANDX->Fid 188 | // 189 | 190 | if ( *(_BYTE *)(pSmbHeaderOrMid + 4) == 0x2F ) 191 | pSmbHeaderOrMid = a3; 192 | else 193 | LOWORD(pSmbHeaderOrMid) = *(_WORD *)(pSmbHeaderOrMid + 0x1E); 194 | v4 = (struct _ERESOURCE *)(a1 + 0x130); 195 | ExAcquireResourceExclusiveLite((PERESOURCE)(a1 + 0x130), 1u); 196 | pTransList = (_DWORD **)(*(_DWORD *)(a1 + 0xF4) + 8); 197 | for ( i = *pTransList; ; i = (_DWORD *)*i ) 198 | { 199 | if ( i == pTransList ) 200 | { 201 | // 202 | // 查到最后了退出 203 | // 204 | 205 | ExReleaseResourceLite(v4); 206 | return 0; 207 | } 208 | 209 | // 210 | // 这里是对比TID,PID,UID,MID 211 | // 这里注意这个MID,如果命令是SMB_CMD_WRITE_ANDX MID就是SMB_CMD_WRITE_ANDX MID数据包中的Fid 212 | // 213 | 214 | v7 = (TRANSCATION *)(i - 6); 215 | if ( *((_WORD *)i + 47) == pSmbHeader_->TID 216 | && v7->ProcessId == pSmbHeader_->PIDLow 217 | && v7->UserId == pSmbHeader_->UID 218 | && v7->MutiplexId == (_WORD)pSmbHeaderOrMid )// MutilplexId如果名令时SMB_CMD_WRITE_ANDX那么这里是Fid 219 | { 220 | break; 221 | } 222 | } 223 | if ( BYTE1(v7->field_0) == 2 ) 224 | { 225 | _InterlockedExchangeAdd((volatile signed __int32 *)(v7->field_8 + 4), 1u); 226 | v9 = (int)(i - 6); 227 | } 228 | else 229 | { 230 | v9 = 0; 231 | } 232 | ExReleaseResourceLite(v4); 233 | return v9; 234 | } 235 | 236 | ``` 237 | 238 | 大家可以看下逻辑。重点在这里如果命令是SMB_CMD_WRITE_ANDX(0x2f)话那么MID的对比就不一样了,这里是用Transaction->MID和SMB_CMD_WRITE_ANDX->Fid比较。如果一样返回pTransaction。那么问题来了。如果服务器端正好有一个其他的Transaction->MID恰好和SMB_CMD_WRITE_ANDX->Fid的相等那么将会返回一个错误的pTransaction。很经典的类型混淆。 239 | 240 | 处理SMB_CMD_WRITE_ANDX的函数SrvSmbWriteAndX代码如下: 241 | ```cpp 242 | signed int __thiscall SrvSmbWriteAndX(int this) 243 | { 244 | ...略 245 | pTrans = (TRANSCATION *)SrvFindTransaction(pTransList, pSmbTranscationBuffer, Fid); 246 | pTrans_ = pTrans; 247 | if ( !pTrans ) 248 | { 249 | if ( (unsigned int)SrvWmiEnableLevel >= 2 && SrvWmiEnableFlags & 1 && KeGetCurrentIrql() < 2u ) 250 | WPP_SF_(64, &unk_1E1CC); 251 | v40 = "d:\\xpsp\\base\\fs\\srv\\smbrdwrt.c"; 252 | v37 = 3216; 253 | goto LABEL_69; 254 | } 255 | if ( pTrans->TotalDataCount - pTrans->nTransDataCounts < v11 ) 256 | { 257 | SrvCloseTransaction(pTrans); 258 | SrvDereferenceTransaction(pTrans_); 259 | _SrvSetSmbError2(v1, 0x80000005, 0, 3238, (int)"d:\\xpsp\\base\\fs\\srv\\smbrdwrt.c"); 260 | StatusCode = 0x80000005; 261 | goto LABEL_100; 262 | } 263 | v31 = Size; 264 | qmemcpy((void *)pTrans->DataBuffer, VirtualAddress, Size); 265 | 266 | // 267 | // !!!!这里将DataBuffer指针给增加了!!!! 268 | // 269 | 270 | pTrans_->DataBuffer += Size; 271 | pTrans_->nTransDataCounts += v31; 272 | ...略 273 | } 274 | ``` 275 | 276 | 看到`pTrans_->DataBuffer += Size`这句相信大家就能明白了。这里将DataBuffer的指针增大了。再处理此Transcation的SMB_COM_TRANSACTION_SECONDARY命令的时候也就是**SrvSmbTransactionSecondary中复制Data的`memcpy`可就越界了!!!!!** 277 | 所以此漏洞可以总结成类型混淆造成的越界写。 278 | 279 | ### 4.3 抓包Demo 280 | 通过对Exploit抓包我们可以看到其漏洞触发过程。 281 | 282 | 首先发送SMB_COM_TRANSACTION命令创建一个TID=2049 PID=0 UID=2049 MID=16385(0x4001)的Transcation: 283 | 284 | ![SMB_COM_TRANSACTION](./wireshark.JPG) 285 | 286 | 然后发送SMB_CMD_WRITE_ANDX命令还增加`pTrans_->DataBuffer`这个指针: 287 | 288 | ![SMB_CMD_WRITE_ANDX](./wirshark1.PNG) 289 | 290 | ## 5 漏洞利用 291 | 从上面描述可以看出,该漏洞为类型混淆导致的越界写漏洞。前期通过spray 使多个TRANSACTION相邻,然后让其中一个TRANSACTION触发该漏洞,再通过该TRANSACTION越界写其相邻的TRANSACTION
292 | 293 | spary 最终 memory view: 294 | ![spray 最终状态](./3.1.png) 295 | 296 | spray的目的是构造出下出三个相邻的transaction, 其中`write_transaction` 主要用于写操作,leak_transaction 主要用于信息泄露,而`control_transaction`为触发漏洞的transaction,触发之后其他`InData`字段会增加0x200, 以致于可写的范围可以向后延伸0x200.利用于这点可以写与其相依的`write_transaction`的`InData`字段。从面达到任意地址写的效果。 297 | 298 | 注: 本次调试中 control_transaction地址为:0x58b90, write_transaction地址为: 0x59c38, leak_transaction地址为:0x5ace0 299 | 300 | 其中TRANSACTION 结构部份如下: 301 | ```c 302 | typedef struct _TRANSACTION { 303 | //... 304 | /*+0xc*/ LPVOID Connection; 305 | //... 306 | /*+0x34*/ LPWORD InSetup; 307 | //... 308 | /*+0x40*/ LPBYTE OutParameters; 309 | /*+0x44*/ LPBYTE InData; /*写的目的地址*/ 310 | /*+0x48*/ LPBYTE OutData; /*读的目的地址*/ 311 | //... 312 | /*+0x60*/ DWORD DataCount; /*控制可读的长度*/ 313 | /*+0x64*/ DWORD TotalDataCount 314 | }TRANSACTION 315 | ``` 316 | 写操作:
317 | ```c 318 | SMB_PROCESSOR_RETURN_TYPE SrvSmbTransactionSecondary ( 319 | SMB_PROCESSOR_PARAMETERS 320 | ) 321 | { 322 | //... 323 | request = (PREQ_TRANSACTION_SECONDARY)WorkContext->RequestParameters; 324 | header = WorkContext->RequestHeader; 325 | transaction = SrvFindTransaction( connection, header, 0 ); 326 | //... 327 | dataOffset = SmbGetUshort( &request->DataOffset ); 328 | dataCount = SmbGetUshort( &request->DataCount ); 329 | dataDisplacement = SmbGetUshort( &request->DataDisplacement ); 330 | //... 331 | 332 | // Copy the parameter and data bytes that arrived in this SMB. 333 | //... 334 | if ( dataCount != 0 ) { 335 | RtlMoveMemory( 336 | transaction->InData + dataDisplacement, 337 | (PCHAR)header + dataOffset, 338 | dataCount); 339 | } 340 | //... 341 | } 342 | ``` 343 | 读操作: 344 | ``` 345 | VOID SrvCompleteExecuteTransaction ( 346 | IN OUT PWORK_CONTEXT WorkContext, 347 | IN SMB_TRANS_STATUS ResultStatus) 348 | { 349 | //... 350 | transaction = WorkContext->Parameters.Transaction; 351 | header = WorkContext->ResponseHeader; 352 | transactionCommand = (UCHAR)SmbGetUshort( &transaction->InSetup[0] ); 353 | //... 354 | 355 | // Copy the appropriate parameter and data bytes into the message. 356 | if ( paramLength != 0 ) { 357 | RtlMoveMemory( paramPtr, transaction->OutParameters, paramLength ); 358 | } 359 | 360 | if ( dataLength != 0 ) { 361 | RtlMoveMemory( dataPtr, transaction->OutData, dataLength ); 362 | } 363 | //... 364 | } 365 | ``` 366 | 367 | 从exp运行的log可以看出该漏洞利用分为两部份:信息泄露 与 代码执行 368 | ![Alt text](./6.png) 369 | 370 | 371 | ### 5.1 信息泄露 372 | 需要泄露的信息包括 `Transaction2Dispatch` Table基址 与 一块NonePagedPool Buffer地址. 通过修改`Transaction2Dispatch` Table中的函数指针来执行 shellcode, 其中NonePagedPool Buffer就是用于存放shellcode.
373 | 374 | #### 5.1.1 泄露Transaction2Dispatch Table基址 375 | 从exp运行的log可以看出首先泄露CONNECTION结构基址:CONNECTION地址存放于`TRANSACTION->Connection`字段。看到这,你可能已经想到该怎么做了:直接利用`constrol transaction`的0x200字节的越界能力修改`write_transaction`的`DataCount`字段让其可以越界读`leak_transaction`上的内容,从而读出`TRANSACTION->Connection`。 但exp作者却并没有这么做,这里并不打算深究其原因,或许是有其他限制,或许不是。 376 | 作者这里利用了另一种复杂不少方法,通过另一种方法修改 `write_transaction`的`DataCount`字段。 377 | 378 | ##### 5.1.1.1 write_transaction初始状态如下: 379 | 380 | ![Alt text](./5.png) 381 | 382 | 可以看出CONNECTION地址为:`89a29c18`,`OutData为0x5acd4 (== 59c38+0x109c)`已经是`write_transaction`的末尾,所以其`DataCount`为0,表示不可读。 383 | ##### 5.1.1.2 修改write_transaction->DataCount 384 | 首先 修改`write_transaction`的InSetup为0x23,这点通过`control_transaction`很容易完成。之后发包触发写`write_transaction`操作,会走到: 385 | ```c 386 | SMB_STATUS SRVFASTCALL ExecuteTransaction ( 387 | IN OUT PWORK_CONTEXT WorkContext) 388 | { 389 | //... 390 | transaction = WorkContext->Parameters.Transaction; 391 | //... 392 | command = SmbGetUshort(&transaction->InSetup[0]); 393 | //... 394 | switch( command ) { 395 | 396 | case TRANS_TRANSACT_NMPIPE: 397 | resultStatus = SrvTransactNamedPipe( WorkContext ); 398 | break; 399 | 400 | case TRANS_PEEK_NMPIPE: //0x23 401 | resultStatus = SrvPeekNamedPipe( WorkContext ); 402 | break; 403 | 404 | case TRANS_CALL_NMPIPE: 405 | resultStatus = SrvCallNamedPipe( WorkContext ); 406 | break; 407 | //... 408 | } 409 | //... 410 | } 411 | ``` 412 | 由于之前已经将`write_transaction`的`InSetup`修改为0x23, 所以会`call SrvPeekNamedPipe`。 413 | ```c 414 | # ChildEBP RetAddr 415 | 00 b21f8d44 b24cdcce srv!ExecuteTransaction+0x23b (FPO: [0,0,0]) 416 | 01 b21f8d7c b248a836 srv!SrvSmbTransactionSecondary+0x2f1 (FPO: [Non-Fpo]) 417 | 02 b21f8d88 b249ad98 srv!SrvProcessSmb+0xb7 (FPO: [0,0,0]) 418 | 03 b21f8dac 805c7160 srv!WorkerThread+0x11e (FPO: [Non-Fpo]) 419 | 04 b21f8ddc 80542dd2 nt!PspSystemThreadStartup+0x34 (FPO: [Non-Fpo]) 420 | 05 00000000 00000000 nt!KiThreadStartup+0x16 421 | ``` 422 | `SrvPeekNamedPipe()` 调用`IoCallDriver`最终调到 `RestartPeekNamedPipe()`函数
423 | ```c 424 | VOID SRVFASTCALL RestartPeekNamedPipe ( 425 | IN OUT PWORK_CONTEXT WorkContext) 426 | { 427 | //... 428 | 429 | // 430 | // Success. Generate and send the response. 431 | // 432 | transaction = WorkContext->Parameters.Transaction; 433 | pipePeekBuffer = (PFILE_PIPE_PEEK_BUFFER)transaction->OutParameters; 434 | 435 | readDataAvailable = (USHORT)pipePeekBuffer->ReadDataAvailable; 436 | messageLength = (USHORT)pipePeekBuffer->MessageLength; 437 | namedPipeState = (USHORT)pipePeekBuffer->NamedPipeState; 438 | 439 | // 440 | // ... then copy them back in the new format. 441 | // 442 | respPeekNmPipe = (PRESP_PEEK_NMPIPE)pipePeekBuffer; 443 | SmbPutAlignedUshort( 444 | &respPeekNmPipe->ReadDataAvailable, 445 | readDataAvailable 446 | ); 447 | SmbPutAlignedUshort( 448 | &respPeekNmPipe->MessageLength, 449 | messageLength 450 | ); 451 | SmbPutAlignedUshort( 452 | &respPeekNmPipe->NamedPipeState, 453 | namedPipeState 454 | ); 455 | 456 | // 457 | // Send the response. Set the output counts. 458 | // 459 | // NT return to us 4 ULONGS of parameter bytes, followed by data. 460 | // We return to the client 6 parameter bytes. 461 | // 462 | transaction->SetupCount = 0; 463 | transaction->ParameterCount = 6; 464 | transaction->DataCount = WorkContext->Irp->IoStatus.Information - (4 * sizeof(ULONG)); 465 | //... 466 | } 467 | ``` 468 | 该函数最终会修改`Transaction->DataCount` 为 0x23c。 469 | ```c 470 | kd> ub 471 | srv!RestartPeekNamedPipe+0x42: 472 | b7700137 66895004 mov word ptr [eax+4],dx 473 | b770013b 83614c00 and dword ptr [ecx+4Ch],0 474 | b770013f c7415406000000 mov dword ptr [ecx+54h],6 475 | b7700146 8b4678 mov eax,dword ptr [esi+78h] 476 | b7700149 8b401c mov eax,dword ptr [eax+1Ch] 477 | b770014c 83e810 sub eax,10h 478 | b770014f 85ff test edi,edi 479 | b7700151 894160 mov dword ptr [ecx+60h],eax 480 | 481 | kd> ?ecx+0x60 482 | Evaluate expression: 367768 = 00059c98 483 | 484 | kd> r eax 485 | eax=0000023c 486 | kd> dd 00059c38 487 | 00059c38 109c020c 00000000 ffdff500 89a29c18 488 | 00059c48 e1ed9900 e15e2960 0005acf8 00058ba8 489 | 00059c58 00020000 00059cd0 00002307 00000001 490 | 00059c68 00000000 00059cd4 8993b3e5 00059cd4 491 | 00059c78 00059d14 ffdff500 0005acd4 00000000 492 | 00059c88 00000000 00000006 00000000 00000ff0 493 | 00059c98 0000023c 00000000 00000001 00000000 494 | 00059ca8 00000101 08000000 0800cee6 00000000 495 | 496 | kd> k 497 | # ChildEBP RetAddr 498 | 00 b7c0ed88 b76dad98 srv!RestartPeekNamedPipe+0x5f 499 | 01 b7c0edac 805c6160 srv!WorkerThread+0x11e 500 | 02 b7c0eddc 80541dd2 nt!PspSystemThreadStartup+0x34 501 | 03 00000000 00000000 nt!KiThreadStartup+0x16 502 | ``` 503 | 至此,已经成功修改了`write_transaction`的`DataCount`值,之后便可以越界读出 `leak_transacion->Connection`值: 89a29c18。 504 | ##### 5.1.1.3 SRV global data pointer 505 | ```c 506 | kd> dds 89a29c18 507 | 89a29c18 02580202 508 | 89a29c1c 0000001d 509 | 89a29c20 00000000 510 | 89a29c24 00000000 511 | 89a29c28 00000000 512 | 89a29c2c 00000000 513 | 89a29c30 00000000 514 | 89a29c34 00000000 515 | 89a29c38 00000000 516 | 89a29c3c b76d8bec srv!SrvGlobalSpinLocks+0x3c 517 | 89a29c40 899d0020 518 | 89a29c44 000005b3 519 | 89a29c48 8976c200 520 | 89a29c4c 00004000 521 | 89a29c50 10000100 522 | 89a29c54 00000000 523 | 89a29c58 8988e010 524 | 89a29c5c 89aedc30 525 | 89a29c60 89a7d898 526 | 89a29c64 0001ffff 527 | ``` 528 | 其中`srv!SrvGlobalSpinLocks+0x3c` 就是所谓的 SRV global data pointer :b76d8bec 529 | ##### 5.1.1.4 Locating function tables 530 | ```c 531 | kd> dds b76d8bec-0x654 532 | b76d8598 b7709683 srv!SrvSmbOpen2 533 | b76d859c b76f62a8 srv!SrvSmbFindFirst2 534 | b76d85a0 b76f74e5 srv!SrvSmbFindNext2 535 | b76d85a4 b76f6309 srv!SrvSmbQueryFsInformation 536 | b76d85a8 b7707293 srv!SrvSmbSetFsInformation 537 | b76d85ac b77041ad srv!SrvSmbQueryPathInformation 538 | b76d85b0 b7703ce7 srv!SrvSmbSetPathInformation 539 | b76d85b4 b77025ad srv!SrvSmbQueryFileInformation 540 | b76d85b8 b770367f srv!SrvSmbSetFileInformation 541 | b76d85bc b7705c85 srv!SrvSmbFsctl 542 | b76d85c0 b7706419 srv!SrvSmbIoctl2 543 | b76d85c4 b7705c85 srv!SrvSmbFsctl 544 | b76d85c8 b7705c85 srv!SrvSmbFsctl 545 | b76d85cc b77047bb srv!SrvSmbCreateDirectory2 546 | b76d85d0 b7709a51 srv!SrvTransactionNotImplemented 547 | b76d85d4 b7709a51 srv!SrvTransactionNotImplemented 548 | b76d85d8 b76fb144 srv!SrvSmbGetDfsReferral 549 | b76d85dc b76faf7e srv!SrvSmbReportDfsInconsistency 550 | b76d85e0 00000000 551 | ``` 552 | #### 5.1.2 泄露Npp Buffer (shellcode buffer) 553 | 这里又得回到`ExecuteTransaction`函数: 554 | ```c 555 | SMB_STATUS SRVFASTCALL ExecuteTransaction ( 556 | IN OUT PWORK_CONTEXT WorkContext) 557 | { 558 | //... 559 | header = WorkContext->ResponseHeader; 560 | response = (PRESP_TRANSACTION)WorkContext->ResponseParameters; 561 | ntResponse = (PRESP_NT_TRANSACTION)WorkContext->ResponseParameters; 562 | 563 | // 564 | // Setup output pointers 565 | // 566 | 567 | if ( transaction->OutParameters == NULL ) { 568 | 569 | // 570 | // Parameters will go into the SMB buffer. Calculate the pointer 571 | // then round it up to the next DWORD address. 572 | // 573 | 574 | transaction->OutParameters = (PCHAR)(transaction->OutSetup + 575 | transaction->MaxSetupCount); 576 | offset = (transaction->OutParameters - (PCHAR)header + 3) & ~3; 577 | transaction->OutParameters = (PCHAR)header + offset; 578 | } 579 | 580 | if ( transaction->OutData == NULL ) { 581 | 582 | // 583 | // Data will go into the SMB buffer. Calculate the pointer 584 | // then round it up to the next DWORD address. 585 | // 586 | 587 | transaction->OutData = transaction->OutParameters + 588 | transaction->MaxParameterCount; 589 | offset = (transaction->OutData - (PCHAR)header + 3) & ~3; 590 | transaction->OutData = (PCHAR)header + offset; 591 | } 592 | //... 593 | command = SmbGetUshort(&transaction->InSetup[0]); 594 | //... 595 | 596 | switch( command ) { 597 | 598 | case TRANS_TRANSACT_NMPIPE: 599 | resultStatus = SrvTransactNamedPipe( WorkContext ); 600 | break; 601 | 602 | case TRANS_PEEK_NMPIPE: 603 | resultStatus = SrvPeekNamedPipe( WorkContext ); 604 | break; 605 | //... 606 | } 607 | ``` 608 | 这在这个函数里有这么一个逻辑,当`transaction->OutParameters==NULL`里,会将`PWORK_CONTEXT->ResponseHeader`加上一定的offset赋于它,`PWORK_CONTEXT->ResponseHeader`就是个NonePagedPool. 609 | ```c 610 | kd> ub eip 611 | srv!ExecuteTransaction+0x60: 612 | b76e8d05 46 inc esi 613 | b76e8d06 50 push eax 614 | b76e8d07 d1e0 shl eax,1 615 | b76e8d09 2bc2 sub eax,edx 616 | b76e8d0b 8d440803 lea eax,[eax+ecx+3] 617 | b76e8d0f 83e0fc and eax,0FFFFFFFCh 618 | b76e8d12 03c2 add eax,edx 619 | b76e8d14 894640 mov dword ptr [esi+40h],eax 620 | 621 | kd> dd 5ace0 622 | 0005ace0 109c020c 00000000 89b2c948 89a29c18 623 | 0005acf0 e1ed9900 e15e2960 0005bda0 00059c50 624 | 0005ad00 00020000 0005ad78 00002361 40010036 625 | 0005ad10 00000000 0005ad0c 8993fa25 0005ad7c 626 | 0005ad20 8993fa28 0005ad7c b76d84ec 00000004 627 | 0005ad30 00000000 00000000 00000000 00000010 628 | 0005ad40 00000000 00000000 00000100 00000000 629 | 0005ad50 00000101 08000000 0800cee6 0000004b 630 | 631 | kd> ? esi+0x40 632 | Evaluate expression: 372000 = 0005ad20 633 | 634 | kd> r eax 635 | eax=8993fa28 636 | 637 | ``` 638 | ##### 5.1.2.1 transaction->OutParameters=NULL 639 | Transaction 初始状态下`OutParameters`并不为NULL: 640 | ```c 641 | kd> dd 5ace0 642 | 0005ace0 109c020c 00000000 89b2c948 89a29c18 643 | 0005acf0 e1ed9900 e15e2960 0005bda0 00059c50 644 | 0005ad00 00020000 0005ad78 00002361 00000001 645 | 0005ad10 00000000 0005ad7c 00000000 0005ad7c 646 | 0005ad20 0005adbc 0005ad7c 0005bd7c 00000000 647 | 0005ad30 00000000 00000000 00000000 00000fc0 648 | 0005ad40 00000000 00000040 00000000 00000000 649 | 0005ad50 00000101 08000000 0800cee6 0000004b 650 | ``` 651 | 这里通过`write_transaction`越界 写 `leak_transaction->OutParameters`为NULL, 然后发包触发写`leak_transaction`操作,之后`leak_transaction->OutParameters`便为一 Npp Buffer值了。 652 | ##### 5.1.2.2 leak_transaction->OutData = &leak_transaction->OutParameters 653 | 这里要事先泄露leak_transaction的基址,其实也不难,通过读leak_transaction的OutData 或 OutParameters 或 InData 字段的值再减去一定的偏移便得到了基址,使`leak_transaction->OutData = &leak_transaction->OutParameters`之后,发包触发leak_transaction读操作便将该Npp buffer地址泄露出来了。 654 | ##### 5.1.2.3 写shellcode到Npp Buffer 655 | 将`control_transaction->OutData`设为`Npp Buffer+0x100`地址,然后发包发送shellcode,便将shellcode写到了`Npp Buffer+0x100`内。 656 | ### 5.2 代码执行 657 | 至此,直接将Npp buffer+0x100写到之前泄露出来的函数表里 658 | ```c 659 | kd> dds b76d8598 660 | b76d8598 b7709683 srv!SrvSmbOpen2 661 | b76d859c b76f62a8 srv!SrvSmbFindFirst2 662 | b76d85a0 b76f74e5 srv!SrvSmbFindNext2 663 | b76d85a4 b76f6309 srv!SrvSmbQueryFsInformation 664 | b76d85a8 b7707293 srv!SrvSmbSetFsInformation 665 | b76d85ac b77041ad srv!SrvSmbQueryPathInformation 666 | b76d85b0 b7703ce7 srv!SrvSmbSetPathInformation 667 | b76d85b4 b77025ad srv!SrvSmbQueryFileInformation 668 | b76d85b8 b770367f srv!SrvSmbSetFileInformation 669 | b76d85bc b7705c85 srv!SrvSmbFsctl 670 | b76d85c0 b7706419 srv!SrvSmbIoctl2 671 | b76d85c4 b7705c85 srv!SrvSmbFsctl 672 | b76d85c8 b7705c85 srv!SrvSmbFsctl 673 | b76d85cc b77047bb srv!SrvSmbCreateDirectory2 674 | b76d85d0 8993fb28 675 | b76d85d4 b7709a51 srv!SrvTransactionNotImplemented 676 | b76d85d8 b76fb144 srv!SrvSmbGetDfsReferral 677 | b76d85dc b76faf7e srv!SrvSmbReportDfsInconsistency 678 | b76d85e0 00000000 679 | ``` 680 | 之后发包就能触发该函数调用: 681 | ```c 682 | kd> k 683 | # ChildEBP RetAddr 684 | WARNING: Frame IP not in any known module. Following frames may be wrong. 685 | 00 b72b4cf0 b76e8d76 0x8993fb28 686 | 01 b72b4d04 b76e341f srv!ExecuteTransaction+0xdb 687 | 02 b72b4d7c b76ca836 srv!SrvSmbTransaction+0x7ac 688 | 03 b72b4d88 b76dad98 srv!SrvProcessSmb+0xb7 689 | 04 b72b4dac 805c6160 srv!WorkerThread+0x11e 690 | 05 b72b4ddc 80541dd2 nt!PspSystemThreadStartup+0x34 691 | 06 00000000 00000000 nt!KiThreadStartup+0x16 692 | ``` 693 | 694 | 695 | 696 | ## 6 关于补丁 697 | 了解了漏洞原理之后修补都很简单了。只要在`srv!SrvFindTransaction`里面判断一下SMB COMMAND的类型是否一致就好了。 698 | 修补后 699 | ```cpp 700 | TRANSCATION *__fastcall SrvFindTransaction(int pConnect, SMB_HEADER *Fid, __int16 a3) 701 | { 702 | _DWORD **pTransList; // eax@4 703 | _DWORD *v6; // ebx@4 704 | PDEVICE_OBJECT v7; // ecx@5 705 | TRANSCATION *pTransaction; // esi@6 706 | char Command_Trans; // al@10 707 | char Command_header; // dl@10 708 | __int16 MIDorFID; // [sp+Ch] [bp-Ch]@2 709 | struct _ERESOURCE *Resource; // [sp+14h] [bp-4h]@4 710 | 711 | if ( Fid->Command == 0x2F ) 712 | MIDorFID = a3; 713 | else 714 | MIDorFID = Fid->MID; 715 | Resource = (struct _ERESOURCE *)(pConnect + 0x19C); 716 | ExAcquireResourceExclusiveLite((PERESOURCE)(pConnect + 0x19C), 1u); 717 | pTransList = (_DWORD **)(*(_DWORD *)(pConnect + 0x160) + 8); 718 | v6 = *pTransList; 719 | if ( *pTransList == pTransList ) 720 | goto LABEL_14; 721 | v7 = WPP_GLOBAL_Control; 722 | while ( 1 ) 723 | { 724 | pTransaction = (TRANSCATION *)(v6 - 6); 725 | if ( *((_WORD *)v6 + 49) == Fid->TID 726 | && pTransaction->PID == Fid->PID 727 | && pTransaction->UID == Fid->UID 728 | && pTransaction->MID == MIDorFID ) 729 | { 730 | break; 731 | } 732 | LABEL_13: 733 | v6 = (_DWORD *)*v6; 734 | if ( v6 == (_DWORD *)(*(_DWORD *)(pConnect + 0x160) + 8) ) 735 | goto LABEL_14; 736 | } 737 | 738 | // 739 | // 这里添加了对COMMAND的比较。比较pTransaction和请求中SMB_HEADER中的COMMAND进行对比 740 | // 741 | 742 | Command_Trans = pTransaction->Command; 743 | Command_header = Fid->Command; 744 | if ( Command_Trans != Command_header ) 745 | { 746 | if ( (PDEVICE_OBJECT *)v7 != &WPP_GLOBAL_Control ) 747 | { 748 | WPP_SF_qDD(v7->AttachedDevice, v7->CurrentIrp, (_BYTE)v6 - 24, Command_header, Command_Trans); 749 | v7 = WPP_GLOBAL_Control; 750 | } 751 | goto LABEL_13; 752 | } 753 | if ( BYTE1(pTransaction->field_0) == 2 ) 754 | { 755 | _InterlockedIncrement((volatile signed __int32 *)(pTransaction->field_8 + 4)); 756 | goto LABEL_15; 757 | } 758 | LABEL_14: 759 | pTransaction = 0; 760 | LABEL_15: 761 | ExReleaseResourceLite(Resource); 762 | return pTransaction; 763 | } 764 | ``` 765 | 766 | 补丁点就是`if ( Command_Trans != Command_header )`看注释的地方。 767 | 768 | ## 7 总结 769 | 总之,这个漏洞还是非常好的,远程任意地址写,还可以信息泄露。威力很大。 770 | 771 | ## 8 联系作者 772 | [pgboy 微博](http://weibo.com/pgboy1988) 773 | [zhong_sf 微博](http://weibo.com/2641521260) 774 | -------------------------------------------------------------------------------- /Etneralromance/andx.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/andx.JPG -------------------------------------------------------------------------------- /Etneralromance/exploit.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/exploit.pcapng -------------------------------------------------------------------------------- /Etneralromance/wireshark.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/wireshark.JPG -------------------------------------------------------------------------------- /Etneralromance/wirshark1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progmboy/vul_analyze_doc/9ec7d5a3268ad091508db6f5463080c5205fc048/Etneralromance/wirshark1.PNG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vul_analyze_doc 2 | 3 | 下载到本地并用一个支持[TOC]markdown的编辑器打开.因为github也不支持TOC 4 | 5 | Please clone to your local. And open the *.md file with a markdown editor which support [TOC] 6 | --------------------------------------------------------------------------------