天天看點

王然的煩惱--她很精通linux啦

寶貝王然最近十分想搞嫁禍于人的惡作劇!

一台linux主機上建立了一個使用者wangran,一共有兩個人王然和王其知道使用者wangran的密碼pw,其中王其還知道root密碼而王然不知道,為了安全起見,遠端使用者都不得用root直接登入主機,也就是王然和王其隻能通過wangran這個使用者來登入,王然自認為是linux高手,她當然知道他們遠端登入的終端是/dev/pts/n,n從0開始遞增,而且她也知道/dev/pts/n的權限就是通過終端登入的使用者也就是wangran的權限,任何su的使用者都不會改變終端檔案的權限,由于她不知道root密碼,然而她又想做一些XX,于是她想出了一個嫁禍于人的辦法,王然開始工作,她的最終目的是在王其登入的終端上執行root指令,設想王其登入的終端是/dev/pts/y,而王然的登入終端是/dev/pts/x,王然想一定能在/dev/pts/y上執行指令,因為她有權操作/dev/pts/y,畢竟他倆都是通過一個username登入的。

     起初,王然把事情想簡單了,她首先來了一個用過linux的人都會的方式:

wangran@DROP:~$echo ls > /dev/pts/y    #當然不能用halt指令來測試了,是以用ls。

為了得知結果,王然讓王其看螢幕有何變化,王其很郁悶,不解為何突然螢幕上被寫了一個ls,而王然隻說這隻是一個惡作劇,以顯示自己水準的高超,她是斷然不會把最終目的告訴王其的,于是接下來的工作是王其在幫着王然完成的。隻寫了一個ls,沒有什麼用,是不是沒有寫回車符号呢,于是精通vt100終端的王然又來了一個:

wangran@DROP:~$echo -e 'pwd^M' > /dev/pts/y #行倒是換了,可是還是沒有執行ls

于是王然拿出了strace,讓王其在螢幕執行:strace bash,王其被出來的一大堆看不懂的資訊給搞懵了,于是離開了座位,把兩個終端全部讓給了王然,此時王然當然可以敲入passwd指令修改root密碼,可是作為一名黑客,這樣做不雅。

     在王其的終端上:strace bash正在運作中:

...

rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0

read(0, "l", 1)                         = 1

write(2, "l", 1l)                        = 1

read(0, "s", 1)                         = 1

write(2, "s", 1s)                        = 1

read(0, "/r", 1)                        = 1

write(2, "/n", 1

)                       = 1

rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0

stat64(".", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0

clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x4005e968) = 8396

waitpid(-1, ...(ls指令的結果)...

隻要你敲入一個字元,strace就會先從标準輸入read出來,在從标準輸出write回去,如果在别的終端上通過echo的方式往另一個終端寫東西,無論如何都是不能被該目的終端解釋為鍵盤輸入的,因為echo隻是将字元寫入了輸出緩沖區,最終将送往螢幕,是以你隻能看到你寫入的資訊,而你無法将字元從另一個終端送入該終端的輸入緩沖區,在tty終端以及真實的終端隻有鍵盤的敲擊才能做到往輸入緩沖區送資訊,而在諸如/dev/pts/n或者序列槽之類的終端,你必須遵循其“線路規程”才可以,比如telnet終端,你在遠端敲入了字母l,該字母l将會按照傳輸線路以及應用協定的規則被編碼,然後主機在接收到該編碼後會同樣按照線路規程和應用協定将資料解碼,最終将字母l送入終端的輸入緩沖區。這個過程是複雜的,可愛的王然決定将黑客精神發揚光大,是以她将這個過程呈現出來。

     以在windows機器上ssh一台linux主機為例,我們執行strace -p pid(随意一個sshd程序号),縮略的結果如下(以#辨別注釋):

read(4, "/3166/213/310/..."..., 16384) = 5 #從檔案辨別符4讀取資料(加密資料)

select(11, [4 6 9 10], [8], NULL, NULL) = 1 (out [8]) #檔案8可寫

write(8, "l", 1) = 1  #寫入檔案8字元"l"

select(11, [4 6 9 10], [], NULL, NULL)  = 1 (in [9]) #檔案9可讀

read(9, "l", 16384) = 1   #檔案9中讀取字元l

select(11, [4 6 9 10], [4], NULL, NULL) = 1 (out [4])

write(4, "a/313/3732/..."..., 36) = 36

select(11, [4 6 9 10], [], NULL, NULL)  = 1 (in [4])

read(4, "/3./364/352/2..."..., 16384) = 52

select(11, [4 6 9 10], [8], NULL, NULL) = 1 (out [8])

write(8, "s", 1) = 1

select(11, [4 6 9 10], [], NULL, NULL)  = 1 (in [9])

read(9, "s", 16384) = 1

write(4, "/20zSMS/215..."..., 36) = 36

select(11, [4 6 9 10], [], NULL, NULL

以上是strace的輸出,可是檔案描述符4,8,9到底是什麼呢?這還得需要lsof指令幫忙,下面是lsof -p pid(sshd的pid)的縮略結果

-----------------------------lsof sshd

...:/usr/src/linux# lsof  -p 8793

COMMAND  PID   USER   FD   TYPE     DEVICE    SIZE     NODE NAME

sshd    8793 zyw    3u  unix 0xc33c7ac0            12870 socket

sshd    8793 zyw    4u  IPv6      12857     TCP host1:ssh->host2 #4是一個套接字

sshd    8793 zyw    5u  unix 0xf413be40            12872 socket

sshd    8793 zyw    6r  FIFO        0,7            12873 pipe

sshd    8793 zyw    7w  FIFO        0,7            12873 pipe

sshd    8793 zyw    8u   CHR        5,2         23921462 /dev/ptmx #8和9都是終端

sshd    8793 zyw    9u   CHR        5,2         23921462 /dev/ptmx

知道了檔案描述符代表的檔案,下一步就是分析sshd的執行過程了,可以從strace的輸出看到,首先從網絡讀取了一段加密的資料,然後将之寫入了/dev/ptmx,然後将又從相同的檔案讀出了剛剛寫入的相同字元,就像回顯一樣,究竟sshd将資料寫到了哪裡?如果輸入l和s之後敲回車,那麼目前目錄的檔案就會顯示出來,這又是為什麼?

     簡單說來,終端是一個古老的東西,大體上分為兩類,第一類是真實的終端,比如通過序列槽連接配接到主機的終端或者slip線路終端等等,這類終端其實很簡單,本質上就是一條網線,你可以将其線路規程了解成實體層協定,看一段slip的tty線路規程代碼就知道了,每個線路規程都有一個receive_buf方法,負責從驅動接收資料并且交給線路規程來處理:slip_receive_buf即是其slip的receive_buf方法,其中調用netif_rx将資料按照線路規程解碼後交給網絡協定棧。;第二類終端當然就是僞終端了,其中又分為兩個小類,一類是console終端,也就是/dev/ttyn(n是一個數)之類的,它們更簡單,這類終端的讀緩沖區和鍵盤之類的輸入裝置相聯系,而寫緩沖區和螢幕或者列印機之類的輸出裝置相聯系,隻要你敲擊了鍵盤,資料就會進入tty系列的讀緩沖,而往tty寫資料,則會顯示到輸出裝置上,另一類網絡僞終端,它們都是成對的,即/dev/ptmx和/dev/pts/n(n是一個數),是以如果你往終端檔案寫一個字元,那麼該字元将從另一端被讀出,反之亦然,是以,王然告訴大家,sshd将資料寫入了ptmx,然後資料就進入了pts/n的讀緩沖區,那麼誰來讀取呢?答案是shell,同樣的執行一下lsof -p pid(一個ssh終端bash的pid),就會發現其标準輸入,輸出都是/dev/pts/n,然後strace -p pid(一個ssh終端bash的pid),就會發現bash先從pts/n讀取資料,然後再往其寫入相同的資料,顯然讀取的資料是sshd寫入ptmx的,而将相同的資料寫入pts/n,sshd則可以從ptmx讀取,然後通過套接字回顯到遠端的windows終端之上,整個過程是sshd-ptmx和bash-pts/n在配合,windows方面隻是一個顯示作用。

     王然的解說結束了,應該還算詳細,說歸說,王然還是總忘不了她的最終任務,是以使出了最後一招,那就是寫一個c檔案,main函數如下:

int fd = open("/dev/pts/0", O_RDWR);

ioctl(fd,   TIOCSTI,   "l ");

ioctl(fd,   TIOCSTI,   "s ");

ioctl(fd,   TIOCSTI,   "/n ");

return 0;

以wangran使用者執行之,得到了令人失望的權限錯誤,看了核心代碼,TIOCSTI的ioctl會調用tiocsti,在tiocsti函數中有:

static int tiocsti(struct tty_struct *tty, char __user *p)

{

    char ch, mbz = 0;

    struct tty_ldisc *ld;

    if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN))

        return -EPERM; //就是在此出的權限錯誤

    ...

    ld->receive_buf(tty, &ch, &mbz, 1); //如果是root使用者或者使用目前終端,那麼就可以将資料放入讀緩沖區了。

}

可是王然畢竟不是王其,怎麼辦?除了放置木馬就徹底沒有辦法了,到頭來,還是沒有找到linux的漏洞啊!可是,且慢!将權限檢查放到核心這樣好嗎?王然覺得很不好,為何不在使用者執行su other的時候将其對應終端的權限修改成other的權限呢?答案在哪?答案在寫核心的人對寫shell的人不信任?王然覺得是這樣的,可是不信任的代價太大了,機制和政策不再分離...明顯是使用者空間的問題為何非要核心來做呢?于是王然修改了核心源碼,将tiocsti中那個判斷去掉,同時修改了bash的源碼,一旦使用者su成了别的使用者,其終端也就成了别的使用者權限,如果su成了root,則wangran使用者根本就無權限操縱pts/n,核心不必再參與使用者空間的授權事務了,正如核心不管使用者密碼的保管一樣,如果一個shell在su的時候沒有改變終端的權限,那麼有一種需求是使用者希望使用該shell在别的終端上執行類似QQ或者MSN遠端協助的功能。

     最後,王然要總結一下在/dev/pts/m通過echo ls > /dev/pts/n為何不能在pts/n上執行ls,當她在windows上的一個ssh終端執行echo ls > /dev/pts/n的時候,整個指令行通過ssh協定傳輸到了linux主機的sshd程序,然後sshd程序将之寫入到/dev/ptmx,最終/dev/pts/m上的bash程序的read傳回sshd寫入ptmx的資料,也就是整個指令行,然後bash執行之,最終将輸出重新定向到了/dev/pts/n,寫入pts/n就意味着資料可以從ptmx中被讀出,确實地,資料被和pts/n相關的sshd程序從ptmx中讀出了,然後通過網絡将ls+/n這些字元顯示到了windows上和/dev/pts/n相關的ssh終端上,可見至始至終,/dev/pts/n上的bash并沒有讀取任何資料,而若想使之執行ls指令就必然要使得bash讀出ls/n,最終寫入/dev/pts/n的資料僅僅顯示到了windows機器相應的ssh終端而已。我們知道,一般終端輸入都和輸入裝置相關,ssh終端雖然拉遠了linux主機和終端的距離,但是不可否認若想在/dev/pts/n上執行ls,則此ls必然需要在windows機器鍵盤輸入,并且是和/dev/pts/n相關的ssh終端視窗的輸入,隻有這樣,資料才能一步步經過windows機器->linux的sshd->linux的/dev/ptmx->/dev/pts/n->bash執行後再通過bash->/dev/pts/n->linux的/dev/ptmx->linux的sshd->windows機器回顯過來。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271817

繼續閱讀