Kisah penelitian dan pengembangan dalam 3 bagian. Bagian 2 - perkembangan.
Ada banyak beech - bahkan lebih banyak manfaat.
Pada bagian pertama artikel, kami berkenalan dengan beberapa alat untuk mengatur terowongan terbalik, melihat kelebihan dan kekurangannya, mempelajari mekanisme multiplexer Yamux dan menjelaskan persyaratan dasar untuk modul powershell yang baru dibuat. Sudah saatnya untuk mulai mengembangkan modul powershell klien untuk implementasi siap pakai dari terowongan terbalik
RSocksTun .
Pertama-tama, kita perlu memahami dalam mode apa modul kita akan berfungsi. Jelas, untuk transfer data utama, kita perlu menggunakan mekanisme soket windows dan kemampuan .Net untuk streaming baca-tulis ke soket. Tetapi, di sisi lain, karena Karena modul kami harus melayani beberapa aliran yamux pada saat yang sama, maka semua operasi I / O tidak boleh sepenuhnya memblokir pelaksanaan program kami. Ini menunjukkan kesimpulan bahwa modul kami harus menggunakan perangkat lunak multithreading dan melakukan operasi baca-tulis dengan server yamux, serta operasi baca-tulis ke server tujuan di aliran program yang berbeda. Yah, tentu saja, perlu untuk menyediakan mekanisme interaksi antara aliran paralel kita. Untungnya, PowerShell memberikan banyak peluang untuk meluncurkan dan mengelola aliran program.
Algoritma kerja umum
Jadi, algoritma umum klien kami harus seperti ini:
- membuat koneksi SSL ke server;
- masuk dengan kata sandi sehingga server dapat membedakan kami dari petugas keamanan;
- tunggu paket yamux untuk menginstal aliran baru, secara berkala menanggapi permintaan server keepalive;
- mulai aliran program socksScript baru (jangan dikacaukan dengan aliran) segera setelah paket yamux tiba untuk menginstal aliran baru. Di dalam socksScript, terapkan pekerjaan server socks5;
- pada saat kedatangan paket dengan data dari yamux - pahami dari header 12-byte yang mana dari aliran data dimaksudkan, serta ukurannya, baca data dari server yamux dan transfer data yang diterima ke aliran dengan nomor aliran yang sesuai;
- secara berkala memonitor ketersediaan data yang ditujukan untuk server yamux di setiap skrip kaus kaki yang sedang berjalan. Jika ada data seperti itu, tambahkan header 12-byte yang sesuai dan kirimkan ke server yamux;
- pada saat kedatangan paket yamux untuk menutup aliran, mentransmisikan sinyal ke aliran yang sesuai untuk mengakhiri aliran dan memutuskan sambungan, dan setelah itu, selesaikan aliran itu sendiri;
Jadi, di klien kami perlu menerapkan setidaknya 3 aliran program:
- yang utama, yang akan membangun koneksi, masuk ke server yamux, menerima data darinya, memproses tajuk yamux dan mengirim data mentah ke aliran program lain;
- stream dengan server kaus kaki. Mungkin ada beberapa - satu untuk setiap aliran. Mereka menerapkan fungsi socks5. Alur ini akan berinteraksi dengan titik tujuan di jaringan internal;
- arus balik. Ia menerima data dari stream kaus kaki, menambahkan header yamux kepada mereka dan mengirimkannya ke server yamux;
Dan, tentu saja, kita perlu menyediakan interaksi antara semua aliran ini.
Kita tidak hanya perlu menyediakan interaksi seperti itu, tetapi juga untuk mendapatkan kenyamanan streaming input-output (mirip dengan soket). Mekanisme yang paling tepat adalah menggunakan pipa perangkat lunak. Di Windows, pipa terdaftar ketika setiap pipa memiliki namanya sendiri, dan anonim - setiap pipa diidentifikasi oleh penangannya. Demi kerahasiaan, tentu saja, kami akan menggunakan pipa anonim. (Lagi pula, kami tidak ingin modul kami dihitung menggunakan pipa terdaftar di sistem - kan?). Dengan demikian, antara arus utama / balik dan kaus kaki-arus, interaksi akan melalui pipa anonim, mendukung aliran I / O asinkron Antara arus utama dan arus balik, komunikasi akan berlangsung melalui mekanisme objek-bersama (variabel tersinkronisasi bersama) (lebih lanjut tentang apa variabel-variabel ini dan bagaimana hidup dengannya, Anda dapat membaca di
sini ).
Informasi tentang menjalankan kaus kaki aliran harus disimpan dalam struktur data yang sesuai. Saat membuat thread kaus kaki dalam struktur ini, kita harus menulis:
- nomor sesi yamux: $ ymxstream;
- 4 variabel untuk bekerja dengan pipa (saluran): $ cipipe, $ copipe, $ sipipe, $ sopipe. Karena saluran anonim berfungsi dalam IN atau OUT, untuk setiap aliran kaus kaki kita memerlukan dua saluran anonim, yang masing-masing harus memiliki dua ujung (pipestream) (server dan klien);
- hasil dari panggilan ke aliran adalah $ AsyncJobResult;
- stream handler - $ Psobj. Melalui itu kita akan menutup aliran dan melepaskan sumber daya;
- hasil pembacaan asinkron dari pipa anonim oleh reverse stream ($ readjob). Variabel ini digunakan dalam aliran reverse yamuxScript untuk pembacaan asinkron dari pipa yang sesuai;
- buffer untuk membaca data untuk setiap aliran kaus kaki;
Aliran utama
Jadi, dari sudut pandang pemrosesan data, pekerjaan program kami dibangun sebagai berikut:
- sisi server (rsockstun - diimplementasikan pada Golang) memunculkan server ssl dan menunggu koneksi dari klien;
- ketika menerima koneksi dari klien, server memeriksa kata sandi, dan jika itu benar, membuat koneksi yamux, menaikkan port kaus kaki dan menunggu koneksi dari klien kaus kaki (rantai proxy, browser, dll.), secara berkala bertukar paket paket keepalive dengan pelanggan kami. Jika kata sandi salah - pengalihan ke halaman yang kami tentukan saat menginstal server dilakukan (ini adalah halaman "legal" untuk administrator keamanan informasi yang waspada);
- ketika menerima koneksi dari klien kaus kaki, server mengirim paket yamux ke klien kami untuk membuat aliran baru (YMX SYN);
Mendapatkan dan menganalisis tajuk YamuxModul kami pertama-tama membuat koneksi SSL ke server dan masuk dengan kata sandi:
$tcpConnection = New-Object System.Net.Sockets.TcpClient($server, $port) $tcpStream = New-Object System.Net.Security.SslStream($tcpConnection.GetStream(),$false,({$True} -as [Net.Security.RemoteCertificateValidationCallback])) $tcpStream.AuthenticateAsClient('127.0.0.1')
Kemudian, skrip menunggu header yamux 12-byte dan mem-parsingnya.
Ada sedikit nuansa ... Seperti yang ditunjukkan oleh latihan, cukup baca 12 byte dari soket:
$num = $tcpStream.Read($tmpbuffer,0,12)
tidak cukup, karena operasi baca dapat diselesaikan setelah kedatangan hanya sebagian dari byte yang diperlukan. Karena itu, kita perlu menunggu 12 byte dalam loop:
do { try { $num = $tcpStream.Read($tmpbuffer,0,12) } catch {} $tnum += $num $ymxbuffer += $tmpbuffer[0..($num-1)] }while ($tnum -lt 12 -and $tcpConnection.Connected)
Setelah loop selesai, kita harus menganalisis header 12-byte yang terkandung dalam variabel $ ymxbuffer untuk jenisnya dan mengatur flag sesuai dengan spesifikasi Yamux.
Header Yamux dapat terdiri dari beberapa jenis:
- ymx syn - instal aliran baru;
- penyelesaian aliran sirip ymx;
- data ymx - merepresentasikan informasi tentang data (ukuran apa dan untuk apa aliran itu dimaksudkan);
- ymx ping - pesan keepalive;
- ymx win update - konfirmasi transfer sebagian data;
Apa pun yang tidak sesuai dengan jenis header yamux yang terdaftar dianggap sebagai situasi luar biasa. Ada 10 pengecualian seperti itu, dan kami percaya ada sesuatu yang salah di sini dan kami sedang menyelesaikan pekerjaan modul kami.
(dan juga menghapus semua file kami, menghapus disk, mengubah nama belakang, membuat paspor baru, meninggalkan negara, dll sesuai daftar ...)Membuat utas kaus kaki baruSetelah menerima paket yamux untuk membuat aliran baru, klien kami membuat dua pipa server anonim ($ sipipe, $ sopipe), untuk in / out, masing-masing, menciptakan pipa klien ($ cipipe, $ copipe) berdasarkan pada mereka:
$sipipe = new-object System.IO.Pipes.AnonymousPipeServerStream(1) $sopipe = new-object System.IO.Pipes.AnonymousPipeServerStream(2,1) $sipipe_clHandle = $sipipe.GetClientHandleAsString() $sopipe_clHandle = $sopipe.GetClientHandleAsString() $cipipe = new-object System.IO.Pipes.AnonymousPipeClientStream(1,$sopipe_clHandle) $copipe = new-object System.IO.Pipes.AnonymousPipeClientStream(2,$sipipe_clHandle)
membuat runspace untuk aliran kaus kaki, menetapkan variabel bersama untuk berinteraksi dengan aliran ini (StopFlag), dan menjalankan scriptblock SocksScript, yang mengimplementasikan fungsionalitas server kaus kaki dalam aliran terpisah:
$state = [PSCustomObject]@{"StreamID"=$ymxstream;"inputStream"=$cipipe;"outputStream"=$copipe} $PS = [PowerShell]::Create() $socksrunspace = [runspacefactory]::CreateRunspace() $socksrunspace.Open() $socksrunspace.SessionStateProxy.SetVariable("StopFlag",$StopFlag) $PS.Runspace = $socksrunspace $PS.AddScript($socksScript).AddArgument($state) | Out-Null [System.IAsyncResult]$AsyncJobResult = $null $StopFlag[$ymxstream] = 0 $AsyncJobResult = $PS.BeginInvoke()
Variabel yang dibuat ditulis ke struktur ArrayList khusus - analog dari Kamus dengan Python
[System.Collections.ArrayList]$streams = @{}
Penambahan terjadi melalui metode Tambahkan bawaan:
$streams.add(@{ymxId=$ymxstream;cinputStream=$cipipe;sinputStream=$sipipe;coutputStream=$copipe;soutputStream=$sopipe;asyncobj=$AsyncJobResult;psobj=$PS;readjob=$null;readbuffer=$readbuffer}) | out-null
Pemrosesan Data YamuxSetelah menerima data yang ditujukan untuk aliran kaus kaki apa pun dari server yamux, kita harus menentukan jumlah aliran yamux (jumlah aliran kaus kaki yang menjadi tujuan data ini) dan jumlah byte data dari header yamux 12-byte:
$ymxstream = [bitconverter]::ToInt32($buffer[7..4],0) $ymxcount = [bitconverter]::ToInt32($buffer[11..8],0)
Kemudian, dari aliran ArrayList, menggunakan bidang ymxId, kita mendapatkan handler dari server-pipa sesuai dengan aliran kaus kaki ini:
if ($streams.Count -gt 1){$streamind = $streams.ymxId.IndexOf($ymxstream)} else {$streamind = 0} $outStream = $streams[$streamind].soutputStream
Setelah itu, kita membaca data dari soket, mengingat bahwa kita perlu membaca sejumlah byte melalui loop:
$databuffer = $null $tnum = 0 do { if ($buffer.length -le ($ymxcount-$tnum)) { $num = $tcpStream.Read($buffer,0,$buffer.Length) }else { $num = $tcpStream.Read($buffer,0,($ymxcount-$tnum)) } $tnum += $num $databuffer += $buffer[0..($num-1)] }while ($tnum -lt $ymxcount -and $tcpConnection.Connected)
dan tulis data yang diterima ke pipa yang sesuai:
$num = $tcpStream.Read($buffer,0,$ymxcount) $outStream.Write($buffer,0,$ymxcount)
Yamux FIN Processing - End StreamKetika kami menerima paket dari server yamix yang memberi sinyal penutupan aliran, kami juga pertama-tama mendapatkan nomor aliran yamux dari header 12-byte:
$ymxstream = [bitconverter]::ToInt32($buffer[7..4],0)
kemudian, melalui variabel yang dibagikan (atau lebih tepatnya, array bendera, di mana indeksnya adalah nomor aliran yamux), kami memberi tanda agar kaus kaki menyelesaikan:
if ($streams.Count -gt 1){$streamind = $streams.ymxId.IndexOf($ymxstream)} else {$streamind = 0} if ($StopFlag[$ymxstream] -eq 0){ write-host "stopflag is 0. Setting to 1" $StopFlag[$ymxstream] = 1 }
setelah mengatur bendera, sebelum membunuh aliran kaus kaki, Anda perlu menunggu sejumlah waktu agar aliran kaus kaki memproses bendera ini. 200 ms sudah cukup untuk ini:
start-sleep -milliseconds 200 #wait for thread check flag
kemudian tutup semua pipa yang terkait dengan aliran ini, tutup Runspace yang sesuai dan bunuh objek Powershell ke sumber daya gratis:
$streams[$streamind].cinputStream.close() $streams[$streamind].coutputStream.close() $streams[$streamind].sinputStream.close() $streams[$streamind].soutputStream.close() $streams[$streamind].psobj.Runspace.close() $streams[$streamind].psobj.Dispose() $streams[$streamind].readbuffer.clear()
Setelah menutup aliran kaus kaki, kita perlu menghapus elemen yang sesuai dari aliran ArrayList:
$streams.RemoveAt($streamind)
Dan pada akhirnya, kita perlu memaksa pengumpul sampah .Net untuk melepaskan sumber daya yang digunakan oleh utas. Jika tidak, skrip kami akan mengkonsumsi sekitar 100-200 MB memori, yang dapat menarik perhatian pengguna yang berpengalaman dan korosif, tetapi kami tidak memerlukan ini:
[System.GC]::Collect()#clear garbage to minimize memory usage
Yamux Script - aliran balik
Seperti disebutkan di atas, data yang diterima dari aliran kaus kaki diproses oleh aliran yamuxScript terpisah, yang dimulai dari awal (setelah koneksi yang berhasil ke server). Tugasnya adalah melakukan polling secara berkala pipa keluaran stream socks yang berlokasi di ArrayList $ stream:
foreach ($stream in $state.streams){ ... }
dan jika ada data di dalamnya, kirimkan ke server yamux, setelah sebelumnya memberikan header yamux 12-byte yang sesuai yang berisi jumlah sesi yamux dan jumlah byte data:
if ($stream.readjob -eq $null){ $stream.readjob = $stream.sinputStream.ReadAsync($stream.readbuffer,0,1024) }elseif ( $stream.readjob.IsCompleted ){ #if read asyncjob completed - generate yamux header $outbuf = [byte[]](0x00,0x00,0x00,0x00)+ [bitconverter]::getbytes([int32]$stream.ymxId)[3..0]+ [bitconverter]::getbytes([int32]$stream.readjob.Result)[3..0] $state.tcpstream.Write($outbuf,0,12) #write raw data from socks thread to yamux $state.tcpstream.Write($stream.readbuffer,0,$stream.readjob.Result) $state.tcpstream.flush() #create new readasync job $stream.readjob = $stream.sinputStream.ReadAsync($stream.readbuffer,0,1024) }else{ #write-host "Not readed" }
YamuxScript juga memonitor flag yang diset pada array $ StopFlag yang dibagikan untuk setiap utas socksScript yang dieksekusi. Bendera ini dapat diatur ke 2 jika server jauh yang socksScript bekerja dengan terputus. Dalam situasi ini, informasi tersebut harus dilaporkan kepada klien kaus kaki. Rangkaiannya adalah sebagai berikut: yamuxScript harus memberi tahu server yamux tentang pemutusan sehingga pada gilirannya memberi sinyal kepada klien kaus kaki.
if ($StopFlag[$stream.ymxId] -eq 2){ $stream.ymxId | out-file -Append c:\work\log.txt $outbuf = [byte[]](0x00,0x01,0x00,0x04)+ [bitconverter]::getbytes([int32]$stream.ymxId)[3..0]+ [byte[]](0x00,0x00,0x00,0x00) $state.tcpstream.Write($outbuf,0,12) $state.tcpstream.flush() }
Pembaruan jendela Yamux
Selain itu, yamuxScript harus memantau jumlah byte yang diterima dari server yamux dan secara berkala mengirim Pesan Pembaruan YMX. Mekanisme di Yamux ini bertanggung jawab untuk memantau dan mengubah apa yang disebut ukuran jendela (mirip dengan protokol TCP) - jumlah byte data yang dapat dikirim tanpa pengakuan. Secara default, ukuran jendela adalah 256 Kbytes. Ini berarti bahwa ketika mengirim atau menerima file atau data yang lebih besar dari ukuran ini, kita perlu mengirim paket pembaruan windpw ke server yamux. Untuk mengontrol jumlah data yang diterima dari server yamux, array khusus $ RcvBytes telah diperkenalkan, di mana arus utama dengan menambah nilai saat ini mencatat jumlah byte yang diterima dari server untuk setiap aliran. Jika ambang batas yang ditetapkan terlampaui, yamuxScript harus mengirim paket ke server WinUpdate dan mengatur ulang penghitung:
if ($RcvBytes[$stream.ymxId] -ge 256144){ #out win update ymx packet with 256K size $outbuf = [byte[]](0x00,0x01,0x00,0x00)+ [bitconverter]::getbytes([int32]$stream.ymxId)[3..0]+ (0x00,0x04,0x00,0x00) $state.tcpstream.Write($outbuf,0,12) $RcvBytes[$stream.ymxId] = 0 }
Streaming SocksScript
Sekarang mari kita pindah langsung ke socksScript itu sendiri.
Ingat bahwa socksScript dipanggil secara asinkron:
$state = [PSCustomObject]@{"StreamID"=$ymxstream;"inputStream"=$cipipe;"outputStream"=$copipe} $PS = [PowerShell]::Create() .... $AsyncJobResult = $PS.BeginInvoke()
dan pada saat panggilan, data berikut hadir dalam variabel $ state yang ditransfer ke aliran:
- $ state.streamId - nomor sesi yamux;
- $ state.inputStream - baca pipa;
- $ state.oututStream - tulis pipa;
Data dalam pipa datang dalam bentuk mentah tanpa tajuk yamux, mis. dalam bentuk di mana mereka berasal dari klien kaus kaki.
Di dalam socksScript, pertama-tama, kita perlu menentukan versi kaus kaki dan memastikan bahwa itu adalah 5:
$state.inputStream.Read($buffer,0,2) | Out-Null $socksVer=$buffer[0] if ($socksVer -eq 5){ ... }
Nah, maka kita melakukan persis seperti yang diterapkan dalam skrip Invoke-SocksProxy. Satu-satunya perbedaan adalah bahwa alih-alih panggilan
$AsyncJobResult.AsyncWaitHandle.WaitOne(); $AsyncJobResult2.AsyncWaitHandle.WaitOne();
Kita perlu memantau koneksi tcp dan flag termination yang sesuai dalam array $ StopFlag dalam mode siklik, jika tidak kita tidak akan dapat mengenali situasi akhir koneksi dari sisi klien kaus kaki dan server ymux:
while ($StopFlag[$state.StreamID] -eq 0 -and $tmpServ.Connected ){ start-sleep -Milliseconds 50 }
Jika koneksi berakhir di sisi tcp dari server yang kita hubungkan, kita menetapkan flag ini menjadi 2, yang akan memaksa yamuxscript untuk mengenali ini dan mengirim paket FIN ymx yang sesuai ke server yamux:
if ($tmpServ.Connected){ $tmpServ.close() }else{ $StopFlag[$state.StreamID] = 2 }
Kita juga harus mengatur tanda ini jika socksScript tidak dapat terhubung ke server tujuan:
if($tmpServ.Connected){ ... } else{ $buffer[1]=4 $state.outputStream.Write($buffer,0,2) $StopFlag[$state.StreamID] = 2 }
Kesimpulan ke bagian kedua
Selama penelitian pengkodean kami, kami dapat membuat klien PowerShell ke server RsocksTun kami dengan kemampuan:
- Koneksi SSL
- otorisasi di server;
- bekerja dengan server yamux dengan dukungan untuk ping keepalive;
- mode operasi multi-utas;
- dukungan untuk mentransfer file besar;
Di luar artikel, ada implementasi fungsi menghubungkan melalui server proxy dan otorisasi di atasnya, serta mengubah skrip kami menjadi versi inline, yang dapat dijalankan dari baris perintah. Itu akan berada di bagian ketiga.
Itu saja untuk hari ini. Seperti yang mereka katakan - berlangganan, seperti, tinggalkan komentar (terutama mengenai pemikiran Anda tentang peningkatan kode dan penambahan fungsionalitas).