"只有继续从容向前,才能不辜负那些在漫漫长夜中依旧褶褶生辉的旧时光"
本文作者:蛇皮怪怪
概述
本文章主要是从代码实现的角度,来对lm | Ntlm Hash的生成过程进行理解
关于lm hash: LM Hash的全名为"LAN Manager Hash" 是微软为了提高windows操作系统的安全性而采用的散列加密算法,其本质是DES加密,尽管LM Hash比较容易被破解,但是为了保证系统的兼容性,Windows只是将LM Hash禁用了(从windows vista和windows server 2008版本开始,windows默认禁用LM Hash)LM hash明文密码被限定在14位以内,也就是说,如果要停止使用LM Hash,将用户的密码设置至14位以上即可,如果LM Hash被禁用了,攻击者通过工具抓取的LM Hash通常为"ad3435b51404eead3b435b51404ee"(表示LM Hash为空值或被禁用)
生成步骤如下
- 用户的密码被限制为最多14个字符。
- 用户的密码转换为大写。
- 密码转换为16进制字符串,不足14字节将会用0来再后面补全。
- 密码的16进制字符串被分成两个7byte部分。每部分转换成比特流,并且长度位56bit,长度不足使用0在左边补齐长度,再分7bit为一组末尾加0,组成新的编码(str_to_key()函数处理)
- 上步骤得到的8byte二组,分别作为DES key为"KGS!@#$%"进行加密。
- 将二组DES加密后的编码拼接,得到最终LM HASH值。
实现代码如下(C++)
using namespace std;
class SecretToLMHash {
string operationString;//待操作字符串
string operationStringHex;//待操作字符串十六进制格式
string leftoperationString;//补零操作时,用于记录拆分开的字符串
string rightoperationString;
string cLeftoperationString;//密文形式
string cRightoperationString;
string binLeftoperationString;//补零操作时,用于记录拆分开的字符串的二进制形式
string binRightoperationString;
string hexChar[16] = { "0","1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
public:
//没有功能的构造函数,可直接无视
SecretToLMHash();
//设置明文字符
bool setSecret(string secret);
//检查密码长度
bool checkLength(string secret);
//将小写转换为大写
void lowercaseToUppercase();
//将密码转化为十六进制字符串
string toHexString(unsigned char x);
//补零操作
void zeroPadding();
//操作字符串拆分
void operationStringBreak();
//将十六进制字符串转换为二进制bit流,并为二进制字符串补零
string binZeroPadding(string hex);
//将二进制转换为十六进制字符串
string binToHex(string bin);
//进行des加密 key为“KGS!@#$%”
string desEncryption(string s);
//获取LMHash结果
string getLMHash();
};
SecretToLMHash::SecretToLMHash() {
}
//设置明文字符
bool SecretToLMHash::setSecret(string secret) {
if (!checkLength(secret)) {//长度超过了14字符
cout << "明文长度超过14字符,请重新输入" << endl;
return false;
}
this->operationString = secret;//设置明文信息
return true;
}
//检查密码长度
bool SecretToLMHash::checkLength(string secret) {
if (secret.length() > 14) {
return false;
}
else {
return true;
}
}
//将小写转换为大写
void SecretToLMHash::lowercaseToUppercase() {
for (unsigned int i = 0; i < operationString.length(); i++) {
operationString[i] = toupper(operationString[i]);
}
}
//将密码转化为十六进制字符串
string SecretToLMHash::toHexString(unsigned char x) {
return hexChar[x/ 16] + hexChar[x % 16];
}
//补零操作
void SecretToLMHash::zeroPadding() {
int i = 28 - operationStringHex.length();
while (i) {
operationStringHex = operationStringHex + "0";
i--;
}
}
//操作字符串拆分
void SecretToLMHash::operationStringBreak() {
for (int i = 0; i < 14; i++) {
leftoperationString = leftoperationString + operationStringHex[i];
}
for (int i = 14; i < 28; i++) {
rightoperationString = rightoperationString + operationStringHex[i];
}
}
//将十六进制字符串转换为二进制bit流,并为二进制字符串补零
string SecretToLMHash::binZeroPadding(string hex) {
string tempString;
string hexToBin[16] = {
"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111"
};
for (unsigned int i = 0; i < hex.length(); i++) {
if (hex[i] >= '0' && hex[i] <= '9') {
tempString = tempString + hexToBin[(char)hex[i] - '0'];
}
else {
tempString = tempString + hexToBin[(char)hex[i] - 'A' + 10];
}
}
hex = "";
for (unsigned int i = 0; i < tempString.length(); i++) {
if (i % 7 == 0 && i) {
hex = hex + "0";
}
hex = hex + tempString[i];
}
hex = hex + "0";
//cout << hex << endl;
return hex;
}
//将二进制转换为十六进制字符串
string SecretToLMHash::binToHex(string bin) {
int index = 0;//记录在四位二进制的第几个位置
int tempHex = 0;//记录四位二进制的十进制值
bool flag = false;//第一次进入循环时,不需要进行转换的拼接
string result;//记录转换的结果
for (unsigned int i = 0; i < bin.length(); i++) {
if (index == 0) {
if (flag) {
result = result + hexChar[tempHex];
tempHex = 0;
}else {
flag = true;
}
}
switch (index) {
case 0: {
tempHex = tempHex + (bin[i] - '0') * 8;
break;
}
case 1: {
tempHex = tempHex + (bin[i] - '0') * 4;
break;
}
case 2: {
tempHex = tempHex + (int)(bin[i] - '0') * 2;
break;
}
case 3: {
tempHex = tempHex + (int)(bin[i] - '0') * 1;
break;
}
}
index++;
index = index % 4;
}
result = result + hexChar[tempHex];//将最后四位的十六进制加上
return result;
}
//进行des加密 key为“KGS!@#$%”
string SecretToLMHash::desEncryption(string s) {
return s;
}
//获取LMHash结果
string SecretToLMHash::getLMHash() {
//明文全部大写
lowercaseToUppercase();
//cout << operationString << endl;
//将明文转换为十六进制
for (unsigned int i = 0; i < operationString.length(); i++) {
operationStringHex = operationStringHex + toHexString((char)operationString[i]);
}
//为十六进制字符串补零
zeroPadding();
//cout << operationStringHex << endl;
//将十六进制字符串拆分
operationStringBreak();
//cout << leftoperationString << endl;
//cout << rightoperationString << endl;
//将十六进制字符串转换为二进制bit流,并为二进制字符串补零
binLeftoperationString = binZeroPadding(leftoperationString);
binRightoperationString = binZeroPadding(rightoperationString);
//cout << leftoperationString << endl;
//cout << rightoperationString << endl;
leftoperationString = binToHex(binLeftoperationString);
rightoperationString = binToHex(binRightoperationString);
//cout << leftoperationString << endl;
//cout << rightoperationString << endl;
cLeftoperationString = desEncryption(leftoperationString);
cRightoperationString = desEncryption(rightoperationString);
return cLeftoperationString + " " + cRightoperationString;
}
int main() {
SecretToLMHash x;
x.setSecret("WELCOME");
cout << x.getLMHash() << endl;
return 0;
}
实现缺陷
-
预计使用openssl库中的des函数,对最后一步进行加密处理
-
但是经过测试发现,openssl库中的des加密算法与生成mlHash的des加密算法不同,预估是置换表不同
-
经过大量查找,还是未找到对应的C++ des库与之对应
-
因此最后一个步骤需要用其他软件来补齐(des计算器)
-
明文:4B47532140232425(KGS!@#$%)
-
秘钥:程序计算的结果
-
总结
-
lmHash在计算过程中,将小写全部转换为大写,造成了一个lmHash值对应多个明文密码,hello与HELLO所产生的lmHash相同。减少了密码碰撞的强度
-
计算过程限制了密码的长度,密码只能14个字符,超过14个字符将突破lmHash的计算规则
-
lmHash应用范围
-
个人版从Windows Vista以前
-
服务器版从Wndows Sever2003以前
-
NTLM Hash生成步骤如下
-
转化为ASCII字符串
-
转换为十六进制字符串
-
转化为Unicode字符串
-
使用MD4消息摘要算法
实现代码如下
using namespace std;
using namespace std;
class SecretToNTLMHash {
string operationString;//待操作字符串
string operationStringHex;//待操作字符串十六进制格式
string operationStringUnicode;//待操作字符串Unicode格式
unsigned char result[128] = {"�"};
string hexChar[16] = { "0","1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
public:
//没有功能的构造函数,可直接无视
SecretToNTLMHash();
//设置明文字符
bool setSecret(string secret);
//将密码转化为十六进制字符串
string toHexString(unsigned char x);
//补零操作
void zeroPadding();
//获取结果
unsigned char * getNTLMHash();
};
SecretToNTLMHash::SecretToNTLMHash() {
}
//设置明文字符
bool SecretToNTLMHash::setSecret(string secret) {
this->operationString = secret;//设置明文信息
return true;
}
//将密码转化为十六进制字符串
string SecretToNTLMHash::toHexString(unsigned char x) {
return hexChar[x / 16] + hexChar[x % 16];
}
//补零操作
void SecretToNTLMHash::zeroPadding() {
for (int i = 0; i < operationStringHex.length(); i += 2) {
operationStringUnicode = operationStringUnicode + operationStringHex[i] + operationStringHex[i + 1] + "00";
}
}
//获取结果
unsigned char * SecretToNTLMHash::getNTLMHash() {
for (int i = 0; i < operationString.length(); i++) {
operationStringHex = operationStringHex + toHexString(operationString[i]);
}
zeroPadding();
//cout << operationStringUnicode << endl;
MD4((unsigned char *)(operationStringUnicode.c_str()), operationStringUnicode.length(), result);
return result;
}
int main() {
SecretToNTLMHash op;
op.setSecret("123456");
cout << op.getNTLMHash() << endl;
return 0;
}
实现缺陷
-
预计使用openssl库中的md5函数,对最后一步进行加密处理
-
但是经过测试发现,openssl库中的md5加密算法与生成ntlmHash的md5加密算法也不同
-
因此,上述代码仅作为对ntlmHash生成过程的深刻理解,不可用于ntlmHash的爆破
总结
-
ntlmHash应用范围
-
个人版从Windows Vista以后
-
服务器版从Wndows Sever2003以后
-
原文始发于微信公众号(0x00实验室):从代码层面分析域内LM | NTLM Hash的生成过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论