一、WinServer2003服务器启用VPN服务
1、在管理工具中打开路由和远程访问
2、右键配置并启用路由和远程访问
3、在弹出的安装向导中点击下一步开始配置
4、选择自定义配置
5、勾选VPN访问
6、点击完成
7、确定开始服务
8、等待初始化结束
9、在计算机管理的本地用户和组中右键添加本地用户以用于VPN链接
10、设置用户名密码
11、右键新添加的用户,打开属性
12、在拨入选项卡下允许远程访问权限
二、WinXP客户端链接VPN
1、在网络连接面板左侧点击创建一个新的连接启动新建连接向导
2、选择连接到我的工作场所的网络
3、随便设置一个名字
4、填入步骤一中设置的服务器的ip地址
5、在弹出的连接对话框中点击属性按钮,在打开的属性面板里选择网络选项卡,更改VPN类型为PPTP VPN
6、设置用户名密码开始连接的同时启动wireshark抓包
7、捕获到完整流量数据包
三、计算响应值
1、提取16字节挑战值
在wireshark中可以十分方便地从挑战包中获得16字节的挑战值
2、提取24字节响应值
在wireshark中发现响应报文的有效载荷是49字节的,而非24字节,查看rfc文档,可以发现这49字节是16字节对等挑战值+8字节空字符+24字节响应值+1字节保留Flag构成
3、程序模拟响应值计算
#!/bin/python3
# ResponseGen.py
import hashlib
from Crypto.Cipher import DES
def NtPasswordHash(password):
return hashlib.new("md4",password.encode("utf-16le")).digest()
def padding(key:bytes):
key=key.rjust(8,b"\x00")
for i in reversed(range(1,9)):
key=int.to_bytes(int.from_bytes(key[:i],"big")<<1,i,"big")+key[i:]
return key
def ChallengeHash(PeerChallenge,Challenge,UserName):
return hashlib.sha1(PeerChallenge+Challenge+username.encode()).digest()[:8]
def DesEncrypt(Clear,key:bytes):
return DES.new(padding(key),DES.MODE_ECB).encrypt(Clear)
def ChallengeResponse(Challenge,PasswordHash):
Response=b""
ZPasswordHash=PasswordHash.ljust(21,b"\x00")
for i in range(3):
Response+=DesEncrypt(Challenge,ZPasswordHash[7*i:7*(i+1)])
return Response
if __name__=="__main__":
ChallengeDump="b6eaed34f4f1e068cedfdb58bf7da53c"
ResponsePayloadDump="1e69bad543d752a6797662d9f19df0e70000000000000000eb5c83aa451c26ff219f4fc0caff7ff12a54aad9867ef68100"
AuthChallenge=bytes.fromhex(ChallengeDump)
ResponsePayload=bytes.fromhex(ResponsePayloadDump)
PeerChallenge=ResponsePayload[:16]
TrueResponse=ResponsePayload[24:48]
print(TrueResponse.hex())
username="PPTP"
password="123456"
PasswordHash=NtPasswordHash(password)
Challenge=ChallengeHash(PeerChallenge,AuthChallenge,username)
Response=ChallengeResponse(Challenge,PasswordHash)
print(Response.hex())
四、MPPE解密算法
1、MPPE协议
1.1 MPPE选项配置报文
MPPE选项配置报文位于PPP CCP报文Option字段中,总长48位(6字节),前8位表示协议类型,MPPE的值为18,中间8位定义了配置报文长度,MPPE的值为6字节,最后32位是支持的参数列表,其中有含义的6位用字母HMSLDC表示。
H表示stateless,为真则每个报文换一次密钥,否则256个报文换一次
M表示中等强度加密,为真则用56位密钥加密
S表示高强度加密,为真则使用128位密钥加密
L表示低强度加密,为真则使用4位密钥加密
C表示MPPC压缩,为真则启用MPPC压缩
D总是为0
1.2 MPPE密文
MPPE密文由PPP Compressed datagram 报文承载,其PPP报文格式如下
前2字节为PPP协议类型,PPP-comp的值为0x00fd,如果PPP LCP中协商了PFC(Protocol Field Compression),则可能被压缩为一字节的0xfd。
中间4位为设置位,A为是否立即更换密钥,如果1.1节中MPPE配置报文中H位的值为真,则该位一直为1,BC是MPPC的配置位,D为真表示该数据被MPPE加密了
后12位为序号,标识这是第几个包,到0xFFF之后重新从0开始,并且即使H=0也要更新密钥
之后的载荷便是真正MPPE加密了的数据
2、MPPE密钥生成算法
-
md4: 计算PasswordHash的MD4哈希,取前16位为 PasswordHashHash
-
GetMasterKey: 计算PasswordHashHash、NtResponse(24字节直接挑战响应)和Magic1的SHA1哈希,同样取前16位作为MasterKey
-
GetAsymmetricStartKey: 根据是否为服务端和发送端选择Magic2或Magic3和MasterKey、SHSPad1、SHSPad2一起进行SHA1哈希,根据密钥强度取前8位或16位得到MasterSessionKey(即该处RFC文档中的MasterSendKey或MasterReceiveKey)
-
GetNewKeyFromSHA: 用StartKey+SHSPad1+SessionKey+SHSPad2进行SHA1哈希,这里StartKey和SessionKey用的都是上一步的MasterSessionKey,根据密钥强度取前8位或16位得到新的SessionKey
5. rc4_key: 使用新的SessionKey作为密钥初始化RC4密钥流用以加密数据RFC3079中第5步写的是用新的SessionKey生成RC4流密钥,附录提供的sample也是直接用第5步生成的流密钥加密数据。但是通过抓包分析发现在实际的MPPE协议中,第4步生成的新的SessionKey要调用一次RFC3078中的MPPE密钥更新算法,更新后的SessionKey才能用作RC4密钥来加密数据。简直是迷惑行为
3、MPPE密钥更新算法
-
GetNewKeyFromSHA: 用StartKey+SHSPad1+SessionKey+SHSPad2进行SHA1哈希,根据密钥强度取前8位或16位得到InterimKey
RFC3078在此处说第一次调用的时候StartKey==SessionKey,这个第一次调用是指第一次调用GetNewKeyFromSHA,而不是第一次调用MPPE密钥更新算法,所以第一次调用是在RFC3079中MPPE密钥生成算法的第4步
-
RC4_key+RC4: 使用InterimKey作为RC4密钥加密InterimKey得到新的SessionKey
-
RC4_key: 使用新的SessionKey生成RC4密钥流用以加密数据
4、MPPE密码算法实现
#!/bin/python3
# MPPE.py
from ResponseGen import NtPasswordHash,ChallengeResponse
import hashlib
from Crypto.Cipher import ARC4
SHSpad1=b"\x00"*40
SHSpad2=b"\xf2"*40
Magic1=b"\x54\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x4D\x50\x50\x45\x20\x4D\x61\x73\x74\x65\x72\x20\x4B\x65\x79"
Magic2=b"\x4F\x6E\x20\x74\x68\x65\x20\x63\x6C\x69\x65\x6E\x74\x20\x73\x69\x64\x65\x2C\x20\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x73\x65\x6E\x64\x20\x6B\x65\x79\x3B\x20\x6F\x6E\x20\x74\x68\x65\x20\x73\x65\x72\x76\x65\x72\x20\x73\x69\x64\x65\x2C\x20\x69\x74\x20\x69\x73\x20\x74\x68\x65\x20\x72\x65\x63\x65\x69\x76\x65\x20\x6B\x65\x79\x2E"
Magic3=b"\x4F\x6E\x20\x74\x68\x65\x20\x63\x6C\x69\x65\x6E\x74\x20\x73\x69\x64\x65\x2C\x20\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68\x65\x20\x72\x65\x63\x65\x69\x76\x65\x20\x6B\x65\x79\x3B\x20\x6F\x6E\x20\x74\x68\x65\x20\x73\x65\x72\x76\x65\x72\x20\x73\x69\x64\x65\x2C\x20\x69\x74\x20\x69\x73\x20\x74\x68\x65\x20\x73\x65\x6E\x64\x20\x6B\x65\x79\x2E"
def GetMasterKey(PasswordHashHash,NtResponse):
return hashlib.sha1(PasswordHashHash+NtResponse+Magic1).digest()[:16]
def GetAsymmetricKey(MasterKey,SessionKeyLength,IsSend,IsServer):
s = Magic2 if IsSend ^ IsServer else Magic3
return hashlib.sha1(MasterKey+SHSpad1+s+SHSpad2).digest()[:SessionKeyLength]
def GetNewKeyFromSHA(StartKey,SessionKey,SessionKeyLength):
return hashlib.sha1(StartKey[:SessionKeyLength]+SHSpad1+SessionKey[:SessionKeyLength]+SHSpad2).digest()[:SessionKeyLength]
def GetSessionKey(password,NtResponse,IsSend,Type="M",Debug=False):
SessionKeyLength=8 if Type!="S" else 16
PasswordHashHash=hashlib.new("md4",NtPasswordHash(password)[:16]).digest()[:]
MasterKey=GetMasterKey(PasswordHashHash,NtResponse)
StartKey=GetAsymmetricKey(MasterKey,SessionKeyLength,IsSend,True)
SessionKeyPad={"L":b"\xd1\x26\x9e","M":b"\xd1","":b""}
SessionKeyIndex={"L":3,"M":1,"S":0}
SessionKey=SessionKeyPad[Type]+GetNewKeyFromSHA(StartKey,StartKey,SessionKeyLength)[SessionKeyIndex[Type]:]
if Debug:
print(PasswordHashHash.hex())
print(MasterKey.hex())
print(StartKey.hex())
print(SessionKey.hex())
return StartKey,SessionKey
def UpsateSessionKey(StartKey,SessionKey,Type="M"):
SessionKeyLength=8 if Type!="S" else 16
InterimKey=GetNewKeyFromSHA(StartKey,SessionKey,SessionKeyLength)
return MPPE_Encrypt(InterimKey,InterimKey)
def MPPE_Decrypt(Encrypted:bytes,SessionKey):
return ARC4.new(SessionKey).decrypt(Encrypted)
def MPPE_Encrypt(plainText:bytes,SessionKey):
return ARC4.new(SessionKey).encrypt(plainText)
if __name__ == "__main__":
UserName = "User"
Password = "clientPass"
NtResponse =bytes.fromhex("82 30 9E CD 8D 70 8B 5E A0 8F AA 39 81 CD 83 54 42 33 11 4A 3D 85 D6 DF")
StartKey,SessionKey=GetSessionKey(Password,NtResponse,True,Type="L",Debug=True)
print(MPPE_Encrypt("test message".encode(),SessionKey).hex())
41c00c584bd2d91c4017a2a12fa59f3f
fdece3717a8c838cb388e527ae3cdd31
8b7cdc149b993a1b
d1269ec49fa62e3e
929137917e5803d668d75898
5、MPPE算法解密测试
#!/bin/python3
from MPPE import *
username = "PPTP"
password = "123456"
NtResponse = bytes.fromhex("c517a6b0c33f026f3288e6d5287afc6ec7cdb2236ad2d652")
print(NtResponse.hex())
StartKey, SessionKey = GetSessionKey(password, NtResponse, False, Type="S", Debug=True)
enc = bytes.fromhex("90013e367452f9852e1835508e773c0d4e9e6f0138e330b294b37c3dbdb89be777fca662a048c1abfdeaeb586ab0df25ef62c28c045f7c177465fcdc611efdb8191b3b542732e741cc31ac3f18d14fe0fa84a0b40984addfd8ffb9a520359ba8bc76a3db31dde2f0a501648adc5c99d460584b24fd6b45e799d9a99eb58fab7e73a11bdb2fe832f0dbef70683ab5a17be689915271526026a8878cdcc54d030c33584be34c16ed00584a6f824ff5c25bef2f89819ab76abffba242db600d23447cd2e08d63ab9ce64b9b7ef22d88e3ba62f588980f65c941a3905ad8936f7673cdc29fdd61b50f12fd6ebdb3dcb31fc9029e46d90120c5d2c9b75e74ad12992543c88ac500192bff08e94fbc6f201dbe58d90c91bef499d81eef2e19ad3463db9ad6222233b8ffda8a2f0d84697b97080b977ee4f1d9b1d21dc823928161adb6d16a45950403cee9bfec587f")
n = enc[1]
for i in range(n+1):
SessionKey = UpdateSessionKey(StartKey, SessionKey, "S")
print(SessionKey.hex())
print(MPPE_Decrypt(enc[2:], SessionKey).hex())
dd97ea03890e253f93af68aabfee2334
8edcd8150dde97c96f45a7c9a1161cd6
9b76b9115d4a29c5f92e4ccf479c29c3
37ca6772a73d1db7111d4e4e3381eea9
0d129f23039d92bd80f13a827d90f7ee
17db84eb52375eb2490f7d6d7f5120a6
00214500014808de000080114a8ec0a82591ffffffff0044004301344e6c01080600e1a4944e06000000c0a8259100000000000000000000000000534500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501083d07080053450000000c0f71313037392d3762653131616330653c084d53465420352e303706062c2b01f90f2b03dc0100ff00000000000000
膜大佬!!!
yyds
请教一下大佬,MPPE密钥生成算法的第5步中,SessionKey更新一次才能用于加密数据这一部分,您是在哪里找到的呢?我也是参考RFC 3078和3079,尝试MS-CHAP v1的MPPE加解密没有成功。
因为https://datatracker.ietf.org/doc/html/rfc3078#section-7.2里面说了,每次加密传输前都要更新一次
虽然对于第一次传输来说这个session key已经是新的了,但是按照RFC的说法再更新一次好像也没什么问题2333
请教一下大佬,RC4生成的到底是SessionKey还是流密钥,懵了。。。
RC4不生成SessionKey,RC4是流加密算法,流密钥是RC4加密用的密钥啊2333
被作业迫害的孩子前来报到
被作业迫害的孩子前来报到
被作业迫害的孩子前来报到+1
被作业迫害的孩子前来报到被作业迫害的孩子前来报到