1) 查看shadow文件的內容
cat /etc/shadow
可以得到shadow文件的內容,限於篇幅,我們舉例說明:
root:$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.:14838:0:99999:7:::
其格式為:
{用戶名}:{加密后的口令密碼}:{口令最后修改時間距原點(1970-1-1)的天數}:{口令最小修改間隔(防止修改口令,如果時限未到,將恢復至舊口令):{口令最大修改間隔}:{口令失效前的警告天數}:{賬戶不活動天數}:{賬號失效天數}:{保留}
【注】:shadow文件為可讀文件,普通用戶沒有讀寫權限,超級用戶擁有讀寫權限。如果密碼字符串為*,則表示系統用戶不能被登入;如果字符串為!,則表示用戶名被禁用;如果字符串為空,則表示沒有密碼。
我們可以使用passwd –d 用戶名 清空一個用戶的口令密碼。
2) 解析shadow文件中密碼字符串的內容
對於示例的密碼域$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.,我們參考了Linux標准源文件passwd.c,在其中的pw_encrypt函數中找到了加密方法。
我們發現所謂的加密算法,其實就是用明文密碼和一個叫salt的東西通過函數crypt()完成加密。
而所謂的密碼域密文也是由三部分組成的,即:$id$salt$encrypted。
【注】: id為1時,采用md5進行加密;
id為5時,采用SHA256進行加密;
id為6時,采用SHA512進行加密。
3) 數據加密函數crypt()講解
i. 頭文件:#define _XOPEN_SOURCE
#include <unistd.h>
ii. 函數原型:char *crypt(const char *key, const char *salt);
iii. 函數說明:crypt()將使用DES演算法將參數key所指的字符串加以編碼,key字符串長度僅取前8個字符,超過此長度的字符沒有意義。參數salt為兩個字符組成的字符串,由a-z、A-Z、0-9,’.’和’/’所組成,用來決定使用4096種不同內建表格的哪一種。函數執行成功后會返回指向編碼過的字符串指針,參數key所指向的字符串不會有所改動。編碼過的字符串長度為13個字符,前兩個字符為參數salt代表的字符串。
iv. 返回值:返回一個指向以NULL結尾的密碼字符串
v. 附加說明:使用GCC編譯時需要加上 –lcrypt
4) 加密參數salt的由來
在我們的示例密碼域中salt為Bg1H/4mz,那么它又是如何來的?
我們還是從標准源文件passwd.c中查找答案。在passwd.c中,我們找到了與salt相關的函數crypt_make_salt。
在函數crypt_make_salt中出現了很多的判斷條件來選擇以何種方式加密(通過id值來判斷),但其中對我們最重要的一條語句是gensalt(salt_len)。
我們繼續查看了函數static char *gensalt (unsigned int salt_size),才發現原來神秘無比的salt參數只是某個固定長度的隨機字符串而已。
5) 最終結論
在我們每次改寫密碼時,都會隨機生成一個這樣的salt。我們登錄時輸入的明文密碼經過上述的演化后與shadow里的密碼域進行字符串比較,以此來判斷是否允許用戶登錄。
【注】:經過上述的分析,我們發現破解linux下的口令也不是什么難事,但前提是你有機會拿到對方的shadow文件。
6) 示例代碼(測試代碼):
#include <pwd.h>
#include <stddef.h>
#include <string.h>
#include <shadow.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]){
if(argc < 2)
{
printf("no usrname input");
return 1;
}
if (geteuid() != 0)
{
fprintf(stderr, "must be setuid root");
return -1;
}
struct spwd *shd= getspnam(argv[1]);
if(shd != NULL)
{
static char crypt_char[80];
strcpy(crypt_char, shd->sp_pwdp);
char salt[13];
int i=0,j=0;
while(shd->sp_pwdp[i]!='\0')
{
salt[i]=shd->sp_pwdp[i];
if(salt[i]=='$')
{
j++;
if(j==3)
{
salt[i+1]='\0';
break;
}
}
i++;
}
if(j<3)
perror("file error or user cannot use.");
if(argc==3)
{
printf("salt: %s, crypt: %s\n", salt, crypt(argv[2], salt));
printf("shadowd passwd: %s\n", shd->sp_pwdp);
}
}
return 0;
}
編譯: gcc passwd.c -lcrypt -o passwd
運行: ./passwd root 123
結果: salt: $1$Bg1H/4mz$, crypt: $1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.
shadowd passwd: $1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.